From a6d444307451400446966224675e823cd5ab950c Mon Sep 17 00:00:00 2001 From: Finley Ge Date: Fri, 3 Jul 2026 18:26:25 +0800 Subject: [PATCH 1/5] refactor: simplify workflow and plugin handling --- .gitignore | 2 + .../openapi/core/plugin/marketplace/api.ts | 14 +- .../core/plugin/tool/TagFilterBox.tsx | 160 ++++- .../components/core/plugin/tool/ToolCard.tsx | 141 +++- .../core/plugin/tool/ToolDetailDrawer.tsx | 91 ++- packages/web/i18n/en/app.json | 15 +- packages/web/i18n/zh-CN/app.json | 13 +- packages/web/i18n/zh-Hant/app.json | 13 +- packages/web/styles/theme.ts | 23 + pnpm-lock.yaml | 2 +- .../config/tool/SystemToolConfigModal.tsx | 315 +++++--- .../config/tool/WorkflowToolConfigModal.tsx | 678 +++++++++++------- .../api/core/plugin/admin/tool/app/update.ts | 21 +- .../api/core/plugin/admin/tool/update.ts | 25 +- .../app/src/pages/config/tool/marketplace.tsx | 201 +++++- projects/marketplace/.env.template | 1 + projects/marketplace/src/env.ts | 1 + .../src/pages/api/admin/pkg/delete.ts | 4 +- .../src/pages/api/admin/pkg/upload.ts | 9 +- .../marketplace/src/pages/api/tool/list.ts | 23 +- projects/marketplace/src/pages/index.tsx | 76 +- projects/marketplace/src/service/auth.ts | 48 ++ projects/marketplace/src/service/tool/data.ts | 1 + projects/marketplace/src/web/query.ts | 5 + projects/marketplace/test/env.test.ts | 10 + .../test/pages/api/admin/pkg/delete.test.ts | 5 +- .../test/pages/api/admin/pkg/upload.test.ts | 158 ++++ projects/marketplace/vitest.config.ts | 1 + 28 files changed, 1549 insertions(+), 507 deletions(-) create mode 100644 projects/marketplace/test/pages/api/admin/pkg/upload.test.ts diff --git a/.gitignore b/.gitignore index adb43235fe2f..05007dcde380 100644 --- a/.gitignore +++ b/.gitignore @@ -49,3 +49,5 @@ pro/admin/worker/ /pro/llm_benchmark/content_benchmark/.env.*.local /pro/llm_benchmark/content_benchmark/eval-runs/ /pro/llm_benchmark/content_benchmark/.cache/ +.gstack/ +.gstack/ diff --git a/packages/global/openapi/core/plugin/marketplace/api.ts b/packages/global/openapi/core/plugin/marketplace/api.ts index bb182788db57..584c7410f78c 100644 --- a/packages/global/openapi/core/plugin/marketplace/api.ts +++ b/packages/global/openapi/core/plugin/marketplace/api.ts @@ -4,7 +4,13 @@ import { PluginToolTagSchema } from '../../../../core/plugin/type'; import type { ToolListItemType } from '../../../../sdk/fastgpt-plugin'; export const MarketplaceOfficialSource = 'official'; +export const MarketplaceCommunitySource = 'community'; export const MarketplacePkgSourceSchema = z.string().trim().min(1); +export const MarketplaceSourceFilterSchema = z.enum([ + MarketplaceOfficialSource, + MarketplaceCommunitySource +]); +export type MarketplaceSourceFilterType = z.infer; const formatToolDetailSchema = z.object({}); const formatToolSimpleSchema = z.object({}); @@ -15,6 +21,7 @@ export type MarketplaceToolListItemType = ToolListItemType & { toolId: string; downloadCount: number; downloadUrl?: string; + source?: string; }; export const MarketplaceToolDetailItemSchema = formatToolDetailSchema.extend({ @@ -27,7 +34,8 @@ export const MarketplaceToolDetailSchema = z.object({ // List export const GetMarketplaceToolsBodySchema = PaginationSchema.extend({ searchKey: z.string().optional(), - tags: z.array(z.string()).nullish() + tags: z.array(z.string()).nullish(), + source: MarketplaceSourceFilterSchema.optional() }); export type GetMarketplaceToolsBodyType = z.infer; @@ -98,9 +106,7 @@ export const DeleteMarketplacePkgResponseSchema = z.object({ description: '插件来源' }) }); -export type DeleteMarketplacePkgResponseType = z.infer< - typeof DeleteMarketplacePkgResponseSchema ->; +export type DeleteMarketplacePkgResponseType = z.infer; // Tags export const GetMarketplaceToolTagsResponseSchema = z.array(PluginToolTagSchema); diff --git a/packages/web/components/core/plugin/tool/TagFilterBox.tsx b/packages/web/components/core/plugin/tool/TagFilterBox.tsx index a9ec35e2302e..923fd0e2cf01 100644 --- a/packages/web/components/core/plugin/tool/TagFilterBox.tsx +++ b/packages/web/components/core/plugin/tool/TagFilterBox.tsx @@ -1,21 +1,38 @@ -import { Box, Flex } from '@chakra-ui/react'; +import { Box, Flex, Menu, MenuButton, MenuItem, MenuList, Portal } from '@chakra-ui/react'; import { useTranslation } from 'next-i18next'; import { parseI18nString } from '@fastgpt/global/common/i18n/utils'; import type { SystemPluginToolTagType } from '@fastgpt/global/core/plugin/type'; import React, { useMemo } from 'react'; +import MyIcon from '../../../common/Icon'; + +export type MarketplaceSourceFilterValue = 'official' | 'community'; const ToolTagFilterBox = ({ tags, selectedTagIds, onTagSelect, - size = 'base' + selectedSource, + onSourceSelect, + size = 'base', + variant = 'default' }: { tags: SystemPluginToolTagType[]; selectedTagIds: string[]; onTagSelect: (tagIds: string[]) => void; + selectedSource?: MarketplaceSourceFilterValue; + onSourceSelect?: (source?: MarketplaceSourceFilterValue) => void; size?: 'base' | 'sm'; + variant?: 'default' | 'marketplace'; }) => { const { t, i18n } = useTranslation(); + const isMarketplaceVariant = variant === 'marketplace'; + const sourceOptions = [ + { label: t('common:All'), value: undefined }, + { label: t('app:toolkit_official'), value: 'official' }, + { label: t('app:toolkit_community'), value: 'community' } + ] as const; + const selectedSourceLabel = + sourceOptions.find((option) => option.value === selectedSource)?.label || t('common:All'); const toggleTag = (tagId: string) => { if (selectedTagIds.includes(tagId)) { @@ -27,11 +44,18 @@ const ToolTagFilterBox = ({ const tagBaseStyles = useMemo(() => { const sizeStyles = { - base: { - px: 3, - py: 1.5, - fontSize: 'sm' - }, + base: isMarketplaceVariant + ? { + px: '13px', + h: '35px', + fontSize: '14px', + lineHeight: '21px' + } + : { + px: 3, + py: 1.5, + fontSize: 'sm' + }, sm: { px: 2, py: 1, @@ -42,14 +66,20 @@ const ToolTagFilterBox = ({ return { ...sizeStyles[size], fontWeight: 'medium', - color: 'myGray.700', + color: isMarketplaceVariant ? '#383F50' : 'myGray.700', border: '1px solid', - borderColor: 'myGray.200', + borderColor: isMarketplaceVariant ? '#E8EBF0' : 'myGray.200', whiteSpace: 'nowrap', flexShrink: 0, - cursor: 'pointer' + cursor: 'pointer', + ...(isMarketplaceVariant + ? { + alignItems: 'center', + justifyContent: 'center' + } + : {}) }; - }, [size]); + }, [isMarketplaceVariant, size]); return ( + {isMarketplaceVariant ? ( + + + + {selectedSourceLabel} + + + + + + + + {sourceOptions.map((option) => { + const isSelected = option.value === selectedSource; + + return ( + onSourceSelect?.(option.value)} + > + {option.label} + {isSelected && } + + ); + })} + + + + ) : ( + onTagSelect([])} + > + {t('common:All')} + + )} onTagSelect([])} - > - {t('common:All')} - - + mx={2} + h={'20px'} + w={'1px'} + bg={isMarketplaceVariant ? '#E8EBF0' : 'myGray.200'} + flexShrink={0} + /> {tags.map((tag) => { @@ -100,8 +213,15 @@ const ToolTagFilterBox = ({ { + if (isMarketplaceVariant) { + return isSelected ? 'myGray.50 !important' : 'white'; + } + return isSelected ? 'myGray.150 !important' : 'transparent'; + })()} + _hover={isMarketplaceVariant ? { bg: 'myGray.50' } : undefined} onClick={() => toggleTag(tag.tagId)} > {t(parseI18nString(tag.tagName, i18n.language))} diff --git a/packages/web/components/core/plugin/tool/ToolCard.tsx b/packages/web/components/core/plugin/tool/ToolCard.tsx index 2e053de3938a..853dd18f73d6 100644 --- a/packages/web/components/core/plugin/tool/ToolCard.tsx +++ b/packages/web/components/core/plugin/tool/ToolCard.tsx @@ -8,6 +8,8 @@ import { parseI18nString } from '@fastgpt/global/common/i18n/utils'; import { PluginStatusEnum, type PluginStatusType } from '@fastgpt/global/core/plugin/type'; import DebugToolTag from './DebugToolTag'; +const marketplaceOfficialSource = 'official'; + export type ToolCardItemType = { id: string; name: string; @@ -43,7 +45,8 @@ const ToolCard = ({ onDelete, onUpdate, onClickCard, - showActionButton = true + showActionButton = true, + variant = 'default' }: { item: ToolCardItemType; systemTitle?: string; @@ -55,10 +58,16 @@ const ToolCard = ({ onUpdate?: () => Promise; onClickCard?: () => void; showActionButton?: boolean; + variant?: 'default' | 'marketplace'; }) => { const { t, i18n } = useTranslation(); const tagsContainerRef = useRef(null); const [visibleTagsCount, setVisibleTagsCount] = useState(item.tags?.length || 0); + const isMarketplaceVariant = variant === 'marketplace'; + const showOfficialBadge = + isMarketplaceVariant && (!item.source || item.source === marketplaceOfficialSource); + const showMarketplaceUninstallButton = + isMarketplaceVariant && mode === 'admin' && item.installed && !showActionButton; useEffect(() => { const calculate = () => { @@ -109,6 +118,8 @@ const ToolCard = ({ }; if (mode === 'admin') { + if (isMarketplaceVariant) return null; + return item.installed ? { label: t('app:toolkit_installed'), @@ -130,14 +141,25 @@ const ToolCard = ({ } : null; } - }, [item.installed, item.status]); + }, [isMarketplaceVariant, item.installed, item.status, mode, t]); return ( )} - - - + + + {parseI18nString(item.name, i18n.language)} + {showOfficialBadge && ( + + {t('app:toolkit_official')} + + )} {item.isDebug && } {statusLabel && ( {parseI18nString(item.description || '', i18n.language) || t('app:templateMarket.no_intro')} - + {item.tags?.slice(0, visibleTagsCount).map((tag) => { return ( @@ -264,14 +327,15 @@ const ToolCard = ({ })} {item.tags && item.tags.length > visibleTagsCount && ( +{item.tags.length - visibleTagsCount} @@ -279,11 +343,18 @@ const ToolCard = ({ )} - + {`by ${item.author || systemTitle || 'FastGPT'}`} {/*TODO: when statistics is ready*/} {/* @@ -296,7 +367,21 @@ const ToolCard = ({ */} - {showActionButton && mode === 'marketplace' ? ( + {showMarketplaceUninstallButton ? ( + + ) : showActionButton && mode === 'marketplace' ? ( - )} - {hasUpdateButton && ( - - )} - - ); - })()} + {showInstallButton && ( + + )} + {showUninstallButton && ( + + )} + {hasUpdateButton && ( + + )} )} diff --git a/packages/web/i18n/en/app.json b/packages/web/i18n/en/app.json index 32efd6f36ca0..f99733c63779 100644 --- a/packages/web/i18n/en/app.json +++ b/packages/web/i18n/en/app.json @@ -83,6 +83,7 @@ "confirm_delete_chats": "Are you sure you want to delete {{n}} chat records?\nThis cannot be undone.", "confirm_delete_folder_tip": "When you delete this folder, all applications and corresponding chat records under it will be deleted.", "confirm_delete_tool": "Confirm to delete this tool?", + "confirm_uninstall_tool": "Confirm uninstall this plugin? Its status will become \"Uninstalled\", and apps using it will no longer be able to call it. Enter the plugin name to confirm.", "copilot_config_message": "Current Node Configuration Information: \n Code Type: {{codeType}} \n Current Code: \\\\`\\\\`\\\\`{{codeType}} \n{{code}} \\\\`\\\\`\\\\` \n Input Parameters: {{inputs}} \n Output Parameters: {{outputs}}", "copilot_confirm_message": "The original configuration has been received to understand the current code structure and input and output parameters. \nPlease explain your optimization requirements.", "copy_one_app": "Create Duplicate", @@ -115,6 +116,9 @@ "custom_plugin_config_title": "{{name}} Configuration", "custom_plugin_create": "Create Plugin", "custom_plugin_delete_success": "Delete successful", + "custom_plugin_install_success": "Install successful", + "custom_plugin_reinstall_success": "Reinstall successful", + "custom_plugin_uninstall_success": "Uninstall successful", "custom_plugin_has_token_fee_label": "Charge Token Fee", "custom_plugin_intro_label": "Introduction", "custom_plugin_intro_placeholder": "Add an introduction for this application", @@ -383,7 +387,7 @@ "tool_input_param_tip": "This tool requires configuration of relevant information for normal operation.", "tool_not_active": "This tool has not been activated yet", "tool_not_desc": "The tool lacks a description ~", - "tool_offset_tips": "This tool is no longer available and will interrupt application operation. Please replace it immediately.", + "tool_offset_tips": "This tool has been uninstalled and will interrupt application operation. Please replace it immediately.", "tool_param_config": "Parameter configuration", "tool_params_description_tips": "The description of parameter functions, if used as tool invocation parameters, affects the model tool invocation effect.", "tool_run_free": "This tool runs without points consumption", @@ -396,8 +400,10 @@ "toolkit_activation_required": "Activation Required", "toolkit_add_resource": "Add resources", "toolkit_basic_config": "Basic Configuration", + "toolkit_basic_info": "Basic Info", "toolkit_batch_update": "Batch Update", "toolkit_call_points_label": "Call Points", + "toolkit_community": "Community", "toolkit_config_system_key": "Configure System Key", "toolkit_contribute_resource": "Contribute Resource", "toolkit_debug_connection_link": "Debug Link", @@ -425,6 +431,7 @@ "toolkit_name": "Name", "toolkit_no_call_points": "This tool does not require call points", "toolkit_no_plugins": "No plugins", + "toolkit_official": "Official", "toolkit_open_marketplace": "Open Marketplace", "toolkit_outputs": "Output Parameters", "toolkit_params_description": "Parameters", @@ -435,12 +442,13 @@ "toolkit_plugin_version": "Plugin Version", "toolkit_promote_tags": "Promote Tags", "toolkit_promote_tags_tip": "Users with the following tags will see the \"Promoted\" badge", + "toolkit_reinstall": "Reinstall", "toolkit_search_placeholder": "Search tool names", "toolkit_select_app": "Select an existing app", "toolkit_select_user_tags": "Select user tags", "toolkit_status": "Status", "toolkit_status_normal": "Normal", - "toolkit_status_offline": "Offline", + "toolkit_status_offline": "Uninstalled", "toolkit_status_soon_offline": "Soon Offline", "toolkit_system_key": "System Key", "toolkit_system_key_configured": "Configured", @@ -462,7 +470,8 @@ "toolkit_tool_name": "Tool Name", "toolkit_tutorial_link": "Open tutorial link", "toolkit_uninstall": "Uninstall", - "toolkit_uninstalled": "Uninstalled", + "toolkit_uninstalled_reinstall_tip": "This plugin has been uninstalled. Please reinstall it.", + "toolkit_uninstalled": "Not Installed", "toolkit_updatable": "Updates Available", "toolkit_updatable_plugins": "Updatable Plugins", "toolkit_user_guide": "User Guide", diff --git a/packages/web/i18n/zh-CN/app.json b/packages/web/i18n/zh-CN/app.json index f2ad0de26078..078a709925c4 100644 --- a/packages/web/i18n/zh-CN/app.json +++ b/packages/web/i18n/zh-CN/app.json @@ -83,6 +83,7 @@ "confirm_delete_chats": "确认删除 {{n}} 组对话记录?记录将会永久删除!", "confirm_delete_folder_tip": "删除该文件夹时,将会删除它下面所有应用及对应的聊天记录。", "confirm_delete_tool": "确认删除该工具?", + "confirm_uninstall_tool": "确认卸载该插件?卸载后插件状态将变更为“已卸载”,正在使用该插件的应用将无法继续调用。请输入插件名称确认。", "copilot_config_message": "`当前节点配置信息: \n代码类型:{{codeType}} \n当前代码: \\`\\`\\`{{codeType}} \n{{code}} \\`\\`\\` \n输入参数: {{inputs}} \n输出参数: {{outputs}}`", "copilot_confirm_message": "已接收到原始配置,了解当前代码结构和输入输出参数。请说明您的优化需求。", "copy_one_app": "创建副本", @@ -115,6 +116,9 @@ "custom_plugin_config_title": "{{name}}配置", "custom_plugin_create": "新建插件", "custom_plugin_delete_success": "删除成功", + "custom_plugin_install_success": "安装成功", + "custom_plugin_reinstall_success": "重新安装成功", + "custom_plugin_uninstall_success": "卸载成功", "custom_plugin_has_token_fee_label": "是否收取 Token 费用", "custom_plugin_intro_label": "介绍", "custom_plugin_intro_placeholder": "为这个应用添加一个介绍", @@ -383,7 +387,7 @@ "tool_input_param_tip": "该工具正常运行需要配置相关信息", "tool_not_active": "该工具尚未激活", "tool_not_desc": "工具缺少描述~", - "tool_offset_tips": "该工具已无法使用,将中断应用运行,请立即替换", + "tool_offset_tips": "该工具已卸载,将中断应用运行,请立即替换", "tool_param_config": "参数配置", "tool_params_description_tips": "参数功能的描述,若作为工具调用参数,影响模型工具调用效果", "tool_run_free": "该工具运行无积分消耗", @@ -396,8 +400,10 @@ "toolkit_activation_required": "需要激活", "toolkit_add_resource": "添加插件", "toolkit_basic_config": "基础配置", + "toolkit_basic_info": "基础信息", "toolkit_batch_update": "批量更新", "toolkit_call_points_label": "调用积分", + "toolkit_community": "社区", "toolkit_config_system_key": "是否配置系统密钥", "toolkit_contribute_resource": "贡献插件", "toolkit_debug_connection_link": "调试链接", @@ -425,6 +431,7 @@ "toolkit_name": "名称", "toolkit_no_call_points": "该工具无需调用积分", "toolkit_no_plugins": "暂无插件", + "toolkit_official": "官方", "toolkit_open_marketplace": "打开插件市场", "toolkit_outputs": "输出参数", "toolkit_params_description": "参数说明", @@ -435,12 +442,13 @@ "toolkit_plugin_version": "插件版本", "toolkit_promote_tags": "推荐标签", "toolkit_promote_tags_tip": "拥有以下标签的用户会看到\"推荐\"标识", + "toolkit_reinstall": "重新安装", "toolkit_search_placeholder": "搜索工具名称", "toolkit_select_app": "选择现有应用", "toolkit_select_user_tags": "选择用户标签", "toolkit_status": "状态", "toolkit_status_normal": "正常", - "toolkit_status_offline": "已下线", + "toolkit_status_offline": "已卸载", "toolkit_status_soon_offline": "即将下线", "toolkit_system_key": "系统密钥", "toolkit_system_key_configured": "已配置", @@ -462,6 +470,7 @@ "toolkit_tool_name": "工具名", "toolkit_tutorial_link": "查看教程链接", "toolkit_uninstall": "卸载", + "toolkit_uninstalled_reinstall_tip": "该插件已卸载,请重新安装", "toolkit_uninstalled": "未安装", "toolkit_updatable": "可更新", "toolkit_updatable_plugins": "可更新的插件", diff --git a/packages/web/i18n/zh-Hant/app.json b/packages/web/i18n/zh-Hant/app.json index 324f99348f49..636cd204e824 100644 --- a/packages/web/i18n/zh-Hant/app.json +++ b/packages/web/i18n/zh-Hant/app.json @@ -81,6 +81,7 @@ "confirm_delete_chats": "確認刪除 {{n}} 組對話記錄?\n記錄將會永久刪除!", "confirm_delete_folder_tip": "刪除該文件夾時,將會刪除它下面所有應用及對應的聊天記錄。", "confirm_delete_tool": "確認刪除該工具?", + "confirm_uninstall_tool": "確認卸載該插件?卸載後插件狀態將變更為「已卸載」,正在使用該插件的應用將無法繼續調用。請輸入插件名稱確認。", "copilot_config_message": "當前節點配置信息: \n代碼類型:{{codeType}} \n當前代碼: \\\\`\\\\`\\\\`{{codeType}} \n{{code}} \\\\`\\\\`\\\\` \n輸入參數: {{inputs}} \n輸出參數: {{outputs}}", "copilot_confirm_message": "已接收到原始配置,了解當前代碼結構和輸入輸出參數。\n請說明您的優化需求。", "copy_one_app": "建立副本", @@ -112,6 +113,9 @@ "custom_plugin_config_title": "{{name}}配置", "custom_plugin_create": "新建外掛", "custom_plugin_delete_success": "刪除成功", + "custom_plugin_install_success": "安裝成功", + "custom_plugin_reinstall_success": "重新安裝成功", + "custom_plugin_uninstall_success": "卸載成功", "custom_plugin_has_token_fee_label": "是否收取 Token 費用", "custom_plugin_intro_label": "介紹", "custom_plugin_intro_placeholder": "為這個應用程式新增一個介紹", @@ -373,7 +377,7 @@ "tool_input_param_tip": "該工具正常運行需要配置相關信息", "tool_not_active": "該工具尚未激活", "tool_not_desc": "工具缺少描述~", - "tool_offset_tips": "該工具已無法使用,將中斷應用運行,請立即替換", + "tool_offset_tips": "該工具已卸載,將中斷應用運行,請立即替換", "tool_param_config": "參數配置", "tool_params_description_tips": "參數功能的描述,若作為工具調用參數,影響模型工具調用效果", "tool_run_free": "該工具運行無積分消耗", @@ -386,8 +390,10 @@ "toolkit_activation_required": "需要激活", "toolkit_add_resource": "添加資源", "toolkit_basic_config": "基礎配置", + "toolkit_basic_info": "基礎資訊", "toolkit_batch_update": "批次更新", "toolkit_call_points_label": "調用積分", + "toolkit_community": "社區", "toolkit_config_system_key": "是否配置系統密鑰", "toolkit_contribute_resource": "貢獻資源", "toolkit_debug_connection_link": "調試連結", @@ -415,6 +421,7 @@ "toolkit_name": "名稱", "toolkit_no_call_points": "該工具無需調用積分", "toolkit_no_plugins": "暫無插件", + "toolkit_official": "官方", "toolkit_open_marketplace": "打開資源市場", "toolkit_outputs": "輸出參數", "toolkit_params_description": "參數說明", @@ -425,12 +432,13 @@ "toolkit_plugin_version": "插件版本", "toolkit_promote_tags": "推薦標籤", "toolkit_promote_tags_tip": "擁有以下標籤的使用者會看到「推薦」標識", + "toolkit_reinstall": "重新安裝", "toolkit_search_placeholder": "搜索工具名稱", "toolkit_select_app": "選擇現有應用", "toolkit_select_user_tags": "選擇使用者標籤", "toolkit_status": "狀態", "toolkit_status_normal": "正常", - "toolkit_status_offline": "下線", + "toolkit_status_offline": "已卸載", "toolkit_status_soon_offline": "即將下線", "toolkit_system_key": "系統密鑰", "toolkit_system_key_configured": "已配置", @@ -451,6 +459,7 @@ "toolkit_tool_name": "工具名", "toolkit_tutorial_link": "查看教學連結", "toolkit_uninstall": "卸載", + "toolkit_uninstalled_reinstall_tip": "該插件已卸載,請重新安裝", "toolkit_uninstalled": "未安裝", "toolkit_updatable": "可更新", "toolkit_updatable_plugins": "可更新的外掛程式", diff --git a/packages/web/styles/theme.ts b/packages/web/styles/theme.ts index 7256359f35bd..7c6be15928dd 100644 --- a/packages/web/styles/theme.ts +++ b/packages/web/styles/theme.ts @@ -247,6 +247,29 @@ const Button = defineStyleConfig({ color: 'red.600' } }, + dangerOutline: { + color: 'red.600', + border: '1px solid', + borderColor: 'red.500', + bg: 'white', + transition: 'background 0.1s', + boxShadow: '0px 0px 1px 0px rgba(19, 51, 107, 0.08), 0px 1px 2px 0px rgba(19, 51, 107, 0.05)', + _hover: { + color: 'red.600', + borderColor: 'red.600', + bg: 'red.50' + }, + _active: { + color: 'red.600', + borderColor: 'red.600', + bg: 'red.50' + }, + _disabled: { + color: 'red.300 !important', + borderColor: 'red.200 !important', + bg: 'white !important' + } + }, grayBase: { bg: 'myGray.150', color: 'myGray.900', diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3318f5038d91..144ae3d39f8d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -21614,7 +21614,7 @@ snapshots: obug: 2.1.1 std-env: 4.1.0 tinyrainbow: 3.1.0 - vitest: 4.1.5(@opentelemetry/api@1.9.0)(@types/node@20.17.24)(@vitest/coverage-v8@4.1.5)(jsdom@26.1.0(bufferutil@4.1.0)(canvas@3.2.3)(utf-8-validate@5.0.10))(vite@6.2.2(@types/node@20.17.24)(jiti@1.21.7)(lightningcss@1.32.0)(sass@1.85.1)(terser@5.39.0)(tsx@4.20.6)(yaml@2.8.4)) + vitest: 4.1.5(@opentelemetry/api@1.9.0)(@types/node@20.17.24)(@vitest/coverage-v8@4.1.5)(jsdom@26.1.0(bufferutil@4.1.0)(canvas@3.2.3)(utf-8-validate@5.0.10))(vite@6.2.2(@types/node@20.17.24)(jiti@2.7.0)(lightningcss@1.32.0)(sass@1.85.1)(terser@5.39.0)(tsx@4.20.6)(yaml@2.8.4)) '@vitest/expect@4.1.5': dependencies: diff --git a/projects/app/src/pageComponents/config/tool/SystemToolConfigModal.tsx b/projects/app/src/pageComponents/config/tool/SystemToolConfigModal.tsx index a64a46ad272a..2f3485834aca 100644 --- a/projects/app/src/pageComponents/config/tool/SystemToolConfigModal.tsx +++ b/projects/app/src/pageComponents/config/tool/SystemToolConfigModal.tsx @@ -53,6 +53,7 @@ import type { UpdateSystemToolBodyType } from '@fastgpt/global/openapi/core/plug import CopyBox from '@fastgpt/web/components/common/String/CopyBox'; import MyIcon from '@fastgpt/web/components/common/Icon'; import { jsonSchema2SecretInput } from '@fastgpt/global/core/app/jsonschema'; +import { useConfirm } from '@fastgpt/web/hooks/useConfirm'; const COST_LIMITS = { max: 1000, min: 0, step: 0.1 }; const FORM_LABEL_WIDTH = '160px'; @@ -181,6 +182,9 @@ const SystemToolConfigModal = ({ const [runtimeConfig, setRuntimeConfig] = useState({}); const [selectedVersion, setSelectedVersion] = useState(); const [tabIndex, setTabIndex] = useState(0); + const { openConfirm: openUninstallConfirm, ConfirmModal: UninstallConfirmModal } = useConfirm({ + type: 'delete' + }); useEffect(() => { setSelectedVersion(undefined); @@ -245,6 +249,7 @@ const SystemToolConfigModal = ({ 'promoteTags', 'hideTags' ]); + const isToolOffline = status === PluginStatusEnum.Offline; // 从 tool 读取只读数据 const inputList = useMemo( @@ -345,8 +350,7 @@ const SystemToolConfigModal = ({ { label: t('app:toolkit_status_soon_offline'), value: PluginStatusEnum.SoonOffline - }, - { label: t('app:toolkit_status_offline'), value: PluginStatusEnum.Offline } + } ], [t] ); @@ -383,6 +387,57 @@ const SystemToolConfigModal = ({ } ); + const { runAsync: onUninstall, loading: uninstalling } = useRequest( + () => + putAdminUpdateSystemTool({ + id: toolId, + status: PluginStatusEnum.Offline, + children: tool?.children?.map((child) => ({ + id: child.id, + systemKeyCost: child.systemKeyCost + })) + }), + { + successToast: t('app:custom_plugin_uninstall_success'), + onSuccess() { + onSuccess(); + onClose(); + } + } + ); + + const { runAsync: onReinstall, loading: reinstalling } = useRequest( + () => + putAdminUpdateSystemTool({ + id: toolId, + status: PluginStatusEnum.Normal, + children: tool?.children?.map((child) => ({ + id: child.id, + systemKeyCost: child.systemKeyCost + })) + }), + { + successToast: t('app:custom_plugin_install_success'), + onSuccess() { + onSuccess(); + onClose(); + } + } + ); + + const openToolUninstallConfirm = () => { + const toolName = parseI18nString(tool?.name || '', i18n.language) || toolId; + + openUninstallConfirm({ + title: t('app:toolkit_uninstall'), + customContent: t('app:confirm_uninstall_tool'), + confirmText: t('app:toolkit_uninstall'), + confirmButtonVariant: 'dangerOutline', + inputConfirmText: toolName, + onConfirm: onUninstall + })(); + }; + // Secret input render const renderInputField = (item: InputConfigType) => { const fieldValue = secretsVal?.[item.key]; @@ -403,7 +458,7 @@ const SystemToolConfigModal = ({ if (item.inputType === 'switch') { return ( - + ); } @@ -417,6 +472,7 @@ const SystemToolConfigModal = ({ list={item.list ?? []} value={typeof fieldValue === 'string' ? fieldValue : undefined} placeholder={item.label} + isDisabled={isToolOffline} onChange={(value) => { setValue(`secretsVal.${item.key}`, value, { shouldDirty: true, @@ -434,6 +490,7 @@ const SystemToolConfigModal = ({ bg={'white'} h={9} borderColor={'myGray.200'} + isDisabled={isToolOffline} {...register(`secretsVal.${item.key}`, { required: item.required })} @@ -451,6 +508,7 @@ const SystemToolConfigModal = ({ register={register} name="systemKeyCost" defaultValue={0} + isDisabled={isToolOffline} {...COST_LIMITS} /> @@ -477,6 +535,7 @@ const SystemToolConfigModal = ({ fontSize={'xs'} min={field.min} step={1} + isDisabled={isToolOffline} value={ runtimeConfig[field.key] === undefined || runtimeConfig[field.key] === null ? '' @@ -517,7 +576,11 @@ const SystemToolConfigModal = ({ width={'100%'} h={9} value={status} + valueLabel={ + status === PluginStatusEnum.Offline ? t('app:toolkit_status_offline') : undefined + } list={pluginStatusSelectList} + isDisabled={isToolOffline} onChange={(e) => setValue('status', e)} /> @@ -541,6 +604,7 @@ const SystemToolConfigModal = ({ h={9} borderRadius={'sm'} bg={'white'} + isDisabled={isToolOffline} /> @@ -549,6 +613,7 @@ const SystemToolConfigModal = ({ { const val = e.target.checked; if (val) { @@ -578,6 +643,7 @@ const SystemToolConfigModal = ({ h={9} borderRadius={'sm'} bg={'white'} + isDisabled={isToolOffline} /> @@ -591,6 +657,7 @@ const SystemToolConfigModal = ({ h={9} borderRadius={'sm'} bg={'white'} + isDisabled={isToolOffline} /> @@ -633,6 +700,7 @@ const SystemToolConfigModal = ({ register={register} defaultValue={0} name={`children.${index}.systemKeyCost`} + isDisabled={isToolOffline} {...COST_LIMITS} /> setSelectedVersion(version)} /> @@ -678,13 +747,66 @@ const SystemToolConfigModal = ({ ); + const offlineVersionInfoSection = ( + + + {t('app:toolkit_id')}: + + + {tool?.id || toolId} + + + + + + } + > + + + width={'100%'} + h={9} + value={selectedVersion || tool?.version} + list={versionSelectList} + isLoading={loadingVersions} + isDisabled + onChange={(version) => setSelectedVersion(version)} + /> + + + + + {tool?.name || '-'} + + + + + + {tool?.intro || '-'} + + + + ); + return ( - - - - {t('app:toolkit_plugin_config')} - - + - {t('admin_plugin:toolkit_runtime_config')} - - - - - - - {basicConfigSection} - {versionInfoSection} - - - - {runtimeConfigSection || ( - - {t('admin_plugin:toolkit_no_runtime_config')} - - )} - - - + + {t('app:toolkit_plugin_config')} + + + {t('admin_plugin:toolkit_runtime_config')} + + + + + + + {basicConfigSection} + {versionInfoSection} + + + + {runtimeConfigSection || ( + + {t('admin_plugin:toolkit_no_runtime_config')} + + )} + + + + )} - - {showRuntimeConfig && tabIndex === 1 && ( - - {t('common:Reset')} - - } - /> - )} - - - - - + {isToolOffline ? ( + <> + + {t('app:toolkit_uninstalled_reinstall_tip')} + + + + + + + ) : ( + <> + + + {showRuntimeConfig && tabIndex === 1 && ( + + {t('common:Reset')} + + } + /> + )} + + + + + + + )} + ); }; diff --git a/projects/app/src/pageComponents/config/tool/WorkflowToolConfigModal.tsx b/projects/app/src/pageComponents/config/tool/WorkflowToolConfigModal.tsx index 41c5bcebd67a..9f0a7fd40ed7 100644 --- a/projects/app/src/pageComponents/config/tool/WorkflowToolConfigModal.tsx +++ b/projects/app/src/pageComponents/config/tool/WorkflowToolConfigModal.tsx @@ -20,7 +20,6 @@ import { useToast } from '@fastgpt/web/hooks/useToast'; import MyTooltip from '@fastgpt/web/components/common/MyTooltip'; import { getPluginToolTags } from '@/web/core/plugin/toolTag/api'; import { useRequest } from '@fastgpt/web/hooks/useRequest'; -import PopoverConfirm from '@fastgpt/web/components/common/MyPopover/PopoverConfirm'; import MyNumberInput from '@fastgpt/web/components/common/Input/NumberInput'; import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip'; import { PluginStatusEnum, type PluginStatusType } from '@fastgpt/global/core/plugin/type'; @@ -34,13 +33,15 @@ import type { UpdateWorkflowToolBodyType } from '@fastgpt/global/openapi/core/plugin/admin/tool/api'; import { - delAdminSystemTool, getAdminAllSystemAppTool, getAdminSystemToolDetail, postAdminCreateAppTypeTool, putAdminUpdateWorkflowTool } from '@/web/core/plugin/admin/tool/api'; import { parseI18nString } from '@fastgpt/global/common/i18n/utils'; +import { useConfirm } from '@fastgpt/web/hooks/useConfirm'; +import CopyBox from '@fastgpt/web/components/common/String/CopyBox'; +import MyIcon from '@fastgpt/web/components/common/Icon'; export const defaultForm: UpdateWorkflowToolBodyType = { id: '', @@ -76,9 +77,15 @@ const WorkflowToolConfigModal = ({ const name = watch('name'); const avatar = watch('avatar'); const associatedPluginId = watch('associatedPluginId'); + const intro = watch('intro'); const currentCost = watch('currentCost'); const status = watch('status'); const hasTokenFee = watch('hasTokenFee'); + const isToolOffline = status === PluginStatusEnum.Offline; + const [toolVersion, setToolVersion] = useState(''); + const { openConfirm: openUninstallConfirm, ConfirmModal: UninstallConfirmModal } = useConfirm({ + type: 'delete' + }); React.useEffect(() => { setValue('tags', selectedTags); @@ -88,6 +95,7 @@ const WorkflowToolConfigModal = ({ async () => { if (toolId) { const res = await getAdminSystemToolDetail({ toolId }); + setToolVersion(res.version); const form: UpdateWorkflowToolBodyType = { id: res.id, status: res.status, @@ -141,6 +149,16 @@ const WorkflowToolConfigModal = ({ })) || [], [i18n.language, tags] ); + const pluginStatusSelectList = useMemo( + () => [ + { label: t('app:toolkit_status_normal'), value: PluginStatusEnum.Normal }, + { + label: t('app:toolkit_status_soon_offline'), + value: PluginStatusEnum.SoonOffline + } + ], + [t] + ); const currentApp = useMemo(() => { return apps.find((item) => item._id === associatedPluginId); @@ -210,301 +228,435 @@ const WorkflowToolConfigModal = ({ } ); - const { runAsync: onDelete, loading: isDeleting } = useRequest(delAdminSystemTool, { - onSuccess() { - toast({ - title: t('app:custom_plugin_delete_success'), - status: 'success' - }); - onSuccess(); - onClose(); + const { runAsync: onUninstall, loading: isUninstalling } = useRequest( + () => + putAdminUpdateWorkflowTool({ + id: toolId, + status: PluginStatusEnum.Offline + }), + { + successToast: t('app:custom_plugin_uninstall_success'), + onSuccess() { + onSuccess(); + onClose(); + } } - }); + ); + + const { runAsync: onReinstall, loading: isReinstalling } = useRequest( + () => + putAdminUpdateWorkflowTool({ + id: toolId, + status: PluginStatusEnum.Normal + }), + { + successToast: t('app:custom_plugin_install_success'), + onSuccess() { + onSuccess(); + onClose(); + } + } + ); + + const openToolUninstallConfirm = () => { + openUninstallConfirm({ + title: t('app:toolkit_uninstall'), + customContent: t('app:confirm_uninstall_tool'), + confirmText: t('app:toolkit_uninstall'), + confirmButtonVariant: 'dangerOutline', + inputConfirmText: name || toolId, + onConfirm: onUninstall + })(); + }; + + const offlineVersionInfoSection = ( + + + + {t('app:toolkit_version_info')} + + + + {t('app:toolkit_id')}: + + + {toolId} + + + + + + + + + + {t('app:toolkit_plugin_version')} + + + + width={'100%'} + h={9} + value={toolVersion || '-'} + list={[ + { + label: toolVersion || '-', + value: toolVersion || '-' + } + ]} + isDisabled + onChange={setToolVersion} + /> + + + + + {t('app:toolkit_plugin_name')} + + + {name || '-'} + + + + + {t('app:toolkit_plugin_intro')} + + + {intro || '-'} + + + + + ); return ( - - - - {t('app:custom_plugin_name_label')} - - - - - - - - - - {t('app:custom_plugin_intro_label')} - -