Commit 450167c9 by Archer Committed by GitHub

App run node update (#2542)

* feat(workflow): allow apps to be invoked like plugins (#2521)

* feat(workflow): allow apps to be invoked like plugins

* fix type

* Encapsulate SSE response methods (#2530)

* perf: sse response fn

* perf: sse response

* fix: ts

* perf: not ssl copy

* perf: myselect auto scroll

* perf: run app code

* fix: app plugin (#2538)

---------

Co-authored-by: heheer <heheer@sealos.io>
parent 67445b40
...@@ -19,3 +19,5 @@ weight: 813 ...@@ -19,3 +19,5 @@ weight: 813
2. 新增 - 插件自定义输入支持单选框 2. 新增 - 插件自定义输入支持单选框
3. 新增 - 插件输出,支持指定某些字段为工具调用结果 3. 新增 - 插件输出,支持指定某些字段为工具调用结果
4. 新增 - 插件支持配置使用引导、全局变量和文件输入 4. 新增 - 插件支持配置使用引导、全局变量和文件输入
5. 优化 - SSE 响应代码。
6. 优化 - 非 HTTPS 环境下支持复制(除非 textarea 复制也不支持)
\ No newline at end of file
...@@ -128,6 +128,7 @@ export enum NodeInputKeyEnum { ...@@ -128,6 +128,7 @@ export enum NodeInputKeyEnum {
// read files // read files
fileUrlList = 'fileUrlList', fileUrlList = 'fileUrlList',
// user select // user select
userSelectOptions = 'userSelectOptions' userSelectOptions = 'userSelectOptions'
} }
......
...@@ -106,6 +106,7 @@ export enum FlowNodeTypeEnum { ...@@ -106,6 +106,7 @@ export enum FlowNodeTypeEnum {
contentExtract = 'contentExtract', contentExtract = 'contentExtract',
httpRequest468 = 'httpRequest468', httpRequest468 = 'httpRequest468',
runApp = 'app', runApp = 'app',
appModule = 'appModule',
pluginModule = 'pluginModule', pluginModule = 'pluginModule',
pluginInput = 'pluginInput', pluginInput = 'pluginInput',
pluginOutput = 'pluginOutput', pluginOutput = 'pluginOutput',
......
...@@ -19,6 +19,7 @@ import { RuntimeNodeItemType } from '../runtime/type'; ...@@ -19,6 +19,7 @@ import { RuntimeNodeItemType } from '../runtime/type';
import { RuntimeEdgeItemType } from './edge'; import { RuntimeEdgeItemType } from './edge';
import { ReadFileNodeResponse } from '../template/system/readFiles/type'; import { ReadFileNodeResponse } from '../template/system/readFiles/type';
import { UserSelectOptionType } from '../template/system/userSelect/type'; import { UserSelectOptionType } from '../template/system/userSelect/type';
import { WorkflowResponseType } from '../../../../service/core/workflow/dispatch/type';
/* workflow props */ /* workflow props */
export type ChatDispatchProps = { export type ChatDispatchProps = {
...@@ -36,9 +37,9 @@ export type ChatDispatchProps = { ...@@ -36,9 +37,9 @@ export type ChatDispatchProps = {
query: UserChatItemValueItemType[]; // trigger query query: UserChatItemValueItemType[]; // trigger query
chatConfig: AppSchema['chatConfig']; chatConfig: AppSchema['chatConfig'];
stream: boolean; stream: boolean;
detail: boolean; // response detail
maxRunTimes: number; maxRunTimes: number;
isToolCall?: boolean; isToolCall?: boolean;
workflowStreamResponse?: WorkflowResponseType;
}; };
export type ModuleDispatchProps<T> = ChatDispatchProps & { export type ModuleDispatchProps<T> = ChatDispatchProps & {
......
...@@ -236,7 +236,7 @@ export const textAdaptGptResponse = ({ ...@@ -236,7 +236,7 @@ export const textAdaptGptResponse = ({
finish_reason?: null | 'stop'; finish_reason?: null | 'stop';
extraData?: Object; extraData?: Object;
}) => { }) => {
return JSON.stringify({ return {
...extraData, ...extraData,
id: '', id: '',
object: '', object: '',
...@@ -252,7 +252,7 @@ export const textAdaptGptResponse = ({ ...@@ -252,7 +252,7 @@ export const textAdaptGptResponse = ({
finish_reason finish_reason
} }
] ]
}); };
}; };
/* Update runtimeNode's outputs with interactive data from history */ /* Update runtimeNode's outputs with interactive data from history */
......
...@@ -16,6 +16,7 @@ import { RunAppModule } from './system/runApp/index'; ...@@ -16,6 +16,7 @@ import { RunAppModule } from './system/runApp/index';
import { PluginInputModule } from './system/pluginInput'; import { PluginInputModule } from './system/pluginInput';
import { PluginOutputModule } from './system/pluginOutput'; import { PluginOutputModule } from './system/pluginOutput';
import { RunPluginModule } from './system/runPlugin'; import { RunPluginModule } from './system/runPlugin';
import { RunAppPluginModule } from './system/runAppPlugin';
import { AiQueryExtension } from './system/queryExtension'; import { AiQueryExtension } from './system/queryExtension';
import type { FlowNodeTemplateType } from '../type/node'; import type { FlowNodeTemplateType } from '../type/node';
...@@ -44,8 +45,8 @@ const systemNodes: FlowNodeTemplateType[] = [ ...@@ -44,8 +45,8 @@ const systemNodes: FlowNodeTemplateType[] = [
LafModule, LafModule,
IfElseNode, IfElseNode,
VariableUpdateNode, VariableUpdateNode,
CodeNode, CodeNode
RunAppModule // RunAppModule
]; ];
/* app flow module templates */ /* app flow module templates */
export const appSystemModuleTemplates: FlowNodeTemplateType[] = [ export const appSystemModuleTemplates: FlowNodeTemplateType[] = [
...@@ -70,5 +71,6 @@ export const moduleTemplatesFlat: FlowNodeTemplateType[] = [ ...@@ -70,5 +71,6 @@ export const moduleTemplatesFlat: FlowNodeTemplateType[] = [
) )
), ),
EmptyNode, EmptyNode,
RunPluginModule RunPluginModule,
RunAppPluginModule
]; ];
...@@ -73,3 +73,12 @@ export const Input_Template_Text_Quote: FlowNodeInputItemType = { ...@@ -73,3 +73,12 @@ export const Input_Template_Text_Quote: FlowNodeInputItemType = {
description: i18nT('app:document_quote_tip'), description: i18nT('app:document_quote_tip'),
valueType: WorkflowIOValueTypeEnum.string valueType: WorkflowIOValueTypeEnum.string
}; };
export const Input_Template_File_Link: FlowNodeInputItemType = {
key: NodeInputKeyEnum.fileUrlList,
renderTypeList: [FlowNodeInputTypeEnum.reference],
required: true,
label: i18nT('app:workflow.user_file_input'),
debugLabel: i18nT('app:workflow.user_file_input'),
description: i18nT('app:workflow.user_file_input_desc'),
valueType: WorkflowIOValueTypeEnum.arrayString
};
import { FlowNodeTemplateTypeEnum } from '../../constants';
import { FlowNodeTypeEnum } from '../../node/constant';
import { FlowNodeTemplateType } from '../../type/node';
import { getHandleConfig } from '../utils';
export const RunAppPluginModule: FlowNodeTemplateType = {
id: FlowNodeTypeEnum.appModule,
templateType: FlowNodeTemplateTypeEnum.other,
flowNodeType: FlowNodeTypeEnum.appModule,
sourceHandle: getHandleConfig(true, true, true, true),
targetHandle: getHandleConfig(true, true, true, true),
intro: '',
name: '',
showStatus: false,
isTool: false,
version: '481',
inputs: [], // [{key:'pluginId'},...]
outputs: []
};
...@@ -22,5 +22,3 @@ type UserSelectInteractive = { ...@@ -22,5 +22,3 @@ type UserSelectInteractive = {
}; };
export type InteractiveNodeResponseItemType = InteractiveBasicType & UserSelectInteractive; export type InteractiveNodeResponseItemType = InteractiveBasicType & UserSelectInteractive;
export type UserInteractiveType = UserSelectInteractive;
import { FlowNodeInputTypeEnum, FlowNodeOutputTypeEnum, FlowNodeTypeEnum } from './node/constant'; import {
chatHistoryValueDesc,
FlowNodeInputTypeEnum,
FlowNodeOutputTypeEnum,
FlowNodeTypeEnum
} from './node/constant';
import { import {
WorkflowIOValueTypeEnum, WorkflowIOValueTypeEnum,
NodeInputKeyEnum, NodeInputKeyEnum,
VariableInputEnum, VariableInputEnum,
variableMap, variableMap,
VARIABLE_NODE_ID VARIABLE_NODE_ID,
NodeOutputKeyEnum
} from './constants'; } from './constants';
import { FlowNodeInputItemType, FlowNodeOutputItemType, ReferenceValueProps } from './type/io.d'; import { FlowNodeInputItemType, FlowNodeOutputItemType, ReferenceValueProps } from './type/io.d';
import { StoreNodeItemType } from './type/node'; import { StoreNodeItemType } from './type/node';
...@@ -25,6 +31,7 @@ import { ...@@ -25,6 +31,7 @@ import {
import { IfElseResultEnum } from './template/system/ifElse/constant'; import { IfElseResultEnum } from './template/system/ifElse/constant';
import { RuntimeNodeItemType } from './runtime/type'; import { RuntimeNodeItemType } from './runtime/type';
import { getReferenceVariableValue } from './runtime/utils'; import { getReferenceVariableValue } from './runtime/utils';
import { Input_Template_History, Input_Template_UserChatInput } from './template/input';
export const getHandleId = (nodeId: string, type: 'source' | 'target', key: string) => { export const getHandleId = (nodeId: string, type: 'source' | 'target', key: string) => {
return `${nodeId}-${type}-${key}`; return `${nodeId}-${type}-${key}`;
...@@ -147,9 +154,11 @@ export const getModuleInputUiField = (input: FlowNodeInputItemType) => { ...@@ -147,9 +154,11 @@ export const getModuleInputUiField = (input: FlowNodeInputItemType) => {
return {}; return {};
}; };
export const pluginData2FlowNodeIO = ( export const pluginData2FlowNodeIO = ({
nodes: StoreNodeItemType[] nodes
): { }: {
nodes: StoreNodeItemType[];
}): {
inputs: FlowNodeInputItemType[]; inputs: FlowNodeInputItemType[];
outputs: FlowNodeOutputItemType[]; outputs: FlowNodeOutputItemType[];
} => { } => {
...@@ -180,6 +189,80 @@ export const pluginData2FlowNodeIO = ( ...@@ -180,6 +189,80 @@ export const pluginData2FlowNodeIO = (
}; };
}; };
export const appData2FlowNodeIO = ({
chatConfig
}: {
chatConfig?: AppChatConfigType;
}): {
inputs: FlowNodeInputItemType[];
outputs: FlowNodeOutputItemType[];
} => {
const variableInput = !chatConfig?.variables
? []
: chatConfig.variables.map((item) => {
const renderTypeMap = {
[VariableInputEnum.input]: [FlowNodeInputTypeEnum.input, FlowNodeInputTypeEnum.reference],
[VariableInputEnum.textarea]: [
FlowNodeInputTypeEnum.textarea,
FlowNodeInputTypeEnum.reference
],
[VariableInputEnum.select]: [FlowNodeInputTypeEnum.select],
[VariableInputEnum.custom]: [
FlowNodeInputTypeEnum.input,
FlowNodeInputTypeEnum.reference
],
default: [FlowNodeInputTypeEnum.reference]
};
return {
key: item.key,
renderTypeList: renderTypeMap[item.type] || renderTypeMap.default,
label: item.label,
debugLabel: item.label,
description: '',
valueType: WorkflowIOValueTypeEnum.any,
required: item.required,
list: item.enums.map((enumItem) => ({
label: enumItem.value,
value: enumItem.value
}))
};
});
// const showFileLink =
// chatConfig?.fileSelectConfig?.canSelectFile || chatConfig?.fileSelectConfig?.canSelectImg;
return {
inputs: [
Input_Template_History,
Input_Template_UserChatInput,
// ...(showFileLink ? [Input_Template_File_Link] : []),
...variableInput
],
outputs: [
{
id: NodeOutputKeyEnum.history,
key: NodeOutputKeyEnum.history,
required: true,
label: 'core.module.output.label.New context',
description: 'core.module.output.description.New context',
valueType: WorkflowIOValueTypeEnum.chatHistory,
valueDesc: chatHistoryValueDesc,
type: FlowNodeOutputTypeEnum.static
},
{
id: NodeOutputKeyEnum.answerText,
key: NodeOutputKeyEnum.answerText,
required: false,
label: 'core.module.output.label.Ai response content',
description: 'core.module.output.description.Ai response content',
valueType: WorkflowIOValueTypeEnum.string,
type: FlowNodeOutputTypeEnum.static
}
]
};
};
export const formatEditorVariablePickerIcon = ( export const formatEditorVariablePickerIcon = (
variables: { key: string; label: string; type?: `${VariableInputEnum}`; required?: boolean }[] variables: { key: string; label: string; type?: `${VariableInputEnum}`; required?: boolean }[]
): EditorVariablePickerType[] => { ): EditorVariablePickerType[] => {
......
import { FlowNodeTemplateType } from '@fastgpt/global/core/workflow/type/node.d'; import { FlowNodeTemplateType } from '@fastgpt/global/core/workflow/type/node.d';
import { FlowNodeTypeEnum, defaultNodeVersion } from '@fastgpt/global/core/workflow/node/constant'; import { FlowNodeTypeEnum, defaultNodeVersion } from '@fastgpt/global/core/workflow/node/constant';
import { pluginData2FlowNodeIO } from '@fastgpt/global/core/workflow/utils'; import { appData2FlowNodeIO, pluginData2FlowNodeIO } from '@fastgpt/global/core/workflow/utils';
import { PluginSourceEnum } from '@fastgpt/global/core/plugin/constants'; import { PluginSourceEnum } from '@fastgpt/global/core/plugin/constants';
import type { PluginRuntimeType } from '@fastgpt/global/core/workflow/runtime/type'; import type { PluginRuntimeType } from '@fastgpt/global/core/workflow/runtime/type';
import { FlowNodeTemplateTypeEnum } from '@fastgpt/global/core/workflow/constants'; import { FlowNodeTemplateTypeEnum } from '@fastgpt/global/core/workflow/constants';
...@@ -52,10 +52,10 @@ const getPluginTemplateById = async ( ...@@ -52,10 +52,10 @@ const getPluginTemplateById = async (
showStatus: true, showStatus: true,
workflow: { workflow: {
nodes: item.modules, nodes: item.modules,
edges: item.edges edges: item.edges,
chatConfig: item.chatConfig
}, },
templateType: FlowNodeTemplateTypeEnum.teamApp, templateType: FlowNodeTemplateTypeEnum.teamApp,
isTool: true,
version: item?.pluginData?.nodeVersion || defaultNodeVersion, version: item?.pluginData?.nodeVersion || defaultNodeVersion,
originCost: 0, originCost: 0,
currentCost: 0 currentCost: 0
...@@ -71,22 +71,27 @@ const getPluginTemplateById = async ( ...@@ -71,22 +71,27 @@ const getPluginTemplateById = async (
/* format plugin modules to plugin preview module */ /* format plugin modules to plugin preview module */
export async function getPluginPreviewNode({ id }: { id: string }): Promise<FlowNodeTemplateType> { export async function getPluginPreviewNode({ id }: { id: string }): Promise<FlowNodeTemplateType> {
const plugin = await getPluginTemplateById(id); const plugin = await getPluginTemplateById(id);
const isPlugin = !!plugin.workflow.nodes.find(
(node) => node.flowNodeType === FlowNodeTypeEnum.pluginInput
);
return { return {
id: getNanoid(), id: getNanoid(),
pluginId: plugin.id, pluginId: plugin.id,
templateType: plugin.templateType, templateType: plugin.templateType,
flowNodeType: FlowNodeTypeEnum.pluginModule, flowNodeType: isPlugin ? FlowNodeTypeEnum.pluginModule : FlowNodeTypeEnum.appModule,
avatar: plugin.avatar, avatar: plugin.avatar,
name: plugin.name, name: plugin.name,
intro: plugin.intro, intro: plugin.intro,
inputExplanationUrl: plugin.inputExplanationUrl, inputExplanationUrl: plugin.inputExplanationUrl,
showStatus: plugin.showStatus, showStatus: plugin.showStatus,
isTool: plugin.isTool, isTool: isPlugin,
version: plugin.version, version: plugin.version,
sourceHandle: getHandleConfig(true, true, true, true), sourceHandle: getHandleConfig(true, true, true, true),
targetHandle: getHandleConfig(true, true, true, true), targetHandle: getHandleConfig(true, true, true, true),
...pluginData2FlowNodeIO(plugin.workflow.nodes) ...(isPlugin
? pluginData2FlowNodeIO({ nodes: plugin.workflow.nodes })
: appData2FlowNodeIO({ chatConfig: plugin.workflow.chatConfig }))
}; };
} }
......
// @ts-nocheck
import type { ChatItemType } from '@fastgpt/global/core/chat/type.d'; import type { ChatItemType } from '@fastgpt/global/core/chat/type.d';
import type { ModuleDispatchProps } from '@fastgpt/global/core/workflow/runtime/type'; import type { ModuleDispatchProps } from '@fastgpt/global/core/workflow/runtime/type';
import { SelectAppItemType } from '@fastgpt/global/core/workflow/template/system/runApp/type'; import { dispatchWorkFlow } from '../index';
import { dispatchWorkFlowV1 } from '../index';
import { MongoApp } from '../../../../core/app/schema';
import { responseWrite } from '../../../../common/response';
import { ChatRoleEnum } from '@fastgpt/global/core/chat/constants'; import { ChatRoleEnum } from '@fastgpt/global/core/chat/constants';
import { SseResponseEventEnum } from '@fastgpt/global/core/workflow/runtime/constants'; import { SseResponseEventEnum } from '@fastgpt/global/core/workflow/runtime/constants';
import { textAdaptGptResponse } from '@fastgpt/global/core/workflow/runtime/utils'; import {
getWorkflowEntryNodeIds,
initWorkflowEdgeStatus,
storeNodes2RuntimeNodes,
textAdaptGptResponse
} from '@fastgpt/global/core/workflow/runtime/utils';
import { NodeInputKeyEnum, NodeOutputKeyEnum } from '@fastgpt/global/core/workflow/constants'; import { NodeInputKeyEnum, NodeOutputKeyEnum } from '@fastgpt/global/core/workflow/constants';
import { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/workflow/runtime/constants'; import { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/workflow/runtime/constants';
import { getHistories, setEntryEntries } from '../utils'; import { getHistories } from '../utils';
import { chatValue2RuntimePrompt, runtimePrompt2ChatsValue } from '@fastgpt/global/core/chat/adapt'; import { chatValue2RuntimePrompt, runtimePrompt2ChatsValue } from '@fastgpt/global/core/chat/adapt';
import { DispatchNodeResultType } from '@fastgpt/global/core/workflow/runtime/type'; import { DispatchNodeResultType } from '@fastgpt/global/core/workflow/runtime/type';
import { authAppByTmbId } from '../../../../support/permission/app/auth';
import { ReadPermissionVal } from '@fastgpt/global/support/permission/constant';
type Props = ModuleDispatchProps<{ type Props = ModuleDispatchProps<{
[NodeInputKeyEnum.userChatInput]: string; [NodeInputKeyEnum.userChatInput]: string;
[NodeInputKeyEnum.history]?: ChatItemType[] | number; [NodeInputKeyEnum.history]?: ChatItemType[] | number;
app: SelectAppItemType; [NodeInputKeyEnum.fileUrlList]?: string[];
}>; }>;
type Response = DispatchNodeResultType<{ type Response = DispatchNodeResultType<{
[NodeOutputKeyEnum.answerText]: string; [NodeOutputKeyEnum.answerText]: string;
[NodeOutputKeyEnum.history]: ChatItemType[]; [NodeOutputKeyEnum.history]: ChatItemType[];
}>; }>;
export const dispatchAppRequest = async (props: Props): Promise<Response> => { export const dispatchRunAppNode = async (props: Props): Promise<Response> => {
const { const {
res, app: workflowApp,
teamId,
stream,
detail,
histories, histories,
inputFiles, query,
params: { userChatInput, history, app } node: { pluginId },
workflowStreamResponse,
params
} = props; } = props;
let start = Date.now();
const { userChatInput, history, ...variables } = params;
if (!userChatInput) { if (!userChatInput) {
return Promise.reject('Input is empty'); return Promise.reject('Input is empty');
} }
if (!pluginId) {
return Promise.reject('pluginId is empty');
}
const appData = await MongoApp.findOne({ // Auth the app by tmbId(Not the user, but the workflow user)
_id: app.id, const { app: appData } = await authAppByTmbId({
teamId appId: pluginId,
tmbId: workflowApp.tmbId,
per: ReadPermissionVal
}); });
if (!appData) { // Auto line
return Promise.reject('App not found'); workflowStreamResponse?.({
} event: SseResponseEventEnum.answer,
if (stream) {
responseWrite({
res,
event: detail ? SseResponseEventEnum.answer : undefined,
data: textAdaptGptResponse({ data: textAdaptGptResponse({
text: '\n' text: '\n'
}) })
}); });
}
const chatHistories = getHistories(history, histories); const chatHistories = getHistories(history, histories);
const { files } = chatValue2RuntimePrompt(query);
const { flowResponses, flowUsages, assistantResponses } = await dispatchWorkFlowV1({ const { flowResponses, flowUsages, assistantResponses } = await dispatchWorkFlow({
...props, ...props,
appId: app.id, app: appData,
modules: setEntryEntries(appData.modules), runtimeNodes: storeNodes2RuntimeNodes(
runtimeModules: undefined, // must reset appData.modules,
getWorkflowEntryNodeIds(appData.modules)
),
runtimeEdges: initWorkflowEdgeStatus(appData.edges),
histories: chatHistories, histories: chatHistories,
inputFiles, query: runtimePrompt2ChatsValue({
startParams: { files,
userChatInput text: userChatInput
} }),
variables: variables
}); });
const completeMessages = chatHistories.concat([ const completeMessages = chatHistories.concat([
{ {
obj: ChatRoleEnum.Human, obj: ChatRoleEnum.Human,
value: runtimePrompt2ChatsValue({ value: query
files: inputFiles,
text: userChatInput
})
}, },
{ {
obj: ChatRoleEnum.AI, obj: ChatRoleEnum.AI,
......
...@@ -11,18 +11,14 @@ import { ...@@ -11,18 +11,14 @@ import {
ChatCompletionAssistantMessageParam ChatCompletionAssistantMessageParam
} from '@fastgpt/global/core/ai/type.d'; } from '@fastgpt/global/core/ai/type.d';
import { NextApiResponse } from 'next'; import { NextApiResponse } from 'next';
import { import { responseWriteController } from '../../../../../common/response';
responseWrite,
responseWriteController,
responseWriteNodeStatus
} from '../../../../../common/response';
import { SseResponseEventEnum } from '@fastgpt/global/core/workflow/runtime/constants'; import { SseResponseEventEnum } from '@fastgpt/global/core/workflow/runtime/constants';
import { textAdaptGptResponse } from '@fastgpt/global/core/workflow/runtime/utils'; import { textAdaptGptResponse } from '@fastgpt/global/core/workflow/runtime/utils';
import { ChatCompletionRequestMessageRoleEnum } from '@fastgpt/global/core/ai/constants'; import { ChatCompletionRequestMessageRoleEnum } from '@fastgpt/global/core/ai/constants';
import { dispatchWorkFlow } from '../../index'; import { dispatchWorkFlow } from '../../index';
import { DispatchToolModuleProps, RunToolResponse, ToolNodeItemType } from './type.d'; import { DispatchToolModuleProps, RunToolResponse, ToolNodeItemType } from './type.d';
import json5 from 'json5'; import json5 from 'json5';
import { DispatchFlowResponse } from '../../type'; import { DispatchFlowResponse, WorkflowResponseType } from '../../type';
import { countGptMessagesTokens } from '../../../../../common/string/tiktoken/index'; import { countGptMessagesTokens } from '../../../../../common/string/tiktoken/index';
import { getNanoid, sliceStrStartEnd } from '@fastgpt/global/common/string/tools'; import { getNanoid, sliceStrStartEnd } from '@fastgpt/global/common/string/tools';
import { AIChatItemType } from '@fastgpt/global/core/chat/type'; import { AIChatItemType } from '@fastgpt/global/core/chat/type';
...@@ -50,9 +46,9 @@ export const runToolWithFunctionCall = async ( ...@@ -50,9 +46,9 @@ export const runToolWithFunctionCall = async (
res, res,
requestOrigin, requestOrigin,
runtimeNodes, runtimeNodes,
detail = false,
node, node,
stream, stream,
workflowStreamResponse,
params: { temperature = 0, maxToken = 4000, aiChatVision } params: { temperature = 0, maxToken = 4000, aiChatVision }
} = props; } = props;
const assistantResponses = response?.assistantResponses || []; const assistantResponses = response?.assistantResponses || [];
...@@ -143,9 +139,9 @@ export const runToolWithFunctionCall = async ( ...@@ -143,9 +139,9 @@ export const runToolWithFunctionCall = async (
if (res && stream) { if (res && stream) {
return streamResponse({ return streamResponse({
res, res,
detail,
toolNodes, toolNodes,
stream: aiResponse stream: aiResponse,
workflowStreamResponse
}); });
} else { } else {
const result = aiResponse as ChatCompletion; const result = aiResponse as ChatCompletion;
...@@ -216,11 +212,9 @@ export const runToolWithFunctionCall = async ( ...@@ -216,11 +212,9 @@ export const runToolWithFunctionCall = async (
content: stringToolResponse content: stringToolResponse
}; };
if (stream && detail) { workflowStreamResponse?.({
responseWrite({
res,
event: SseResponseEventEnum.toolResponse, event: SseResponseEventEnum.toolResponse,
data: JSON.stringify({ data: {
tool: { tool: {
id: tool.id, id: tool.id,
toolName: '', toolName: '',
...@@ -228,9 +222,8 @@ export const runToolWithFunctionCall = async ( ...@@ -228,9 +222,8 @@ export const runToolWithFunctionCall = async (
params: '', params: '',
response: sliceStrStartEnd(stringToolResponse, 500, 500) response: sliceStrStartEnd(stringToolResponse, 500, 500)
} }
})
});
} }
});
return { return {
toolRunResponse, toolRunResponse,
...@@ -260,12 +253,14 @@ export const runToolWithFunctionCall = async ( ...@@ -260,12 +253,14 @@ export const runToolWithFunctionCall = async (
]; ];
// console.log(tokens, 'tool'); // console.log(tokens, 'tool');
if (stream && detail) { // Run tool status
responseWriteNodeStatus({ workflowStreamResponse?.({
res, event: SseResponseEventEnum.flowNodeStatus,
data: {
status: 'running',
name: node.name name: node.name
});
} }
});
// tool assistant // tool assistant
const toolAssistants = toolsRunResponse const toolAssistants = toolsRunResponse
...@@ -337,14 +332,14 @@ export const runToolWithFunctionCall = async ( ...@@ -337,14 +332,14 @@ export const runToolWithFunctionCall = async (
async function streamResponse({ async function streamResponse({
res, res,
detail,
toolNodes, toolNodes,
stream stream,
workflowStreamResponse
}: { }: {
res: NextApiResponse; res: NextApiResponse;
detail: boolean;
toolNodes: ToolNodeItemType[]; toolNodes: ToolNodeItemType[];
stream: StreamChatType; stream: StreamChatType;
workflowStreamResponse?: WorkflowResponseType;
}) { }) {
const write = responseWriteController({ const write = responseWriteController({
res, res,
...@@ -367,9 +362,9 @@ async function streamResponse({ ...@@ -367,9 +362,9 @@ async function streamResponse({
const content = responseChoice?.content || ''; const content = responseChoice?.content || '';
textAnswer += content; textAnswer += content;
responseWrite({ workflowStreamResponse?.({
write, write,
event: detail ? SseResponseEventEnum.answer : undefined, event: SseResponseEventEnum.answer,
data: textAdaptGptResponse({ data: textAdaptGptResponse({
text: content text: content
}) })
...@@ -397,11 +392,10 @@ async function streamResponse({ ...@@ -397,11 +392,10 @@ async function streamResponse({
toolAvatar: toolNode.avatar toolAvatar: toolNode.avatar
}); });
if (detail) { workflowStreamResponse?.({
responseWrite({
write, write,
event: SseResponseEventEnum.toolCall, event: SseResponseEventEnum.toolCall,
data: JSON.stringify({ data: {
tool: { tool: {
id: functionId, id: functionId,
toolName: toolNode.name, toolName: toolNode.name,
...@@ -410,9 +404,8 @@ async function streamResponse({ ...@@ -410,9 +404,8 @@ async function streamResponse({
params: functionCall.arguments, params: functionCall.arguments,
response: '' response: ''
} }
})
});
} }
});
} }
continue; continue;
...@@ -424,11 +417,10 @@ async function streamResponse({ ...@@ -424,11 +417,10 @@ async function streamResponse({
if (currentTool) { if (currentTool) {
currentTool.arguments += arg; currentTool.arguments += arg;
if (detail) { workflowStreamResponse?.({
responseWrite({
write, write,
event: SseResponseEventEnum.toolParams, event: SseResponseEventEnum.toolParams,
data: JSON.stringify({ data: {
tool: { tool: {
id: functionId, id: functionId,
toolName: '', toolName: '',
...@@ -436,9 +428,8 @@ async function streamResponse({ ...@@ -436,9 +428,8 @@ async function streamResponse({
params: arg, params: arg,
response: '' response: ''
} }
})
});
} }
});
} }
} }
} }
......
...@@ -8,11 +8,7 @@ import { ...@@ -8,11 +8,7 @@ import {
ChatCompletionAssistantMessageParam ChatCompletionAssistantMessageParam
} from '@fastgpt/global/core/ai/type'; } from '@fastgpt/global/core/ai/type';
import { NextApiResponse } from 'next'; import { NextApiResponse } from 'next';
import { import { responseWriteController } from '../../../../../common/response';
responseWrite,
responseWriteController,
responseWriteNodeStatus
} from '../../../../../common/response';
import { SseResponseEventEnum } from '@fastgpt/global/core/workflow/runtime/constants'; import { SseResponseEventEnum } from '@fastgpt/global/core/workflow/runtime/constants';
import { textAdaptGptResponse } from '@fastgpt/global/core/workflow/runtime/utils'; import { textAdaptGptResponse } from '@fastgpt/global/core/workflow/runtime/utils';
import { ChatCompletionRequestMessageRoleEnum } from '@fastgpt/global/core/ai/constants'; import { ChatCompletionRequestMessageRoleEnum } from '@fastgpt/global/core/ai/constants';
...@@ -30,6 +26,7 @@ import { AIChatItemType } from '@fastgpt/global/core/chat/type'; ...@@ -30,6 +26,7 @@ import { AIChatItemType } from '@fastgpt/global/core/chat/type';
import { GPTMessages2Chats } from '@fastgpt/global/core/chat/adapt'; import { GPTMessages2Chats } from '@fastgpt/global/core/chat/adapt';
import { updateToolInputValue } from './utils'; import { updateToolInputValue } from './utils';
import { computedMaxToken, computedTemperature } from '../../../../ai/utils'; import { computedMaxToken, computedTemperature } from '../../../../ai/utils';
import { WorkflowResponseType } from '../../type';
type FunctionCallCompletion = { type FunctionCallCompletion = {
id: string; id: string;
...@@ -56,9 +53,9 @@ export const runToolWithPromptCall = async ( ...@@ -56,9 +53,9 @@ export const runToolWithPromptCall = async (
res, res,
requestOrigin, requestOrigin,
runtimeNodes, runtimeNodes,
detail = false,
node, node,
stream, stream,
workflowStreamResponse,
params: { temperature = 0, maxToken = 4000, aiChatVision } params: { temperature = 0, maxToken = 4000, aiChatVision }
} = props; } = props;
const assistantResponses = response?.assistantResponses || []; const assistantResponses = response?.assistantResponses || [];
...@@ -143,9 +140,9 @@ export const runToolWithPromptCall = async ( ...@@ -143,9 +140,9 @@ export const runToolWithPromptCall = async (
if (res && stream) { if (res && stream) {
const { answer } = await streamResponse({ const { answer } = await streamResponse({
res, res,
detail,
toolNodes, toolNodes,
stream: aiResponse stream: aiResponse,
workflowStreamResponse
}); });
return answer; return answer;
...@@ -159,9 +156,8 @@ export const runToolWithPromptCall = async ( ...@@ -159,9 +156,8 @@ export const runToolWithPromptCall = async (
const { answer: replaceAnswer, toolJson } = parseAnswer(answer); const { answer: replaceAnswer, toolJson } = parseAnswer(answer);
// No tools // No tools
if (!toolJson) { if (!toolJson) {
if (replaceAnswer === ERROR_TEXT && stream && detail) { if (replaceAnswer === ERROR_TEXT) {
responseWrite({ workflowStreamResponse?.({
res,
event: SseResponseEventEnum.answer, event: SseResponseEventEnum.answer,
data: textAdaptGptResponse({ data: textAdaptGptResponse({
text: replaceAnswer text: replaceAnswer
...@@ -206,11 +202,9 @@ export const runToolWithPromptCall = async ( ...@@ -206,11 +202,9 @@ export const runToolWithPromptCall = async (
})(); })();
// SSE response to client // SSE response to client
if (stream && detail) { workflowStreamResponse?.({
responseWrite({
res,
event: SseResponseEventEnum.toolCall, event: SseResponseEventEnum.toolCall,
data: JSON.stringify({ data: {
tool: { tool: {
id: toolJson.id, id: toolJson.id,
toolName: toolNode.name, toolName: toolNode.name,
...@@ -219,9 +213,8 @@ export const runToolWithPromptCall = async ( ...@@ -219,9 +213,8 @@ export const runToolWithPromptCall = async (
params: toolJson.arguments, params: toolJson.arguments,
response: '' response: ''
} }
})
});
} }
});
const moduleRunResponse = await dispatchWorkFlow({ const moduleRunResponse = await dispatchWorkFlow({
...props, ...props,
...@@ -245,11 +238,9 @@ export const runToolWithPromptCall = async ( ...@@ -245,11 +238,9 @@ export const runToolWithPromptCall = async (
return moduleRunResponse.toolResponses ? String(moduleRunResponse.toolResponses) : 'none'; return moduleRunResponse.toolResponses ? String(moduleRunResponse.toolResponses) : 'none';
})(); })();
if (stream && detail) { workflowStreamResponse?.({
responseWrite({
res,
event: SseResponseEventEnum.toolResponse, event: SseResponseEventEnum.toolResponse,
data: JSON.stringify({ data: {
tool: { tool: {
id: toolJson.id, id: toolJson.id,
toolName: '', toolName: '',
...@@ -257,9 +248,8 @@ export const runToolWithPromptCall = async ( ...@@ -257,9 +248,8 @@ export const runToolWithPromptCall = async (
params: '', params: '',
response: sliceStrStartEnd(stringToolResponse, 500, 500) response: sliceStrStartEnd(stringToolResponse, 500, 500)
} }
})
});
} }
});
return { return {
moduleRunResponse, moduleRunResponse,
...@@ -267,12 +257,14 @@ export const runToolWithPromptCall = async ( ...@@ -267,12 +257,14 @@ export const runToolWithPromptCall = async (
}; };
})(); })();
if (stream && detail) { // Run tool status
responseWriteNodeStatus({ workflowStreamResponse?.({
res, event: SseResponseEventEnum.flowNodeStatus,
data: {
status: 'running',
name: node.name name: node.name
});
} }
});
// 合并工具调用的结果,使用 functionCall 格式存储。 // 合并工具调用的结果,使用 functionCall 格式存储。
const assistantToolMsgParams: ChatCompletionAssistantMessageParam = { const assistantToolMsgParams: ChatCompletionAssistantMessageParam = {
...@@ -340,13 +332,13 @@ ANSWER: `; ...@@ -340,13 +332,13 @@ ANSWER: `;
async function streamResponse({ async function streamResponse({
res, res,
detail, stream,
stream workflowStreamResponse
}: { }: {
res: NextApiResponse; res: NextApiResponse;
detail: boolean;
toolNodes: ToolNodeItemType[]; toolNodes: ToolNodeItemType[];
stream: StreamChatType; stream: StreamChatType;
workflowStreamResponse?: WorkflowResponseType;
}) { }) {
const write = responseWriteController({ const write = responseWriteController({
res, res,
...@@ -370,9 +362,9 @@ async function streamResponse({ ...@@ -370,9 +362,9 @@ async function streamResponse({
textAnswer += content; textAnswer += content;
if (startResponseWrite) { if (startResponseWrite) {
responseWrite({ workflowStreamResponse?.({
write, write,
event: detail ? SseResponseEventEnum.answer : undefined, event: SseResponseEventEnum.answer,
data: textAdaptGptResponse({ data: textAdaptGptResponse({
text: content text: content
}) })
...@@ -384,9 +376,9 @@ async function streamResponse({ ...@@ -384,9 +376,9 @@ async function streamResponse({
// find first : index // find first : index
const firstIndex = textAnswer.indexOf(':'); const firstIndex = textAnswer.indexOf(':');
textAnswer = textAnswer.substring(firstIndex + 1).trim(); textAnswer = textAnswer.substring(firstIndex + 1).trim();
responseWrite({ workflowStreamResponse?.({
write, write,
event: detail ? SseResponseEventEnum.answer : undefined, event: SseResponseEventEnum.answer,
data: textAdaptGptResponse({ data: textAdaptGptResponse({
text: textAnswer text: textAnswer
}) })
......
...@@ -12,24 +12,21 @@ import { ...@@ -12,24 +12,21 @@ import {
ChatCompletionAssistantMessageParam ChatCompletionAssistantMessageParam
} from '@fastgpt/global/core/ai/type'; } from '@fastgpt/global/core/ai/type';
import { NextApiResponse } from 'next'; import { NextApiResponse } from 'next';
import { import { responseWriteController } from '../../../../../common/response';
responseWrite,
responseWriteController,
responseWriteNodeStatus
} from '../../../../../common/response';
import { SseResponseEventEnum } from '@fastgpt/global/core/workflow/runtime/constants'; import { SseResponseEventEnum } from '@fastgpt/global/core/workflow/runtime/constants';
import { textAdaptGptResponse } from '@fastgpt/global/core/workflow/runtime/utils'; import { textAdaptGptResponse } from '@fastgpt/global/core/workflow/runtime/utils';
import { ChatCompletionRequestMessageRoleEnum } from '@fastgpt/global/core/ai/constants'; import { ChatCompletionRequestMessageRoleEnum } from '@fastgpt/global/core/ai/constants';
import { dispatchWorkFlow } from '../../index'; import { dispatchWorkFlow } from '../../index';
import { DispatchToolModuleProps, RunToolResponse, ToolNodeItemType } from './type.d'; import { DispatchToolModuleProps, RunToolResponse, ToolNodeItemType } from './type.d';
import json5 from 'json5'; import json5 from 'json5';
import { DispatchFlowResponse } from '../../type'; import { DispatchFlowResponse, WorkflowResponseType } from '../../type';
import { countGptMessagesTokens } from '../../../../../common/string/tiktoken/index'; import { countGptMessagesTokens } from '../../../../../common/string/tiktoken/index';
import { GPTMessages2Chats } from '@fastgpt/global/core/chat/adapt'; import { GPTMessages2Chats } from '@fastgpt/global/core/chat/adapt';
import { AIChatItemType } from '@fastgpt/global/core/chat/type'; import { AIChatItemType } from '@fastgpt/global/core/chat/type';
import { updateToolInputValue } from './utils'; import { updateToolInputValue } from './utils';
import { computedMaxToken, computedTemperature } from '../../../../ai/utils'; import { computedMaxToken, computedTemperature } from '../../../../ai/utils';
import { sliceStrStartEnd } from '@fastgpt/global/common/string/tools'; import { sliceStrStartEnd } from '@fastgpt/global/common/string/tools';
import { addLog } from '../../../../../common/system/log';
type ToolRunResponseType = { type ToolRunResponseType = {
toolRunResponse: DispatchFlowResponse; toolRunResponse: DispatchFlowResponse;
...@@ -58,9 +55,9 @@ export const runToolWithToolChoice = async ( ...@@ -58,9 +55,9 @@ export const runToolWithToolChoice = async (
res, res,
requestOrigin, requestOrigin,
runtimeNodes, runtimeNodes,
detail = false,
node, node,
stream, stream,
workflowStreamResponse,
params: { temperature = 0, maxToken = 4000, aiChatVision } params: { temperature = 0, maxToken = 4000, aiChatVision }
} = props; } = props;
const assistantResponses = response?.assistantResponses || []; const assistantResponses = response?.assistantResponses || [];
...@@ -145,6 +142,8 @@ export const runToolWithToolChoice = async ( ...@@ -145,6 +142,8 @@ export const runToolWithToolChoice = async (
const ai = getAIApi({ const ai = getAIApi({
timeout: 480000 timeout: 480000
}); });
try {
const aiResponse = await ai.chat.completions.create(requestBody, { const aiResponse = await ai.chat.completions.create(requestBody, {
headers: { headers: {
Accept: 'application/json, text/plain, */*' Accept: 'application/json, text/plain, */*'
...@@ -155,7 +154,7 @@ export const runToolWithToolChoice = async ( ...@@ -155,7 +154,7 @@ export const runToolWithToolChoice = async (
if (res && stream) { if (res && stream) {
return streamResponse({ return streamResponse({
res, res,
detail, workflowStreamResponse,
toolNodes, toolNodes,
stream: aiResponse stream: aiResponse
}); });
...@@ -225,11 +224,9 @@ export const runToolWithToolChoice = async ( ...@@ -225,11 +224,9 @@ export const runToolWithToolChoice = async (
content: stringToolResponse content: stringToolResponse
}; };
if (stream && detail) { workflowStreamResponse?.({
responseWrite({
res,
event: SseResponseEventEnum.toolResponse, event: SseResponseEventEnum.toolResponse,
data: JSON.stringify({ data: {
tool: { tool: {
id: tool.id, id: tool.id,
toolName: '', toolName: '',
...@@ -237,9 +234,8 @@ export const runToolWithToolChoice = async ( ...@@ -237,9 +234,8 @@ export const runToolWithToolChoice = async (
params: '', params: '',
response: sliceStrStartEnd(stringToolResponse, 500, 500) response: sliceStrStartEnd(stringToolResponse, 500, 500)
} }
})
});
} }
});
return { return {
toolRunResponse, toolRunResponse,
...@@ -268,12 +264,14 @@ export const runToolWithToolChoice = async ( ...@@ -268,12 +264,14 @@ export const runToolWithToolChoice = async (
// console.log(tokens, 'tool'); // console.log(tokens, 'tool');
if (stream && detail) { // Run tool status
responseWriteNodeStatus({ workflowStreamResponse?.({
res, event: SseResponseEventEnum.flowNodeStatus,
data: {
status: 'running',
name: node.name name: node.name
});
} }
});
// tool assistant // tool assistant
const toolAssistants = toolsRunResponse const toolAssistants = toolsRunResponse
...@@ -342,18 +340,24 @@ export const runToolWithToolChoice = async ( ...@@ -342,18 +340,24 @@ export const runToolWithToolChoice = async (
assistantResponses: [...assistantResponses, ...toolNodeAssistant.value] assistantResponses: [...assistantResponses, ...toolNodeAssistant.value]
}; };
} }
} catch (error) {
addLog.warn(`LLM response error`, {
requestBody
});
return Promise.reject(error);
}
}; };
async function streamResponse({ async function streamResponse({
res, res,
detail,
toolNodes, toolNodes,
stream stream,
workflowStreamResponse
}: { }: {
res: NextApiResponse; res: NextApiResponse;
detail: boolean;
toolNodes: ToolNodeItemType[]; toolNodes: ToolNodeItemType[];
stream: StreamChatType; stream: StreamChatType;
workflowStreamResponse?: WorkflowResponseType;
}) { }) {
const write = responseWriteController({ const write = responseWriteController({
res, res,
...@@ -375,9 +379,9 @@ async function streamResponse({ ...@@ -375,9 +379,9 @@ async function streamResponse({
const content = responseChoice.content || ''; const content = responseChoice.content || '';
textAnswer += content; textAnswer += content;
responseWrite({ workflowStreamResponse?.({
write, write,
event: detail ? SseResponseEventEnum.answer : undefined, event: SseResponseEventEnum.answer,
data: textAdaptGptResponse({ data: textAdaptGptResponse({
text: content text: content
}) })
...@@ -405,11 +409,9 @@ async function streamResponse({ ...@@ -405,11 +409,9 @@ async function streamResponse({
toolAvatar: toolNode.avatar toolAvatar: toolNode.avatar
}); });
if (detail) { workflowStreamResponse?.({
responseWrite({
write,
event: SseResponseEventEnum.toolCall, event: SseResponseEventEnum.toolCall,
data: JSON.stringify({ data: {
tool: { tool: {
id: toolCall.id, id: toolCall.id,
toolName: toolNode.name, toolName: toolNode.name,
...@@ -418,9 +420,8 @@ async function streamResponse({ ...@@ -418,9 +420,8 @@ async function streamResponse({
params: toolCall.function.arguments, params: toolCall.function.arguments,
response: '' response: ''
} }
})
});
} }
});
continue; continue;
} }
...@@ -437,11 +438,10 @@ async function streamResponse({ ...@@ -437,11 +438,10 @@ async function streamResponse({
if (currentTool) { if (currentTool) {
currentTool.function.arguments += arg; currentTool.function.arguments += arg;
if (detail) { workflowStreamResponse?.({
responseWrite({
write, write,
event: SseResponseEventEnum.toolParams, event: SseResponseEventEnum.toolParams,
data: JSON.stringify({ data: {
tool: { tool: {
id: currentTool.id, id: currentTool.id,
toolName: '', toolName: '',
...@@ -449,9 +449,8 @@ async function streamResponse({ ...@@ -449,9 +449,8 @@ async function streamResponse({
params: arg, params: arg,
response: '' response: ''
} }
})
});
} }
});
} }
} }
} }
......
...@@ -31,7 +31,7 @@ import { ...@@ -31,7 +31,7 @@ import {
import type { AIChatNodeProps } from '@fastgpt/global/core/workflow/runtime/type.d'; import type { AIChatNodeProps } from '@fastgpt/global/core/workflow/runtime/type.d';
import { replaceVariable } from '@fastgpt/global/common/string/tools'; import { replaceVariable } from '@fastgpt/global/common/string/tools';
import type { ModuleDispatchProps } from '@fastgpt/global/core/workflow/runtime/type'; import type { ModuleDispatchProps } from '@fastgpt/global/core/workflow/runtime/type';
import { responseWrite, responseWriteController } from '../../../../common/response'; import { responseWriteController } from '../../../../common/response';
import { getLLMModel, ModelTypeEnum } from '../../../ai/model'; import { getLLMModel, ModelTypeEnum } from '../../../ai/model';
import type { SearchDataResponseItemType } from '@fastgpt/global/core/dataset/type'; import type { SearchDataResponseItemType } from '@fastgpt/global/core/dataset/type';
import { NodeInputKeyEnum, NodeOutputKeyEnum } from '@fastgpt/global/core/workflow/constants'; import { NodeInputKeyEnum, NodeOutputKeyEnum } from '@fastgpt/global/core/workflow/constants';
...@@ -41,6 +41,7 @@ import { filterSearchResultsByMaxChars } from '../../utils'; ...@@ -41,6 +41,7 @@ import { filterSearchResultsByMaxChars } from '../../utils';
import { getHistoryPreview } from '@fastgpt/global/core/chat/utils'; import { getHistoryPreview } from '@fastgpt/global/core/chat/utils';
import { addLog } from '../../../../common/system/log'; import { addLog } from '../../../../common/system/log';
import { computedMaxToken, computedTemperature } from '../../../ai/utils'; import { computedMaxToken, computedTemperature } from '../../../ai/utils';
import { WorkflowResponseType } from '../type';
export type ChatProps = ModuleDispatchProps< export type ChatProps = ModuleDispatchProps<
AIChatNodeProps & { AIChatNodeProps & {
...@@ -60,11 +61,11 @@ export const dispatchChatCompletion = async (props: ChatProps): Promise<ChatResp ...@@ -60,11 +61,11 @@ export const dispatchChatCompletion = async (props: ChatProps): Promise<ChatResp
res, res,
requestOrigin, requestOrigin,
stream = false, stream = false,
detail = false,
user, user,
histories, histories,
node: { name }, node: { name },
query, query,
workflowStreamResponse,
params: { params: {
model, model,
temperature = 0, temperature = 0,
...@@ -179,8 +180,8 @@ export const dispatchChatCompletion = async (props: ChatProps): Promise<ChatResp ...@@ -179,8 +180,8 @@ export const dispatchChatCompletion = async (props: ChatProps): Promise<ChatResp
// sse response // sse response
const { answer } = await streamResponse({ const { answer } = await streamResponse({
res, res,
detail, stream: response,
stream: response workflowStreamResponse
}); });
if (!answer) { if (!answer) {
...@@ -340,12 +341,12 @@ async function getChatMessages({ ...@@ -340,12 +341,12 @@ async function getChatMessages({
async function streamResponse({ async function streamResponse({
res, res,
detail, stream,
stream workflowStreamResponse
}: { }: {
res: NextApiResponse; res: NextApiResponse;
detail: boolean;
stream: StreamChatType; stream: StreamChatType;
workflowStreamResponse?: WorkflowResponseType;
}) { }) {
const write = responseWriteController({ const write = responseWriteController({
res, res,
...@@ -360,9 +361,9 @@ async function streamResponse({ ...@@ -360,9 +361,9 @@ async function streamResponse({
const content = part.choices?.[0]?.delta?.content || ''; const content = part.choices?.[0]?.delta?.content || '';
answer += content; answer += content;
responseWrite({ workflowStreamResponse?.({
write, write,
event: detail ? SseResponseEventEnum.answer : undefined, event: SseResponseEventEnum.answer,
data: textAdaptGptResponse({ data: textAdaptGptResponse({
text: content text: content
}) })
......
import { NextApiResponse } from 'next';
import { NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants'; import { NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants';
import { import {
DispatchNodeResponseKeyEnum, DispatchNodeResponseKeyEnum,
...@@ -21,7 +20,6 @@ import { ...@@ -21,7 +20,6 @@ import {
FlowNodeTypeEnum FlowNodeTypeEnum
} from '@fastgpt/global/core/workflow/node/constant'; } from '@fastgpt/global/core/workflow/node/constant';
import { replaceVariable } from '@fastgpt/global/common/string/tools'; import { replaceVariable } from '@fastgpt/global/common/string/tools';
import { responseWrite, responseWriteNodeStatus } from '../../../common/response';
import { getSystemTime } from '@fastgpt/global/common/time/timezone'; import { getSystemTime } from '@fastgpt/global/common/time/timezone';
import { replaceVariableLabel } from '@fastgpt/global/core/workflow/utils'; import { replaceVariableLabel } from '@fastgpt/global/core/workflow/utils';
...@@ -41,8 +39,7 @@ import { dispatchPluginOutput } from './plugin/runOutput'; ...@@ -41,8 +39,7 @@ import { dispatchPluginOutput } from './plugin/runOutput';
import { removeSystemVariable, valueTypeFormat } from './utils'; import { removeSystemVariable, valueTypeFormat } from './utils';
import { import {
filterWorkflowEdges, filterWorkflowEdges,
checkNodeRunStatus, checkNodeRunStatus
getLastInteractiveValue
} from '@fastgpt/global/core/workflow/runtime/utils'; } from '@fastgpt/global/core/workflow/runtime/utils';
import { ChatNodeUsageType } from '@fastgpt/global/support/wallet/bill/type'; import { ChatNodeUsageType } from '@fastgpt/global/support/wallet/bill/type';
import { dispatchRunTools } from './agent/runTool/index'; import { dispatchRunTools } from './agent/runTool/index';
...@@ -62,12 +59,11 @@ import { dispatchTextEditor } from './tools/textEditor'; ...@@ -62,12 +59,11 @@ import { dispatchTextEditor } from './tools/textEditor';
import { dispatchCustomFeedback } from './tools/customFeedback'; import { dispatchCustomFeedback } from './tools/customFeedback';
import { dispatchReadFiles } from './tools/readFiles'; import { dispatchReadFiles } from './tools/readFiles';
import { dispatchUserSelect } from './interactive/userSelect'; import { dispatchUserSelect } from './interactive/userSelect';
import { FlowNodeOutputItemType } from '@fastgpt/global/core/workflow/type/io';
import { import {
InteractiveNodeResponseItemType, InteractiveNodeResponseItemType,
UserInteractiveType,
UserSelectInteractive UserSelectInteractive
} from '@fastgpt/global/core/workflow/template/system/userSelect/type'; } from '@fastgpt/global/core/workflow/template/system/userSelect/type';
import { dispatchRunAppNode } from './agent/runAppModule';
const callbackMap: Record<FlowNodeTypeEnum, Function> = { const callbackMap: Record<FlowNodeTypeEnum, Function> = {
[FlowNodeTypeEnum.workflowStart]: dispatchWorkflowStart, [FlowNodeTypeEnum.workflowStart]: dispatchWorkflowStart,
...@@ -79,6 +75,7 @@ const callbackMap: Record<FlowNodeTypeEnum, Function> = { ...@@ -79,6 +75,7 @@ const callbackMap: Record<FlowNodeTypeEnum, Function> = {
[FlowNodeTypeEnum.contentExtract]: dispatchContentExtract, [FlowNodeTypeEnum.contentExtract]: dispatchContentExtract,
[FlowNodeTypeEnum.httpRequest468]: dispatchHttp468Request, [FlowNodeTypeEnum.httpRequest468]: dispatchHttp468Request,
[FlowNodeTypeEnum.runApp]: dispatchAppRequest, [FlowNodeTypeEnum.runApp]: dispatchAppRequest,
[FlowNodeTypeEnum.appModule]: dispatchRunAppNode,
[FlowNodeTypeEnum.pluginModule]: dispatchRunPlugin, [FlowNodeTypeEnum.pluginModule]: dispatchRunPlugin,
[FlowNodeTypeEnum.pluginInput]: dispatchPluginInput, [FlowNodeTypeEnum.pluginInput]: dispatchPluginInput,
[FlowNodeTypeEnum.pluginOutput]: dispatchPluginOutput, [FlowNodeTypeEnum.pluginOutput]: dispatchPluginOutput,
...@@ -115,7 +112,6 @@ export async function dispatchWorkFlow(data: Props): Promise<DispatchFlowRespons ...@@ -115,7 +112,6 @@ export async function dispatchWorkFlow(data: Props): Promise<DispatchFlowRespons
variables = {}, variables = {},
user, user,
stream = false, stream = false,
detail = false,
...props ...props
} = data; } = data;
...@@ -261,13 +257,10 @@ export async function dispatchWorkFlow(data: Props): Promise<DispatchFlowRespons ...@@ -261,13 +257,10 @@ export async function dispatchWorkFlow(data: Props): Promise<DispatchFlowRespons
nodeOutputs nodeOutputs
}; };
if (stream && res) { props.workflowStreamResponse?.({
responseWrite({
res,
event: SseResponseEventEnum.interactive, event: SseResponseEventEnum.interactive,
data: JSON.stringify({ interactive: interactiveResult }) data: { interactive: interactiveResult }
}); });
}
return { return {
type: ChatItemValueTypeEnum.interactive, type: ChatItemValueTypeEnum.interactive,
...@@ -401,11 +394,13 @@ export async function dispatchWorkFlow(data: Props): Promise<DispatchFlowRespons ...@@ -401,11 +394,13 @@ export async function dispatchWorkFlow(data: Props): Promise<DispatchFlowRespons
} }
async function nodeRunWithActive(node: RuntimeNodeItemType) { async function nodeRunWithActive(node: RuntimeNodeItemType) {
// push run status messages // push run status messages
if (res && stream && detail && node.showStatus) { if (node.showStatus) {
responseStatus({ props.workflowStreamResponse?.({
res, event: SseResponseEventEnum.flowNodeStatus,
name: node.name, data: {
status: 'running' status: 'running',
name: node.name
}
}); });
} }
const startTime = Date.now(); const startTime = Date.now();
...@@ -420,7 +415,6 @@ export async function dispatchWorkFlow(data: Props): Promise<DispatchFlowRespons ...@@ -420,7 +415,6 @@ export async function dispatchWorkFlow(data: Props): Promise<DispatchFlowRespons
histories, histories,
user, user,
stream, stream,
detail,
node, node,
runtimeNodes, runtimeNodes,
runtimeEdges, runtimeEdges,
...@@ -510,23 +504,6 @@ export async function dispatchWorkFlow(data: Props): Promise<DispatchFlowRespons ...@@ -510,23 +504,6 @@ export async function dispatchWorkFlow(data: Props): Promise<DispatchFlowRespons
}; };
} }
/* sse response modules staus */
export function responseStatus({
res,
status,
name
}: {
res: NextApiResponse;
status?: 'running' | 'finish';
name?: string;
}) {
if (!name) return;
responseWriteNodeStatus({
res,
name
});
}
/* get system variable */ /* get system variable */
export function getSystemVariable({ export function getSystemVariable({
user, user,
......
...@@ -14,7 +14,6 @@ import type { ...@@ -14,7 +14,6 @@ import type {
} from '@fastgpt/global/core/workflow/template/system/userSelect/type'; } from '@fastgpt/global/core/workflow/template/system/userSelect/type';
import { updateUserSelectedResult } from '../../../chat/controller'; import { updateUserSelectedResult } from '../../../chat/controller';
import { textAdaptGptResponse } from '@fastgpt/global/core/workflow/runtime/utils'; import { textAdaptGptResponse } from '@fastgpt/global/core/workflow/runtime/utils';
import { responseWrite } from '../../../../common/response';
import { chatValue2RuntimePrompt } from '@fastgpt/global/core/chat/adapt'; import { chatValue2RuntimePrompt } from '@fastgpt/global/core/chat/adapt';
type Props = ModuleDispatchProps<{ type Props = ModuleDispatchProps<{
...@@ -29,10 +28,7 @@ type UserSelectResponse = DispatchNodeResultType<{ ...@@ -29,10 +28,7 @@ type UserSelectResponse = DispatchNodeResultType<{
export const dispatchUserSelect = async (props: Props): Promise<UserSelectResponse> => { export const dispatchUserSelect = async (props: Props): Promise<UserSelectResponse> => {
const { const {
res, workflowStreamResponse,
detail,
histories,
stream,
app: { _id: appId }, app: { _id: appId },
chatId, chatId,
node: { nodeId, isEntry }, node: { nodeId, isEntry },
...@@ -43,10 +39,9 @@ export const dispatchUserSelect = async (props: Props): Promise<UserSelectRespon ...@@ -43,10 +39,9 @@ export const dispatchUserSelect = async (props: Props): Promise<UserSelectRespon
// Interactive node is not the entry node, return interactive result // Interactive node is not the entry node, return interactive result
if (!isEntry) { if (!isEntry) {
const answerText = description ? `\n${description}` : undefined; const answerText = description ? `\n${description}` : undefined;
if (res && stream && answerText) { if (answerText) {
responseWrite({ workflowStreamResponse?.({
res, event: SseResponseEventEnum.fastAnswer,
event: detail ? SseResponseEventEnum.fastAnswer : undefined,
data: textAdaptGptResponse({ data: textAdaptGptResponse({
text: answerText text: answerText
}) })
......
...@@ -2,7 +2,6 @@ import { ...@@ -2,7 +2,6 @@ import {
DispatchNodeResponseKeyEnum, DispatchNodeResponseKeyEnum,
SseResponseEventEnum SseResponseEventEnum
} from '@fastgpt/global/core/workflow/runtime/constants'; } from '@fastgpt/global/core/workflow/runtime/constants';
import { responseWrite } from '../../../../common/response';
import { textAdaptGptResponse } from '@fastgpt/global/core/workflow/runtime/utils'; import { textAdaptGptResponse } from '@fastgpt/global/core/workflow/runtime/utils';
import type { ModuleDispatchProps } from '@fastgpt/global/core/workflow/runtime/type'; import type { ModuleDispatchProps } from '@fastgpt/global/core/workflow/runtime/type';
import { NodeOutputKeyEnum } from '@fastgpt/global/core/workflow/constants'; import { NodeOutputKeyEnum } from '@fastgpt/global/core/workflow/constants';
...@@ -16,24 +15,19 @@ export type AnswerResponse = DispatchNodeResultType<{ ...@@ -16,24 +15,19 @@ export type AnswerResponse = DispatchNodeResultType<{
export const dispatchAnswer = (props: Record<string, any>): AnswerResponse => { export const dispatchAnswer = (props: Record<string, any>): AnswerResponse => {
const { const {
res, workflowStreamResponse,
detail,
stream,
params: { text = '' } params: { text = '' }
} = props as AnswerProps; } = props as AnswerProps;
const formatText = typeof text === 'string' ? text : JSON.stringify(text, null, 2); const formatText = typeof text === 'string' ? text : JSON.stringify(text, null, 2);
const responseText = `\n${formatText}`; const responseText = `\n${formatText}`;
if (res && stream) { workflowStreamResponse?.({
responseWrite({ event: SseResponseEventEnum.fastAnswer,
res,
event: detail ? SseResponseEventEnum.fastAnswer : undefined,
data: textAdaptGptResponse({ data: textAdaptGptResponse({
text: responseText text: responseText
}) })
}); });
}
return { return {
[NodeOutputKeyEnum.answerText]: responseText, [NodeOutputKeyEnum.answerText]: responseText,
......
...@@ -6,7 +6,6 @@ import type { ModuleDispatchProps } from '@fastgpt/global/core/workflow/runtime/ ...@@ -6,7 +6,6 @@ import type { ModuleDispatchProps } from '@fastgpt/global/core/workflow/runtime/
import { NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants'; import { NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants';
import { DispatchNodeResultType } from '@fastgpt/global/core/workflow/runtime/type'; import { DispatchNodeResultType } from '@fastgpt/global/core/workflow/runtime/type';
import { addCustomFeedbacks } from '../../../chat/controller'; import { addCustomFeedbacks } from '../../../chat/controller';
import { responseWrite } from '../../../../common/response';
import { textAdaptGptResponse } from '@fastgpt/global/core/workflow/runtime/utils'; import { textAdaptGptResponse } from '@fastgpt/global/core/workflow/runtime/utils';
type Props = ModuleDispatchProps<{ type Props = ModuleDispatchProps<{
...@@ -16,12 +15,11 @@ type Response = DispatchNodeResultType<{}>; ...@@ -16,12 +15,11 @@ type Response = DispatchNodeResultType<{}>;
export const dispatchCustomFeedback = (props: Record<string, any>): Response => { export const dispatchCustomFeedback = (props: Record<string, any>): Response => {
const { const {
res,
app: { _id: appId }, app: { _id: appId },
chatId, chatId,
responseChatItemId: chatItemId, responseChatItemId: chatItemId,
stream, stream,
detail, workflowStreamResponse,
params: { system_textareaInput: feedbackText = '' } params: { system_textareaInput: feedbackText = '' }
} = props as Props; } = props as Props;
...@@ -36,9 +34,8 @@ export const dispatchCustomFeedback = (props: Record<string, any>): Response => ...@@ -36,9 +34,8 @@ export const dispatchCustomFeedback = (props: Record<string, any>): Response =>
if (stream) { if (stream) {
if (!chatId || !chatItemId) { if (!chatId || !chatItemId) {
responseWrite({ workflowStreamResponse?.({
res, event: SseResponseEventEnum.fastAnswer,
event: detail ? SseResponseEventEnum.fastAnswer : undefined,
data: textAdaptGptResponse({ data: textAdaptGptResponse({
text: `\n\n**自定义反馈成功: (仅调试模式下展示该内容)**: "${feedbackText}"\n\n` text: `\n\n**自定义反馈成功: (仅调试模式下展示该内容)**: "${feedbackText}"\n\n`
}) })
......
...@@ -14,7 +14,6 @@ import { SERVICE_LOCAL_HOST } from '../../../../common/system/tools'; ...@@ -14,7 +14,6 @@ import { SERVICE_LOCAL_HOST } from '../../../../common/system/tools';
import { addLog } from '../../../../common/system/log'; import { addLog } from '../../../../common/system/log';
import { DispatchNodeResultType } from '@fastgpt/global/core/workflow/runtime/type'; import { DispatchNodeResultType } from '@fastgpt/global/core/workflow/runtime/type';
import { getErrText } from '@fastgpt/global/common/error/utils'; import { getErrText } from '@fastgpt/global/common/error/utils';
import { responseWrite } from '../../../../common/response';
import { textAdaptGptResponse } from '@fastgpt/global/core/workflow/runtime/utils'; import { textAdaptGptResponse } from '@fastgpt/global/core/workflow/runtime/utils';
import { getSystemPluginCb } from '../../../../../plugins/register'; import { getSystemPluginCb } from '../../../../../plugins/register';
...@@ -43,15 +42,13 @@ const UNDEFINED_SIGN = 'UNDEFINED_SIGN'; ...@@ -43,15 +42,13 @@ const UNDEFINED_SIGN = 'UNDEFINED_SIGN';
export const dispatchHttp468Request = async (props: HttpRequestProps): Promise<HttpResponse> => { export const dispatchHttp468Request = async (props: HttpRequestProps): Promise<HttpResponse> => {
let { let {
res,
detail,
app: { _id: appId }, app: { _id: appId },
chatId, chatId,
stream,
responseChatItemId, responseChatItemId,
variables, variables,
node: { outputs }, node: { outputs },
histories, histories,
workflowStreamResponse,
params: { params: {
system_httpMethod: httpMethod = 'POST', system_httpMethod: httpMethod = 'POST',
system_httpReqUrl: httpReqUrl, system_httpReqUrl: httpReqUrl,
...@@ -158,10 +155,9 @@ export const dispatchHttp468Request = async (props: HttpRequestProps): Promise<H ...@@ -158,10 +155,9 @@ export const dispatchHttp468Request = async (props: HttpRequestProps): Promise<H
results[key] = valueTypeFormat(formatResponse[key], output.valueType); results[key] = valueTypeFormat(formatResponse[key], output.valueType);
} }
if (stream && typeof formatResponse[NodeOutputKeyEnum.answerText] === 'string') { if (typeof formatResponse[NodeOutputKeyEnum.answerText] === 'string') {
responseWrite({ workflowStreamResponse?.({
res, event: SseResponseEventEnum.fastAnswer,
event: detail ? SseResponseEventEnum.fastAnswer : undefined,
data: textAdaptGptResponse({ data: textAdaptGptResponse({
text: formatResponse[NodeOutputKeyEnum.answerText] text: formatResponse[NodeOutputKeyEnum.answerText]
}) })
......
...@@ -2,7 +2,6 @@ import type { ChatItemType } from '@fastgpt/global/core/chat/type.d'; ...@@ -2,7 +2,6 @@ import type { ChatItemType } from '@fastgpt/global/core/chat/type.d';
import type { ModuleDispatchProps } from '@fastgpt/global/core/workflow/runtime/type'; import type { ModuleDispatchProps } from '@fastgpt/global/core/workflow/runtime/type';
import { SelectAppItemType } from '@fastgpt/global/core/workflow/template/system/runApp/type'; import { SelectAppItemType } from '@fastgpt/global/core/workflow/template/system/runApp/type';
import { dispatchWorkFlow } from '../index'; import { dispatchWorkFlow } from '../index';
import { responseWrite } from '../../../../common/response';
import { ChatRoleEnum } from '@fastgpt/global/core/chat/constants'; import { ChatRoleEnum } from '@fastgpt/global/core/chat/constants';
import { SseResponseEventEnum } from '@fastgpt/global/core/workflow/runtime/constants'; import { SseResponseEventEnum } from '@fastgpt/global/core/workflow/runtime/constants';
import { import {
...@@ -31,10 +30,8 @@ type Response = DispatchNodeResultType<{ ...@@ -31,10 +30,8 @@ type Response = DispatchNodeResultType<{
export const dispatchAppRequest = async (props: Props): Promise<Response> => { export const dispatchAppRequest = async (props: Props): Promise<Response> => {
const { const {
res,
app: workflowApp, app: workflowApp,
stream, workflowStreamResponse,
detail,
histories, histories,
query, query,
params: { userChatInput, history, app } params: { userChatInput, history, app }
...@@ -51,15 +48,12 @@ export const dispatchAppRequest = async (props: Props): Promise<Response> => { ...@@ -51,15 +48,12 @@ export const dispatchAppRequest = async (props: Props): Promise<Response> => {
per: ReadPermissionVal per: ReadPermissionVal
}); });
if (res && stream) { workflowStreamResponse?.({
responseWrite({ event: SseResponseEventEnum.fastAnswer,
res,
event: detail ? SseResponseEventEnum.answer : undefined,
data: textAdaptGptResponse({ data: textAdaptGptResponse({
text: '\n' text: '\n'
}) })
}); });
}
const chatHistories = getHistories(history, histories); const chatHistories = getHistories(history, histories);
const { files } = chatValue2RuntimePrompt(query); const { files } = chatValue2RuntimePrompt(query);
......
...@@ -8,7 +8,6 @@ import { getReferenceVariableValue } from '@fastgpt/global/core/workflow/runtime ...@@ -8,7 +8,6 @@ import { getReferenceVariableValue } from '@fastgpt/global/core/workflow/runtime
import { TUpdateListItem } from '@fastgpt/global/core/workflow/template/system/variableUpdate/type'; import { TUpdateListItem } from '@fastgpt/global/core/workflow/template/system/variableUpdate/type';
import { ModuleDispatchProps } from '@fastgpt/global/core/workflow/runtime/type'; import { ModuleDispatchProps } from '@fastgpt/global/core/workflow/runtime/type';
import { removeSystemVariable, valueTypeFormat } from '../utils'; import { removeSystemVariable, valueTypeFormat } from '../utils';
import { responseWrite } from '../../../../common/response';
type Props = ModuleDispatchProps<{ type Props = ModuleDispatchProps<{
[NodeInputKeyEnum.updateList]: TUpdateListItem[]; [NodeInputKeyEnum.updateList]: TUpdateListItem[];
...@@ -16,7 +15,7 @@ type Props = ModuleDispatchProps<{ ...@@ -16,7 +15,7 @@ type Props = ModuleDispatchProps<{
type Response = DispatchNodeResultType<{}>; type Response = DispatchNodeResultType<{}>;
export const dispatchUpdateVariable = async (props: Props): Promise<Response> => { export const dispatchUpdateVariable = async (props: Props): Promise<Response> => {
const { res, detail, stream, params, variables, runtimeNodes } = props; const { params, variables, runtimeNodes, workflowStreamResponse } = props;
const { updateList } = params; const { updateList } = params;
updateList.forEach((item) => { updateList.forEach((item) => {
...@@ -54,13 +53,10 @@ export const dispatchUpdateVariable = async (props: Props): Promise<Response> => ...@@ -54,13 +53,10 @@ export const dispatchUpdateVariable = async (props: Props): Promise<Response> =>
} }
}); });
if (detail && stream) { workflowStreamResponse?.({
responseWrite({
res,
event: SseResponseEventEnum.updateVariables, event: SseResponseEventEnum.updateVariables,
data: JSON.stringify(removeSystemVariable(variables)) data: removeSystemVariable(variables)
}); });
}
return { return {
[DispatchNodeResponseKeyEnum.nodeResponse]: { [DispatchNodeResponseKeyEnum.nodeResponse]: {
......
...@@ -4,7 +4,10 @@ import { ...@@ -4,7 +4,10 @@ import {
ChatItemValueItemType, ChatItemValueItemType,
ToolRunResponseItemType ToolRunResponseItemType
} from '@fastgpt/global/core/chat/type'; } from '@fastgpt/global/core/chat/type';
import { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/workflow/runtime/constants'; import {
DispatchNodeResponseKeyEnum,
SseResponseEventEnum
} from '@fastgpt/global/core/workflow/runtime/constants';
import { RuntimeNodeItemType } from '@fastgpt/global/core/workflow/runtime/type'; import { RuntimeNodeItemType } from '@fastgpt/global/core/workflow/runtime/type';
import { RuntimeEdgeItemType } from '@fastgpt/global/core/workflow/type/edge'; import { RuntimeEdgeItemType } from '@fastgpt/global/core/workflow/type/edge';
import { ChatNodeUsageType } from '@fastgpt/global/support/wallet/bill/type'; import { ChatNodeUsageType } from '@fastgpt/global/support/wallet/bill/type';
...@@ -21,3 +24,15 @@ export type DispatchFlowResponse = { ...@@ -21,3 +24,15 @@ export type DispatchFlowResponse = {
[DispatchNodeResponseKeyEnum.assistantResponses]: AIChatItemValueItemType[]; [DispatchNodeResponseKeyEnum.assistantResponses]: AIChatItemValueItemType[];
newVariables: Record<string, string>; newVariables: Record<string, string>;
}; };
export type WorkflowResponseType = ({
write,
event,
data,
stream
}: {
write?: ((text: string) => void) | undefined;
event: SseResponseEventEnum;
data: Record<string, any>;
stream?: boolean | undefined;
}) => void;
...@@ -6,6 +6,56 @@ import { ...@@ -6,6 +6,56 @@ import {
NodeOutputKeyEnum NodeOutputKeyEnum
} from '@fastgpt/global/core/workflow/constants'; } from '@fastgpt/global/core/workflow/constants';
import { RuntimeEdgeItemType } from '@fastgpt/global/core/workflow/runtime/type'; import { RuntimeEdgeItemType } from '@fastgpt/global/core/workflow/runtime/type';
import { responseWrite } from '../../../common/response';
import { NextApiResponse } from 'next';
import { SseResponseEventEnum } from '@fastgpt/global/core/workflow/runtime/constants';
export const getWorkflowResponseWrite = ({
res,
detail,
streamResponse,
id
}: {
res?: NextApiResponse;
detail: boolean;
streamResponse: boolean;
id: string;
}) => {
return ({
write,
event,
data,
stream
}: {
write?: (text: string) => void;
event: SseResponseEventEnum;
data: Record<string, any>;
stream?: boolean; // Focus set stream response
}) => {
const useStreamResponse = stream ?? streamResponse;
if (!res || res.closed || !useStreamResponse) return;
const detailEvent = [
SseResponseEventEnum.error,
SseResponseEventEnum.flowNodeStatus,
SseResponseEventEnum.flowResponses,
SseResponseEventEnum.interactive,
SseResponseEventEnum.toolCall,
SseResponseEventEnum.toolParams,
SseResponseEventEnum.toolResponse,
SseResponseEventEnum.updateVariables
];
if (!detail && detailEvent.includes(event)) return;
responseWrite({
res,
write,
event: detail ? event : undefined,
data: JSON.stringify(data)
});
};
};
export const filterToolNodeIdByEdges = ({ export const filterToolNodeIdByEdges = ({
nodeId, nodeId,
......
// @ts-nocheck
import { chats2GPTMessages } from '@fastgpt/global/core/chat/adapt';
import { filterGPTMessageByMaxTokens } from '../../../chat/utils';
import type { ChatItemType } from '@fastgpt/global/core/chat/type.d';
import { ChatItemValueTypeEnum, ChatRoleEnum } from '@fastgpt/global/core/chat/constants';
import { getAIApi } from '../../../ai/config';
import type { ClassifyQuestionAgentItemType } from '@fastgpt/global/core/workflow/template/system/classifyQuestion/type';
import { NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants';
import { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/workflow/runtime/constants';
import type { ModuleDispatchProps } from '@fastgpt/global/core/workflow/runtime/type';
import { replaceVariable } from '@fastgpt/global/common/string/tools';
import { Prompt_CQJson } from '@fastgpt/global/core/ai/prompt/agent';
import { LLMModelItemType } from '@fastgpt/global/core/ai/model.d';
import { ModelTypeEnum, getLLMModel } from '../../../ai/model';
import { getHistories } from '../utils';
import { formatModelChars2Points } from '../../../../support/wallet/usage/utils';
import { ChatCompletionRequestMessageRoleEnum } from '@fastgpt/global/core/ai/constants';
import {
ChatCompletionCreateParams,
ChatCompletionMessageParam,
ChatCompletionTool
} from '@fastgpt/global/core/ai/type';
import { DispatchNodeResultType } from '@fastgpt/global/core/workflow/runtime/type';
import { chatValue2RuntimePrompt } from '@fastgpt/global/core/chat/adapt';
import {
countMessagesTokens,
countGptMessagesTokens
} from '../../../../common/string/tiktoken/index';
type Props = ModuleDispatchProps<{
[NodeInputKeyEnum.aiModel]: string;
[NodeInputKeyEnum.aiSystemPrompt]?: string;
[NodeInputKeyEnum.history]?: ChatItemType[] | number;
[NodeInputKeyEnum.userChatInput]: string;
[NodeInputKeyEnum.agents]: ClassifyQuestionAgentItemType[];
}>;
type CQResponse = DispatchNodeResultType<{
[key: string]: any;
}>;
type ActionProps = Props & { cqModel: LLMModelItemType };
const agentFunName = 'classify_question';
/* request openai chat */
export const dispatchClassifyQuestion = async (props: Props): Promise<CQResponse> => {
const {
user,
module: { name },
histories,
params: { model, history = 6, agents, userChatInput }
} = props as Props;
if (!userChatInput) {
return Promise.reject('Input is empty');
}
const cqModel = getLLMModel(model);
const chatHistories = getHistories(history, histories);
const { arg, tokens } = await (async () => {
if (cqModel.toolChoice) {
return toolChoice({
...props,
histories: chatHistories,
cqModel
});
}
if (cqModel.functionCall) {
return functionCall({
...props,
histories: chatHistories,
cqModel
});
}
return completions({
...props,
histories: chatHistories,
cqModel
});
})();
const result = agents.find((item) => item.key === arg?.type) || agents[agents.length - 1];
const { totalPoints, modelName } = formatModelChars2Points({
model: cqModel.model,
tokens,
modelType: ModelTypeEnum.llm
});
return {
[result.key]: true,
[DispatchNodeResponseKeyEnum.nodeResponse]: {
totalPoints: user.openaiAccount?.key ? 0 : totalPoints,
model: modelName,
query: userChatInput,
tokens,
cqList: agents,
cqResult: result.value,
contextTotalLen: chatHistories.length + 2
},
[DispatchNodeResponseKeyEnum.nodeDispatchUsages]: [
{
moduleName: name,
totalPoints: user.openaiAccount?.key ? 0 : totalPoints,
model: modelName,
tokens
}
]
};
};
const getFunctionCallSchema = async ({
cqModel,
histories,
params: { agents, systemPrompt, userChatInput }
}: ActionProps) => {
const messages: ChatItemType[] = [
...histories,
{
obj: ChatRoleEnum.Human,
value: [
{
type: ChatItemValueTypeEnum.text,
text: {
content: systemPrompt
? `<背景知识>
${systemPrompt}
</背景知识>
问题: "${userChatInput}"
`
: userChatInput
}
}
]
}
];
const adaptMessages = chats2GPTMessages({ messages, reserveId: false });
const filterMessages = await filterGPTMessageByMaxTokens({
messages: adaptMessages,
maxTokens: cqModel.maxContext
});
// function body
const agentFunction = {
name: agentFunName,
description: '结合对话记录及背景知识,对问题进行分类,并返回对应的类型字段',
parameters: {
type: 'object',
properties: {
type: {
type: 'string',
description: `问题类型。下面是几种可选的问题类型: ${agents
.map((item) => `${item.value},返回:'${item.key}'`)
.join(';')}`,
enum: agents.map((item) => item.key)
}
},
required: ['type']
}
};
return {
agentFunction,
filterMessages
};
};
const toolChoice = async (props: ActionProps) => {
const { user, cqModel } = props;
const { agentFunction, filterMessages } = await getFunctionCallSchema(props);
// function body
const tools: ChatCompletionTool[] = [
{
type: 'function',
function: agentFunction
}
];
const ai = getAIApi({
userKey: user.openaiAccount,
timeout: 480000
});
const response = await ai.chat.completions.create({
model: cqModel.model,
temperature: 0.01,
messages: filterMessages,
tools,
tool_choice: { type: 'function', function: { name: agentFunName } }
});
try {
const arg = JSON.parse(
response?.choices?.[0]?.message?.tool_calls?.[0]?.function?.arguments || ''
);
const completeMessages: ChatCompletionMessageParam[] = [
...filterMessages,
{
role: ChatCompletionRequestMessageRoleEnum.Assistant,
tool_calls: response.choices?.[0]?.message?.tool_calls
}
];
return {
arg,
tokens: await countGptMessagesTokens(completeMessages, tools)
};
} catch (error) {
console.log(response.choices?.[0]?.message);
console.log('Your model may not support toll_call', error);
return {
arg: {},
tokens: 0
};
}
};
const functionCall = async (props: ActionProps) => {
const { user, cqModel } = props;
const { agentFunction, filterMessages } = await getFunctionCallSchema(props);
const functions: ChatCompletionCreateParams.Function[] = [agentFunction];
const ai = getAIApi({
userKey: user.openaiAccount,
timeout: 480000
});
const response = await ai.chat.completions.create({
model: cqModel.model,
temperature: 0.01,
messages: filterMessages,
function_call: {
name: agentFunName
},
functions
});
try {
const arg = JSON.parse(response?.choices?.[0]?.message?.function_call?.arguments || '');
const completeMessages: ChatCompletionMessageParam[] = [
...filterMessages,
{
role: ChatCompletionRequestMessageRoleEnum.Assistant,
function_call: response.choices?.[0]?.message?.function_call
}
];
return {
arg,
tokens: await countGptMessagesTokens(completeMessages, undefined, functions)
};
} catch (error) {
console.log(response.choices?.[0]?.message);
console.log('Your model may not support toll_call', error);
return {
arg: {},
tokens: 0
};
}
};
const completions = async ({
cqModel,
user,
histories,
params: { agents, systemPrompt = '', userChatInput }
}: ActionProps) => {
const messages: ChatItemType[] = [
{
obj: ChatRoleEnum.Human,
value: [
{
type: ChatItemValueTypeEnum.text,
text: {
content: replaceVariable(cqModel.customCQPrompt || Prompt_CQJson, {
systemPrompt: systemPrompt || 'null',
typeList: agents
.map((item) => `{"questionType": "${item.value}", "typeId": "${item.key}"}`)
.join('\n'),
history: histories
.map((item) => `${item.obj}:${chatValue2RuntimePrompt(item.value).text}`)
.join('\n'),
question: userChatInput
})
}
}
]
}
];
const ai = getAIApi({
userKey: user.openaiAccount,
timeout: 480000
});
const data = await ai.chat.completions.create({
model: cqModel.model,
temperature: 0.01,
messages: chats2GPTMessages({ messages, reserveId: false }),
stream: false
});
const answer = data.choices?.[0].message?.content || '';
const id =
agents.find((item) => answer.includes(item.key) || answer.includes(item.value))?.key || '';
return {
tokens: await countMessagesTokens(messages),
arg: { type: id }
};
};
export const Prompt_Tool_Call = `<Instruction>
你是一个智能机器人,除了可以回答用户问题外,你还掌握工具的使用能力。有时候,你可以依赖工具的运行结果,来更准确的回答用户。
工具使用了 JSON Schema 的格式声明,其中 toolId 是工具的 description 是工具的描述,parameters 是工具的参数,包括参数的类型和描述,required 是必填参数的列表。
请你根据工具描述,决定回答问题或是使用工具。在完成任务过程中,USER代表用户的输入,TOOL_RESPONSE代表工具运行结果。ASSISTANT 代表你的输出。
你的每次输出都必须以0,1开头,代表是否需要调用工具:
0: 不使用工具,直接回答内容。
1: 使用工具,返回工具调用的参数。
例如:
USER: 你好呀
ANSWER: 0: 你好,有什么可以帮助你的么?
USER: 今天杭州的天气如何
ANSWER: 1: {"toolId":"testToolId",arguments:{"city": "杭州"}}
TOOL_RESPONSE: """
晴天......
"""
ANSWER: 0: 今天杭州是晴天。
USER: 今天杭州的天气适合去哪里玩?
ANSWER: 1: {"toolId":"testToolId2",arguments:{"query": "杭州 天气 去哪里玩"}}
TOOL_RESPONSE: """
晴天. 西湖、灵隐寺、千岛湖……
"""
ANSWER: 0: 今天杭州是晴天,适合去西湖、灵隐寺、千岛湖等地玩。
</Instruction>
现在,我们开始吧!下面是你本次可以使用的工具:
"""
{{toolsPrompt}}
"""
下面是正式的对话内容:
USER: {{question}}
ANSWER:
`;
// @ts-nocheck
import { NodeOutputKeyEnum } from '@fastgpt/global/core/workflow/constants';
import { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/workflow/runtime/constants';
import type {
DispatchNodeResultType,
RuntimeNodeItemType
} from '@fastgpt/global/core/workflow/runtime/type';
import { ModelTypeEnum, getLLMModel } from '../../../../ai/model';
import { getHistories } from '../../utils';
import { runToolWithToolChoice } from './toolChoice';
import { DispatchToolModuleProps, ToolModuleItemType } from './type.d';
import { ChatItemType } from '@fastgpt/global/core/chat/type';
import { ChatRoleEnum } from '@fastgpt/global/core/chat/constants';
import {
GPTMessages2Chats,
chats2GPTMessages,
getSystemPrompt,
runtimePrompt2ChatsValue
} from '@fastgpt/global/core/chat/adapt';
import { formatModelChars2Points } from '../../../../../support/wallet/usage/utils';
import { getHistoryPreview } from '@fastgpt/global/core/chat/utils';
import { runToolWithFunctionCall } from './functionCall';
import { runToolWithPromptCall } from './promptCall';
import { replaceVariable } from '@fastgpt/global/common/string/tools';
import { Prompt_Tool_Call } from './constants';
type Response = DispatchNodeResultType<{}>;
export const dispatchRunTools = async (props: DispatchToolModuleProps): Promise<Response> => {
const {
module: { name, outputs },
runtimeModules,
histories,
params: { model, systemPrompt, userChatInput, history = 6 }
} = props;
const toolModel = getLLMModel(model);
const chatHistories = getHistories(history, histories);
/* get tool params */
// get tool output targets
const toolOutput = outputs.find((output) => output.key === NodeOutputKeyEnum.selectedTools);
if (!toolOutput) {
return Promise.reject('No tool output found');
}
const targets = toolOutput.targets;
// Gets the module to which the tool is connected
const toolModules = targets
.map((item) => {
const tool = runtimeModules.find((module) => module.moduleId === item.moduleId);
return tool;
})
.filter(Boolean)
.map<ToolModuleItemType>((tool) => {
const toolParams = tool?.inputs.filter((input) => !!input.toolDescription) || [];
return {
...(tool as RuntimeNodeItemType),
toolParams
};
});
const messages: ChatItemType[] = [
...getSystemPrompt(systemPrompt),
...chatHistories,
{
obj: ChatRoleEnum.Human,
value: runtimePrompt2ChatsValue({
text: userChatInput,
files: []
})
}
];
const {
dispatchFlowResponse, // tool flow response
totalTokens,
completeMessages = [], // The actual message sent to AI(just save text)
assistantResponses = [] // FastGPT system store assistant.value response
} = await (async () => {
const adaptMessages = chats2GPTMessages({ messages, reserveId: false });
if (toolModel.toolChoice) {
return runToolWithToolChoice({
...props,
toolModules,
toolModel,
messages: adaptMessages
});
}
if (toolModel.functionCall) {
return runToolWithFunctionCall({
...props,
toolModules,
toolModel,
messages: adaptMessages
});
}
const lastMessage = adaptMessages[adaptMessages.length - 1];
if (typeof lastMessage.content !== 'string') {
return Promise.reject('暂时只支持纯文本');
}
lastMessage.content = replaceVariable(Prompt_Tool_Call, {
question: userChatInput
});
return runToolWithPromptCall({
...props,
toolModules,
toolModel,
messages: adaptMessages
});
})();
const { totalPoints, modelName } = formatModelChars2Points({
model,
tokens: totalTokens,
modelType: ModelTypeEnum.llm
});
// flat child tool response
const childToolResponse = dispatchFlowResponse.map((item) => item.flowResponses).flat();
// concat tool usage
const totalPointsUsage =
totalPoints +
dispatchFlowResponse.reduce((sum, item) => {
const childrenTotal = item.flowUsages.reduce((sum, item) => sum + item.totalPoints, 0);
return sum + childrenTotal;
}, 0);
const flatUsages = dispatchFlowResponse.map((item) => item.flowUsages).flat();
return {
[DispatchNodeResponseKeyEnum.assistantResponses]: assistantResponses,
[DispatchNodeResponseKeyEnum.nodeResponse]: {
totalPoints: totalPointsUsage,
toolCallTokens: totalTokens,
model: modelName,
query: userChatInput,
historyPreview: getHistoryPreview(GPTMessages2Chats(completeMessages, false)),
toolDetail: childToolResponse
},
[DispatchNodeResponseKeyEnum.nodeDispatchUsages]: [
{
moduleName: name,
totalPoints,
model: modelName,
tokens: totalTokens
},
...flatUsages
]
};
};
import { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/workflow/runtime/constants';
import type { ModuleDispatchProps } from '@fastgpt/global/core/workflow/runtime/type';
import { DispatchNodeResultType } from '@fastgpt/global/core/workflow/runtime/type';
export type AnswerProps = ModuleDispatchProps<{}>;
export type AnswerResponse = DispatchNodeResultType<{}>;
export const dispatchStopToolCall = (props: Record<string, any>): AnswerResponse => {
return {
[DispatchNodeResponseKeyEnum.nodeResponse]: {
toolStop: true
}
};
};
import { ChatCompletionMessageParam } from '@fastgpt/global/core/ai/type';
import { NodeInputKeyEnum, NodeOutputKeyEnum } from '@fastgpt/global/core/workflow/constants';
import { FlowNodeInputItemType } from '@fastgpt/global/core/workflow/node/type';
import type {
ModuleDispatchProps,
DispatchNodeResponseType
} from '@fastgpt/global/core/workflow/runtime/type';
import type { RuntimeNodeItemType } from '@fastgpt/global/core/workflow/runtime/type';
import { ChatNodeUsageType } from '@fastgpt/global/support/wallet/bill/type';
import type { DispatchFlowResponse } from '../../type.d';
import { AIChatItemValueItemType, ChatItemValueItemType } from '@fastgpt/global/core/chat/type';
export type DispatchToolModuleProps = ModuleDispatchProps<{
[NodeInputKeyEnum.history]?: ChatItemType[];
[NodeInputKeyEnum.aiModel]: string;
[NodeInputKeyEnum.aiSystemPrompt]: string;
[NodeInputKeyEnum.userChatInput]: string;
}>;
export type RunToolResponse = {
dispatchFlowResponse: DispatchFlowResponse[];
totalTokens: number;
completeMessages?: ChatCompletionMessageParam[];
assistantResponses?: AIChatItemValueItemType[];
};
export type ToolModuleItemType = RuntimeNodeItemType & {
toolParams: RuntimeNodeItemType['inputs'];
};
import type { SearchDataResponseItemType } from '@fastgpt/global/core/dataset/type';
import type { ModuleDispatchProps } from '@fastgpt/global/core/workflow/runtime/type';
import { NodeInputKeyEnum, NodeOutputKeyEnum } from '@fastgpt/global/core/workflow/constants';
import { datasetSearchResultConcat } from '@fastgpt/global/core/dataset/search/utils';
import { filterSearchResultsByMaxChars } from '../../utils';
type DatasetConcatProps = ModuleDispatchProps<
{
[NodeInputKeyEnum.datasetMaxTokens]: number;
} & { [key: string]: SearchDataResponseItemType[] }
>;
type DatasetConcatResponse = {
[NodeOutputKeyEnum.datasetQuoteQA]: SearchDataResponseItemType[];
};
export async function dispatchDatasetConcat(
props: DatasetConcatProps
): Promise<DatasetConcatResponse> {
const {
params: { limit = 1500, ...quoteMap }
} = props as DatasetConcatProps;
const quoteList = Object.values(quoteMap).filter((list) => Array.isArray(list));
const rrfConcatResults = datasetSearchResultConcat(
quoteList.map((list) => ({
k: 60,
list
}))
);
return {
[NodeOutputKeyEnum.datasetQuoteQA]: await filterSearchResultsByMaxChars(rrfConcatResults, limit)
};
}
// @ts-nocheck
import {
DispatchNodeResponseType,
DispatchNodeResultType
} from '@fastgpt/global/core/workflow/runtime/type.d';
import { formatModelChars2Points } from '../../../../support/wallet/usage/utils';
import type { SelectedDatasetType } from '@fastgpt/global/core/workflow/api.d';
import type { SearchDataResponseItemType } from '@fastgpt/global/core/dataset/type';
import type { ModuleDispatchProps } from '@fastgpt/global/core/workflow/runtime/type';
import { ModelTypeEnum, getLLMModel, getVectorModel } from '../../../ai/model';
import { searchDatasetData } from '../../../dataset/search/controller';
import { NodeInputKeyEnum, NodeOutputKeyEnum } from '@fastgpt/global/core/workflow/constants';
import { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/workflow/runtime/constants';
import { DatasetSearchModeEnum } from '@fastgpt/global/core/dataset/constants';
import { getHistories } from '../utils';
import { datasetSearchQueryExtension } from '../../../dataset/search/utils';
import { ChatNodeUsageType } from '@fastgpt/global/support/wallet/bill/type';
import { checkTeamReRankPermission } from '../../../../support/permission/teamLimit';
type DatasetSearchProps = ModuleDispatchProps<{
[NodeInputKeyEnum.datasetSelectList]: SelectedDatasetType;
[NodeInputKeyEnum.datasetSimilarity]: number;
[NodeInputKeyEnum.datasetMaxTokens]: number;
[NodeInputKeyEnum.datasetSearchMode]: `${DatasetSearchModeEnum}`;
[NodeInputKeyEnum.userChatInput]: string;
[NodeInputKeyEnum.datasetSearchUsingReRank]: boolean;
[NodeInputKeyEnum.datasetSearchUsingExtensionQuery]: boolean;
[NodeInputKeyEnum.datasetSearchExtensionModel]: string;
[NodeInputKeyEnum.datasetSearchExtensionBg]: string;
}>;
export type DatasetSearchResponse = DispatchNodeResultType<{
isEmpty?: boolean;
unEmpty?: boolean;
[NodeOutputKeyEnum.datasetQuoteQA]: SearchDataResponseItemType[];
}>;
export async function dispatchDatasetSearch(
props: DatasetSearchProps
): Promise<DatasetSearchResponse> {
const {
teamId,
histories,
module,
params: {
datasets = [],
similarity,
limit = 1500,
usingReRank,
searchMode,
userChatInput,
datasetSearchUsingExtensionQuery,
datasetSearchExtensionModel,
datasetSearchExtensionBg
}
} = props as DatasetSearchProps;
if (!Array.isArray(datasets)) {
return Promise.reject('Quote type error');
}
if (datasets.length === 0) {
return Promise.reject('core.chat.error.Select dataset empty');
}
if (!userChatInput) {
return Promise.reject('core.chat.error.User input empty');
}
// query extension
const extensionModel =
datasetSearchUsingExtensionQuery && datasetSearchExtensionModel
? getLLMModel(datasetSearchExtensionModel)
: undefined;
const { concatQueries, rewriteQuery, aiExtensionResult } = await datasetSearchQueryExtension({
query: userChatInput,
extensionModel,
extensionBg: datasetSearchExtensionBg,
histories: getHistories(6, histories)
});
// console.log(concatQueries, rewriteQuery, aiExtensionResult);
// get vector
const vectorModel = getVectorModel(datasets[0]?.vectorModel?.model);
// start search
const {
searchRes,
tokens,
usingSimilarityFilter,
usingReRank: searchUsingReRank
} = await searchDatasetData({
teamId,
reRankQuery: `${rewriteQuery}`,
queries: concatQueries,
model: vectorModel.model,
similarity,
limit,
datasetIds: datasets.map((item) => item.datasetId),
searchMode,
usingReRank: usingReRank && (await checkTeamReRankPermission(teamId))
});
// count bill results
// vector
const { totalPoints, modelName } = formatModelChars2Points({
model: vectorModel.model,
tokens,
modelType: ModelTypeEnum.vector
});
const responseData: DispatchNodeResponseType & { totalPoints: number } = {
totalPoints,
query: concatQueries.join('\n'),
model: modelName,
tokens,
similarity: usingSimilarityFilter ? similarity : undefined,
limit,
searchMode,
searchUsingReRank: searchUsingReRank,
quoteList: searchRes
};
const nodeDispatchUsages: ChatNodeUsageType[] = [
{
totalPoints,
moduleName: module.name,
model: modelName,
tokens
}
];
if (aiExtensionResult) {
const { totalPoints, modelName } = formatModelChars2Points({
model: aiExtensionResult.model,
tokens: aiExtensionResult.tokens,
modelType: ModelTypeEnum.llm
});
responseData.totalPoints += totalPoints;
responseData.tokens = aiExtensionResult.tokens;
responseData.extensionModel = modelName;
responseData.extensionResult =
aiExtensionResult.extensionQueries?.join('\n') ||
JSON.stringify(aiExtensionResult.extensionQueries);
nodeDispatchUsages.push({
totalPoints,
moduleName: 'core.module.template.Query extension',
model: modelName,
tokens: aiExtensionResult.tokens
});
}
return {
isEmpty: searchRes.length === 0 ? true : undefined,
unEmpty: searchRes.length > 0 ? true : undefined,
quoteQA: searchRes,
[DispatchNodeResponseKeyEnum.nodeResponse]: responseData,
nodeDispatchUsages,
[DispatchNodeResponseKeyEnum.toolResponses]: searchRes.map((item) => ({
id: item.id,
text: `${item.q}\n${item.a}`.trim()
}))
};
}
import { NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants';
import type { ChatItemType } from '@fastgpt/global/core/chat/type.d';
import type { ModuleDispatchProps } from '@fastgpt/global/core/workflow/runtime/type';
import { getHistories } from '../utils';
export type HistoryProps = ModuleDispatchProps<{
maxContext?: number;
[NodeInputKeyEnum.history]: ChatItemType[];
}>;
export const dispatchHistory = (props: Record<string, any>) => {
const {
histories,
params: { maxContext }
} = props as HistoryProps;
return {
history: getHistories(maxContext, histories)
};
};
import { NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants';
import type { ModuleDispatchProps } from '@fastgpt/global/core/workflow/runtime/type';
export type UserChatInputProps = ModuleDispatchProps<{
[NodeInputKeyEnum.userChatInput]: string;
}>;
export const dispatchChatInput = (props: Record<string, any>) => {
const {
params: { userChatInput }
} = props as UserChatInputProps;
return {
userChatInput
};
};
// @ts-nocheck
import type { ModuleDispatchProps } from '@fastgpt/global/core/workflow/runtime/type';
import { dispatchWorkFlowV1 } from '../index';
import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
import {
FlowNodeTemplateTypeEnum,
NodeInputKeyEnum
} from '@fastgpt/global/core/workflow/constants';
import { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/workflow/runtime/constants';
import { splitCombinePluginId } from '../../../app/plugin/controller';
import { setEntryEntries, DYNAMIC_INPUT_KEY } from '../utils';
import { DispatchNodeResultType } from '@fastgpt/global/core/workflow/runtime/type';
import { PluginRuntimeType, PluginTemplateType } from '@fastgpt/global/core/plugin/type';
import { PluginSourceEnum } from '@fastgpt/global/core/plugin/constants';
import { MongoPlugin } from '../../../plugin/schema';
type RunPluginProps = ModuleDispatchProps<{
[NodeInputKeyEnum.pluginId]: string;
[key: string]: any;
}>;
type RunPluginResponse = DispatchNodeResultType<{}>;
const getPluginTemplateById = async (id: string): Promise<PluginTemplateType> => {
const { source, pluginId } = await splitCombinePluginId(id);
if (source === PluginSourceEnum.community) {
const item = global.communityPluginsV1?.find((plugin) => plugin.id === pluginId);
if (!item) return Promise.reject('plugin not found');
return item;
}
if (source === PluginSourceEnum.personal) {
const item = await MongoPlugin.findById(id).lean();
if (!item) return Promise.reject('plugin not found');
return {
id: String(item._id),
teamId: String(item.teamId),
name: item.name,
avatar: item.avatar,
intro: item.intro,
showStatus: true,
source: PluginSourceEnum.personal,
modules: item.modules,
templateType: FlowNodeTemplateTypeEnum.teamApp
};
}
return Promise.reject('plugin not found');
};
const getPluginRuntimeById = async (id: string): Promise<PluginRuntimeType> => {
const plugin = await getPluginTemplateById(id);
return {
teamId: plugin.teamId,
name: plugin.name,
avatar: plugin.avatar,
showStatus: plugin.showStatus,
modules: plugin.modules
};
};
export const dispatchRunPlugin = async (props: RunPluginProps): Promise<RunPluginResponse> => {
const {
mode,
teamId,
tmbId,
params: { pluginId, ...data }
} = props;
if (!pluginId) {
return Promise.reject('pluginId can not find');
}
const plugin = await getPluginRuntimeById(pluginId);
if (plugin.teamId && plugin.teamId !== teamId) {
return Promise.reject('plugin not found');
}
// concat dynamic inputs
const inputModule = plugin.modules.find((item) => item.flowType === FlowNodeTypeEnum.pluginInput);
if (!inputModule) return Promise.reject('Plugin error, It has no set input.');
const hasDynamicInput = inputModule.inputs.find((input) => input.key === DYNAMIC_INPUT_KEY);
const startParams: Record<string, any> = (() => {
if (!hasDynamicInput) return data;
const params: Record<string, any> = {
[DYNAMIC_INPUT_KEY]: {}
};
for (const key in data) {
const input = inputModule.inputs.find((input) => input.key === key);
if (input) {
params[key] = data[key];
} else {
params[DYNAMIC_INPUT_KEY][key] = data[key];
}
}
return params;
})();
const { flowResponses, flowUsages, assistantResponses } = await dispatchWorkFlowV1({
...props,
modules: setEntryEntries(plugin.modules).map((module) => ({
...module,
showStatus: false
})),
runtimeModules: undefined, // must reset
startParams
});
const output = flowResponses.find((item) => item.moduleType === FlowNodeTypeEnum.pluginOutput);
if (output) {
output.moduleLogo = plugin.avatar;
}
return {
assistantResponses,
// responseData, // debug
[DispatchNodeResponseKeyEnum.nodeResponse]: {
moduleLogo: plugin.avatar,
totalPoints: flowResponses.reduce((sum, item) => sum + (item.totalPoints || 0), 0),
pluginOutput: output?.pluginOutput,
pluginDetail:
mode === 'test' && plugin.teamId === teamId
? flowResponses.filter((item) => {
const filterArr = [FlowNodeTypeEnum.pluginOutput];
return !filterArr.includes(item.moduleType as any);
})
: undefined
},
[DispatchNodeResponseKeyEnum.nodeDispatchUsages]: [
{
moduleName: plugin.name,
totalPoints: flowUsages.reduce((sum, item) => sum + (item.totalPoints || 0), 0),
model: plugin.name,
tokens: 0
}
],
[DispatchNodeResponseKeyEnum.toolResponses]: output?.pluginOutput ? output.pluginOutput : {},
...(output ? output.pluginOutput : {})
};
};
import type { ModuleDispatchProps } from '@fastgpt/global/core/workflow/runtime/type';
export type PluginInputProps = ModuleDispatchProps<{
[key: string]: any;
}>;
export const dispatchPluginInput = (props: PluginInputProps) => {
const { params } = props;
return params;
};
import type { ModuleDispatchProps } from '@fastgpt/global/core/workflow/runtime/type';
import { DispatchNodeResultType } from '@fastgpt/global/core/workflow/runtime/type.d';
import { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/workflow/runtime/constants';
export type PluginOutputProps = ModuleDispatchProps<{
[key: string]: any;
}>;
export type PluginOutputResponse = DispatchNodeResultType<{}>;
export const dispatchPluginOutput = (props: PluginOutputProps): PluginOutputResponse => {
const { params } = props;
return {
[DispatchNodeResponseKeyEnum.nodeResponse]: {
totalPoints: 0,
pluginOutput: params
}
};
};
import { SseResponseEventEnum } from '@fastgpt/global/core/workflow/runtime/constants';
import { responseWrite } from '../../../../common/response';
import { textAdaptGptResponse } from '@fastgpt/global/core/workflow/runtime/utils';
import type { ModuleDispatchProps } from '@fastgpt/global/core/workflow/runtime/type';
import { NodeOutputKeyEnum } from '@fastgpt/global/core/workflow/constants';
import { DispatchNodeResultType } from '@fastgpt/global/core/workflow/runtime/type';
export type AnswerProps = ModuleDispatchProps<{
text: string;
}>;
export type AnswerResponse = DispatchNodeResultType<{
[NodeOutputKeyEnum.answerText]: string;
}>;
export const dispatchAnswer = (props: Record<string, any>): AnswerResponse => {
const {
res,
detail,
stream,
params: { text = '' }
} = props as AnswerProps;
const formatText = typeof text === 'string' ? text : JSON.stringify(text, null, 2);
if (stream) {
responseWrite({
res,
event: detail ? SseResponseEventEnum.fastAnswer : undefined,
data: textAdaptGptResponse({
text: `\n${formatText}`
})
});
}
return {
[NodeOutputKeyEnum.answerText]: `\n${formatText}`
};
};
// @ts-nocheck
import type { ModuleDispatchProps } from '@fastgpt/global/core/workflow/runtime/type';
import { NodeInputKeyEnum, NodeOutputKeyEnum } from '@fastgpt/global/core/workflow/constants';
import { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/workflow/runtime/constants';
import axios from 'axios';
import { valueTypeFormat } from '../utils';
import { SERVICE_LOCAL_HOST } from '../../../../common/system/tools';
import { DispatchNodeResultType } from '@fastgpt/global/core/workflow/runtime/type';
import { DYNAMIC_INPUT_KEY } from '../utils';
type HttpRequestProps = ModuleDispatchProps<{
[NodeInputKeyEnum.abandon_httpUrl]: string;
[NodeInputKeyEnum.httpMethod]: string;
[NodeInputKeyEnum.httpReqUrl]: string;
[NodeInputKeyEnum.httpHeaders]: string;
[key: string]: any;
}>;
type HttpResponse = DispatchNodeResultType<{
[NodeOutputKeyEnum.failed]?: boolean;
[key: string]: any;
}>;
const flatDynamicParams = (params: Record<string, any>) => {
const dynamicParams = params[DYNAMIC_INPUT_KEY];
if (!dynamicParams) return params;
return {
...params,
...dynamicParams,
[DYNAMIC_INPUT_KEY]: undefined
};
};
export const dispatchHttpRequest = async (props: HttpRequestProps): Promise<HttpResponse> => {
let {
appId,
chatId,
responseChatItemId,
variables,
module: { outputs },
params: {
system_httpMethod: httpMethod = 'POST',
system_httpReqUrl: httpReqUrl,
system_httpHeader: httpHeader,
...body
}
} = props;
if (!httpReqUrl) {
return Promise.reject('Http url is empty');
}
body = flatDynamicParams(body);
const requestBody = {
appId,
chatId,
responseChatItemId,
variables,
data: body
};
const requestQuery = {
appId,
chatId,
...variables,
...body
};
const formatBody = transformFlatJson({ ...requestBody });
// parse header
const headers = await (() => {
try {
if (!httpHeader) return {};
return JSON.parse(httpHeader);
} catch (error) {
return Promise.reject('Header 为非法 JSON 格式');
}
})();
try {
const response = await fetchData({
method: httpMethod,
url: httpReqUrl,
headers,
body: formatBody,
query: requestQuery
});
// format output value type
const results: Record<string, any> = {};
for (const key in response) {
const output = outputs.find((item) => item.key === key);
if (!output) continue;
results[key] = valueTypeFormat(response[key], output.valueType);
}
return {
[DispatchNodeResponseKeyEnum.nodeResponse]: {
totalPoints: 0,
body: formatBody,
httpResult: response
},
...results
};
} catch (error) {
console.log(error);
return {
[NodeOutputKeyEnum.failed]: true,
[DispatchNodeResponseKeyEnum.nodeResponse]: {
totalPoints: 0,
body: formatBody,
httpResult: { error }
}
};
}
};
async function fetchData({
method,
url,
headers,
body,
query
}: {
method: string;
url: string;
headers: Record<string, any>;
body: Record<string, any>;
query: Record<string, any>;
}): Promise<Record<string, any>> {
const { data: response } = await axios<Record<string, any>>({
method,
baseURL: `http://${SERVICE_LOCAL_HOST}`,
url,
headers: {
'Content-Type': 'application/json',
...headers
},
timeout: 360000,
params: method === 'GET' ? query : {},
data: method === 'POST' ? body : {}
});
/*
parse the json:
{
user: {
name: 'xxx',
age: 12
},
list: [
{
name: 'xxx',
age: 50
},
[{ test: 22 }]
],
psw: 'xxx'
}
result: {
'user': { name: 'xxx', age: 12 },
'user.name': 'xxx',
'user.age': 12,
'list': [ { name: 'xxx', age: 50 }, [ [Object] ] ],
'list[0]': { name: 'xxx', age: 50 },
'list[0].name': 'xxx',
'list[0].age': 50,
'list[1]': [ { test: 22 } ],
'list[1][0]': { test: 22 },
'list[1][0].test': 22,
'psw': 'xxx'
}
*/
const parseJson = (obj: Record<string, any>, prefix = '') => {
let result: Record<string, any> = {};
if (Array.isArray(obj)) {
for (let i = 0; i < obj.length; i++) {
result[`${prefix}[${i}]`] = obj[i];
if (Array.isArray(obj[i])) {
result = {
...result,
...parseJson(obj[i], `${prefix}[${i}]`)
};
} else if (typeof obj[i] === 'object') {
result = {
...result,
...parseJson(obj[i], `${prefix}[${i}].`)
};
}
}
} else if (typeof obj == 'object') {
for (const key in obj) {
result[`${prefix}${key}`] = obj[key];
if (Array.isArray(obj[key])) {
result = {
...result,
...parseJson(obj[key], `${prefix}${key}`)
};
} else if (typeof obj[key] === 'object') {
result = {
...result,
...parseJson(obj[key], `${prefix}${key}.`)
};
}
}
}
return result;
};
return parseJson(response);
}
function transformFlatJson(obj: Record<string, any>) {
for (let key in obj) {
if (typeof obj[key] === 'object') {
transformFlatJson(obj[key]);
}
if (key.includes('.')) {
let parts = key.split('.');
if (parts.length <= 1) continue;
const firstKey = parts.shift();
if (!firstKey) continue;
const lastKey = parts.join('.');
if (obj[firstKey]) {
obj[firstKey] = {
...obj[firstKey],
[lastKey]: obj[key]
};
} else {
obj[firstKey] = { [lastKey]: obj[key] };
}
transformFlatJson(obj[firstKey]);
delete obj[key];
}
}
return obj;
}
// @ts-nocheck
import type { ModuleDispatchProps } from '@fastgpt/global/core/workflow/runtime/type';
import { NodeInputKeyEnum, NodeOutputKeyEnum } from '@fastgpt/global/core/workflow/constants';
import { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/workflow/runtime/constants';
import axios from 'axios';
import { DYNAMIC_INPUT_KEY, valueTypeFormat } from '../utils';
import { SERVICE_LOCAL_HOST } from '../../../../common/system/tools';
import { addLog } from '../../../../common/system/log';
import { DispatchNodeResultType } from '@fastgpt/global/core/workflow/runtime/type';
import { getErrText } from '@fastgpt/global/common/error/utils';
type PropsArrType = {
key: string;
type: string;
value: string;
};
type HttpRequestProps = ModuleDispatchProps<{
[NodeInputKeyEnum.abandon_httpUrl]: string;
[NodeInputKeyEnum.httpMethod]: string;
[NodeInputKeyEnum.httpReqUrl]: string;
[NodeInputKeyEnum.httpHeaders]: PropsArrType[];
[NodeInputKeyEnum.httpParams]: PropsArrType[];
[NodeInputKeyEnum.httpJsonBody]: string;
[DYNAMIC_INPUT_KEY]: Record<string, any>;
[key: string]: any;
}>;
type HttpResponse = DispatchNodeResultType<{
[NodeOutputKeyEnum.failed]?: boolean;
[key: string]: any;
}>;
const UNDEFINED_SIGN = 'UNDEFINED_SIGN';
export const dispatchHttp468Request = async (props: HttpRequestProps): Promise<HttpResponse> => {
let {
appId,
chatId,
responseChatItemId,
variables,
module: { outputs },
histories,
params: {
system_httpMethod: httpMethod = 'POST',
system_httpReqUrl: httpReqUrl,
system_httpHeader: httpHeader,
system_httpParams: httpParams = [],
system_httpJsonBody: httpJsonBody,
[DYNAMIC_INPUT_KEY]: dynamicInput,
...body
}
} = props;
if (!httpReqUrl) {
return Promise.reject('Http url is empty');
}
const concatVariables = {
appId,
chatId,
responseChatItemId,
...variables,
histories: histories.slice(-10),
...body
};
httpReqUrl = replaceVariable(httpReqUrl, concatVariables);
// parse header
const headers = await (() => {
try {
if (!httpHeader || httpHeader.length === 0) return {};
// array
return httpHeader.reduce((acc: Record<string, string>, item) => {
const key = replaceVariable(item.key, concatVariables);
const value = replaceVariable(item.value, concatVariables);
acc[key] = valueTypeFormat(value, 'string');
return acc;
}, {});
} catch (error) {
return Promise.reject('Header 为非法 JSON 格式');
}
})();
const params = httpParams.reduce((acc: Record<string, string>, item) => {
const key = replaceVariable(item.key, concatVariables);
const value = replaceVariable(item.value, concatVariables);
acc[key] = valueTypeFormat(value, 'string');
return acc;
}, {});
const requestBody = await (() => {
if (!httpJsonBody) return { [DYNAMIC_INPUT_KEY]: dynamicInput };
httpJsonBody = replaceVariable(httpJsonBody, concatVariables);
try {
const jsonParse = JSON.parse(httpJsonBody);
const removeSignJson = removeUndefinedSign(jsonParse);
return { [DYNAMIC_INPUT_KEY]: dynamicInput, ...removeSignJson };
} catch (error) {
console.log(error);
return Promise.reject(`Invalid JSON body: ${httpJsonBody}`);
}
})();
try {
const { formatResponse, rawResponse } = await fetchData({
method: httpMethod,
url: httpReqUrl,
headers,
body: requestBody,
params
});
// format output value type
const results: Record<string, any> = {};
for (const key in formatResponse) {
const output = outputs.find((item) => item.key === key);
if (!output) continue;
results[key] = valueTypeFormat(formatResponse[key], output.valueType);
}
return {
[DispatchNodeResponseKeyEnum.nodeResponse]: {
totalPoints: 0,
params: Object.keys(params).length > 0 ? params : undefined,
body: Object.keys(requestBody).length > 0 ? requestBody : undefined,
headers: Object.keys(headers).length > 0 ? headers : undefined,
httpResult: rawResponse
},
[DispatchNodeResponseKeyEnum.toolResponses]: results,
[NodeOutputKeyEnum.httpRawResponse]: rawResponse,
...results
};
} catch (error) {
addLog.error('Http request error', error);
return {
[NodeOutputKeyEnum.failed]: true,
[DispatchNodeResponseKeyEnum.nodeResponse]: {
totalPoints: 0,
params: Object.keys(params).length > 0 ? params : undefined,
body: Object.keys(requestBody).length > 0 ? requestBody : undefined,
headers: Object.keys(headers).length > 0 ? headers : undefined,
httpResult: { error: formatHttpError(error) }
},
[NodeOutputKeyEnum.httpRawResponse]: getErrText(error)
};
}
};
async function fetchData({
method,
url,
headers,
body,
params
}: {
method: string;
url: string;
headers: Record<string, any>;
body: Record<string, any>;
params: Record<string, any>;
}): Promise<Record<string, any>> {
const { data: response } = await axios({
method,
baseURL: `http://${SERVICE_LOCAL_HOST}`,
url,
headers: {
'Content-Type': 'application/json',
...headers
},
timeout: 120000,
params: params,
data: ['POST', 'PUT', 'PATCH'].includes(method) ? body : undefined
});
/*
parse the json:
{
user: {
name: 'xxx',
age: 12
},
list: [
{
name: 'xxx',
age: 50
},
[{ test: 22 }]
],
psw: 'xxx'
}
result: {
'user': { name: 'xxx', age: 12 },
'user.name': 'xxx',
'user.age': 12,
'list': [ { name: 'xxx', age: 50 }, [ [Object] ] ],
'list[0]': { name: 'xxx', age: 50 },
'list[0].name': 'xxx',
'list[0].age': 50,
'list[1]': [ { test: 22 } ],
'list[1][0]': { test: 22 },
'list[1][0].test': 22,
'psw': 'xxx'
}
*/
const parseJson = (obj: Record<string, any>, prefix = '') => {
let result: Record<string, any> = {};
if (Array.isArray(obj)) {
for (let i = 0; i < obj.length; i++) {
result[`${prefix}[${i}]`] = obj[i];
if (Array.isArray(obj[i])) {
result = {
...result,
...parseJson(obj[i], `${prefix}[${i}]`)
};
} else if (typeof obj[i] === 'object') {
result = {
...result,
...parseJson(obj[i], `${prefix}[${i}].`)
};
}
}
} else if (typeof obj == 'object') {
for (const key in obj) {
result[`${prefix}${key}`] = obj[key];
if (Array.isArray(obj[key])) {
result = {
...result,
...parseJson(obj[key], `${prefix}${key}`)
};
} else if (typeof obj[key] === 'object') {
result = {
...result,
...parseJson(obj[key], `${prefix}${key}.`)
};
}
}
}
return result;
};
return {
formatResponse:
typeof response === 'object' && !Array.isArray(response) ? parseJson(response) : {},
rawResponse: response
};
}
function replaceVariable(text: string, obj: Record<string, any>) {
for (const [key, value] of Object.entries(obj)) {
if (value === undefined) {
text = text.replace(new RegExp(`{{${key}}}`, 'g'), UNDEFINED_SIGN);
} else {
const replacement = JSON.stringify(value);
const unquotedReplacement =
replacement.startsWith('"') && replacement.endsWith('"')
? replacement.slice(1, -1)
: replacement;
text = text.replace(new RegExp(`{{${key}}}`, 'g'), unquotedReplacement);
}
}
return text || '';
}
function removeUndefinedSign(obj: Record<string, any>) {
for (const key in obj) {
if (obj[key] === UNDEFINED_SIGN) {
obj[key] = undefined;
} else if (Array.isArray(obj[key])) {
obj[key] = obj[key].map((item: any) => {
if (item === UNDEFINED_SIGN) {
return undefined;
} else if (typeof item === 'object') {
removeUndefinedSign(item);
}
return item;
});
} else if (typeof obj[key] === 'object') {
removeUndefinedSign(obj[key]);
}
}
return obj;
}
function formatHttpError(error: any) {
return {
message: error?.message,
name: error?.name,
method: error?.config?.method,
baseURL: error?.config?.baseURL,
url: error?.config?.url,
code: error?.code,
status: error?.status
};
}
// @ts-nocheck
import type { ChatItemType } from '@fastgpt/global/core/chat/type.d';
import type { ModuleDispatchProps } from '@fastgpt/global/core/workflow/runtime/type';
import { NodeInputKeyEnum, NodeOutputKeyEnum } from '@fastgpt/global/core/workflow/constants';
import { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/workflow/runtime/constants';
import { ModelTypeEnum, getLLMModel } from '../../../../core/ai/model';
import { formatModelChars2Points } from '../../../../support/wallet/usage/utils';
import { queryExtension } from '../../../../core/ai/functions/queryExtension';
import { getHistories } from '../utils';
import { hashStr } from '@fastgpt/global/common/string/tools';
import { DispatchNodeResultType } from '@fastgpt/global/core/workflow/runtime/type';
type Props = ModuleDispatchProps<{
[NodeInputKeyEnum.aiModel]: string;
[NodeInputKeyEnum.aiSystemPrompt]?: string;
[NodeInputKeyEnum.history]?: ChatItemType[] | number;
[NodeInputKeyEnum.userChatInput]: string;
}>;
type Response = DispatchNodeResultType<{
[NodeOutputKeyEnum.text]: string;
}>;
export const dispatchQueryExtension = async ({
histories,
module,
params: { model, systemPrompt, history, userChatInput }
}: Props): Promise<Response> => {
if (!userChatInput) {
return Promise.reject('Question is empty');
}
const queryExtensionModel = getLLMModel(model);
const chatHistories = getHistories(history, histories);
const { extensionQueries, tokens } = await queryExtension({
chatBg: systemPrompt,
query: userChatInput,
histories: chatHistories,
model: queryExtensionModel.model
});
extensionQueries.unshift(userChatInput);
const { totalPoints, modelName } = formatModelChars2Points({
model: queryExtensionModel.model,
tokens,
modelType: ModelTypeEnum.llm
});
const set = new Set<string>();
const filterSameQueries = extensionQueries.filter((item) => {
// 删除所有的标点符号与空格等,只对文本进行比较
const str = hashStr(item.replace(/[^\p{L}\p{N}]/gu, ''));
if (set.has(str)) return false;
set.add(str);
return true;
});
return {
[DispatchNodeResponseKeyEnum.nodeResponse]: {
totalPoints,
model: modelName,
tokens,
query: userChatInput,
textOutput: JSON.stringify(filterSameQueries)
},
[DispatchNodeResponseKeyEnum.nodeDispatchUsages]: [
{
moduleName: module.name,
totalPoints,
model: modelName,
tokens
}
],
[NodeOutputKeyEnum.text]: JSON.stringify(filterSameQueries)
};
};
// @ts-nocheck
import type { ModuleDispatchProps } from '@fastgpt/global/core/workflow/runtime/type';
import { NodeInputKeyEnum, NodeOutputKeyEnum } from '@fastgpt/global/core/workflow/constants';
import { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/workflow/runtime/constants';
import axios from 'axios';
import { DYNAMIC_INPUT_KEY, valueTypeFormat } from '../utils';
import { SERVICE_LOCAL_HOST } from '../../../../common/system/tools';
import { addLog } from '../../../../common/system/log';
import { DispatchNodeResultType } from '@fastgpt/global/core/workflow/runtime/type';
type LafRequestProps = ModuleDispatchProps<{
[NodeInputKeyEnum.httpReqUrl]: string;
[DYNAMIC_INPUT_KEY]: Record<string, any>;
[key: string]: any;
}>;
type LafResponse = DispatchNodeResultType<{
[NodeOutputKeyEnum.failed]?: boolean;
[key: string]: any;
}>;
const UNDEFINED_SIGN = 'UNDEFINED_SIGN';
export const dispatchLafRequest = async (props: LafRequestProps): Promise<LafResponse> => {
let {
appId,
chatId,
responseChatItemId,
variables,
module: { outputs },
histories,
params: { system_httpReqUrl: httpReqUrl, [DYNAMIC_INPUT_KEY]: dynamicInput, ...body }
} = props;
if (!httpReqUrl) {
return Promise.reject('Http url is empty');
}
const concatVariables = {
appId,
chatId,
responseChatItemId,
...variables,
...body
};
httpReqUrl = replaceVariable(httpReqUrl, concatVariables);
const requestBody = {
systemParams: {
appId,
chatId,
responseChatItemId,
histories: histories.slice(0, 10)
},
variables,
...dynamicInput,
...body
};
try {
const { formatResponse, rawResponse } = await fetchData({
method: 'POST',
url: httpReqUrl,
body: requestBody
});
// format output value type
const results: Record<string, any> = {};
for (const key in formatResponse) {
const output = outputs.find((item) => item.key === key);
if (!output) continue;
results[key] = valueTypeFormat(formatResponse[key], output.valueType);
}
return {
assistantResponses: [],
[DispatchNodeResponseKeyEnum.nodeResponse]: {
totalPoints: 0,
body: Object.keys(requestBody).length > 0 ? requestBody : undefined,
httpResult: rawResponse
},
[DispatchNodeResponseKeyEnum.toolResponses]: rawResponse,
[NodeOutputKeyEnum.httpRawResponse]: rawResponse,
...results
};
} catch (error) {
addLog.error('Http request error', error);
return {
[NodeOutputKeyEnum.failed]: true,
[DispatchNodeResponseKeyEnum.nodeResponse]: {
totalPoints: 0,
body: Object.keys(requestBody).length > 0 ? requestBody : undefined,
httpResult: { error: formatHttpError(error) }
}
};
}
};
async function fetchData({
method,
url,
body
}: {
method: string;
url: string;
body: Record<string, any>;
}): Promise<Record<string, any>> {
const { data: response } = await axios({
method,
baseURL: `http://${SERVICE_LOCAL_HOST}`,
url,
headers: {
'Content-Type': 'application/json'
},
data: body
});
const parseJson = (obj: Record<string, any>, prefix = '') => {
let result: Record<string, any> = {};
if (Array.isArray(obj)) {
for (let i = 0; i < obj.length; i++) {
result[`${prefix}[${i}]`] = obj[i];
if (Array.isArray(obj[i])) {
result = {
...result,
...parseJson(obj[i], `${prefix}[${i}]`)
};
} else if (typeof obj[i] === 'object') {
result = {
...result,
...parseJson(obj[i], `${prefix}[${i}].`)
};
}
}
} else if (typeof obj == 'object') {
for (const key in obj) {
result[`${prefix}${key}`] = obj[key];
if (Array.isArray(obj[key])) {
result = {
...result,
...parseJson(obj[key], `${prefix}${key}`)
};
} else if (typeof obj[key] === 'object') {
result = {
...result,
...parseJson(obj[key], `${prefix}${key}.`)
};
}
}
}
return result;
};
return {
formatResponse:
typeof response === 'object' && !Array.isArray(response) ? parseJson(response) : {},
rawResponse: response
};
}
function replaceVariable(text: string, obj: Record<string, any>) {
for (const [key, value] of Object.entries(obj)) {
if (value === undefined) {
text = text.replace(new RegExp(`{{${key}}}`, 'g'), UNDEFINED_SIGN);
} else {
const replacement = JSON.stringify(value);
const unquotedReplacement =
replacement.startsWith('"') && replacement.endsWith('"')
? replacement.slice(1, -1)
: replacement;
text = text.replace(new RegExp(`{{${key}}}`, 'g'), unquotedReplacement);
}
}
return text || '';
}
function removeUndefinedSign(obj: Record<string, any>) {
for (const key in obj) {
if (obj[key] === UNDEFINED_SIGN) {
obj[key] = undefined;
} else if (Array.isArray(obj[key])) {
obj[key] = obj[key].map((item: any) => {
if (item === UNDEFINED_SIGN) {
return undefined;
} else if (typeof item === 'object') {
removeUndefinedSign(item);
}
return item;
});
} else if (typeof obj[key] === 'object') {
removeUndefinedSign(obj[key]);
}
}
return obj;
}
function formatHttpError(error: any) {
return {
message: error?.message,
name: error?.name,
method: error?.config?.method,
baseURL: error?.config?.baseURL,
url: error?.config?.url,
code: error?.code,
status: error?.status
};
}
import {
AIChatItemValueItemType,
ChatHistoryItemResType,
ChatItemValueItemType,
ToolRunResponseItemType
} from '@fastgpt/global/core/chat/type';
import { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/workflow/runtime/constants';
import { ChatNodeUsageType } from '@fastgpt/global/support/wallet/bill/type';
export type DispatchFlowResponse = {
flowResponses: ChatHistoryItemResType[];
flowUsages: ChatNodeUsageType[];
[DispatchNodeResponseKeyEnum.toolResponses]: ToolRunResponseItemType;
[DispatchNodeResponseKeyEnum.assistantResponses]: AIChatItemValueItemType[];
newVariables: Record<string, string>;
};
// @ts-nocheck
import type { ChatItemType } from '@fastgpt/global/core/chat/type.d';
import {
WorkflowIOValueTypeEnum,
NodeOutputKeyEnum
} from '@fastgpt/global/core/workflow/constants';
import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
import { FlowNodeItemType, StoreNodeItemType } from '@fastgpt/global/core/workflow/type/node';
export const DYNAMIC_INPUT_KEY = 'DYNAMIC_INPUT_KEY';
export const setEntryEntries = (modules: StoreNodeItemType[]) => {
const initRunningModuleType: Record<string, boolean> = {
questionInput: true,
[FlowNodeTypeEnum.pluginInput]: true
};
modules.forEach((item) => {
if (initRunningModuleType[item.flowType]) {
item.isEntry = true;
}
});
return modules;
};
export const checkTheModuleConnectedByTool = (
modules: FlowNodeItemType[],
module: FlowNodeItemType
) => {
let sign = false;
const toolModules = modules.filter((item) => item.flowType === FlowNodeTypeEnum.tools);
toolModules.forEach((item) => {
const toolOutput = item.outputs.find(
(output) => output.key === NodeOutputKeyEnum.selectedTools
);
toolOutput?.targets.forEach((target) => {
if (target.moduleId === module.moduleId) {
sign = true;
}
});
});
return sign;
};
export const getHistories = (history?: ChatItemType[] | number, histories: ChatItemType[] = []) => {
if (!history) return [];
if (typeof history === 'number') return histories.slice(-history);
if (Array.isArray(history)) return history;
return [];
};
/* value type format */
export const valueTypeFormat = (value: any, type?: `${WorkflowIOValueTypeEnum}`) => {
if (value === undefined) return;
if (type === 'string') {
if (typeof value !== 'object') return String(value);
return JSON.stringify(value);
}
if (type === 'number') return Number(value);
if (type === 'boolean') return Boolean(value);
return value;
};
...@@ -50,6 +50,9 @@ const MySelect = <T = any,>( ...@@ -50,6 +50,9 @@ const MySelect = <T = any,>(
}> }>
) => { ) => {
const ButtonRef = useRef<HTMLButtonElement>(null); const ButtonRef = useRef<HTMLButtonElement>(null);
const MenuListRef = useRef<HTMLDivElement>(null);
const SelectedItemRef = useRef<HTMLDivElement>(null);
const menuItemStyles: MenuItemProps = { const menuItemStyles: MenuItemProps = {
borderRadius: 'sm', borderRadius: 'sm',
py: 2, py: 2,
...@@ -71,6 +74,14 @@ const MySelect = <T = any,>( ...@@ -71,6 +74,14 @@ const MySelect = <T = any,>(
} }
})); }));
useEffect(() => {
if (isOpen && MenuListRef.current && SelectedItemRef.current) {
const menu = MenuListRef.current;
const selectedItem = SelectedItemRef.current;
menu.scrollTop = selectedItem.offsetTop - menu.offsetTop - 100;
}
}, [isOpen]);
return ( return (
<Box <Box
css={css({ css={css({
...@@ -113,6 +124,7 @@ const MySelect = <T = any,>( ...@@ -113,6 +124,7 @@ const MySelect = <T = any,>(
</MenuButton> </MenuButton>
<MenuList <MenuList
ref={MenuListRef}
className={props.className} className={props.className}
minW={(() => { minW={(() => {
const w = ButtonRef.current?.clientWidth; const w = ButtonRef.current?.clientWidth;
...@@ -140,6 +152,7 @@ const MySelect = <T = any,>( ...@@ -140,6 +152,7 @@ const MySelect = <T = any,>(
{...menuItemStyles} {...menuItemStyles}
{...(value === item.value {...(value === item.value
? { ? {
ref: SelectedItemRef,
color: 'primary.600', color: 'primary.600',
bg: 'myGray.100' bg: 'myGray.100'
} }
......
...@@ -45,7 +45,11 @@ const NodeInputSelect = ({ ...@@ -45,7 +45,11 @@ const NodeInputSelect = ({
{ {
type: FlowNodeInputTypeEnum.switch, type: FlowNodeInputTypeEnum.switch,
icon: FlowNodeInputMap[FlowNodeInputTypeEnum.switch].icon, icon: FlowNodeInputMap[FlowNodeInputTypeEnum.switch].icon,
title: t('common:core.workflow.inputType.Manual select')
},
{
type: FlowNodeInputTypeEnum.select,
icon: FlowNodeInputMap[FlowNodeInputTypeEnum.select].icon,
title: t('common:core.workflow.inputType.Manual select') title: t('common:core.workflow.inputType.Manual select')
}, },
{ {
......
...@@ -902,6 +902,7 @@ ...@@ -902,6 +902,7 @@
"System Plugin": "System", "System Plugin": "System",
"System input module": "System input", "System input module": "System input",
"Team Plugin": "Team", "Team Plugin": "Team",
"Team app": "Team",
"Tool module": "Tool", "Tool module": "Tool",
"UnKnow Module": "Unknown module", "UnKnow Module": "Unknown module",
"http body placeholder": "Same syntax as APIFox" "http body placeholder": "Same syntax as APIFox"
...@@ -1162,6 +1163,7 @@ ...@@ -1162,6 +1163,7 @@
"Please bind laf accout first": "Please bind laf account first", "Please bind laf accout first": "Please bind laf account first",
"Plugin List": "Plugin list", "Plugin List": "Plugin list",
"Search plugin": "Search plugin", "Search plugin": "Search plugin",
"Search_app": "Search app",
"Set Name": "Name the plugin", "Set Name": "Name the plugin",
"contribute": "Contribute plugins", "contribute": "Contribute plugins",
"go to laf": "Go to write", "go to laf": "Go to write",
......
...@@ -902,6 +902,7 @@ ...@@ -902,6 +902,7 @@
"System Plugin": "系统插件", "System Plugin": "系统插件",
"System input module": "系统输入", "System input module": "系统输入",
"Team Plugin": "团队插件", "Team Plugin": "团队插件",
"Team app": "团队应用",
"Tool module": "工具", "Tool module": "工具",
"UnKnow Module": "未知模块", "UnKnow Module": "未知模块",
"http body placeholder": "与 Apifox 相同的语法" "http body placeholder": "与 Apifox 相同的语法"
...@@ -1162,6 +1163,7 @@ ...@@ -1162,6 +1163,7 @@
"Please bind laf accout first": "请先绑定 laf 账号", "Please bind laf accout first": "请先绑定 laf 账号",
"Plugin List": "插件列表", "Plugin List": "插件列表",
"Search plugin": "搜索插件", "Search plugin": "搜索插件",
"Search_app": "搜索应用",
"Set Name": "给插件取个名字", "Set Name": "给插件取个名字",
"contribute": "贡献插件", "contribute": "贡献插件",
"go to laf": "去编写", "go to laf": "去编写",
......
...@@ -114,7 +114,7 @@ export const onCreateApp = async ({ ...@@ -114,7 +114,7 @@ export const onCreateApp = async ({
type, type,
version: 'v2', version: 'v2',
pluginData, pluginData,
...(type === AppTypeEnum.plugin && { 'pluginData.nodeVersion': defaultNodeVersion }) 'pluginData.nodeVersion': defaultNodeVersion
} }
], ],
{ session } { session }
......
...@@ -56,7 +56,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse<any>): Promise< ...@@ -56,7 +56,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse<any>): Promise<
scheduledTriggerNextTime: chatConfig?.scheduledTriggerConfig?.cronString scheduledTriggerNextTime: chatConfig?.scheduledTriggerConfig?.cronString
? getNextTimeByCronStringAndTimezone(chatConfig.scheduledTriggerConfig) ? getNextTimeByCronStringAndTimezone(chatConfig.scheduledTriggerConfig)
: null, : null,
...(app.type === AppTypeEnum.plugin && { 'pluginData.nodeVersion': _id }) 'pluginData.nodeVersion': _id
}, },
{ {
session session
......
...@@ -73,7 +73,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse<any>): Promise< ...@@ -73,7 +73,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse<any>): Promise<
scheduledTriggerNextTime: scheduledTriggerConfig?.cronString scheduledTriggerNextTime: scheduledTriggerConfig?.cronString
? getNextTimeByCronStringAndTimezone(scheduledTriggerConfig) ? getNextTimeByCronStringAndTimezone(scheduledTriggerConfig)
: null, : null,
...(app.type === AppTypeEnum.plugin && { 'pluginData.nodeVersion': _id }) 'pluginData.nodeVersion': _id
}); });
}); });
......
...@@ -28,6 +28,8 @@ import { ...@@ -28,6 +28,8 @@ import {
storeNodes2RuntimeNodes storeNodes2RuntimeNodes
} from '@fastgpt/global/core/workflow/runtime/utils'; } from '@fastgpt/global/core/workflow/runtime/utils';
import { StoreNodeItemType } from '@fastgpt/global/core/workflow/type/node'; import { StoreNodeItemType } from '@fastgpt/global/core/workflow/type/node';
import { getWorkflowResponseWrite } from '@fastgpt/service/core/workflow/dispatch/utils';
import { getNanoid } from '@fastgpt/global/common/string/tools';
export type Props = { export type Props = {
messages: ChatCompletionMessageParam[]; messages: ChatCompletionMessageParam[];
...@@ -95,6 +97,12 @@ async function handler(req: NextApiRequest, res: NextApiResponse) { ...@@ -95,6 +97,12 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
} }
runtimeNodes = rewriteNodeOutputByHistories(chatMessages, runtimeNodes); runtimeNodes = rewriteNodeOutputByHistories(chatMessages, runtimeNodes);
const workflowResponseWrite = getWorkflowResponseWrite({
res,
detail: true,
streamResponse: true,
id: getNanoid(24)
});
/* start process */ /* start process */
const { flowResponses, flowUsages } = await dispatchWorkFlow({ const { flowResponses, flowUsages } = await dispatchWorkFlow({
...@@ -112,8 +120,8 @@ async function handler(req: NextApiRequest, res: NextApiResponse) { ...@@ -112,8 +120,8 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
chatConfig, chatConfig,
histories: chatMessages, histories: chatMessages,
stream: true, stream: true,
detail: true, maxRunTimes: 200,
maxRunTimes: 200 workflowStreamResponse: workflowResponseWrite
}); });
responseWrite({ responseWrite({
......
...@@ -54,7 +54,6 @@ async function handler( ...@@ -54,7 +54,6 @@ async function handler(
chatConfig: defaultApp.chatConfig, chatConfig: defaultApp.chatConfig,
histories: [], histories: [],
stream: false, stream: false,
detail: true,
maxRunTimes: 200 maxRunTimes: 200
}); });
......
...@@ -48,8 +48,6 @@ import { OutLinkChatAuthProps } from '@fastgpt/global/support/permission/chat'; ...@@ -48,8 +48,6 @@ import { OutLinkChatAuthProps } from '@fastgpt/global/support/permission/chat';
import { UserChatItemType } from '@fastgpt/global/core/chat/type'; import { UserChatItemType } from '@fastgpt/global/core/chat/type';
import { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/workflow/runtime/constants'; import { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/workflow/runtime/constants';
import { dispatchWorkFlowV1 } from '@fastgpt/service/core/workflow/dispatchV1';
import { setEntryEntries } from '@fastgpt/service/core/workflow/dispatchV1/utils';
import { NextAPI } from '@/service/middleware/entry'; import { NextAPI } from '@/service/middleware/entry';
import { getAppLatestVersion } from '@fastgpt/service/core/app/controller'; import { getAppLatestVersion } from '@fastgpt/service/core/app/controller';
import { ReadPermissionVal } from '@fastgpt/global/support/permission/constant'; import { ReadPermissionVal } from '@fastgpt/global/support/permission/constant';
...@@ -65,6 +63,7 @@ import { ...@@ -65,6 +63,7 @@ import {
} from '@fastgpt/global/core/app/plugin/utils'; } from '@fastgpt/global/core/app/plugin/utils';
import { getSystemTime } from '@fastgpt/global/common/time/timezone'; import { getSystemTime } from '@fastgpt/global/common/time/timezone';
import { rewriteNodeOutputByHistories } from '@fastgpt/global/core/workflow/runtime/utils'; import { rewriteNodeOutputByHistories } from '@fastgpt/global/core/workflow/runtime/utils';
import { getWorkflowResponseWrite } from '@fastgpt/service/core/workflow/dispatch/utils';
type FastGptWebChatProps = { type FastGptWebChatProps = {
chatId?: string; // undefined: get histories from messages, '': new chat, 'xxxxx': get histories from db chatId?: string; // undefined: get histories from messages, '': new chat, 'xxxxx': get histories from db
...@@ -243,6 +242,13 @@ async function handler(req: NextApiRequest, res: NextApiResponse) { ...@@ -243,6 +242,13 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
runtimeNodes = rewriteNodeOutputByHistories(newHistories, runtimeNodes); runtimeNodes = rewriteNodeOutputByHistories(newHistories, runtimeNodes);
const workflowResponseWrite = getWorkflowResponseWrite({
res,
detail,
streamResponse: stream,
id: chatId || getNanoid(24)
});
/* start flow controller */ /* start flow controller */
const { flowResponses, flowUsages, assistantResponses, newVariables } = await (async () => { const { flowResponses, flowUsages, assistantResponses, newVariables } = await (async () => {
if (app.version === 'v2') { if (app.version === 'v2') {
...@@ -263,31 +269,11 @@ async function handler(req: NextApiRequest, res: NextApiResponse) { ...@@ -263,31 +269,11 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
chatConfig, chatConfig,
histories: newHistories, histories: newHistories,
stream, stream,
detail, maxRunTimes: 200,
maxRunTimes: 200 workflowStreamResponse: workflowResponseWrite
}); });
} }
return dispatchWorkFlowV1({ return Promise.reject('请升级工作流');
res,
mode: 'chat',
user,
teamId: String(teamId),
tmbId: String(tmbId),
appId: String(app._id),
chatId,
responseChatItemId,
//@ts-ignore
modules: setEntryEntries(app.modules),
variables,
inputFiles: files,
histories: newHistories,
startParams: {
userChatInput: text
},
stream,
detail,
maxRunTimes: 200
});
})(); })();
// save chat // save chat
...@@ -346,9 +332,8 @@ async function handler(req: NextApiRequest, res: NextApiResponse) { ...@@ -346,9 +332,8 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
: filterPublicNodeResponseData({ flowResponses }); : filterPublicNodeResponseData({ flowResponses });
if (stream) { if (stream) {
responseWrite({ workflowResponseWrite({
res, event: SseResponseEventEnum.answer,
event: detail ? SseResponseEventEnum.answer : undefined,
data: textAdaptGptResponse({ data: textAdaptGptResponse({
text: null, text: null,
finish_reason: 'stop' finish_reason: 'stop'
...@@ -362,10 +347,9 @@ async function handler(req: NextApiRequest, res: NextApiResponse) { ...@@ -362,10 +347,9 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
if (detail) { if (detail) {
if (responseDetail || isPlugin) { if (responseDetail || isPlugin) {
responseWrite({ workflowResponseWrite({
res,
event: SseResponseEventEnum.flowResponses, event: SseResponseEventEnum.flowResponses,
data: JSON.stringify(feResponseData) data: feResponseData
}); });
} }
} }
......
...@@ -142,16 +142,15 @@ const NodeTemplatesModal = ({ isOpen, onClose }: ModuleTemplateListProps) => { ...@@ -142,16 +142,15 @@ const NodeTemplatesModal = ({ isOpen, onClose }: ModuleTemplateListProps) => {
searchVal?: string; searchVal?: string;
}) => { }) => {
if (type === TemplateTypeEnum.teamPlugin) { if (type === TemplateTypeEnum.teamPlugin) {
const plugins = await getTeamPlugTemplates({ const teamApps = await getTeamPlugTemplates({
parentId, parentId,
searchKey: searchVal, searchKey: searchVal
type: [AppTypeEnum.folder, AppTypeEnum.httpPlugin, AppTypeEnum.plugin]
}).then((res) => res.filter((app) => app.id !== appId)); }).then((res) => res.filter((app) => app.id !== appId));
return plugins.map<NodeTemplateListItemType>((plugin) => { return teamApps.map<NodeTemplateListItemType>((app) => {
const member = members.find((member) => member.tmbId === plugin.tmbId); const member = members.find((member) => member.tmbId === app.tmbId);
return { return {
...plugin, ...app,
author: member?.memberName, author: member?.memberName,
authorAvatar: member?.avatar authorAvatar: member?.avatar
}; };
...@@ -266,7 +265,7 @@ const NodeTemplatesModal = ({ isOpen, onClose }: ModuleTemplateListProps) => { ...@@ -266,7 +265,7 @@ const NodeTemplatesModal = ({ isOpen, onClose }: ModuleTemplateListProps) => {
}, },
{ {
icon: 'core/modules/teamPlugin', icon: 'core/modules/teamPlugin',
label: t('common:core.module.template.Team Plugin'), label: t('common:core.module.template.Team app'),
value: TemplateTypeEnum.teamPlugin value: TemplateTypeEnum.teamPlugin
} }
]} ]}
...@@ -302,7 +301,11 @@ const NodeTemplatesModal = ({ isOpen, onClose }: ModuleTemplateListProps) => { ...@@ -302,7 +301,11 @@ const NodeTemplatesModal = ({ isOpen, onClose }: ModuleTemplateListProps) => {
<Input <Input
h={'full'} h={'full'}
bg={'myGray.50'} bg={'myGray.50'}
placeholder={t('common:plugin.Search plugin')} placeholder={
templateType === TemplateTypeEnum.teamPlugin
? t('common:plugin.Search_app')
: t('common:plugin.Search plugin')
}
onChange={(e) => setSearchKey(e.target.value)} onChange={(e) => setSearchKey(e.target.value)}
/> />
</InputGroup> </InputGroup>
...@@ -424,7 +427,10 @@ const RenderList = React.memo(function RenderList({ ...@@ -424,7 +427,10 @@ const RenderList = React.memo(function RenderList({
const templateNode = await (async () => { const templateNode = await (async () => {
try { try {
// get plugin preview module // get plugin preview module
if (template.flowNodeType === FlowNodeTypeEnum.pluginModule) { if (
template.flowNodeType === FlowNodeTypeEnum.pluginModule ||
template.flowNodeType === FlowNodeTypeEnum.appModule
) {
setLoading(true); setLoading(true);
const res = await getPreviewPluginNode({ appId: template.id }); const res = await getPreviewPluginNode({ appId: template.id });
......
...@@ -38,6 +38,7 @@ const nodeTypes: Record<FlowNodeTypeEnum, any> = { ...@@ -38,6 +38,7 @@ const nodeTypes: Record<FlowNodeTypeEnum, any> = {
[FlowNodeTypeEnum.contentExtract]: dynamic(() => import('./nodes/NodeExtract')), [FlowNodeTypeEnum.contentExtract]: dynamic(() => import('./nodes/NodeExtract')),
[FlowNodeTypeEnum.httpRequest468]: dynamic(() => import('./nodes/NodeHttp')), [FlowNodeTypeEnum.httpRequest468]: dynamic(() => import('./nodes/NodeHttp')),
[FlowNodeTypeEnum.runApp]: NodeSimple, [FlowNodeTypeEnum.runApp]: NodeSimple,
[FlowNodeTypeEnum.appModule]: NodeSimple,
[FlowNodeTypeEnum.pluginInput]: dynamic(() => import('./nodes/NodePluginIO/PluginInput')), [FlowNodeTypeEnum.pluginInput]: dynamic(() => import('./nodes/NodePluginIO/PluginInput')),
[FlowNodeTypeEnum.pluginOutput]: dynamic(() => import('./nodes/NodePluginIO/PluginOutput')), [FlowNodeTypeEnum.pluginOutput]: dynamic(() => import('./nodes/NodePluginIO/PluginOutput')),
[FlowNodeTypeEnum.pluginModule]: NodeSimple, [FlowNodeTypeEnum.pluginModule]: NodeSimple,
......
...@@ -19,7 +19,6 @@ import { storeNode2FlowNode, getLatestNodeTemplate } from '@/web/core/workflow/u ...@@ -19,7 +19,6 @@ import { storeNode2FlowNode, getLatestNodeTemplate } from '@/web/core/workflow/u
import { getNanoid } from '@fastgpt/global/common/string/tools'; import { getNanoid } from '@fastgpt/global/common/string/tools';
import { useContextSelector } from 'use-context-selector'; import { useContextSelector } from 'use-context-selector';
import { WorkflowContext } from '../../../context'; import { WorkflowContext } from '../../../context';
import { useI18n } from '@/web/context/I18n';
import { moduleTemplatesFlat } from '@fastgpt/global/core/workflow/template/constants'; import { moduleTemplatesFlat } from '@fastgpt/global/core/workflow/template/constants';
import { QuestionOutlineIcon } from '@chakra-ui/icons'; import { QuestionOutlineIcon } from '@chakra-ui/icons';
import MyTooltip from '@fastgpt/web/components/common/MyTooltip'; import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
...@@ -84,7 +83,10 @@ const NodeCard = (props: Props) => { ...@@ -84,7 +83,10 @@ const NodeCard = (props: Props) => {
const { data: nodeTemplate, runAsync: getNodeLatestTemplate } = useRequest2( const { data: nodeTemplate, runAsync: getNodeLatestTemplate } = useRequest2(
async () => { async () => {
if (node?.flowNodeType === FlowNodeTypeEnum.pluginModule) { if (
node?.flowNodeType === FlowNodeTypeEnum.pluginModule ||
node?.flowNodeType === FlowNodeTypeEnum.appModule
) {
if (!node?.pluginId) return; if (!node?.pluginId) return;
const template = await getPreviewPluginNode({ appId: node.pluginId }); const template = await getPreviewPluginNode({ appId: node.pluginId });
...@@ -115,7 +117,10 @@ const NodeCard = (props: Props) => { ...@@ -115,7 +117,10 @@ const NodeCard = (props: Props) => {
const template = moduleTemplatesFlat.find((item) => item.flowNodeType === node?.flowNodeType); const template = moduleTemplatesFlat.find((item) => item.flowNodeType === node?.flowNodeType);
if (!node || !template) return; if (!node || !template) return;
if (node?.flowNodeType === FlowNodeTypeEnum.pluginModule) { if (
node?.flowNodeType === FlowNodeTypeEnum.pluginModule ||
node?.flowNodeType === FlowNodeTypeEnum.appModule
) {
if (!node.pluginId) return; if (!node.pluginId) return;
onResetNode({ onResetNode({
id: nodeId, id: nodeId,
...@@ -298,11 +303,6 @@ const MenuRender = React.memo(function MenuRender({ ...@@ -298,11 +303,6 @@ const MenuRender = React.memo(function MenuRender({
const { t } = useTranslation(); const { t } = useTranslation();
const { openDebugNode, DebugInputModal } = useDebug(); const { openDebugNode, DebugInputModal } = useDebug();
const { openConfirm: onOpenConfirmDeleteNode, ConfirmModal: ConfirmDeleteModal } = useConfirm({
content: t('common:core.module.Confirm Delete Node'),
type: 'delete'
});
const setNodes = useContextSelector(WorkflowContext, (v) => v.setNodes); const setNodes = useContextSelector(WorkflowContext, (v) => v.setNodes);
const setEdges = useContextSelector(WorkflowContext, (v) => v.setEdges); const setEdges = useContextSelector(WorkflowContext, (v) => v.setEdges);
const { computedNewNodeName } = useWorkflowUtils(); const { computedNewNodeName } = useWorkflowUtils();
...@@ -420,7 +420,6 @@ const MenuRender = React.memo(function MenuRender({ ...@@ -420,7 +420,6 @@ const MenuRender = React.memo(function MenuRender({
</Box> </Box>
))} ))}
</Box> </Box>
<ConfirmDeleteModal />
<DebugInputModal /> <DebugInputModal />
</> </>
); );
...@@ -429,7 +428,6 @@ const MenuRender = React.memo(function MenuRender({ ...@@ -429,7 +428,6 @@ const MenuRender = React.memo(function MenuRender({
menuForbid?.copy, menuForbid?.copy,
menuForbid?.delete, menuForbid?.delete,
t, t,
ConfirmDeleteModal,
DebugInputModal, DebugInputModal,
openDebugNode, openDebugNode,
nodeId, nodeId,
......
...@@ -21,6 +21,10 @@ const RenderList: { ...@@ -21,6 +21,10 @@ const RenderList: {
Component: dynamic(() => import('./templates/TextInput')) Component: dynamic(() => import('./templates/TextInput'))
}, },
{ {
types: [FlowNodeInputTypeEnum.select],
Component: dynamic(() => import('./templates/Select'))
},
{
types: [FlowNodeInputTypeEnum.numberInput], types: [FlowNodeInputTypeEnum.numberInput],
Component: dynamic(() => import('./templates/NumberInput')) Component: dynamic(() => import('./templates/NumberInput'))
}, },
......
...@@ -52,7 +52,6 @@ export const getScheduleTriggerApp = async () => { ...@@ -52,7 +52,6 @@ export const getScheduleTriggerApp = async () => {
chatConfig: defaultApp.chatConfig, chatConfig: defaultApp.chatConfig,
histories: [], histories: [],
stream: false, stream: false,
detail: false,
maxRunTimes: 200 maxRunTimes: 200
}); });
pushChatUsage({ pushChatUsage({
......
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import { useToast } from '@fastgpt/web/hooks/useToast'; import { useToast } from '@fastgpt/web/hooks/useToast';
import { useCallback } from 'react'; import { useCallback } from 'react';
import { hasHttps } from '@fastgpt/web/common/system/utils';
/** /**
* copy text data * copy text data
...@@ -16,7 +17,7 @@ export const useCopyData = () => { ...@@ -16,7 +17,7 @@ export const useCopyData = () => {
duration = 1000 duration = 1000
) => { ) => {
try { try {
if (navigator.clipboard) { if (hasHttps() && navigator.clipboard) {
await navigator.clipboard.writeText(data); await navigator.clipboard.writeText(data);
} else { } else {
throw new Error(''); throw new Error('');
......
...@@ -7,7 +7,7 @@ import type { ...@@ -7,7 +7,7 @@ import type {
} from '@fastgpt/global/core/workflow/type/node'; } from '@fastgpt/global/core/workflow/type/node';
import { getMyApps } from '../api'; import { getMyApps } from '../api';
import type { ListAppBody } from '@/pages/api/core/app/list'; import type { ListAppBody } from '@/pages/api/core/app/list';
import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant'; import { defaultNodeVersion, FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
import { FlowNodeTemplateTypeEnum } from '@fastgpt/global/core/workflow/constants'; import { FlowNodeTemplateTypeEnum } from '@fastgpt/global/core/workflow/constants';
import type { GetPreviewNodeQuery } from '@/pages/api/core/app/plugin/getPreviewNode'; import type { GetPreviewNodeQuery } from '@/pages/api/core/app/plugin/getPreviewNode';
import { AppTypeEnum } from '@fastgpt/global/core/app/constants'; import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
...@@ -23,12 +23,15 @@ export const getTeamPlugTemplates = (data?: ListAppBody) => ...@@ -23,12 +23,15 @@ export const getTeamPlugTemplates = (data?: ListAppBody) =>
pluginId: app._id, pluginId: app._id,
isFolder: app.type === AppTypeEnum.folder || app.type === AppTypeEnum.httpPlugin, isFolder: app.type === AppTypeEnum.folder || app.type === AppTypeEnum.httpPlugin,
templateType: FlowNodeTemplateTypeEnum.teamApp, templateType: FlowNodeTemplateTypeEnum.teamApp,
flowNodeType: FlowNodeTypeEnum.pluginModule, flowNodeType:
app.type === AppTypeEnum.workflow
? FlowNodeTypeEnum.appModule
: FlowNodeTypeEnum.pluginModule,
avatar: app.avatar, avatar: app.avatar,
name: app.name, name: app.name,
intro: app.intro, intro: app.intro,
showStatus: false, showStatus: false,
version: app.pluginData?.nodeVersion || '481', version: app.pluginData?.nodeVersion || defaultNodeVersion,
isTool: true isTool: true
})) }))
); );
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment