Coze源码分析-资源库-删除提示词-前端源码

发布于:2025-09-06 ⋅ 阅读:(17) ⋅ 点赞:(0)

概述

本文深入分析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() 显示删除成功提示

该流程包含多层安全验证和处理:

  1. 权限验证:通过ActionKey.Delete检查用户是否有删除权限
  2. 用户确认:使用Modal.confirm防止误删除操作
  3. API调用:使用DeletePromptResource API安全删除资源
  4. 状态同步:删除成功后自动刷新列表保持数据一致性
  5. 用户反馈:通过Toast提示用户操作结果
  6. 错误处理:API调用失败时提供相应的错误提示
    整个流程确保了提示词删除的安全性和用户体验的友好性。

核心组件实现

组件层次结构

提示词删除功能涉及多个层次的组件:

  1. LibraryPage组件:资源库主页面,整合各种资源配置
  2. BaseLibraryPage组件:资源库核心逻辑,渲染资源列表
  3. Table组件:资源列表表格,包含操作列
  4. TableAction组件:表格行操作菜单,包含删除选项
  5. 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. 删除操作的完整流程

删除提示词的完整流程如下:

用户 前端界面 TableAction组件 useDeleteAction PlaygroundApi 后端服务 点击表格行的"..."按钮 渲染操作菜单 点击"删除"选项 显示确认弹窗 确认删除 调用删除处理函数 获取删除策略 请求删除策略 返回策略信息 返回策略结果 检查删除权限 执行删除操作 发送删除请求 返回删除结果 返回操作结果 显示成功/失败提示 刷新列表数据 用户 前端界面 TableAction组件 useDeleteAction PlaygroundApi 后端服务

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 || '');
    },
  }}
/>

实际调用流程

  1. 权限检查:通过 libraryResource.actions 检查删除权限
  2. 用户确认:TableAction 组件内置确认弹窗
  3. 执行删除:调用 delPrompt 函数
  4. API请求:使用 PlaygroundApi.DeletePromptResource 发送删除请求
  5. 结果处理:成功后刷新列表并显示提示

idl2ts-cli 工具

工具名称

@coze-arch/idl2ts-cli

详细地址

项目路径frontend/infra/idl/idl2ts-cli/

工具详细信息

版本:0.1.7

描述:IDL(Interface Definition Language)到TypeScript的转换工具

主要功能

  1. gen命令:从Thrift或Protocol Buffer文件生成API代码
  2. 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.thriftpassport.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自动生成

网站公告

今日签到

点亮在社区的每一天
去签到