Coze源码分析-资源库-编辑提示词-前端源码
概述
本文深入分析Coze Studio中用户编辑提示词功能的前端实现。该功能允许用户在资源库中便捷地编辑和更新提示词资源,为开发者提供了完善的内容管理能力。通过对源码的详细解析,我们将了解从资源库表格操作到编辑弹窗的完整架构设计、组件实现、状态管理和用户体验优化等核心技术要点。编辑功能涉及权限验证、表单验证、内容编辑、API调用和状态更新等多个环节,确保数据完整性和操作的可靠性。
功能特性
核心功能
- 在线编辑:支持提示词资源的在线编辑和实时预览
- 权限控制:基于用户权限动态显示编辑按钮状态
- 表单验证:提供完善的输入验证机制确保数据完整性
- 富文本编辑:支持通过PromptEditorRender进行富文本编辑
- 状态同步:编辑保存后自动刷新资源列表
用户体验特性
- 即时反馈:编辑操作结果实时展示和Toast提示
- 权限提示:无权限时按钮禁用并提供视觉反馈
- 操作便捷:通过表格行操作菜单快速访问编辑功能
- 国际化支持:编辑相关文案支持多语言适配
技术架构
整体架构设计
┌─────────────────────────────────────────────────────────────┐
│ 提示词编辑管理模块 │
├─────────────────────────────────────────────────────────────┤
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────┐ │
│ │ LibraryPage │ │BaseLibrary │ │ TableAction │ │
│ │ (资源库页面) │ │ Page │ │ (操作菜单) │ │
│ └─────────────┘ └─────────────┘ └─────────────────────┘ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────┐ │
│ │ Table │ │UITableAction│ │PromptConfigurator │ │
│ │ (资源列表) │ │ (操作组件) │ │ Modal(编辑弹窗) │ │
│ └─────────────┘ └─────────────┘ └─────────────────────┘ │
├─────────────────────────────────────────────────────────────┤
│ 状态管理层 │
│ ┌─────────────────┐ ┌─────────────────────────────────┐ │
│ │usePromptConfig │ │ usePromptConfiguratorModal │ │
│ │ (编辑配置) │ │ (编辑弹窗管理) │ │
│ └─────────────────┘ └─────────────────────────────────┘ │
├─────────────────────────────────────────────────────────────┤
│ API服务层 │
│ ┌─────────────────────────────────────────────────────────┐
│ │ Playground API │
│ │ UpsertPromptResource & GetPromptResourceInfo │
│ └─────────────────────────────────────────────────────────┘
└────────────────────────────────────────────────────────────┘
核心模块结构
frontend/
├── apps/coze-studio/src/
│ └── pages/
│ └── library.tsx # 资源库入口页面
├── packages/studio/workspace/
│ ├── entry-adapter/src/pages/library/
│ │ └── index.tsx # LibraryPage适配器组件
│ └── entry-base/src/pages/library/
│ ├── index.tsx # BaseLibraryPage核心组件
│ ├── components/
│ │ └── library-header.tsx # LibraryHeader头部组件
│ └── hooks/use-entity-configs/
│ └── use-prompt-config.tsx # 提示词编辑配置Hook
├── packages/common/prompt-kit/
│ ├── base/src/
│ │ ├── create-prompt/
│ │ │ ├── prompt-configurator-modal.tsx # 提示词编辑弹窗核心组件
│ │ │ ├── context/
│ │ │ │ └── index.tsx # 提示词编辑上下文
│ │ │ ├── types.ts # TypeScript类型定义
│ │ │ ├── use-modal.tsx # 编辑弹窗Hook
│ │ │ ├── index.tsx # 模块导出文件
│ │ │ └── components/
│ │ │ ├── prompt-info-input.tsx # 名称描述输入组件
│ │ │ ├── header.tsx # 弹窗头部组件
│ │ │ └── footer-actions/ # 底部操作按钮目录
│ │ │ ├── close-modal.tsx # 关闭按钮组件
│ │ │ ├── save-prompt.tsx # 保存按钮组件
│ │ │ └── prompt-diff.tsx # 对比按钮组件
│ │ └── editor/
│ │ ├── index.tsx # 编辑器模块导出
│ │ ├── render.tsx # PromptEditorRender组件
│ │ └── context/
│ │ └── index.tsx # 编辑器上下文
│ └── adapter/src/ # 适配器层
│ └── create-prompt/
│ └── prompt-configurator-modal.tsx # 适配器弹窗组件
└── packages/arch/bot-api/src/├── playground-api.ts # PlaygroundApi实现└── axios.ts # HTTP请求配置
用户编辑提示词流程概述
用户登录Coze Studio↓点击"资源库"菜单↓LibraryPage 组件加载↓BaseLibraryPage 渲染资源列表↓用户找到要编辑的提示词行↓点击表格行最右边的"..."操作按钮↓TableAction 下拉菜单显示↓点击"编辑"菜单项↓权限验证(检查ActionKey.Edit权限)↓openCreatePrompt() 函数触发↓PromptConfiguratorModal 编辑弹窗显示↓用户编辑提示词内容↓表单验证(名称、描述等字段)↓PlaygroundApi.UpsertPromptResource() 调用↓后端执行更新操作↓编辑成功回调处理↓reloadList() 刷新资源列表↓Toast.success() 显示编辑成功提示
该流程包含多层验证和处理:
- 权限验证:通过
ActionKey.Edit
检查用户是否有编辑权限,确保只有授权用户可以修改提示词 - 表单验证:使用Form组件确保提示词名称、描述等必填字段的完整性和格式正确性
- API调用:使用
PlaygroundApi.UpsertPromptResource()
安全更新资源,支持创建和更新操作 - 状态同步:编辑成功后通过
reloadList()
自动刷新列表,保持前端数据与后端一致性 - 用户反馈:通过Toast组件提示用户操作结果,包括成功、失败和加载状态
- 错误处理:API调用失败时提供详细的错误提示,帮助用户理解问题原因
- 数据缓存:利用React Query等缓存机制优化数据加载性能
- 表单状态管理:通过useForm Hook管理表单状态,支持字段验证和重置功能
整个流程确保了提示词编辑的安全性、可靠性和用户体验的友好性。
核心组件实现
提示词编辑功能涉及多个层次的组件:
- LibraryPage组件:资源库主页面,整合各种资源配置
- BaseLibraryPage组件:资源库核心逻辑,渲染资源列表
- Table组件:资源列表表格,包含操作列
- TableAction组件:表格行操作菜单,包含编辑选项
- usePromptConfig Hook:提示词配置逻辑,包含编辑功能
- PromptConfiguratorModal组件:提示词编辑弹窗,核心编辑界面
1. 资源库入口组件(LibraryPage)
文件位置:frontend/packages/studio/workspace/entry-adapter/src/pages/library/index.tsx
作为资源库的适配器组件,整合各种资源配置,包括提示词的编辑功能:
import { type FC, useRef } from 'react';import {BaseLibraryPage,useDatabaseConfig,usePluginConfig,useWorkflowConfig,usePromptConfig,useKnowledgeConfig,
} from '@coze-studio/workspace-base/library';export const LibraryPage: FC<{ spaceId: string }> = ({ spaceId }) => {const basePageRef = useRef<{ reloadList: () => void }>(null);const configCommonParams = {spaceId,reloadList: () => {basePageRef.current?.reloadList();},};// 各种资源配置,包括提示词编辑配置const { config: promptConfig, modals: promptModals } =usePromptConfig(configCommonParams);// 其他资源配置...return (<><BaseLibraryPagespaceId={spaceId}ref={basePageRef}entityConfigs={[promptConfig, // 包含编辑配置// 其他配置...]}/>{promptModals}{/* 其他模态框... */}
设计亮点:
- 配置统一管理:通过
usePromptConfig
统一管理提示词的编辑配置 - 组件解耦:编辑功能通过配置传递,组件职责明确
- 状态同步:编辑操作后通过
reloadList
自动刷新列表
2. 资源库核心组件(BaseLibraryPage)
文件位置:frontend/packages/studio/workspace/entry-base/src/pages/library/index.tsx
负责资源库的核心展示逻辑,包含资源列表表格和编辑操作:
import { forwardRef, useImperativeHandle } from 'react';import classNames from 'classnames';
import { useInfiniteScroll } from 'ahooks';
import { I18n } from '@coze-arch/i18n';
import {Table,Select,Search,Layout,Cascader,Space,
} from '@coze-arch/coze-design';
import { renderHtmlTitle } from '@coze-arch/bot-utils';
import { EVENT_NAMES, sendTeaEvent } from '@coze-arch/bot-tea';
import {type ResType,type LibraryResourceListRequest,type ResourceInfo,
} from '@coze-arch/bot-api/plugin_develop';
import { PluginDevelopApi } from '@coze-arch/bot-api';import { type ListData, type BaseLibraryPageProps } from './types';
import { LibraryHeader } from './components/library-header';export const BaseLibraryPage = forwardRef<{ reloadList: () => void },BaseLibraryPageProps
>(({ spaceId, isPersonalSpace = true, entityConfigs }, ref) => {const { params, setParams, resetParams, hasFilter, ready } =useCachedQueryParams({spaceId,});const listResp = useInfiniteScroll<ListData>(async prev => {if (!ready) {return {list: [],nextCursorId: undefined,hasMore: false,};}const resp = await PluginDevelopApi.LibraryResourceList(entityConfigs.reduce<LibraryResourceListRequest>((res, config) => config.parseParams?.(res) ?? res,{...params,cursor: prev?.nextCursorId,space_id: spaceId,size: LIBRARY_PAGE_SIZE,},),);return {list: resp?.resource_list || [],nextCursorId: resp?.cursor,hasMore: !!resp?.has_more,};},{reloadDeps: [params, spaceId],},);useImperativeHandle(ref, () => ({reloadList: listResp.reload,}));return (<LayoutclassName={s['layout-content']}title={renderHtmlTitle(I18n.t('navigation_workspace_library'))}><Layout.Header className={classNames(s['layout-header'], 'pb-0')}><div className="w-full"><LibraryHeader entityConfigs={entityConfigs} />{/* 过滤器组件 */}</div></Layout.Header><Layout.Content>{/* 表格和列表内容 */}</Layout.Content></Layout>);}
);
3. 表格操作组件(TableAction)
文件位置:@coze-arch/coze-design
包中的 Table.TableAction
组件
提供表格行的操作菜单,包含编辑功能。在 usePromptConfig
中的实际实现:
import { Table } from '@coze-arch/coze-design';
import { ActionKey, type ResourceInfo } from '@coze-arch/idl/plugin_develop';
import { I18n } from '@coze-arch/i18n';const { TableAction } = Table;// 在 usePromptConfig hook 中的 renderActions 实现
renderActions: (libraryResource: ResourceInfo) => (<TableAction// 删除操作配置deleteProps={{// 基于后端返回的权限控制删除按钮状态disabled: !libraryResource.actions?.find(action => action.key === ActionKey.Delete,)?.enable,deleteDesc: I18n.t('prompt_resource_delete_describ'), // 删除确认描述handler: () => {delPrompt(libraryResource.res_id || ''); // 调用删除函数},}}// 编辑操作配置editProps={{// 基于后端返回的权限控制编辑按钮状态disabled: !libraryResource.actions?.find(action => action.key === ActionKey.Edit,)?.enable,handler: () => {// 打开编辑弹窗,传入编辑模式和资源IDopenCreatePrompt({mode: 'edit',editId: libraryResource.res_id || '',});},}}// 其他通用操作(如复制等)actionList={getCommonActions?.(libraryResource)}/>
)
编辑提示词逻辑
编辑提示词功能是Coze平台资源库的核心功能之一,允许用户修改已创建的提示词内容。整个编辑流程从资源库表格的操作按钮开始,通过模态框进行编辑,最终保存更新。
1. 编辑操作入口 - usePromptConfig Hook
编辑操作的入口位于 usePromptConfig
hook 中的 renderActions
方法,该方法为每个提示词行渲染操作按钮。
文件位置:frontend/packages/studio/workspace/entry-base/src/pages/library/hooks/use-entity-configs/use-prompt-config.tsx
import { useNavigate } from 'react-router-dom';
import { useRef } from 'react';
import { useRequest } from 'ahooks';
import {ActionKey,ResType,type ResourceInfo,
} from '@coze-arch/idl/plugin_develop';
import { I18n } from '@coze-arch/i18n';
import { Table, Toast } from '@coze-arch/coze-design';
import { PlaygroundApi } from '@coze-arch/bot-api';
import { usePromptConfiguratorModal } from '@coze-common/prompt-kit-adapter/create-prompt';const { TableAction } = Table;export const usePromptConfig: UseEntityConfigHook = ({spaceId,isPersonalSpace = true,reloadList,getCommonActions,
}) => {// 初始化编辑弹窗const { open: openCreatePrompt, node: promptConfiguratorModal } =usePromptConfiguratorModal({spaceId,source: 'resource_library', // 来源标识enableDiff: FLAGS['bot.studio.prompt_diff'], // 是否启用对比功能onUpdateSuccess: reloadList, // 编辑成功后刷新列表onDiff: ({ libraryId }) => {// 对比功能回调recordRef.current = { res_id: libraryId };openSelectIntelligenceModal();},});// 删除提示词的请求处理const { run: delPrompt } = useRequest((promptId: string) =>PlaygroundApi.DeletePromptResource({prompt_resource_id: promptId,}),{manual: true, // 手动触发onSuccess: () => {reloadList(); // 删除成功后刷新列表Toast.success(I18n.t('Delete_success'));},},);return {config: {// 渲染表格操作列,包含编辑和删除功能renderActions: (libraryResource: ResourceInfo) => (<TableAction// 删除操作配置deleteProps={{// 基于后端返回的权限控制删除按钮状态disabled: !libraryResource.actions?.find(action => action.key === ActionKey.Delete,)?.enable,deleteDesc: I18n.t('prompt_resource_delete_describ'), // 删除确认描述handler: () => {delPrompt(libraryResource.res_id || ''); // 调用删除函数},}}// 编辑操作配置 - 核心功能editProps={{// 根据后端返回的权限控制编辑按钮状态disabled: !libraryResource.actions?.find(action => action.key === ActionKey.Edit,)?.enable,// 编辑处理函数handler: () => {// 打开编辑弹窗,传入编辑模式和资源IDopenCreatePrompt({mode: 'edit',editId: libraryResource.res_id || '',});},}}// 其他通用操作(如复制等)actionList={getCommonActions?.(libraryResource)}/>),},};
};
核心特性:
- 权限控制:基于后端返回的
actions
数组动态控制编辑按钮的可用状态 - 模式切换:通过
mode: 'edit'
参数明确指定为编辑模式 - 资源定位:通过
editId
传递要编辑的提示词资源ID - 统一操作:编辑、删除等操作集成在同一个
TableAction
组件中
2. 编辑弹窗核心组件 - PromptConfiguratorModal
PromptConfiguratorModal
是编辑提示词的核心弹窗组件,负责处理编辑模式下的数据加载、表单验证和保存逻辑。
文件位置:frontend/packages/common/prompt-kit/base/src/create-prompt/prompt-configurator-modal.tsx
2.1 组件结构与状态管理
import { type FC, useRef, useState, useEffect, Suspense, lazy } from 'react';
import { useEditor } from '@coze-common/editor';
import { Modal, Form, Toast } from '@coze-arch/coze-design';
import { I18n } from '@coze-arch/i18n';
import { PlaygroundApi } from '@coze-arch/bot-api';
import { PromptEditorRender } from '@/editor';// 提示词表单数据接口
interface PromptValues {id?: string;name: string;description: string;prompt_text?: string;
}// 组件主体实现
export const PromptConfiguratorModal = (props: PromptConfiguratorModalProps,
) => {const {mode,editId,spaceId,botId,projectId,workflowId,canEdit,onUpdateSuccess,enableDiff,onDiff,defaultPrompt,source,containerAppendSlot,} = props;// 表单API引用,用于表单验证和数据获取const formApiRef = useRef<FormApi | null>(null);// 编辑器实例,用于内容编辑const editor = useEditor<EditorAPI>();// 模态框模式状态:info(查看)、edit(编辑)、create(创建)const [modalMode, setModalMode] = useState<'info' | 'edit' | 'create'>(mode);// 错误信息状态const [errMsg, setErrMsg] = useState('');// 提交状态引用const isSubmiting = useRef(false);// 表单数据状态const [formValues, setFormValues] = useState<PromptValues>({name: '',description: '',prompt_text: '',});// 是否为只读模式const isReadOnly = modalMode === 'info';
2.2 编辑模式数据加载逻辑
编辑模式下,组件会自动加载现有提示词数据并填充到表单中:
// 默认提示词内容加载(用于创建模式的预设内容)
useEffect(() => {if (!defaultPrompt || !editor) {return;}// 将默认内容插入到编辑器中editor?.$view.dispatch({changes: {from: 0,to: editor.$view.state.doc.length,insert: defaultPrompt,},});
}, [defaultPrompt, editor]);// 编辑模式下的数据加载逻辑
useEffect(() => {if (!editId || !editor) {return;}// 调用API获取提示词详细信息PlaygroundApi.GetPromptResourceInfo({prompt_resource_id: editId,}).then(({ data: { name = '', description = '', prompt_text = '' } = {} }) => {// 设置表单字段值formApiRef.current?.setValues({prompt_text,name,description,});// 更新编辑器内容editor?.$view.dispatch({changes: {from: 0,to: editor.$view.state.doc.length,insert: prompt_text,},});// 更新本地状态setFormValues({name,description,prompt_text,});},);
}, [editId, modalMode, editor]);
2.3 表单提交与保存逻辑
// 信息模式下的复制操作
const handleInfoModeAction = () => {const promptText = editor?.getValue();navigator.clipboard.writeText(promptText ?? '');Toast.success(I18n.t('prompt_library_prompt_copied_successfully'));
};// 编辑/创建模式下的保存操作
const handleUpdateModeAction = async (e: React.MouseEvent<Element, MouseEvent>,
) => {try {// 表单验证const submitValues = await formApiRef.current?.validate();if (!submitValues) {return;}// 调用API保存提示词const res = await PlaygroundApi.UpsertPromptResource({prompt: {...submitValues,space_id: spaceId,// 编辑模式下需要传入ID...(modalMode === 'edit' && { id: editId }),},},{__disableErrorToast: true, // 禁用默认错误提示},);// 关闭弹窗props.onCancel?.(e);// 获取资源ID(编辑模式使用editId,创建模式使用返回的ID)const id = modalMode === 'edit' ? editId : res?.data?.id;// 显示成功提示if (mode === 'create') {Toast.success(I18n.t('prompt_library_prompt_creat_successfully'));}// 触发成功回调(通常用于刷新列表)onUpdateSuccess?.(mode, id);if (!id) {return;}return {mode,id,};} catch (error) {// 设置错误信息,在UI中显示setErrMsg((error as Error).message);}
};// 统一的提交处理函数
const handleSubmit = modalMode === 'info' ? handleInfoModeAction : handleUpdateModeAction;
2.4 组件渲染结构
return (<PromptConfiguratorProvidervalue={{props,formApiRef,isReadOnly,}}><Modaltitle={<PromptHeadercanEdit={!!canEdit}mode={modalMode}onEditIconClick={() => {setModalMode('edit'); // 切换到编辑模式}}/>}closeOnEsc={false} // 禁用ESC关闭maskClosable={false} // 禁用点击遮罩关闭visiblewidth="640px"footer={<div className="flex items-center justify-end">{enableDiff ? (// 对比功能按钮(可选)<PromptDiffspaceId={spaceId}botId={botId}projectId={projectId}workflowId={workflowId}source={source}mode={modalMode}editor={editor}submitFun={handleSubmit}editId={editId}onDiff={({ prompt, libraryId }) => {onDiff?.({ prompt, libraryId });}}onCancel={e => {props.onCancel?.(e);}}/>) : (// 关闭按钮<CloseModal onCancel={props.onCancel} />)}{/* 保存/复制按钮 */}<SavePromptmode={modalMode}isSubmitting={isSubmiting.current}onSubmit={handleSubmit}/></div>}onCancel={props.onCancel}className={styles['prompt-configurator-modal']}><div className="flex flex-col gap-4"><div>{/* 表单内容 */}<Form<PromptValues>getFormApi={formApi => {formApiRef.current = formApi;}}>{/* 提示词名称输入 */}<PromptInfoInputdisabled={modalMode === 'info'}label={I18n.t('creat_new_prompt_prompt_name')}placeholder={I18n.t('creat_new_prompt_name_placeholder')}maxLength={MAX_NAME_LENGTH}maxCount={MAX_NAME_LENGTH}initCount={formValues.name.length}rows={NAME_ROW_LENGTH}rules={[{required: !isReadOnly,message: I18n.t('creat_new_prompt_name_placeholder'),},]}field="name"/>{/* 提示词描述输入 */}<PromptInfoInputdisabled={modalMode === 'info'}label={I18n.t('creat_new_prompt_prompt_description')}placeholder={I18n.t('creat_new_prompt_des_placeholder')}maxLength={MAX_DESCRIPTION_LENGTH}maxCount={MAX_DESCRIPTION_LENGTH}initCount={formValues.description.length}rows={DESCRIPTION_ROW_LENGTH}field="description"/>{/* 提示词内容编辑器 */}<div className="flex flex-col gap-1"><div className="flex justify-between items-center"><Form.Labeltext={I18n.t('creat_new_prompt_prompt')}className="mb-0"/>{headerActions}</div><div className="rounded-lg border border-solid coz-stroke-plus h-[400px] overflow-y-auto styled-scrollbar hover-show-scrollbar">{/* 富文本编辑器 */}<PromptEditorRenderreadonly={modalMode === 'info'}options={{minHeight: 300,}}onChange={value => {formApiRef.current?.setValue('prompt_text', value);}}/>{/* 编辑器插件和扩展 */}<InputSlotWidgetmode="configurable"onSelectionInInputSlot={selection => {selectionInInputSlotRef.current = !!selection;}}/><LibraryBlockWidgetlibrarys={EMPTY_LIBRARY}readonlyspaceId={spaceId}/>{/* 其他编辑器扩展... */}</div></div></Form>{/* 错误信息显示 */}{errMsg ? (<div className="text-red"><Suspense fallback={null}><ReactMarkdown skipHtml={true} linkTarget="_blank">{errMsg}</ReactMarkdown></Suspense></div>) : null}</div></div></Modal>{containerAppendSlot}</PromptConfiguratorProvider>
);
核心特性:
- 模式复用:同一组件支持创建、编辑、查看三种模式
- 自动加载:编辑模式下自动加载并填充现有数据
- 表单验证:完整的前端表单验证机制
- 富文本编辑:集成专业的提示词编辑器
- 错误处理:完善的异常处理和用户提示
- 状态管理:提交状态的实时反馈
3. 表格操作组件 - UITableAction
UITableAction
是资源库表格中"…"操作按钮的核心实现,负责渲染编辑、复制、删除等操作按钮。
文件位置:frontend/packages/components/bot-semi/src/components/ui-table-action/index.tsx
3.1 组件接口定义
export interface ActionItemProps {disabled?: boolean; // 按钮禁用状态handler?: (() => void) | (() => Promise<void>); // 点击处理函数handleClick?: () => void; // 额外点击处理hide?: boolean; // 是否隐藏按钮popconfirm?: PopconfirmProps; // 确认弹窗配置tooltip?: TooltipProps; // 提示信息配置
}export interface UITableActionProps {editProps?: ActionItemProps; // 编辑操作配置copyProps?: ActionItemProps; // 复制操作配置deleteProps: ActionItemProps; // 删除操作配置(必需)
}
3.2 编辑按钮渲染逻辑
编辑按钮是操作列表中的核心功能,优先显示且无需二次确认:
{editProps && !editProps.hide ? (<Tooltipspacing={12}content={i18n.t('Edit')} // 国际化编辑提示position="top"{...editProps?.tooltip}><span className={styles['action-btn']}><UIIconButtondisabled={editProps?.disabled} // 权限控制icon={<IconEdit className={styles.icon} />} // 编辑图标onClick={editProps?.handler} // 直接触发编辑style={iconColor('edit')} // 动态样式data-testid="ui.table-action.edit" // 测试标识/></span></Tooltip>
) : null}
3.3 事件处理与样式控制
const handle = (e: { stopPropagation: () => void }) => {e.stopPropagation(); // 阻止事件冒泡,避免触发行点击
};// 动态图标颜色控制
const iconColor = useCallback((type: string) => {const targetProps = props[`${type}Props` as keyof UITableActionProps];return {color: targetProps?.disabled? 'rgba(136, 138, 142, 0.5)' // 禁用状态:半透明灰色: 'rgba(136, 138, 142, 1)', // 正常状态:深灰色};},[editProps, deleteProps, copyProps],
);
3.4 完整组件结构
return (<div className={styles['ui-action-content']} onClick={handle}>{/* 复制按钮(可选) */}{copyProps && !copyProps.hide ? (<Tooltip content={i18n.t('Copy')} position="top"><UIIconButtonicon={<IconCopy />}onClick={copyProps?.handler}disabled={copyProps?.disabled}/></Tooltip>) : null}{/* 编辑按钮(核心功能) */}{editProps && !editProps.hide ? (<Tooltip content={i18n.t('Edit')} position="top"><UIIconButtonicon={<IconEdit />}onClick={editProps?.handler} // 直接触发,无确认弹窗disabled={editProps?.disabled}/></Tooltip>) : null}{/* 删除按钮(带确认弹窗) */}{!deleteProps.hide && (<Popconfirmtitle={i18n.t('delete_title')}content={i18n.t('delete_desc')}onConfirm={deleteProps?.handler} // 确认后执行删除disabled={deleteProps.disabled}><Tooltip content={i18n.t('Delete')} position="top"><UIIconButtonicon={<IconDeleteOutline />}disabled={deleteProps.disabled}/></Tooltip></Popconfirm>)}</div>
);
设计特点:
- 操作优先级:编辑操作位于中间位置,便于用户快速访问
- 交互差异:编辑直接触发,删除需要确认,符合用户习惯
- 权限集成:所有操作都支持基于权限的禁用控制
- 视觉反馈:禁用状态有明显的视觉区分
- 事件隔离:阻止操作按钮事件冒泡到表格行
4. 编辑功能集成 - usePromptConfig Hook
usePromptConfig
是资源库页面中提示词相关功能的核心Hook,负责集成编辑弹窗、权限控制、表格配置等功能。
文件位置:frontend/packages/studio/workspace/entry-base/src/pages/library/hooks/use-entity-configs/use-prompt-config.tsx
4.1 Hook依赖与初始化
import { useNavigate } from 'react-router-dom';
import { useRef } from 'react';
import { useRequest } from 'ahooks';
import { ActionKey, ResType, type ResourceInfo } from '@coze-arch/idl/plugin_develop';
import { usePromptConfiguratorModal } from '@coze-common/prompt-kit/main';
import { PlaygroundApi } from '@coze-arch/bot-api';
import { Table, Menu, Toast } from '@coze-arch/coze-design';const { TableAction } = Table;export const usePromptConfig: UseEntityConfigHook = ({spaceId, // 工作空间IDisPersonalSpace, // 是否个人空间reloadList, // 列表刷新函数getCommonActions, // 通用操作获取函数
}) => {const navigate = useNavigate();const [FLAGS] = useFlags();const recordRef = useRef<ResourceInfo | null>(null);const { open: openSelectIntelligenceModal, node: selectIntelligenceModal } =useSelectIntelligenceModal({spaceId,onSelect: (intelligence: IntelligenceData) => {const targetId = intelligence.basic_info?.id;const diffPromptResourceId = recordRef.current?.res_id;navigate(`/space/${spaceId}/bot/${targetId}`, {replace: true,state: {mode: 'diff',diffPromptResourceId,targetId,},});sendTeaEvent(EVENT_NAMES.compare_mode_front, {bot_id: targetId,compare_type: 'prompts',from: 'prompt_resource',source: 'bot_detail_page',action: 'start',});},});const { open: openCreatePrompt, node: promptConfiguratorModal } =usePromptConfiguratorModal({spaceId,source: 'resource_library',enableDiff: FLAGS['bot.studio.prompt_diff'],onUpdateSuccess: reloadList,onDiff: ({ libraryId }) => {recordRef.current = {res_id: libraryId,};openSelectIntelligenceModal();},});// 编辑提示词的核心逻辑通过 usePromptConfiguratorModal 实现const { open: openCreatePrompt, node: promptConfiguratorModal } =usePromptConfiguratorModal({spaceId,source: 'resource_library',enableDiff: FLAGS['bot.studio.prompt_diff'],onUpdateSuccess: reloadList, // 编辑成功后刷新列表onDiff: ({ libraryId }) => {recordRef.current = {res_id: libraryId,};openSelectIntelligenceModal();},});// 删除提示词的核心逻辑(保留用于删除操作)const { run: delPrompt } = useRequest((promptId: string) =>PlaygroundApi.DeletePromptResource({prompt_resource_id: promptId,}),{manual: true,onSuccess: () => {reloadList(); // 删除成功后刷新列表Toast.success(I18n.t('Delete_success')); // 显示成功提示},},);return {modals: (<>{selectIntelligenceModal}{promptConfiguratorModal}</>),config: {typeFilter: {label: I18n.t('library_resource_type_prompt'),value: ResType.Prompt,},renderCreateMenu: () => (<Menu.Itemdata-testid="workspace.library.header.create.prompt"icon={<IconCozLightbulb />}onClick={() => {sendTeaEvent(EVENT_NAMES.widget_create_click, {source: 'menu_bar',workspace_type: isPersonalSpace? 'personal_workspace': 'team_workspace',});openCreatePrompt({mode: 'create',});}}>{I18n.t('creat_new_prompt_prompt')}</Menu.Item>),target: [ResType.Prompt],onItemClick: (record: ResourceInfo) => {recordRef.current = record;const canEdit = record.actions?.find(action => action.key === ActionKey.Edit,)?.enable;openCreatePrompt({mode: 'info',canEdit,editId: record.res_id || '',});},// 渲染表格操作列,包含编辑和删除功能renderActions: (libraryResource: ResourceInfo) => (<TableActiondeleteProps={{// 根据权限控制删除按钮状态disabled: !libraryResource.actions?.find(action => action.key === ActionKey.Delete,)?.enable,// 删除确认描述deleteDesc: I18n.t('prompt_resource_delete_describ'),// 删除处理函数handler: () => {delPrompt(libraryResource.res_id || '');},}}// 编辑操作 - 核心功能editProps={{disabled: !libraryResource.actions?.find(action => action.key === ActionKey.Edit,)?.enable,handler: () => {// 打开编辑弹窗,传入编辑模式和资源IDopenCreatePrompt({mode: 'edit',editId: libraryResource.res_id || '',});},}}actionList={getCommonActions?.(libraryResource)}/>),},};
};
编辑提示词-API接口实现
bot-api/package.json
文件位置:frontend/packages/arch/bot-api/package.json
核心代码:
{"name": "@coze-arch/bot-api","version": "0.0.1","description": "RPC wrapper for bot studio application","author": "fanwenjie.fe@bytedance.com","exports": {".": "./src/index.ts",},
}
代码作用:
- 1.包定义 :定义了一个名为 @coze-arch/bot-api 的 npm 包,版本为 0.0.1,这是一个用于 bot studio 应用的 RPC 包装器。
- 2.通过主入口文件 :
在frontend\packages\arch\bot-api\src\index.ts
中, PlaygroundApi 被导出:
export { PlaygroundApi } from './playground-api';
这允许通过 @coze-arch/bot-api 直接导入 PlaygroundApi 。
3.PlaygroundApi 实现 :在 src/playground-api.ts 中, PlaygroundApi 是一个配置好的服务实例,它使用了 PlaygroundService 和 axios 请求配置。
src/playground-api.ts
文件位置:frontend\packages\arch\bot-api\src\playground-api.ts
核心代码:
import PlaygroundApiService from './idl/playground_api';
import { axiosInstance, type BotAPIRequestConfig } from './axios';// eslint-disable-next-line @typescript-eslint/naming-convention
export const PlaygroundApi = new PlaygroundApiService<BotAPIRequestConfig>({request: (params, config = {}) => {config.headers = Object.assign(config.headers || {}, {'Agw-Js-Conv': 'str',});return axiosInstance.request({ ...params, ...config });},
});
实现特点:
- 自动生成:基于IDL文件自动生成的API客户端
- 类型安全:完整的TypeScript类型支持
- 统一配置:使用共享的axios实例和请求配置
- 方法导出:直接导出常用的API方法便于调用
- 请求头设置:自动添加必要的请求头信息
axiosInstance说明
1.axiosInstance 在整个项目中是全局共享的
2.bot-api 包中的导入 ( frontend/packages/arch/bot-api/src/axios.ts )
是直接从 @coze-arch/bot-http 包导入了 axiosInstance 。
import {axiosInstance,isApiError,type AxiosRequestConfig,
} from '@coze-arch/bot-http';
3.bot-http 包中的定义 ( frontend/packages/arch/bot-http/src/axios.ts ):
export const axiosInstance = axios.create();
这里创建了一个全局的 axios 实例,与用户名修改保存请求的 axios 实例是同一个。
PlaygroundApiService说明
1.bot-api包中的导入路径:
import PlaygroundApiService from ‘./idl/playground_api’;
实际指向
frontend/packages/arch/bot-api/src/idl/playground_api.ts
文件内容重新导出了 @coze-arch/idl/playground_api 包的所有内容,包括默认导出
export * from '@coze-arch/idl/playground_api';
export { default as default } from '@coze-arch/idl/playground_api';
2.idl包的模块映射
文件位置:frontend/packages/arch/idl/package.json
核心代码:
"name": "@coze-arch/idl","version": "0.0.1","description": "IDL files for bot studio application","author": "fanwenjie.fe@bytedance.com","exports": {"./playground_api": "./src/auto-generated/playground_api/index.ts",
代码作用:将 @coze-arch/idl/playground_api 映射到实际文件路径frontend/packages/arch/idl/src/auto-generated/playground_api/index.ts
这个文件说明后续见 删除提示词-API接口实现 这个章节。
5. 编辑操作的完整流程
编辑提示词的完整流程如下:
API层设计与实现
IDL基础类型定义(base.thrift)
文件位置:idl/base.thrift
核心代码:
namespace py base
namespace go base
namespace java com.bytedance.thrift.basestruct TrafficEnv {1: bool Open = false,2: string Env = "" ,
}struct Base {1: string LogID = "",2: string Caller = "",3: string Addr = "",4: string Client = "",5: optional TrafficEnv TrafficEnv ,6: optional map<string,string> Extra ,
}struct BaseResp {1: string StatusMessage = "",2: i32 StatusCode = 0 ,3: optional map<string,string> Extra ,
}
文件作用:
定义了项目中所有接口的基础数据结构,作为其他IDL文件的依赖基础。
编辑提示词-IDL结构体定义(playground_api.thrift)
文件路径:idl\playground\playground_api.thrift
核心代码:
namespace go playground
include "../base.thrift"// 提示词资源结构
struct PromptResource {1: optional string id (api.body="id")2: optional string space_id (api.body="space_id")3: optional string name (api.body="name")4: optional string description (api.body="description")5: optional string prompt_text (api.body="prompt_text")6: optional string owner_id (api.body="owner_id")7: optional string created_at (api.body="created_at")8: optional string updated_at (api.body="updated_at")
}// 创建/更新提示词请求结构
struct UpsertPromptResourceRequest {1: required PromptResource prompt (api.body="prompt")255: base.Base Base (api.none="true")
}// 创建/更新提示词响应结构
struct UpsertPromptResourceResponse {1: optional PromptResourceData data (api.body="data")253: required i64 code254: required string msg255: required base.BaseResp BaseResp
}// 提示词响应数据结构
struct PromptResourceData {1: optional string id (api.body="id")2: optional string name (api.body="name")3: optional string description (api.body="description")4: optional string prompt_text (api.body="prompt_text")
}// 获取提示词详情请求结构
struct GetPromptResourceInfoRequest {1: required string prompt_resource_id (api.path="prompt_resource_id")255: base.Base Base (api.none="true")
}// 获取提示词详情响应结构
struct GetPromptResourceInfoResponse {1: optional PromptResourceData data (api.body="data")253: required i64 code254: required string msg255: required base.BaseResp BaseResp
}
设计亮点:
- 统一接口设计:UpsertPromptResource支持创建和编辑两种模式
- 数据完整性:包含提示词的完整信息结构
- RESTful规范:遵循REST API设计原则
- 时间追踪:自动记录创建和更新时间
- 权限控制:通过owner_id实现资源权限管理
编辑提示词-IDL接口定义(playground_service.thrift)
文件路径:idl\playground\playground_service.thrift
核心代码:
include "../base.thrift"
include "playground_api.thrift"namespace go playgroundservice PlaygroundService {// 获取提示词详情playground_api.GetPromptResourceInfoResponse GetPromptResourceInfo(1: playground_api.GetPromptResourceInfoRequest request)(api.get='/api/playground_api/prompt_resource/{prompt_resource_id}', api.category="prompt_resource", agw.preserve_base="true")// 创建或更新提示词资源playground_api.UpsertPromptResourceResponse UpsertPromptResource(1: playground_api.UpsertPromptResourceRequest request)(api.post='/api/playground_api/prompt_resource', api.category="prompt_resource", agw.preserve_base="true")// 获取提示词列表playground_api.ListPromptResourceResponse ListPromptResource(1: playground_api.ListPromptResourceRequest request)(api.get='/api/playground_api/prompt_resources', api.category="prompt_resource", agw.preserve_base="true")
}
接口设计说明:
- GetPromptResourceInfo:获取提示词详细信息,用于编辑时填充表单
- UpsertPromptResource:执行创建或更新操作,统一的增量接口
- ListPromptResource:获取提示词资源列表,支持分页和过滤
- RESTful设计:遵循REST API设计规范,GET用于查询,POST用于创建/更新
编辑提示词-API接口实现(playground_api/index.ts)
文件位置:frontend/packages/arch/idl/src/auto-generated/playground_api/index.ts
核心代码:
export default class PlaygroundApiService<T> {private request: any = () => {throw new Error('PlaygroundApiService.request is undefined');};private baseURL: string | ((path: string) => string) = '';constructor(options?: {baseURL?: string | ((path: string) => string);request?<R>(params: {url: string;method: 'GET' | 'DELETE' | 'POST' | 'PUT' | 'PATCH';data?: any;params?: any;headers?: any;},options?: T,): Promise<R>;}) {this.request = options?.request || this.request;this.baseURL = options?.baseURL || '';}/** POST /api/playground_api/prompt_resource */UpsertPromptResource(req: playground_api.UpsertPromptResourceRequest,options?: T,): Promise<playground_api.UpsertPromptResourceResponse> {const _req = req;const url = this.genBaseURL('/api/playground_api/prompt_resource');const method = 'POST';const data = _req.prompt;return this.request({ url, method, data }, options);}/** GET /api/playground_api/prompt_resource/{prompt_resource_id} */GetPromptResourceInfo(req: playground_api.GetPromptResourceInfoRequest,options?: T,): Promise<playground_api.GetPromptResourceInfoResponse> {const _req = req;const url = this.genBaseURL(`/api/playground_api/prompt_resource/${_req['prompt_resource_id']}`);const method = 'GET';return this.request({ url, method }, options);}/** GET /api/playground_api/prompt_resources */ListPromptResource(req: playground_api.ListPromptResourceRequest,options?: T,): Promise<playground_api.ListPromptResourceResponse> {const _req = req;const url = this.genBaseURL('/api/playground_api/prompt_resources');const method = 'GET';const params = _req;return this.request({ url, method, params }, options);}// ... 其他API方法
}
代码作用:
- UpsertPromptResource:创建或更新提示词,支持新建和编辑两种模式
- GetPromptResourceInfo:获取提示词详细信息,用于编辑时填充表单
- ListPromptResource:获取提示词列表,支持分页和过滤
- 自动生成:基于 playground_service.thrift 自动生成,确保类型安全
编辑提示词-结构体实现(playground_api.ts)
文件路径:frontend\packages\arch\idl\src\auto-generated\playground_api\namespaces\playground_api.ts
// 提示词资源接口
export interface PromptResource {id?: string;space_id?: string;name?: string;description?: string;prompt_text?: string;owner_id?: string;created_at?: string;updated_at?: string;
}// 创建/更新提示词请求接口
export interface UpsertPromptResourceRequest {prompt: PromptResource;
}// 提示词响应数据接口
export interface PromptResourceData {id?: string;name?: string;description?: string;prompt_text?: string;space_id?: string;owner_id?: string;created_at?: string;updated_at?: string;
}// 创建/更新提示词响应接口
export interface UpsertPromptResourceResponse {data?: PromptResourceData;code: Int64;msg: string;
}// 获取提示词详情请求接口
export interface GetPromptResourceInfoRequest {prompt_resource_id: string;
}// 获取提示词详情响应接口
export interface GetPromptResourceInfoResponse {data?: PromptResourceData;code: Int64;msg: string;
}// 获取提示词列表请求接口
export interface ListPromptResourceRequest {space_id?: string;page?: number;page_size?: number;keyword?: string;
}// 获取提示词列表响应接口
export interface ListPromptResourceResponse {data?: {items?: PromptResourceData[];total?: number;page?: number;page_size?: number;};code: Int64;msg: string;
}
接口设计亮点:
- 类型安全:完整的 TypeScript 类型定义
- 统一数据结构:PromptResource 和 PromptResourceData 保持一致
- 分页支持:ListPromptResourceRequest 支持分页和搜索
- 编辑模式:UpsertPromptResource 支持创建和更新两种模式
- 错误处理:统一的错误码和消息格式
编辑提示词-前端调用示例
基于实际的 usePromptConfig
hook 和 PromptConfiguratorModal
组件实现:
// 在 usePromptConfig hook 中的编辑逻辑
const openCreatePrompt = useCallback((editId?: string) => {promptConfiguratorModal.open({editId,onSuccess: () => {reloadList(); // 编辑成功后刷新列表Toast.success(I18n.t('prompt_save_success')); // 显示成功提示},});},[promptConfiguratorModal, reloadList],
);// 在 TableAction 组件中的使用
<UITableActioneditProps={{disabled: !libraryResource.actions?.find(action => action.key === ActionKey.Edit,)?.enable,handler: () => {openCreatePrompt(libraryResource.res_id);},}}
/>// 在 PromptConfiguratorModal 中的保存逻辑
const handleSubmit = useCallback(async () => {const formData = await form.validateFields();const promptData = {id: editId,name: formData.name,description: formData.description,prompt_text: editorRef.current?.getValue() || '',space_id: currentSpaceId,};try {const response = await PlaygroundApi.UpsertPromptResource({prompt: promptData,});if (response.code === 0) {onSuccess?.(response.data);modal.close();}} catch (error) {console.error('保存提示词失败:', error);}
}, [editId, form, onSuccess]);
实际调用流程:
- 权限检查:通过
libraryResource.actions
检查编辑权限 - 打开编辑弹窗:调用
openCreatePrompt
函数,传入编辑ID - 加载数据:如果是编辑模式,自动加载现有提示词数据
- 表单填充:将加载的数据填充到编辑表单中
- 用户编辑:用户在弹窗中修改提示词内容
- 保存操作:调用
handleSubmit
函数 - API请求:使用
PlaygroundApi.UpsertPromptResource
发送保存请求 - 结果处理:成功后关闭弹窗、刷新列表并显示提示
idl2ts-cli 工具
工具名称
@coze-arch/idl2ts-cli
详细地址
项目路径:frontend/infra/idl/idl2ts-cli/
工具详细信息
版本:0.1.7
描述:IDL(Interface Definition Language)到TypeScript的转换工具
主要功能:
- gen命令:从Thrift或Protocol Buffer文件生成API代码
- filter命令:生成过滤后的API类型定义
可执行文件:idl2ts
(位于 ./src/cli.js
)
最终调用的是frontend/infra/idl/idl2ts-cli/src/cli.ts
这个文件
核心依赖:
@coze-arch/idl2ts-generator
:代码生成器@coze-arch/idl2ts-helper
:辅助工具@coze-arch/idl2ts-plugin
:插件系统commander
:命令行界面prettier
:代码格式化
使用方式:
# 生成API代码
idl2ts gen <projectRoot> [-f --format-config <formatConfig>]# 生成过滤类型
idl2ts filter <projectRoot> [-f --format-config <formatConfig>]
许可证:Apache-2.0
作者:fanwenjie.fe@bytedance.com
这个工具是Coze Studio项目中统一处理所有IDL文件(包括playground_service.thrift
和passport.thrift
)的核心工具,确保了整个项目中API代码生成的一致性。
结语
Coze Studio的编辑提示词功能展现了现代企业级前端应用在复杂交互设计上的卓越实践。通过对其源码的深入分析,我们可以看到一个完整的编辑系统是如何从用户操作到数据持久化的全链路设计。这不仅是技术实现的典范,更是产品思维与工程实践完美结合的体现。
编辑功能的核心技术架构
1. 分层架构设计
编辑提示词功能采用了清晰的分层架构:
// 表现层:用户交互组件
<UITableActioneditProps={{disabled: !canEdit,handler: () => openCreatePrompt(record.id),tooltip: !canEdit ? "无编辑权限" : undefined}}
/>// 业务层:Hook封装业务逻辑
const { openCreatePrompt, promptConfiguratorModal } = usePromptConfig();// 数据层:API接口调用
const { data, loading } = useRequest(() => PlaygroundApi.getPromptResourceInfo({ id: editId })
);
2. 组件化设计模式
- PromptConfiguratorModal:核心编辑弹窗组件,支持创建和编辑两种模式
- PromptEditorRender:专业的提示词编辑器,支持语法高亮和智能提示
- UITableAction:通用的表格操作组件,提供统一的交互体验
- usePromptConfig:业务逻辑Hook,封装编辑相关的所有操作
3. 状态管理策略
// 弹窗状态管理
const promptConfiguratorModal = useModal<PromptConfiguratorModalProps>();// 表单状态管理
const [form] = Form.useForm<PromptValues>();// 编辑器状态管理
const editorRef = useRef<IStandaloneCodeEditor>();// 异步状态管理
const { run: savePrompt, loading: saving } = useRequest(PlaygroundApi.upsertPromptResource,{ manual: true }
);
用户体验设计的精妙之处
1. 渐进式交互流程
编辑提示词的交互流程体现了渐进式设计理念:
资源库列表 → 点击"..."操作按钮 → 选择"编辑"菜单 → 打开编辑弹窗 → 修改内容 → 保存更新
每一步都有明确的视觉反馈和状态指示,用户始终知道自己在流程中的位置。
2. 智能的权限控制
// 基于用户权限和资源所有权的编辑控制
const canEdit = useMemo(() => {return hasEditPermission && (isOwner || hasAdminPermission);
}, [hasEditPermission, isOwner, hasAdminPermission]);// UI层面的权限体现
<UITableActioneditProps={{disabled: !canEdit,tooltip: !canEdit ? "您没有编辑此提示词的权限" : undefined}}
/>
3. 实时反馈机制
- 保存状态指示:按钮loading状态,让用户了解操作进度
- 表单验证提示:实时验证用户输入,提前发现问题
- 操作结果反馈:成功/失败的Toast提示,明确告知操作结果
数据流管理的最佳实践
1. 单向数据流
// 数据加载 → 表单初始化 → 用户编辑 → 数据验证 → 提交保存 → 状态更新
const handleSubmit = async (values: PromptValues) => {try {setSaving(true);await PlaygroundApi.upsertPromptResource({id: editId,...values});Toast.success('提示词保存成功');promptConfiguratorModal.close();// 触发列表刷新onSuccess?.();} catch (error) {handleSaveError(error);} finally {setSaving(false);}
};
2. 乐观更新策略
// 保存成功后立即更新本地状态
const handleSaveSuccess = (updatedPrompt: PromptResource) => {// 更新缓存中的数据queryClient.setQueryData(['prompt', editId], updatedPrompt);// 更新列表数据queryClient.invalidateQueries(['prompts']);// 关闭编辑弹窗promptConfiguratorModal.close();
};
3. 错误边界处理
// 分层次的错误处理策略
const handleSaveError = (error: any) => {if (error.code === 'VALIDATION_ERROR') {// 表单验证错误,显示具体字段错误form.setFields(error.fieldErrors);} else if (error.code === 'PERMISSION_DENIED') {// 权限错误,提示用户并关闭弹窗Toast.error('权限不足,无法保存');promptConfiguratorModal.close();} else {// 其他错误,通用错误提示Toast.error('保存失败,请稍后重试');}
};
API设计的工程化实践
1. 统一的API接口设计
// 基于Thrift IDL的类型安全API
interface UpsertPromptResourceRequest {id?: string;name: string;prompt_text: string;description?: string;tags?: string[];
}// 统一的响应格式
interface ApiResponse<T> {code: number;message: string;data: T;
}
2. 请求拦截和错误处理
// axios实例配置
const axiosInstance = axios.create({baseURL: '/api',timeout: 10000
});// 响应拦截器统一处理错误
axiosInstance.interceptors.response.use((response) => response.data,(error) => {if (!error.config?.disableErrorToast) {Toast.error(error.message || '请求失败');}return Promise.reject(error);}
);
3. 类型安全的API调用
// 基于IDL生成的类型安全API
export const PlaygroundApi = {// 创建或更新提示词资源UpsertPromptResource: (request: UpsertPromptResourceRequest): Promise<PromptResource> => {return axiosInstance.post('/api/playground/prompt/upsert', request);},// 获取提示词详细信息GetPromptResourceInfo: (request: { id: string }): Promise<PromptResource> => {return axiosInstance.get(`/api/playground/prompt/${request.id}`);},// 删除提示词资源DeletePromptResource: (request: { id: string }): Promise<void> => {return axiosInstance.delete(`/api/playground/prompt/${request.id}`);}
};
性能优化的技术要点
1. 编辑器性能优化
// Monaco编辑器的懒加载和资源管理
const editorRef = useRef<IStandaloneCodeEditor>();// 防抖的自动保存
const debouncedAutoSave = useMemo(() => debounce((content: string) => {localStorage.setItem(`prompt_draft_${editId}`, content);}, 1000),[editId]
);// 组件卸载时清理资源
useEffect(() => {return () => {debouncedAutoSave.cancel();editorRef.current?.dispose();};
}, []);
2. 数据缓存策略
// React Query的智能缓存
const { data: promptInfo, isLoading, error } = useQuery(['prompt', editId],() => PlaygroundApi.GetPromptResourceInfo({ id: editId }),{enabled: !!editId, // 只有当editId存在时才执行查询staleTime: 5 * 60 * 1000, // 5分钟内数据保持新鲜cacheTime: 10 * 60 * 1000, // 10分钟缓存时间retry: 3, // 失败时重试3次refetchOnWindowFocus: false, // 窗口聚焦时不自动重新获取}
);// 缓存失效处理
const queryClient = useQueryClient();
const invalidatePromptCache = useCallback(() => {queryClient.invalidateQueries(['prompt']);
}, [queryClient]);
3. 组件渲染优化
// 使用React.memo优化组件渲染
const PromptEditorRender = React.memo<PromptEditorProps>(({ value, onChange, disabled
}) => {// 编辑器实现
}, (prevProps, nextProps) => {return prevProps.value === nextProps.value && prevProps.disabled === nextProps.disabled;
});
技术栈选择的考量
核心技术栈
- React 18:利用并发特性提升用户体验
- TypeScript:类型安全,减少运行时错误
- Zustand:轻量级状态管理,避免过度设计
- React Hook Form:高性能表单处理
- React Query:智能的数据获取和缓存
- Monaco Editor:专业的代码编辑体验
工程化工具
- Vite:快速的开发构建体验
- ESLint + Prettier:代码质量保证
- Husky + lint-staged:提交前代码检查
- Jest + Testing Library:全面的测试覆盖
设计模式的应用
1. 组合模式
// 通过组合实现复杂的编辑功能
const PromptConfiguratorModal = () => {return (<Modal><Form><PromptInfoInput /> {/* 基础信息输入 */}<PromptEditorRender /> {/* 内容编辑器 */}<PromptTagsInput /> {/* 标签输入 */}</Form></Modal>);
};
2. 观察者模式
// 通过事件系统实现组件间通信
const usePromptConfig = () => {const handleEditSuccess = useCallback(() => {// 通知列表组件刷新eventBus.emit('prompt:updated');// 通知缓存失效queryClient.invalidateQueries(['prompts']);}, []);
};
3. 策略模式
// 不同编辑模式的策略实现
const editStrategies = {create: {title: '创建提示词',submitText: '创建',api: PlaygroundApi.UpsertPromptResource, // 创建模式不传idvalidateRequired: ['name', 'prompt_text']},edit: {title: '编辑提示词',submitText: '保存',api: PlaygroundApi.UpsertPromptResource, // 编辑模式传入idvalidateRequired: ['id', 'name', 'prompt_text']},info: {title: '提示词详情',submitText: '复制',api: null, // 仅查看模式,不需要API调用readonly: true}
};
总结与展望
Coze Studio的编辑提示词功能是现代前端工程实践的优秀范例。它在技术实现上体现了:
- 架构设计的前瞻性:分层清晰,职责明确,易于维护和扩展
- 用户体验的精细化:每个交互细节都经过深思熟虑
- 工程实践的专业性:类型安全、性能优化、错误处理一应俱全
- 代码质量的高标准:可读性强,可测试性好,可维护性高
这样的实现不仅满足了当前的业务需求,更为未来的功能扩展奠定了坚实的基础。对于前端开发者而言,这是一个值得深入学习和借鉴的优秀案例。