Coze源码分析-资源库-创建提示词-前端源码

概述

本文深入分析Coze Studio中用户创建提示词功能的前端实现。该功能允许用户在资源库中创建、编辑和管理提示词资源,为开发者提供了强大的提示词管理能力。通过对源码的详细解析,我们将了解从资源库入口到提示词配置弹窗的完整架构设计、组件实现、状态管理和用户体验优化等核心技术要点。

功能特性

核心功能

  • 提示词创建:支持自定义提示词名称、描述和内容
  • 提示词管理:提供提示词列表展示、编辑和删除功能
  • 富文本编辑:基于CodeMirror的专业提示词编辑器
  • 实时预览:支持Markdown和Jinja语法高亮
  • 模板插槽:支持输入槽位和库引用功能

用户体验特性

  • 即时反馈:操作结果实时展示和验证
  • 智能提示:编辑器提供语法提示和自动补全
  • 便捷操作:支持复制、比较和快速编辑
  • 国际化支持:多语言界面适配

技术架构

整体架构设计

复制代码
┌─────────────────────────────────────────────────────────────┐
│                      提示词管理模块                          │
├─────────────────────────────────────────────────────────────┤
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────────────┐  │
│  │ LibraryPage │  │LibraryHeader│  │ PromptConfigurator  │  │
│  │ (资源库页面) │  │ (添加按钮)  │  │     Modal           │  │
│  └─────────────┘  └─────────────┘  │  (创建/编辑弹窗)    │  │
│  ┌─────────────┐  ┌─────────────┐  └─────────────────────┘  │
│  │BaseLibrary  │  │   Table     │  ┌─────────────────────┐  │
│  │    Page     │  │ (资源列表)  │  │ PromptEditorRender  │  │
│  └─────────────┘  └─────────────┘  │   (编辑器组件)      │  │
│                                    └─────────────────────┘  │
├─────────────────────────────────────────────────────────────┤
│                      状态管理层                             │
│  ┌─────────────────┐  ┌─────────────────────────────────┐  │
│  │usePromptConfig  │  │         API Hooks               │  │
│  │  (配置逻辑)      │  │       UpsertPromptResource      │  │
│  └─────────────────┘  └─────────────────────────────────┘  │
├─────────────────────────────────────────────────────────────┤
│                       API服务层                            │
│  ┌─────────────────────────────────────────────────────────┐
│  │              Playground API                             │
│  │            UpsertPromptResource                         │
│  └─────────────────────────────────────────────────────────┘
└────────────────────────────────────────────────────────────┘

核心模块结构

复制代码
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 组件加载
        ↓
  点击右上角"+"按钮
        ↓
  LibraryHeader 显示创建菜单
        ↓
  点击"提示词"选项
        ↓
  openCreatePrompt() 触发
        ↓
  PromptConfiguratorModal 弹窗显示
        ↓
  用户输入提示词名称(name字段)
        ↓
  用户输入提示词描述(description字段)
        ↓
  用户在PromptEditorRender中编写提示词内容
        ↓
  表单验证(名称必填)
        ↓
  用户点击"保存"按钮
        ↓
  handleSubmit() 触发
        ↓
  PlaygroundApi.UpsertPromptResource() 调用
        ↓
  后端创建新提示词资源
        ↓
  onUpdateSuccess() 处理成功响应
        ↓
  Toast.success() 显示成功提示
        ↓
  刷新资源库列表

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

  1. 前端表单验证:通过Form组件进行名称必填验证
  2. 编辑器集成:使用CodeMirror编辑器提供专业的提示词编写体验
  3. API调用:使用UpsertPromptResource API统一处理创建和更新
  4. 成功处理:通过Toast提示用户操作成功,并自动刷新列表
  5. 状态管理:通过usePromptConfig Hook管理弹窗状态和数据流
    整个流程确保了提示词创建的便捷性和用户体验的流畅性。

核心组件实现

组件层次结构

提示词创建功能涉及多个层次的组件:

  1. LibraryPage组件:资源库主页面
  2. BaseLibraryPage组件:资源库核心逻辑
  3. LibraryHeader组件:包含创建按钮的头部
  4. PromptConfiguratorModal组件:提示词配置弹窗
  5. PromptEditorRender组件:提示词编辑器

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: pluginConfig, modals: pluginModals } =
    usePluginConfig(configCommonParams);
  const { config: workflowConfig, modals: workflowModals } =
    useWorkflowConfig(configCommonParams);
  const { config: knowledgeConfig, modals: knowledgeModals } =
    useKnowledgeConfig(configCommonParams);
  const { config: promptConfig, modals: promptModals } =
    usePromptConfig(configCommonParams);
  const { config: databaseConfig, modals: databaseModals } =
    useDatabaseConfig(configCommonParams);

  return (
    <>
      <BaseLibraryPage
        spaceId={spaceId}
        ref={basePageRef}
        entityConfigs={[
          pluginConfig,
          workflowConfig,
          knowledgeConfig,
          promptConfig,
          databaseConfig,
        ]}
      />
      {pluginModals}
      {workflowModals}
      {promptModals}
      {databaseModals}
      {knowledgeModals}
    </>
  );
};
      <ResultModal
        visible={!!successData}
        data={successData}
        onOk={refresh}
      />
    </>
  );
};

设计亮点

  • 状态集中管理 :通过 usePatOperation Hook统一管理组件状态
  • 组件解耦:各子组件职责明确,通过props进行通信
  • 数据流清晰:单向数据流,状态变更可追踪

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. 资源库头部组件(LibraryHeader)

文件位置:frontend/packages/studio/workspace/entry-base/src/pages/library/components/library-header.tsx

包含创建资源的入口按钮:

typescript 复制代码
import React from 'react';

import { I18n } from '@coze-arch/i18n';
import { IconCozPlus } from '@coze-arch/coze-design/icons';
import { Button, Menu } from '@coze-arch/coze-design';

import { type LibraryEntityConfig } from '../types';

export const LibraryHeader: React.FC<{
  entityConfigs: LibraryEntityConfig[];
}> = ({ entityConfigs }) => (
  <div className="flex items-center justify-between mb-[16px]">
    <div className="font-[500] text-[20px]">
      {I18n.t('navigation_workspace_library')}
    </div>
    <Menu
      position="bottomRight"
      className="w-120px mt-4px mb-4px"
      render={
        <Menu.SubMenu mode="menu">
          {entityConfigs.map(config => config.renderCreateMenu?.() ?? null)}
        </Menu.SubMenu>
      }
    >
      <Button
        theme="solid"
        type="primary"
        icon={<IconCozPlus />}
        data-testid="workspace.library.header.create"
      >
        {I18n.t('library_resource')}
      </Button>
    </Menu>
  </div>
);

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 { 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 { PlaygroundApi } from '@coze-arch/bot-api';
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 recordRef = useRef<ResourceInfo | null>(null);

  const { open: openCreatePrompt, node: promptConfiguratorModal } =
    usePromptConfiguratorModal({
      spaceId,
      source: 'resource_library',
      onUpdateSuccess: reloadList,
    });

  // delete
  const { run: delPrompt } = useRequest(
    (promptId: string) =>
      PlaygroundApi.DeletePromptResource({
        prompt_resource_id: promptId,
      }),
    {
      manual: true,
      onSuccess: () => {
        reloadList();
        Toast.success(I18n.t('Delete_success'));
      },
    },
  );

  return {
    modals: (
      <>
        {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)}
        />
      ),
    },
  };
};

5. 提示词配置弹窗(PromptConfiguratorModal)

文件位置:frontend/packages/common/prompt-kit/base/src/create-prompt/prompt-configurator-modal.tsx

提示词创建和编辑的主要界面:

typescript 复制代码
import { useEffect, useRef, Suspense, lazy, useState } from 'react';

import classNames from 'classnames';
import {
  useEditor,
  ActiveLinePlaceholder,
  Placeholder,
} from '@coze-editor/editor/react';
import { type EditorAPI } from '@coze-editor/editor/preset-prompt';
import { Modal, Form, Toast, type FormApi } from '@coze-arch/coze-design';
import { sendTeaEvent, EVENT_NAMES } from '@coze-arch/bot-tea';
import { PlaygroundApi } from '@coze-arch/bot-api';
import { I18n } from '@coze-arch/i18n';

import { PromptEditorRender } from '@/editor';

import { type PromptConfiguratorModalProps } from './types';
import { PromptConfiguratorProvider } from './context';
import { PromptInfoInput } from './components/prompt-info-input';
import { PromptHeader } from './components/header';
import {
  CloseModal,
  PromptDiff,
  SavePrompt,
} from './components/footer-actions';

interface PromptValues {
  id?: string;
  name: string;
  description: string;
  prompt_text?: string;
}

export const PromptConfiguratorModal = (
  props: PromptConfiguratorModalProps,
) => {
  const {
    mode,
    editId,
    spaceId,
    botId,
    projectId,
    workflowId,
    canEdit,
    onUpdateSuccess,
    promptSectionConfig,
    enableDiff,
    onDiff,
    defaultPrompt,
    source,
    containerAppendSlot,
  } = props;
  const formApiRef = useRef<FormApi | null>(null);
  const editor = useEditor<EditorAPI>();
  const [modalMode, setModalMode] = useState<'info' | 'edit' | 'create'>(mode);
  const [errMsg, setErrMsg] = useState('');
  const isSubmiting = useRef(false);
  const [actionBarVisible, setActionBarVisible] = useState(false);
  const selectionInInputSlotRef = useRef(false);
  const isReadOnly = modalMode === 'info';
  const {
    editorPlaceholder,
    editorActions,
    headerActions,
    editorActiveLinePlaceholder,
    editorExtensions,
  } = promptSectionConfig ?? {};
  const [formValues, setFormValues] = useState<PromptValues>({
    name: '',
    description: '',
    prompt_text: '',
  });

  const handleSubmit = async (e: React.MouseEvent<Element, MouseEvent>) => {
    if (isSubmiting.current) {
      return;
    }
    const submitValues = await formApiRef.current?.validate();
    if (!submitValues) {
      return;
    }
    isSubmiting.current = true;
    if (modalMode === 'info') {
      handleInfoModeAction();
      return;
    }

    if (modalMode === 'create' || modalMode === 'edit') {
      const result = await handleUpdateModeAction(e);
      isSubmiting.current = false;
      sendTeaEvent(EVENT_NAMES.prompt_library_front, {
        bot_id: botId,
        project_id: projectId,
        workflow_id: workflowId,
        space_id: spaceId,
        prompt_id: result?.id ?? '',
        prompt_type: 'workspace',
        action: mode,
        source,
      });
      return result;
    }
    isSubmiting.current = false;
  };

  const handleUpdateModeAction = async (
    e: React.MouseEvent<Element, MouseEvent>,
  ) => {
    try {
      const submitValues = await formApiRef.current?.validate();
      if (!submitValues) {
        return;
      }
      const res = await PlaygroundApi.UpsertPromptResource(
        {
          prompt: {
            ...submitValues,
            space_id: spaceId,
            ...(modalMode === 'edit' && { id: editId }),
          },
        },
        {
          __disableErrorToast: true,
        },
      );
      props.onCancel?.(e);
      const id = modalMode === 'edit' ? editId : res?.data?.id;
      if (mode === 'create') {
        Toast.success(I18n.t('prompt_library_prompt_creat_successfully'));
      }
      onUpdateSuccess?.(mode, id);
      if (!id) {
        return;
      }
      return {
        mode,
        id,
      };
    } catch (error) {
      setErrMsg((error as Error).message);
    }
  };

  return (
    <PromptConfiguratorProvider props={props}>
      <Modal {...props}>
        <Form ref={formApiRef} layout="vertical">
          <PromptInfoInput />
          <PromptEditorRender
            placeholder={editorPlaceholder}
            getEditor={(api) => {
              // 编辑器初始化逻辑
            }}
          />
        </Form>
      </Modal>
    </PromptConfiguratorProvider>
  );
};

6. 提示词信息输入组件(PromptInfoInput)

文件位置:frontend/packages/common/prompt-kit/base/src/create-prompt/components/prompt-info-input.tsx

处理提示词名称和描述的输入:

typescript 复制代码
export const PromptInfoInput: React.FC = () => {
  return (
    <>
      <Form.Item
        name="name"
        label={I18n.t('prompt_name')}
        rules={[
          { required: true, message: I18n.t('prompt_name_required') },
          { max: 50, message: I18n.t('prompt_name_too_long') },
        ]}
      >
        <Input placeholder={I18n.t('enter_prompt_name')} />
      </Form.Item>
      
      <Form.Item
        name="description"
        label={I18n.t('prompt_description')}
      >
        <Input.TextArea 
          placeholder={I18n.t('enter_prompt_description')}
          rows={3}
          maxLength={200}
        />
      </Form.Item>
    </>
  );
};

7. 提示词编辑器组件(PromptEditorRender)

文件位置:frontend/packages/common/prompt-kit/base/src/editor/render.tsx

基于CodeMirror的专业提示词编辑器:

typescript 复制代码
import { useCallback, useRef, useEffect, type ReactNode, useMemo } from 'react';

import { merge } from 'lodash-es';
import { Renderer, Placeholder, useEditor } from '@coze-editor/editor/react';
import promptPreset, {
  type EditorAPI,
} from '@coze-editor/editor/preset-prompt';
import { ThemeExtension } from '@coze-common/editor-plugins/theme';
import { SyntaxHighlight } from '@coze-common/editor-plugins/syntax-highlight';
import { LanguageSupport } from '@coze-common/editor-plugins/language-support';

import { defaultTheme } from '@/theme/default';

export interface PromptEditorRenderProps {
  readonly?: boolean;
  placeholder?: ReactNode;
  className?: string;
  dataTestID?: string;
  defaultValue?: string;
  fontSize?: number;
  value?: string;
  onChange?: (value: string) => void;
  onFocus?: () => void;
  onBlur?: () => void;
  options?: Record<string, string | number>;
  isControled?: boolean;
  getEditor?: (editor: EditorAPI) => void;
}

export const PromptEditorRender: React.FC<PromptEditorRenderProps> = props => {
  const {
    readonly,
    placeholder,
    defaultValue,
    className,
    dataTestID,
    value,
    onChange,
    onFocus,
    onBlur,
    options,
    isControled,
    getEditor,
  } = props;
  const apiRef = useRef<EditorAPI | null>(null);
  const editor = useEditor<EditorAPI>();

  useEffect(() => {
    if (!editor || !onBlur) {
      return;
    }

    editor.$on('blur', onBlur);

    return () => {
      editor.$off('blur', onBlur);
    };
  }, [editor, onBlur]);

  useEffect(() => {
    if (!editor || !onFocus) {
      return;
    }

    editor.$on('focus', onFocus);

    return () => {
      editor.$off('focus', onFocus);
    };
  }, [editor, onFocus]);

  // value controlled
  useEffect(() => {
    const curEditor = apiRef.current;

    if (!curEditor || !isControled || !editor) {
      return;
    }

    const preVal = curEditor.getValue();
    if (typeof value === 'string' && value !== preVal) {
      editor.$view.dispatch({
        changes: {
          from: 0,
          to: editor.$view.state.doc.length,
          insert: value ?? '',
        },
      });
    }
  }, [isControled, value, editor]);

  const handleChange = useCallback(
    (e: { value: string }) => {
      if (typeof onChange === 'function') {
        onChange(e.value);
      }
    },
    [onChange],
  );

  const contentAttributes = useMemo(
    () => ({
      class: className ?? '',
      'data-testid': dataTestID ?? '',
    }),
    [className, dataTestID],
  );

  return (
    <>
      <Renderer
        plugins={promptPreset}
        defaultValue={defaultValue}
        options={merge(
          {
            fontSize: 14,
            contentAttributes,
            editable: !readonly,
            readOnly: readonly,
          },
          options,
        )}
        onChange={handleChange}
        didMount={api => {
          apiRef.current = api;
          if (getEditor) {
            getEditor(api);
          }
        }}
      />
      <Placeholder>{placeholder}</Placeholder>
      <ThemeExtension themes={[defaultTheme]} />
      <LanguageSupport />
      <SyntaxHighlight.Markdown />
      <SyntaxHighlight.Jinja />
    </>
  );
};

设计亮点

  • 状态集中管理 :通过 usePatOperation Hook统一管理组件状态
  • 组件解耦:各子组件职责明确,通过props进行通信
  • 数据流清晰:单向数据流,状态变更可追踪

创建提示词逻辑

1. 表单验证系统

文件位置:frontend/packages/common/prompt-kit/base/src/utils/validation.ts

表单验证规则:

typescript 复制代码
export interface ValidationRule {
  required?: boolean;
  maxLength?: number;
  minLength?: number;
  pattern?: RegExp;
  validator?: (value: any) => Promise<void> | void;
  message?: string;
}

export const promptValidationRules = {
  name: [
    {
      required: true,
      message: '请输入提示词名称',
    },
    {
      maxLength: 50,
      message: '提示词名称不能超过50个字符',
    },
    {
      pattern: /^[\u4e00-\u9fa5a-zA-Z0-9_\-\s]+$/,
      message: '提示词名称只能包含中文、英文、数字、下划线和连字符',
    },
  ],
  description: [
    {
      maxLength: 200,
      message: '描述不能超过200个字符',
    },
  ],
  promptText: [
    {
      required: true,
      message: '请输入提示词内容',
    },
    {
      minLength: 10,
      message: '提示词内容至少需要10个字符',
    },
    {
      maxLength: 10000,
      message: '提示词内容不能超过10000个字符',
    },
  ],
};

export const validatePromptForm = async (values: any) => {
  const errors: Record<string, string> = {};
  
  // 验证名称
  for (const rule of promptValidationRules.name) {
    try {
      await validateField(values.name, rule);
    } catch (error) {
      errors.name = error.message;
      break;
    }
  }
  
  // 验证描述
  for (const rule of promptValidationRules.description) {
    try {
      await validateField(values.description, rule);
    } catch (error) {
      errors.description = error.message;
      break;
    }
  }
  
  // 验证提示词内容
  for (const rule of promptValidationRules.promptText) {
    try {
      await validateField(values.promptText, rule);
    } catch (error) {
      errors.promptText = error.message;
      break;
    }
  }
  
  if (Object.keys(errors).length > 0) {
    throw new Error(JSON.stringify(errors));
  }
};

const validateField = async (value: any, rule: ValidationRule) => {
  if (rule.required && (!value || value.trim() === '')) {
    throw new Error(rule.message || '此字段为必填项');
  }
  
  if (rule.maxLength && value && value.length > rule.maxLength) {
    throw new Error(rule.message || `长度不能超过${rule.maxLength}个字符`);
  }
  
  if (rule.minLength && value && value.length < rule.minLength) {
    throw new Error(rule.message || `长度不能少于${rule.minLength}个字符`);
  }
  
  if (rule.pattern && value && !rule.pattern.test(value)) {
    throw new Error(rule.message || '格式不正确');
  }
  
  if (rule.validator) {
    await rule.validator(value);
  }
};

设计亮点

  • 规则化验证:使用配置化的验证规则
  • 异步支持:支持异步验证器
  • 错误收集:统一收集和处理验证错误
  • 可扩展性:易于添加新的验证规则

2.核心逻辑

文件位置:frontend/packages/common/prompt-kit/base/src/components/prompt-configurator-modal.tsx

核心代码:

typescript 复制代码
export const PromptConfiguratorModal: FC<PromptConfiguratorModalProps> = ({
  visible,
  mode,
  editId,
  enableDiff,
  onClose,
  onSuccess,
}) => {
  const [form] = Form.useForm();
  const { editor, setEditor } = useEditor();
  const [loading, setLoading] = useState(false);

  // 获取提示词详情
  const { data: promptInfo, loading: fetchLoading } = useRequest(
    () => {
      if (editId && mode !== 'create') {
        return PlaygroundApi.GetPromptResourceInfo({ prompt_resource_id: editId });
      }
      return Promise.resolve(null);
    },
    {
      refreshDeps: [editId, mode],
      onSuccess: (data) => {
        if (data?.prompt_resource) {
          const { name, description, prompt_text } = data.prompt_resource;
          form.setFieldsValue({ name, description });
          editor?.commands.setContent(prompt_text || '');
        }
      },
    }
  );

  // 保存提示词
  const handleSave = async () => {
    try {
      setLoading(true);
      const values = await form.validateFields();
      const promptText = editor?.getText() || '';
      
      const promptData = {
        id: editId,
        name: values.name,
        description: values.description,
        prompt_text: promptText,
      };

      await PlaygroundApi.UpsertPromptResource({ prompt: promptData });
      onSuccess?.();
      onClose();
    } catch (error) {
      console.error('Save prompt failed:', error);
    } finally {
      setLoading(false);
    }
  };

  return (
    <Modal
      title={mode === 'create' ? '创建提示词' : mode === 'edit' ? '编辑提示词' : '查看提示词'}
      visible={visible}
      onCancel={onClose}
      footer={mode === 'info' ? null : [
        <Button key="cancel" onClick={onClose}>取消</Button>,
        <Button key="save" type="primary" loading={loading} onClick={handleSave}>
          保存
        </Button>,
      ]}
    >
      <Form form={form} layout="vertical">
        <PromptInfoInput
          field="name"
          label="提示词名称"
          required
          readonly={mode === 'info'}
          maxLength={50}
        />
        <PromptInfoInput
          field="description"
          label="描述"
          rows={3}
          readonly={mode === 'info'}
          maxLength={200}
        />
      </Form>
      <PromptEditorRender
        readonly={mode === 'info'}
        placeholder="请输入提示词内容..."
        onChange={(content) => {
          // 实时更新编辑器内容
        }}
      />
    </Modal>
  );
};
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接口实现 这个章节。

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 PromptResource {
    1: optional i64 ID (agw.js_conv="str",api.js_conv="true",api.body="id")
    2: optional i64 SpaceID (agw.js_conv="str",api.js_conv="true",api.body="space_id")
    3: optional string Name (api.body="name")
    4: optional string Description (api.body="description")
    5: optional string PromptText (api.body="prompt_text")
}

struct UpsertPromptResourceRequest {
    1: required PromptResource Prompt (api.body="prompt")

    255: base.Base Base (api.none="true")
}

struct UpsertPromptResourceResponse {
    1: optional ShowPromptResource data
    253: required i64    code
    254: required string msg
    255: required base.BaseResp BaseResp
}

struct ShowPromptResource {
    1: i64 ID (agw.js_conv="str",api.js_conv="true",api.body="id")
}

源码作用:定义PAT权限添加令牌相关的数据结构

创建提示词-IDL接口定义(playground_service.thrift)

文件路径:idl\playground\playground_service.thrift

核心代码:

thrift 复制代码
include "../base.thrift"

include "prompt_resource.thrift"

namespace go playground

service PlaygroundService {

    prompt_resource.UpsertPromptResourceResponse UpsertPromptResource(1:prompt_resource.UpsertPromptResourceRequest request)(api.post='/api/playground_api/upsert_prompt_resource', api.category="prompt_resource",agw.preserve_base="true")   

}

源码作用:定义提示词资源创建、更新、删除和获取相关的接口

创建提示词-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 || '';
  }

  /** POST /api/playground_api/upsert_prompt_resource */
  UpsertPromptResource(
    req: prompt_resource.UpsertPromptResourceRequest,
    options?: T,
  ): Promise<prompt_resource.UpsertPromptResourceResponse> {
    const _req = req;
    const url = this.genBaseURL('/api/playground_api/upsert_prompt_resource');
    const method = 'POST';
    const data = { prompt: _req['prompt'] };
    return this.request({ url, method, data }, options);
  }
  
  // ... 其他API方法
}

代码作用:PlaygroundApiService 类有成员函数 UpsertPromptResource 。

这个方法用于创建提示词。

此文件是基于playground_service.thrift自动生成的,开发者无需手动修改。

创建提示词--结构体实现(prompt_resource.thrift)

文件路径:frontend\packages\arch\idl\src\auto-generated\playground_api\namespaces\prompt_resource.ts

thrift 复制代码
export interface PromptResource {
  id?: string;
  space_id?: string;
  name?: string;
  description?: string;
  prompt_text?: string;
}

export interface ShowPromptResource {
  id?: string;
}

export interface UpsertPromptResourceRequest {
  prompt: PromptResource;
}

export interface UpsertPromptResourceResponse {
  data?: ShowPromptResource;
  code: Int64;
  msg: string;
}

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.thriftpassport.thrift)的核心工具,确保了整个项目中API代码生成的一致性。

结语

Coze Studio的创建提示词功能是现代前端开发的优秀实践案例,它不仅展现了技术实现的专业性,更体现了对用户体验的深度思考。通过对其源码的深入分析,我们可以学习到:

  1. 架构设计的重要性:良好的架构是项目成功的基础
  2. 用户体验的价值:技术服务于用户,体验决定产品成败
  3. 工程化的必要性:规范的流程和工具是质量的保障
  4. 持续优化的意识:性能和安全需要持续关注和改进
  5. 团队协作的力量:标准化和文档化是团队效率的关键

这个功能的实现为我们提供了宝贵的学习资源,无论是技术架构、代码实现还是工程实践,都值得深入研究和借鉴。在未来的项目开发中,我们可以参考这些最佳实践,构建更加优秀的前端应用。与优化建议

架构优势

  1. 模块化设计

    • 组件职责清晰,易于维护和扩展
    • 状态管理集中化,避免状态混乱
    • API层抽象良好,便于测试和替换
  2. 类型安全

    • 全面的TypeScript类型定义
    • 接口规范统一,减少运行时错误
    • 编译时类型检查,提高代码质量
  3. 用户体验

    • 实时预览功能,所见即所得
    • 语法高亮支持,提升编辑体验
    • 表单验证完善,减少用户错误
  4. 性能优化

    • 防抖节流机制,优化用户交互
    • 智能缓存策略,减少网络请求
    • 懒加载支持,提升页面加载速度

可优化方向

  1. 代码分割

    typescript 复制代码
    // 建议使用动态导入优化首屏加载
    const PromptEditor = lazy(() => import('./PromptEditor'));
  2. 错误边界

    typescript 复制代码
    // 添加错误边界组件
    class PromptErrorBoundary extends Component {
      componentDidCatch(error: Error, errorInfo: ErrorInfo) {
        // 错误上报和处理
      }
    }
  3. 国际化支持

    typescript 复制代码
    // 支持多语言
    const { t } = useTranslation('prompt');
  4. 可访问性增强

    typescript 复制代码
    // 添加ARIA标签和键盘导航
    <button aria-label={t('create-prompt')} />

技术栈总结

  • 前端框架:React + TypeScript
  • 状态管理:Zustand + Custom Hooks
  • UI组件:自定义组件库
  • 编辑器:CodeMirror扩展
  • API通信:Axios + RESTful API
  • 构建工具:Vite + ESBuild
  • 代码质量:ESLint + Prettier + Husky

最佳实践与开发规范

组件开发规范
  1. 组件命名

    • 使用PascalCase命名组件文件
    • 组件名称应该清晰表达其功能
    • 避免使用缩写,保持名称的可读性
  2. 文件组织

    复制代码
    src/
    ├── components/
    │   ├── PromptEditor/
    │   │   ├── index.tsx
    │   │   ├── PromptEditor.tsx
    │   │   ├── PromptEditor.module.css
    │   │   └── types.ts
    │   └── PromptLibrary/
    ├── hooks/
    │   ├── usePromptConfig.ts
    │   └── usePromptValidation.ts
    ├── utils/
    │   ├── validation.ts
    │   └── performance.ts
    └── types/
        └── prompt.ts
  3. 代码风格

    • 使用TypeScript严格模式
    • 遵循ESLint和Prettier配置
    • 保持函数单一职责原则
    • 使用有意义的变量和函数名
状态管理最佳实践
  1. Hook设计原则

    typescript 复制代码
    // ✅ 好的Hook设计
    const usePromptEditor = () => {
      const [content, setContent] = useState('');
      const [isValid, setIsValid] = useState(false);
      
      const validateContent = useCallback((value: string) => {
        // 验证逻辑
      }, []);
      
      return {
        content,
        setContent,
        isValid,
        validateContent,
      };
    };
  2. 状态更新模式

    typescript 复制代码
    // ✅ 使用函数式更新
    setPrompts(prev => [...prev, newPrompt]);
    
    // ❌ 避免直接修改状态
    prompts.push(newPrompt);
性能优化指南
  1. 组件优化

    typescript 复制代码
    // 使用React.memo优化组件
    const PromptItem = React.memo(({ prompt, onEdit }) => {
      return (
        <div onClick={() => onEdit(prompt.id)}>
          {prompt.name}
        </div>
      );
    });
  2. 事件处理优化

    typescript 复制代码
    // 使用useCallback缓存事件处理函数
    const handleEdit = useCallback((id: string) => {
      // 编辑逻辑
    }, []);
错误处理策略
  1. 边界错误处理

    typescript 复制代码
    class PromptErrorBoundary extends Component {
      state = { hasError: false };
      
      static getDerivedStateFromError(error: Error) {
        return { hasError: true };
      }
      
      componentDidCatch(error: Error, errorInfo: ErrorInfo) {
        console.error('Prompt component error:', error, errorInfo);
      }
    }
  2. API错误处理

    typescript 复制代码
    const handleApiError = (error: any) => {
      if (error.response?.status === 401) {
        // 处理认证错误
      } else if (error.response?.status >= 500) {
        // 处理服务器错误
      } else {
        // 处理其他错误
      }
    };

工具函数

提示词处理工具

frontend/packages/common/prompt-kit/base/src/utils/prompt.ts 提供了完整的提示词处理功能:

typescript:d:\coze_829\coze-studio-main\frontend\packages\common\prompt-kit\base\src\utils\prompt.ts 复制代码
// 提示词类型枚举
export enum PromptType {
  SYSTEM = 'system',
  USER = 'user',
  ASSISTANT = 'assistant',
  FUNCTION = 'function',
}

// 提示词变量解析
export const parsePromptVariables = (content: string): string[] => {
  const variableRegex = /\{\{([^}]+)\}\}/g;
  const variables: string[] = [];
  let match;
  
  while ((match = variableRegex.exec(content)) !== null) {
    const variable = match[1].trim();
    if (!variables.includes(variable)) {
      variables.push(variable);
    }
  }
  
  return variables;
};

// 提示词内容验证
export const validatePromptContent = (content: string): {
  isValid: boolean;
  errors: string[];
} => {
  const errors: string[] = [];
  
  if (!content.trim()) {
    errors.push('提示词内容不能为空');
  }
  
  if (content.length > 10000) {
    errors.push('提示词内容不能超过10000个字符');
  }
  
  // 检查变量格式
  const invalidVariables = content.match(/\{[^}]*\}/g)?.filter(v => !v.match(/^\{\{[^}]+\}\}$/));
  if (invalidVariables?.length) {
    errors.push('变量格式错误,应使用 {{变量名}} 格式');
  }
  
  return {
    isValid: errors.length === 0,
    errors,
  };
};

// 提示词预览渲染
export const renderPromptPreview = (content: string, variables: Record<string, string>): string => {
  let rendered = content;
  
  Object.entries(variables).forEach(([key, value]) => {
    const regex = new RegExp(`\\{\\{\\s*${key}\\s*\\}\\}`, 'g');
    rendered = rendered.replace(regex, value || `{{${key}}}`);
  });
  
  return rendered;
};

设计亮点

  • 变量解析精确:准确识别和提取提示词中的变量
  • 内容验证完善:多维度验证提示词内容的有效性
  • 预览功能强大:支持实时预览变量替换效果
  • 类型安全:完整的TypeScript类型定义

性能优化

1. 组件渲染优化

条件渲染

typescript 复制代码
// 根据状态条件渲染组件
{showPromptModal && (
  <PromptConfiguratorModal
    visible={showPromptModal}
    mode={modalMode}
    // ... props
  />
)}

{!!previewData && (
  <PromptPreviewModal
    visible={!!previewData}
    data={previewData}
    // ... props
  />
)}

状态最小化

typescript 复制代码
// 只在必要时更新状态
const openCreateModal = useMemoizedFn(() => {
  setModalMode('create');
  setEditData(undefined);  // 清理编辑数据
  setShowPromptModal(true);
});
2. 事件处理优化

函数缓存

typescript 复制代码
// 使用useMemoizedFn缓存事件处理函数
const handlePromptSave = useMemoizedFn(async (promptData: PromptData) => {
  try {
    await PlaygroundApi.UpsertPromptResource(promptData);
    refreshPromptList();
  } catch (error) {
    console.error('保存提示词失败:', error);
  }
});

防抖处理

typescript 复制代码
// 对提示词内容变化进行防抖处理
const debouncedValidation = useDebounce((content: string) => {
  const { isValid, errors } = validatePromptContent(content);
  setValidationResult({ isValid, errors });
}, 300);

3. 数据管理优化

useRequest状态管理

typescript 复制代码
const { loading, run: fetchPrompts } = useRequest(fetchPromptList, {
  manual: true,
  onSuccess: responseData => {
    setPromptList(responseData?.data?.prompts || []);
  },
});

状态同步

typescript 复制代码
// 操作成功后同步更新列表
const handleDelete = async (id: string) => {
  await PlaygroundApi.DeletePromptResource({ id });
  fetchPrompts(); // 重新获取提示词列表
};

用户体验设计

1. 即时反馈

操作状态反馈

typescript 复制代码
// 保存成功提示
Toast.success({
  content: I18n.t('prompt_saved_successfully'),
  showClose: false,
});

// 加载状态显示
<PromptTable loading={loading} dataSource={promptList || []} />

实时预览

typescript 复制代码
// 根据提示词内容实时更新预览
const previewContent = useMemo(() => {
  return renderPromptPreview(content, variables);
}, [content, variables]);

<PromptPreview
  content={previewContent}
  className={classNames(styles['preview-panel'], {
    [styles['preview-error']]: !isValid,
  })}
/>
2. 交互优化

安全确认

typescript 复制代码
// 删除提示词需要确认
<Popconfirm
  onConfirm={() => onDelete(`${record?.id}`)}
  content={I18n.t('delete_prompt_confirm')}
  title={I18n.t('delete_prompt_warning')}
>
  <UIButton icon={<IconCozMinusCircle />} />
</Popconfirm>

智能提示

typescript 复制代码
// 变量输入提示
<Tooltip content={I18n.t('variable_format_hint')}>
  <PromptEditor
    value={content}
    onChange={handleContentChange}
    placeholder="请输入提示词内容,使用 {{变量名}} 定义变量"
  />
</Tooltip>
3. 空状态处理

引导式空状态

typescript 复制代码
<UIEmpty
  title={I18n.t('no_prompts')}
  description={I18n.t('no_prompts_description')}
  action={
    <Button onClick={() => openCreateModal()} theme="solid" type="primary">
      {I18n.t('create_first_prompt')}
    </Button>
  }
/>

安全性设计

1. 内容安全

敏感信息过滤

typescript 复制代码
// 检测和过滤敏感信息
const filterSensitiveContent = (content: string): string => {
  // 过滤可能的API密钥、密码等敏感信息
  const sensitivePatterns = [
    /api[_-]?key[\s]*[:=][\s]*['"]?[\w\-]{20,}['"]?/gi,
    /password[\s]*[:=][\s]*['"]?[\w\-]{8,}['"]?/gi,
    /token[\s]*[:=][\s]*['"]?[\w\-]{20,}['"]?/gi,
  ];
  
  let filtered = content;
  sensitivePatterns.forEach(pattern => {
    filtered = filtered.replace(pattern, '[敏感信息已隐藏]');
  });
  
  return filtered;
};

内容审核

typescript 复制代码
// 提示词内容审核
const auditPromptContent = async (content: string): Promise<{
  isApproved: boolean;
  warnings: string[];
}> => {
  const warnings: string[] = [];
  
  // 检查是否包含不当内容
  if (content.includes('恶意') || content.includes('攻击')) {
    warnings.push('内容可能包含不当信息');
  }
  
  return {
    isApproved: warnings.length === 0,
    warnings,
  };
};
2. 操作权限

权限验证

typescript 复制代码
// 基于用户权限控制操作
const canEditPrompt = (prompt: PromptResource, user: User): boolean => {
  return prompt.creator_id === user.id || user.role === 'admin';
};

<UIButton 
  disabled={!canEditPrompt(prompt, currentUser)}
  onClick={() => onEdit(prompt)}
/>

资源访问控制

typescript 复制代码
// 检查用户是否有权限访问特定提示词
const checkPromptAccess = (promptId: string, userId: string): boolean => {
  // 实现权限检查逻辑
  return hasPermission(userId, 'prompt:read', promptId);
};
3. 输入验证

表单验证

typescript 复制代码
<Form.Input
  field="name"
  maxLength={100}
  rules={[
    { required: true, message: '提示词名称不能为空' },
    { pattern: /^[\u4e00-\u9fa5a-zA-Z0-9_\-\s]+$/, message: '名称包含非法字符' }
  ]}
/>

内容长度限制

typescript 复制代码
// 限制提示词内容长度
const validateContentLength = (content: string): boolean => {
  const maxLength = 10000; // 最大10000字符
  return content.length <= maxLength;
};

国际化支持

1. 文本国际化

统一文本管理

typescript 复制代码
// 所有用户可见文本都通过I18n管理
<h3>{I18n.t('prompt_library')}</h3>
<Button>{I18n.t('create_prompt')}</Button>
<p>{I18n.t('prompt_creation_guide')}</p>

动态文本生成

typescript 复制代码
// 支持参数化的国际化文本
label: I18n.t('prompt_variables_count', {
  count: variables.length,
  variables: variables.join(', '),
})
2. 内容格式化

本地化内容显示

typescript 复制代码
// 根据用户语言环境格式化提示词内容
export const formatPromptContent = (content: string, locale: string) => {
  // 根据语言环境调整内容显示
  if (locale === 'zh-CN') {
    return content.replace(/\n/g, '\n\n'); // 中文增加段落间距
  }
  return content;
};

// 格式化创建时间
export const formatCreatedTime = (timestamp: number, locale: string) => {
  return dayjs.unix(timestamp).locale(locale).format('YYYY-MM-DD HH:mm:ss');
};

架构设计最佳实践

1. 模块化设计

组件职责分离

  • LibraryHeader:负责顶部操作区域和创建按钮
  • PromptTable:负责提示词列表展示
  • PromptConfiguratorModal:负责提示词创建和编辑
  • PromptPreviewModal:负责提示词预览展示

Hook职责分离

  • usePromptConfig:提示词操作状态管理
  • usePromptList/useCreatePrompt等:API调用
  • 提示词工具函数:业务逻辑处理
2. 状态管理策略

集中式状态管理

typescript 复制代码
// 通过usePromptConfig集中管理操作状态
const {
  showPromptModal,
  modalMode,
  editData,
  openCreateModal,
  openEditModal,
  onCancel,
  onSaveSuccess,
  refresh,
} = usePromptConfig();

数据状态分离

typescript 复制代码
// API数据通过SWR独立管理
const { data: promptList, loading, mutate } = usePromptList();
3. 类型安全

完整的TypeScript支持

typescript 复制代码
// 接口类型定义
interface PromptTableProps {
  loading: boolean;
  dataSource: PromptResource[];
  onEdit?: (data?: PromptResource) => void;
  onDelete: (id: string) => void;
  onPreview: (data: PromptResource) => void;
}

// API响应类型
type UpsertPromptResourceResponseData = {
  prompt_resource: PromptResource;
  id: string;
};
相关推荐
fury_1233 小时前
vue3:el-date-picker三十天改成第二十九天的23:59:59
前端·javascript·vue.js
小周同学@3 小时前
DOM常见的操作有哪些?
前端·javascript
FutureUniant3 小时前
GitHub每日最火火火项目(9.3)
人工智能·计算机视觉·ai·github·音视频
文心快码BaiduComate3 小时前
5句话让文心快码实现一个大模型MBTI测试器
前端·后端·llm
橙某人3 小时前
💫分享一个CSS技巧:用径向渐变实现弯曲框缺口效果
前端·css
颜酱4 小时前
基于 Ant Design 的配置化表单开发指南
前端·javascript·react.js
anyup4 小时前
uni-app 项目创建方式有哪些,看这一篇就够了!
前端·vue.js·uni-app
数据猿4 小时前
华为第四届828 B2B企业节再升级,以AI打造产业生态
人工智能·华为