mirror of
https://github.com/labring/FastGPT.git
synced 2025-12-25 20:02:47 +00:00
perf: agent skill editor
This commit is contained in:
parent
21b8bcace5
commit
1f100276f6
|
|
@ -3,10 +3,10 @@ import type { AppDeleteJobData } from './index';
|
|||
import { findAppAndAllChildren, deleteAppDataProcessor } from '../controller';
|
||||
import { addLog } from '../../../common/system/log';
|
||||
import { batchRun } from '@fastgpt/global/common/system/utils';
|
||||
import type { AppSchema } from '@fastgpt/global/core/app/type';
|
||||
import type { AppSchemaType } from '@fastgpt/global/core/app/type';
|
||||
import { MongoApp } from '../schema';
|
||||
|
||||
const deleteApps = async ({ teamId, apps }: { teamId: string; apps: AppSchema[] }) => {
|
||||
const deleteApps = async ({ teamId, apps }: { teamId: string; apps: AppSchemaType[] }) => {
|
||||
const results = await batchRun(
|
||||
apps,
|
||||
async (app) => {
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ import type {
|
|||
DispatchNodeResultType,
|
||||
ModuleDispatchProps
|
||||
} from '@fastgpt/global/core/workflow/runtime/type';
|
||||
import { getLLMModel } from '../../../../ai/model';
|
||||
import { getNodeErrResponse, getHistories } from '../../utils';
|
||||
import type { AIChatItemValueItemType, ChatItemType } from '@fastgpt/global/core/chat/type';
|
||||
import { ChatRoleEnum } from '@fastgpt/global/core/chat/constants';
|
||||
|
|
@ -23,16 +22,14 @@ import { systemSubInfo } from './sub/constants';
|
|||
import { textAdaptGptResponse } from '@fastgpt/global/core/workflow/runtime/utils';
|
||||
import { dispatchPlanAgent, dispatchReplanAgent } from './sub/plan';
|
||||
|
||||
import { getFileInputPrompt, readFileTool } from './sub/file/utils';
|
||||
import { getFileInputPrompt } from './sub/file/utils';
|
||||
import type { ChatCompletionMessageParam, ChatCompletionTool } from '@fastgpt/global/core/ai/type';
|
||||
import type { AgentPlanType } from './sub/plan/type';
|
||||
import type { localeType } from '@fastgpt/global/common/i18n/type';
|
||||
import { stepCall } from './master/call';
|
||||
import { addLog } from '../../../../../common/system/log';
|
||||
import { matchSkillForPlan } from './skillMatcher';
|
||||
import { matchSkillForId, matchSkillForPlan } from './skillMatcher';
|
||||
import type { SkillToolType } from '@fastgpt/global/core/ai/skill/type';
|
||||
import type { GetSubAppInfoFnType, SubAppRuntimeType } from './type';
|
||||
import { agentSkillToToolRuntime } from './sub/tool/utils';
|
||||
import type { SubAppRuntimeType } from './type';
|
||||
import { getSubapps } from './utils';
|
||||
|
||||
export type DispatchAgentModuleProps = ModuleDispatchProps<{
|
||||
|
|
@ -84,7 +81,6 @@ export const dispatchRunAgent = async (props: DispatchAgentModuleProps): Promise
|
|||
isAskAgent = true
|
||||
}
|
||||
} = props;
|
||||
const agentModel = getLLMModel(model);
|
||||
const chatHistories = getHistories(history, histories);
|
||||
const historiesMessages = chats2GPTMessages({
|
||||
messages: chatHistories,
|
||||
|
|
@ -135,22 +131,22 @@ export const dispatchRunAgent = async (props: DispatchAgentModuleProps): Promise
|
|||
});
|
||||
|
||||
// Get sub apps
|
||||
let { completionTools, subAppsMap } = await getSubapps({
|
||||
let { completionTools: agentCompletionTools, subAppsMap: agentSubAppsMap } = await getSubapps({
|
||||
tools: selectedTools,
|
||||
tmbId: runningAppInfo.tmbId,
|
||||
lang,
|
||||
filesMap
|
||||
});
|
||||
const getSubAppInfo = (id: string) => {
|
||||
const toolNode = subAppsMap.get(id) || systemSubInfo[id];
|
||||
const toolNode = agentSubAppsMap.get(id) || systemSubInfo[id];
|
||||
return {
|
||||
name: toolNode?.name || '',
|
||||
avatar: toolNode?.avatar || '',
|
||||
toolDescription: toolNode?.toolDescription || toolNode?.name || ''
|
||||
};
|
||||
};
|
||||
console.log(JSON.stringify(completionTools, null, 2), 'topAgent completionTools');
|
||||
console.log(subAppsMap, 'topAgent subAppsMap');
|
||||
// console.log(JSON.stringify(completionTools, null, 2), 'topAgent completionTools');
|
||||
// console.log(subAppsMap, 'topAgent subAppsMap');
|
||||
|
||||
/* ===== AI Start ===== */
|
||||
|
||||
|
|
@ -175,7 +171,62 @@ export const dispatchRunAgent = async (props: DispatchAgentModuleProps): Promise
|
|||
|
||||
if (taskIsComplexity) {
|
||||
/* ===== Plan Agent ===== */
|
||||
let currentSkillId: string | undefined = matchedSkillId;
|
||||
const mergeSkill = ({
|
||||
skillCompletionTools,
|
||||
skillSubAppsMap
|
||||
}: {
|
||||
skillCompletionTools: ChatCompletionTool[];
|
||||
skillSubAppsMap: Map<string, SubAppRuntimeType>;
|
||||
}) => {
|
||||
// 将 skill 的 completionTools 和 subAppsMap 合并到 topAgent,如果重复,则以 skill 的为准。
|
||||
agentCompletionTools = skillCompletionTools.concat(
|
||||
agentCompletionTools.filter(
|
||||
(item) =>
|
||||
!skillCompletionTools.some((item2) => item2.function.name === item.function.name)
|
||||
)
|
||||
);
|
||||
[...skillSubAppsMap].forEach(([id, item]) => {
|
||||
agentSubAppsMap.set(id, item);
|
||||
});
|
||||
console.log(JSON.stringify(agentCompletionTools, null, 2), 'merge completionTools');
|
||||
console.log(agentSubAppsMap, 'merge subAppsMap');
|
||||
};
|
||||
const skillMatch = async () => {
|
||||
const matchResult = await matchSkillForPlan({
|
||||
teamId: runningUserInfo.teamId,
|
||||
tmbId: runningAppInfo.tmbId,
|
||||
appId: runningAppInfo.id,
|
||||
userInput: lastInteractive ? interactiveInput : userChatInput,
|
||||
messages: historiesMessages, // 传入完整的对话历史
|
||||
model,
|
||||
lang
|
||||
});
|
||||
|
||||
if (matchResult.matched) {
|
||||
matchedSkillId = String(matchResult.skill._id);
|
||||
mergeSkill({
|
||||
skillCompletionTools: matchResult.completionTools,
|
||||
skillSubAppsMap: matchResult.subAppsMap
|
||||
});
|
||||
|
||||
// 可选: 推送匹配信息给前端
|
||||
workflowStreamResponse?.({
|
||||
event: SseResponseEventEnum.answer,
|
||||
data: textAdaptGptResponse({
|
||||
text: `📋 找到参考技能: ${matchResult.systemPrompt}`
|
||||
})
|
||||
});
|
||||
|
||||
return {
|
||||
skillPrompt: matchResult.systemPrompt
|
||||
};
|
||||
}
|
||||
addLog.debug(`未匹配到 skill,原因: ${matchResult.reason}`);
|
||||
return {
|
||||
skillPrompt: ''
|
||||
};
|
||||
};
|
||||
|
||||
const planCallFn = async () => {
|
||||
// 点了确认。此时肯定有 agentPlans
|
||||
if (
|
||||
|
|
@ -185,58 +236,16 @@ export const dispatchRunAgent = async (props: DispatchAgentModuleProps): Promise
|
|||
) {
|
||||
planHistoryMessages = undefined;
|
||||
} else {
|
||||
// 🆕 执行 Skill 匹配(仅在 isPlanStep 且没有 planHistoryMessages 时)
|
||||
let skillSystemPrompt: string | undefined;
|
||||
// match skill
|
||||
const matchResult = await matchSkillForPlan({
|
||||
teamId: runningUserInfo.teamId,
|
||||
tmbId: runningAppInfo.tmbId,
|
||||
appId: runningAppInfo.id,
|
||||
userInput: lastInteractive ? interactiveInput : userChatInput,
|
||||
messages: historiesMessages, // 传入完整的对话历史
|
||||
model,
|
||||
lang
|
||||
});
|
||||
|
||||
if (matchResult.matched) {
|
||||
skillSystemPrompt = matchResult.systemPrompt;
|
||||
currentSkillId = String(matchResult.skill._id);
|
||||
|
||||
// 将 skill 的 completionTools 和 subAppsMap 合并到topAgent,如果重复,则以 skill 的为准。
|
||||
completionTools = matchResult.completionTools.concat(
|
||||
completionTools.filter(
|
||||
(item) =>
|
||||
!matchResult.completionTools.some(
|
||||
(item2) => item2.function.name === item.function.name
|
||||
)
|
||||
)
|
||||
);
|
||||
[...matchResult.subAppsMap].forEach(([id, item]) => {
|
||||
subAppsMap.set(id, item);
|
||||
});
|
||||
console.log(JSON.stringify(completionTools, null, 2), 'merge completionTools');
|
||||
console.log(subAppsMap, 'merge subAppsMap');
|
||||
|
||||
// 可选: 推送匹配信息给前端
|
||||
workflowStreamResponse?.({
|
||||
event: SseResponseEventEnum.answer,
|
||||
data: textAdaptGptResponse({
|
||||
text: `📋 找到参考技能: ${matchResult.systemPrompt}`
|
||||
})
|
||||
});
|
||||
} else {
|
||||
addLog.debug(`未匹配到 skill,原因: ${matchResult.reason}`);
|
||||
}
|
||||
|
||||
const { skillPrompt } = await skillMatch();
|
||||
const { answerText, plan, completeMessages, usages, interactiveResponse } =
|
||||
await dispatchPlanAgent({
|
||||
historyMessages: planHistoryMessages || historiesMessages,
|
||||
userInput: lastInteractive ? interactiveInput : userChatInput,
|
||||
interactive: lastInteractive,
|
||||
completionTools,
|
||||
completionTools: agentCompletionTools,
|
||||
getSubAppInfo,
|
||||
// TODO: 需要区分?systemprompt 需要替换成 role 和 target 么?
|
||||
systemPrompt: skillSystemPrompt || systemPrompt,
|
||||
systemPrompt: skillPrompt || systemPrompt,
|
||||
model,
|
||||
temperature,
|
||||
top_p: aiChatTopP,
|
||||
|
|
@ -301,7 +310,7 @@ export const dispatchRunAgent = async (props: DispatchAgentModuleProps): Promise
|
|||
[DispatchNodeResponseKeyEnum.memories]: {
|
||||
[planMessagesKey]: filterMemoryMessages(completeMessages),
|
||||
[agentPlanKey]: agentPlan,
|
||||
[skillMatchKey]: currentSkillId
|
||||
[skillMatchKey]: matchedSkillId
|
||||
},
|
||||
[DispatchNodeResponseKeyEnum.interactive]: interactiveResponse
|
||||
};
|
||||
|
|
@ -326,7 +335,7 @@ export const dispatchRunAgent = async (props: DispatchAgentModuleProps): Promise
|
|||
userInput: lastInteractive ? interactiveInput : userChatInput,
|
||||
plan,
|
||||
interactive: lastInteractive,
|
||||
completionTools,
|
||||
completionTools: agentCompletionTools,
|
||||
getSubAppInfo,
|
||||
systemPrompt,
|
||||
model,
|
||||
|
|
@ -420,41 +429,26 @@ export const dispatchRunAgent = async (props: DispatchAgentModuleProps): Promise
|
|||
});
|
||||
if (result) return result;
|
||||
}
|
||||
// 如果有保存的 skill id,恢复 skill 的 tools
|
||||
else if (matchedSkillId) {
|
||||
addLog.debug(`恢复 skill tools, skill id: ${matchedSkillId}`);
|
||||
const skill = await matchSkillForId({
|
||||
id: matchedSkillId,
|
||||
tmbId: runningAppInfo.tmbId,
|
||||
lang
|
||||
});
|
||||
if (skill) {
|
||||
mergeSkill({
|
||||
skillCompletionTools: skill.skillTools,
|
||||
skillSubAppsMap: skill.skillSubAppsMap
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
addLog.debug(`Start master agent`, {
|
||||
agentPlan: JSON.stringify(agentPlan, null, 2)
|
||||
});
|
||||
|
||||
// 如果有保存的 skill id,恢复 skill 的 tools
|
||||
if (matchedSkillId) {
|
||||
addLog.debug(`恢复 skill tools, skill id: ${matchedSkillId}`);
|
||||
try {
|
||||
const { MongoAiSkill } = await import('../../../../ai/skill/schema');
|
||||
const skill = await MongoAiSkill.findById(matchedSkillId).lean();
|
||||
if (skill && skill.tools) {
|
||||
const { completionTools: skillTools, subAppsMap: skillSubAppsMap } = await getSubapps({
|
||||
tools: skill.tools,
|
||||
tmbId: runningAppInfo.tmbId,
|
||||
lang
|
||||
});
|
||||
|
||||
// 合并 skill 的 tools 到 completionTools
|
||||
completionTools = skillTools.concat(
|
||||
completionTools.filter(
|
||||
(item) => !skillTools.some((item2) => item2.function.name === item.function.name)
|
||||
)
|
||||
);
|
||||
[...skillSubAppsMap].forEach(([id, item]) => {
|
||||
subAppsMap.set(id, item);
|
||||
});
|
||||
|
||||
addLog.debug(`成功恢复 skill tools`);
|
||||
}
|
||||
} catch (error) {
|
||||
addLog.error(`恢复 skill tools 失败:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
/* ===== Master agent, 逐步执行 plan ===== */
|
||||
if (!agentPlan) return Promise.reject('没有 plan');
|
||||
|
||||
|
|
@ -470,10 +464,10 @@ export const dispatchRunAgent = async (props: DispatchAgentModuleProps): Promise
|
|||
...props,
|
||||
getSubAppInfo,
|
||||
steps: agentPlan.steps, // 传入所有步骤,而不仅仅是未执行的步骤
|
||||
completionTools,
|
||||
completionTools: agentCompletionTools,
|
||||
step,
|
||||
filesMap,
|
||||
subAppsMap
|
||||
subAppsMap: agentSubAppsMap
|
||||
});
|
||||
|
||||
// Merge response
|
||||
|
|
@ -513,7 +507,7 @@ export const dispatchRunAgent = async (props: DispatchAgentModuleProps): Promise
|
|||
[agentPlanKey]: agentPlan,
|
||||
[planMessagesKey]: undefined,
|
||||
[replanMessagesKey]: undefined,
|
||||
[skillMatchKey]: currentSkillId
|
||||
[skillMatchKey]: matchedSkillId
|
||||
},
|
||||
[DispatchNodeResponseKeyEnum.assistantResponses]: assistantResponses,
|
||||
[DispatchNodeResponseKeyEnum.nodeResponse]: {
|
||||
|
|
|
|||
|
|
@ -260,22 +260,26 @@ export const stepCall = async ({
|
|||
usages
|
||||
};
|
||||
} else if (tool.type === 'workflow' || tool.type === 'toolWorkflow') {
|
||||
const fn = tool.type === 'workflow' ? dispatchApp : dispatchPlugin;
|
||||
// const fn = tool.type === 'workflow' ? dispatchApp : dispatchPlugin;
|
||||
|
||||
const { response, usages } = await fn({
|
||||
...props,
|
||||
node,
|
||||
workflowStreamResponse: childWorkflowStreamResponse,
|
||||
callParams: {
|
||||
appId: node.pluginId,
|
||||
version: node.version,
|
||||
...requestParams
|
||||
}
|
||||
});
|
||||
// const { response, usages } = await fn({
|
||||
// ...props,
|
||||
// node,
|
||||
// workflowStreamResponse: childWorkflowStreamResponse,
|
||||
// callParams: {
|
||||
// appId: node.pluginId,
|
||||
// version: node.version,
|
||||
// ...requestParams
|
||||
// }
|
||||
// });
|
||||
|
||||
// return {
|
||||
// response,
|
||||
// usages
|
||||
// };
|
||||
return {
|
||||
response,
|
||||
usages
|
||||
response: 'Can not find the tool',
|
||||
usages: []
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -5,73 +5,11 @@ import type { ChatCompletionMessageParam, ChatCompletionTool } from '@fastgpt/gl
|
|||
import { getLLMModel } from '../../../../ai/model';
|
||||
import { getNanoid } from '@fastgpt/global/common/string/tools';
|
||||
import { getErrText } from '@fastgpt/global/common/error/utils';
|
||||
import { agentSkillToToolRuntime } from './sub/tool/utils';
|
||||
import type { SubAppRuntimeType } from './type';
|
||||
import type { localeType } from '@fastgpt/global/common/i18n/type';
|
||||
import { getSubapps } from './utils';
|
||||
import { addLog } from '../../../../../common/system/log';
|
||||
|
||||
/**
|
||||
* 构建 Skill Tools 数组
|
||||
* 参考 MatcherService.ts 的 match 函数
|
||||
*/
|
||||
export const buildSkillTools = (skills: AiSkillSchemaType[]) => {
|
||||
const skillCompletionTools: ChatCompletionTool[] = [];
|
||||
const skillsMap: Record<string, AiSkillSchemaType> = {};
|
||||
|
||||
for (const skill of skills) {
|
||||
// 生成唯一函数名
|
||||
const functionName = getNanoid(6);
|
||||
skill.name = functionName;
|
||||
skillsMap[functionName] = skill;
|
||||
|
||||
if (skill.description) {
|
||||
skillCompletionTools.push({
|
||||
type: 'function',
|
||||
function: {
|
||||
name: functionName,
|
||||
description: skill.description,
|
||||
parameters: {
|
||||
type: 'object',
|
||||
properties: {},
|
||||
required: []
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return { skillCompletionTools, skillsMap };
|
||||
};
|
||||
|
||||
/**
|
||||
* 格式化 Skill 为 SystemPrompt
|
||||
* 将匹配到的 skill 格式化为 XML 提示词
|
||||
*/
|
||||
export const formatSkillAsSystemPrompt = (skill: AiSkillSchemaType): string => {
|
||||
const lines = ['<reference_skill>', `**参考技能**: ${skill.name}`, ''];
|
||||
|
||||
if (skill.description) {
|
||||
lines.push(`**描述**: ${skill.description}`, '');
|
||||
}
|
||||
|
||||
if (skill.steps && skill.steps.trim()) {
|
||||
lines.push(`**步骤信息**:`, skill.steps, '');
|
||||
}
|
||||
|
||||
lines.push(
|
||||
'**说明**:',
|
||||
'1. 以上是用户之前保存的类似任务的执行框架',
|
||||
'2. 请参考该技能的宏观阶段划分和资源方向',
|
||||
'3. 根据当前用户的具体需求,调整和优化框架',
|
||||
'4. 保持阶段的逻辑性和方向的清晰性',
|
||||
'',
|
||||
'</reference_skill>'
|
||||
);
|
||||
|
||||
return lines.join('\n');
|
||||
};
|
||||
|
||||
/**
|
||||
* 主匹配函数
|
||||
* 参考 MatcherService.ts 的 match 方法
|
||||
|
|
@ -107,6 +45,67 @@ export const matchSkillForPlan = async ({
|
|||
subAppsMap: Map<string, SubAppRuntimeType>;
|
||||
}
|
||||
> => {
|
||||
/**
|
||||
* 构建 Skill Tools 数组
|
||||
* 参考 MatcherService.ts 的 match 函数
|
||||
*/
|
||||
const buildSkillTools = (skills: AiSkillSchemaType[]) => {
|
||||
const skillCompletionTools: ChatCompletionTool[] = [];
|
||||
const skillsMap: Record<string, AiSkillSchemaType> = {};
|
||||
|
||||
for (const skill of skills) {
|
||||
// 生成唯一函数名
|
||||
const functionName = getNanoid(6);
|
||||
skill.name = functionName;
|
||||
skillsMap[functionName] = skill;
|
||||
|
||||
if (skill.description) {
|
||||
skillCompletionTools.push({
|
||||
type: 'function',
|
||||
function: {
|
||||
name: functionName,
|
||||
description: skill.description,
|
||||
parameters: {
|
||||
type: 'object',
|
||||
properties: {},
|
||||
required: []
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return { skillCompletionTools, skillsMap };
|
||||
};
|
||||
|
||||
/**
|
||||
* 格式化 Skill 为 SystemPrompt
|
||||
* 将匹配到的 skill 格式化为 XML 提示词
|
||||
*/
|
||||
const formatSkillAsSystemPrompt = (skill: AiSkillSchemaType): string => {
|
||||
const lines = ['<reference_skill>', `**参考技能**: ${skill.name}`, ''];
|
||||
|
||||
if (skill.description) {
|
||||
lines.push(`**描述**: ${skill.description}`, '');
|
||||
}
|
||||
|
||||
if (skill.steps && skill.steps.trim()) {
|
||||
lines.push(`**步骤信息**:`, skill.steps, '');
|
||||
}
|
||||
|
||||
lines.push(
|
||||
'**说明**:',
|
||||
'1. 以上是用户之前保存的类似任务的执行框架',
|
||||
'2. 请参考该技能的宏观阶段划分和资源方向',
|
||||
'3. 根据当前用户的具体需求,调整和优化框架',
|
||||
'4. 保持阶段的逻辑性和方向的清晰性',
|
||||
'',
|
||||
'</reference_skill>'
|
||||
);
|
||||
|
||||
return lines.join('\n');
|
||||
};
|
||||
|
||||
addLog.debug('matchSkillForPlan start');
|
||||
const modelData = getLLMModel(model);
|
||||
|
||||
|
|
@ -224,3 +223,26 @@ export const matchSkillForPlan = async ({
|
|||
};
|
||||
}
|
||||
};
|
||||
|
||||
export const matchSkillForId = async ({
|
||||
id,
|
||||
tmbId,
|
||||
lang
|
||||
}: {
|
||||
id: string;
|
||||
tmbId: string;
|
||||
lang?: localeType;
|
||||
}) => {
|
||||
const skill = await MongoAiSkill.findById(id).lean();
|
||||
if (!skill || !skill.tools) return;
|
||||
const { completionTools: skillTools, subAppsMap: skillSubAppsMap } = await getSubapps({
|
||||
tools: skill.tools,
|
||||
tmbId,
|
||||
lang
|
||||
});
|
||||
|
||||
return {
|
||||
skillTools,
|
||||
skillSubAppsMap
|
||||
};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -54,3 +54,15 @@ const MyIconButton = ({
|
|||
};
|
||||
|
||||
export default MyIconButton;
|
||||
|
||||
export const MyDeleteIconButton = ({ onClick, ...props }: Omit<Props, 'icon'>) => {
|
||||
return (
|
||||
<MyIconButton
|
||||
hoverBg="red.50"
|
||||
hoverColor="red.600"
|
||||
onClick={onClick}
|
||||
{...props}
|
||||
icon={'delete'}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -451,7 +451,6 @@
|
|||
"core.dataset.My Dataset": "My Dataset",
|
||||
"core.dataset.Query extension intro": "Enabling the question optimization function can improve the accuracy of Dataset searches during continuous conversations. After enabling this function, when performing Dataset searches, the AI will complete the missing information of the question based on the conversation history.",
|
||||
"core.dataset.Quote Length": "Quote Content Length",
|
||||
"core.dataset.Read Dataset": "View Dataset Details",
|
||||
"core.dataset.Set Website Config": "Start Configuring",
|
||||
"core.dataset.Start export": "Export Started",
|
||||
"core.dataset.Text collection": "Text Dataset",
|
||||
|
|
|
|||
|
|
@ -454,7 +454,6 @@
|
|||
"core.dataset.My Dataset": "我的知识库",
|
||||
"core.dataset.Query extension intro": "开启问题优化功能,可以提高提高连续对话时,知识库搜索的精度。开启该功能后,在进行知识库搜索时,会根据对话记录,利用 AI 补全问题缺失的信息。",
|
||||
"core.dataset.Quote Length": "引用内容长度",
|
||||
"core.dataset.Read Dataset": "查看知识库详情",
|
||||
"core.dataset.Set Website Config": "开始配置",
|
||||
"core.dataset.Start export": "已开始导出",
|
||||
"core.dataset.Text collection": "文本数据集",
|
||||
|
|
|
|||
|
|
@ -450,7 +450,6 @@
|
|||
"core.dataset.My Dataset": "我的知識庫",
|
||||
"core.dataset.Query extension intro": "開啟問題最佳化功能,可以提高連續對話時知識庫搜尋的準確度。開啟此功能後,在進行知識庫搜尋時,系統會根據對話記錄,利用 AI 補充問題中缺少的資訊。",
|
||||
"core.dataset.Quote Length": "引用內容長度",
|
||||
"core.dataset.Read Dataset": "檢視知識庫詳細資料",
|
||||
"core.dataset.Set Website Config": "開始設定",
|
||||
"core.dataset.Start export": "已開始匯出",
|
||||
"core.dataset.Text collection": "文字資料集",
|
||||
|
|
|
|||
|
|
@ -10,9 +10,13 @@ import type {
|
|||
SkillAgentParamsType
|
||||
} from '@fastgpt/global/core/chat/helperBot/skillAgent/type';
|
||||
|
||||
export type HelperBotRefType = {
|
||||
restartChat: () => void;
|
||||
};
|
||||
export type HelperBotProps = {
|
||||
emptyDom?: ReactNode;
|
||||
fileSelectConfig?: AppFileSelectConfigType;
|
||||
ChatBoxRef: React.ForwardedRef<HelperBotRefType>;
|
||||
} & (
|
||||
| {
|
||||
type: typeof HelperBotTypeEnum.topAgent;
|
||||
|
|
@ -29,6 +33,7 @@ type HelperBotContextType = HelperBotProps & {};
|
|||
|
||||
export const HelperBotContext = createContext<HelperBotContextType>({
|
||||
type: HelperBotTypeEnum.topAgent,
|
||||
ChatBoxRef: null,
|
||||
metadata: {
|
||||
role: '',
|
||||
taskObject: '',
|
||||
|
|
|
|||
|
|
@ -1,17 +1,11 @@
|
|||
import React, { useCallback, useRef, useState } from 'react';
|
||||
import React, { forwardRef, useCallback, useImperativeHandle, useRef, useState } from 'react';
|
||||
|
||||
import HelperBotContextProvider, { type HelperBotProps } from './context';
|
||||
import type {
|
||||
AIChatItemValueItemType,
|
||||
ChatItemType,
|
||||
ChatSiteItemType
|
||||
} from '@fastgpt/global/core/chat/type';
|
||||
import HelperBotContextProvider, { type HelperBotRefType, type HelperBotProps } from './context';
|
||||
import type { AIChatItemValueItemType } from '@fastgpt/global/core/chat/type';
|
||||
import { getNanoid } from '@fastgpt/global/common/string/tools';
|
||||
import MyBox from '@fastgpt/web/components/common/MyBox';
|
||||
import { ChatRoleEnum, ChatStatusEnum } from '@fastgpt/global/core/chat/constants';
|
||||
import type { getPaginationRecordsBody } from '@/pages/api/core/chat/getPaginationRecords';
|
||||
import { ChatRoleEnum } from '@fastgpt/global/core/chat/constants';
|
||||
import { useScrollPagination } from '@fastgpt/web/hooks/useScrollPagination';
|
||||
import type { PaginationResponse } from '@fastgpt/web/common/fetch/type';
|
||||
import { Box } from '@chakra-ui/react';
|
||||
import HumanItem from './components/HumanItem';
|
||||
import AIItem from './components/AIItem';
|
||||
|
|
@ -37,7 +31,7 @@ import { streamFetch } from '@/web/common/api/fetch';
|
|||
import type { generatingMessageProps } from '../ChatContainer/type';
|
||||
import { SseResponseEventEnum } from '@fastgpt/global/core/workflow/runtime/constants';
|
||||
|
||||
const ChatBox = ({ type, metadata, onApply, ...props }: HelperBotProps) => {
|
||||
const ChatBox = ({ type, metadata, onApply, ChatBoxRef, ...props }: HelperBotProps) => {
|
||||
const { toast } = useToast();
|
||||
const { t } = useTranslation();
|
||||
|
||||
|
|
@ -59,8 +53,7 @@ const ChatBox = ({ type, metadata, onApply, ...props }: HelperBotProps) => {
|
|||
const requestParams = useMemoEnhance(() => {
|
||||
return {
|
||||
chatId,
|
||||
type,
|
||||
metadata
|
||||
type
|
||||
};
|
||||
}, []);
|
||||
|
||||
|
|
@ -368,6 +361,14 @@ const ChatBox = ({ type, metadata, onApply, ...props }: HelperBotProps) => {
|
|||
setIsChatting(false);
|
||||
});
|
||||
|
||||
useImperativeHandle(ChatBoxRef, () => ({
|
||||
restartChat() {
|
||||
abortRequest();
|
||||
setChatRecords([]);
|
||||
setChatId(getNanoid(12));
|
||||
}
|
||||
}));
|
||||
|
||||
return (
|
||||
<MyBox display={'flex'} flexDirection={'column'} h={'100%'} position={'relative'}>
|
||||
<ScrollData
|
||||
|
|
|
|||
|
|
@ -1,11 +1,14 @@
|
|||
import { Box, Flex, IconButton } from '@chakra-ui/react';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import React, { useEffect, useMemo } from 'react';
|
||||
import React, { useEffect, useMemo, useRef } from 'react';
|
||||
import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
|
||||
import { useSafeState } from 'ahooks';
|
||||
import type { AppFormEditFormType } from '@fastgpt/global/core/app/formEdit/type';
|
||||
import type {
|
||||
AppFormEditFormType,
|
||||
SelectedToolItemType
|
||||
} from '@fastgpt/global/core/app/formEdit/type';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import { AppContext } from '../../context';
|
||||
import { useChatTest } from '../../useChatTest';
|
||||
|
|
@ -20,10 +23,10 @@ import { ChatTypeEnum } from '@/components/core/chat/ChatContainer/ChatBox/const
|
|||
import type { Form2WorkflowFnType } from '../FormComponent/type';
|
||||
import FillRowTabs from '@fastgpt/web/components/common/Tabs/FillRowTabs';
|
||||
import HelperBot from '@/components/core/chat/HelperBot';
|
||||
import type { HelperBotRefType } from '@/components/core/chat/HelperBot/context';
|
||||
import { HelperBotTypeEnum } from '@fastgpt/global/core/chat/helperBot/type';
|
||||
import { getToolPreviewNode } from '@/web/core/app/api/tool';
|
||||
import type { FlowNodeTemplateType } from '@fastgpt/global/core/workflow/type/node';
|
||||
import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||
import { loadGeneratedTools } from './utils';
|
||||
|
||||
type Props = {
|
||||
appForm: AppFormEditFormType;
|
||||
|
|
@ -36,6 +39,7 @@ const ChatTest = ({ appForm, setAppForm, setRenderEdit, form2WorkflowFn }: Props
|
|||
const { toast } = useToast();
|
||||
|
||||
const [activeTab, setActiveTab] = useSafeState<'helper' | 'chat_debug'>('helper');
|
||||
const HelperBotRef = useRef<HelperBotRefType>(null);
|
||||
|
||||
const { appDetail } = useContextSelector(AppContext, (v) => v);
|
||||
const datasetCiteData = useContextSelector(ChatItemContext, (v) => v.datasetCiteData);
|
||||
|
|
@ -126,7 +130,11 @@ const ChatTest = ({ appForm, setAppForm, setRenderEdit, form2WorkflowFn }: Props
|
|||
aria-label={'delete'}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
restartChat();
|
||||
if (activeTab === 'helper') {
|
||||
HelperBotRef.current?.restartChat();
|
||||
} else {
|
||||
restartChat();
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</MyTooltip>
|
||||
|
|
@ -134,27 +142,14 @@ const ChatTest = ({ appForm, setAppForm, setRenderEdit, form2WorkflowFn }: Props
|
|||
<Box flex={1}>
|
||||
{activeTab === 'helper' && (
|
||||
<HelperBot
|
||||
ChatBoxRef={HelperBotRef}
|
||||
type={HelperBotTypeEnum.topAgent}
|
||||
metadata={topAgentMetadata}
|
||||
onApply={async (formData) => {
|
||||
const targetToolIds = formData.tools || [];
|
||||
const newTools: FlowNodeTemplateType[] = [];
|
||||
const failedToolIds: string[] = [];
|
||||
|
||||
const results = await Promise.all(
|
||||
targetToolIds.map((toolId: string) =>
|
||||
getToolPreviewNode({ appId: toolId })
|
||||
.then((tool) => ({ status: 'fulfilled' as const, toolId, tool }))
|
||||
.catch((error) => ({ status: 'rejected' as const, toolId, error }))
|
||||
)
|
||||
);
|
||||
|
||||
results.forEach((result) => {
|
||||
if (result.status === 'fulfilled') {
|
||||
newTools.push(result.tool);
|
||||
} else if (result.status === 'rejected') {
|
||||
failedToolIds.push(result.toolId);
|
||||
}
|
||||
const newTools = await loadGeneratedTools({
|
||||
newToolIds: formData.tools || [],
|
||||
existsTools: appForm.selectedTools,
|
||||
fileSelectConfig: appForm.chatConfig.fileSelectConfig
|
||||
});
|
||||
|
||||
setAppForm((prev) => {
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ import { cardStyles } from '../../constants';
|
|||
import { SmallAddIcon } from '@chakra-ui/icons';
|
||||
import { getAiSkillDetail } from '@/web/core/ai/skill/api';
|
||||
import { getToolConfigStatus } from '@fastgpt/global/core/app/formEdit/utils';
|
||||
import MyIconButton, { MyDeleteIconButton } from '@fastgpt/web/components/common/Icon/button';
|
||||
|
||||
const DatasetSelectModal = dynamic(() => import('@/components/core/app/DatasetSelectModal'));
|
||||
const DatasetParamsModal = dynamic(() => import('@/components/core/app/DatasetParamsModal'));
|
||||
|
|
@ -298,45 +299,69 @@ const EditForm = ({
|
|||
similarity={appForm.dataset.similarity}
|
||||
limit={appForm.dataset.limit}
|
||||
usingReRank={appForm.dataset.usingReRank}
|
||||
datasetSearchUsingExtensionQuery={appForm.dataset.datasetSearchUsingExtensionQuery}
|
||||
usingExtensionQuery={appForm.dataset.datasetSearchUsingExtensionQuery}
|
||||
queryExtensionModel={appForm.dataset.datasetSearchExtensionModel}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
<Grid gridTemplateColumns={'repeat(2, minmax(0, 1fr))'} gridGap={[2, 4]}>
|
||||
{selectDatasets.map((item) => (
|
||||
<MyTooltip key={item.datasetId} label={t('common:core.dataset.Read Dataset')}>
|
||||
<Flex
|
||||
overflow={'hidden'}
|
||||
alignItems={'center'}
|
||||
p={2}
|
||||
bg={'white'}
|
||||
boxShadow={'0 4px 8px -2px rgba(16,24,40,.1),0 2px 4px -2px rgba(16,24,40,.06)'}
|
||||
borderRadius={'md'}
|
||||
border={theme.borders.base}
|
||||
cursor={'pointer'}
|
||||
onClick={() =>
|
||||
router.push({
|
||||
pathname: '/dataset/detail',
|
||||
query: {
|
||||
datasetId: item.datasetId
|
||||
}
|
||||
})
|
||||
<Flex
|
||||
key={item.datasetId}
|
||||
overflow={'hidden'}
|
||||
alignItems={'center'}
|
||||
p={2}
|
||||
bg={'white'}
|
||||
boxShadow={'0 4px 8px -2px rgba(16,24,40,.1),0 2px 4px -2px rgba(16,24,40,.06)'}
|
||||
borderRadius={'md'}
|
||||
border={'base'}
|
||||
_hover={{
|
||||
'& .controler': {
|
||||
display: 'flex'
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Avatar src={item.avatar} w={'1.5rem'} borderRadius={'sm'} />
|
||||
<Box
|
||||
ml={2}
|
||||
flex={'1 0 0'}
|
||||
w={0}
|
||||
className={'textEllipsis'}
|
||||
fontSize={'sm'}
|
||||
color={'myGray.900'}
|
||||
>
|
||||
<Avatar src={item.avatar} w={'1.5rem'} borderRadius={'sm'} />
|
||||
<Box
|
||||
ml={2}
|
||||
flex={'1 0 0'}
|
||||
w={0}
|
||||
className={'textEllipsis'}
|
||||
fontSize={'sm'}
|
||||
color={'myGray.900'}
|
||||
>
|
||||
{item.name}
|
||||
</Box>
|
||||
</Flex>
|
||||
</MyTooltip>
|
||||
{item.name}
|
||||
</Box>
|
||||
|
||||
{/* Icon */}
|
||||
<Box className="controler" display={['flex', 'none']} alignItems={'center'}>
|
||||
<MyIconButton
|
||||
icon={'common/viewLight'}
|
||||
onClick={() =>
|
||||
router.push({
|
||||
pathname: '/dataset/detail',
|
||||
query: {
|
||||
datasetId: item.datasetId
|
||||
}
|
||||
})
|
||||
}
|
||||
/>
|
||||
<MyDeleteIconButton
|
||||
onClick={() => {
|
||||
setAppForm((state) => ({
|
||||
...state,
|
||||
dataset: {
|
||||
...state.dataset,
|
||||
datasets:
|
||||
state.dataset.datasets?.filter(
|
||||
(pre) => pre.datasetId !== item.datasetId
|
||||
) || []
|
||||
}
|
||||
}));
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</Flex>
|
||||
))}
|
||||
</Grid>
|
||||
</Box>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { Box, Flex, IconButton } from '@chakra-ui/react';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import React, { useMemo } from 'react';
|
||||
import React, { useMemo, useRef } from 'react';
|
||||
import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import type { SkillEditType, SelectedToolItemType } from '@fastgpt/global/core/app/formEdit/type';
|
||||
|
|
@ -8,11 +8,8 @@ import type { AppFormEditFormType } from '@fastgpt/global/core/app/formEdit/type
|
|||
import MyBox from '@fastgpt/web/components/common/MyBox';
|
||||
import HelperBot from '@/components/core/chat/HelperBot';
|
||||
import { HelperBotTypeEnum } from '@fastgpt/global/core/chat/helperBot/type';
|
||||
import { getToolPreviewNode } from '@/web/core/app/api/tool';
|
||||
import {
|
||||
validateToolConfiguration,
|
||||
getToolConfigStatus
|
||||
} from '@fastgpt/global/core/app/formEdit/utils';
|
||||
import { loadGeneratedTools } from '../utils';
|
||||
import type { HelperBotRefType } from '@/components/core/chat/HelperBot/context';
|
||||
|
||||
type Props = {
|
||||
topAgentSelectedTools?: SelectedToolItemType[];
|
||||
|
|
@ -22,6 +19,7 @@ type Props = {
|
|||
};
|
||||
const ChatTest = ({ topAgentSelectedTools = [], skill, appForm, onAIGenerate }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const ChatBoxRef = useRef<HelperBotRefType>(null);
|
||||
|
||||
const skillAgentMetadata = useMemo(() => {
|
||||
return {
|
||||
|
|
@ -56,85 +54,40 @@ const ChatTest = ({ topAgentSelectedTools = [], skill, appForm, onAIGenerate }:
|
|||
aria-label={'delete'}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
ChatBoxRef.current?.restartChat();
|
||||
}}
|
||||
/>
|
||||
</MyTooltip>
|
||||
</Flex>
|
||||
<Box flex={1}>
|
||||
<HelperBot
|
||||
ChatBoxRef={ChatBoxRef}
|
||||
type={HelperBotTypeEnum.skillAgent}
|
||||
metadata={skillAgentMetadata}
|
||||
onApply={async (generatedSkillData) => {
|
||||
console.log(generatedSkillData, 222);
|
||||
|
||||
// 1. 提取所有步骤中的工具 ID(去重,仅保留 type='tool')
|
||||
const allToolIds = new Set<string>();
|
||||
// 1. 计算新的 tool
|
||||
const newToolIds: string[] = [];
|
||||
generatedSkillData.execution_plan.steps.forEach((step) => {
|
||||
step.expectedTools?.forEach((tool) => {
|
||||
if (tool.type === 'tool') {
|
||||
allToolIds.add(tool.id);
|
||||
const exists = skill.selectedTools.find((t) => t.pluginId === tool.id);
|
||||
if (exists) return;
|
||||
newToolIds.push(tool.id);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// 2. 分类工具:已有的和新的
|
||||
const existingTools: SelectedToolItemType[] = [];
|
||||
const newToolIds: string[] = [];
|
||||
|
||||
Array.from(allToolIds).forEach((toolId) => {
|
||||
const existingTool = skill.selectedTools.find((t) => t.pluginId === toolId);
|
||||
if (existingTool) {
|
||||
existingTools.push(existingTool);
|
||||
} else {
|
||||
newToolIds.push(toolId);
|
||||
}
|
||||
});
|
||||
|
||||
// 3. 并行获取新工具详情
|
||||
const newTools: SelectedToolItemType[] = [];
|
||||
const newTools = await loadGeneratedTools({
|
||||
newToolIds,
|
||||
existsTools: skill.selectedTools,
|
||||
topAgentSelectedTools,
|
||||
fileSelectConfig: appForm.chatConfig.fileSelectConfig
|
||||
});
|
||||
|
||||
if (newToolIds.length > 0) {
|
||||
const results = await Promise.all(
|
||||
newToolIds.map((toolId: string) =>
|
||||
getToolPreviewNode({ appId: toolId })
|
||||
.then((tool) => ({ status: 'fulfilled' as const, toolId, tool }))
|
||||
.catch((error) => ({ status: 'rejected' as const, toolId, error }))
|
||||
)
|
||||
);
|
||||
|
||||
results.forEach((result) => {
|
||||
if (result.status !== 'fulfilled') return;
|
||||
const tool = result.tool;
|
||||
// 验证工具配置
|
||||
const toolValid = validateToolConfiguration({
|
||||
toolTemplate: tool,
|
||||
canSelectFile: appForm.chatConfig.fileSelectConfig?.canSelectFile,
|
||||
canSelectImg: appForm.chatConfig.fileSelectConfig?.canSelectImg
|
||||
});
|
||||
|
||||
if (toolValid) {
|
||||
// 添加与 top 相同工具的配置
|
||||
const topTool = topAgentSelectedTools.find(
|
||||
(item) => item.pluginId === tool.pluginId
|
||||
);
|
||||
if (topTool) {
|
||||
tool.inputs.forEach((input) => {
|
||||
const topInput = topTool.inputs.find((topIn) => topIn.key === input.key);
|
||||
if (topInput) {
|
||||
input.value = topInput.value;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
newTools.push({
|
||||
...tool,
|
||||
configStatus: getToolConfigStatus(tool).status
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 3. 构建 stepsText(保持原有逻辑)
|
||||
// 4. 构建 stepsText
|
||||
const stepsText = generatedSkillData.execution_plan.steps
|
||||
.map((step, index) => {
|
||||
let stepText = `步骤 ${index + 1}: ${step.title}\n${step.description}`;
|
||||
|
|
@ -148,12 +101,12 @@ const ChatTest = ({ topAgentSelectedTools = [], skill, appForm, onAIGenerate }:
|
|||
})
|
||||
.join('\n\n');
|
||||
|
||||
// 4. 应用生成的数据,以 AI 生成的工具列表为准
|
||||
// 5. 应用生成的数据,以 AI 生成的工具列表为准
|
||||
onAIGenerate({
|
||||
name: generatedSkillData.plan_analysis.name || skill.name,
|
||||
description: generatedSkillData.plan_analysis.description || skill.description,
|
||||
stepsText: stepsText,
|
||||
selectedTools: [...existingTools, ...newTools]
|
||||
selectedTools: newTools
|
||||
});
|
||||
}}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import {
|
|||
IconButton,
|
||||
Textarea
|
||||
} from '@chakra-ui/react';
|
||||
import MyIconButton, { MyDeleteIconButton } from '@fastgpt/web/components/common/Icon/button';
|
||||
import { type AppFileSelectConfigType } from '@fastgpt/global/core/app/type/config';
|
||||
import type { SelectedToolItemType, SkillEditType } from '@fastgpt/global/core/app/formEdit/type';
|
||||
import { useRouter } from 'next/router';
|
||||
|
|
@ -265,38 +266,57 @@ const EditForm = ({
|
|||
</Flex>
|
||||
<Grid gridTemplateColumns={'repeat(2, minmax(0, 1fr))'} gridGap={[2, 4]}>
|
||||
{selectDatasets.map((item) => (
|
||||
<MyTooltip key={item.datasetId} label={t('common:core.dataset.Read Dataset')}>
|
||||
<Flex
|
||||
overflow={'hidden'}
|
||||
alignItems={'center'}
|
||||
p={2}
|
||||
bg={'white'}
|
||||
boxShadow={'0 4px 8px -2px rgba(16,24,40,.1),0 2px 4px -2px rgba(16,24,40,.06)'}
|
||||
borderRadius={'md'}
|
||||
border={theme.borders.base}
|
||||
cursor={'pointer'}
|
||||
onClick={() =>
|
||||
router.push({
|
||||
pathname: '/dataset/detail',
|
||||
query: {
|
||||
datasetId: item.datasetId
|
||||
}
|
||||
})
|
||||
<Flex
|
||||
key={item.datasetId}
|
||||
overflow={'hidden'}
|
||||
alignItems={'center'}
|
||||
p={2}
|
||||
bg={'white'}
|
||||
boxShadow={'0 4px 8px -2px rgba(16,24,40,.1),0 2px 4px -2px rgba(16,24,40,.06)'}
|
||||
borderRadius={'md'}
|
||||
border={'base'}
|
||||
_hover={{
|
||||
'& .controler': {
|
||||
display: 'flex'
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Avatar src={item.avatar} w={'1.5rem'} borderRadius={'sm'} />
|
||||
<Box
|
||||
ml={2}
|
||||
flex={'1 0 0'}
|
||||
w={0}
|
||||
className={'textEllipsis'}
|
||||
fontSize={'sm'}
|
||||
color={'myGray.900'}
|
||||
>
|
||||
<Avatar src={item.avatar} w={'1.5rem'} borderRadius={'sm'} />
|
||||
<Box
|
||||
ml={2}
|
||||
flex={'1 0 0'}
|
||||
w={0}
|
||||
className={'textEllipsis'}
|
||||
fontSize={'sm'}
|
||||
color={'myGray.900'}
|
||||
>
|
||||
{item.name}
|
||||
</Box>
|
||||
</Flex>
|
||||
</MyTooltip>
|
||||
{item.name}
|
||||
</Box>
|
||||
|
||||
{/* Icon */}
|
||||
<Box className="controler" display={['flex', 'none']} alignItems={'center'}>
|
||||
<MyIconButton
|
||||
icon={'common/viewLight'}
|
||||
onClick={() =>
|
||||
router.push({
|
||||
pathname: '/dataset/detail',
|
||||
query: {
|
||||
datasetId: item.datasetId
|
||||
}
|
||||
})
|
||||
}
|
||||
/>
|
||||
<MyDeleteIconButton
|
||||
onClick={() => {
|
||||
setValue(
|
||||
'dataset.list',
|
||||
selectDatasets?.filter((pre) => pre.datasetId !== item.datasetId) || [],
|
||||
{ shouldDirty: true }
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</Flex>
|
||||
))}
|
||||
</Grid>
|
||||
</Box>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import type { SkillEditType } from '@fastgpt/global/core/app/formEdit/type';
|
||||
import type { SelectedToolItemType } from '@fastgpt/global/core/app/formEdit/type';
|
||||
import type { AppChatConfigType } from '@fastgpt/global/core/app/type';
|
||||
import type { AppFormEditFormType } from '@fastgpt/global/core/app/formEdit/type';
|
||||
import type {
|
||||
|
|
@ -28,7 +28,12 @@ import { getDefaultAppForm } from '@fastgpt/global/core/app/utils';
|
|||
import type { FlowNodeInputItemType } from '@fastgpt/global/core/workflow/type/io';
|
||||
import { getAppChatConfig } from '@fastgpt/global/core/workflow/utils';
|
||||
import { Input_Template_File_Link } from '@fastgpt/global/core/workflow/template/input';
|
||||
import { getToolConfigStatus } from '@fastgpt/global/core/app/formEdit/utils';
|
||||
import {
|
||||
getToolConfigStatus,
|
||||
validateToolConfiguration
|
||||
} from '@fastgpt/global/core/app/formEdit/utils';
|
||||
import { getToolPreviewNode } from '@/web/core/app/api/tool';
|
||||
import type { AppFileSelectConfigType } from '@fastgpt/global/core/app/type/config';
|
||||
|
||||
/* format app nodes to edit form */
|
||||
export const appWorkflow2AgentForm = ({
|
||||
|
|
@ -232,3 +237,56 @@ export function agentForm2AppWorkflow(
|
|||
chatConfig: data.chatConfig
|
||||
};
|
||||
}
|
||||
|
||||
export const loadGeneratedTools = async ({
|
||||
newToolIds,
|
||||
existsTools = [],
|
||||
topAgentSelectedTools = [],
|
||||
fileSelectConfig
|
||||
}: {
|
||||
newToolIds: string[]; // 新的,完整的 toolId
|
||||
existsTools?: SelectedToolItemType[];
|
||||
topAgentSelectedTools?: SelectedToolItemType[];
|
||||
fileSelectConfig?: AppFileSelectConfigType;
|
||||
}): Promise<SelectedToolItemType[]> => {
|
||||
const results = (
|
||||
await Promise.all(
|
||||
newToolIds.map<Promise<SelectedToolItemType | undefined>>(async (toolId: string) => {
|
||||
// 已经存在的工具,直接返回
|
||||
const existTool = existsTools.find((tool) => tool.pluginId === toolId);
|
||||
if (existTool) {
|
||||
return existTool;
|
||||
}
|
||||
|
||||
// 新工具,需要与已配置的 tool 进行 input 合并
|
||||
const tool = await getToolPreviewNode({ appId: toolId });
|
||||
// 验证工具配置
|
||||
const toolValid = validateToolConfiguration({
|
||||
toolTemplate: tool,
|
||||
canSelectFile: fileSelectConfig?.canSelectFile,
|
||||
canSelectImg: fileSelectConfig?.canSelectImg
|
||||
});
|
||||
if (!toolValid) {
|
||||
return;
|
||||
}
|
||||
|
||||
const topTool = topAgentSelectedTools.find((item) => item.pluginId === toolId);
|
||||
if (topTool) {
|
||||
tool.inputs.forEach((input) => {
|
||||
const topInput = topTool.inputs.find((topIn) => topIn.key === input.key);
|
||||
if (topInput) {
|
||||
input.value = topInput.value;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
...tool,
|
||||
configStatus: getToolConfigStatus(tool).status
|
||||
};
|
||||
})
|
||||
)
|
||||
).filter((item) => item !== undefined);
|
||||
|
||||
return results;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ import { getWebLLMModel } from '@/web/common/system/utils';
|
|||
import ToolSelect from '../FormComponent/ToolSelector/ToolSelect';
|
||||
import OptimizerPopover from '@/components/common/PromptEditor/OptimizerPopover';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
import MyIconButton, { MyDeleteIconButton } from '@fastgpt/web/components/common/Icon/button';
|
||||
|
||||
const DatasetSelectModal = dynamic(() => import('@/components/core/app/DatasetSelectModal'));
|
||||
const DatasetParamsModal = dynamic(() => import('@/components/core/app/DatasetParamsModal'));
|
||||
|
|
@ -289,38 +290,62 @@ const EditForm = ({
|
|||
)}
|
||||
<Grid gridTemplateColumns={'repeat(2, minmax(0, 1fr))'} gridGap={[2, 4]}>
|
||||
{selectDatasets.map((item) => (
|
||||
<MyTooltip key={item.datasetId} label={t('common:core.dataset.Read Dataset')}>
|
||||
<Flex
|
||||
overflow={'hidden'}
|
||||
alignItems={'center'}
|
||||
p={2}
|
||||
bg={'white'}
|
||||
boxShadow={'0 4px 8px -2px rgba(16,24,40,.1),0 2px 4px -2px rgba(16,24,40,.06)'}
|
||||
borderRadius={'md'}
|
||||
border={theme.borders.base}
|
||||
cursor={'pointer'}
|
||||
onClick={() =>
|
||||
router.push({
|
||||
pathname: '/dataset/detail',
|
||||
query: {
|
||||
datasetId: item.datasetId
|
||||
}
|
||||
})
|
||||
<Flex
|
||||
key={item.datasetId}
|
||||
overflow={'hidden'}
|
||||
alignItems={'center'}
|
||||
p={2}
|
||||
bg={'white'}
|
||||
boxShadow={'0 4px 8px -2px rgba(16,24,40,.1),0 2px 4px -2px rgba(16,24,40,.06)'}
|
||||
borderRadius={'md'}
|
||||
border={'base'}
|
||||
_hover={{
|
||||
'& .controler': {
|
||||
display: 'flex'
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Avatar src={item.avatar} w={'1.5rem'} borderRadius={'sm'} />
|
||||
<Box
|
||||
ml={2}
|
||||
flex={'1 0 0'}
|
||||
w={0}
|
||||
className={'textEllipsis'}
|
||||
fontSize={'sm'}
|
||||
color={'myGray.900'}
|
||||
>
|
||||
<Avatar src={item.avatar} w={'1.5rem'} borderRadius={'sm'} />
|
||||
<Box
|
||||
ml={2}
|
||||
flex={'1 0 0'}
|
||||
w={0}
|
||||
className={'textEllipsis'}
|
||||
fontSize={'sm'}
|
||||
color={'myGray.900'}
|
||||
>
|
||||
{item.name}
|
||||
</Box>
|
||||
</Flex>
|
||||
</MyTooltip>
|
||||
{item.name}
|
||||
</Box>
|
||||
|
||||
{/* Icon */}
|
||||
<Box className="controler" display={['flex', 'none']} alignItems={'center'}>
|
||||
<MyIconButton
|
||||
icon={'common/viewLight'}
|
||||
onClick={() =>
|
||||
router.push({
|
||||
pathname: '/dataset/detail',
|
||||
query: {
|
||||
datasetId: item.datasetId
|
||||
}
|
||||
})
|
||||
}
|
||||
/>
|
||||
<MyDeleteIconButton
|
||||
onClick={() => {
|
||||
setAppForm((state) => ({
|
||||
...state,
|
||||
dataset: {
|
||||
...state.dataset,
|
||||
datasets:
|
||||
state.dataset.datasets?.filter(
|
||||
(pre) => pre.datasetId !== item.datasetId
|
||||
) || []
|
||||
}
|
||||
}));
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</Flex>
|
||||
))}
|
||||
</Grid>
|
||||
</Box>
|
||||
|
|
|
|||
|
|
@ -9,13 +9,14 @@ import { MongoChatItem } from '@fastgpt/service/core/chat/chatItemSchema';
|
|||
import { MongoChat } from '@fastgpt/service/core/chat/chatSchema';
|
||||
import { MongoAppChatLog } from '@fastgpt/service/core/app/logs/chatLogsSchema';
|
||||
import { MongoChatItemResponse } from '@fastgpt/service/core/chat/chatItemResponseSchema';
|
||||
import { ChatItemValueTypeEnum, ChatRoleEnum } from '@fastgpt/global/core/chat/constants';
|
||||
import { ChatFileTypeEnum, ChatRoleEnum } from '@fastgpt/global/core/chat/constants';
|
||||
import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
|
||||
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
|
||||
import { MongoTeamMember } from '@fastgpt/service/support/user/team/teamMemberSchema';
|
||||
import { MongoTeam } from '@fastgpt/service/support/user/team/teamSchema';
|
||||
import { MongoUser } from '@fastgpt/service/support/user/schema';
|
||||
import { TeamMemberRoleEnum } from '@fastgpt/global/support/user/team/constant';
|
||||
import type { SearchDataResponseItemType } from '@fastgpt/global/core/dataset/type';
|
||||
|
||||
const createMockProps = (
|
||||
overrides?: Partial<Props>,
|
||||
|
|
@ -41,7 +42,6 @@ const createMockProps = (
|
|||
obj: ChatRoleEnum.Human,
|
||||
value: [
|
||||
{
|
||||
type: ChatItemValueTypeEnum.text,
|
||||
text: {
|
||||
content: 'Hello, how are you?'
|
||||
}
|
||||
|
|
@ -52,7 +52,6 @@ const createMockProps = (
|
|||
obj: ChatRoleEnum.AI,
|
||||
value: [
|
||||
{
|
||||
type: ChatItemValueTypeEnum.text,
|
||||
text: {
|
||||
content: 'I am doing well, thank you!'
|
||||
}
|
||||
|
|
@ -142,9 +141,8 @@ describe('pushChatRecords', () => {
|
|||
obj: ChatRoleEnum.Human,
|
||||
value: [
|
||||
{
|
||||
type: ChatItemValueTypeEnum.file,
|
||||
file: {
|
||||
type: 'image',
|
||||
type: ChatFileTypeEnum.image,
|
||||
name: 'test.jpg',
|
||||
url: 'https://example.com/test.jpg',
|
||||
key: 'file-key-123'
|
||||
|
|
@ -192,7 +190,6 @@ describe('pushChatRecords', () => {
|
|||
obj: ChatRoleEnum.AI,
|
||||
value: [
|
||||
{
|
||||
type: ChatItemValueTypeEnum.text,
|
||||
text: { content: 'Response' }
|
||||
}
|
||||
],
|
||||
|
|
@ -224,7 +221,7 @@ describe('pushChatRecords', () => {
|
|||
});
|
||||
|
||||
it('should handle dataset search node with quoteList', async () => {
|
||||
const quote = {
|
||||
const quote: SearchDataResponseItemType = {
|
||||
id: 'quote-1',
|
||||
chunkIndex: 0,
|
||||
datasetId: 'dataset-1',
|
||||
|
|
@ -242,7 +239,6 @@ describe('pushChatRecords', () => {
|
|||
obj: ChatRoleEnum.AI,
|
||||
value: [
|
||||
{
|
||||
type: ChatItemValueTypeEnum.text,
|
||||
text: { content: 'Based on the search results...' }
|
||||
}
|
||||
],
|
||||
|
|
@ -531,7 +527,6 @@ describe('pushChatRecords', () => {
|
|||
obj: ChatRoleEnum.Human,
|
||||
value: [
|
||||
{
|
||||
type: ChatItemValueTypeEnum.text,
|
||||
text: { content: 'Hello' }
|
||||
}
|
||||
]
|
||||
|
|
@ -555,7 +550,6 @@ describe('pushChatRecords', () => {
|
|||
obj: ChatRoleEnum.AI,
|
||||
value: [
|
||||
{
|
||||
type: ChatItemValueTypeEnum.text,
|
||||
text: { content: 'Hello' }
|
||||
}
|
||||
]
|
||||
|
|
@ -580,7 +574,6 @@ describe('pushChatRecords', () => {
|
|||
dataId: 'data-id-1',
|
||||
value: [
|
||||
{
|
||||
type: ChatItemValueTypeEnum.interactive,
|
||||
interactive: {
|
||||
type: 'userSelect',
|
||||
params: {
|
||||
|
|
@ -599,7 +592,6 @@ describe('pushChatRecords', () => {
|
|||
obj: ChatRoleEnum.Human,
|
||||
value: [
|
||||
{
|
||||
type: ChatItemValueTypeEnum.text,
|
||||
text: { content: 'Option 1' }
|
||||
}
|
||||
]
|
||||
|
|
@ -639,7 +631,6 @@ describe('pushChatRecords', () => {
|
|||
dataId: 'data-id-1',
|
||||
value: [
|
||||
{
|
||||
type: ChatItemValueTypeEnum.interactive,
|
||||
interactive: {
|
||||
type: 'userInput',
|
||||
params: {
|
||||
|
|
@ -664,7 +655,6 @@ describe('pushChatRecords', () => {
|
|||
obj: ChatRoleEnum.Human,
|
||||
value: [
|
||||
{
|
||||
type: ChatItemValueTypeEnum.text,
|
||||
text: { content: JSON.stringify({ username: 'john_doe' }) }
|
||||
}
|
||||
]
|
||||
|
|
@ -705,11 +695,9 @@ describe('pushChatRecords', () => {
|
|||
dataId: 'data-id-1',
|
||||
value: [
|
||||
{
|
||||
type: ChatItemValueTypeEnum.text,
|
||||
text: { content: 'Payment required' }
|
||||
},
|
||||
{
|
||||
type: ChatItemValueTypeEnum.interactive,
|
||||
interactive: {
|
||||
type: 'paymentPause',
|
||||
params: {}
|
||||
|
|
@ -729,8 +717,6 @@ describe('pushChatRecords', () => {
|
|||
});
|
||||
// PaymentPause is removed, and AI response is appended
|
||||
expect(chatItem?.value.length).toBeGreaterThan(0);
|
||||
// The first value should be text, last one should be from AI response
|
||||
expect(chatItem?.value[0].type).toBe(ChatItemValueTypeEnum.text);
|
||||
});
|
||||
|
||||
it('should merge AI response values', async () => {
|
||||
|
|
@ -744,11 +730,9 @@ describe('pushChatRecords', () => {
|
|||
dataId: 'data-id-1',
|
||||
value: [
|
||||
{
|
||||
type: ChatItemValueTypeEnum.text,
|
||||
text: { content: 'First response' }
|
||||
},
|
||||
{
|
||||
type: ChatItemValueTypeEnum.interactive,
|
||||
interactive: {
|
||||
type: 'userSelect',
|
||||
params: { options: ['A', 'B'] }
|
||||
|
|
@ -763,7 +747,6 @@ describe('pushChatRecords', () => {
|
|||
obj: ChatRoleEnum.AI,
|
||||
value: [
|
||||
{
|
||||
type: ChatItemValueTypeEnum.text,
|
||||
text: { content: 'Second response' }
|
||||
}
|
||||
],
|
||||
|
|
@ -797,7 +780,6 @@ describe('pushChatRecords', () => {
|
|||
durationSeconds: 1.5,
|
||||
value: [
|
||||
{
|
||||
type: ChatItemValueTypeEnum.interactive,
|
||||
interactive: {
|
||||
type: 'userSelect',
|
||||
params: { options: ['A', 'B'] }
|
||||
|
|
@ -840,7 +822,6 @@ describe('pushChatRecords', () => {
|
|||
dataId: 'data-id-1',
|
||||
value: [
|
||||
{
|
||||
type: ChatItemValueTypeEnum.interactive,
|
||||
interactive: {
|
||||
type: 'userSelect',
|
||||
params: { options: ['A', 'B'] }
|
||||
|
|
|
|||
Loading…
Reference in New Issue