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

概述

本文深入分析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

作为资源库的适配器组件,整合各种资源配置,包括提示词的删除功能:

typescript 复制代码
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

负责资源库的核心展示逻辑,包含资源列表表格和删除操作:

typescript 复制代码
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 组件

提供表格行的操作菜单,包含删除功能:

typescript 复制代码
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 组件来处理表格行的操作。

核心实现逻辑:

typescript 复制代码
// 在 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

核心代码:

typescript 复制代码
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

核心代码:

typescript 复制代码
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

管理提示词的删除功能和状态:

typescript 复制代码
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

核心代码:

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 被导出:
typescript 复制代码
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

核心代码:

typescript 复制代码
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 。

typescript 复制代码
import {
  axiosInstance,
  isApiError,
  type AxiosRequestConfig,
} from '@coze-arch/bot-http';

3.bot-http 包中的定义 ( frontend/packages/arch/bot-http/src/axios.ts ):

typescript 复制代码
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 包的所有内容,包括默认导出

typescript 复制代码
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

核心代码:

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

核心代码:

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

核心代码:

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

核心代码:

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

核心代码:

typescript 复制代码
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

typescript 复制代码
// 删除策略接口
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 实现:

typescript 复制代码
// 在 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:代码格式化

使用方式:

bash 复制代码
# 生成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. 组件化设计优势
typescript 复制代码
// 高度复用的删除操作组件
const { TableAction } = Table;
<TableAction
  deleteProps={{
    disabled: !canDelete,
    onConfirm: () => handleDelete(record.id),
    title: "确认删除提示词?",
    description: "删除后无法恢复,请谨慎操作"
  }}
/>
3. 状态管理的最佳实践
  • useRequest集成:统一的异步状态管理
  • 乐观更新策略:删除成功后立即更新UI状态
  • 错误回滚机制:删除失败时恢复原始状态

删除操作的用户体验设计要点

1. 渐进式操作引导
typescript 复制代码
// 三步式删除流程设计
用户点击"..."按钮 → 显示操作菜单 → 点击删除 → 确认弹窗 → 执行删除
2. 即时反馈机制
typescript 复制代码
// 删除成功的即时反馈
Toast.success({
  content: "提示词删除成功",
  duration: 3000
});

// 删除失败的错误提示
Toast.error({
  content: "删除失败,请稍后重试",
  duration: 5000
});
3. 视觉状态指示
  • 加载状态:删除过程中的loading指示器
  • 禁用状态:无权限时的按钮禁用样式
  • 危险操作标识:删除按钮的红色警告色彩

安全性和权限控制的实现

1. 前端权限控制
typescript 复制代码
// 基于用户权限的UI状态控制
const canDelete = useMemo(() => {
  return hasPermission('prompt:delete') && 
         record.creatorId === currentUser.id;
}, [record, currentUser]);

<UITableAction
  deleteProps={{
    disabled: !canDelete,
    tooltip: !canDelete ? "无删除权限" : undefined
  }}
/>
2. API层安全验证
typescript 复制代码
// 删除请求的安全参数
const deleteRequest = {
  promptResourceId: record.id,
  deleteReason: "用户主动删除",
  confirmToken: generateConfirmToken()
};
3. 操作审计日志
  • 操作记录:记录删除操作的用户、时间、原因
  • 数据备份:删除前的数据快照保存
  • 恢复机制:支持管理员级别的数据恢复

错误处理和异常情况的处理策略

1. 分层错误处理
typescript 复制代码
// 网络错误处理
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. 异常状态恢复
typescript 复制代码
// 删除失败后的状态恢复
const handleDeleteFailure = () => {
  setDeleting(false);
  setSelectedItems(prevItems => [...prevItems, failedItem]);
  refreshList();
};
3. 用户友好的错误提示
  • 具体错误信息:明确告知用户错误原因
  • 操作建议:提供解决问题的具体步骤
  • 重试机制:支持用户重新尝试删除操作

性能优化和最佳实践

1. 批量删除优化
typescript 复制代码
// 批量删除的性能优化
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. 内存管理优化
typescript 复制代码
// 组件卸载时清理定时器和事件监听
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自动生成
相关推荐
n87655 小时前
Playwright Java WEB UI 自动化实现框架与逻辑
java·前端·ui
keyipatience5 小时前
TypeScript学习(1)
前端·javascript·学习·typescript·html·css3
xifangge20255 小时前
基于 JavaScript 实现 CNC 数控切削参数与螺纹公差纯前端核算引擎
开发语言·前端·javascript
wangruofeng6 小时前
2026 Web 技术选型指南,AI 时代如何选择适合项目的技术栈
前端·aigc·ai编程
honkun66 小时前
vue 表格组件 vxe-table 修改滚动条样式,修改滚动条样式,统一宽度、颜色与自动隐藏
前端·javascript·vue.js
爱勇宝6 小时前
研究数字孪生的这半年,做了个 Demo,也有了一些新想法
前端·程序员
不可能片场6 小时前
工程实践 一个缓存把选片从十分钟压到一分钟
前端·electron
前端小万6 小时前
探索副业五个月,最终以放弃告终
前端·后端·面试
FRing036 小时前
JavaScript入门到精通二十四(正则表达式、元字符、修饰符)
开发语言·前端·javascript·正则表达式
葡萄城技术团队7 小时前
在 SpreadJS 里新增右键菜单,把业务动作放到用户手边
前端