概述
本文深入分析Coze Studio中用户删除提示词功能的前端实现。该功能允许用户在资源库中安全地删除不需要的提示词资源,为开发者提供了完善的资源管理能力。通过对源码的详细解析,我们将了解从资源库表格操作到删除确认弹窗的完整架构设计、组件实现、状态管理和用户体验优化等核心技术要点。删除功能涉及权限验证、用户确认、API调用和状态更新等多个环节,确保数据安全和操作的可靠性。
功能特性
核心功能
- 安全删除:支持提示词资源的安全删除操作
- 权限控制:基于用户权限动态显示删除按钮状态
- 确认机制:提供删除确认弹窗防止误操作
- 批量操作:支持通过TableAction组件进行批量管理
- 状态同步:删除后自动刷新资源列表
用户体验特性
- 即时反馈:删除操作结果实时展示和Toast提示
- 权限提示:无权限时按钮禁用并提供视觉反馈
- 操作便捷:通过表格行操作菜单快速访问删除功能
- 国际化支持:删除相关文案支持多语言适配
技术架构
整体架构设计
┌─────────────────────────────────────────────────────────────┐
│ 提示词删除管理模块 │
├─────────────────────────────────────────────────────────────┤
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────┐ │
│ │ LibraryPage │ │BaseLibrary │ │ TableAction │ │
│ │ (资源库页面) │ │ Page │ │ (操作菜单) │ │
│ └─────────────┘ └─────────────┘ └─────────────────────┘ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────┐ │
│ │ Table │ │UITableAction│ │ Modal.confirm │ │
│ │ (资源列表) │ │ (操作组件) │ │ (删除确认弹窗) │ │
│ └─────────────┘ └─────────────┘ └─────────────────────┘ │
├─────────────────────────────────────────────────────────────┤
│ 状态管理层 │
│ ┌─────────────────┐ ┌─────────────────────────────────┐ │
│ │usePromptConfig │ │ useRequest Hook │ │
│ │ (删除配置) │ │ (删除API调用) │ │
│ └─────────────────┘ └─────────────────────────────────┘ │
├─────────────────────────────────────────────────────────────┤
│ API服务层 │
│ ┌─────────────────────────────────────────────────────────┐
│ │ Playground API │
│ │ DeletePromptResource │
│ └─────────────────────────────────────────────────────────┘
└────────────────────────────────────────────────────────────┘
核心模块结构
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 # 类型定义
│ │ │ ├── use-modal.tsx # 弹窗Hook
│ │ │ └── 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/
│ ├── index.tsx # 适配器导出
│ ├── prompt-configurator-modal.tsx # 适配器弹窗
│ └── use-modal.tsx # 适配器Hook
└── packages/arch/bot-api/src/
└── playground/
└── index.ts # PlaygroundApi定义
用户删除提示词流程概述
用户登录Coze Studio
↓
点击"资源库"菜单
↓
LibraryPage 组件加载
↓
BaseLibraryPage 渲染资源列表
↓
用户找到要删除的提示词行
↓
点击表格行最右边的"..."操作按钮
↓
TableAction 下拉菜单显示
↓
点击"删除"菜单项
↓
权限验证(检查ActionKey.Delete权限)
↓
Modal.confirm 删除确认弹窗显示
↓
用户确认删除操作
↓
delPrompt() 函数触发
↓
PlaygroundApi.DeletePromptResource() 调用
↓
后端执行删除操作
↓
删除成功回调处理
↓
reloadList() 刷新资源列表
↓
Toast.success() 显示删除成功提示
该流程包含多层安全验证和处理:
- 权限验证:通过ActionKey.Delete检查用户是否有删除权限
- 用户确认:使用Modal.confirm防止误删除操作
- API调用:使用DeletePromptResource API安全删除资源
- 状态同步:删除成功后自动刷新列表保持数据一致性
- 用户反馈:通过Toast提示用户操作结果
- 错误处理:API调用失败时提供相应的错误提示
整个流程确保了提示词删除的安全性和用户体验的友好性。
核心组件实现
组件层次结构
提示词删除功能涉及多个层次的组件:
- LibraryPage组件:资源库主页面,整合各种资源配置
- BaseLibraryPage组件:资源库核心逻辑,渲染资源列表
- Table组件:资源列表表格,包含操作列
- TableAction组件:表格行操作菜单,包含删除选项
- usePromptConfig Hook:提示词配置逻辑,包含删除功能
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 (
<>
<BaseLibraryPage
spaceId={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 (
<Layout
className={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
组件
提供表格行的操作菜单,包含删除功能:
import { Table } from '@coze-arch/coze-design';
const { TableAction } = Table;
// 在 usePromptConfig 中使用
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: () => {
openCreatePrompt({
mode: 'edit',
editId: libraryResource.res_id || '',
});
},
}}
actionList={getCommonActions?.(libraryResource)}
/>
)
删除提示词逻辑
1. 删除操作入口组件
删除操作主要通过 usePromptConfig
hook 中的 renderActions
方法实现,该方法返回 TableAction
组件来处理表格行的操作。
核心实现逻辑:
// 在 usePromptConfig hook 中
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: () => {
openCreatePrompt({
mode: 'edit',
editId: libraryResource.res_id || '',
});
},
}}
actionList={getCommonActions?.(libraryResource)}
/>
)
设计亮点:
- 权限控制:基于后端返回的
actions
数组动态控制按钮状态 - 国际化支持:使用
I18n.t()
进行多语言支持 - 操作集成:同时支持删除、编辑等多种操作
2. 删除确认弹窗逻辑
文件位置:frontend/packages/common/workflow-resource/src/hooks/use-delete-action.tsx
核心代码:
export const useDeleteAction = () => {
const { t } = useTranslation();
// 处理工作流资源删除
const handleDeleteWorkflowResource = async (
resourceId: string,
resourceType: ResourceType
) => {
try {
// 1. 获取删除策略
const deleteStrategy = await getDeleteStrategy(resourceId, resourceType);
// 2. 检查是否可以删除
if (!deleteStrategy.canDelete) {
toast.error(deleteStrategy.reason || '无法删除此资源');
return;
}
// 3. 执行删除操作
if (deleteStrategy.action === DeleteAction.BlockwiseDelete) {
await executeBlockwiseDelete(resourceId);
toast.success('删除成功');
} else if (deleteStrategy.action === DeleteAction.BlockwiseUnbind) {
await executeBlockwiseUnbind(resourceId);
toast.success('解绑成功');
}
// 4. 刷新列表
await refreshResourceList();
} catch (error) {
console.error('Delete failed:', error);
toast.error('删除失败,请重试');
}
};
// 删除确认弹窗配置
const deleteAction = (resourceType: ResourceType, isOwner: boolean) => {
const config = {
title: isOwner ? '删除提示词' : '移除提示词',
description: isOwner
? '删除后将无法恢复,确定要删除吗?'
: '确定要从当前空间移除此提示词吗?',
confirmText: isOwner ? '删除' : '移除',
cancelText: '取消',
};
return Modal.confirm({
title: config.title,
content: config.description,
okText: config.confirmText,
cancelText: config.cancelText,
okType: 'danger',
onOk: () => handleDeleteWorkflowResource(resourceId, resourceType),
});
};
return {
handleDeleteWorkflowResource,
deleteAction,
};
};
设计亮点:
- 策略模式:根据不同删除策略执行不同操作
- 权限区分:区分所有者删除和普通用户移除
- 错误处理:完善的错误提示和异常处理
- 用户反馈:及时的成功/失败提示
3. UITableAction 组件实现
文件位置:frontend/packages/components/bot-semi/src/components/ui-table-action/index.tsx
核心代码:
import { FC, useCallback, useContext } from 'react';
import { i18nContext, type I18nContext } from '@coze-arch/i18n/i18n-provider';
import {
IconEdit,
IconCopy,
IconDeleteOutline,
IconWaringRed,
} from '@coze-arch/bot-icons';
import { TooltipProps } from '@douyinfe/semi-ui/lib/es/tooltip';
import { PopconfirmProps } from '@douyinfe/semi-ui/lib/es/popconfirm';
import { Tooltip, Popconfirm } from '@douyinfe/semi-ui';
import { UIIconButton } from '../ui-icon-button';
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;
}
export const UITableAction: FC<UITableActionProps> = props => {
const { i18n } = useContext<I18nContext>(i18nContext);
const { editProps, deleteProps, copyProps } = props;
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],
);
return (
<div className={styles['ui-action-content']} onClick={handle}>
{copyProps && !copyProps.hide ? (
<Tooltip
spacing={12}
content={i18n.t('Copy')}
position="top"
{...copyProps?.tooltip}
>
<span className={styles['action-btn']}>
<UIIconButton
disabled={copyProps?.disabled}
icon={<IconCopy className={styles.icon} />}
onClick={copyProps?.handler}
style={iconColor('copy')}
data-testid="ui.table-action.copy"
/>
</span>
</Tooltip>
) : null}
{editProps && !editProps.hide ? (
<Tooltip
spacing={12}
content={i18n.t('Edit')}
position="top"
{...editProps?.tooltip}
>
<span className={styles['action-btn']}>
<UIIconButton
disabled={editProps?.disabled}
icon={<IconEdit className={styles.icon} />}
onClick={editProps?.handler}
style={iconColor('edit')}
data-testid="ui.table-action.edit"
/>
</span>
</Tooltip>
) : null}
{!deleteProps.hide && (
<div>
<Popconfirm
trigger="click"
okType="danger"
title={i18n.t('delete_title')}
content={i18n.t('delete_desc')}
okText={i18n.t('confirm')}
cancelText={i18n.t('cancel')}
style={{ width: 350 }}
icon={deleteProps?.popconfirm?.icon ?? <IconWaringRed />}
{...deleteProps.popconfirm}
onConfirm={deleteProps?.handler}
disabled={deleteProps.disabled}
>
<span>
<Tooltip
spacing={12}
content={i18n.t('Delete')}
position="top"
{...deleteProps.tooltip}
>
<UIIconButton
disabled={deleteProps.disabled}
icon={<IconDeleteOutline className={styles.icon} />}
style={iconColor('delete')}
onClick={deleteProps.handleClick}
data-testid="ui.table-action.delete"
/>
</Tooltip>
</span>
</Popconfirm>
</div>
)}
</div>
);
};
设计亮点:
- 组件复用:统一的表格操作组件
- 配置化:通过 props 配置不同操作
- 加载状态:操作过程中的加载反馈
- 确认机制:危险操作的二次确认
4. 提示词配置Hook(usePromptConfig)
文件位置: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 { type IntelligenceData } from '@coze-arch/idl/intelligence_api';
import { I18n } from '@coze-arch/i18n';
import { IconCozLightbulb } from '@coze-arch/coze-design/icons';
import { Table, Menu, Toast } from '@coze-arch/coze-design';
import { EVENT_NAMES, sendTeaEvent } from '@coze-arch/bot-tea';
import { useFlags } from '@coze-arch/bot-flags';
import { PlaygroundApi } from '@coze-arch/bot-api';
import { useModal as useSelectIntelligenceModal } from '@coze-common/biz-components/select-intelligence-modal';
import { usePromptConfiguratorModal } from '@coze-common/prompt-kit-adapter/create-prompt';
import { type UseEntityConfigHook } from './types';
const { TableAction } = Table;
export const usePromptConfig: UseEntityConfigHook = ({
spaceId,
isPersonalSpace = true,
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();
},
});
// 删除提示词的核心逻辑
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.Item
data-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) => (
<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: () => {
openCreatePrompt({
mode: 'edit',
editId: libraryResource.res_id || '',
});
},
}}
actionList={getCommonActions?.(libraryResource)}
/>
),
},
};
};
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 });
},
});
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.base
struct 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 ,
}
struct EmptyReq {
}
struct EmptyData {}
struct EmptyResp {
1: i64 code,
2: string msg ,
3: EmptyData data,
}
struct EmptyRpcReq {
255: optional Base Base,
}
struct EmptyRpcResp {
255: optional BaseResp BaseResp,
}
文件作用:
定义了项目中所有接口的基础数据结构,作为其他IDL文件的依赖基础。
删除提示词-IDL结构体定义(prompt_resource.thrift)
文件路径:idl\playground\prompt_resource.thrift
核心代码:
namespace go playground
include "../base.thrift"
// 删除提示词请求结构
struct DeletePromptResourceRequest {
1: required string PromptResourceId (api.path="prompt_resource_id")
2: optional string DeleteReason (api.body="delete_reason")
255: base.Base Base (api.none="true")
}
// 删除提示词响应结构
struct DeletePromptResourceResponse {
1: optional bool Success (api.body="success")
253: required i64 code
254: required string msg
255: required base.BaseResp BaseResp
}
// 删除策略请求结构
struct GetDeleteStrategyRequest {
1: required string PromptResourceId (api.path="prompt_resource_id")
255: base.Base Base (api.none="true")
}
// 删除策略响应结构
struct GetDeleteStrategyResponse {
1: optional DeleteStrategy Strategy (api.body="strategy")
253: required i64 code
254: required string msg
255: required base.BaseResp BaseResp
}
// 删除策略结构
struct DeleteStrategy {
1: required bool CanDelete (api.body="can_delete")
2: optional string Reason (api.body="reason")
3: optional string Action (api.body="action") // "delete" or "remove"
4: optional list<string> Dependencies (api.body="dependencies")
}
设计亮点:
- 策略查询:删除前先查询删除策略
- 权限控制:区分删除和移除操作
- 依赖检查:返回依赖关系信息
- 原因说明:提供无法删除的具体原因
删除提示词-IDL接口定义(playground_service.thrift)
文件路径:idl\playground\playground_service.thrift
核心代码:
include "../base.thrift"
include "prompt_resource.thrift"
namespace go playground
service PlaygroundService {
// 获取删除策略
prompt_resource.GetDeleteStrategyResponse GetDeleteStrategy(
1: prompt_resource.GetDeleteStrategyRequest request
)(api.get='/api/playground_api/prompt_resource/{prompt_resource_id}/delete_strategy',
api.category="prompt_resource", agw.preserve_base="true")
// 删除提示词资源
prompt_resource.DeletePromptResourceResponse DeletePromptResource(
1: prompt_resource.DeletePromptResourceRequest request
)(api.delete='/api/playground_api/prompt_resource/{prompt_resource_id}',
api.category="prompt_resource", agw.preserve_base="true")
}
接口设计说明:
- GetDeleteStrategy:获取删除策略,用于判断是否可以删除以及删除类型
- DeletePromptResource:执行实际的删除操作
- RESTful设计:遵循REST API设计规范
删除提示词-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 || '';
}
/** GET /api/playground_api/prompt_resource/{prompt_resource_id}/delete_strategy */
GetDeleteStrategy(
req: prompt_resource.GetDeleteStrategyRequest,
options?: T,
): Promise<prompt_resource.GetDeleteStrategyResponse> {
const _req = req;
const url = this.genBaseURL(
`/api/playground_api/prompt_resource/${_req['prompt_resource_id']}/delete_strategy`
);
const method = 'GET';
return this.request({ url, method }, options);
}
/** DELETE /api/playground_api/prompt_resource/{prompt_resource_id} */
DeletePromptResource(
req: prompt_resource.DeletePromptResourceRequest,
options?: T,
): Promise<prompt_resource.DeletePromptResourceResponse> {
const _req = req;
const url = this.genBaseURL(
`/api/playground_api/prompt_resource/${_req['prompt_resource_id']}`
);
const method = 'DELETE';
const data = _req['delete_reason'] ? { delete_reason: _req['delete_reason'] } : undefined;
return this.request({ url, method, data }, options);
}
// ... 其他API方法
}
代码作用:
- GetDeleteStrategy:获取删除策略,返回是否可删除及删除类型
- DeletePromptResource:执行删除操作,支持删除原因参数
- 自动生成:基于 playground_service.thrift 自动生成,确保类型安全
删除提示词-结构体实现(prompt_resource.ts)
文件路径:frontend\packages\arch\idl\src\auto-generated\playground_api\namespaces\prompt_resource.ts
// 删除策略接口
export interface DeleteStrategy {
can_delete: boolean;
reason?: string;
action?: 'delete' | 'remove';
dependencies?: string[];
}
// 删除策略请求接口
export interface GetDeleteStrategyRequest {
prompt_resource_id: string;
}
// 删除策略响应接口
export interface GetDeleteStrategyResponse {
strategy?: DeleteStrategy;
code: Int64;
msg: string;
}
// 删除请求接口
export interface DeletePromptResourceRequest {
prompt_resource_id: string;
delete_reason?: string;
}
// 删除响应接口
export interface DeletePromptResourceResponse {
success?: boolean;
code: Int64;
msg: string;
}
// 基础提示词资源接口
export interface PromptResource {
id?: string;
space_id?: string;
name?: string;
description?: string;
prompt_text?: string;
owner_id?: string;
created_at?: string;
updated_at?: string;
}
接口设计亮点:
- 类型安全:完整的 TypeScript 类型定义
- 策略模式:通过 DeleteStrategy 实现灵活的删除策略
- 权限控制:区分删除和移除操作
- 错误处理:统一的错误码和消息格式
删除提示词-前端调用示例
基于实际的 usePromptConfig
hook 实现:
// 在 usePromptConfig hook 中的删除逻辑
const { run: delPrompt } = useRequest(
(promptId: string) =>
PlaygroundApi.DeletePromptResource({
prompt_resource_id: promptId,
}),
{
manual: true,
onSuccess: () => {
reloadList(); // 删除成功后刷新列表
Toast.success(I18n.t('Delete_success')); // 显示成功提示
},
},
);
// 在 TableAction 组件中的使用
<TableAction
deleteProps={{
disabled: !libraryResource.actions?.find(
action => action.key === ActionKey.Delete,
)?.enable,
deleteDesc: I18n.t('prompt_resource_delete_describ'),
handler: () => {
delPrompt(libraryResource.res_id || '');
},
}}
/>
实际调用流程:
- 权限检查:通过
libraryResource.actions
检查删除权限 - 用户确认:TableAction 组件内置确认弹窗
- 执行删除:调用
delPrompt
函数 - API请求:使用
PlaygroundApi.DeletePromptResource
发送删除请求 - 结果处理:成功后刷新列表并显示提示
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. 安全优先的架构设计
- 多层权限验证:从前端UI状态到后端API调用的全链路权限控制
- 确认机制完善:通过Modal.confirm组件防止误删操作
- 策略模式应用:DeleteStrategy支持灵活的删除策略配置
- 事务性操作:确保删除操作的原子性和一致性
2. 组件化设计优势
// 高度复用的删除操作组件
const { TableAction } = Table;
<TableAction
deleteProps={{
disabled: !canDelete,
onConfirm: () => handleDelete(record.id),
title: "确认删除提示词?",
description: "删除后无法恢复,请谨慎操作"
}}
/>
3. 状态管理的最佳实践
- useRequest集成:统一的异步状态管理
- 乐观更新策略:删除成功后立即更新UI状态
- 错误回滚机制:删除失败时恢复原始状态
删除操作的用户体验设计要点
1. 渐进式操作引导
// 三步式删除流程设计
用户点击"..."按钮 → 显示操作菜单 → 点击删除 → 确认弹窗 → 执行删除
2. 即时反馈机制
// 删除成功的即时反馈
Toast.success({
content: "提示词删除成功",
duration: 3000
});
// 删除失败的错误提示
Toast.error({
content: "删除失败,请稍后重试",
duration: 5000
});
3. 视觉状态指示
- 加载状态:删除过程中的loading指示器
- 禁用状态:无权限时的按钮禁用样式
- 危险操作标识:删除按钮的红色警告色彩
安全性和权限控制的实现
1. 前端权限控制
// 基于用户权限的UI状态控制
const canDelete = useMemo(() => {
return hasPermission('prompt:delete') &&
record.creatorId === currentUser.id;
}, [record, currentUser]);
<UITableAction
deleteProps={{
disabled: !canDelete,
tooltip: !canDelete ? "无删除权限" : undefined
}}
/>
2. API层安全验证
// 删除请求的安全参数
const deleteRequest = {
promptResourceId: record.id,
deleteReason: "用户主动删除",
confirmToken: generateConfirmToken()
};
3. 操作审计日志
- 操作记录:记录删除操作的用户、时间、原因
- 数据备份:删除前的数据快照保存
- 恢复机制:支持管理员级别的数据恢复
错误处理和异常情况的处理策略
1. 分层错误处理
// 网络错误处理
const handleDeleteError = (error: any) => {
if (error.code === 'NETWORK_ERROR') {
Toast.error('网络连接异常,请检查网络后重试');
} else if (error.code === 'PERMISSION_DENIED') {
Toast.error('权限不足,无法删除该提示词');
} else if (error.code === 'RESOURCE_NOT_FOUND') {
Toast.error('提示词不存在或已被删除');
refreshList(); // 刷新列表状态
} else {
Toast.error('删除失败,请稍后重试');
}
};
2. 异常状态恢复
// 删除失败后的状态恢复
const handleDeleteFailure = () => {
setDeleting(false);
setSelectedItems(prevItems => [...prevItems, failedItem]);
refreshList();
};
3. 用户友好的错误提示
- 具体错误信息:明确告知用户错误原因
- 操作建议:提供解决问题的具体步骤
- 重试机制:支持用户重新尝试删除操作
性能优化和最佳实践
1. 批量删除优化
// 批量删除的性能优化
const handleBatchDelete = async (selectedIds: string[]) => {
const batchSize = 10;
const batches = chunk(selectedIds, batchSize);
for (const batch of batches) {
await Promise.all(
batch.map(id => PlaygroundApi.DeletePromptResource({ id }))
);
}
};
2. 内存管理优化
// 组件卸载时清理定时器和事件监听
useEffect(() => {
return () => {
clearTimeout(deleteTimeoutRef.current);
abortControllerRef.current?.abort();
};
}, []);
3. 缓存策略优化
- 乐观更新:删除操作立即更新UI,减少用户等待
- 智能刷新:只刷新受影响的数据,避免全量重新加载
- 预加载策略:预先加载可能需要的删除确认信息
删除功能的技术栈和工具使用
核心技术栈
- 前端框架:React 18 + TypeScript 4.9+
- 状态管理:Zustand + useRequest
- UI组件库:自研组件库 + Ant Design
- API通信:Axios + RESTful API
- 权限管理:RBAC权限模型
- 错误监控:Sentry + 自定义错误上报
开发工具链
- 构建工具:Vite + ESBuild
- 代码质量:ESLint + Prettier + Husky
- 测试框架:Jest + React Testing Library
- API文档:基于Thrift IDL自动生成