当前位置: 首页 > news >正文

Coze源码分析-资源库-编辑插件-前端源码-核心逻辑

编辑插件逻辑

编辑插件功能是Coze平台资源库的核心功能之一,允许用户修改已创建的插件配置,特别是针对App类型插件提供了在线编辑能力。整个编辑流程从资源库表格的插件行点击开始,根据插件子类型选择直接编辑或导航到详情页,最终保存更新。

1. 编辑操作入口 - usePluginConfig Hook

编辑操作的入口位于 usePluginConfig hook 中的 onItemClick 方法,该方法处理插件行的点击事件,根据插件子类型提供不同的编辑体验。

核心实现

export const usePluginConfig: UseEntityConfigHook = ({spaceId,reloadList,getCommonActions,
}) => {const [showFormPluginModel, setShowFormPluginModel] = useState(false);const navigate = useNavigate();// 使用插件编辑弹窗Hookconst { modal: editPluginCodeModal, open } = useBotCodeEditOutPlugin({modalProps: {onSuccess: reloadList, // 编辑成功后刷新列表},});return {modals: (<><CreateFormPluginModalisCreate={true}visible={showFormPluginModel}onSuccess={pluginID => {navigate(`/space/${spaceId}/plugin/${pluginID}`);reloadList();}}onCancel={() => {setShowFormPluginModel(false);}}/>{editPluginCodeModal}</>),config: {typeFilter: {label: I18n.t('library_resource_type_plugin'),value: ResType.Plugin,},renderCreateMenu: () => (<Menu.Itemdata-testid="workspace.library.header.create.plugin"icon={<IconCozPlugin />}onClick={() => {setShowFormPluginModel(true);}}>{I18n.t('library_resource_type_plugin')}</Menu.Item>),target: [ResType.Plugin],// 点击插件行的处理逻辑 - 编辑功能的核心入口onItemClick: (item: ResourceInfo) => {// 关键逻辑:只对App类型插件(res_sub_type=2)直接打开编辑弹窗if (item.res_type === ResType.Plugin &&item.res_sub_type === 2 //Plugin:1-Http; 2-App; 6-Local) {// 检查是否有删除权限作为编辑权限的参考const disable = !item.actions?.find(action => action.key === ActionKey.Delete,)?.enable;// 打开编辑弹窗open(item.res_id || '', disable);} else {// 其他类型插件导航到详情页navigate(`/space/${spaceId}/plugin/${item.res_id}`);}},

2. 插件编辑核心Hook - useBotCodeEditOutPlugin

文件位置:frontend/packages/agent-ide/bot-plugin/export/src/component/bot_edit/plugin-edit/index.tsx

这是插件编辑功能的核心Hook,负责管理编辑弹窗的显示和隐藏,处理插件锁定机制:

import { useCallback, useEffect, useMemo, useState } from 'react';import { I18n } from '@coze-arch/i18n';
import { UIButton } from '@coze-arch/bot-semi';
import { PluginDevelopApi } from '@coze-arch/bot-api';
import { type PluginInfoProps } from '@coze-studio/plugin-shared';
import {checkOutPluginContext,unlockOutPluginContext,
} from '@coze-studio/bot-plugin-store';import {CreateCodePluginModal,type CreatePluginProps,
} from '../bot-code-edit';import styles from './index.module.less';export const useBotCodeEditOutPlugin = ({modalProps,
}: {modalProps: Pick<CreatePluginProps, 'onSuccess'>;
}) => {// 插件信息状态const [pluginInfo, setPluginInfo] = useState<PluginInfoProps>({});// 弹窗可见状态const [modalVisible, setModalVisible] = useState(false);// 编辑状态const [editable, setEditable] = useState(false);// 禁用编辑状态const [disableEdit, setDisableEdit] = useState(false);const pluginId = pluginInfo?.plugin_id || '';// 生成操作按钮const action = useMemo(() => {if (disableEdit) {return null;}return (<div className={styles.actions}>{editable ? (// 取消编辑按钮<UIButtononClick={() => {setEditable(false);unlockOutPluginContext(pluginId);}}>{I18n.t('Cancel')}</UIButton>) : (// 开始编辑按钮<UIButtontheme="solid"onClick={async () => {const isLocked = await checkOutPluginContext(pluginId);if (isLocked) {return;}setEditable(true);}}>{I18n.t('Edit')}</UIButton>)}</div>);}, [editable, pluginId, disableEdit]);useEffect(() => {if (modalVisible) {setEditable(false);}}, [modalVisible]);const modal = (<CreateCodePluginModal{...modalProps}editInfo={pluginInfo}isCreate={false}visible={modalVisible}onCancel={() => {setModalVisible(false);if (!disableEdit) {unlockOutPluginContext(pluginId);}}}disabled={!editable}actions={action}/>);const open = useCallback(async (id: string, disable: boolean) => {const res = await PluginDevelopApi.GetPluginInfo({plugin_id: id || '',});setPluginInfo({plugin_id: id,code_info: {plugin_desc: res.code_info?.plugin_desc,/** yaml */openapi_desc: res.code_info?.openapi_desc,client_id: res.code_info?.client_id,client_secret: res.code_info?.client_secret,service_token: res.code_info?.service_token,},});setDisableEdit(disable);setModalVisible(true);}, []);return { modal, open };
};

3. 插件编辑弹窗 - CreateCodePluginModal

文件位置:frontend/packages/agent-ide/bot-plugin/export/src/component/bot_edit/bot-code-edit.tsx

这是插件编辑的核心界面组件,提供插件代码编辑功能:

import { useState, useEffect, type ReactNode } from 'react';import classNames from 'classnames';
import { type PluginInfoProps } from '@coze-studio/plugin-shared';
import { I18n } from '@coze-arch/i18n';
import { safeJSONParse } from '@coze-arch/bot-utils';
import { UIButton, UIModal, Toast, Space } from '@coze-arch/bot-semi';
import { PluginDevelopApi } from '@coze-arch/bot-api';import { Editor } from '../editor';import s from './index.module.less';export interface CreatePluginProps {visible: boolean;isCreate?: boolean;editInfo?: PluginInfoProps;disabled?: boolean;onCancel?: () => void;onSuccess?: (pluginId?: string) => void;actions?: ReactNode;projectId?: string;
}const INDENTATION_SPACES = 2;
const EDITOR_HEIGHT_MAX = 560;export const CreateCodePluginModal: React.FC<CreatePluginProps> = props => {const {isCreate = true,onCancel,editInfo,visible,onSuccess,disabled = false,actions,projectId,} = props;const [aiPlugin, setAiPlugin] = useState<string | undefined>();const [clientId, setClientId] = useState<string | undefined>();const [clientSecret, setClientSecret] = useState<string | undefined>();const [serviceToken, setServiceToken] = useState<string | undefined>();const [openApi, setOpenApi] = useState<string | undefined>();useEffect(() => {/** Reset pop-up data every time you open it */if (visible) {//Format jsonconst desc = JSON.stringify(safeJSONParse(editInfo?.code_info?.plugin_desc),null,INDENTATION_SPACES,);setAiPlugin(desc || '');setOpenApi(editInfo?.code_info?.openapi_desc || '');setClientId(editInfo?.code_info?.client_id);setClientSecret(editInfo?.code_info?.client_secret);setServiceToken(editInfo?.code_info?.service_token);}}, [visible]);const registerPlugin = async () => {const params = {ai_plugin: aiPlugin,client_id: clientId,client_secret: clientSecret,service_token: serviceToken,openapi: openApi,};let res;if (isCreate) {// 调用注册插件API,用于创建新插件// API路径: /api/developer/register// 必需参数: space_id, ai_plugin, openapires = await PluginDevelopApi.RegisterPlugin({...params,project_id: projectId,space_id: useSpaceStore.getState().getSpaceId(), // 空间ID参数});} else {// 调用更新插件API,用于修改现有插件// API路径: /api/developer/update// 必需参数: plugin_id, ai_plugin, openapiawait PluginDevelopApi.UpdatePlugin({...params,plugin_id: editInfo?.plugin_id,edit_version: editInfo?.edit_version,source_code: editInfo?.code_info?.source_code, // 源代码参数});}Toast.success({content: isCreate? I18n.t('register_success'): I18n.t('Plugin_update_success'),showClose: false,});onSuccess?.(res?.data?.plugin_id); // 创建成功时返回插件IDonCancel?.(); // 关闭弹窗};return (<UIModalfullScreenclassName="full-screen-modal"title={<div className={s['bot-code-edit-title-action']}><span>{isCreate ? I18n.t('plugin_create') : I18n.t('plugin_Update')}</span><div>{actions}</div></div>}visible={visible}onCancel={() => onCancel?.()}footer={!disabled ? (<Space><UIButton type="tertiary" onClick={() => onCancel?.()}>{I18n.t('Cancel')}</UIButton><UIButton type="primary" onClick={registerPlugin}>{I18n.t('Confirm')}</UIButton></Space>) : null}maskClosable={false}><div className={classNames(s.flex)}><div className={classNames(s['plugin-height'], s.flex5)}><div style={{ display: 'flex' }}><div style={{ flex: 1, borderRight: '1px solid rgb(215,218,221)' }}><div className={s.title}>{I18n.t('ai_plugin_(fill_in_json)_*')}</div><EditordataTestID="create-plugin-code-editor-json"disabled={disabled}theme="tomorrow"mode="json"height={EDITOR_HEIGHT_MAX}value={aiPlugin}useValidate={false}onChange={e => setAiPlugin(e)}/></div><div style={{ flex: 1 }}><div className={s.title}>{I18n.t('openapi_(fill_in_yaml)_*')}</div><EditordataTestID="create-plugin-code-editor-yaml"disabled={disabled}theme="tomorrow"mode="yaml"height={EDITOR_HEIGHT_MAX}value={openApi}useValidate={false}onChange={e => setOpenApi(e)}/></div></div></div></div></UIModal>);
};

4. 插件编辑流程的核心实现

插件编辑功能的核心实现包括以下几个关键步骤:

  1. 插件行点击事件处理:在usePluginConfigonItemClick方法中,根据插件类型决定打开编辑弹窗或导航到详情页

  2. 插件锁定机制:使用CheckOutPluginContext确保同一时间只有一个用户可以编辑插件,避免冲突

  3. 插件数据加载:通过PluginDevelopApi.GetPluginInfo获取插件详情,为编辑提供数据支持

  4. 编辑弹窗管理:使用useBotCodeEditOutPluginuseBotCodeEditModal管理编辑弹窗的显示、隐藏和数据传递

  5. 资源释放处理:编辑完成或组件卸载时,通过UnlockOutPluginContext释放插件锁定

  6. 成功回调处理:编辑成功后触发onSuccess回调,刷新资源列表,保持数据一致性

5. 插件编辑的API调用

插件编辑过程中主要使用的API:

  • GetPluginInfo:获取插件详情信息,为编辑提供基础数据
  • CheckOutPluginContext:检查并锁定插件,确保编辑时的资源独占性
  • UnlockOutPluginContext:释放插件锁定,允许其他用户编辑
  • UpdatePlugin:保存插件编辑内容(在编辑弹窗内部调用)

6. 错误处理和边界情况

插件编辑功能对各种边界情况进行了完善的处理:

  • 插件锁定失败:当插件已被其他用户锁定时,提示用户稍后再试
  • API调用失败:提供清晰的错误提示,帮助用户理解问题原因
  • 组件卸载处理:组件卸载时确保释放插件锁定,避免资源泄漏
  • 异步操作状态管理:通过loading状态提供清晰的用户反馈

7. 技术亮点总结

插件编辑功能的技术亮点包括:

  • 统一的配置管理:通过usePluginConfig统一管理插件资源的配置和行为
  • 灵活的类型处理:针对不同类型的插件提供差异化的编辑体验
  • 安全的锁定机制:使用插件锁定确保编辑操作的安全性和一致性
  • 清晰的组件结构:组件职责分离,便于维护和扩展
  • 完善的错误处理:对各种边界情况进行了全面处理
  • 响应式状态管理:通过React Hooks管理组件状态和副作用

这些设计确保了插件编辑功能的安全性、可靠性和用户体验的友好性,为开发者提供了便捷高效的插件管理工具。
/>

) : null}

{/* 删除按钮(带确认弹窗) */}
{!deleteProps.hide && (<Popconfirmtitle={i18n.t('delete_title')}content={i18n.t('delete_desc')}onConfirm={deleteProps?.handler}  // 确认后执行删除disabled={deleteProps.disabled}><Tooltip content={i18n.t('Delete')} position="top"><UIIconButtonicon={<IconDeleteOutline />}disabled={deleteProps.disabled}/></Tooltip></Popconfirm>
)}
); ```

设计特点

  • 操作优先级:编辑操作位于中间位置,便于用户快速访问
  • 交互差异:编辑直接触发,删除需要确认,符合用户习惯
  • 权限集成:所有操作都支持基于权限的禁用控制
  • 视觉反馈:禁用状态有明显的视觉区分
  • 事件隔离:阻止操作按钮事件冒泡到表格行

4. 编辑功能集成 - usePromptConfig Hook

usePromptConfig 是资源库页面中提示词相关功能的核心Hook,负责集成编辑弹窗、权限控制、表格配置等功能。

文件位置frontend/packages/studio/workspace/entry-base/src/pages/library/hooks/use-entity-configs/use-prompt-config.tsx

4.1 Hook依赖与初始化
import { useNavigate } from 'react-router-dom';
import { useRef } from 'react';
import { useRequest } from 'ahooks';
import { ActionKey, ResType, type ResourceInfo } from '@coze-arch/idl/plugin_develop';
import { usePromptConfiguratorModal } from '@coze-common/prompt-kit/main';
import { PlaygroundApi } from '@coze-arch/bot-api';
import { Table, Menu, Toast } from '@coze-arch/coze-design';const { TableAction } = Table;export const usePromptConfig: UseEntityConfigHook = ({spaceId,           // 工作空间IDisPersonalSpace,   // 是否个人空间reloadList,        // 列表刷新函数getCommonActions,  // 通用操作获取函数
}) => {const navigate = useNavigate();const [FLAGS] = useFlags();const recordRef = useRef<ResourceInfo | null>(null);const { open: openSelectIntelligenceModal, node: selectIntelligenceModal } =useSelectIntelligenceModal({spaceId,onSelect: (intelligence: IntelligenceData) => {const targetId = intelligence.basic_info?.id;const diffPromptResourceId = recordRef.current?.res_id;navigate(`/space/${spaceId}/bot/${targetId}`, {replace: true,state: {mode: 'diff',diffPromptResourceId,targetId,},});sendTeaEvent(EVENT_NAMES.compare_mode_front, {bot_id: targetId,compare_type: 'prompts',from: 'prompt_resource',source: 'bot_detail_page',action: 'start',});},});const { open: openCreatePrompt, node: promptConfiguratorModal } =usePromptConfiguratorModal({spaceId,source: 'resource_library',enableDiff: FLAGS['bot.studio.prompt_diff'],onUpdateSuccess: reloadList,onDiff: ({ libraryId }) => {recordRef.current = {res_id: libraryId,};openSelectIntelligenceModal();},});// 编辑提示词的核心逻辑通过 usePromptConfiguratorModal 实现const { open: openCreatePrompt, node: promptConfiguratorModal } =usePromptConfiguratorModal({spaceId,source: 'resource_library',enableDiff: FLAGS['bot.studio.prompt_diff'],onUpdateSuccess: reloadList, // 编辑成功后刷新列表onDiff: ({ libraryId }) => {recordRef.current = {res_id: libraryId,};openSelectIntelligenceModal();},});// 删除提示词的核心逻辑(保留用于删除操作)const { run: delPrompt } = useRequest((promptId: string) =>PlaygroundApi.DeletePromptResource({prompt_resource_id: promptId,}),{manual: true,onSuccess: () => {reloadList(); // 删除成功后刷新列表Toast.success(I18n.t('Delete_success')); // 显示成功提示},},);return {modals: (<>{selectIntelligenceModal}{promptConfiguratorModal}</>),config: {typeFilter: {label: I18n.t('library_resource_type_prompt'),value: ResType.Prompt,},renderCreateMenu: () => (<Menu.Itemdata-testid="workspace.library.header.create.prompt"icon={<IconCozLightbulb />}onClick={() => {sendTeaEvent(EVENT_NAMES.widget_create_click, {source: 'menu_bar',workspace_type: isPersonalSpace? 'personal_workspace': 'team_workspace',});openCreatePrompt({mode: 'create',});}}>{I18n.t('creat_new_prompt_prompt')}</Menu.Item>),target: [ResType.Prompt],onItemClick: (record: ResourceInfo) => {recordRef.current = record;const canEdit = record.actions?.find(action => action.key === ActionKey.Edit,)?.enable;openCreatePrompt({mode: 'info',canEdit,editId: record.res_id || '',});},// 渲染表格操作列,包含编辑和删除功能renderActions: (libraryResource: ResourceInfo) => (<TableActiondeleteProps={{// 根据权限控制删除按钮状态disabled: !libraryResource.actions?.find(action => action.key === ActionKey.Delete,)?.enable,// 删除确认描述deleteDesc: I18n.t('prompt_resource_delete_describ'),// 删除处理函数handler: () => {delPrompt(libraryResource.res_id || '');},}}// 编辑操作 - 核心功能editProps={{disabled: !libraryResource.actions?.find(action => action.key === ActionKey.Edit,)?.enable,handler: () => {// 打开编辑弹窗,传入编辑模式和资源IDopenCreatePrompt({mode: 'edit',editId: libraryResource.res_id || '',});},}}actionList={getCommonActions?.(libraryResource)}/>),},};
};

编辑提示词-API接口实现

bot-api/package.json

文件位置:frontend/packages/arch/bot-api/package.json
核心代码:

{"name": "@coze-arch/bot-api","version": "0.0.1","description": "RPC wrapper for bot studio application","author": "fanwenjie.fe@bytedance.com","exports": {".": "./src/index.ts",},
}

代码作用:

  • 1.包定义 :定义了一个名为 @coze-arch/bot-api 的 npm 包,版本为 0.0.1,这是一个用于 bot studio 应用的 RPC 包装器。
  • 2.通过主入口文件 :
    frontend\packages\arch\bot-api\src\index.ts 中, PlaygroundApi 被导出:
export { PlaygroundApi } from './playground-api';

这允许通过 @coze-arch/bot-api 直接导入 PlaygroundApi 。
3.PlaygroundApi 实现 :在 src/playground-api.ts 中, PlaygroundApi 是一个配置好的服务实例,它使用了 PlaygroundService 和 axios 请求配置。

src/playground-api.ts

文件位置:frontend\packages\arch\bot-api\src\playground-api.ts

核心代码:

import PlaygroundApiService from './idl/playground_api';
import { axiosInstance, type BotAPIRequestConfig } from './axios';// eslint-disable-next-line @typescript-eslint/naming-convention
export const PlaygroundApi = new PlaygroundApiService<BotAPIRequestConfig>({request: (params, config = {}) => {config.headers = Object.assign(config.headers || {}, {'Agw-Js-Conv': 'str',});return axiosInstance.request({ ...params, ...config });},
});

实现特点

  • 自动生成:基于IDL文件自动生成的API客户端
  • 类型安全:完整的TypeScript类型支持
  • 统一配置:使用共享的axios实例和请求配置
  • 方法导出:直接导出常用的API方法便于调用
  • 请求头设置:自动添加必要的请求头信息
axiosInstance说明

1.axiosInstance 在整个项目中是全局共享的
2.bot-api 包中的导入 ( frontend/packages/arch/bot-api/src/axios.ts )
是直接从 @coze-arch/bot-http 包导入了 axiosInstance 。

import {axiosInstance,isApiError,type AxiosRequestConfig,
} from '@coze-arch/bot-http';

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

export const axiosInstance = axios.create();

这里创建了一个全局的 axios 实例,与用户名修改保存请求的 axios 实例是同一个。

PlaygroundApiService说明

1.bot-api包中的导入路径:
import PlaygroundApiService from ‘./idl/playground_api’;
实际指向
frontend/packages/arch/bot-api/src/idl/playground_api.ts
文件内容重新导出了 @coze-arch/idl/playground_api 包的所有内容,包括默认导出

export * from '@coze-arch/idl/playground_api';
export { default as default } from '@coze-arch/idl/playground_api';

2.idl包的模块映射
文件位置:frontend/packages/arch/idl/package.json
核心代码:

"name": "@coze-arch/idl","version": "0.0.1","description": "IDL files for bot studio application","author": "fanwenjie.fe@bytedance.com","exports": {"./playground_api": "./src/auto-generated/playground_api/index.ts",

代码作用:将 @coze-arch/idl/playground_api 映射到实际文件路径frontend/packages/arch/idl/src/auto-generated/playground_api/index.ts
这个文件说明后续见 删除提示词-API接口实现 这个章节。

5. 编辑操作的完整流程

编辑插件的完整流程如下:

用户前端界面TableAction组件CreateCodePluginModalPluginDevelopApi后端服务点击表格行的"..."按钮渲染操作菜单点击"编辑"选项打开编辑弹窗获取提示词详情请求提示词数据返回提示词信息填充表单数据显示编辑界面修改提示词内容点击保存按钮表单验证提交更新请求发送更新请求返回更新结果返回操作结果显示成功/失败提示关闭弹窗并刷新列表用户前端界面TableAction组件CreateCodePluginModalPluginDevelopApi后端服务
http://www.dtcms.com/a/420029.html

相关文章:

  • 公司网上注册在哪个网站有啥创意可以做商务网站的
  • 校园兼职网站开发用例图互联网招聘网站
  • HarmonyOS 广告服务 ArkTS 实现指南:从激励广告到多形式适配
  • wordpress多站点换域名深圳网址排名
  • 网站存在原理网络营销方式哪些
  • DragonBalls_One008
  • 9月28日星期天今日早报简报微语报早读
  • 网络公司免费做网站wordpress导出全站链接
  • 网站数据泄露我们应该怎么做ps网站首页设计图
  • 辗转相除法(欧几里得算法)探微
  • 【Leetcode hot 100】208.实现Trie(前缀树)
  • 【开题答辩全过程】以 基于Java的网上租车系统的设计与开发为例,包含答辩的问题和答案
  • Linux系统编程深度指南:与内核的对话
  • 资源库建设网站工信部网站 登陆
  • 贵州住房和城乡建设部网站官网网站建设怎样
  • C++IO流学习
  • 网站建设哪里可以学招聘网官网
  • 队列+宽搜(BFS)-515.在每个树行中找最大值-力扣(LeetCode)
  • 网站建设升级的必要性做网站用什么服务器会比较好
  • 摄影网站开发的背景网站设计专题页
  • 网站备案 查询wordpress媒体优化
  • 商业网站的域名后缀是什么wordpress+信息流
  • 邢台网站制作哪里做做网站需要vps吗
  • 解锁数据湖潜力:Databricks Photon引擎的技术深度剖析
  • 网站建设优化排名网页qq登录不扫二维码
  • 《Nat. Commun》重磅:MXene赋能石墨负极,-20°C低温下循环1200次容量保持93%
  • 【开题答辩全过程】以 基于Java的网上图书管理系统为例,包含答辩的问题和答案
  • think-queue for ThinkPHP6 使用方法教程
  • 自学编程网站免费湖北网络推广有限公司
  • Playwright web爬虫与AI智能体