Commit 14228b00 by Finley Ge Committed by GitHub

feat: new tool node get the latest version rather than keep latest (#7064)

* fix: workflow system tool use pluginModule

* fix: zip upload cancel && bump plugin sdk version

* Handle latest-version tool previews and confirm keep latest

* chore: update pro submodule

* perf: api

* perf: name

* fix: refine tool preview version selection

* fix(systemTool): sort workflow tool versions by time

---------

Co-authored-by: archer <545436317@qq.com>
parent 4c4384a4
...@@ -13,6 +13,7 @@ import { SystemToolSystemSecretStatusEnum } from '../constants'; ...@@ -13,6 +13,7 @@ import { SystemToolSystemSecretStatusEnum } from '../constants';
export const SystemToolBaseSchema = z.object({ export const SystemToolBaseSchema = z.object({
id: z.string(), id: z.string(),
version: z.string(), version: z.string(),
versionLabel: z.string().optional(),
etag: z.string().optional() etag: z.string().optional()
}); });
...@@ -102,8 +103,14 @@ export const SystemToolDetailSchema = z.object({ ...@@ -102,8 +103,14 @@ export const SystemToolDetailSchema = z.object({
export type SystemToolDetailType = z.infer<typeof SystemToolDetailSchema>; export type SystemToolDetailType = z.infer<typeof SystemToolDetailSchema>;
export const SystemToolVersionSchema = z.object({ export const SystemToolVersionSchema = z.object({
version: z.string(), version: z.string().meta({
versionDescription: z.string().optional() example: '68ad85a7463006c963799a05',
description: '工具版本标识。工作流工具为关联应用版本 ID,普通插件工具为插件版本号'
}),
versionDescription: z.string().optional().meta({
example: 'Workflow v1',
description: '工具版本展示名。工作流工具为关联应用版本名称'
})
}); });
export type SystemToolVersionType = z.infer<typeof SystemToolVersionSchema>; export type SystemToolVersionType = z.infer<typeof SystemToolVersionSchema>;
import z from 'zod';
import {
FlowNodeTemplateTypeSchema,
NodeTemplateListItemTypeSchema
} from '../../../../core/workflow/type/node';
import { OpenAPIFlowNodeOutputItemTypeSchema } from '../../workflow/node';
import { BoolSchema } from '../../../../common/zod';
const ToolNodeTemplateListItemSchema = NodeTemplateListItemTypeSchema.extend({
toolDescription: z.string().optional().meta({
description: '工具调用描述'
})
}).catchall(z.any());
const ToolPreviewNodeResponseSchema = FlowNodeTemplateTypeSchema.omit({
outputs: true
}).extend({
outputs: z.array(OpenAPIFlowNodeOutputItemTypeSchema)
});
/* ============================================================================
* API: 获取系统工具模板列表
* Route: POST /api/core/app/tool/getSystemToolTemplates
* Method: POST
* Description: 获取可添加到工作流中的系统工具模板列表,支持搜索、标签筛选和工具集子工具查询
* Tags: ['系统工具', 'Read']
* ============================================================================ */
export const GetSystemToolTemplatesBodySchema = z.object({
getAll: z.boolean().optional().meta({
example: false,
description: '是否获取全部工具。当前接口保留该字段用于兼容旧调用'
}),
searchKey: z.string().optional().meta({
example: 'weather',
description: '搜索关键字,会匹配工具名称、简介、工具描述和标签'
}),
parentId: z.string().nullish().meta({
example: 'systemTool-map',
description: '工具集父工具 ID。传入后返回该工具集下的子工具模板'
}),
tags: z
.array(z.string())
.optional()
.meta({
example: ['search'],
description: '工具标签筛选条件'
})
});
export type GetSystemToolTemplatesBodyType = z.infer<typeof GetSystemToolTemplatesBodySchema>;
export const GetSystemToolTemplatesResponseSchema = z.array(ToolNodeTemplateListItemSchema);
export type GetSystemToolTemplatesResponseType = z.infer<
typeof GetSystemToolTemplatesResponseSchema
>;
/* ============================================================================
* API: 获取工具路径
* Route: GET /api/core/app/tool/path
* Method: GET
* Description: 获取系统工具或工具集子工具在工具树中的路径
* Tags: ['系统工具', 'Read']
* ============================================================================ */
export const GetToolPathQuerySchema = z.object({
sourceId: z.string().nullish().meta({
example: 'systemTool-map/geocode',
description: '工具 ID。为空时返回空路径'
}),
type: z.enum(['current', 'parent']).optional().meta({
example: 'current',
description: '路径类型:current 返回当前工具路径,parent 只返回父工具路径'
})
});
export type GetToolPathQueryType = z.infer<typeof GetToolPathQuerySchema>;
export const ToolPathItemSchema = z.object({
parentId: z.string().meta({
example: 'systemTool-map',
description: '路径节点 ID'
}),
parentName: z.string().meta({
example: 'Map',
description: '路径节点名称'
})
});
export const GetToolPathResponseSchema = z.array(ToolPathItemSchema);
export type GetToolPathResponseType = z.infer<typeof GetToolPathResponseSchema>;
/* ============================================================================
* API: 获取工具预览节点
* Route: GET /api/core/app/tool/getPreviewNode
* Method: GET
* Description: 根据工具 ID 和版本配置生成可插入工作流画布的工具节点模板,支持系统工具和我的工具(MCP、HTTP、工作流工具)
* Tags: ['系统工具', 'HTTP 工具管理', 'MCP 工具管理', '团队插件管理', 'Read']
* ============================================================================ */
const GetPreviewNodeBaseQuerySchema = z.object({
appId: z.string().meta({
example: 'systemTool-weather',
description:
'工具 ID,支持系统工具 systemTool/commercial、我的工具 personal/mcp/http 组合 ID 及工具集子工具 ID'
})
});
export const GetPreviewNodeQuerySchema = z.union([
GetPreviewNodeBaseQuerySchema.extend({
versionId: z.string().meta({
example: '68ad85a7463006c963799a05',
description:
'工具版本 ID,与 getLatestVersion 必须二选一。传空字符串时返回最新版节点数据,但响应中的 version 为空'
}),
getLatestVersion: z.undefined().optional()
}),
GetPreviewNodeBaseQuerySchema.extend({
versionId: z.undefined().optional(),
getLatestVersion: BoolSchema.refine((value) => value === true, {
message: 'getLatestVersion must be true when provided'
}).meta({
example: true,
description:
'是否获取最新版本 ID,与 versionId 必须二选一。只能传 true,表示返回最新版节点数据并带上具体 version'
})
})
]);
export type GetPreviewNodeQuery = z.infer<typeof GetPreviewNodeQuerySchema>;
export const GetPreviewNodeResponseSchema = ToolPreviewNodeResponseSchema;
export type GetPreviewNodeResponse = z.infer<typeof GetPreviewNodeResponseSchema>;
import z from 'zod';
export const ToolDetailBodySchema = z.object({});
export const ToolDetailQuerySchema = z.object({
/** 系统工具的 ID
* - systemTool-xxxx
* - systemTool-xxxx/childId
* - comercial-xxxx,
*/
id: z.string().meta({ description: '系统工具的 ID' }),
version: z.string().optional().meta({ description: '系统工具的版本, 如果不填则返回最新版本' }),
source: z.string().optional().meta({ description: '系统工具的来源,默认为 system' })
});
export const ToolDetailResponseSchema = z.object({
// 基本信息
id: z.string(),
isToolSet: z.boolean().meta({ description: '是否为工具集' }),
tags: z.array(z.string()).nullish().meta({ description: '系统工具的标签' }),
avatar: z.string().optional().meta({ description: '系统工具的头像' }),
name: z.string().meta({ description: '系统工具的名称' }),
intro: z.string().optional().meta({ description: '系统工具的简介' }),
author: z.string().optional().meta({ description: '系统工具的作者' }),
instructions: z.string().optional().meta({ description: '使用说明 (文字)' }),
courseUrl: z.string().optional().meta({ description: '系统工具的教程链接' }),
readmeUrl: z.string().optional().meta({ description: '系统工具的 README 文档链接' }),
// 输入输出
// 计费相关
/** 这个字段暂时没有用 */
originCost: z.number().optional().meta({ description: '原始价格' }),
currentCost: z.number().optional().meta({ description: '价格' }),
hasTokenFee: z.boolean().optional().meta({ description: '是否配置了系统密钥' }),
systemKeyCost: z.number().optional().meta({ description: '系统密钥费用' })
});
export type ToolDetailBodyType = z.infer<typeof ToolDetailBodySchema>;
export type ToolDetailQueryType = z.infer<typeof ToolDetailQuerySchema>;
export type ToolDetailResponseType = z.infer<typeof ToolDetailResponseSchema>;
import { OpenAPIPath } from '../../../../type';
export const ToolDetailPath = {
'/api/core/app/tool/detail': {
summary: '获取系统工具详情'
}
} satisfies OpenAPIPath;
import { ToolDetailPath } from './detail'; import type { OpenAPIPath } from '../../../type';
import { TagsMap } from '../../../tag';
import {
GetPreviewNodeQuerySchema,
GetPreviewNodeResponseSchema,
GetSystemToolTemplatesBodySchema,
GetSystemToolTemplatesResponseSchema,
GetToolPathQuerySchema,
GetToolPathResponseSchema
} from './api';
export const ToolPath = { export const ToolPath: OpenAPIPath = {
...ToolDetailPath '/core/app/tool/getSystemToolTemplates': {
// TODO post: {
summary: '获取系统工具模板列表',
description: '获取可添加到工作流中的系统工具模板列表,支持搜索、标签筛选和工具集子工具查询',
tags: [TagsMap.appSystemTool],
requestBody: {
content: {
'application/json': {
schema: GetSystemToolTemplatesBodySchema
}
}
},
responses: {
200: {
description: '成功获取系统工具模板列表',
content: {
'application/json': {
schema: GetSystemToolTemplatesResponseSchema
}
}
}
}
}
},
'/core/app/tool/path': {
get: {
summary: '获取工具路径',
description: '获取系统工具或工具集子工具在工具树中的路径',
tags: [TagsMap.appSystemTool],
requestParams: {
query: GetToolPathQuerySchema
},
responses: {
200: {
description: '成功获取工具路径',
content: {
'application/json': {
schema: GetToolPathResponseSchema
}
}
}
}
}
},
'/core/app/tool/getPreviewNode': {
get: {
summary: '获取工具节点信息',
description:
'根据工具 ID 和版本配置生成可插入工作流画布的工具节点模板,支持系统工具和我的工具(MCP、HTTP、工作流工具)',
tags: [TagsMap.appSystemTool, TagsMap.httpTools, TagsMap.mcpTools, TagsMap.pluginTeam],
requestParams: {
query: GetPreviewNodeQuerySchema
},
responses: {
200: {
description: '成功获取工具节点信息',
content: {
'application/json': {
schema: GetPreviewNodeResponseSchema
}
}
}
}
}
}
}; };
import z from 'zod';
export const SystemToolListItemSchema = z.object({
// 基础信息
id: z.string().meta({ description: '系统工具的 ID' }),
isToolSet: z.boolean().meta({ description: '是否为工具集' }),
avatar: z.string().meta({ description: '工具的图标' }),
name: z.string().meta({ description: '工具的名称' }),
intro: z.string().meta({ description: '工具的简介' }),
author: z.string().meta({ description: '工具的作者' }),
tags: z.array(z.string()).meta({ description: '工具的标签' }),
// 计费相关
currentCost: z.number().meta({ description: '当前使用的费用' }),
systemKeyCost: z.number().meta({ description: '系统密钥的费用' }),
hasTokenFee: z.boolean().meta({ description: '是否有系统密钥费用' })
});
export const SystemToolListBodySchema = z.object({
searchKey: z.string().optional(),
tags: z.array(z.string()).optional()
});
export const SystemToolListQuerySchema = z.object({});
export const SystemToolListResponseSchema = z.array(SystemToolListItemSchema);
export type SystemToolListBodyType = z.infer<typeof SystemToolListBodySchema>;
export type SystemToolListQueryType = z.infer<typeof SystemToolListQuerySchema>;
export type SystemToolListResponseType = z.infer<typeof SystemToolListResponseSchema>;
...@@ -5,7 +5,6 @@ import { ...@@ -5,7 +5,6 @@ import {
} from '../../../../../core/app/tool/systemTool/type/base'; } from '../../../../../core/app/tool/systemTool/type/base';
import type { AdminSystemToolDetailType } from '../../../../../core/app/tool/systemTool/type'; import type { AdminSystemToolDetailType } from '../../../../../core/app/tool/systemTool/type';
import { import {
AdminSystemToolChildDetailSchema,
AdminSystemToolDetailSchema, AdminSystemToolDetailSchema,
AdminSystemToolListItemSchema AdminSystemToolListItemSchema
} from '../../../../../core/app/tool/systemTool/type'; } from '../../../../../core/app/tool/systemTool/type';
...@@ -28,17 +27,40 @@ export type GetAdminSystemToolsQueryType = z.infer<typeof GetAdminSystemToolsQue ...@@ -28,17 +27,40 @@ export type GetAdminSystemToolsQueryType = z.infer<typeof GetAdminSystemToolsQue
export const GetAdminSystemToolsResponseSchema = z.array(AdminSystemToolListItemSchema); export const GetAdminSystemToolsResponseSchema = z.array(AdminSystemToolListItemSchema);
export type GetAdminSystemToolsResponseType = z.infer<typeof GetAdminSystemToolsResponseSchema>; export type GetAdminSystemToolsResponseType = z.infer<typeof GetAdminSystemToolsResponseSchema>;
// Admin tool detail /* ============================================================================
* API: 获取系统工具详情
* Route: GET /api/core/plugin/admin/tool/detail
* Method: GET
* Description: 管理员获取系统工具详情,支持按版本查看
* Tags: ['管理员系统工具管理', 'Read']
* ============================================================================ */
export const GetAdminSystemToolDetailQuerySchema = z.object({ export const GetAdminSystemToolDetailQuerySchema = z.object({
toolId: z.string(), toolId: z.string().meta({
version: z.string().optional() example: 'systemTool-weather',
description: '系统工具 ID,支持系统工具、商业工具和工具集子工具 ID'
}),
version: z.string().optional().meta({
example: '68ad85a7463006c963799a05',
description: '工具版本 ID。为空时返回最新版本详情'
})
}); });
export type GetAdminSystemToolDetailQueryType = z.infer<typeof GetAdminSystemToolDetailQuerySchema>; export type GetAdminSystemToolDetailQueryType = z.infer<typeof GetAdminSystemToolDetailQuerySchema>;
export type GetAdminSystemToolDetailResponseType = AdminSystemToolDetailType; export type GetAdminSystemToolDetailResponseType = AdminSystemToolDetailType;
// Admin tool versions /* ============================================================================
* API: 获取系统工具版本列表
* Route: GET /api/core/plugin/admin/tool/versions
* Method: GET
* Description: 管理员获取系统工具版本列表,工作流工具返回关联应用版本 ID 和版本名称
* Tags: ['管理员系统工具管理', 'Read']
* ============================================================================ */
export const GetAdminSystemToolVersionsQuerySchema = z.object({ export const GetAdminSystemToolVersionsQuerySchema = z.object({
toolId: z.string() toolId: z.string().meta({
example: 'systemTool-weather',
description: '系统工具 ID,支持系统工具、商业工具和工具集子工具 ID'
})
}); });
export type GetAdminSystemToolVersionsQueryType = z.infer< export type GetAdminSystemToolVersionsQueryType = z.infer<
typeof GetAdminSystemToolVersionsQuerySchema typeof GetAdminSystemToolVersionsQuerySchema
......
...@@ -5,8 +5,8 @@ import { ...@@ -5,8 +5,8 @@ import {
GetTeamToolDetailQuerySchema, GetTeamToolDetailQuerySchema,
GetTeamToolVersionsQuerySchema, GetTeamToolVersionsQuerySchema,
GetTeamToolVersionsResponseSchema, GetTeamToolVersionsResponseSchema,
TeamToolDetailSchema OpenAPITeamToolDetailSchema
} from './tool/dto'; } from './tool/api';
export const PluginTeamPath: OpenAPIPath = { export const PluginTeamPath: OpenAPIPath = {
'/core/plugin/team/tool/list': { '/core/plugin/team/tool/list': {
...@@ -40,7 +40,7 @@ export const PluginTeamPath: OpenAPIPath = { ...@@ -40,7 +40,7 @@ export const PluginTeamPath: OpenAPIPath = {
description: '获取工具卡片详情成功', description: '获取工具卡片详情成功',
content: { content: {
'application/json': { 'application/json': {
schema: TeamToolDetailSchema schema: OpenAPITeamToolDetailSchema
} }
} }
} }
......
...@@ -5,6 +5,15 @@ import { ...@@ -5,6 +5,15 @@ import {
SystemToolListItemSchema SystemToolListItemSchema
} from '../../../../../core/app/tool/systemTool/type'; } from '../../../../../core/app/tool/systemTool/type';
import { SystemToolVersionSchema } from '../../../../../core/app/tool/systemTool/type/base'; import { SystemToolVersionSchema } from '../../../../../core/app/tool/systemTool/type/base';
import { OpenAPIFlowNodeOutputItemTypeSchema } from '../../../workflow/node';
/* ============================================================================
* API: 获取团队插件列表
* Route: GET /api/core/plugin/team/tool/list
* Method: GET
* Description: 获取当前团队可用的插件和系统工具列表
* Tags: ['团队插件管理', 'Read']
* ============================================================================ */
export const GetTeamSystemPluginListQuerySchema = z.object({}); export const GetTeamSystemPluginListQuerySchema = z.object({});
...@@ -19,10 +28,27 @@ export type GetTeamPluginListResponseType = z.infer<typeof GetTeamPluginListResp ...@@ -19,10 +28,27 @@ export type GetTeamPluginListResponseType = z.infer<typeof GetTeamPluginListResp
export const GetTeamToolDetailSourceEnum = z.enum(['system', 'team']); export const GetTeamToolDetailSourceEnum = z.enum(['system', 'team']);
/* ============================================================================
* API: 获取团队工具详情
* Route: GET /api/core/plugin/team/tool/detail
* Method: GET
* Description: 获取当前团队视角下的工具详情,支持系统工具和团队工具来源
* Tags: ['团队插件管理', 'Read']
* ============================================================================ */
export const GetTeamToolDetailQuerySchema = z.object({ export const GetTeamToolDetailQuerySchema = z.object({
toolId: z.string(), toolId: z.string().meta({
version: z.string().optional(), example: 'systemTool-weather',
source: GetTeamToolDetailSourceEnum.optional() description: '工具 ID,支持系统工具、团队工具和工具集子工具 ID'
}),
version: z.string().optional().meta({
example: '68ad85a7463006c963799a05',
description: '工具版本 ID。为空时返回最新版本详情'
}),
source: GetTeamToolDetailSourceEnum.optional().meta({
example: 'system',
description: '工具来源。system 表示系统工具,team 表示当前团队工具'
})
}); });
export type GetTeamToolDetailQueryType = z.infer<typeof GetTeamToolDetailQuerySchema>; export type GetTeamToolDetailQueryType = z.infer<typeof GetTeamToolDetailQuerySchema>;
...@@ -39,9 +65,31 @@ export const TeamToolDetailSchema = z.object({ ...@@ -39,9 +65,31 @@ export const TeamToolDetailSchema = z.object({
}); });
export type GetTeamToolDetailResponseType = z.infer<typeof TeamToolDetailSchema>; export type GetTeamToolDetailResponseType = z.infer<typeof TeamToolDetailSchema>;
export const GetTeamToolVersionsQuerySchema = z.object({ const OpenAPISystemToolChildDetailSchema = SystemToolChildDetailSchema.omit({
toolId: z.string(), outputs: true
source: GetTeamToolDetailSourceEnum.optional() }).extend({
outputs: z.array(OpenAPIFlowNodeOutputItemTypeSchema)
});
export const OpenAPITeamToolDetailSchema = TeamToolDetailSchema.omit({
outputs: true,
children: true
}).extend({
outputs: z.array(OpenAPIFlowNodeOutputItemTypeSchema).optional(),
children: z.array(OpenAPISystemToolChildDetailSchema).optional()
});
/* ============================================================================
* API: 获取团队工具版本列表
* Route: GET /api/core/plugin/team/tool/versions
* Method: GET
* Description: 获取当前团队视角下的工具版本列表,工作流工具返回关联应用版本 ID 和版本名称
* Tags: ['团队插件管理', 'Read']
* ============================================================================ */
export const GetTeamToolVersionsQuerySchema = GetTeamToolDetailQuerySchema.pick({
toolId: true,
source: true
}); });
export type GetTeamToolVersionsQueryType = z.infer<typeof GetTeamToolVersionsQuerySchema>; export type GetTeamToolVersionsQueryType = z.infer<typeof GetTeamToolVersionsQuerySchema>;
......
import z from 'zod';
export const GetTeamToolDetailQuerySchema = z.object({
toolId: z.string()
});
export type GetTeamToolDetailQueryType = z.infer<typeof GetTeamToolDetailQuerySchema>;
export const ToolDetailItemSchema = z.object({
name: z.string(),
intro: z.string(),
icon: z.string().nullish(),
readme: z.string().nullish(),
versionList: z.array(
z.object({
inputs: z.array(
z.object({
key: z.string(),
label: z.string().nullish(),
description: z.string().nullish(),
valueType: z.string().nullish()
})
),
outputs: z.array(
z.object({
key: z.string(),
label: z.string().nullish(),
description: z.string().nullish(),
valueType: z.string().nullish()
})
)
})
)
});
export const TeamToolDetailSchema = z.object({
tools: z.array(ToolDetailItemSchema),
downloadUrl: z.string()
});
export type GetTeamToolDetailResponseType = z.infer<typeof TeamToolDetailSchema>;
...@@ -17,7 +17,7 @@ const OpenAPIFlowNodeInputItemTypeSchema = FlowNodeInputItemTypeSchema.meta({ ...@@ -17,7 +17,7 @@ const OpenAPIFlowNodeInputItemTypeSchema = FlowNodeInputItemTypeSchema.meta({
// `invalidCondition` in FlowNodeOutputItemTypeSchema is a Zod function schema used only // `invalidCondition` in FlowNodeOutputItemTypeSchema is a Zod function schema used only
// by the editor to validate outputs; function schemas cannot be represented in JSON // by the editor to validate outputs; function schemas cannot be represented in JSON
// Schema, so we strip it before exposing via OpenAPI. // Schema, so we strip it before exposing via OpenAPI.
const OpenAPIFlowNodeOutputItemTypeSchema = FlowNodeOutputItemTypeSchema.omit({ export const OpenAPIFlowNodeOutputItemTypeSchema = FlowNodeOutputItemTypeSchema.omit({
invalidCondition: true invalidCondition: true
}).meta({ }).meta({
description: '工作流节点输出配置' description: '工作流节点输出配置'
......
...@@ -33,7 +33,7 @@ export const openAPITagGroups = [ ...@@ -33,7 +33,7 @@ export const openAPITagGroups = [
}, },
{ {
name: '核心-工具管理', name: '核心-工具管理',
tags: [TagsMap.httpTools, TagsMap.mcpTools, TagsMap.mcpServer] tags: [TagsMap.appSystemTool, TagsMap.httpTools, TagsMap.mcpTools, TagsMap.mcpServer]
}, },
{ {
name: '核心-AI 相关', name: '核心-AI 相关',
......
...@@ -12,6 +12,7 @@ export const TagsMap = { ...@@ -12,6 +12,7 @@ export const TagsMap = {
mcpTools: 'MCP 工具管理', mcpTools: 'MCP 工具管理',
httpTools: 'HTTP 工具管理', httpTools: 'HTTP 工具管理',
mcpServer: 'MCP 发布管理', mcpServer: 'MCP 发布管理',
appSystemTool: '系统工具',
/* 核心-AI 相关 */ /* 核心-AI 相关 */
aiSkill: 'AI技能管理', aiSkill: 'AI技能管理',
......
import { describe, expect, it } from 'vitest';
import { GetPreviewNodeQuerySchema } from '@fastgpt/global/openapi/core/app/tool/api';
describe('GetPreviewNodeQuerySchema', () => {
it('accepts versionId, including empty string', () => {
expect(
GetPreviewNodeQuerySchema.safeParse({
appId: 'systemTool-weather',
versionId: ''
}).success
).toBe(true);
expect(
GetPreviewNodeQuerySchema.safeParse({
appId: 'systemTool-weather',
versionId: '68ad85a7463006c963799a05'
}).success
).toBe(true);
});
it('accepts getLatestVersion only when versionId is omitted', () => {
expect(
GetPreviewNodeQuerySchema.safeParse({
appId: 'systemTool-weather',
getLatestVersion: true
}).success
).toBe(true);
});
it('rejects missing or duplicated version selectors', () => {
expect(
GetPreviewNodeQuerySchema.safeParse({
appId: 'systemTool-weather'
}).success
).toBe(false);
expect(
GetPreviewNodeQuerySchema.safeParse({
appId: 'systemTool-weather',
versionId: '',
getLatestVersion: true
}).success
).toBe(false);
});
it('rejects getLatestVersion=false', () => {
expect(
GetPreviewNodeQuerySchema.safeParse({
appId: 'systemTool-weather',
getLatestVersion: false
}).success
).toBe(false);
});
});
...@@ -84,18 +84,26 @@ type AppToolType = WorkflowTemplateType & { ...@@ -84,18 +84,26 @@ type AppToolType = WorkflowTemplateType & {
export async function getChildAppPreviewNode({ export async function getChildAppPreviewNode({
appId, appId,
versionId, versionId,
getLatestVersion,
lang = 'en', lang = 'en',
source: toolSource = 'system' source: toolSource = 'system'
}: { }: {
appId: string; appId: string;
versionId?: string; versionId?: string;
getLatestVersion?: boolean;
lang?: localeType; lang?: localeType;
source?: string; source?: string;
}): Promise<FlowNodeTemplateType> { }): Promise<FlowNodeTemplateType> {
const { source, pluginId } = splitCombineToolId(appId); const { source, pluginId } = splitCombineToolId(appId);
if (source === AppToolSourceEnum.systemTool || source === AppToolSourceEnum.commercial) { if (source === AppToolSourceEnum.systemTool || source === AppToolSourceEnum.commercial) {
return getToolPreviewNode({ pluginId: appId, versionId, lang, source: toolSource }); return getToolPreviewNode({
pluginId: appId,
versionId,
getLatestVersion,
lang,
source: source === AppToolSourceEnum.commercial ? AppToolSourceEnum.commercial : toolSource
});
} }
// 存在 app 里面的插件的情况 // 存在 app 里面的插件的情况
...@@ -106,7 +114,11 @@ export async function getChildAppPreviewNode({ ...@@ -106,7 +114,11 @@ export async function getChildAppPreviewNode({
if (!item) return Promise.reject(PluginErrEnum.unExist); if (!item) return Promise.reject(PluginErrEnum.unExist);
if (AppFolderTypeList.includes(item.type)) return Promise.reject(PluginErrEnum.unExist); if (AppFolderTypeList.includes(item.type)) return Promise.reject(PluginErrEnum.unExist);
const version = await getAppVersionById({ appId: pluginId, versionId, app: item }); const version = await getAppVersionById({
appId: pluginId,
versionId: versionId || undefined,
app: item
});
const isLatest = const isLatest =
version.versionId && Types.ObjectId.isValid(version.versionId) version.versionId && Types.ObjectId.isValid(version.versionId)
...@@ -128,6 +140,8 @@ export async function getChildAppPreviewNode({ ...@@ -128,6 +140,8 @@ export async function getChildAppPreviewNode({
}; };
} }
const shouldReturnVersion = versionId ? true : versionId === undefined && getLatestVersion;
return { return {
id: String(item._id), id: String(item._id),
teamId: String(item.teamId), teamId: String(item.teamId),
...@@ -142,8 +156,8 @@ export async function getChildAppPreviewNode({ ...@@ -142,8 +156,8 @@ export async function getChildAppPreviewNode({
}, },
templateType: FlowNodeTemplateTypeEnum.teamApp, templateType: FlowNodeTemplateTypeEnum.teamApp,
version: versionId ? version?.versionId : '', version: shouldReturnVersion ? (version.versionId ?? '') : '',
versionLabel: version?.versionName, versionLabel: shouldReturnVersion ? version.versionName : undefined,
isLatestVersion: isLatest, isLatestVersion: isLatest,
originCost: 0, originCost: 0,
...@@ -159,7 +173,11 @@ export async function getChildAppPreviewNode({ ...@@ -159,7 +173,11 @@ export async function getChildAppPreviewNode({
const item = await MongoApp.findById(parentId).lean(); const item = await MongoApp.findById(parentId).lean();
if (!item) return Promise.reject(PluginErrEnum.unExist); if (!item) return Promise.reject(PluginErrEnum.unExist);
const version = await getAppVersionById({ appId: parentId, versionId, app: item }); const version = await getAppVersionById({
appId: parentId,
versionId: versionId || undefined,
app: item
});
const toolConfig = version.nodes[0].toolConfig?.mcpToolSet; const toolConfig = version.nodes[0].toolConfig?.mcpToolSet;
const tool = await (async () => { const tool = await (async () => {
if (toolConfig?.toolList) { if (toolConfig?.toolList) {
...@@ -201,7 +219,11 @@ export async function getChildAppPreviewNode({ ...@@ -201,7 +219,11 @@ export async function getChildAppPreviewNode({
const item = await MongoApp.findById(parentId).lean(); const item = await MongoApp.findById(parentId).lean();
if (!item) return Promise.reject(PluginErrEnum.unExist); if (!item) return Promise.reject(PluginErrEnum.unExist);
const version = await getAppVersionById({ appId: parentId, versionId, app: item }); const version = await getAppVersionById({
appId: parentId,
versionId: versionId || undefined,
app: item
});
const toolConfig = version.nodes[0].toolConfig?.httpToolSet; const toolConfig = version.nodes[0].toolConfig?.httpToolSet;
const tool = await (async () => { const tool = await (async () => {
if (toolConfig?.toolList) { if (toolConfig?.toolList) {
......
...@@ -16,21 +16,24 @@ import { NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants'; ...@@ -16,21 +16,24 @@ import { NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants';
export async function getToolPreviewNode({ export async function getToolPreviewNode({
pluginId, pluginId,
versionId, versionId,
getLatestVersion,
lang = 'en', lang = 'en',
source: toolSource = 'system' source: toolSource = 'system'
}: { }: {
pluginId: string; pluginId: string;
versionId?: string; versionId?: string;
getLatestVersion?: boolean;
lang?: localeType; lang?: localeType;
source?: string; source?: string;
}): Promise<FlowNodeTemplateType> { }): Promise<FlowNodeTemplateType> {
const systemToolRepo = SystemToolRepo.getInstance(); const systemToolRepo = SystemToolRepo.getInstance();
const toolDetail = await systemToolRepo.getSystemToolDetail({ const toolDetail = await systemToolRepo.getSystemToolDetail({
pluginId, pluginId,
version: versionId, version: versionId || undefined,
lang, lang,
source: toolSource source: toolSource
}); });
const shouldReturnVersion = versionId ? true : versionId === undefined && getLatestVersion;
const inputs = [ const inputs = [
...(toolDetail.secrets?.length ...(toolDetail.secrets?.length
...@@ -65,8 +68,8 @@ export async function getToolPreviewNode({ ...@@ -65,8 +68,8 @@ export async function getToolPreviewNode({
isTool: true, isTool: true,
catchError: false, catchError: false,
version: versionId, // 为 undefined 时,为保持最新版 version: shouldReturnVersion ? toolDetail.version : '',
versionLabel: versionId, versionLabel: shouldReturnVersion ? (toolDetail.versionLabel ?? toolDetail.version) : undefined,
isLatestVersion: toolDetail.isLatestVersion, isLatestVersion: toolDetail.isLatestVersion,
showSourceHandle: true, showSourceHandle: true,
showTargetHandle: true, showTargetHandle: true,
......
...@@ -172,7 +172,8 @@ export class SystemToolRepo { ...@@ -172,7 +172,8 @@ export class SystemToolRepo {
name: dbTool.customConfig.name, name: dbTool.customConfig.name,
status: dbTool.status, status: dbTool.status,
toolDescription: dbTool.customConfig.toolDescription ?? dbTool.customConfig.intro ?? '', toolDescription: dbTool.customConfig.toolDescription ?? dbTool.customConfig.intro ?? '',
version: dbTool.customConfig.version ?? '', version: appVersion.versionId,
versionLabel: appVersion.versionName,
intro: dbTool.customConfig.intro ?? '', intro: dbTool.customConfig.intro ?? '',
tags: dbTool.customConfig.tags ?? [], tags: dbTool.customConfig.tags ?? [],
author: dbTool.customConfig.author ?? global.feConfigs.systemTitle ?? '', author: dbTool.customConfig.author ?? global.feConfigs.systemTitle ?? '',
...@@ -318,13 +319,8 @@ export class SystemToolRepo { ...@@ -318,13 +319,8 @@ export class SystemToolRepo {
lang?: `${LangEnum}`; lang?: `${LangEnum}`;
}): Promise<SystemToolVersionType[]> => { }): Promise<SystemToolVersionType[]> => {
const { pluginId: rawPluginId, source: pluginSource } = splitCombineToolId(pluginId); const { pluginId: rawPluginId, source: pluginSource } = splitCombineToolId(pluginId);
if (pluginSource === AppToolSourceEnum.commercial) { const tool = await MongoSystemTool.findOne({ pluginId });
const tool = await this.getSystemToolRecord(pluginId); if (tool?.customConfig?.associatedPluginId) {
if (!tool || !tool.customConfig?.associatedPluginId) {
return Promise.reject('Plugin is not associated with a app');
}
const { associatedPluginId } = tool.customConfig; const { associatedPluginId } = tool.customConfig;
const appVersions = await MongoAppVersion.find( const appVersions = await MongoAppVersion.find(
{ {
...@@ -334,10 +330,11 @@ export class SystemToolRepo { ...@@ -334,10 +330,11 @@ export class SystemToolRepo {
{ {
versionName: 1 versionName: 1
} }
); ).sort({ time: -1, _id: -1 });
return appVersions.map((item) => ({ return appVersions.map((item) => ({
version: item.versionName version: String(item._id),
versionDescription: item.versionName
})); }));
} }
...@@ -345,7 +342,7 @@ export class SystemToolRepo { ...@@ -345,7 +342,7 @@ export class SystemToolRepo {
const versions = await pluginClient.listPluginVersions({ const versions = await pluginClient.listPluginVersions({
pluginId: parentToolId, pluginId: parentToolId,
source source: pluginSource === AppToolSourceEnum.commercial ? AppToolSourceEnum.commercial : source
}); });
return versions.map((item) => ({ return versions.map((item) => ({
......
...@@ -140,7 +140,10 @@ export async function rewriteAppWorkflowToDetail({ ...@@ -140,7 +140,10 @@ export async function rewriteAppWorkflowToDetail({
nodes.map(async (node) => { nodes.map(async (node) => {
// Tool node // Tool node
if (node.pluginId) { if (node.pluginId) {
const result = await loadToolNode({ id: node.pluginId, versionId: node.version }); const result = await loadToolNode({
id: node.pluginId,
versionId: node.version ?? ''
});
if (result.success) { if (result.success) {
const preview = result.data!; const preview = result.data!;
node.avatar = preview.avatar ?? node.avatar; node.avatar = preview.avatar ?? node.avatar;
......
...@@ -116,6 +116,7 @@ export const getAgentRuntimeTools = async ({ ...@@ -116,6 +116,7 @@ export const getAgentRuntimeTools = async ({
const [toolNode] = await Promise.all([ const [toolNode] = await Promise.all([
getChildAppPreviewNode({ getChildAppPreviewNode({
appId: tool.id, appId: tool.id,
versionId: '',
lang lang
}), }),
...(authAppId ...(authAppId
......
...@@ -78,6 +78,8 @@ describe('getToolPreviewNode', () => { ...@@ -78,6 +78,8 @@ describe('getToolPreviewNode', () => {
inputList: secrets inputList: secrets
}); });
expect(result.inputs[1]?.key).toBe('city'); expect(result.inputs[1]?.key).toBe('city');
expect(result.version).toBe('1.0.0');
expect(result.versionLabel).toBe('1.0.0');
}); });
it('keeps inputs unchanged when system tool has no secrets', async () => { it('keeps inputs unchanged when system tool has no secrets', async () => {
...@@ -116,6 +118,76 @@ describe('getToolPreviewNode', () => { ...@@ -116,6 +118,76 @@ describe('getToolPreviewNode', () => {
expect(result.inputs).toHaveLength(1); expect(result.inputs).toHaveLength(1);
expect(result.inputs[0]?.key).toBe('city'); expect(result.inputs[0]?.key).toBe('city');
expect(result.version).toBe('');
expect(result.versionLabel).toBeUndefined();
});
it('returns latest version id when requested explicitly', async () => {
mocks.getSystemToolDetail.mockResolvedValueOnce({
id: 'systemTool-weather',
version: '1.0.0',
status: 1,
source: 'system',
isToolSet: false,
avatar: 'weather.svg',
name: 'Weather',
intro: 'Weather query',
author: 'FastGPT',
tags: [],
toolDescription: 'Weather query',
currentCost: 0,
systemKeyCost: 0,
hasTokenFee: false,
hasSystemSecret: false,
inputs: [],
outputs: []
});
const result = await getToolPreviewNode({
pluginId: 'systemTool-weather',
getLatestVersion: true,
lang: 'en'
});
expect(result.version).toBe('1.0.0');
expect(result.versionLabel).toBe('1.0.0');
});
it('uses latest data but returns empty version when versionId is an empty string', async () => {
mocks.getSystemToolDetail.mockResolvedValueOnce({
id: 'systemTool-weather',
version: '1.0.0',
status: 1,
source: 'system',
isToolSet: false,
avatar: 'weather.svg',
name: 'Weather',
intro: 'Weather query',
author: 'FastGPT',
tags: [],
toolDescription: 'Weather query',
currentCost: 0,
systemKeyCost: 0,
hasTokenFee: false,
hasSystemSecret: false,
inputs: [],
outputs: []
});
const result = await getToolPreviewNode({
pluginId: 'systemTool-weather',
versionId: '',
lang: 'en'
});
expect(mocks.getSystemToolDetail).toHaveBeenCalledWith({
pluginId: 'systemTool-weather',
version: undefined,
lang: 'en',
source: 'system'
});
expect(result.version).toBe('');
expect(result.versionLabel).toBeUndefined();
}); });
it('returns plugin module preview for commercial workflow tools', async () => { it('returns plugin module preview for commercial workflow tools', async () => {
...@@ -124,6 +196,7 @@ describe('getToolPreviewNode', () => { ...@@ -124,6 +196,7 @@ describe('getToolPreviewNode', () => {
version: 'workflow-version', version: 'workflow-version',
status: 1, status: 1,
source: 'system', source: 'system',
versionLabel: 'Workflow v1',
isToolSet: false, isToolSet: false,
avatar: 'workflow.svg', avatar: 'workflow.svg',
name: 'Workflow Tool', name: 'Workflow Tool',
...@@ -159,5 +232,7 @@ describe('getToolPreviewNode', () => { ...@@ -159,5 +232,7 @@ describe('getToolPreviewNode', () => {
expect(result.toolConfig).toBeUndefined(); expect(result.toolConfig).toBeUndefined();
expect(result.isFolder).toBe(false); expect(result.isFolder).toBe(false);
expect(result.inputs[0]?.key).toBe('query'); expect(result.inputs[0]?.key).toBe('query');
expect(result.version).toBe('workflow-version');
expect(result.versionLabel).toBe('Workflow v1');
}); });
}); });
...@@ -3,9 +3,11 @@ import { SystemToolSystemSecretStatusEnum } from '@fastgpt/global/core/app/tool/ ...@@ -3,9 +3,11 @@ import { SystemToolSystemSecretStatusEnum } from '@fastgpt/global/core/app/tool/
const mocks = vi.hoisted(() => ({ const mocks = vi.hoisted(() => ({
listTools: vi.fn(), listTools: vi.fn(),
listPluginVersions: vi.fn(),
getTool: vi.fn(), getTool: vi.fn(),
findSystemTools: vi.fn(), findSystemTools: vi.fn(),
findSystemTool: vi.fn(), findSystemTool: vi.fn(),
findAppVersions: vi.fn(),
findAppById: vi.fn(), findAppById: vi.fn(),
getAppLatestVersion: vi.fn(), getAppLatestVersion: vi.fn(),
getAppVersionById: vi.fn(), getAppVersionById: vi.fn(),
...@@ -15,6 +17,7 @@ const mocks = vi.hoisted(() => ({ ...@@ -15,6 +17,7 @@ const mocks = vi.hoisted(() => ({
vi.mock('@fastgpt/service/thirdProvider/fastgptPlugin', () => ({ vi.mock('@fastgpt/service/thirdProvider/fastgptPlugin', () => ({
pluginClient: { pluginClient: {
listTools: mocks.listTools, listTools: mocks.listTools,
listPluginVersions: mocks.listPluginVersions,
getTool: mocks.getTool getTool: mocks.getTool
} }
})); }));
...@@ -40,6 +43,12 @@ vi.mock('@fastgpt/service/core/app/version/controller', () => ({ ...@@ -40,6 +43,12 @@ vi.mock('@fastgpt/service/core/app/version/controller', () => ({
checkIsLatestVersion: mocks.checkIsLatestVersion checkIsLatestVersion: mocks.checkIsLatestVersion
})); }));
vi.mock('@fastgpt/service/core/app/version/schema', () => ({
MongoAppVersion: {
find: mocks.findAppVersions
}
}));
import { SystemToolRepo } from '@fastgpt/service/core/app/tool/systemTool/systemTool.repo'; import { SystemToolRepo } from '@fastgpt/service/core/app/tool/systemTool/systemTool.repo';
const createPluginTool = ({ const createPluginTool = ({
...@@ -158,7 +167,11 @@ describe('SystemToolRepo.getSystemToolList', () => { ...@@ -158,7 +167,11 @@ describe('SystemToolRepo.getSystemToolList', () => {
mocks.listTools.mockResolvedValue([ mocks.listTools.mockResolvedValue([
createPluginTool({ pluginId: 'no-secret', name: 'No secret' }), createPluginTool({ pluginId: 'no-secret', name: 'No secret' }),
createPluginTool({ pluginId: 'need-secret', name: 'Need secret', hasSecret: true }), createPluginTool({ pluginId: 'need-secret', name: 'Need secret', hasSecret: true }),
createPluginTool({ pluginId: 'configured-secret', name: 'Configured secret', hasSecret: true }) createPluginTool({
pluginId: 'configured-secret',
name: 'Configured secret',
hasSecret: true
})
]); ]);
mocks.findSystemTools.mockResolvedValue([ mocks.findSystemTools.mockResolvedValue([
createToolConfig({ pluginId: 'no-secret', pluginOrder: 1, tags: [] }), createToolConfig({ pluginId: 'no-secret', pluginOrder: 1, tags: [] }),
...@@ -186,7 +199,7 @@ describe('SystemToolRepo.getSystemToolList', () => { ...@@ -186,7 +199,7 @@ describe('SystemToolRepo.getSystemToolList', () => {
}); });
describe('SystemToolRepo.getSystemToolDetail', () => { describe('SystemToolRepo.getSystemToolDetail', () => {
it('returns saved author for workflow tools', async () => { it('returns app version id and label for workflow tools', async () => {
mocks.findSystemTool.mockResolvedValue({ mocks.findSystemTool.mockResolvedValue({
pluginId: 'systemTool-workflow-tool', pluginId: 'systemTool-workflow-tool',
status: 'Normal', status: 'Normal',
...@@ -210,6 +223,7 @@ describe('SystemToolRepo.getSystemToolDetail', () => { ...@@ -210,6 +223,7 @@ describe('SystemToolRepo.getSystemToolDetail', () => {
}); });
mocks.getAppLatestVersion.mockResolvedValue({ mocks.getAppLatestVersion.mockResolvedValue({
versionId: 'latest-version', versionId: 'latest-version',
versionName: 'Latest Version',
nodes: [] nodes: []
}); });
mocks.checkIsLatestVersion.mockResolvedValue(true); mocks.checkIsLatestVersion.mockResolvedValue(true);
...@@ -220,5 +234,64 @@ describe('SystemToolRepo.getSystemToolDetail', () => { ...@@ -220,5 +234,64 @@ describe('SystemToolRepo.getSystemToolDetail', () => {
expect(tool.author).toBe('Custom Author'); expect(tool.author).toBe('Custom Author');
expect(tool.hasTokenFee).toBe(true); expect(tool.hasTokenFee).toBe(true);
expect(tool.version).toBe('latest-version');
expect(tool.versionLabel).toBe('Latest Version');
});
});
describe('SystemToolRepo.getVersions', () => {
it('returns app version ids and names for workflow tools', async () => {
const sortedAppVersions = [
{
_id: '507f1f77bcf86cd799439012',
versionName: 'Workflow v2'
},
{
_id: '507f1f77bcf86cd799439013',
versionName: 'Workflow v1'
}
];
const sortAppVersions = vi.fn().mockResolvedValue(sortedAppVersions);
mocks.findSystemTool.mockResolvedValue({
pluginId: 'commercial-workflow-tool',
customConfig: {
associatedPluginId: '507f1f77bcf86cd799439011'
}
});
mocks.findAppVersions.mockReturnValue({
sort: sortAppVersions
});
const versions = await SystemToolRepo.getInstance().getVersions({
pluginId: 'commercial-workflow-tool'
});
expect(versions).toEqual([
{
version: '507f1f77bcf86cd799439012',
versionDescription: 'Workflow v2'
},
{
version: '507f1f77bcf86cd799439013',
versionDescription: 'Workflow v1'
}
]);
expect(sortAppVersions).toHaveBeenCalledWith({ time: -1, _id: -1 });
});
it('lists commercial plugin versions from commercial source', async () => {
mocks.findSystemTool.mockResolvedValue(undefined);
mocks.listPluginVersions.mockResolvedValue([{ version: '2.0.0' }, { version: '1.0.0' }]);
const versions = await SystemToolRepo.getInstance().getVersions({
pluginId: 'commercial-search'
});
expect(mocks.listPluginVersions).toHaveBeenCalledWith({
pluginId: 'search',
source: 'commercial'
});
expect(versions).toEqual([{ version: '2.0.0' }, { version: '1.0.0' }]);
}); });
}); });
...@@ -191,6 +191,7 @@ ...@@ -191,6 +191,7 @@
"invalid_json_format": "JSON format error", "invalid_json_format": "JSON format error",
"json_schema_tip": "Example:\n{\n \"name\": \"test\",\n \"description\":\"测试\",\n \"schema\": {\n \"type\":\"object\",\n \"properties\": {\n \"test\": {\n \"type\": \"string\",\n \"description\": \"Text field\"\n }\n },\n \"required\": [\n \"test\"\n ]\n }\n}", "json_schema_tip": "Example:\n{\n \"name\": \"test\",\n \"description\":\"测试\",\n \"schema\": {\n \"type\":\"object\",\n \"properties\": {\n \"test\": {\n \"type\": \"string\",\n \"description\": \"Text field\"\n }\n },\n \"required\": [\n \"test\"\n ]\n }\n}",
"keep_the_latest": "Keep the latest", "keep_the_latest": "Keep the latest",
"keep_the_latest_confirm_tip": "Please confirm whether to keep the latest version. Upstream tool updates may make the current configuration or features unavailable. Proceed with caution.",
"llm_multimodal_audio": "Audio", "llm_multimodal_audio": "Audio",
"llm_multimodal_image": "Image", "llm_multimodal_image": "Image",
"llm_multimodal_select_placeholder": "Select multimodal capabilities", "llm_multimodal_select_placeholder": "Select multimodal capabilities",
......
...@@ -191,6 +191,7 @@ ...@@ -191,6 +191,7 @@
"invalid_json_format": "JSON 格式错误", "invalid_json_format": "JSON 格式错误",
"json_schema_tip": "示例:\n{\n \"name\": \"test\",\n \"description\":\"测试\",\n \"schema\": {\n \"type\":\"object\",\n \"properties\": {\n \"test\": {\n \"type\": \"string\",\n \"description\": \"Text field\"\n }\n },\n \"required\": [\n \"test\"\n ]\n }\n}", "json_schema_tip": "示例:\n{\n \"name\": \"test\",\n \"description\":\"测试\",\n \"schema\": {\n \"type\":\"object\",\n \"properties\": {\n \"test\": {\n \"type\": \"string\",\n \"description\": \"Text field\"\n }\n },\n \"required\": [\n \"test\"\n ]\n }\n}",
"keep_the_latest": "保持最新版本", "keep_the_latest": "保持最新版本",
"keep_the_latest_confirm_tip": "请确认是否保持最新版。上游工具更新可能导致当前配置或功能不可用,请谨慎操作。",
"llm_multimodal_audio": "音频", "llm_multimodal_audio": "音频",
"llm_multimodal_image": "图片", "llm_multimodal_image": "图片",
"llm_multimodal_select_placeholder": "请选择多模态能力", "llm_multimodal_select_placeholder": "请选择多模态能力",
......
...@@ -186,6 +186,7 @@ ...@@ -186,6 +186,7 @@
"invalid_json_format": "JSON 格式錯誤", "invalid_json_format": "JSON 格式錯誤",
"json_schema_tip": "示例:\n{\n \"name\": \"test\",\n \"description\":\"測試\",\n \"schema\": {\n \"type\":\"object\",\n \"properties\": {\n \"test\": {\n \"type\": \"string\",\n \"description\": \"Text field\"\n }\n },\n \"required\": [\n \"test\"\n ]\n }\n}", "json_schema_tip": "示例:\n{\n \"name\": \"test\",\n \"description\":\"測試\",\n \"schema\": {\n \"type\":\"object\",\n \"properties\": {\n \"test\": {\n \"type\": \"string\",\n \"description\": \"Text field\"\n }\n },\n \"required\": [\n \"test\"\n ]\n }\n}",
"keep_the_latest": "保持最新版本", "keep_the_latest": "保持最新版本",
"keep_the_latest_confirm_tip": "請確認是否保持最新版。上游工具更新可能導致當前配置或功能不可用,請謹慎操作。",
"llm_multimodal_audio": "音頻", "llm_multimodal_audio": "音頻",
"llm_multimodal_image": "圖片", "llm_multimodal_image": "圖片",
"llm_multimodal_select_placeholder": "請選擇多模態能力", "llm_multimodal_select_placeholder": "請選擇多模態能力",
......
Subproject commit 1ebfaac6a4e6faef2c01d4b6785add3debf7285f Subproject commit 35458b567a3b719987bdd4e9a18dc747a8b34495
...@@ -189,7 +189,7 @@ export const useSkillManager = ({ ...@@ -189,7 +189,7 @@ export const useSkillManager = ({
return toolId; return toolId;
} }
const toolTemplate = await getToolPreviewNode({ appId: toolId }); const toolTemplate = await getToolPreviewNode({ appId: toolId, versionId: '' });
const toolValid = validateToolConfiguration({ const toolValid = validateToolConfiguration({
toolTemplate, toolTemplate,
......
...@@ -431,7 +431,7 @@ export const loadGeneratedTools = async ({ ...@@ -431,7 +431,7 @@ export const loadGeneratedTools = async ({
} }
// 新工具,需要与已配置的 tool 进行 input 合并 // 新工具,需要与已配置的 tool 进行 input 合并
const tool = await getToolPreviewNode({ appId: toolId }); const tool = await getToolPreviewNode({ appId: toolId, versionId: '' });
// 验证工具配置 // 验证工具配置
const toolValid = validateToolConfiguration({ const toolValid = validateToolConfiguration({
toolTemplate: tool, toolTemplate: tool,
......
...@@ -263,7 +263,7 @@ const RenderList = React.memo(function RenderList({ ...@@ -263,7 +263,7 @@ const RenderList = React.memo(function RenderList({
const { runAsync: onClickAdd, loading: isLoading } = useRequest( const { runAsync: onClickAdd, loading: isLoading } = useRequest(
async (template: NodeTemplateListItemType) => { async (template: NodeTemplateListItemType) => {
const res = await getToolPreviewNode({ appId: template.id }); const res = await getToolPreviewNode({ appId: template.id, versionId: '' });
const isToolSetTemplate = template.flowNodeType === FlowNodeTypeEnum.toolSet; const isToolSetTemplate = template.flowNodeType === FlowNodeTypeEnum.toolSet;
if (!isToolSetTemplate) { if (!isToolSetTemplate) {
......
...@@ -245,7 +245,10 @@ const NodeTemplateList = ({ ...@@ -245,7 +245,10 @@ const NodeTemplateList = ({
].includes(template.flowNodeType); ].includes(template.flowNodeType);
if (shouldLoadPreviewNode) { if (shouldLoadPreviewNode) {
const node = await getToolPreviewNode({ appId: template.id }); const node = await getToolPreviewNode({
appId: template.id,
getLatestVersion: true
});
return { return {
...node, ...node,
flowNodeType: flowNodeType:
......
...@@ -23,6 +23,7 @@ import { ToolSourceHandle, ToolTargetHandle } from './Handle/ToolHandle'; ...@@ -23,6 +23,7 @@ import { ToolSourceHandle, ToolTargetHandle } from './Handle/ToolHandle';
import { ConnectionSourceHandle, ConnectionTargetHandle } from './Handle/ConnectionHandle'; import { ConnectionSourceHandle, ConnectionTargetHandle } from './Handle/ConnectionHandle';
import { useDebug } from '../../hooks/useDebug'; import { useDebug } from '../../hooks/useDebug';
import { getToolPreviewNode } from '@/web/core/app/api/tool'; import { getToolPreviewNode } from '@/web/core/app/api/tool';
import { getAppVersionList } from '@/web/core/app/api/version';
import { getTeamToolVersions } from '@/web/core/plugin/team/api'; import { getTeamToolVersions } from '@/web/core/plugin/team/api';
import { storeNode2FlowNode } from '@/web/core/workflow/utils'; import { storeNode2FlowNode } from '@/web/core/workflow/utils';
import { getNanoid } from '@fastgpt/global/common/string/tools'; import { getNanoid } from '@fastgpt/global/common/string/tools';
...@@ -55,6 +56,8 @@ import { splitCombineToolId, getToolRawId } from '@fastgpt/global/core/app/tool/ ...@@ -55,6 +56,8 @@ import { splitCombineToolId, getToolRawId } from '@fastgpt/global/core/app/tool/
import { AppToolSourceEnum } from '@fastgpt/global/core/app/tool/constants'; import { AppToolSourceEnum } from '@fastgpt/global/core/app/tool/constants';
import { getAppPermission } from '@/web/core/app/api'; import { getAppPermission } from '@/web/core/app/api';
import { ObjectIdSchema } from '@fastgpt/global/common/type/mongo'; import { ObjectIdSchema } from '@fastgpt/global/common/type/mongo';
import { useConfirm } from '@fastgpt/web/hooks/useConfirm';
import type { SystemToolVersionType } from '@fastgpt/global/core/app/tool/systemTool/type/base';
type Props = FlowNodeItemType & { type Props = FlowNodeItemType & {
children?: React.ReactNode | React.ReactNode[] | string; children?: React.ReactNode | React.ReactNode[] | string;
...@@ -220,15 +223,22 @@ const NodeCard = (props: Props) => { ...@@ -220,15 +223,22 @@ const NodeCard = (props: Props) => {
const isAppNode = node && AppNodeFlowNodeTypeMap[node?.flowNodeType]; const isAppNode = node && AppNodeFlowNodeTypeMap[node?.flowNodeType];
const isLoopNode = isNestedParentNodeType(node?.flowNodeType ?? ''); const isLoopNode = isNestedParentNodeType(node?.flowNodeType ?? '');
const showVersion = useMemo(() => { const showVersion = useMemo(() => {
// 1. MCP tool and HTTP tool set do not have version const source = node?.pluginId ? splitCombineToolId(node.pluginId).source : undefined;
// 1. MCP/HTTP single tools use the latest toolset content and do not expose version selection.
if (source === AppToolSourceEnum.mcp || source === AppToolSourceEnum.http) return false;
// 2. MCP/HTTP tool sets do not have version
if ( if (
isAppNode && isAppNode &&
(node.toolConfig?.mcpToolSet || node.toolConfig?.mcpTool || node?.toolConfig?.httpToolSet) (node.toolConfig?.mcpToolSet ||
node.toolConfig?.mcpTool ||
node?.toolConfig?.httpToolSet ||
node?.toolConfig?.httpTool)
) )
return false; return false;
// 2. Team app/System commercial plugin // 3. Team app/System commercial plugin
if (isAppNode && node?.pluginId && !node?.pluginData?.error) return true; if (isAppNode && node?.pluginId && !node?.pluginData?.error) return true;
// 3. System tool // 4. System tool
if (isAppNode && node?.toolConfig?.systemTool) return true; if (isAppNode && node?.toolConfig?.systemTool) return true;
return false; return false;
...@@ -576,6 +586,13 @@ const NodeVersion = React.memo(function NodeVersion({ node }: { node: FlowNodeIt ...@@ -576,6 +586,13 @@ const NodeVersion = React.memo(function NodeVersion({ node }: { node: FlowNodeIt
const { t } = useTranslation(); const { t } = useTranslation();
const onResetNode = useContextSelector(WorkflowActionsContext, (v) => v.onResetNode); const onResetNode = useContextSelector(WorkflowActionsContext, (v) => v.onResetNode);
const { openConfirm: openKeepLatestConfirm, ConfirmModal: KeepLatestConfirmModal } = useConfirm({
content: t('app:keep_the_latest_confirm_tip')
});
const toolSource = useMemo(
() => (node.pluginId ? splitCombineToolId(node.pluginId).source : undefined),
[node.pluginId]
);
const { const {
runAsync: loadVersions, runAsync: loadVersions,
...@@ -585,15 +602,35 @@ const NodeVersion = React.memo(function NodeVersion({ node }: { node: FlowNodeIt ...@@ -585,15 +602,35 @@ const NodeVersion = React.memo(function NodeVersion({ node }: { node: FlowNodeIt
async () => { async () => {
if (!node.pluginId) return []; if (!node.pluginId) return [];
const { source } = splitCombineToolId(node.pluginId); const { authAppId } = splitCombineToolId(node.pluginId);
if (toolSource === AppToolSourceEnum.mcp || toolSource === AppToolSourceEnum.http) return [];
if (toolSource === AppToolSourceEnum.personal) {
if (!authAppId) return [];
const { list = [] } = await getAppVersionList({
appId: authAppId,
isPublish: true,
offset: 0,
pageSize: 100
});
return list.map<SystemToolVersionType>((item) => ({
version: item._id,
versionDescription: item.versionName
}));
}
return getTeamToolVersions({ return getTeamToolVersions({
toolId: node.pluginId, toolId: node.pluginId,
source: source === AppToolSourceEnum.systemTool ? 'system' : 'team' source:
toolSource === AppToolSourceEnum.systemTool || toolSource === AppToolSourceEnum.commercial
? 'system'
: 'team'
}); });
}, },
{ {
refreshDeps: [node.pluginId] refreshDeps: [node.pluginId, toolSource]
} }
); );
...@@ -602,7 +639,10 @@ const NodeVersion = React.memo(function NodeVersion({ node }: { node: FlowNodeIt ...@@ -602,7 +639,10 @@ const NodeVersion = React.memo(function NodeVersion({ node }: { node: FlowNodeIt
if (!node) return; if (!node) return;
if (node.pluginId) { if (node.pluginId) {
const template = await getToolPreviewNode({ appId: node.pluginId, version: versionId }); const template = await getToolPreviewNode({
appId: node.pluginId,
versionId
});
if (!!template) { if (!!template) {
onResetNode({ onResetNode({
...@@ -623,6 +663,19 @@ const NodeVersion = React.memo(function NodeVersion({ node }: { node: FlowNodeIt ...@@ -623,6 +663,19 @@ const NodeVersion = React.memo(function NodeVersion({ node }: { node: FlowNodeIt
refreshDeps: [node, onResetNode] refreshDeps: [node, onResetNode]
} }
); );
const onSelectVersion = useCallback(
(versionId: string) => {
if (!versionId) {
openKeepLatestConfirm({
onConfirm: () => onUpdateVersion('')
})();
return;
}
return onUpdateVersion(versionId);
},
[onUpdateVersion, openKeepLatestConfirm]
);
const renderVersionList = useCreation( const renderVersionList = useCreation(
() => [ () => [
...@@ -631,8 +684,7 @@ const NodeVersion = React.memo(function NodeVersion({ node }: { node: FlowNodeIt ...@@ -631,8 +684,7 @@ const NodeVersion = React.memo(function NodeVersion({ node }: { node: FlowNodeIt
value: '' value: ''
}, },
...versionList.map((item) => ({ ...versionList.map((item) => ({
label: item.version, label: item.versionDescription || item.version,
description: item.versionDescription,
value: item.version value: item.version
})) }))
], ],
...@@ -652,10 +704,11 @@ const NodeVersion = React.memo(function NodeVersion({ node }: { node: FlowNodeIt ...@@ -652,10 +704,11 @@ const NodeVersion = React.memo(function NodeVersion({ node }: { node: FlowNodeIt
}, [node.isLatestVersion, node.version, node.versionLabel, t]); }, [node.isLatestVersion, node.version, node.versionLabel, t]);
return ( return (
<>
<MySelect <MySelect
className="nowheel" className="nowheel"
value={node.version} value={node.version}
onChange={onUpdateVersion} onChange={onSelectVersion}
isLoading={isUpdating || isLoadingVersions} isLoading={isUpdating || isLoadingVersions}
customOnOpen={loadVersions} customOnOpen={loadVersions}
placeholder={node?.versionLabel} placeholder={node?.versionLabel}
...@@ -664,6 +717,8 @@ const NodeVersion = React.memo(function NodeVersion({ node }: { node: FlowNodeIt ...@@ -664,6 +717,8 @@ const NodeVersion = React.memo(function NodeVersion({ node }: { node: FlowNodeIt
list={renderVersionList} list={renderVersionList}
valueLabel={valueLabel} valueLabel={valueLabel}
/> />
<KeepLatestConfirmModal isLoading={isUpdating} />
</>
); );
}); });
......
...@@ -190,7 +190,7 @@ const RenderList = React.memo(function RenderList({ ...@@ -190,7 +190,7 @@ const RenderList = React.memo(function RenderList({
const { runAsync: onClickAdd, loading: isLoading } = useRequest( const { runAsync: onClickAdd, loading: isLoading } = useRequest(
async (template: NodeTemplateListItemType) => { async (template: NodeTemplateListItemType) => {
const res = await getToolPreviewNode({ appId: template.id }); const res = await getToolPreviewNode({ appId: template.id, versionId: '' });
const isToolSetTemplate = template.flowNodeType === FlowNodeTypeEnum.toolSet; const isToolSetTemplate = template.flowNodeType === FlowNodeTypeEnum.toolSet;
/* Invalid plugin check /* Invalid plugin check
......
...@@ -238,7 +238,7 @@ const HomeChatWindow = () => { ...@@ -238,7 +238,7 @@ const HomeChatWindow = () => {
const tools: FlowNodeTemplateType[] = await Promise.all( const tools: FlowNodeTemplateType[] = await Promise.all(
selectedToolIds.map(async (toolId) => { selectedToolIds.map(async (toolId) => {
const node = await getToolPreviewNode({ appId: toolId }); const node = await getToolPreviewNode({ appId: toolId, versionId: '' });
node.inputs = node.inputs.map((input) => { node.inputs = node.inputs.map((input) => {
const tool = availableTools.find((tool) => tool.pluginId === toolId); const tool = availableTools.find((tool) => tool.pluginId === toolId);
const value = tool?.inputs?.[input.key]; const value = tool?.inputs?.[input.key];
...@@ -433,9 +433,7 @@ const HomeChatWindow = () => { ...@@ -433,9 +433,7 @@ const HomeChatWindow = () => {
> >
<IconButton <IconButton
aria-label="Open history" aria-label="Open history"
icon={ icon={<MyIcon name="core/chat/sidebar/menu" w="20px" h="20px" color="currentColor" />}
<MyIcon name="core/chat/sidebar/menu" w="20px" h="20px" color="currentColor" />
}
variant="unstyled" variant="unstyled"
{...mobileChatHeaderIconButtonStyle} {...mobileChatHeaderIconButtonStyle}
onClick={onOpenSlider} onClick={onOpenSlider}
......
...@@ -329,7 +329,7 @@ const SystemToolConfigModal = ({ ...@@ -329,7 +329,7 @@ const SystemToolConfigModal = ({
const versionSelectList = useMemo( const versionSelectList = useMemo(
() => () =>
toolVersions.map((item) => ({ toolVersions.map((item) => ({
label: item.version, label: item.versionDescription || item.version,
value: item.version value: item.version
})), })),
[toolVersions] [toolVersions]
......
...@@ -5,26 +5,40 @@ import { getChildAppPreviewNode } from '@fastgpt/service/core/app/tool/controlle ...@@ -5,26 +5,40 @@ import { getChildAppPreviewNode } from '@fastgpt/service/core/app/tool/controlle
import { type FlowNodeTemplateType } from '@fastgpt/global/core/workflow/type/node'; import { type FlowNodeTemplateType } from '@fastgpt/global/core/workflow/type/node';
import { NextAPI } from '@/service/middleware/entry'; import { NextAPI } from '@/service/middleware/entry';
import { type ApiRequestProps } from '@fastgpt/service/type/next'; import { type ApiRequestProps } from '@fastgpt/service/type/next';
import type { NextApiResponse } from 'next';
import { getLocale } from '@fastgpt/service/common/middle/i18n'; import { getLocale } from '@fastgpt/service/common/middle/i18n';
import { splitCombineToolId } from '@fastgpt/global/core/app/tool/utils'; import { splitCombineToolId } from '@fastgpt/global/core/app/tool/utils';
import { ReadPermissionVal } from '@fastgpt/global/support/permission/constant'; import { ReadPermissionVal } from '@fastgpt/global/support/permission/constant';
import { authApp } from '@fastgpt/service/support/permission/app/auth'; import { authApp } from '@fastgpt/service/support/permission/app/auth';
import { parseApiInput } from '@fastgpt/service/common/zod/requestParseError';
export type GetPreviewNodeQuery = { appId: string; version?: string }; import {
GetPreviewNodeQuerySchema,
GetPreviewNodeResponseSchema,
type GetPreviewNodeQuery
} from '@fastgpt/global/openapi/core/app/tool/api';
async function handler( async function handler(
req: ApiRequestProps<{}, GetPreviewNodeQuery>, req: ApiRequestProps<Record<string, never>, GetPreviewNodeQuery>
_res: NextApiResponse<any>
): Promise<FlowNodeTemplateType> { ): Promise<FlowNodeTemplateType> {
const { appId, version } = req.query; const {
query: { appId, versionId, getLatestVersion }
} = parseApiInput({
req,
querySchema: GetPreviewNodeQuerySchema
});
const { authAppId } = splitCombineToolId(appId); const { authAppId } = splitCombineToolId(appId);
if (authAppId) { if (authAppId) {
await authApp({ req, authToken: true, appId: authAppId, per: ReadPermissionVal }); await authApp({ req, authToken: true, appId: authAppId, per: ReadPermissionVal });
} }
return getChildAppPreviewNode({ appId, versionId: version, lang: getLocale(req) }); return GetPreviewNodeResponseSchema.parse(
await getChildAppPreviewNode({
appId,
versionId,
getLatestVersion,
lang: getLocale(req)
})
);
} }
export default NextAPI(handler); export default NextAPI(handler);
import { type NodeTemplateListItemType } from '@fastgpt/global/core/workflow/type/node'; import { type NodeTemplateListItemType } from '@fastgpt/global/core/workflow/type/node';
import { NextAPI } from '@/service/middleware/entry'; import { NextAPI } from '@/service/middleware/entry';
import { type ParentIdType } from '@fastgpt/global/common/parentFolder/type';
import { type ApiRequestProps } from '@fastgpt/service/type/next'; import { type ApiRequestProps } from '@fastgpt/service/type/next';
import { getLocale } from '@fastgpt/service/common/middle/i18n'; import { getLocale } from '@fastgpt/service/common/middle/i18n';
import { authCert } from '@fastgpt/service/support/permission/auth/common'; import { authCert } from '@fastgpt/service/support/permission/auth/common';
...@@ -9,19 +8,25 @@ import { FlowNodeTemplateTypeEnum } from '@fastgpt/global/core/workflow/constant ...@@ -9,19 +8,25 @@ import { FlowNodeTemplateTypeEnum } from '@fastgpt/global/core/workflow/constant
import { getUserDetail } from '@fastgpt/service/support/user/controller'; import { getUserDetail } from '@fastgpt/service/support/user/controller';
import { SystemToolRepo } from '@fastgpt/service/core/app/tool/systemTool/systemTool.repo'; import { SystemToolRepo } from '@fastgpt/service/core/app/tool/systemTool/systemTool.repo';
import { replaceRegChars } from '@fastgpt/global/common/string/tools'; import { replaceRegChars } from '@fastgpt/global/common/string/tools';
import {
GetSystemToolTemplatesBodySchema,
GetSystemToolTemplatesResponseSchema,
type GetSystemToolTemplatesBodyType
} from '@fastgpt/global/openapi/core/app/tool/api';
import { parseApiInput } from '@fastgpt/service/common/zod/requestParseError';
export type GetSystemPluginTemplatesBody = { export type GetSystemPluginTemplatesBody = GetSystemToolTemplatesBodyType;
getAll?: boolean;
searchKey?: string;
parentId?: ParentIdType;
tags?: string[];
};
export async function handler( export async function handler(
req: ApiRequestProps<GetSystemPluginTemplatesBody> req: ApiRequestProps<GetSystemPluginTemplatesBody>
): Promise<NodeTemplateListItemType[]> { ): Promise<NodeTemplateListItemType[]> {
const { teamId, tmbId, isRoot } = await authCert({ req, authToken: true }); const { teamId, tmbId, isRoot } = await authCert({ req, authToken: true });
const { tags, parentId, searchKey } = req.body; const {
body: { tags, parentId, searchKey }
} = parseApiInput({
req,
bodySchema: GetSystemToolTemplatesBodySchema
});
const lang = getLocale(req); const lang = getLocale(req);
const searchRegex = getSearchRegex(searchKey); const searchRegex = getSearchRegex(searchKey);
...@@ -56,7 +61,9 @@ export async function handler( ...@@ -56,7 +61,9 @@ export async function handler(
hasTokenFee: parent.hasTokenFee hasTokenFee: parent.hasTokenFee
})) ?? []; })) ?? [];
return filterTemplatesBySearchKey(childTemplates, searchRegex); return GetSystemToolTemplatesResponseSchema.parse(
filterTemplatesBySearchKey(childTemplates, searchRegex)
);
} }
// no parentId, get all tools // no parentId, get all tools
const tools = await systemToolRepo.getSystemToolList({ const tools = await systemToolRepo.getSystemToolList({
...@@ -65,7 +72,7 @@ export async function handler( ...@@ -65,7 +72,7 @@ export async function handler(
tags tags
}); });
return tools const templates = tools
.filter((item) => { .filter((item) => {
if (isRoot) return true; if (isRoot) return true;
if (item.hideTags && item.hideTags.some((tag) => userTags.includes(tag))) return false; if (item.hideTags && item.hideTags.some((tag) => userTags.includes(tag))) return false;
...@@ -82,6 +89,8 @@ export async function handler( ...@@ -82,6 +89,8 @@ export async function handler(
tags: tool.tags tags: tool.tags
})) }))
.filter((item) => filterTemplateBySearchKey(item, searchRegex)); .filter((item) => filterTemplateBySearchKey(item, searchRegex));
return GetSystemToolTemplatesResponseSchema.parse(templates);
} }
export default NextAPI(handler); export default NextAPI(handler);
......
import type { ApiRequestProps, ApiResponseType } from '@fastgpt/service/type/next'; import type { ApiRequestProps } from '@fastgpt/service/type/next';
import { NextAPI } from '@/service/middleware/entry'; import { NextAPI } from '@/service/middleware/entry';
import {
type GetPathProps,
type ParentTreePathItemType
} from '@fastgpt/global/common/parentFolder/type';
import { getLocale } from '@fastgpt/service/common/middle/i18n'; import { getLocale } from '@fastgpt/service/common/middle/i18n';
import { SystemToolRepo } from '@fastgpt/service/core/app/tool/systemTool/systemTool.repo'; import { SystemToolRepo } from '@fastgpt/service/core/app/tool/systemTool/systemTool.repo';
import { splitCombineToolId } from '@fastgpt/global/core/app/tool/utils'; import { splitCombineToolId } from '@fastgpt/global/core/app/tool/utils';
import { AppToolSourceEnum } from '@fastgpt/global/core/app/tool/constants'; import { AppToolSourceEnum } from '@fastgpt/global/core/app/tool/constants';
import {
GetToolPathQuerySchema,
GetToolPathResponseSchema,
type GetToolPathQueryType,
type GetToolPathResponseType
} from '@fastgpt/global/openapi/core/app/tool/api';
import { parseApiInput } from '@fastgpt/service/common/zod/requestParseError';
export type pathQuery = GetPathProps; export type pathQuery = GetToolPathQueryType;
export type pathBody = {}; export type pathBody = Record<string, never>;
export type pathResponse = Promise<ParentTreePathItemType[]>; export type pathResponse = Promise<GetToolPathResponseType>;
export async function handler( export async function handler(req: ApiRequestProps<pathBody, pathQuery>): Promise<pathResponse> {
req: ApiRequestProps<pathBody, pathQuery>, const {
res: ApiResponseType<any> query: { sourceId: pluginId, type = 'current' }
): Promise<pathResponse> { } = parseApiInput({
const { sourceId: pluginId, type = 'current' } = req.query; req,
querySchema: GetToolPathQuerySchema
});
const lang = getLocale(req); const lang = getLocale(req);
if (!pluginId) return []; if (!pluginId) return GetToolPathResponseSchema.parse([]);
const parentToolId = getParentToolId(pluginId); const parentToolId = getParentToolId(pluginId);
const pathToolIds = type === 'parent' ? (parentToolId ? [parentToolId] : []) : []; const pathToolIds = type === 'parent' ? (parentToolId ? [parentToolId] : []) : [];
...@@ -34,7 +39,9 @@ export async function handler( ...@@ -34,7 +39,9 @@ export async function handler(
pathToolIds.push(pluginId); pathToolIds.push(pluginId);
} }
return Promise.all(pathToolIds.map((toolId) => getToolPathItem({ toolId, lang }))); return GetToolPathResponseSchema.parse(
await Promise.all(pathToolIds.map((toolId) => getToolPathItem({ toolId, lang })))
);
} }
export default NextAPI(handler); export default NextAPI(handler);
...@@ -58,7 +65,7 @@ async function getToolPathItem({ ...@@ -58,7 +65,7 @@ async function getToolPathItem({
}: { }: {
toolId: string; toolId: string;
lang: ReturnType<typeof getLocale>; lang: ReturnType<typeof getLocale>;
}): Promise<ParentTreePathItemType> { }): Promise<GetToolPathResponseType[number]> {
const systemToolRepo = SystemToolRepo.getInstance(); const systemToolRepo = SystemToolRepo.getInstance();
const { source } = splitCombineToolId(toolId); const { source } = splitCombineToolId(toolId);
const tool = await systemToolRepo.getSystemToolDetail({ const tool = await systemToolRepo.getSystemToolDetail({
......
...@@ -2,7 +2,7 @@ import { NextAPI } from '@/service/middleware/entry'; ...@@ -2,7 +2,7 @@ import { NextAPI } from '@/service/middleware/entry';
import { MongoAppVersion } from '@fastgpt/service/core/app/version/schema'; import { MongoAppVersion } from '@fastgpt/service/core/app/version/schema';
import { type ApiRequestProps } from '@fastgpt/service/type/next'; import { type ApiRequestProps } from '@fastgpt/service/type/next';
import { authApp } from '@fastgpt/service/support/permission/app/auth'; import { authApp } from '@fastgpt/service/support/permission/app/auth';
import { WritePermissionVal } from '@fastgpt/global/support/permission/constant'; import { ReadPermissionVal } from '@fastgpt/global/support/permission/constant';
import { parsePaginationRequest } from '@fastgpt/service/common/api/pagination'; import { parsePaginationRequest } from '@fastgpt/service/common/api/pagination';
import { addSourceMember } from '@fastgpt/service/support/user/utils'; import { addSourceMember } from '@fastgpt/service/support/user/utils';
import { parseApiInput } from '@fastgpt/service/common/zod/requestParseError'; import { parseApiInput } from '@fastgpt/service/common/zod/requestParseError';
...@@ -23,7 +23,7 @@ async function handler( ...@@ -23,7 +23,7 @@ async function handler(
}).body; }).body;
const { offset, pageSize } = parsePaginationRequest(req); const { offset, pageSize } = parsePaginationRequest(req);
await authApp({ appId, req, per: WritePermissionVal, authToken: true }); await authApp({ appId, req, per: ReadPermissionVal, authToken: true });
const match = { const match = {
appId, appId,
......
...@@ -11,6 +11,7 @@ import { ...@@ -11,6 +11,7 @@ import {
GetAdminSystemToolVersionsResponseSchema GetAdminSystemToolVersionsResponseSchema
} from '@fastgpt/global/openapi/core/plugin/admin/tool/api'; } from '@fastgpt/global/openapi/core/plugin/admin/tool/api';
import { SystemToolRepo } from '@fastgpt/service/core/app/tool/systemTool/systemTool.repo'; import { SystemToolRepo } from '@fastgpt/service/core/app/tool/systemTool/systemTool.repo';
import { parseApiInput } from '@fastgpt/service/common/zod/requestParseError';
export type getSystemToolVersionsQuery = GetAdminSystemToolVersionsQueryType; export type getSystemToolVersionsQuery = GetAdminSystemToolVersionsQueryType;
...@@ -22,7 +23,12 @@ async function handler( ...@@ -22,7 +23,12 @@ async function handler(
req: ApiRequestProps<getSystemToolVersionsBody, getSystemToolVersionsQuery>, req: ApiRequestProps<getSystemToolVersionsBody, getSystemToolVersionsQuery>,
res: ApiResponseType<any> res: ApiResponseType<any>
): Promise<getSystemToolVersionsResponse> { ): Promise<getSystemToolVersionsResponse> {
const { toolId } = GetAdminSystemToolVersionsQuerySchema.parse(req.query); const {
query: { toolId }
} = parseApiInput({
req,
querySchema: GetAdminSystemToolVersionsQuerySchema
});
const lang = getLocale(req); const lang = getLocale(req);
await authSystemAdmin({ req }); await authSystemAdmin({ req });
......
import type { ApiRequestProps, ApiResponseType } from '@fastgpt/service/type/next'; import type { ApiRequestProps } from '@fastgpt/service/type/next';
import { NextAPI } from '@/service/middleware/entry'; import { NextAPI } from '@/service/middleware/entry';
import { getLocale } from '@fastgpt/service/common/middle/i18n'; import { getLocale } from '@fastgpt/service/common/middle/i18n';
import { authCert } from '@fastgpt/service/support/permission/auth/common'; import { authCert } from '@fastgpt/service/support/permission/auth/common';
...@@ -7,20 +7,23 @@ import { ...@@ -7,20 +7,23 @@ import {
TeamToolDetailSchema, TeamToolDetailSchema,
type GetTeamToolDetailQueryType, type GetTeamToolDetailQueryType,
type GetTeamToolDetailResponseType type GetTeamToolDetailResponseType
} from '@fastgpt/global/openapi/core/plugin/team/tool/dto'; } from '@fastgpt/global/openapi/core/plugin/team/tool/api';
import { SystemToolRepo } from '@fastgpt/service/core/app/tool/systemTool/systemTool.repo'; import { SystemToolRepo } from '@fastgpt/service/core/app/tool/systemTool/systemTool.repo';
import { parseApiInput } from '@fastgpt/service/common/zod/requestParseError';
export type detailQuery = GetTeamToolDetailQueryType; export type detailQuery = GetTeamToolDetailQueryType;
export type detailBody = {}; export type detailBody = Record<string, never>;
export type detailResponse = GetTeamToolDetailResponseType; export type detailResponse = GetTeamToolDetailResponseType;
async function handler( async function handler(req: ApiRequestProps<detailBody, detailQuery>): Promise<detailResponse> {
req: ApiRequestProps<detailBody, detailQuery>, const {
res: ApiResponseType<any> query: { toolId, source, version }
): Promise<detailResponse> { } = parseApiInput({
const { toolId, source, version } = GetTeamToolDetailQuerySchema.parse(req.query); req,
querySchema: GetTeamToolDetailQuerySchema
});
const lang = getLocale(req); const lang = getLocale(req);
const { teamId } = await authCert({ req, authToken: true }); const { teamId } = await authCert({ req, authToken: true });
......
import type { ApiRequestProps, ApiResponseType } from '@fastgpt/service/type/next'; import type { ApiRequestProps } from '@fastgpt/service/type/next';
import { NextAPI } from '@/service/middleware/entry'; import { NextAPI } from '@/service/middleware/entry';
import { import {
type GetTeamSystemPluginListQueryType, type GetTeamSystemPluginListQueryType,
type GetTeamPluginListResponseType type GetTeamPluginListResponseType
} from '@fastgpt/global/openapi/core/plugin/team/tool/dto'; } from '@fastgpt/global/openapi/core/plugin/team/tool/api';
import { authCert } from '@fastgpt/service/support/permission/auth/common'; import { authCert } from '@fastgpt/service/support/permission/auth/common';
import { getLocale } from '@fastgpt/service/common/middle/i18n'; import { getLocale } from '@fastgpt/service/common/middle/i18n';
import { SystemToolRepo } from '@fastgpt/service/core/app/tool/systemTool/systemTool.repo'; import { SystemToolRepo } from '@fastgpt/service/core/app/tool/systemTool/systemTool.repo';
...@@ -12,7 +12,7 @@ import type { UserTagsType } from '@fastgpt/global/support/user/type'; ...@@ -12,7 +12,7 @@ import type { UserTagsType } from '@fastgpt/global/support/user/type';
export type listQuery = GetTeamSystemPluginListQueryType; export type listQuery = GetTeamSystemPluginListQueryType;
export type listBody = {}; export type listBody = Record<string, never>;
export type listResponse = GetTeamPluginListResponseType; export type listResponse = GetTeamPluginListResponseType;
...@@ -26,10 +26,7 @@ const hasMatchedUserTag = ({ ...@@ -26,10 +26,7 @@ const hasMatchedUserTag = ({
return !!targetTags?.some((tag) => userTags.includes(tag)); return !!targetTags?.some((tag) => userTags.includes(tag));
}; };
async function handler( async function handler(req: ApiRequestProps<listBody, listQuery>): Promise<listResponse> {
req: ApiRequestProps<listBody, listQuery>,
res: ApiResponseType<any>
): Promise<listResponse> {
const lang = getLocale(req); const lang = getLocale(req);
const { teamId, tmbId } = await authCert({ req, authToken: true }); const { teamId, tmbId } = await authCert({ req, authToken: true });
......
...@@ -2,25 +2,30 @@ import { NextAPI } from '@/service/middleware/entry'; ...@@ -2,25 +2,30 @@ import { NextAPI } from '@/service/middleware/entry';
import { getLocale } from '@fastgpt/service/common/middle/i18n'; import { getLocale } from '@fastgpt/service/common/middle/i18n';
import { SystemToolRepo } from '@fastgpt/service/core/app/tool/systemTool/systemTool.repo'; import { SystemToolRepo } from '@fastgpt/service/core/app/tool/systemTool/systemTool.repo';
import { authCert } from '@fastgpt/service/support/permission/auth/common'; import { authCert } from '@fastgpt/service/support/permission/auth/common';
import type { ApiRequestProps, ApiResponseType } from '@fastgpt/service/type/next'; import type { ApiRequestProps } from '@fastgpt/service/type/next';
import { import {
GetTeamToolVersionsQuerySchema, GetTeamToolVersionsQuerySchema,
GetTeamToolVersionsResponseSchema, GetTeamToolVersionsResponseSchema,
type GetTeamToolVersionsQueryType, type GetTeamToolVersionsQueryType,
type GetTeamToolVersionsResponseType type GetTeamToolVersionsResponseType
} from '@fastgpt/global/openapi/core/plugin/team/tool/dto'; } from '@fastgpt/global/openapi/core/plugin/team/tool/api';
import { parseApiInput } from '@fastgpt/service/common/zod/requestParseError';
export type getSystemToolVersionsQuery = GetTeamToolVersionsQueryType; export type getSystemToolVersionsQuery = GetTeamToolVersionsQueryType;
export type getSystemToolVersionsBody = {}; export type getSystemToolVersionsBody = Record<string, never>;
export type getSystemToolVersionsResponse = GetTeamToolVersionsResponseType; export type getSystemToolVersionsResponse = GetTeamToolVersionsResponseType;
async function handler( async function handler(
req: ApiRequestProps<getSystemToolVersionsBody, getSystemToolVersionsQuery>, req: ApiRequestProps<getSystemToolVersionsBody, getSystemToolVersionsQuery>
res: ApiResponseType<any>
): Promise<getSystemToolVersionsResponse> { ): Promise<getSystemToolVersionsResponse> {
const { toolId, source } = GetTeamToolVersionsQuerySchema.parse(req.query); const {
query: { toolId, source }
} = parseApiInput({
req,
querySchema: GetTeamToolVersionsQuerySchema
});
const lang = getLocale(req); const lang = getLocale(req);
const { teamId } = await authCert({ req, authToken: true }); const { teamId } = await authCert({ req, authToken: true });
......
...@@ -21,7 +21,7 @@ import ToolDetailDrawer from '@fastgpt/web/components/core/plugin/tool/ToolDetai ...@@ -21,7 +21,7 @@ import ToolDetailDrawer from '@fastgpt/web/components/core/plugin/tool/ToolDetai
import { useUserStore } from '../../../web/support/user/useUserStore'; import { useUserStore } from '../../../web/support/user/useUserStore';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import { getDocPath } from '@/web/common/system/doc'; import { getDocPath } from '@/web/common/system/doc';
import type { GetTeamPluginListResponseType } from '@fastgpt/global/openapi/core/plugin/team/tool/dto'; import type { GetTeamPluginListResponseType } from '@fastgpt/global/openapi/core/plugin/team/tool/api';
import { parseI18nString } from '@fastgpt/global/common/i18n/utils'; import { parseI18nString } from '@fastgpt/global/common/i18n/utils';
import DashboardContainer from '@/pageComponents/dashboard/Container'; import DashboardContainer from '@/pageComponents/dashboard/Container';
import { useSystem } from '@fastgpt/web/hooks/useSystem'; import { useSystem } from '@fastgpt/web/hooks/useSystem';
......
...@@ -6,16 +6,18 @@ import type { ...@@ -6,16 +6,18 @@ import type {
import { getAppDetailById, getMyApps } from '../api'; import { getAppDetailById, getMyApps } from '../api';
import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant'; import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
import { FlowNodeTemplateTypeEnum } from '@fastgpt/global/core/workflow/constants'; import { FlowNodeTemplateTypeEnum } from '@fastgpt/global/core/workflow/constants';
import type { GetPreviewNodeQuery } from '@/pages/api/core/app/tool/getPreviewNode';
import { AppTypeEnum } from '@fastgpt/global/core/app/constants'; import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
import type { import type {
GetPathProps, GetPathProps,
ParentIdType, ParentIdType,
ParentTreePathItemType ParentTreePathItemType
} from '@fastgpt/global/common/parentFolder/type'; } from '@fastgpt/global/common/parentFolder/type';
import type { GetSystemPluginTemplatesBody } from '@/pages/api/core/app/tool/getSystemToolTemplates';
import { AppToolSourceEnum } from '@fastgpt/global/core/app/tool/constants'; import { AppToolSourceEnum } from '@fastgpt/global/core/app/tool/constants';
import { getMcpChildren } from './mcpTools'; import { getMcpChildren } from './mcpTools';
import type {
GetPreviewNodeQuery,
GetSystemToolTemplatesBodyType
} from '@fastgpt/global/openapi/core/app/tool/api';
/* ============ team plugin ============== */ /* ============ team plugin ============== */
export const getTeamAppTemplates = async (data?: { export const getTeamAppTemplates = async (data?: {
...@@ -83,7 +85,7 @@ export const getTeamAppTemplates = async (data?: { ...@@ -83,7 +85,7 @@ export const getTeamAppTemplates = async (data?: {
}; };
/* ============ Tool ============== */ /* ============ Tool ============== */
export const getAppToolTemplates = (data: GetSystemPluginTemplatesBody) => export const getAppToolTemplates = (data: GetSystemToolTemplatesBodyType) =>
POST<NodeTemplateListItemType[]>('/core/app/tool/getSystemToolTemplates', data); POST<NodeTemplateListItemType[]>('/core/app/tool/getSystemToolTemplates', data);
export const getAppToolPaths = (data: GetPathProps) => { export const getAppToolPaths = (data: GetPathProps) => {
......
...@@ -2,13 +2,13 @@ import { GET } from '@/web/common/api/request'; ...@@ -2,13 +2,13 @@ import { GET } from '@/web/common/api/request';
import type { import type {
GetTeamSystemPluginListQueryType, GetTeamSystemPluginListQueryType,
GetTeamPluginListResponseType GetTeamPluginListResponseType
} from '@fastgpt/global/openapi/core/plugin/team/tool/dto'; } from '@fastgpt/global/openapi/core/plugin/team/tool/api';
import type { import type {
GetTeamToolDetailQueryType, GetTeamToolDetailQueryType,
GetTeamToolDetailResponseType, GetTeamToolDetailResponseType,
GetTeamToolVersionsQueryType, GetTeamToolVersionsQueryType,
GetTeamToolVersionsResponseType GetTeamToolVersionsResponseType
} from '@fastgpt/global/openapi/core/plugin/team/tool/dto'; } from '@fastgpt/global/openapi/core/plugin/team/tool/api';
export const getTeamSystemPluginList = (data: GetTeamSystemPluginListQueryType) => export const getTeamSystemPluginList = (data: GetTeamSystemPluginListQueryType) =>
GET<GetTeamPluginListResponseType>(`/core/plugin/team/tool/list`, data); GET<GetTeamPluginListResponseType>(`/core/plugin/team/tool/list`, data);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment