Commit 007ca097 by Archer Committed by GitHub

perf: workflow runtime (#6562)

* perf: workflow runtime

* perf: lable input and dispatch workflow

* fix: workflow dispatch

* fix: workflow dispatch

* fix: workflow dispatch

* fix: workflow dispatch

* perf: workflow runtime

* perf: workflow runtime
parent 6ea65f64
...@@ -19,7 +19,7 @@ export const JsonSchemaPropertiesItemSchema = z.object({ ...@@ -19,7 +19,7 @@ export const JsonSchemaPropertiesItemSchema = z.object({
not: z.any().optional(), // 不匹配 not: z.any().optional(), // 不匹配
// 枚举和常量 // 枚举和常量
enum: z.array(z.string()).optional(), // 枚举值 enum: z.array(z.any()).optional(), // 枚举值
const: z.any().optional(), // 常量值 const: z.any().optional(), // 常量值
// 字符串约束 // 字符串约束
......
...@@ -67,7 +67,9 @@ export async function createSinks(options: CreateSinksOptions): Promise<CreateSi ...@@ -67,7 +67,9 @@ export async function createSinks(options: CreateSinksOptions): Promise<CreateSi
timestampStyle: 'reset', timestampStyle: 'reset',
categorySeparator: ':', categorySeparator: ':',
timestamp: () => dayjs().format('YYYY-MM-DD HH:mm:ss') timestamp: () => dayjs().format('YYYY-MM-DD HH:mm:ss'),
// Full depth for nested objects (e.g. Zod errors) in console output
inspectOptions: { depth: 5 }
}) })
}), }),
(record) => levelFilter(record, consoleLevel) (record) => levelFilter(record, consoleLevel)
......
...@@ -366,9 +366,16 @@ export const runWorkflow = async (data: RunWorkflowProps): Promise<DispatchFlowR ...@@ -366,9 +366,16 @@ export const runWorkflow = async (data: RunWorkflowProps): Promise<DispatchFlowR
string, string,
{ node: RuntimeNodeItemType; skippedNodeIdList: Set<string> } { node: RuntimeNodeItemType; skippedNodeIdList: Set<string> }
>(); >();
private runningNodeCount = 0;
private maxConcurrency: number; private maxConcurrency: number;
private resolve: (e: WorkflowQueue) => void; private resolve: (e: WorkflowQueue) => void;
private processingActive = false; // 标记是否正在处理队列
// Buffer
// 可以根据 nodeId 获取所有的 source 边和 target 边
private edgeIndex = {
bySource: new Map<string, RuntimeEdgeItemType[]>(),
byTarget: new Map<string, RuntimeEdgeItemType[]>()
};
constructor({ constructor({
maxConcurrency = 10, maxConcurrency = 10,
...@@ -388,6 +395,20 @@ export const runWorkflow = async (data: RunWorkflowProps): Promise<DispatchFlowR ...@@ -388,6 +395,20 @@ export const runWorkflow = async (data: RunWorkflowProps): Promise<DispatchFlowR
if (!node) return; if (!node) return;
this.addSkipNode(node, new Set(skippedNodeIdList)); this.addSkipNode(node, new Set(skippedNodeIdList));
}); });
// 一次性构建索引 - O(m)
const filteredEdges = filterWorkflowEdges(runtimeEdges);
filteredEdges.forEach((edge) => {
if (!this.edgeIndex.bySource.has(edge.source)) {
this.edgeIndex.bySource.set(edge.source, []);
}
this.edgeIndex.bySource.get(edge.source)!.push(edge);
if (!this.edgeIndex.byTarget.has(edge.target)) {
this.edgeIndex.byTarget.set(edge.target, []);
}
this.edgeIndex.byTarget.get(edge.target)!.push(edge);
});
} }
// Add active node to queue (if already in the queue, it will not be added again) // Add active node to queue (if already in the queue, it will not be added again)
...@@ -397,50 +418,79 @@ export const runWorkflow = async (data: RunWorkflowProps): Promise<DispatchFlowR ...@@ -397,50 +418,79 @@ export const runWorkflow = async (data: RunWorkflowProps): Promise<DispatchFlowR
} }
this.activeRunQueue.add(nodeId); this.activeRunQueue.add(nodeId);
this.processActiveNode(); // 非递归触发:如果没有正在处理,则启动处理循环
if (!this.processingActive) {
this.startProcessing();
}
}
// 迭代处理队列(替代递归的 processActiveNode)
private async startProcessing() {
// 防止重复启动
if (this.processingActive) {
return;
} }
// Process next active node
private async processActiveNode() { this.processingActive = true;
// Finish
if (this.activeRunQueue.size === 0 && this.runningNodeCount === 0) { try {
const runningNodePromises = new Set<Promise<unknown>>();
// 迭代循环替代递归
while (true) {
// 检查结束条件
if (this.activeRunQueue.size === 0 && runningNodePromises.size === 0) {
if (isDebugMode) { if (isDebugMode) {
// 没有下一个激活节点,说明debug 进入了一个“即将结束”状态。可以开始处理 skip 节点 // 没有下一个激活节点,说明debug 进入了一个”即将结束”状态。可以开始处理 skip 节点
if (this.debugNextStepRunNodes.length === 0 && this.skipNodeQueue.size > 0) { if (this.debugNextStepRunNodes.length === 0 && this.skipNodeQueue.size > 0) {
this.processSkipNodes(); await this.processSkipNodes();
continue;
} else { } else {
this.resolve(this); this.resolve(this);
break;
} }
return;
} }
// 如果没有交互响应,则开始处理 skip(交互响应的 skip 需要留给后续处理) // 如果没有交互响应,则开始处理 skip(交互响应的 skip 需要留给后续处理)
if (this.skipNodeQueue.size > 0 && !this.nodeInteractiveResponse) { if (this.skipNodeQueue.size > 0 && !this.nodeInteractiveResponse) {
this.processSkipNodes(); await this.processSkipNodes();
continue;
} else { } else {
this.resolve(this); this.resolve(this);
break;
} }
return;
} }
// Over max concurrency(如果 this.activeRunQueue.size === 0 条件触发,代表肯定有节点在运行) // 检查并发限制
if (this.activeRunQueue.size === 0 || this.runningNodeCount >= this.maxConcurrency) { if (this.activeRunQueue.size === 0 || runningNodePromises.size >= this.maxConcurrency) {
return; if (runningNodePromises.size > 0) {
// 当上一个节点运行结束时,立即运行下一轮
await Promise.race(runningNodePromises);
} else {
// 理论上不应出现此情况,防御性退回到让出进程
await surrenderProcess();
}
continue;
} }
await surrenderProcess(); // 处理下一个节点
const nodeId = this.activeRunQueue.keys().next().value; const nodeId = this.activeRunQueue.keys().next().value;
const node = nodeId ? this.runtimeNodesMap.get(nodeId) : undefined; const node = nodeId ? this.runtimeNodesMap.get(nodeId) : undefined;
if (nodeId) { if (nodeId) {
this.activeRunQueue.delete(nodeId); this.activeRunQueue.delete(nodeId);
} }
if (node) {
this.runningNodeCount++;
this.checkNodeCanRun(node).finally(() => { if (node) {
this.runningNodeCount--; // 不再递归调用,异步执行节点(不等待完成)
this.processActiveNode(); const nodePromise: Promise<unknown> = this.checkNodeCanRun(node).finally(() => {
runningNodePromises.delete(nodePromise);
}); });
runningNodePromises.add(nodePromise);
}
}
} finally {
this.processingActive = false;
} }
} }
...@@ -453,17 +503,16 @@ export const runWorkflow = async (data: RunWorkflowProps): Promise<DispatchFlowR ...@@ -453,17 +503,16 @@ export const runWorkflow = async (data: RunWorkflowProps): Promise<DispatchFlowR
this.skipNodeQueue.set(node.nodeId, { node, skippedNodeIdList: concatSkippedNodeIdList }); this.skipNodeQueue.set(node.nodeId, { node, skippedNodeIdList: concatSkippedNodeIdList });
} }
// 迭代处理 skip 节点(每次只处理一个,然后返回主循环检查 active)
private async processSkipNodes() { private async processSkipNodes() {
// 取一个 node,并且从队列里删除
await surrenderProcess(); await surrenderProcess();
const skipItem = this.skipNodeQueue.values().next().value; const skipItem = this.skipNodeQueue.values().next().value;
if (skipItem) { if (skipItem) {
this.skipNodeQueue.delete(skipItem.node.nodeId); this.skipNodeQueue.delete(skipItem.node.nodeId);
this.checkNodeCanRun(skipItem.node, skipItem.skippedNodeIdList).finally(() => { await this.checkNodeCanRun(skipItem.node, skipItem.skippedNodeIdList).catch((error) => {
this.processActiveNode(); logger.error('Workflow skip node run error', { error, nodeName: skipItem.node.name });
}); });
} else {
this.processActiveNode();
} }
} }
...@@ -579,7 +628,7 @@ export const runWorkflow = async (data: RunWorkflowProps): Promise<DispatchFlowR ...@@ -579,7 +628,7 @@ export const runWorkflow = async (data: RunWorkflowProps): Promise<DispatchFlowR
// run module // run module
const dispatchRes: NodeResponseType = await (async () => { const dispatchRes: NodeResponseType = await (async () => {
if (callbackMap[node.flowNodeType]) { if (callbackMap[node.flowNodeType]) {
const targetEdges = runtimeEdges.filter((item) => item.source === node.nodeId); const targetEdges = this.edgeIndex.bySource.get(node.nodeId) || [];
const errorHandleId = getHandleId(node.nodeId, 'source_catch', 'right'); const errorHandleId = getHandleId(node.nodeId, 'source_catch', 'right');
try { try {
...@@ -848,9 +897,7 @@ export const runWorkflow = async (data: RunWorkflowProps): Promise<DispatchFlowR ...@@ -848,9 +897,7 @@ export const runWorkflow = async (data: RunWorkflowProps): Promise<DispatchFlowR
// Get next source edges and update status // Get next source edges and update status
const skipHandleId = result[DispatchNodeResponseKeyEnum.skipHandleId] || []; const skipHandleId = result[DispatchNodeResponseKeyEnum.skipHandleId] || [];
const targetEdges = filterWorkflowEdges(runtimeEdges).filter( const targetEdges = this.edgeIndex.bySource.get(node.nodeId) || [];
(item) => item.source === node.nodeId
);
// update edge status // update edge status
targetEdges.forEach((edge) => { targetEdges.forEach((edge) => {
...@@ -864,23 +911,20 @@ export const runWorkflow = async (data: RunWorkflowProps): Promise<DispatchFlowR ...@@ -864,23 +911,20 @@ export const runWorkflow = async (data: RunWorkflowProps): Promise<DispatchFlowR
// 同时可以去重 // 同时可以去重
const nextStepActiveNodesMap = new Map<string, RuntimeNodeItemType>(); const nextStepActiveNodesMap = new Map<string, RuntimeNodeItemType>();
const nextStepSkipNodesMap = new Map<string, RuntimeNodeItemType>(); const nextStepSkipNodesMap = new Map<string, RuntimeNodeItemType>();
runtimeNodes.forEach((node) => { targetEdges.forEach((edge) => {
if (targetEdges.some((item) => item.target === node.nodeId && item.status === 'active')) { const targetNode = this.runtimeNodesMap.get(edge.target);
nextStepActiveNodesMap.set(node.nodeId, node); if (!targetNode) return;
}
if ( if (edge.status === 'active') {
targetEdges.some((item) => item.target === node.nodeId && item.status === 'skipped') nextStepActiveNodesMap.set(targetNode.nodeId, targetNode);
) { } else if (edge.status === 'skipped') {
nextStepSkipNodesMap.set(node.nodeId, node); nextStepSkipNodesMap.set(targetNode.nodeId, targetNode);
} }
}); });
const nextStepActiveNodes = Array.from(nextStepActiveNodesMap.values());
const nextStepSkipNodes = Array.from(nextStepSkipNodesMap.values());
return { return {
nextStepActiveNodes, nextStepActiveNodes: Array.from(nextStepActiveNodesMap.values()),
nextStepSkipNodes nextStepSkipNodes: Array.from(nextStepSkipNodesMap.values())
}; };
}; };
...@@ -900,11 +944,6 @@ export const runWorkflow = async (data: RunWorkflowProps): Promise<DispatchFlowR ...@@ -900,11 +944,6 @@ export const runWorkflow = async (data: RunWorkflowProps): Promise<DispatchFlowR
return; return;
} }
logger.debug('Run workflow node', {
maxRunTimes: data.maxRunTimes,
appId: data.runningAppInfo.id
});
// Get node run status by edges // Get node run status by edges
const status = checkNodeRunStatus({ const status = checkNodeRunStatus({
nodesMap: this.runtimeNodesMap, nodesMap: this.runtimeNodesMap,
...@@ -1111,6 +1150,10 @@ export const runWorkflow = async (data: RunWorkflowProps): Promise<DispatchFlowR ...@@ -1111,6 +1150,10 @@ export const runWorkflow = async (data: RunWorkflowProps): Promise<DispatchFlowR
}); });
const workflowQueue = await new Promise<WorkflowQueue>((resolve) => { const workflowQueue = await new Promise<WorkflowQueue>((resolve) => {
logger.info('Workflow run start', {
maxRunTimes: data.maxRunTimes,
appId: data.runningAppInfo.id
});
const workflowQueue = new WorkflowQueue({ const workflowQueue = new WorkflowQueue({
resolve, resolve,
defaultSkipNodeQueue: data.lastInteractive?.skipNodeQueue || data.defaultSkipNodeQueue defaultSkipNodeQueue: data.lastInteractive?.skipNodeQueue || data.defaultSkipNodeQueue
......
...@@ -334,6 +334,7 @@ ...@@ -334,6 +334,7 @@
"publish_channel.wecom.empty": "Publish to WeCom bot. Please <a>bind a custom domain</a> and complete domain verification first.", "publish_channel.wecom.empty": "Publish to WeCom bot. Please <a>bind a custom domain</a> and complete domain verification first.",
"publish_success": "Publish Successful", "publish_success": "Publish Successful",
"question_guide_tip": "After the conversation, 3 guiding questions will be generated for you.", "question_guide_tip": "After the conversation, 3 guiding questions will be generated for you.",
"raw_params": "original parameters",
"reasoning_response": "Output thinking", "reasoning_response": "Output thinking",
"recharge": "Go to recharge", "recharge": "Go to recharge",
"reference_variable": "Reference variables", "reference_variable": "Reference variables",
......
...@@ -334,6 +334,7 @@ ...@@ -334,6 +334,7 @@
"publish_channel.wecom.empty": "发布到企业微信机器人,请先 <a>绑定自定义域名</a>,并且通过域名校验。", "publish_channel.wecom.empty": "发布到企业微信机器人,请先 <a>绑定自定义域名</a>,并且通过域名校验。",
"publish_success": "发布成功", "publish_success": "发布成功",
"question_guide_tip": "对话结束后,会为你生成 3 个引导性问题。", "question_guide_tip": "对话结束后,会为你生成 3 个引导性问题。",
"raw_params": "原始参数",
"reasoning_response": "输出思考", "reasoning_response": "输出思考",
"recharge": "去充值", "recharge": "去充值",
"reference_variable": "引用变量", "reference_variable": "引用变量",
......
...@@ -319,6 +319,7 @@ ...@@ -319,6 +319,7 @@
"publish_channel": "發布通道", "publish_channel": "發布通道",
"publish_success": "發布成功", "publish_success": "發布成功",
"question_guide_tip": "對話結束後,會為你產生 3 個引導性問題。", "question_guide_tip": "對話結束後,會為你產生 3 個引導性問題。",
"raw_params": "原始參數",
"reasoning_response": "輸出思考", "reasoning_response": "輸出思考",
"recharge": "去充值", "recharge": "去充值",
"reference_variable": "引用變量", "reference_variable": "引用變量",
......
{ {
"name": "app", "name": "app",
"version": "4.14.8.1", "version": "4.14.8.4",
"private": false, "private": false,
"scripts": { "scripts": {
"dev": "npm run build:workers && next dev", "dev": "npm run build:workers && next dev",
......
...@@ -32,6 +32,7 @@ const getFlattenedErrorKeys = (errors: any, prefix = ''): string[] => { ...@@ -32,6 +32,7 @@ const getFlattenedErrorKeys = (errors: any, prefix = ''): string[] => {
const LabelAndFormRender = ({ const LabelAndFormRender = ({
label, label,
required, required,
description,
placeholder, placeholder,
inputType, inputType,
showValueType, showValueType,
...@@ -40,6 +41,7 @@ const LabelAndFormRender = ({ ...@@ -40,6 +41,7 @@ const LabelAndFormRender = ({
}: { }: {
label: string | React.ReactNode; label: string | React.ReactNode;
required?: boolean; required?: boolean;
description?: string;
placeholder?: string; placeholder?: string;
showValueType?: boolean; showValueType?: boolean;
form: UseFormReturn<any>; form: UseFormReturn<any>;
...@@ -57,7 +59,7 @@ const LabelAndFormRender = ({ ...@@ -57,7 +59,7 @@ const LabelAndFormRender = ({
<Box _notLast={{ mb: 4 }}> <Box _notLast={{ mb: 4 }}>
<Flex alignItems={'center'} mb={1}> <Flex alignItems={'center'} mb={1}>
{typeof label === 'string' ? <FormLabel required={required}>{t(label)}</FormLabel> : label} {typeof label === 'string' ? <FormLabel required={required}>{t(label)}</FormLabel> : label}
{placeholder && <QuestionTip ml={1} label={placeholder} />} {description && <QuestionTip ml={1} label={description} />}
</Flex> </Flex>
<Controller <Controller
......
...@@ -107,7 +107,7 @@ const VariableInputForm = ({ ...@@ -107,7 +107,7 @@ const VariableInputForm = ({
{...item} {...item}
isUnChange={isUnChange} isUnChange={isUnChange}
key={item.key} key={item.key}
placeholder={item.description} description={item.description}
inputType={variableInputTypeToInputType(item.type, item.valueType)} inputType={variableInputTypeToInputType(item.type, item.valueType)}
form={variablesForm} form={variablesForm}
fieldName={`variables.${item.key}`} fieldName={`variables.${item.key}`}
...@@ -150,7 +150,7 @@ const VariableInputForm = ({ ...@@ -150,7 +150,7 @@ const VariableInputForm = ({
{...item} {...item}
isUnChange={isUnChange} isUnChange={isUnChange}
key={item.key} key={item.key}
placeholder={item.description} description={item.description}
inputType={variableInputTypeToInputType(item.type, item.valueType)} inputType={variableInputTypeToInputType(item.type, item.valueType)}
form={variablesForm} form={variablesForm}
fieldName={`variables.${item.key}`} fieldName={`variables.${item.key}`}
...@@ -192,7 +192,7 @@ const VariableInputForm = ({ ...@@ -192,7 +192,7 @@ const VariableInputForm = ({
{...item} {...item}
isUnChange={isUnChange} isUnChange={isUnChange}
key={item.key} key={item.key}
placeholder={item.description} description={item.description}
inputType={variableInputTypeToInputType(item.type)} inputType={variableInputTypeToInputType(item.type)}
bg={'myGray.50'} bg={'myGray.50'}
form={variablesForm} form={variablesForm}
......
...@@ -47,7 +47,7 @@ const ChatHomeVariablesForm = ({ chatForm }: Props) => { ...@@ -47,7 +47,7 @@ const ChatHomeVariablesForm = ({ chatForm }: Props) => {
{...item} {...item}
key={item.key} key={item.key}
fieldName={`variables.${item.key}`} fieldName={`variables.${item.key}`}
placeholder={item.description} description={item.description}
inputType={variableInputTypeToInputType(item.type, item.valueType)} inputType={variableInputTypeToInputType(item.type, item.valueType)}
form={variablesForm} form={variablesForm}
bg={'myGray.50'} bg={'myGray.50'}
...@@ -63,7 +63,7 @@ const ChatHomeVariablesForm = ({ chatForm }: Props) => { ...@@ -63,7 +63,7 @@ const ChatHomeVariablesForm = ({ chatForm }: Props) => {
{...item} {...item}
key={item.key} key={item.key}
fieldName={`variables.${item.key}`} fieldName={`variables.${item.key}`}
placeholder={item.description} description={item.description}
inputType={variableInputTypeToInputType(item.type)} inputType={variableInputTypeToInputType(item.type)}
form={variablesForm} form={variablesForm}
bg={'myGray.50'} bg={'myGray.50'}
......
...@@ -104,7 +104,7 @@ const VariablePopover = ({ chatType }: { chatType: ChatTypeEnum }) => { ...@@ -104,7 +104,7 @@ const VariablePopover = ({ chatType }: { chatType: ChatTypeEnum }) => {
<LabelAndFormRender <LabelAndFormRender
{...item} {...item}
key={item.key} key={item.key}
placeholder={item.description} description={item.description}
inputType={variableInputTypeToInputType(item.type)} inputType={variableInputTypeToInputType(item.type)}
form={variablesForm} form={variablesForm}
fieldName={`variables.${item.key}`} fieldName={`variables.${item.key}`}
...@@ -137,7 +137,7 @@ const VariablePopover = ({ chatType }: { chatType: ChatTypeEnum }) => { ...@@ -137,7 +137,7 @@ const VariablePopover = ({ chatType }: { chatType: ChatTypeEnum }) => {
<LabelAndFormRender <LabelAndFormRender
{...item} {...item}
key={item.key} key={item.key}
placeholder={item.description} description={item.description}
inputType={variableInputTypeToInputType(item.type)} inputType={variableInputTypeToInputType(item.type)}
form={variablesForm} form={variablesForm}
fieldName={`variables.${item.key}`} fieldName={`variables.${item.key}`}
...@@ -156,7 +156,7 @@ const VariablePopover = ({ chatType }: { chatType: ChatTypeEnum }) => { ...@@ -156,7 +156,7 @@ const VariablePopover = ({ chatType }: { chatType: ChatTypeEnum }) => {
<LabelAndFormRender <LabelAndFormRender
{...item} {...item}
key={item.key} key={item.key}
placeholder={item.description} description={item.description}
inputType={variableInputTypeToInputType(item.type)} inputType={variableInputTypeToInputType(item.type)}
form={variablesForm} form={variablesForm}
fieldName={`variables.${item.key}`} fieldName={`variables.${item.key}`}
......
...@@ -147,7 +147,7 @@ const ChatTest = ({ ...@@ -147,7 +147,7 @@ const ChatTest = ({
inputType={inputType} inputType={inputType}
fieldName={paramName} fieldName={paramName}
form={form} form={form}
placeholder={paramName} description={paramName}
/> />
); );
} }
......
...@@ -130,7 +130,8 @@ const ChatTest = ({ ...@@ -130,7 +130,8 @@ const ChatTest = ({
inputType={inputType} inputType={inputType}
form={form} form={form}
fieldName={paramName} fieldName={paramName}
placeholder={paramInfo.description} bg={'myGray.50'}
description={paramInfo.description}
/> />
); );
} }
......
...@@ -15,6 +15,7 @@ import type { GetMcpToolsBodyType } from '@fastgpt/global/openapi/core/app/mcpTo ...@@ -15,6 +15,7 @@ import type { GetMcpToolsBodyType } from '@fastgpt/global/openapi/core/app/mcpTo
import { getMCPTools } from '@/web/core/app/api/tool'; import { getMCPTools } from '@/web/core/app/api/tool';
import HeaderAuthConfig from '@/components/common/secret/HeaderAuthConfig'; import HeaderAuthConfig from '@/components/common/secret/HeaderAuthConfig';
import { type StoreSecretValueType } from '@fastgpt/global/common/secret/type'; import { type StoreSecretValueType } from '@fastgpt/global/common/secret/type';
import type { JsonSchemaPropertiesItemType } from '@fastgpt/global/core/app/jsonschema';
const EditForm = ({ const EditForm = ({
url, url,
...@@ -208,25 +209,30 @@ const ToolDetailModal = ({ tool, onClose }: { tool: McpToolConfigType; onClose: ...@@ -208,25 +209,30 @@ const ToolDetailModal = ({ tool, onClose }: { tool: McpToolConfigType; onClose:
w={'530px'} w={'530px'}
> >
<ModalBody> <ModalBody>
<Flex pb={6} borderBottom={'1px solid'} borderColor={'myGray.200'}> <Flex
pb={6}
borderBottom={'1px solid'}
borderColor={'myGray.200'}
alignItems={'flex-start'}
>
<Avatar src={appDetail.avatar} borderRadius={'md'} w={'40px'} /> <Avatar src={appDetail.avatar} borderRadius={'md'} w={'40px'} />
<Box ml={'14px'}> <Box ml={'14px'}>
<Box fontSize={'16px'} color={'myGray.900'}> <Box fontSize={'16px'} color={'myGray.900'}>
{tool.name} {tool.name}
</Box> </Box>
<Box fontSize={'12px'} color={'myGray.500'}> <Box fontSize={'12px'} color={'myGray.500'} maxH={'100px'} overflow={'auto'}>
{tool.description} {tool.description}
</Box> </Box>
</Box> </Box>
</Flex> </Flex>
<Box mt={6} color={'myGray.900'} fontWeight={'medium'}> <Box mt={6} color={'myGray.900'} fontWeight={'medium'}>
{t('common:Params')} {t('app:raw_params')}
</Box> </Box>
<Box mt={3}> <Box mt={3}>
{Object.entries(tool.inputSchema.properties || {}).map( {Object.entries(tool.inputSchema.properties || {}).map(
([paramName, paramInfo]: [string, any]) => ( ([paramName, paramInfo]: [string, JsonSchemaPropertiesItemType]) => (
<Box key={paramName} py={2} borderBottom={'1px solid'} borderColor={'myGray.150'}> <Box key={paramName} py={2} borderBottom={'1px solid'} borderColor={'myGray.150'}>
<Flex alignItems="center"> <Flex alignItems="center">
{tool.inputSchema.required?.includes(paramName) && ( {tool.inputSchema.required?.includes(paramName) && (
...@@ -248,7 +254,11 @@ const ToolDetailModal = ({ tool, onClose }: { tool: McpToolConfigType; onClose: ...@@ -248,7 +254,11 @@ const ToolDetailModal = ({ tool, onClose }: { tool: McpToolConfigType; onClose:
border={'1px solid'} border={'1px solid'}
borderColor={'myGray.200'} borderColor={'myGray.200'}
> >
{paramInfo.type} {paramInfo.type ||
paramInfo.anyOf?.map((item) => item.type).join(',') ||
paramInfo.oneOf?.map((item) => item.type).join(',') ||
paramInfo.allOf?.map((item) => item.type).join(',') ||
'any'}
</Box> </Box>
</Flex> </Flex>
......
...@@ -269,7 +269,7 @@ export const useDebug = () => { ...@@ -269,7 +269,7 @@ export const useDebug = () => {
key={item.key} key={item.key}
label={item.label} label={item.label}
required={item.required} required={item.required}
placeholder={t(item.placeholder || item.description)} description={t(item.placeholder || item.description)}
inputType={nodeInputTypeToInputType(item.renderTypeList)} inputType={nodeInputTypeToInputType(item.renderTypeList)}
form={variablesForm} form={variablesForm}
fieldName={`nodeVariables.${item.key}`} fieldName={`nodeVariables.${item.key}`}
...@@ -284,7 +284,7 @@ export const useDebug = () => { ...@@ -284,7 +284,7 @@ export const useDebug = () => {
key={item.key} key={item.key}
label={item.label} label={item.label}
required={item.required} required={item.required}
placeholder={t(item.description)} description={t(item.description)}
inputType={variableInputTypeToInputType(item.type)} inputType={variableInputTypeToInputType(item.type)}
form={variablesForm} form={variablesForm}
fieldName={`variables.${item.key}`} fieldName={`variables.${item.key}`}
...@@ -297,7 +297,7 @@ export const useDebug = () => { ...@@ -297,7 +297,7 @@ export const useDebug = () => {
key={item.key} key={item.key}
label={item.label} label={item.label}
required={item.required} required={item.required}
placeholder={t(item.description)} description={t(item.description)}
inputType={variableInputTypeToInputType(item.type)} inputType={variableInputTypeToInputType(item.type)}
form={variablesForm} form={variablesForm}
fieldName={`variables.${item.key}`} fieldName={`variables.${item.key}`}
...@@ -310,7 +310,7 @@ export const useDebug = () => { ...@@ -310,7 +310,7 @@ export const useDebug = () => {
key={item.key} key={item.key}
label={item.label} label={item.label}
required={item.required} required={item.required}
placeholder={item.description} description={item.description}
inputType={variableInputTypeToInputType(item.type)} inputType={variableInputTypeToInputType(item.type)}
form={variablesForm} form={variablesForm}
fieldName={`variables.${item.key}`} fieldName={`variables.${item.key}`}
......
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