Commit 8c6ddb46 by Finley Ge Committed by GitHub

fix: app detail rewrite workaround (#7058)

* fix: workflow system tool use pluginModule

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

* fix: detail rewrite

* fix(app): stringify enum options and keep reference selection
parent ecc45a58
...@@ -115,15 +115,15 @@ const getNodeInputRenderTypeFromSchemaInputType = ({ ...@@ -115,15 +115,15 @@ const getNodeInputRenderTypeFromSchemaInputType = ({
if (type === 'array' && items?.enum && items.enum.length > 0) { if (type === 'array' && items?.enum && items.enum.length > 0) {
return { return {
value: [], value: [],
renderTypeList: [FlowNodeInputTypeEnum.multipleSelect], renderTypeList: [FlowNodeInputTypeEnum.multipleSelect, FlowNodeInputTypeEnum.reference],
list: items.enum.map((item: any) => ({ label: item, value: item })) list: items.enum.map(formatJsonSchemaEnumOption)
}; };
} }
if (enumList && enumList.length > 0) { if (enumList && enumList.length > 0) {
return { return {
value: enumList[0], value: String(enumList[0]),
renderTypeList: [FlowNodeInputTypeEnum.select], renderTypeList: [FlowNodeInputTypeEnum.select, FlowNodeInputTypeEnum.reference],
list: enumList.map((item) => ({ label: item, value: item })) list: enumList.map(formatJsonSchemaEnumOption)
}; };
} }
if (type === 'string') { if (type === 'string') {
...@@ -140,12 +140,18 @@ const getNodeInputRenderTypeFromSchemaInputType = ({ ...@@ -140,12 +140,18 @@ const getNodeInputRenderTypeFromSchemaInputType = ({
} }
if (type === 'boolean') { if (type === 'boolean') {
return { return {
renderTypeList: [FlowNodeInputTypeEnum.switch] renderTypeList: [FlowNodeInputTypeEnum.switch, FlowNodeInputTypeEnum.reference]
}; };
} }
return { renderTypeList: [FlowNodeInputTypeEnum.JSONEditor, FlowNodeInputTypeEnum.reference] }; return { renderTypeList: [FlowNodeInputTypeEnum.JSONEditor, FlowNodeInputTypeEnum.reference] };
}; };
/** 将 JSON Schema enum 值规范成节点输入选项,避免响应 schema 因非字符串 value 解析失败。 */
const formatJsonSchemaEnumOption = (item: any) => {
const value = String(item);
return { label: value, value };
};
export const jsonSchema2NodeInput = ({ export const jsonSchema2NodeInput = ({
jsonSchema = { type: 'Object' }, jsonSchema = { type: 'Object' },
schemaType schemaType
......
...@@ -11,6 +11,7 @@ import { ...@@ -11,6 +11,7 @@ import {
str2OpenApiSchema str2OpenApiSchema
} from '@fastgpt/global/core/app/jsonschema'; } from '@fastgpt/global/core/app/jsonschema';
import { WorkflowIOValueTypeEnum } from '@fastgpt/global/core/workflow/constants'; import { WorkflowIOValueTypeEnum } from '@fastgpt/global/core/workflow/constants';
import { FlowNodeInputItemTypeSchema } from '@fastgpt/global/core/workflow/type/io';
describe('jsonSchema2NodeInput', () => { describe('jsonSchema2NodeInput', () => {
it('should return correct node input for http schema', () => { it('should return correct node input for http schema', () => {
...@@ -46,7 +47,7 @@ describe('jsonSchema2NodeInput', () => { ...@@ -46,7 +47,7 @@ describe('jsonSchema2NodeInput', () => {
toolDescription: undefined, toolDescription: undefined,
required: false, required: false,
value: '11', value: '11',
renderTypeList: ['select'], renderTypeList: ['select', 'reference'],
list: [ list: [
{ {
label: '11', label: '11',
...@@ -74,7 +75,7 @@ describe('jsonSchema2NodeInput', () => { ...@@ -74,7 +75,7 @@ describe('jsonSchema2NodeInput', () => {
valueType: 'boolean', valueType: 'boolean',
toolDescription: undefined, toolDescription: undefined,
required: false, required: false,
renderTypeList: ['switch'] renderTypeList: ['switch', 'reference']
}, },
{ {
key: 'object', key: 'object',
...@@ -202,7 +203,7 @@ describe('jsonSchema2NodeInput', () => { ...@@ -202,7 +203,7 @@ describe('jsonSchema2NodeInput', () => {
toolDescription: '选择热榜来源网站(可多选)', toolDescription: '选择热榜来源网站(可多选)',
required: true, required: true,
value: [], value: [],
renderTypeList: ['multipleSelect'], renderTypeList: ['multipleSelect', 'reference'],
list: [ list: [
{ label: '36kr', value: '36kr' }, { label: '36kr', value: '36kr' },
{ label: 'zhihu', value: 'zhihu' }, { label: 'zhihu', value: 'zhihu' },
...@@ -213,6 +214,51 @@ describe('jsonSchema2NodeInput', () => { ...@@ -213,6 +214,51 @@ describe('jsonSchema2NodeInput', () => {
} }
]); ]);
}); });
it('should stringify enum options to match node input schema', () => {
const jsonSchema: JSONSchemaInputType = {
type: 'object',
properties: {
count: {
type: 'number',
enum: [1, 2],
description: 'Number enum'
},
flags: {
type: 'array',
items: {
type: 'boolean',
enum: [true, false]
}
}
}
};
const result = jsonSchema2NodeInput({ jsonSchema, schemaType: 'mcp' });
result.forEach((item) => expect(() => FlowNodeInputItemTypeSchema.parse(item)).not.toThrow());
expect(result).toMatchObject([
{
key: 'count',
value: '1',
valueType: WorkflowIOValueTypeEnum.number,
renderTypeList: ['select', 'reference'],
list: [
{ label: '1', value: '1' },
{ label: '2', value: '2' }
]
},
{
key: 'flags',
valueType: WorkflowIOValueTypeEnum.arrayBoolean,
renderTypeList: ['multipleSelect', 'reference'],
list: [
{ label: 'true', value: 'true' },
{ label: 'false', value: 'false' }
]
}
]);
});
}); });
describe('getNodeInputTypeFromSchemaInputType', () => { describe('getNodeInputTypeFromSchemaInputType', () => {
......
...@@ -144,10 +144,20 @@ export async function rewriteAppWorkflowToDetail({ ...@@ -144,10 +144,20 @@ export async function rewriteAppWorkflowToDetail({
node.inputs = preview.inputs.map((item) => { node.inputs = preview.inputs.map((item) => {
const input = inputsMap.get(item.key); const input = inputsMap.get(item.key);
const selectedRenderType =
input?.renderTypeList?.[input?.selectedTypeIndex ?? 0] ?? item.renderTypeList?.[0];
const selectedTypeIndex = selectedRenderType
? item.renderTypeList.findIndex((renderType) => renderType === selectedRenderType)
: -1;
return { return {
...item, ...item,
value: input?.value, value: input?.value,
selectedTypeIndex: input?.selectedTypeIndex selectedTypeIndex:
selectedTypeIndex >= 0 &&
(selectedTypeIndex > 0 || input?.selectedTypeIndex !== undefined)
? selectedTypeIndex
: undefined
}; };
}); });
node.outputs = preview.outputs.map((item) => { node.outputs = preview.outputs.map((item) => {
......
import { describe, expect, it } from 'vitest'; import { beforeEach, describe, expect, it, vi } from 'vitest';
import { rewriteAppWorkflowToDetail } from '@fastgpt/service/core/app/utils';
import { MongoAgentSkills } from '@fastgpt/service/core/ai/skill/model/schema'; import { MongoAgentSkills } from '@fastgpt/service/core/ai/skill/model/schema';
import { AgentSkillSourceEnum, AgentSkillTypeEnum } from '@fastgpt/global/core/ai/skill/constants'; import { AgentSkillSourceEnum, AgentSkillTypeEnum } from '@fastgpt/global/core/ai/skill/constants';
import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant'; import {
import { NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants'; FlowNodeInputTypeEnum,
FlowNodeOutputTypeEnum,
FlowNodeTypeEnum
} from '@fastgpt/global/core/workflow/node/constant';
import { NodeInputKeyEnum, WorkflowIOValueTypeEnum } from '@fastgpt/global/core/workflow/constants';
import type { StoreNodeItemType } from '@fastgpt/global/core/workflow/type/node'; import type { StoreNodeItemType } from '@fastgpt/global/core/workflow/type/node';
import type { SelectedAgentSkillItemType } from '@fastgpt/global/core/app/formEdit/type'; import type { SelectedAgentSkillItemType } from '@fastgpt/global/core/app/formEdit/type';
import { getNanoid } from '@fastgpt/global/common/string/tools'; import { getNanoid } from '@fastgpt/global/common/string/tools';
import { getUser } from '@test/datas/users'; import { getUser } from '@test/datas/users';
const { getChildAppPreviewNodeMock, authAppByTmbIdMock } = vi.hoisted(() => ({
getChildAppPreviewNodeMock: vi.fn(),
authAppByTmbIdMock: vi.fn()
}));
vi.mock('@fastgpt/service/core/app/tool/controller', async (importOriginal) => {
const mod = await importOriginal<typeof import('@fastgpt/service/core/app/tool/controller')>();
return {
...mod,
getChildAppPreviewNode: getChildAppPreviewNodeMock
};
});
vi.mock('@fastgpt/service/support/permission/app/auth', async (importOriginal) => {
const mod = await importOriginal<typeof import('@fastgpt/service/support/permission/app/auth')>();
return {
...mod,
authAppByTmbId: authAppByTmbIdMock
};
});
const { rewriteAppWorkflowToDetail } = await import('@fastgpt/service/core/app/utils');
describe('rewriteAppWorkflowToDetail - agent skills', () => { describe('rewriteAppWorkflowToDetail - agent skills', () => {
beforeEach(() => {
getChildAppPreviewNodeMock.mockReset();
authAppByTmbIdMock.mockReset();
});
it('在 app detail 改写阶段标记已删除 Skill,并刷新可用 Skill 的快照信息', async () => { it('在 app detail 改写阶段标记已删除 Skill,并刷新可用 Skill 的快照信息', async () => {
const user = await getUser(`agent-skill-detail-${getNanoid(6)}`); const user = await getUser(`agent-skill-detail-${getNanoid(6)}`);
const [activeSkill, deletedSkill] = await MongoAgentSkills.create([ const [activeSkill, deletedSkill] = await MongoAgentSkills.create([
...@@ -87,4 +118,76 @@ describe('rewriteAppWorkflowToDetail - agent skills', () => { ...@@ -87,4 +118,76 @@ describe('rewriteAppWorkflowToDetail - agent skills', () => {
} }
]); ]);
}); });
it('刷新最新工具节点时使用新 renderTypeList,并保留旧节点选中的引用类型', async () => {
getChildAppPreviewNodeMock.mockResolvedValue({
id: 'mcp-app-1/tool',
flowNodeType: FlowNodeTypeEnum.tool,
name: 'Tool',
avatar: 'new-avatar',
intro: '',
inputs: [
{
key: 'size',
label: 'Size',
valueType: WorkflowIOValueTypeEnum.number,
value: '1',
renderTypeList: [FlowNodeInputTypeEnum.select, FlowNodeInputTypeEnum.reference],
list: [
{ label: '1', value: '1' },
{ label: '2', value: '2' }
]
}
],
outputs: [
{
id: 'rawResponse',
key: 'rawResponse',
type: FlowNodeOutputTypeEnum.static,
valueType: WorkflowIOValueTypeEnum.any
}
],
version: '',
versionLabel: 'latest',
isLatestVersion: true
});
authAppByTmbIdMock.mockResolvedValue({});
const nodes = [
{
nodeId: 'tool',
flowNodeType: FlowNodeTypeEnum.tool,
pluginId: 'mcp-app-1/tool',
inputs: [
{
key: 'size',
label: 'Size',
valueType: WorkflowIOValueTypeEnum.number,
value: ['start', 'amount'],
selectedTypeIndex: 1,
renderTypeList: [FlowNodeInputTypeEnum.select, FlowNodeInputTypeEnum.reference]
}
],
outputs: []
} as StoreNodeItemType
];
await rewriteAppWorkflowToDetail({
nodes,
teamId: 'team-1',
ownerTmbId: 'tmb-1',
isRoot: false
});
expect(nodes[0].inputs[0]).toMatchObject({
key: 'size',
value: ['start', 'amount'],
selectedTypeIndex: 1,
renderTypeList: [FlowNodeInputTypeEnum.select, FlowNodeInputTypeEnum.reference],
list: [
{ label: '1', value: '1' },
{ label: '2', value: '2' }
]
});
});
}); });
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