perf: helperbot ui

This commit is contained in:
archer 2025-12-23 18:00:20 +08:00
parent 1f100276f6
commit 363f9b888a
No known key found for this signature in database
GPG Key ID: 4446499B846D4A9E
25 changed files with 631 additions and 316 deletions

View File

@ -10,11 +10,9 @@ import type { HelperBotChatItemType } from './type';
import { simpleUserContentPart } from '../adapt';
export const helperChats2GPTMessages = ({
messages,
reserveTool = false
messages
}: {
messages: HelperBotChatItemType[];
reserveTool?: boolean;
}): ChatCompletionMessageParam[] => {
let results: ChatCompletionMessageParam[] = [];
@ -66,29 +64,25 @@ export const helperChats2GPTMessages = ({
//AI: 只需要把根节点转化即可
item.value.forEach((value, i) => {
if ('tool' in value && reserveTool) {
const tool_calls: ChatCompletionMessageToolCall[] = [
{
id: value.tool.id,
type: 'function',
function: {
name: value.tool.functionName,
arguments: value.tool.params
}
}
];
const toolResponse: ChatCompletionToolMessageParam[] = [
{
tool_call_id: value.tool.id,
role: ChatCompletionRequestMessageRoleEnum.Tool,
content: value.tool.response
}
];
aiResults.push({
role: ChatCompletionRequestMessageRoleEnum.Assistant,
tool_calls
});
aiResults.push(...toolResponse);
if ('collectionForm' in value) {
const text = JSON.stringify(
value.collectionForm.params.inputForm.map((item) => ({
label: item.label,
type: item.type
}))
);
// Concat text
const lastValue = item.value[i - 1];
const lastResult = aiResults[aiResults.length - 1];
if (lastValue && typeof lastResult?.content === 'string') {
lastResult.content += text;
} else {
aiResults.push({
role: ChatCompletionRequestMessageRoleEnum.Assistant,
content: text
});
}
} else if ('text' in value && typeof value.text?.content === 'string') {
if (!value.text.content && item.value.length > 1) {
return;

View File

@ -2,6 +2,7 @@ import { ObjectIdSchema } from '../../../common/type/mongo';
import { z } from 'zod';
import { ChatRoleEnum } from '../constants';
import { UserChatItemSchema, SystemChatItemSchema, ToolModuleResponseItemSchema } from '../type';
import { UserInputInteractiveSchema } from '../../workflow/template/system/interactive/type';
export enum HelperBotTypeEnum {
topAgent = 'topAgent',
@ -34,7 +35,7 @@ export const AIChatItemValueItemSchema = z.union([
})
}),
z.object({
tool: ToolModuleResponseItemSchema
collectionForm: UserInputInteractiveSchema
})
]);
export type AIChatItemValueItemType = z.infer<typeof AIChatItemValueItemSchema>;

View File

@ -19,7 +19,9 @@ export enum SseResponseEventEnum {
agentPlan = 'agentPlan', // agent plan
formData = 'formData', // form data for TopAgent
// Helperbot
collectionForm = 'collectionForm', // collection form for HelperBot
topAgentConfig = 'topAgentConfig', // form data for TopAgent
generatedSkill = 'generatedSkill' // generated skill for SkillAgent
}

View File

@ -1,4 +1,8 @@
import type { HelperBotDispatchParamsType, HelperBotDispatchResponseType } from '../type';
import {
AICollectionAnswerSchema,
type HelperBotDispatchParamsType,
type HelperBotDispatchResponseType
} from '../type';
import { helperChats2GPTMessages } from '@fastgpt/global/core/chat/helperBot/adaptor';
import { getPrompt } from './prompt';
import { createLLMResponse } from '../../../../ai/llm/request';
@ -6,10 +10,17 @@ import { getLLMModel } from '../../../../ai/model';
import { SseResponseEventEnum } from '@fastgpt/global/core/workflow/runtime/constants';
import { textAdaptGptResponse } from '@fastgpt/global/core/workflow/runtime/utils';
import { generateResourceList } from './utils';
import { TopAgentFormDataSchema } from './type';
import { TopAgentAnswerSchema, TopAgentFormDataSchema } from './type';
import { addLog } from '../../../../../common/system/log';
import { formatAIResponse } from '../utils';
import type { TopAgentParamsType } from '@fastgpt/global/core/chat/helperBot/topAgent/type';
import type {
UserInputFormItemType,
UserInputInteractive
} from '@fastgpt/global/core/workflow/template/system/interactive/type';
import { getNanoid } from '@fastgpt/global/common/string/tools';
import { WorkflowIOValueTypeEnum } from '@fastgpt/global/core/workflow/constants';
import { FlowNodeInputTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
export const dispatchTopAgent = async (
props: HelperBotDispatchParamsType<TopAgentParamsType>
@ -37,27 +48,20 @@ export const dispatchTopAgent = async (
});
const historyMessages = helperChats2GPTMessages({
messages: histories,
reserveTool: false
messages: histories
});
const conversationMessages = [
{ role: 'system' as const, content: systemPrompt },
...historyMessages,
{ role: 'user' as const, content: query }
];
console.log(JSON.stringify(conversationMessages, null, 2));
const llmResponse = await createLLMResponse({
body: {
messages: conversationMessages,
model: modelData,
stream: true
},
onStreaming: ({ text }) => {
workflowResponseWrite?.({
event: SseResponseEventEnum.answer,
data: textAdaptGptResponse({ text })
});
},
onReasoning: ({ text }) => {
workflowResponseWrite?.({
event: SseResponseEventEnum.answer,
@ -71,9 +75,9 @@ export const dispatchTopAgent = async (
const answerText = llmResponse.answerText;
const reasoningText = llmResponse.reasoningText;
console.log('Top agent response:', answerText);
try {
const responseJson = JSON.parse(answerText);
const responseJson = TopAgentAnswerSchema.parse(JSON.parse(answerText));
if (responseJson.phase === 'generation') {
addLog.debug('🔄 TopAgent: Configuration generation phase');
@ -82,12 +86,12 @@ export const dispatchTopAgent = async (
role: responseJson.task_analysis?.role,
taskObject: responseJson.task_analysis?.goal,
tools: responseJson.resources?.tools?.map((tool: any) => tool.id),
fileUploadEnabled: responseJson.resources?.system_features?.file_upload?.enabled || false
fileUploadEnabled: responseJson.resources?.file_upload?.enabled
});
if (formData) {
workflowResponseWrite?.({
event: SseResponseEventEnum.formData,
event: SseResponseEventEnum.topAgentConfig,
data: formData
});
}
@ -102,16 +106,60 @@ export const dispatchTopAgent = async (
} else if (responseJson.phase === 'collection') {
addLog.debug('📝 TopAgent: Information collection phase');
const displayText = responseJson.question || answerText;
const formDeata = responseJson.form;
if (formDeata) {
const inputForm: UserInputInteractive = {
type: 'userInput',
params: {
inputForm: formDeata.map((item) => {
return {
type: item.type as FlowNodeInputTypeEnum,
key: getNanoid(6),
label: item.label,
value: '',
required: false,
valueType:
item.type === FlowNodeInputTypeEnum.numberInput
? WorkflowIOValueTypeEnum.number
: WorkflowIOValueTypeEnum.string,
list:
'options' in item
? item.options?.map((option) => ({ label: option, value: option }))
: undefined
};
}),
description: responseJson.question
}
};
workflowResponseWrite?.({
event: SseResponseEventEnum.collectionForm,
data: inputForm
});
return {
aiResponse: formatAIResponse({
text: responseJson.question,
reasoning: reasoningText,
collectionForm: inputForm
}),
usage
};
}
workflowResponseWrite?.({
event: SseResponseEventEnum.answer,
data: textAdaptGptResponse({ text: responseJson.question })
});
return {
aiResponse: formatAIResponse({
text: displayText,
reasoning: responseJson.reasoning || reasoningText
text: responseJson.question,
reasoning: reasoningText
}),
usage
};
} else {
addLog.warn(`[Top agent] Unknown phase: ${responseJson.phase}`);
addLog.warn(`[Top agent] Unknown phase`, responseJson);
return {
aiResponse: formatAIResponse({
text: answerText,

View File

@ -102,20 +102,50 @@ ${buildMetadataInfo(metadata)}
"question": "实际向用户提出的问题内容"
}
{
"phase": "collection",
"reasoning": "需要首先了解任务的基本定位和目标场景,这将决定后续需要确认的工具类型和能力边界",
"question": "我想了解一下您希望这个流程模板实现什么功能?能否详细描述一下具体要处理什么样的任务或问题?"
}
4
{
"phase": "collection",
"reasoning": "需要确认参数化设计的重点方向,这将影响流程模板的灵活性设计",
"question": "关于流程的参数化设计,用户最需要调整的是:\\nA. 输入数据源(不同类型的数据库/文件)\\nB. 处理参数(阈值、过滤条件、算法选择)\\nC. 输出格式(报告类型、文件格式、目标系统)\\nD. 执行环境(触发方式、频率、并发度)\\n\\n请选择最符合的选项或输入您的详细回答"
"question": "我需要和你确认一些参数,请根据你的需求选择对应的选项:",
"form": [
{
"type": "input",
"label": "请输入你想优化的方向"
},
{
"type": "numberInput",
"label": "你想优化多少次"
},
{
"type": "select",
"label": "关于流程的参数化设计,用户最需要调整的是",
"options": [
"输入数据源(不同类型的数据库/文件)",
"处理参数(阈值、过滤条件、算法选择)",
"输出格式(报告类型、文件格式、目标系统)",
"执行环境(触发方式、频率、并发度)"
]
},
{
"type": "multipleSelect",
"label": "你想了解用户什么信息",
"options": [
"选项 A",
"选项 B",
"选项 C",
"选项 D"
]
}
]
}
@ -277,24 +307,23 @@ ${resourceList}
JSON
{
"phase": "generation",
"reasoning": "详细说明所有资源的选择理由:工具、知识库和系统功能如何协同工作来完成任务目标",
"task_analysis": {
"goal": "任务的核心目标描述",
"role": "该流程的角色信息",
"key_features": "收集到的信息,对任务的深度理解和定位"
},
"reasoning": "详细说明所有资源的选择理由:工具、知识库和系统功能如何协同工作来完成任务目标",
"resources": {
"tools": [
{"id": "工具ID", "type": "tool"}
"工具ID"
],
"knowledges": [
{"id": "知识库ID", "type": "knowledge"}
"知识库ID"
],
"system_features": {
"file_upload": {
"enabled": true/false,
"purpose": "说明原因enabled=true时必填",
"file_types": ["可选的文件类型"]
"purpose": "说明原因enabled=true时必填"
}
}
}
@ -309,7 +338,6 @@ ${resourceList}
* system_features: 系统功能配置对象
- file_upload.enabled: 是否需要文件上传
- file_upload.purpose: 为什么需要enabled=true时必填
- file_upload.file_types: 建议的文件类型["pdf", "xlsx"]
** 1**
{
@ -321,14 +349,13 @@ ${resourceList}
"reasoning": "使用数据分析工具处理Excel数据需要用户上传自己的财务报表文件",
"resources": {
"tools": [
{"id": "data_analysis/tool", "type": "tool"}
"data_analysis/tool"
],
"knowledges": [],
"system_features": {
"file_upload": {
"enabled": true,
"purpose": "需要您上传财务报表文件Excel或PDF格式进行数据提取和分析",
"file_types": ["xlsx", "xls", "pdf"]
"purpose": "需要您上传财务报表文件Excel或PDF格式进行数据提取和分析"
}
}
}
@ -340,10 +367,10 @@ ${resourceList}
"reasoning": "使用搜索工具获取实时信息,结合知识库的专业知识",
"resources": {
"tools": [
{"id": "metaso/metasoSearch", "type": "tool"}
"metaso/metasoSearch"
],
"knowledges": [
{"id": "travel_kb", "type": "knowledge"}
"travel_kb"
],
"system_features": {
"file_upload": {
@ -374,9 +401,9 @@ ${resourceList}
{
"resources": {
"tools": [
{"id": "bing/webSearch", "type": "tool"},
{"id": "google/search", "type": "tool"},
{"id": "metaso/metasoSearch", "type": "tool"}
"bing/webSearch",
"google/search",
"metaso/metasoSearch"
// ❌ 错误:这三个都是网页搜索工具,只应该选择一个最合适的
]
}
@ -437,7 +464,7 @@ ${resourceList}
<conversation_rules>
****
- ** JSON ** \`phase\` 字段
- \`{"phase": "collection", "reasoning": "...", "question": "..."}\`
- \`{"phase": "collection", "reasoning": "...", "question": "...","form":[...]}\`
- \`{"phase": "generation", "task_analysis": {...}, "resources": {...}, ...}\`
- JSON
- \\\`\\\`\\\`json

View File

@ -1,4 +1,5 @@
import { z } from 'zod';
import { AICollectionAnswerSchema } from '../type';
export const TopAgentFormDataSchema = z.object({
role: z.string().optional(),
@ -7,3 +8,31 @@ export const TopAgentFormDataSchema = z.object({
fileUploadEnabled: z.boolean().optional().default(false)
});
export type TopAgentFormDataType = z.infer<typeof TopAgentFormDataSchema>;
// 表单收集
export const TopAgentCollectionAnswerSchema = AICollectionAnswerSchema.extend({
phase: z.literal('collection'),
reasoning: z.string().nullish()
});
export const TopAgentGenerationAnswerSchema = z.object({
phase: z.literal('generation'),
reasoning: z.string().nullish(),
task_analysis: z.object({
goal: z.string(),
role: z.string(),
key_features: z.string()
}),
resources: z.object({
tools: z.array(z.string()),
knowledges: z.array(z.string()),
file_upload: z.object({
enabled: z.boolean(),
purpose: z.string()
})
})
});
export const TopAgentAnswerSchema = z.discriminatedUnion('phase', [
TopAgentCollectionAnswerSchema,
TopAgentGenerationAnswerSchema
]);
export type TopAgentAnswerType = z.infer<typeof TopAgentAnswerSchema>;

View File

@ -6,6 +6,7 @@ import {
} from '@fastgpt/global/core/chat/helperBot/type';
import { WorkflowResponseFnSchema } from '../../../workflow/dispatch/type';
import { LocaleList } from '@fastgpt/global/common/i18n/type';
import { FlowNodeInputTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
export const HelperBotDispatchParamsSchema = z.object({
query: z.string(),
@ -40,3 +41,19 @@ export const HelperBotDispatchResponseSchema = z.object({
})
});
export type HelperBotDispatchResponseType = z.infer<typeof HelperBotDispatchResponseSchema>;
/* AI 表单输出 schema */
const InputSchema = z.object({
type: z.enum([FlowNodeInputTypeEnum.input, FlowNodeInputTypeEnum.numberInput]),
label: z.string()
});
const SelectSchema = z.object({
type: z.enum([FlowNodeInputTypeEnum.select, FlowNodeInputTypeEnum.multipleSelect]),
label: z.string(),
options: z.array(z.string())
});
export const AICollectionAnswerSchema = z.object({
question: z.string(), // 可能只有一个问题,可能
form: z.array(z.union([InputSchema, SelectSchema])).optional()
});
export type AICollectionAnswerType = z.infer<typeof AICollectionAnswerSchema>;

View File

@ -1,11 +1,14 @@
import type { AIChatItemValueItemType } from '@fastgpt/global/core/chat/helperBot/type';
import type { UserInputInteractive } from '@fastgpt/global/core/workflow/template/system/interactive/type';
export const formatAIResponse = ({
text,
reasoning
reasoning,
collectionForm
}: {
text: string;
reasoning?: string;
collectionForm?: UserInputInteractive;
}): AIChatItemValueItemType[] => {
const result: AIChatItemValueItemType[] = [];
@ -23,5 +26,11 @@ export const formatAIResponse = ({
}
});
if (collectionForm) {
result.push({
collectionForm
});
}
return result;
};

View File

@ -38,7 +38,6 @@ import { MaxLengthPlugin } from './plugins/MaxLengthPlugin';
import { VariableLabelNode } from './plugins/VariableLabelPlugin/node';
import VariableLabelPlugin from './plugins/VariableLabelPlugin';
import { useDeepCompareEffect } from 'ahooks';
import VariablePickerPlugin from './plugins/VariablePickerPlugin';
import MarkdownPlugin from './plugins/MarkdownPlugin';
import MyIcon from '../../Icon';
import ListExitPlugin from './plugins/ListExitPlugin';
@ -48,7 +47,6 @@ import type { SkillLabelItemType } from './plugins/SkillLabelPlugin';
import SkillLabelPlugin from './plugins/SkillLabelPlugin';
import { SkillNode } from './plugins/SkillLabelPlugin/node';
import type { SkillOptionItemType } from './plugins/SkillPickerPlugin';
import { FlowNodeTemplateType } from '@fastgpt/global/core/workflow/type/node';
const Placeholder = ({ children, padding }: { children: React.ReactNode; padding: string }) => (
<Box

View File

@ -7,11 +7,11 @@ import { mergeRegister } from '@lexical/utils';
import { registerLexicalTextEntity } from '../../utils';
import type { FlowNodeTemplateType } from '@fastgpt/global/core/workflow/type/node';
import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
import type { SelectedToolItemType } from '@fastgpt/global/core/app/formEdit/type';
const REGEX = new RegExp(getSkillRegexString(), 'i');
export type SkillLabelItemType = FlowNodeTemplateType & {
configStatus: 'active' | 'invalid' | 'waitingForConfig';
export type SkillLabelItemType = SelectedToolItemType & {
tooltip?: string;
};

View File

@ -17,7 +17,7 @@ import {
import React, { useState } from 'react';
import ReactDOM from 'react-dom';
import { useCallback, useEffect, useRef, useMemo } from 'react';
import { Box, Flex } from '@chakra-ui/react';
import { Box, Flex, HStack } from '@chakra-ui/react';
import { useBasicTypeaheadTriggerMatch } from '../../utils';
import Avatar from '../../../../Avatar';
import MyIcon from '../../../../Icon';
@ -27,6 +27,10 @@ import { useRequest2 } from '../../../../../../hooks/useRequest';
import type { ParentIdType } from '@fastgpt/global/common/parentFolder/type';
import { useTranslation } from 'next-i18next';
/*
tool/toolset
*/
export type SkillOptionItemType = {
description?: string;
list: SkillItemType[];
@ -40,16 +44,20 @@ export type SkillItemType = {
id: string;
label: string;
icon?: string;
description?: string;
canClick: boolean;
children?: SkillOptionItemType;
// Folder
open?: boolean;
isFolder?: boolean;
folderChildren?: SkillItemType[];
// System tool/ model
showArrow?: boolean;
children?: SkillOptionItemType;
// Toolset
tools?: {
id: string;
name: string;
}[];
};
export default function SkillPickerPlugin({
@ -558,6 +566,12 @@ export default function SkillPickerPlugin({
getFlattenedVisibleItems
]);
const selectedTool = useMemo(() => {
const item = skillOptions[currentColumnIndex]?.list[currentRowIndex];
if (!item || !item.canClick) return null;
return item;
}, [skillOptions, currentColumnIndex, currentRowIndex]);
// Recursively render item list
const renderItemList = useCallback(
(
@ -661,8 +675,8 @@ export default function SkillPickerPlugin({
) : columnData.onFolderLoad ? (
<Box w={3} flexShrink={0} />
) : null}
{item.icon && <Avatar src={item.icon} w={'1.2rem'} borderRadius={'xs'} />}
{item.icon && <Avatar src={item.icon} w={'1.2rem'} borderRadius={'xs'} />}
{/* Folder content */}
<Box fontSize={'sm'} fontWeight={'medium'} flex={'1 0 0'} className="textEllipsis">
{item.label}
@ -672,9 +686,6 @@ export default function SkillPickerPlugin({
</Box>
)}
</Box>
{item.showArrow && (
<MyIcon name={'core/chat/chevronRight'} w={'0.8rem'} color={'myGray.400'} />
)}
</MyBox>
);
@ -779,8 +790,12 @@ export default function SkillPickerPlugin({
// Insert skill node text at current selection
selection.insertNodes([$createTextNode(`{{@${skillId}@}}`)]);
closeMenu();
});
// Close menu after editor update to avoid flushSync warning
setTimeout(() => {
closeMenu();
}, 0);
} else {
// If onClick didn't return a skillId, just close the menu
closeMenu();
@ -821,6 +836,63 @@ export default function SkillPickerPlugin({
{skillOptions.map((column, index) => {
return renderColumn(column, index);
})}
{selectedTool && (
<Box
ml={2}
p={2.5}
borderRadius={'sm'}
w={'200px'}
boxShadow={
'0 4px 10px 0 rgba(19, 51, 107, 0.10), 0 0 1px 0 rgba(19, 51, 107, 0.10)'
}
bg={'white'}
flexShrink={0}
maxH={'350px'}
overflow={'auto'}
>
<HStack>
{selectedTool.icon && (
<Avatar src={selectedTool.icon} w={'1.3rem'} borderRadius={'xs'} />
)}
{/* Folder content */}
<Box fontSize={'sm'} fontWeight={'medium'}>
{selectedTool.label}
</Box>
</HStack>
<Box color={'myGray.500'} fontSize={'xs'} className="textEllipsis3">
{selectedTool.description || t('app:tool_not_desc')}
</Box>
{/* Tools */}
{selectedTool.tools && selectedTool.tools.length > 0 && (
<>
<Box mt={2} color={'myGray.900'} fontSize={'sm'}>
{t('app:tools')}({selectedTool.tools.length})
</Box>
{selectedTool.tools.map((tool) => (
<Box
key={tool.id}
mt={1}
fontSize={'xs'}
color={'myGray.600'}
display={'flex'}
alignItems={'center'}
gap={1.5}
>
<Box
w={'6px'}
h={'6px'}
borderRadius={'50%'}
bg={'primary.600'}
flexShrink={0}
/>
{tool.name}
</Box>
))}
</>
)}
</Box>
)}
</Flex>,
anchorElementRef.current!
);

View File

@ -57,8 +57,12 @@ export default function VariableLabelPickerPlugin({
selection.insertNodes([
$createTextNode(`{{$${selectedOption.parent?.id}.${selectedOption.key}$}}`)
]);
closeMenu();
});
// Close menu after editor update to avoid flushSync warning
setTimeout(() => {
closeMenu();
}, 0);
},
[editor]
);

View File

@ -35,8 +35,12 @@ export default function VariablePickerPlugin({
nodeToRemove.remove();
}
selection.insertNodes([$createTextNode(`{{${selectedOption.key}}}`)]);
closeMenu();
});
// Close menu after editor update to avoid flushSync warning
setTimeout(() => {
closeMenu();
}, 0);
},
[editor]
);

View File

@ -58,7 +58,6 @@
"auto_execute_default_prompt_placeholder": "Default questions sent when executing automatically",
"auto_execute_tip": "After turning it on, the workflow will be automatically triggered when the user enters the conversation interface. \nExecution order: 1. Dialogue starter; 2. Global variables; 3. Automatic execution.",
"auto_save": "Auto save",
"can_select_toolset": "Entire toolset available for selection",
"change_app_type": "Change App Type",
"chat_agent_intro": "AI autonomously plans executable processes",
"chat_debug": "Chat Preview",
@ -392,6 +391,7 @@
"tool_detail": "Tool details",
"tool_input_param_tip": "This tool requires configuration of relevant information for normal operation.",
"tool_not_active": "This tool has not been activated yet",
"tool_not_desc": "The tool lacks a description ~",
"tool_offset_tips": "This tool is no longer available and will interrupt application operation. Please replace it immediately.",
"tool_param_config": "Parameter configuration",
"tool_params_description_tips": "The description of parameter functions, if used as tool invocation parameters, affects the model tool invocation effect.",

View File

@ -60,7 +60,6 @@
"auto_execute_default_prompt_placeholder": "自动执行时,发送的默认问题",
"auto_execute_tip": "开启后用户进入对话界面将自动触发工作流。执行顺序1、对话开场白2、全局变量3、自动执行。",
"auto_save": "自动保存",
"can_select_toolset": "可选择整个工具集",
"change_app_type": "更改应用类型",
"chat_agent_intro": "由 AI 自主规划可执行流程",
"chat_debug": "调试预览",
@ -411,6 +410,7 @@
"tool_input_param_tip": "该工具正常运行需要配置相关信息",
"tool_load_failed": "部分工具加载失败",
"tool_not_active": "该工具尚未激活",
"tool_not_desc": "工具缺少描述~",
"tool_offset_tips": "该工具已无法使用,将中断应用运行,请立即替换",
"tool_param_config": "参数配置",
"tool_params_description_tips": "参数功能的描述,若作为工具调用参数,影响模型工具调用效果",

View File

@ -58,7 +58,6 @@
"auto_execute_default_prompt_placeholder": "自動執行時,傳送的預設問題",
"auto_execute_tip": "開啟後,使用者進入對話式介面將自動觸發工作流程。\n執行順序1、對話開場白2、全域變數3、自動執行。",
"auto_save": "自動儲存",
"can_select_toolset": "可選擇整個工具集",
"change_app_type": "更改應用程式類型",
"chat_agent_intro": "由 AI 自主規劃可執行流程",
"chat_debug": "聊天預覽",
@ -388,6 +387,7 @@
"tool_detail": "工具詳情",
"tool_input_param_tip": "該工具正常運行需要配置相關信息",
"tool_not_active": "該工具尚未激活",
"tool_not_desc": "工具缺少描述~",
"tool_offset_tips": "該工具已無法使用,將中斷應用運行,請立即替換",
"tool_param_config": "參數配置",
"tool_params_description_tips": "參數功能的描述,若作為工具調用參數,影響模型工具調用效果",

View File

@ -6,7 +6,10 @@ import type {
type ToolModuleResponseItemType
} from '@fastgpt/global/core/chat/type';
import type { SseResponseEventEnum } from '@fastgpt/global/core/workflow/runtime/constants';
import type { WorkflowInteractiveResponseType } from '@fastgpt/global/core/workflow/template/system/interactive/type';
import type {
UserInputInteractive,
WorkflowInteractiveResponseType
} from '@fastgpt/global/core/workflow/template/system/interactive/type';
import type { TopAgentFormDataType } from '@fastgpt/service/core/chat/HelperBot/dispatch/topAgent/type';
import type { GeneratedSkillDataType } from '@fastgpt/global/core/chat/helperBot/generatedSkill/type';
@ -26,7 +29,8 @@ export type generatingMessageProps = {
nodeResponse?: ChatHistoryItemResType;
durationSeconds?: number;
// Agent
// HelperBot
collectionForm?: UserInputInteractive;
formData?: TopAgentFormDataType;
generatedSkill?: GeneratedSkillDataType;
};

View File

@ -8,7 +8,8 @@ import {
AccordionItem,
AccordionPanel,
Flex,
HStack
HStack,
Button
} from '@chakra-ui/react';
import { useTranslation } from 'next-i18next';
import MyIcon from '@fastgpt/web/components/common/Icon';
@ -16,6 +17,11 @@ import Markdown from '@/components/Markdown';
import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
import MyIconButton from '@fastgpt/web/components/common/Icon/button';
import { useCopyData } from '@fastgpt/web/hooks/useCopyData';
import type { UserInputInteractive } from '@fastgpt/global/core/workflow/template/system/interactive/type';
import { Controller, useForm } from 'react-hook-form';
import { nodeInputTypeToInputType } from '@/components/core/app/formRender/utils';
import FormLabel from '@fastgpt/web/components/common/MyBox/FormLabel';
import InputRender from '@/components/core/app/formRender';
const accordionButtonStyle = {
w: 'auto',
@ -69,7 +75,6 @@ const RenderResoningContent = React.memo(function RenderResoningContent({
</Accordion>
);
});
const RenderText = React.memo(function RenderText({
showAnimation,
text
@ -86,19 +91,87 @@ const RenderText = React.memo(function RenderText({
return <Markdown source={source} showAnimation={showAnimation} />;
});
const RenderCollectionForm = React.memo(function RenderCollectionForm({
collectionForm,
onSubmit
}: {
collectionForm: UserInputInteractive;
onSubmit: (formData: string) => void;
}) {
const { t } = useTranslation();
const { control, handleSubmit } = useForm();
const submitted = collectionForm.params.submitted;
return (
<Box>
<Box mb={3}>{collectionForm.params.description}</Box>
<Flex flexDirection={'column'} gap={3}>
{collectionForm.params.inputForm.map((input) => {
const inputType = nodeInputTypeToInputType([input.type]);
return (
<Controller
key={input.key}
control={control}
name={input.key}
render={({ field: { onChange, value }, fieldState: { error } }) => {
return (
<Box>
<FormLabel whiteSpace={'pre-wrap'} mb={0.5}>
{input.label}
</FormLabel>
<InputRender
{...input}
inputType={inputType}
value={value}
onChange={onChange}
isDisabled={submitted}
isInvalid={!!error}
isRichText={false}
/>
</Box>
);
}}
/>
);
})}
</Flex>
{!submitted && (
<Flex justifyContent={'flex-end'} mt={4}>
<Button
size={'sm'}
onClick={handleSubmit((data) => {
// 需要把 label 作为 key
const dataByLabel = Object.fromEntries(
collectionForm.params.inputForm.map((input) => [input.label, data[input.key]])
);
onSubmit(JSON.stringify(dataByLabel));
})}
>
{t('common:Submit')}
</Button>
</Flex>
)}
</Box>
);
});
const AIItem = ({
chat,
isChatting,
isLastChild
isLastChild,
onSubmitCollectionForm
}: {
chat: HelperBotChatItemSiteType;
isChatting: boolean;
isLastChild: boolean;
onSubmitCollectionForm: (formData: string) => void;
}) => {
const { t } = useTranslation();
const { copyData } = useCopyData();
console.log(chat, 111122);
return (
<Box
_hover={{
@ -136,6 +209,15 @@ const AIItem = ({
/>
);
}
if ('collectionForm' in value && value.collectionForm) {
return (
<RenderCollectionForm
key={i}
collectionForm={value.collectionForm}
onSubmit={onSubmitCollectionForm}
/>
);
}
})}
</Box>
{/* Controller */}

View File

@ -1,7 +1,7 @@
import React, { forwardRef, useCallback, useImperativeHandle, useRef, useState } from 'react';
import React, { useCallback, useImperativeHandle, useRef, useState } from 'react';
import HelperBotContextProvider, { type HelperBotRefType, type HelperBotProps } from './context';
import type { AIChatItemValueItemType } from '@fastgpt/global/core/chat/type';
import HelperBotContextProvider, { type HelperBotProps } from './context';
import type { AIChatItemValueItemType } from '@fastgpt/global/core/chat/helperBot/type';
import { getNanoid } from '@fastgpt/global/common/string/tools';
import MyBox from '@fastgpt/web/components/common/MyBox';
import { ChatRoleEnum } from '@fastgpt/global/core/chat/constants';
@ -145,7 +145,7 @@ const ChatBox = ({ type, metadata, onApply, ChatBoxRef, ...props }: HelperBotPro
event,
text = '',
reasoningText,
tool,
collectionForm,
formData,
generatedSkill
}: generatingMessageProps) => {
@ -158,26 +158,34 @@ const ChatBox = ({ type, metadata, onApply, ChatBoxRef, ...props }: HelperBotPro
const updateValue: AIChatItemValueItemType = item.value[updateIndex];
// Special event: form data
if (event === SseResponseEventEnum.formData && formData) {
if (type === HelperBotTypeEnum.topAgent) {
onApply?.(formData);
}
if (event === SseResponseEventEnum.collectionForm && collectionForm) {
return {
...item,
value: item.value.concat({
collectionForm
})
};
}
if (
event === SseResponseEventEnum.topAgentConfig &&
formData &&
type === HelperBotTypeEnum.topAgent
) {
onApply(formData);
return item;
}
// Special event: generated skill
if (event === SseResponseEventEnum.generatedSkill && generatedSkill) {
console.log('📊 HelperBot: Received generatedSkill event', generatedSkill);
// 直接将生成的 skill 数据传递给 onApply 回调(仅在 skillAgent 类型时)
if (type === HelperBotTypeEnum.skillAgent) {
onApply?.(generatedSkill);
}
if (
event === SseResponseEventEnum.generatedSkill &&
generatedSkill &&
type === HelperBotTypeEnum.skillAgent
) {
onApply(generatedSkill);
return item;
}
if (event === SseResponseEventEnum.answer || event === SseResponseEventEnum.fastAnswer) {
if (reasoningText) {
if (updateValue?.reasoning) {
if ('reasoning' in updateValue && updateValue.reasoning) {
updateValue.reasoning.content += reasoningText;
return {
...item,
@ -200,7 +208,7 @@ const ChatBox = ({ type, metadata, onApply, ChatBoxRef, ...props }: HelperBotPro
}
}
if (text) {
if (updateValue?.text) {
if ('text' in updateValue && updateValue.text) {
updateValue.text.content += text;
return {
...item,
@ -224,50 +232,6 @@ const ChatBox = ({ type, metadata, onApply, ChatBoxRef, ...props }: HelperBotPro
}
}
// Tool call
if (event === SseResponseEventEnum.toolCall && tool) {
const val: AIChatItemValueItemType = {
tool: {
...tool,
response: ''
}
};
return {
...item,
value: [...item.value, val]
};
}
if (event === SseResponseEventEnum.toolParams && tool && updateValue?.tool) {
if (tool.params) {
updateValue.tool.params += tool.params;
return {
...item,
value: [
...item.value.slice(0, updateIndex),
updateValue,
...item.value.slice(updateIndex + 1)
]
};
}
return item;
}
if (event === SseResponseEventEnum.toolResponse && tool && updateValue?.tool) {
if (tool.response) {
// replace tool response
updateValue.tool.response += tool.response;
return {
...item,
value: [
...item.value.slice(0, updateIndex),
updateValue,
...item.value.slice(updateIndex + 1)
]
};
}
return item;
}
return item;
})
);
@ -275,91 +239,98 @@ const ChatBox = ({ type, metadata, onApply, ChatBoxRef, ...props }: HelperBotPro
generatingScroll(false);
}
);
const handleSendMessage = useMemoizedFn(async ({ query = '' }: onSendMessageParamsType) => {
// Init check
if (isChatting) {
return toast({
title: t('chat:is_chatting'),
status: 'warning'
});
}
abortRequest();
query = query.trim();
if (!query) {
toast({
title: t('chat:content_empty'),
status: 'warning'
});
return;
}
const chatItemDataId = getNanoid(24);
const newChatList: HelperBotChatItemSiteType[] = [
...chatRecords,
// 用户消息
{
_id: getNanoid(24),
createTime: new Date(),
dataId: chatItemDataId,
obj: ChatRoleEnum.Human,
value: [
{
text: {
content: query
}
}
]
},
// AI 消息 - 空白,用于接收流式输出
{
_id: getNanoid(24),
createTime: new Date(),
dataId: chatItemDataId,
obj: ChatRoleEnum.AI,
value: [
{
text: {
content: ''
}
}
]
const handleSendMessage = useMemoizedFn(
async ({ query = '', collectionFormData }: onSendMessageParamsType) => {
// Init check
if (isChatting) {
return toast({
title: t('chat:is_chatting'),
status: 'warning'
});
}
];
setChatRecords(newChatList);
resetInputVal({});
scrollToBottom();
abortRequest();
query = query.trim();
const mergeQuery = query || collectionFormData;
setIsChatting(true);
try {
const abortSignal = new AbortController();
chatController.current = abortSignal;
console.log('metadata-fronted', metadata);
const { responseText } = await streamFetch({
url: '/api/core/chat/helperBot/completions',
data: {
chatId,
chatItemId: chatItemDataId,
query,
files: chatForm.getValues('files').map((item) => ({
type: item.type,
key: item.key,
// url: item.url,
name: item.name
})),
metadata: {
type: type,
data: metadata
}
if (!mergeQuery) {
toast({
title: t('chat:content_empty'),
status: 'warning'
});
return;
}
const chatItemDataId = getNanoid(24);
const newChatList: HelperBotChatItemSiteType[] = [
...chatRecords,
// 用户消息
{
_id: getNanoid(24),
createTime: new Date(),
dataId: chatItemDataId,
obj: ChatRoleEnum.Human,
value: [
{
text: {
content: mergeQuery
}
}
]
},
onMessage: generatingMessage,
abortCtrl: abortSignal
});
} catch (error) {}
setIsChatting(false);
});
// AI 消息 - 空白,用于接收流式输出
...(query
? [
{
_id: getNanoid(24),
createTime: new Date(),
dataId: chatItemDataId,
obj: ChatRoleEnum.AI,
value: [
{
text: {
content: ''
}
}
]
}
]
: [])
];
setChatRecords(newChatList);
resetInputVal({});
scrollToBottom();
setIsChatting(true);
try {
const abortSignal = new AbortController();
chatController.current = abortSignal;
const { responseText } = await streamFetch({
url: '/api/core/chat/helperBot/completions',
data: {
chatId,
chatItemId: chatItemDataId,
query: mergeQuery,
files: chatForm.getValues('files').map((item) => ({
type: item.type,
key: item.key,
// url: item.url,
name: item.name
})),
metadata: {
type: type,
data: metadata
}
},
onMessage: generatingMessage,
abortCtrl: abortSignal
});
} catch (error) {}
setIsChatting(false);
}
);
useImperativeHandle(ChatBoxRef, () => ({
restartChat() {
@ -397,6 +368,7 @@ const ChatBox = ({ type, metadata, onApply, ChatBoxRef, ...props }: HelperBotPro
chat={item}
isChatting={isChatting}
isLastChild={index === chatRecords.length - 1}
onSubmitCollectionForm={(data) => handleSendMessage({ query: data })}
/>
)}
</Box>

View File

@ -2,6 +2,7 @@ import type { UserInputFileItemType } from '../ChatContainer/ChatBox/type';
export type onSendMessageParamsType = {
query?: string;
collectionFormData?: string;
files?: UserInputFileItemType[];
};
export type onSendMessageFnType = (e: onSendMessageParamsType) => Promise<any>;

View File

@ -3,7 +3,6 @@ import {
Box,
Flex,
Grid,
useTheme,
useDisclosure,
Button,
HStack,
@ -16,10 +15,9 @@ import { type AppFileSelectConfigType } from '@fastgpt/global/core/app/type/conf
import type { SelectedToolItemType, SkillEditType } from '@fastgpt/global/core/app/formEdit/type';
import { useRouter } from 'next/router';
import { useTranslation } from 'next-i18next';
import { useForm } from 'react-hook-form';
import { useFieldArray, useForm } from 'react-hook-form';
import dynamic from 'next/dynamic';
import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
import Avatar from '@fastgpt/web/components/common/Avatar';
import MyIcon from '@fastgpt/web/components/common/Icon';
import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip';
@ -32,6 +30,8 @@ import { useContextSelector } from 'use-context-selector';
import { AppContext } from '@/pageComponents/app/detail/context';
import { useConfirm } from '@fastgpt/web/hooks/useConfirm';
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
import { useSkillManager } from '../hooks/useSkillManager';
import PromptEditor from '@fastgpt/web/components/common/Textarea/PromptEditor';
const DatasetSelectModal = dynamic(() => import('@/components/core/app/DatasetSelectModal'));
@ -52,7 +52,6 @@ const EditForm = ({
onClose,
onSave
}: EditFormProps) => {
const theme = useTheme();
const router = useRouter();
const { t } = useTranslation();
const appId = useContextSelector(AppContext, (v) => v.appId);
@ -64,7 +63,8 @@ const EditForm = ({
watch,
setValue,
reset,
formState: { isDirty }
formState: { isDirty },
control
} = useForm<SkillEditType>({
defaultValues: skill
});
@ -75,10 +75,39 @@ const EditForm = ({
}, [skill, reset]);
const selectedModel = getWebLLMModel(model);
const selectedTools = watch('selectedTools') || [];
const stepsText = watch('stepsText') || '';
const selectDatasets = watch('dataset.list') || [];
const skillName = watch('name');
const {
fields: selectedTools,
prepend: prependSelectedTools,
remove: removeSelectedTools,
update: updateSelectedTools
} = useFieldArray({
control,
name: 'selectedTools',
keyName: '_id'
});
const { skillOption, selectedSkills, onClickSkill, onRemoveSkill, SkillModal } = useSkillManager({
topAgentSelectedTools,
selectedTools,
onDeleteTool: (id) => {
removeSelectedTools(selectedTools.findIndex((item) => item.id === id));
},
onUpdateOrAddTool: (tool) => {
const index = selectedTools.findIndex((item) => item.id === tool.id);
if (index === -1) {
prependSelectedTools(tool);
} else {
updateSelectedTools(index, tool);
}
},
canSelectFile: fileSelectConfig?.canSelectFile,
canSelectImg: fileSelectConfig?.canSelectImg
});
const {
isOpen: isOpenDatasetSelect,
onOpen: onOpenKbSelect,
@ -204,7 +233,21 @@ const EditForm = ({
<FormLabel>{t('app:execution_steps')}</FormLabel>
</HStack>
<Box mt={2}>
<Textarea
<PromptEditor
minH={160}
title={t('app:execution_steps')}
placeholder={t('app:no_steps_yet')}
isRichText
skillOption={skillOption}
selectedSkills={selectedSkills}
onClickSkill={onClickSkill}
onRemoveSkill={onRemoveSkill}
value={stepsText}
onChange={(e) => {
setValue('stepsText', e);
}}
/>
{/* <Textarea
{...register('stepsText')}
maxLength={1000000}
bg={'myGray.50'}
@ -213,7 +256,7 @@ const EditForm = ({
placeholder={t('app:no_steps_yet')}
fontSize={'sm'}
color={'myGray.900'}
/>
/> */}
</Box>
</Box>
@ -225,23 +268,16 @@ const EditForm = ({
selectedTools={selectedTools}
fileSelectConfig={fileSelectConfig}
onAddTool={(e) => {
setValue('selectedTools', [e, ...(selectedTools || [])], { shouldDirty: true });
prependSelectedTools(e);
}}
onUpdateTool={(e) => {
setValue(
'selectedTools',
selectedTools?.map((item) => (item.id === e.id ? e : item)) || [],
{ shouldDirty: true }
updateSelectedTools(
selectedTools.findIndex((item) => item.id === e.id),
e
);
}}
onRemoveTool={(id) => {
setValue(
'selectedTools',
selectedTools?.filter((item) => item.pluginId !== id) || [],
{
shouldDirty: true
}
);
removeSelectedTools(selectedTools.findIndex((item) => item.id === id));
}}
/>
</Box>
@ -338,6 +374,7 @@ const EditForm = ({
)}
<ConfirmModal />
<SkillModal />
</>
);
};

View File

@ -1,4 +1,3 @@
import type { ParentIdType } from '@fastgpt/global/common/parentFolder/type';
import type {
SkillOptionItemType,
SkillItemType
@ -8,20 +7,16 @@ import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
import { useTranslation } from 'next-i18next';
import { useCallback, useMemo, useState } from 'react';
import {
checkNeedsUserConfiguration,
getToolConfigStatus,
validateToolConfiguration
} from '@fastgpt/global/core/app/formEdit/utils';
import { useToast } from '@fastgpt/web/hooks/useToast';
import {
FlowNodeInputTypeEnum,
FlowNodeTypeEnum
} from '@fastgpt/global/core/workflow/node/constant';
import { workflowStartNodeId } from '@/web/core/app/constants';
import { NodeOutputKeyEnum } from '@fastgpt/global/core/workflow/constants';
import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
import { getNanoid } from '@fastgpt/global/common/string/tools';
import type { SkillLabelItemType } from '@fastgpt/web/components/common/Textarea/PromptEditor/plugins/SkillLabelPlugin';
import dynamic from 'next/dynamic';
import type { AppFormEditFormType } from '@fastgpt/global/core/app/formEdit/type';
import type { SelectedToolItemType } from '@fastgpt/global/core/app/formEdit/type';
import {
getAppToolTemplates,
getToolPreviewNode,
@ -46,15 +41,15 @@ const isSubApp = (flowNodeType: FlowNodeTypeEnum) => {
return !!subAppTypeMap[flowNodeType];
};
export type SelectedToolItemType = AppFormEditFormType['selectedTools'][number];
export const useSkillManager = ({
topAgentSelectedTools,
selectedTools,
onUpdateOrAddTool,
onDeleteTool,
canSelectFile,
canSelectImg
}: {
topAgentSelectedTools: SelectedToolItemType[];
selectedTools: SelectedToolItemType[];
onDeleteTool: (id: string) => void;
onUpdateOrAddTool: (tool: SelectedToolItemType) => void;
@ -70,26 +65,33 @@ export const useSkillManager = ({
const data = await getAppToolTemplates({ getAll: true }).catch((err) => {
return [];
});
return data.map<SkillItemType>((item) => {
return {
id: item.id,
parentId: item.parentId,
label: item.name,
icon: item.avatar,
showArrow: item.isFolder,
canClick: true
};
});
return data
.map<SkillItemType>((item) => {
return {
id: item.id,
parentId: item.parentId,
label: item.name,
icon: item.avatar,
description: item.intro,
showArrow: item.isFolder,
canClick: true,
tools: data
.filter((tool) => tool.parentId === item.id)
.map((tool) => ({
id: tool.id,
name: tool.name
}))
};
})
.filter((item) => !item.parentId);
},
{
manual: false
}
);
const onLoadSystemTool = useCallback(
async ({ parentId = null }: { parentId?: ParentIdType; searchKey?: string }) => {
return systemTools.filter((tool) => {
return tool.parentId === parentId;
});
async ({}: { searchKey?: string }) => {
return systemTools;
},
[systemTools]
);
@ -145,19 +147,21 @@ export const useSkillManager = ({
}, []);
const onAddAppOrTool = useCallback(
async (appId: string) => {
const toolTemplate = await getToolPreviewNode({ appId });
if (!toolTemplate) {
return;
async (toolId: string) => {
// Check tool exists, if exists, not update/add tool
const existsTool = selectedTools.find((tool) => tool.pluginId === toolId);
if (existsTool) {
return existsTool.id;
}
const checkRes = validateToolConfiguration({
const toolTemplate = await getToolPreviewNode({ appId: toolId });
const toolValid = validateToolConfiguration({
toolTemplate,
canSelectFile,
canSelectImg
});
if (!checkRes) {
if (!toolValid) {
toast({
title: t('app:simple_tool_tips'),
status: 'warning'
@ -165,19 +169,20 @@ export const useSkillManager = ({
return;
}
// 添加与 top 相同工具的配置
const topTool = topAgentSelectedTools.find((tool) => tool.pluginId === toolTemplate.pluginId);
if (topTool) {
toolTemplate.inputs.forEach((input) => {
const topInput = topTool.inputs.find((tInput) => tInput.key === input.key);
if (topInput) {
input.value = topInput.value;
}
});
}
const tool: SelectedToolItemType = {
...toolTemplate,
id: `tool_${getNanoid(6)}`,
inputs: toolTemplate.inputs.map((input) => {
// 如果是文件上传类型,设置为从工作流开始节点获取用户文件
if (input.renderTypeList.includes(FlowNodeInputTypeEnum.fileSelect)) {
return {
...input,
value: [[workflowStartNodeId, NodeOutputKeyEnum.userFiles]]
};
}
return input;
})
id: toolId
};
onUpdateOrAddTool({
@ -187,7 +192,7 @@ export const useSkillManager = ({
return tool.id;
},
[canSelectFile, canSelectImg, onUpdateOrAddTool, t, toast]
[canSelectFile, canSelectImg, onUpdateOrAddTool, selectedTools, t, toast, topAgentSelectedTools]
);
/* ===== Skill option ===== */
@ -195,21 +200,9 @@ export const useSkillManager = ({
return {
onSelect: async (id: string) => {
if (id === 'systemTool') {
const data = await onLoadSystemTool({ parentId: null });
const data = await onLoadSystemTool({});
return {
description: t('app:can_select_toolset'),
list: data,
onSelect: async (id: string) => {
const data = await onLoadSystemTool({ parentId: id });
return {
onClick: onAddAppOrTool,
list: data.map((item) => ({
id: item.id,
label: item.label,
canClick: true
}))
};
},
onClick: onAddAppOrTool
};
} else if (id === 'myTools') {
@ -277,8 +270,8 @@ export const useSkillManager = ({
if (!tool) return;
if (isSubApp(tool.flowNodeType)) {
const { needConfig } = getToolConfigStatus(tool);
if (!needConfig) return;
const hasFormInput = checkNeedsUserConfiguration(tool);
if (!hasFormInput) return;
setConfigTool(tool);
} else {
console.log('onClickSkill', id);

View File

@ -308,14 +308,20 @@ const RenderList = React.memo(function RenderList({
key={template.id}
label={
<Box py={2} minW={['auto', '250px']}>
<Flex alignItems={'center'}>
<Flex alignItems={'center'} w={'100%'}>
<MyAvatar
src={template.avatar}
w={'1.75rem'}
objectFit={'contain'}
borderRadius={'sm'}
/>
<Box fontWeight={'bold'} ml={3} color={'myGray.900'} overflow={'hidden'}>
<Box
fontWeight={'bold'}
ml={3}
color={'myGray.900'}
flex={'1 0 0'}
overflow={'hidden'}
>
{t(parseI18nString(template.name, i18n.language))}
</Box>
{isSystemTool && (

View File

@ -75,6 +75,7 @@ async function handler(
return {
...toolNode,
id: toolNode.pluginId!,
inputs: mergedInputs
};
} catch (error) {

View File

@ -14,6 +14,7 @@ import type { OnOptimizePromptProps } from '@/components/common/PromptEditor/Opt
import type { OnOptimizeCodeProps } from '@/pageComponents/app/detail/WorkflowComponents/Flow/nodes/NodeCode/Copilot';
import type { AIChatItemValueItemType } from '@fastgpt/global/core/chat/type';
import type { TopAgentFormDataType } from '@fastgpt/service/core/chat/HelperBot/dispatch/topAgent/type';
import type { UserInputInteractive } from '@fastgpt/global/core/workflow/template/system/interactive/type';
type StreamFetchProps = {
url?: string;
@ -52,7 +53,15 @@ type ResponseQueueItemType = CommonResponseType &
tools: any;
}
| {
event: SseResponseEventEnum.formData;
event: SseResponseEventEnum.collectionForm;
collectionForm: UserInputInteractive;
}
| {
event: SseResponseEventEnum.generatedSkill;
data: any;
}
| {
event: SseResponseEventEnum.topAgentConfig;
data: TopAgentFormDataType;
}
);
@ -274,7 +283,12 @@ export const streamFetch = ({
event,
agentPlan: rest.agentPlan
});
} else if (event === SseResponseEventEnum.formData) {
} else if (event === SseResponseEventEnum.collectionForm) {
onMessage({
event,
collectionForm: rest
});
} else if (event === SseResponseEventEnum.topAgentConfig) {
// Directly call onMessage for formData, no need to queue
onMessage({
event,