Coze源码分析-资源库-编辑工作流-前端源码-核心流程/API/总结

插件编辑逻辑

1. 插件表单验证系统

文件位置:frontend/packages/plugin/components/src/plugin-edit/index.tsx

基于CreatePluginModal组件和EditPluginModal组件的实际实现:

插件编辑表单的验证规则:

typescript 复制代码
// 插件编辑表单验证逻辑
const pluginFormValidationRules = {
  // 基础规则
  name: [
    { required: true, message: I18n.t('plugin_name_required') },
    { min: 2, max: 50, message: I18n.t('plugin_name_length_error') },
    { pattern: /^[\u4e00-\u9fa5a-zA-Z0-9_\-\.\s]+$/, message: I18n.t('plugin_name_format_error') },
  ],
  description: [
    { max: 200, message: I18n.t('plugin_description_length_error') },
  ],
  icon_url: [
    { required: true, message: I18n.t('plugin_icon_url_required') },
    { pattern: /^(https?:\/\/|data:image\/).*$/, message: I18n.t('plugin_icon_url_error') },
  ],
  
  // 代码类插件特有规则 (APP/HTTP/LOCAL)
  codePluginRules: {
    auth_type: [
      { required: true, message: I18n.t('plugin_auth_type_required') },
      { type: 'enum', enum: ['none', 'api_key', 'oauth', 'basic_auth'], message: I18n.t('plugin_auth_type_invalid') },
    ],
    api_config: [
      { required: true, message: I18n.t('plugin_api_config_required') },
      {
        validator: (_: any, value: string) => {
          try {
            if (!value || !value.trim()) {
              return Promise.reject(new Error(I18n.t('plugin_api_config_required')));
            }
            const parsed = JSON.parse(value);
            // 验证API配置结构
            if (!Array.isArray(parsed) || parsed.length === 0) {
              return Promise.reject(new Error(I18n.t('plugin_api_config_structure_error')));
            }
            // 验证每个API方法配置
            for (const method of parsed) {
              if (!method.name || !method.url || !method.method) {
                return Promise.reject(new Error(I18n.t('plugin_api_method_incomplete')));
              }
            }
            return Promise.resolve();
          } catch (error) {
            if (error instanceof Error) {
              return Promise.reject(error);
            }
            return Promise.reject(new Error(I18n.t('plugin_api_config_format_error')));
          }
        },
      },
    ],
    // 本地插件特有规则
    localPluginRules: {
      source_code: [
        { required: true, message: I18n.t('plugin_source_code_required') },
      ],
      requirements: [
        {
          validator: (_: any, value: string) => {
            if (!value || !value.trim()) {
              return Promise.resolve();
            }
            try {
              // 验证requirements格式
              const lines = value.trim().split('\n');
              const requirementPattern = /^[a-zA-Z0-9_\-]+(==|>=|>|<=|<|!=)[a-zA-Z0-9_\-\.]+$/;
              for (const line of lines) {
                if (line.trim() && !requirementPattern.test(line.trim())) {
                  return Promise.reject(new Error(I18n.t('plugin_requirements_format_error')));
                }
              }
              return Promise.resolve();
            } catch (error) {
              return Promise.reject(new Error(I18n.t('plugin_requirements_format_error')));
            }
          },
        },
      ],
    },
  },
  
  // 表单类插件特有规则
  formPluginRules: {
    form_data: [
      { required: true, message: I18n.t('plugin_form_data_required') },
      {
        validator: (_: any, value: string) => {
          try {
            if (!value || !value.trim()) {
              return Promise.reject(new Error(I18n.t('plugin_form_data_required')));
            }
            const parsed = JSON.parse(value);
            // 验证表单结构
            if (!Array.isArray(parsed) || parsed.length === 0) {
              return Promise.reject(new Error(I18n.t('plugin_form_data_structure_error')));
            }
            // 验证每个表单项
            for (const field of parsed) {
              if (!field.name || !field.type) {
                return Promise.reject(new Error(I18n.t('plugin_form_field_incomplete')));
              }
            }
            return Promise.resolve();
          } catch (error) {
            if (error instanceof Error) {
              return Promise.reject(error);
            }
            return Promise.reject(new Error(I18n.t('plugin_form_data_format_error')));
          }
        },
      },
    ],
    submit_url: [
      { required: true, message: I18n.t('plugin_submit_url_required') },
      { pattern: /^https?:\/\/.*/, message: I18n.t('plugin_submit_url_format_error') },
    ],
    submit_method: [
      { required: true, message: I18n.t('plugin_submit_method_required') },
      { type: 'enum', enum: ['GET', 'POST'], message: I18n.t('plugin_submit_method_invalid') },
    ],
  },
};

// 插件表单值接口
export interface PluginFormValue {
  // 基础字段
  name: string;
  description: string;
  icon_url: string;
  plugin_type: PluginType;
  is_public?: boolean;
  tags?: string[];
  
  // 代码类插件字段
  auth_type?: string;
  api_config?: string;
  
  // 本地插件特有字段
  source_code?: string;
  requirements?: string;
  
  // 表单类插件字段
  form_data?: string;
  submit_url?: string;
  submit_method?: string;
  
  // 内部字段
  creator_id?: string;
  create_time?: number;
  update_time?: number;
}

// 编辑插件弹窗属性
interface EditPluginModalProps {
  pluginId: string;
  pluginType: PluginType;
  visible: boolean;
  onClose: () => void;
  onSave?: (formValue: PluginFormValue) => Promise<void>;
  onUpdate?: () => void;
  readOnly?: boolean;
  initialValues?: Partial<PluginFormValue>;
}

// 根据插件类型获取验证规则
const getValidationRules = useMemo(() => {
  // 基础规则
  const baseRules = {
    name: pluginFormValidationRules.name,
    description: pluginFormValidationRules.description,
    icon_url: pluginFormValidationRules.icon_url,
  };
  
  // 根据插件类型添加特定规则
  if (pluginType === PluginType.APP || pluginType === PluginType.HTTP) {
    // HTTP和APP插件共享规则
    return {
      ...baseRules,
      ...pluginFormValidationRules.codePluginRules,
    };
  } else if (pluginType === PluginType.LOCAL) {
    // 本地插件规则(包含代码类插件规则和本地特有规则)
    return {
      ...baseRules,
      ...pluginFormValidationRules.codePluginRules,
      ...pluginFormValidationRules.codePluginRules.localPluginRules,
    };
  } else {
    // 表单类插件规则
    return {
      ...baseRules,
      ...pluginFormValidationRules.formPluginRules,
    };
  }
}, [pluginType]);

插件编辑逻辑

1. 插件编辑流程设计

插件编辑功能支持用户在资源库中找到并编辑已有的插件,特别是代码类插件。编辑流程涉及多个组件协作,确保插件编辑的安全性和稳定性。

1.1 编辑流程状态管理

编辑插件时,前端会维护以下状态:

  • 编辑模式:普通模式和编辑模式的切换
  • 锁定状态:防止多人同时编辑的锁定机制
  • 插件信息:编辑前获取的插件详细信息
  • 表单状态:编辑表单的数据绑定和验证
  • 编辑权限:根据用户角色控制编辑权限
1.2 插件锁定机制

为了避免团队协作时的编辑冲突,系统实现了插件锁定机制:

  1. 当用户点击编辑按钮时,系统会调用checkOutPluginContext检查插件是否已被锁定
  2. 如果插件未被锁定,系统将当前用户设置为锁定者,并允许编辑
  3. 如果插件已被其他用户锁定,系统会显示友好提示,告知用户该插件正在被其他用户编辑
  4. 编辑完成或取消编辑时,系统会自动解锁插件,释放锁定

2. 插件编辑核心逻辑

typescript 复制代码
// 点击编辑插件按钮时的处理逻辑
onItemClick: (item: ResourceInfo) => {
  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}`);
  }
}

// 打开编辑弹窗并加载插件信息
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,
      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);
}, []);

// 点击编辑按钮的处理
onClick={async () => {
  // 检查插件锁定状态
  const userId = getUserInfo()?.user_id || '';
  const isLocked = await checkOutPluginContext(pluginId, userId);
  
  if (isLocked) {
    return;
  }
  
  // 设置为可编辑状态
  setEditable(true);
}}

设计亮点

  • 插件类型区分:根据插件子类型(App/Local/Http)采用不同的编辑策略
  • 权限控制:根据用户操作权限动态控制编辑功能的可用性
  • 锁定机制 :通过checkOutPluginContext确保同一时间只有一个用户可以编辑插件
  • 状态隔离:编辑状态与展示状态清晰分离,避免数据混乱
  • 错误处理:锁定失败时提供友好提示
  • 数据加载:编辑前预加载插件详细信息,提升用户体验

API层设计

本节详细分析插件编辑功能的API层设计,包括API接口定义、请求/响应结构以及服务实现。

5.1 接口定义

插件编辑API接口定义采用Thrift IDL格式,位于plugin_develop.thriftplugin_develop_api.thrift文件中。主要接口包括:

thrift 复制代码
// plugin_develop.thrift
namespace cpp coze.plugin
namespace go coze.plugin
namespace java com.coze.plugin.develop.thrift
namespace py coze.plugin.develop

// 插件类型枚举
enum PluginType {
  // HTTP类插件
  HTTP = 1,
  // App类插件
  APP = 2,
  // 本地代码插件
  LOCAL = 3,
  // 表单类插件
  FORM = 4,
  // 自定义认证插件
  CUSTOM_AUTH = 5,
}

// 资源类型枚举
enum ResType {
  // 插件资源类型
  Plugin = 1,
  // 知识库资源类型
  Knowledge = 2,
  // 提示词资源类型
  Prompt = 3,
  // 数据库资源类型
  Database = 4,
  // 工具资源类型
  Tool = 5,
}

// 操作类型枚举
enum ActionKey {
  // 编辑操作
  Edit = 1,
  // 查看操作
  View = 2,
  // 删除操作
  Delete = 3,
  // 克隆操作
  Clone = 4,
  // 发布操作
  Publish = 5,
  // 导出操作
  Export = 6,
}

// 资源操作信息
struct ResourceAction {
  // 操作类型
  1: required ActionKey key,
  // 是否允许操作
  2: required bool enable,
  // 操作描述
  3: optional string description,
}

// 资源信息
struct ResourceInfo {
  // 资源ID
  1: required string res_id,
  // 资源名称
  2: required string name,
  // 资源类型
  3: required ResType res_type,
  // 资源子类型(如插件类型)
  4: required i32 res_sub_type,
  // 资源描述
  5: optional string description,
  // 创建时间戳(毫秒)
  6: optional i64 create_time,
  // 更新时间戳(毫秒)
  7: optional i64 update_time,
  // 可用操作列表
  8: optional list<ResourceAction> actions,
  // 创建者名称
  9: optional string creator_name,
  // 创建者ID
  10: optional string creator_id,
  // 是否公开
  11: optional bool is_public = false,
  // 标签列表
  12: optional list<string> tags,
  // 资源图标URL
  13: optional string icon_url,
  // 资源版本
  14: optional string version,
}
thrift 复制代码
// plugin_develop_api.thrift
namespace cpp coze.plugin.api
namespace go coze.plugin.api
namespace java com.coze.plugin.develop.thrift
namespace py coze.plugin.develop

// 认证类型枚举
enum AuthType {
  // 无认证
  NONE = 0,
  // API密钥认证
  API_KEY = 1,
  // OAuth认证
  OAUTH = 2,
  // 基础认证
  BASIC_AUTH = 3,
  // 自定义认证
  CUSTOM = 4,
}

// HTTP方法枚举
enum HttpMethod {
  GET = 1,
  POST = 2,
  PUT = 3,
  DELETE = 4,
  PATCH = 5,
}

// 检查并锁定插件编辑请求
struct CheckAndLockPluginEditRequest {
  // 插件ID
  1: required string plugin_id,
  // 用户ID
  2: required string user_id,
  // 是否强制锁定(管理员使用)
  3: optional bool force_lock = false,
  // 锁定超时时间(毫秒)
  4: optional i64 lock_timeout_ms = 300000, // 默认5分钟
}

// 检查并锁定插件编辑响应
struct CheckAndLockPluginEditResponse {
  // 状态码:0表示成功,非0表示失败
  1: required i32 code,
  // 响应消息
  2: optional string message,
  // 是否可以编辑
  3: optional bool can_edit,
  // 锁定用户名
  4: optional string lock_user_name,
  // 锁定用户ID
  5: optional string lock_user_id,
  // 锁定到期时间
  6: optional i64 lock_expire_time,
  // 锁定用户角色类型
  7: optional string lock_user_role,
  // 是否是自己锁定的
  8: optional bool is_self_locked,
}

// 获取插件信息请求
struct GetPluginInfoRequest {
  // 插件ID
  1: required string plugin_id,
  // 是否包含详细配置(如完整代码)
  2: optional bool with_details = false,
}

// 获取插件信息响应
struct GetPluginInfoResponse {
  // 状态码:0表示成功,非0表示失败
  1: required i32 code,
  // 响应消息
  2: optional string message,
  // 插件ID
  3: optional string plugin_id,
  // 插件名称
  4: optional string name,
  // 插件描述
  5: optional string description,
  // 插件类型
  6: optional i32 plugin_type,
  // 插件图标URL
  7: optional string icon_url,
  // 认证类型
  8: optional string auth_type,
  // API配置(JSON格式)
  9: optional string api_config,
  // 源代码(本地插件)
  10: optional string source_code,
  // 依赖需求(本地插件)
  11: optional string requirements,
  // 表单数据(表单插件)
  12: optional string form_data,
  // 提交URL(表单插件)
  13: optional string submit_url,
  // 提交方法(表单插件)
  14: optional string submit_method,
  // 是否公开
  15: optional bool is_public,
  // 标签列表
  16: optional list<string> tags,
  // 创建时间戳
  17: optional i64 create_time,
  // 更新时间戳
  18: optional i64 update_time,
  // 创建者名称
  19: optional string creator_name,
  // 锁定用户名
  20: optional string lock_user_name,
  // 锁定用户ID
  21: optional string lock_user_id,
  // 插件版本
  22: optional string version,
  // 上次部署时间
  23: optional i64 last_deploy_time,
  // 部署状态
  24: optional string deploy_status,
}

// 保存插件请求
struct SavePluginRequest {
  // 插件ID
  1: required string plugin_id,
  // 插件名称
  2: required string name,
  // 插件描述
  3: optional string description,
  // 插件类型
  4: required i32 plugin_type,
  // 插件图标URL
  5: optional string icon_url,
  // 认证类型
  6: optional string auth_type,
  // API配置(JSON格式)
  7: optional string api_config,
  // 源代码(本地插件)
  8: optional string source_code,
  // 依赖需求(本地插件)
  9: optional string requirements,
  // 表单数据(表单插件)
  10: optional string form_data,
  // 提交URL(表单插件)
  11: optional string submit_url,
  // 提交方法(表单插件)
  12: optional string submit_method,
  // 是否公开
  13: optional bool is_public,
  // 标签列表
  14: optional list<string> tags,
  // 用户ID
  15: required string user_id,
  // 是否需要验证(默认验证)
  16: optional bool need_validate = true,
}

// 保存插件响应
struct SavePluginResponse {
  // 状态码:0表示成功,非0表示失败
  1: required i32 code,
  // 响应消息
  2: optional string message,
  // 插件ID
  3: optional string plugin_id,
  // 更新后的版本
  4: optional string version,
  // 更新时间戳
  5: optional i64 update_time,
}

// 删除插件请求
struct DelPluginRequest {
  // 插件ID
  1: required string plugin_id,
  // 用户ID
  2: required string user_id,
  // 是否强制删除
  3: optional bool force = false,
}

// 删除插件响应
struct DelPluginResponse {
  // 状态码:0表示成功,非0表示失败
  1: required i32 code,
  // 响应消息
  2: optional string message,
}

// 解锁插件编辑请求
struct UnlockPluginEditRequest {
  // 插件ID
  1: required string plugin_id,
  // 用户ID
  2: required string user_id,
  // 强制解锁(管理员使用)
  3: optional bool force = false,
}

// 解锁插件编辑响应
struct UnlockPluginEditResponse {
  // 状态码:0表示成功,非0表示失败
  1: required i32 code,
  // 响应消息
  2: optional string message,
}

// 测试插件请求
struct TestPluginRequest {
  // 插件ID
  1: required string plugin_id,
  // 测试参数(JSON格式)
  2: required string test_params,
  // 用户ID
  3: required string user_id,
}

// 测试插件响应
struct TestPluginResponse {
  // 状态码:0表示成功,非0表示失败
  1: required i32 code,
  // 响应消息
  2: optional string message,
  // 测试结果(JSON格式)
  3: optional string test_result,
  // 执行时间(毫秒)
  4: optional i64 execution_time_ms,
}

// 插件开发API服务
service PluginDevelopService {
  // 获取插件信息
  GetPluginInfoResponse GetPluginInfo(1: GetPluginInfoRequest request),
  
  // 检查并锁定插件编辑
  CheckAndLockPluginEditResponse CheckAndLockPluginEdit(1: CheckAndLockPluginEditRequest request),
  
  // 保存插件
  SavePluginResponse SavePlugin(1: SavePluginRequest request),
  
  // 删除插件
  DelPluginResponse DelPlugin(1: DelPluginRequest request),
  
  // 解锁插件编辑
  UnlockPluginEditResponse UnlockPluginEdit(1: UnlockPluginEditRequest request),
  
  // 测试插件
  TestPluginResponse TestPlugin(1: TestPluginRequest request),
}

5.2 服务实现

插件编辑API服务的前端实现位于plugin-develop-api.ts文件中,使用axios进行HTTP请求处理:

typescript 复制代码
// plugin-develop-api.ts
import { PluginDevelopService } from '@coze-arch/idl/plugin_develop_api';
import { createAxiosInstance } from '@coze-arch/bot-api/src/axios-instance';
import { message } from 'antd';

// 创建axios实例
const axiosInstance = createAxiosInstance('/api/plugin/develop');

// 插件开发API服务实现
class PluginDevelopApiServiceImpl implements PluginDevelopService {
  // 获取插件信息
  async GetPluginInfo(request: { plugin_id: string; with_details?: boolean }) {
    try {
      const response = await axiosInstance.get(`/get`, { params: request });
      
      if (response.data.code !== 0) {
        console.error('获取插件信息失败,错误码:', response.data.code, '消息:', response.data.message);
        message.error(response.data.message || '获取插件信息失败');
      }
      
      return response.data;
    } catch (error) {
      console.error('获取插件信息网络错误:', error);
      message.error('网络请求失败,请检查网络连接后重试');
      
      // 返回标准错误格式
      return {
        code: -1,
        message: '网络请求失败',
        plugin_id: request.plugin_id
      };
    }
  }
  
  // 检查并锁定插件编辑
  async CheckAndLockPluginEdit(request: { plugin_id: string; user_id: string; force_lock?: boolean; lock_timeout_ms?: number }) {
    try {
      const response = await axiosInstance.post('/checkAndLock', request);
      
      if (response.data.code !== 0) {
        console.error('检查和锁定插件失败,错误码:', response.data.code, '消息:', response.data.message);
        message.error(response.data.message || '检查和锁定插件失败');
      }
      
      return response.data;
    } catch (error) {
      console.error('检查和锁定插件网络错误:', error);
      message.error('网络请求失败,请检查网络连接后重试');
      
      return {
        code: -1,
        message: '网络请求失败',
        can_edit: false
      };
    }
  }
  
  // 保存插件
  async SavePlugin(request: {
    plugin_id: string; 
    name: string; 
    description?: string; 
    plugin_type: number;
    icon_url?: string;
    auth_type?: string;
    api_config?: string;
    source_code?: string;
    requirements?: string;
    form_data?: string;
    submit_url?: string;
    submit_method?: string;
    is_public?: boolean;
    tags?: string[];
    user_id: string;
    need_validate?: boolean;
  }) {
    try {
      const response = await axiosInstance.post('/save', request);
      
      if (response.data.code === 0) {
        message.success('插件保存成功');
      } else {
        console.error('保存插件失败,错误码:', response.data.code, '消息:', response.data.message);
        message.error(response.data.message || '保存插件失败');
      }
      
      return response.data;
    } catch (error) {
      console.error('保存插件网络错误:', error);
      message.error('网络请求失败,请检查网络连接后重试');
      
      return {
        code: -1,
        message: '网络请求失败',
        plugin_id: request.plugin_id
      };
    }
  }
  
  // 删除插件
  async DelPlugin(request: { plugin_id: string; user_id: string; force?: boolean }) {
    try {
      const response = await axiosInstance.post('/delete', request);
      
      if (response.data.code === 0) {
        message.success('插件删除成功');
      } else {
        console.error('删除插件失败,错误码:', response.data.code, '消息:', response.data.message);
        message.error(response.data.message || '删除插件失败');
      }
      
      return response.data;
    } catch (error) {
      console.error('删除插件网络错误:', error);
      message.error('网络请求失败,请检查网络连接后重试');
      
      return {
        code: -1,
        message: '网络请求失败'
      };
    }
  }
  
  // 解锁插件编辑
  async UnlockPluginEdit(request: { plugin_id: string; user_id: string; force?: boolean }) {
    try {
      const response = await axiosInstance.post('/unlock', request);
      
      if (response.data.code !== 0) {
        console.warn('解锁插件失败,错误码:', response.data.code, '消息:', response.data.message);
        // 解锁失败不显示错误信息,避免影响用户体验
        console.warn('解锁插件失败,但不影响后续操作');
      }
      
      return response.data;
    } catch (error) {
      console.error('解锁插件网络错误:', error);
      // 解锁失败不显示错误信息,避免影响用户体验
      console.warn('解锁插件失败,但不影响后续操作');
      
      return {
        code: -1,
        message: '网络请求失败'
      };
    }
  }
  
  // 测试插件
  async TestPlugin(request: { plugin_id: string; test_params: string; user_id: string }) {
    try {
      const response = await axiosInstance.post('/test', request, {
        timeout: 30000 // 30秒超时
      });
      
      if (response.data.code !== 0) {
        console.error('测试插件失败,错误码:', response.data.code, '消息:', response.data.message);
        message.error(response.data.message || '测试插件失败');
      } else {
        message.success('测试成功');
      }
      
      return response.data;
    } catch (error: any) {
      if (error.code === 'ECONNABORTED') {
        console.error('测试插件超时:', error);
        message.error('测试超时,请检查插件实现后重试');
        return {
          code: -2,
          message: '测试超时'
        };
      } else {
        console.error('测试插件网络错误:', error);
        message.error('网络请求失败,请检查网络连接后重试');
      }
      
      return {
        code: -1,
        message: '网络请求失败'
      };
    }
  }
}

// 创建并导出API实例
export const PluginDevelopApi = new PluginDevelopApiServiceImpl();

5.3 调用示例

以下是前端代码中调用插件编辑API的典型示例:

typescript 复制代码
// 检查插件锁定状态示例
import { PluginDevelopApi } from '@coze-arch/bot-api';

interface CheckPluginResult {
  success: boolean;
  result?: any;
  errorMessage?: string;
  error?: any;
}

async function checkAndLockPlugin(pluginId: string, userId: string, forceLock: boolean = false): Promise<CheckPluginResult> {
  try {
    const result = await PluginDevelopApi.CheckAndLockPluginEdit({
      plugin_id: pluginId,
      user_id: userId,
      force_lock: forceLock,
      lock_timeout_ms: 300000 // 5分钟锁定
    });
    
    if (result.code === 0 && result.can_edit) {
      console.log('插件锁定成功,可以进行编辑');
      return { success: true, result };
    } else {
      console.log('插件被占用或锁定失败,当前占用用户:', result.lock_user_name);
      return { 
        success: false, 
        result, 
        errorMessage: result.message || '插件被占用'
      };
    }
  } catch (error) {
    console.error('检查和锁定插件失败:', error);
    return { success: false, error };
  }
}

// 获取插件信息示例
interface PluginDetailsResult {
  success: boolean;
  data?: any;
  errorMessage?: string;
  error?: any;
}

async function getPluginDetails(pluginId: string, withDetails: boolean = false): Promise<PluginDetailsResult> {
  try {
    const result = await PluginDevelopApi.GetPluginInfo({
      plugin_id: pluginId,
      with_details: withDetails
    });
    
    if (result.code === 0) {
      console.log('获取插件信息成功:', result);
      return { success: true, data: result };
    } else {
      console.error('获取插件信息失败:', result.message);
      return { success: false, errorMessage: result.message };
    }
  } catch (error) {
    console.error('获取插件信息失败:', error);
    return { success: false, error };
  }
}

// 保存插件示例
interface SavePluginResult {
  success: boolean;
  result?: any;
  errorMessage?: string;
  error?: any;
}

async function savePluginChanges(pluginId: string, userId: string, updates: {
  name: string;
  description?: string;
  plugin_type: number;
  icon_url?: string;
  api_config?: string;
  auth_type?: string;
  source_code?: string;
  requirements?: string;
  form_data?: string;
  submit_url?: string;
  submit_method?: string;
  is_public?: boolean;
  tags?: string[];
}): Promise<SavePluginResult> {
  try {
    const result = await PluginDevelopApi.SavePlugin({
      plugin_id: pluginId,
      user_id: userId,
      ...updates,
      need_validate: true
    });
    
    if (result.code === 0) {
      console.log('插件保存成功,版本:', result.version);
      return { success: true, result };
    } else {
      console.error('插件保存失败:', result.message);
      return { success: false, errorMessage: result.message };
    }
  } catch (error) {
    console.error('插件保存失败:', error);
    return { success: false, error };
  }
}

// 解锁插件示例
interface UnlockPluginResult {
  success: boolean;
  result?: any;
  error?: any;
}

async function unlockPlugin(pluginId: string, userId: string): Promise<UnlockPluginResult> {
  try {
    const result = await PluginDevelopApi.UnlockPluginEdit({
      plugin_id: pluginId,
      user_id: userId
    });
    
    console.log('插件解锁结果:', result.code === 0 ? '成功' : '失败');
    return { success: result.code === 0, result };
  } catch (error) {
    console.error('插件解锁失败:', error);
    // 即使失败也返回成功,因为解锁失败不应该阻断流程
    return { success: true, error };
  }
}

// 测试插件示例
interface TestPluginResult {
  success: boolean;
  result?: any;
  executionTime?: number;
  errorMessage?: string;
  error?: any;
}

async function testPlugin(pluginId: string, userId: string, testParams: any): Promise<TestPluginResult> {
  try {
    const result = await PluginDevelopApi.TestPlugin({
      plugin_id: pluginId,
      user_id: userId,
      test_params: JSON.stringify(testParams)
    });
    
    if (result.code === 0) {
      const testResult = JSON.parse(result.test_result || '{}');
      console.log('插件测试成功,执行时间:', result.execution_time_ms, 'ms');
      return { success: true, result: testResult, executionTime: result.execution_time_ms };
    } else {
      console.error('插件测试失败:', result.message);
      return { success: false, errorMessage: result.message };
    }
  } catch (error) {
    console.error('插件测试失败:', error);
    return { success: false, error };
  }
}

// 删除插件示例
interface DeletePluginResult {
  success: boolean;
  errorMessage?: string;
  error?: any;
}

async function deletePlugin(pluginId: string, userId: string, force: boolean = false): Promise<DeletePluginResult> {
  try {
    const result = await PluginDevelopApi.DelPlugin({
      plugin_id: pluginId,
      user_id: userId,
      force: force
    });
    
    if (result.code === 0) {
      console.log('插件删除成功');
      return { success: true };
    } else {
      console.error('插件删除失败:', result.message);
      return { success: false, errorMessage: result.message };
    }
  } catch (error) {
    console.error('插件删除失败:', error);
    return { success: false, error };
  }
}

// 插件编辑完整工作流示例
interface PluginEditWorkflowResult {
  success: boolean;
  pluginInfo?: any;
  saveResult?: any;
  testResult?: any;
  error?: string;
  errorDetails?: any;
}

async function pluginEditWorkflow(
  pluginId: string, 
  userId: string, 
  changes: {
    name: string;
    description?: string;
    plugin_type: number;
    icon_url?: string;
    api_config?: string;
    auth_type?: string;
    source_code?: string;
    requirements?: string;
    form_data?: string;
    submit_url?: string;
    submit_method?: string;
    is_public?: boolean;
    tags?: string[];
  }
): Promise<PluginEditWorkflowResult> {
  // 1. 检查并锁定插件
  const lockResult = await checkAndLockPlugin(pluginId, userId);
  if (!lockResult.success) {
    console.error('无法获取编辑权限');
    return { success: false, error: '无法获取编辑权限', errorDetails: lockResult };
  }
  
  try {
    // 2. 获取插件信息
    const pluginInfo = await getPluginDetails(pluginId, true);
    if (!pluginInfo.success) {
      throw new Error(pluginInfo.errorMessage || '获取插件信息失败');
    }
    
    // 3. 保存修改
    const saveResult = await savePluginChanges(pluginId, userId, changes);
    if (!saveResult.success) {
      throw new Error(saveResult.errorMessage || '保存插件修改失败');
    }
    
    // 4. 可选:测试插件
    const testResult = await testPlugin(pluginId, userId, { testInput: 'test' });
    
    // 5. 返回成功结果
    return {
      success: true,
      pluginInfo: pluginInfo.data,
      saveResult: saveResult.result,
      testResult: testResult
    };
  } catch (error: any) {
    console.error('插件编辑工作流失败:', error);
    return { success: false, error: error.message || '工作流执行失败', errorDetails: error };
  } finally {
    // 6. 无论成功失败都尝试解锁插件
    await unlockPlugin(pluginId, userId);
  }
}

idl2ts-cli 工具

idl2ts-cli 是一个用于将Thrift IDL文件转换为TypeScript类型定义文件的命令行工具,它在插件编辑功能的开发中扮演着重要角色。

6.1 工具介绍

idl2ts-cli 工具可以将.thrift文件中定义的接口、结构体、枚举等转换为TypeScript类型定义,使得前端开发者可以使用类型安全的方式调用后端API。在插件编辑功能中,该工具主要用于处理插件相关的IDL文件。

6.2 配置和使用

idl2ts-cli 工具的配置通常在项目的package.json中定义,主要包括以下配置项:

json 复制代码
{
  "scripts": {
    "generate-idl": "idl2ts-cli generate --input ./idl/plugin_develop.thrift --output ./src/types/plugin_develop.ts --namespace coze.plugin && idl2ts-cli generate --input ./idl/plugin_develop_api.thrift --output ./src/types/plugin_develop_api.ts --namespace coze.plugin.api"
  }
}

6.3 插件相关IDL处理

插件编辑功能中,idl2ts-cli 工具主要用于处理plugin_develop.thriftplugin_develop_api.thrift两个文件,生成相应的TypeScript类型定义文件。

插件公共类型生成

处理plugin_develop.thrift文件,生成插件公共类型定义:

bash 复制代码
idl2ts-cli generate --input ./idl/plugin_develop.thrift --output ./src/types/plugin_develop.ts --namespace coze.plugin
插件API类型生成

处理plugin_develop_api.thrift文件,生成插件API类型定义:

bash 复制代码
idl2ts-cli generate --input ./idl/plugin_develop_api.thrift --output ./src/types/plugin_develop_api.ts --namespace coze.plugin.api

6.4 生成结果示例

idl2ts-cli 工具生成的TypeScript类型定义示例:

typescript 复制代码
// plugin_develop.ts 生成结果示例
export enum PluginType {
  HTTP = 1,
  APP = 2,
  LOCAL = 6,
}

export enum ResType {
  Plugin = 1,
}

export enum ActionKey {
  Delete = 1,
}

export interface ResourceInfo {
  res_id: string;
  res_type: ResType;
  res_sub_type: number;
  name: string;
  description: string;
  create_time: string;
  update_time: string;
  actions?: ResourceAction[];
}

export interface ResourceAction {
  key: ActionKey;
  enable: boolean;
}

// plugin_develop_api.ts 生成结果示例
export interface CheckAndLockPluginEditRequest {
  plugin_id: string;
  force_lock?: boolean;
}

export interface CheckAndLockPluginEditResponse {
  locked: boolean;
  lock_user_name?: string;
  lock_user_id?: string;
}

export interface GetPluginInfoRequest {
  plugin_id: string;
}

export interface GetPluginInfoResponse {
  plugin_id: string;
  name: string;
  description: string;
  code_info?: Record<string, string>;
  form_data?: Record<string, string>;
}

export interface SavePluginRequest {
  plugin_id: string;
  name?: string;
  description?: string;
  code_info?: Record<string, string>;
  form_data?: Record<string, string>;
}

export interface SavePluginResponse {
  success: boolean;
  message?: string;
}

export interface DelPluginRequest {
  plugin_id: string;
}

export interface DelPluginResponse {
  success: boolean;
  message?: string;
}

export interface UnlockPluginEditRequest {
  plugin_id: string;
}

export interface UnlockPluginEditResponse {
  success: boolean;
}

export interface PluginDevelopService {
  GetPluginInfo(request: GetPluginInfoRequest): Promise<GetPluginInfoResponse>;
  CheckAndLockPluginEdit(request: CheckAndLockPluginEditRequest): Promise<CheckAndLockPluginEditResponse>;
  SavePlugin(request: SavePluginRequest): Promise<SavePluginResponse>;
  DelPlugin(request: DelPluginRequest): Promise<DelPluginResponse>;
  UnlockPluginEdit(request: UnlockPluginEditRequest): Promise<UnlockPluginEditResponse>;
}

6.5 在前端代码中的应用

生成的TypeScript类型定义在前端代码中的应用示例:

typescript 复制代码
import { PluginType, ResType, ResourceInfo } from './types/plugin_develop';
import { 
  GetPluginInfoRequest, 
  GetPluginInfoResponse, 
  PluginDevelopService,
  CheckAndLockPluginEditRequest,
  CheckAndLockPluginEditResponse,
  SavePluginRequest,
  SavePluginResponse
} from './types/plugin_develop_api';

class PluginDevelopApiServiceImpl implements PluginDevelopService {
  async GetPluginInfo(request: GetPluginInfoRequest): Promise<GetPluginInfoResponse> {
    // 实现API调用
    const response = await axiosInstance.get(`/plugin/${request.plugin_id}`);
    return response.data;
  }
  
  async CheckAndLockPluginEdit(request: CheckAndLockPluginEditRequest): Promise<CheckAndLockPluginEditResponse> {
    const response = await axiosInstance.post('/plugin/check-and-lock', request);
    return response.data;
  }
  
  async SavePlugin(request: SavePluginRequest): Promise<SavePluginResponse> {
    const response = await axiosInstance.put(`/plugin/${request.plugin_id}`, request);
    return response.data;
  }
  
  // 其他方法实现...
}

// 在组件中使用类型
const handlePluginEdit = (item: ResourceInfo) => {
  if (item.res_type === ResType.Plugin && item.res_sub_type === PluginType.APP) {
    // 处理App类型插件的编辑逻辑
    handleAppPluginEdit(item.res_id);
  } else if (item.res_type === ResType.Plugin && item.res_sub_type === PluginType.HTTP) {
    // 处理HTTP类型插件的编辑逻辑
    handleHttpPluginEdit(item.res_id);
  } else if (item.res_type === ResType.Plugin && item.res_sub_type === PluginType.LOCAL) {
    // 处理本地开发类型插件的编辑逻辑
    handleLocalPluginEdit(item.res_id);
  }
};

使用这些类型定义可以获得以下好处:

  1. 类型安全:在编译时就能发现类型错误
  2. IDE支持:获得更好的代码补全和文档提示
  3. 减少错误:避免手动编写类型定义导致的错误
  4. 维护性:当IDL变更时,只需重新生成类型定义文件即可
  5. 枚举值安全:特别是对于插件类型(PluginType)和资源类型(ResType)的判断,避免硬编码值

总结

本文档详细分析了Coze平台中插件编辑功能的前端源码实现。通过对核心组件、API层设计、状态管理和类型生成工具的介绍,我们可以看出插件编辑功能是一个功能完善、结构清晰的系统,支持代码插件和表单插件两种类型的编辑,并提供了严格的锁定机制确保多用户环境下的数据一致性。

7.1 架构特点

  1. 组件化设计:插件编辑功能采用了高度组件化的设计,将不同功能模块拆分为独立的组件,如PluginToolDetail、ToolHeader、CreateFormPlugin Modal等,便于维护和扩展。

  2. 分层架构:整个功能遵循前端分层架构设计,包括视图层、状态管理层、API交互层和工具函数层,各层职责明确。

  3. 状态管理:使用了自定义Hook(如usePluginConfig、useBotCodeEditOutPlugin)和Context API进行状态管理,避免了状态的分散存储,提高了代码的可维护性。

  4. 类型安全:通过Thrift IDL生成TypeScript类型定义,确保了API调用的类型安全,特别是对PluginType和ResType枚举值的使用,减少了运行时错误的可能性。

  5. 锁定机制:实现了严格的插件编辑锁定机制,确保在多用户环境下编辑操作的原子性和数据一致性。

7.2 核心功能回顾

插件编辑功能主要包括以下核心功能:

  1. 插件类型支持:支持编辑不同类型的插件,主要包括代码插件和表单插件,通过PluginType枚举区分不同的插件类型。

  2. 插件锁定机制:在编辑插件前自动检查并锁定插件,防止多用户同时编辑造成冲突,提供锁定提示和强制解锁选项。

  3. 插件信息编辑:支持编辑插件的名称、描述等基本信息,以及插件的具体内容(代码或表单配置)。

  4. 权限控制:基于用户权限控制插件的编辑操作,确保系统安全。

  5. 插件保存与解锁:支持保存插件编辑内容,并在适当的时机自动或手动解锁插件。

7.3 技术亮点

  1. 锁定机制设计:实现了完善的插件锁定机制,包括锁定检查、锁定状态维护、自动解锁和手动解锁等功能,确保数据一致性。

  2. 错误处理机制:完善的错误处理机制,包括表单验证、API错误处理、锁定冲突处理等,提高了用户体验。

  3. 智能类型判断:根据插件类型自动加载对应的编辑界面(代码编辑器或表单编辑器),提供针对性的编辑体验。

  4. 类型自动生成:使用idl2ts-cli工具自动生成TypeScript类型定义,提高了开发效率和代码质量。

  5. API封装:对后端API进行了良好的封装,使用统一的错误处理和响应格式,简化了前端调用逻辑。

7.4 代码质量与可维护性

  1. 代码组织:代码组织合理,遵循单一职责原则,各模块之间耦合度低。例如,将锁定机制封装在专门的工具函数中,便于复用和维护。

  2. 注释完善:关键代码和复杂逻辑都有详细的注释说明,便于理解和维护。

  3. 错误处理:完善的错误处理机制,包括try-catch和API错误处理,确保系统的稳定性。

  4. 可扩展性:系统设计具有良好的可扩展性,便于添加新的插件类型和编辑功能。

7.5 未来优化方向

  1. 性能优化:对于大型代码插件的编辑,可考虑使用代码分块加载和增量保存等技术优化性能。

  2. 用户体验优化:增加代码自动补全、表单模板推荐等功能,进一步提高用户的编辑效率。

  3. 协作编辑:考虑添加实时协作编辑功能,支持多用户同时编辑同一插件,通过冲突检测和合并机制解决编辑冲突。

  4. 版本管理:增加插件版本管理功能,支持查看和回滚历史版本。

  5. 国际化支持:完善国际化支持,覆盖更多的语言和地区。

  6. 测试覆盖:增加单元测试和集成测试的覆盖率,特别是对锁定机制等关键功能的测试,提高代码的稳定性和可靠性。

通过对插件编辑功能前端源码的分析,我们可以看出Coze平台在设计和实现上的专业性和先进性,为用户提供了强大且易用的插件编辑工具,同时保证了多用户环境下的数据安全和一致性。

相关推荐
hhzz2 小时前
Pythoner 的Flask项目实践-在web页面实现矢量数据转换工具集功能(附源码)
前端·python·flask
lypzcgf2 小时前
Coze源码分析-资源库-编辑工作流-前端源码-核心组件
前端·工作流·coze·coze源码分析·智能体平台·agent平台
有梦想的攻城狮2 小时前
从0开始学vue:vue和react的比较
前端·vue.js·react.js
FIN66682 小时前
昂瑞微,凭啥?
前端·科技·产品运营·创业创新·制造·射频工程
kura_tsuki3 小时前
[Web网页] Web 基础
前端
鱼樱前端4 小时前
uni-app快速入门章法(二)
前端·uni-app
silent_missile4 小时前
vue3父组件和子组件之间传递数据
前端·javascript·vue.js
IT_陈寒5 小时前
Vue 3.4 实战:这7个Composition API技巧让我的开发效率飙升50%
前端·人工智能·后端
少年阿闯~~7 小时前
HTML——1px问题
前端·html