mirror of
https://github.com/labring/FastGPT.git
synced 2025-08-03 13:38:00 +00:00
4.8 preview (#1288)
* Revert "lafAccount add pat & re request when token invalid (#76)" (#77) This reverts commit 83d85dfe37adcaef4833385ea52ee79fd84720be. * perf: workflow ux * system config * Newflow (#89) * docs: Add doc for Xinference (#1266) Signed-off-by: Carson Yang <yangchuansheng33@gmail.com> * Revert "lafAccount add pat & re request when token invalid (#76)" (#77) This reverts commit 83d85dfe37adcaef4833385ea52ee79fd84720be. * perf: workflow ux * system config * Revert "lafAccount add pat & re request when token invalid (#76)" (#77) This reverts commit 83d85dfe37adcaef4833385ea52ee79fd84720be. * Revert "lafAccount add pat & re request when token invalid (#76)" (#77) This reverts commit 83d85dfe37adcaef4833385ea52ee79fd84720be. * Revert "lafAccount add pat & re request when token invalid (#76)" (#77) This reverts commit 83d85dfe37adcaef4833385ea52ee79fd84720be. * rename code * move code * update flow * input type selector * perf: workflow runtime * feat: node adapt newflow * feat: adapt plugin * feat: 360 connection * check workflow * perf: flow 性能 * change plugin input type (#81) * change plugin input type * plugin label mode * perf: nodecard * debug * perf: debug ui * connection ui * change workflow ui (#82) * feat: workflow debug * adapt openAPI for new workflow (#83) * adapt openAPI for new workflow * i18n * perf: plugin debug * plugin input ui * delete * perf: global variable select * fix rebase * perf: workflow performance * feat: input render type icon * input icon * adapt flow (#84) * adapt newflow * temp * temp * fix * feat: app schedule trigger * feat: app schedule trigger * perf: schedule ui * feat: ioslatevm run js code * perf: workflow varialbe table ui * feat: adapt simple mode * feat: adapt input params * output * feat: adapt tamplate * fix: ts * add if-else module (#86) * perf: worker * if else node * perf: tiktoken worker * fix: ts * perf: tiktoken * fix if-else node (#87) * fix if-else node * type * fix * perf: audio render * perf: Parallel worker * log * perf: if else node * adapt plugin * prompt * perf: reference ui * reference ui * handle ux * template ui and plugin tool * adapt v1 workflow * adapt v1 workflow completions * perf: time variables * feat: workflow keyboard shortcuts * adapt v1 workflow * update workflow example doc (#88) * fix: simple mode select tool --------- Signed-off-by: Carson Yang <yangchuansheng33@gmail.com> Co-authored-by: Carson Yang <yangchuansheng33@gmail.com> Co-authored-by: heheer <71265218+newfish-cmyk@users.noreply.github.com> * doc * perf: extract node * extra node field * update plugin version * doc * variable * change doc & fix prompt editor (#90) * fold workflow code * value type label --------- Signed-off-by: Carson Yang <yangchuansheng33@gmail.com> Co-authored-by: Carson Yang <yangchuansheng33@gmail.com> Co-authored-by: heheer <71265218+newfish-cmyk@users.noreply.github.com>
This commit is contained in:
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,5 @@
|
||||
import { FlowNodeTypeEnum } from '@fastgpt/global/core/module/node/constant';
|
||||
import { ModuleItemType } from '@fastgpt/global/core/module/type.d';
|
||||
import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
|
||||
import { StoreNodeItemType } from '@fastgpt/global/core/workflow/type/index.d';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
|
||||
export function checkChatSupportSelectFileByChatModels(models: string[] = []) {
|
||||
@@ -14,10 +14,11 @@ export function checkChatSupportSelectFileByChatModels(models: string[] = []) {
|
||||
return false;
|
||||
}
|
||||
|
||||
export function checkChatSupportSelectFileByModules(modules: ModuleItemType[] = []) {
|
||||
export function checkChatSupportSelectFileByModules(modules: StoreNodeItemType[] = []) {
|
||||
const chatModules = modules.filter(
|
||||
(item) =>
|
||||
item.flowType === FlowNodeTypeEnum.chatNode || item.flowType === FlowNodeTypeEnum.tools
|
||||
item.flowNodeType === FlowNodeTypeEnum.chatNode ||
|
||||
item.flowNodeType === FlowNodeTypeEnum.tools
|
||||
);
|
||||
const models: string[] = chatModules.map(
|
||||
(item) => item.inputs.find((item) => item.key === 'model')?.value || ''
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import { GET, POST, DELETE, PUT } from '@/web/common/api/request';
|
||||
import { FlowNodeTemplateType } from '@fastgpt/global/core/module/type';
|
||||
import { FlowNodeTemplateType } from '@fastgpt/global/core/workflow/type/index.d';
|
||||
import { ParentTreePathItemType } from '@fastgpt/global/common/parentFolder/type';
|
||||
import { PluginTypeEnum } from '@fastgpt/global/core/plugin/constants';
|
||||
import {
|
||||
@@ -36,6 +36,6 @@ export const getSystemPlugTemplates = () =>
|
||||
GET<FlowNodeTemplateType[]>('/core/plugin/pluginTemplate/getSystemPluginTemplates');
|
||||
|
||||
export const getPreviewPluginModule = (id: string) =>
|
||||
GET<FlowNodeTemplateType>('/core/plugin/getPreviewModule', { id });
|
||||
GET<FlowNodeTemplateType>('/core/plugin/getPreviewNode', { id });
|
||||
export const getOnePlugin = (id: string) => GET<PluginItemSchema>('/core/plugin/detail', { id });
|
||||
export const delOnePlugin = (pluginId: string) => DELETE('/core/plugin/delete', { pluginId });
|
||||
|
501
projects/app/src/web/core/workflow/adapt.ts
Normal file
501
projects/app/src/web/core/workflow/adapt.ts
Normal file
@@ -0,0 +1,501 @@
|
||||
import {
|
||||
FlowNodeTemplateTypeEnum,
|
||||
NodeInputKeyEnum,
|
||||
NodeOutputKeyEnum,
|
||||
WorkflowIOValueTypeEnum
|
||||
} from '@fastgpt/global/core/workflow/constants';
|
||||
import {
|
||||
FlowNodeInputTypeEnum,
|
||||
FlowNodeOutputTypeEnum,
|
||||
FlowNodeTypeEnum
|
||||
} from '@fastgpt/global/core/workflow/node/constant';
|
||||
import { getHandleConfig } from '@fastgpt/global/core/workflow/template/utils';
|
||||
import {
|
||||
FlowNodeItemType,
|
||||
FlowNodeTemplateType,
|
||||
StoreNodeItemType
|
||||
} from '@fastgpt/global/core/workflow/type';
|
||||
import { VARIABLE_NODE_ID } from './constants';
|
||||
import { getHandleId, splitGuideModule } from '@fastgpt/global/core/workflow/utils';
|
||||
import { StoreEdgeItemType } from '@fastgpt/global/core/workflow/type/edge';
|
||||
import { LLMModelTypeEnum } from '@fastgpt/global/core/ai/constants';
|
||||
import {
|
||||
FlowNodeInputItemType,
|
||||
FlowNodeOutputItemType
|
||||
} from '@fastgpt/global/core/workflow/type/io';
|
||||
import { PluginTypeEnum } from '@fastgpt/global/core/plugin/constants';
|
||||
|
||||
export const systemConfigNode2VariableNode = (node: FlowNodeItemType) => {
|
||||
const template: FlowNodeTemplateType = {
|
||||
id: FlowNodeTypeEnum.globalVariable,
|
||||
templateType: FlowNodeTemplateTypeEnum.other,
|
||||
flowNodeType: FlowNodeTypeEnum.emptyNode,
|
||||
sourceHandle: getHandleConfig(false, false, false, false),
|
||||
targetHandle: getHandleConfig(false, false, false, false),
|
||||
avatar: '/imgs/workflow/variable.png',
|
||||
name: '全局变量',
|
||||
intro: '',
|
||||
unique: true,
|
||||
forbidDelete: true,
|
||||
inputs: [],
|
||||
outputs: []
|
||||
};
|
||||
|
||||
const { variableModules } = splitGuideModule(node);
|
||||
|
||||
const variableNode: FlowNodeItemType = {
|
||||
nodeId: VARIABLE_NODE_ID,
|
||||
...template,
|
||||
outputs: variableModules.map((item) => ({
|
||||
id: item.key,
|
||||
type: FlowNodeOutputTypeEnum.dynamic,
|
||||
label: item.label,
|
||||
key: item.key,
|
||||
valueType: WorkflowIOValueTypeEnum.any
|
||||
}))
|
||||
};
|
||||
|
||||
return variableNode;
|
||||
};
|
||||
|
||||
/* adapt v1 workfwlo */
|
||||
enum InputTypeEnum {
|
||||
triggerAndFinish = 'triggerAndFinish',
|
||||
systemInput = 'systemInput', // history, userChatInput, variableInput
|
||||
|
||||
input = 'input', // one line input
|
||||
numberInput = 'numberInput',
|
||||
select = 'select',
|
||||
slider = 'slider',
|
||||
target = 'target', // data input
|
||||
switch = 'switch',
|
||||
|
||||
// editor
|
||||
textarea = 'textarea',
|
||||
JSONEditor = 'JSONEditor',
|
||||
|
||||
addInputParam = 'addInputParam', // params input
|
||||
|
||||
selectApp = 'selectApp',
|
||||
|
||||
// chat special input
|
||||
aiSettings = 'aiSettings',
|
||||
|
||||
// ai model select
|
||||
selectLLMModel = 'selectLLMModel',
|
||||
settingLLMModel = 'settingLLMModel',
|
||||
|
||||
// dataset special input
|
||||
selectDataset = 'selectDataset',
|
||||
selectDatasetParamsModal = 'selectDatasetParamsModal',
|
||||
settingDatasetQuotePrompt = 'settingDatasetQuotePrompt',
|
||||
|
||||
hidden = 'hidden',
|
||||
custom = 'custom'
|
||||
}
|
||||
enum FlowTypeEnum {
|
||||
userGuide = 'userGuide',
|
||||
questionInput = 'questionInput',
|
||||
chatNode = 'chatNode',
|
||||
|
||||
datasetSearchNode = 'datasetSearchNode',
|
||||
datasetConcatNode = 'datasetConcatNode',
|
||||
|
||||
answerNode = 'answerNode',
|
||||
classifyQuestion = 'classifyQuestion',
|
||||
contentExtract = 'contentExtract',
|
||||
httpRequest468 = 'httpRequest468',
|
||||
runApp = 'app',
|
||||
pluginModule = 'pluginModule',
|
||||
pluginInput = 'pluginInput',
|
||||
pluginOutput = 'pluginOutput',
|
||||
queryExtension = 'cfr',
|
||||
tools = 'tools',
|
||||
stopTool = 'stopTool',
|
||||
lafModule = 'lafModule'
|
||||
}
|
||||
enum OutputTypeEnum {
|
||||
answer = 'answer',
|
||||
source = 'source',
|
||||
hidden = 'hidden',
|
||||
|
||||
addOutputParam = 'addOutputParam'
|
||||
}
|
||||
type V1WorkflowType = {
|
||||
name: string;
|
||||
avatar?: string;
|
||||
intro?: string;
|
||||
moduleId: string;
|
||||
position?: {
|
||||
x: number;
|
||||
y: number;
|
||||
};
|
||||
flowType: FlowTypeEnum;
|
||||
showStatus?: boolean;
|
||||
inputs: {
|
||||
valueType?: WorkflowIOValueTypeEnum; // data type
|
||||
type: InputTypeEnum; // Node Type. Decide on a render style
|
||||
key: `${NodeInputKeyEnum}` | string;
|
||||
value?: any;
|
||||
label: string;
|
||||
description?: string;
|
||||
required?: boolean;
|
||||
toolDescription?: string; // If this field is not empty, it is entered as a tool
|
||||
|
||||
edit?: boolean; // Whether to allow editing
|
||||
editField?: {
|
||||
inputType?: boolean;
|
||||
required?: boolean;
|
||||
isToolInput?: boolean;
|
||||
name?: boolean;
|
||||
key?: boolean;
|
||||
description?: boolean;
|
||||
dataType?: boolean;
|
||||
defaultValue?: boolean;
|
||||
};
|
||||
defaultEditField?: {
|
||||
inputType?: InputTypeEnum; // input type
|
||||
outputType?: `${FlowNodeOutputTypeEnum}`;
|
||||
required?: boolean;
|
||||
key?: string;
|
||||
label?: string;
|
||||
description?: string;
|
||||
valueType?: WorkflowIOValueTypeEnum;
|
||||
isToolInput?: boolean;
|
||||
defaultValue?: string;
|
||||
};
|
||||
|
||||
connected?: boolean; // There are incoming data
|
||||
|
||||
showTargetInApp?: boolean;
|
||||
showTargetInPlugin?: boolean;
|
||||
|
||||
hideInApp?: boolean;
|
||||
hideInPlugin?: boolean;
|
||||
|
||||
placeholder?: string; // input,textarea
|
||||
|
||||
list?: { label: string; value: any }[]; // select
|
||||
|
||||
markList?: { label: string; value: any }[]; // slider
|
||||
step?: number; // slider
|
||||
max?: number; // slider, number input
|
||||
min?: number; // slider, number input
|
||||
|
||||
llmModelType?: `${LLMModelTypeEnum}`;
|
||||
}[];
|
||||
outputs: {
|
||||
type?: OutputTypeEnum;
|
||||
key: `${NodeOutputKeyEnum}` | string;
|
||||
valueType?: WorkflowIOValueTypeEnum;
|
||||
|
||||
label?: string;
|
||||
description?: string;
|
||||
required?: boolean;
|
||||
defaultValue?: any;
|
||||
|
||||
edit?: boolean;
|
||||
editField?: {
|
||||
inputType?: boolean;
|
||||
required?: boolean;
|
||||
isToolInput?: boolean;
|
||||
name?: boolean;
|
||||
key?: boolean;
|
||||
description?: boolean;
|
||||
dataType?: boolean;
|
||||
defaultValue?: boolean;
|
||||
};
|
||||
defaultEditField?: {
|
||||
inputType?: `${FlowNodeInputTypeEnum}`; // input type
|
||||
outputType?: `${FlowNodeOutputTypeEnum}`;
|
||||
required?: boolean;
|
||||
key?: string;
|
||||
label?: string;
|
||||
description?: string;
|
||||
valueType?: `${WorkflowIOValueTypeEnum}`;
|
||||
isToolInput?: boolean;
|
||||
defaultValue?: string;
|
||||
};
|
||||
|
||||
targets: { moduleId: string; key: string }[];
|
||||
}[];
|
||||
|
||||
// runTime field
|
||||
isEntry?: boolean;
|
||||
pluginType?: `${PluginTypeEnum}`;
|
||||
parentId?: string;
|
||||
};
|
||||
export const v1Workflow2V2 = (
|
||||
nodes: V1WorkflowType[]
|
||||
): {
|
||||
nodes: StoreNodeItemType[];
|
||||
edges: StoreEdgeItemType[];
|
||||
} => {
|
||||
let copyNodes = JSON.parse(JSON.stringify(nodes)) as V1WorkflowType[];
|
||||
|
||||
// 只保留1个开始节点
|
||||
copyNodes = copyNodes.filter((node, index, self) => {
|
||||
if (node.flowType === FlowTypeEnum.questionInput) {
|
||||
return index === self.findIndex((item) => item.flowType === FlowTypeEnum.questionInput);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
const newNodes: StoreNodeItemType[] = copyNodes.map((node) => {
|
||||
// flowNodeType adapt
|
||||
const nodeTypeMap = {
|
||||
[FlowTypeEnum.userGuide]: FlowNodeTypeEnum.systemConfig,
|
||||
[FlowTypeEnum.questionInput]: FlowNodeTypeEnum.workflowStart,
|
||||
[FlowTypeEnum.chatNode]: FlowNodeTypeEnum.chatNode,
|
||||
[FlowTypeEnum.datasetSearchNode]: FlowNodeTypeEnum.datasetSearchNode,
|
||||
[FlowTypeEnum.datasetConcatNode]: FlowNodeTypeEnum.datasetConcatNode,
|
||||
[FlowTypeEnum.answerNode]: FlowNodeTypeEnum.answerNode,
|
||||
[FlowTypeEnum.classifyQuestion]: FlowNodeTypeEnum.classifyQuestion,
|
||||
[FlowTypeEnum.contentExtract]: FlowNodeTypeEnum.contentExtract,
|
||||
[FlowTypeEnum.httpRequest468]: FlowNodeTypeEnum.httpRequest468,
|
||||
[FlowTypeEnum.runApp]: FlowNodeTypeEnum.runApp,
|
||||
[FlowTypeEnum.pluginModule]: FlowNodeTypeEnum.pluginModule,
|
||||
[FlowTypeEnum.pluginInput]: FlowNodeTypeEnum.pluginInput,
|
||||
[FlowTypeEnum.pluginOutput]: FlowNodeTypeEnum.pluginOutput,
|
||||
[FlowTypeEnum.queryExtension]: FlowNodeTypeEnum.queryExtension,
|
||||
[FlowTypeEnum.tools]: FlowNodeTypeEnum.tools,
|
||||
[FlowTypeEnum.stopTool]: FlowNodeTypeEnum.stopTool,
|
||||
[FlowTypeEnum.lafModule]: FlowNodeTypeEnum.lafModule
|
||||
};
|
||||
|
||||
const inputTypeMap: Record<any, FlowNodeInputTypeEnum> = {
|
||||
[InputTypeEnum.systemInput]: FlowNodeInputTypeEnum.input,
|
||||
[InputTypeEnum.input]: FlowNodeInputTypeEnum.input,
|
||||
[InputTypeEnum.numberInput]: FlowNodeInputTypeEnum.numberInput,
|
||||
[InputTypeEnum.select]: FlowNodeInputTypeEnum.select,
|
||||
[InputTypeEnum.target]: FlowNodeInputTypeEnum.reference,
|
||||
[InputTypeEnum.switch]: FlowNodeInputTypeEnum.switch,
|
||||
[InputTypeEnum.textarea]: FlowNodeInputTypeEnum.textarea,
|
||||
[InputTypeEnum.JSONEditor]: FlowNodeInputTypeEnum.JSONEditor,
|
||||
[InputTypeEnum.addInputParam]: FlowNodeInputTypeEnum.addInputParam,
|
||||
[InputTypeEnum.selectApp]: FlowNodeInputTypeEnum.selectApp,
|
||||
[InputTypeEnum.selectLLMModel]: FlowNodeInputTypeEnum.selectLLMModel,
|
||||
[InputTypeEnum.settingLLMModel]: FlowNodeInputTypeEnum.settingLLMModel,
|
||||
[InputTypeEnum.selectDataset]: FlowNodeInputTypeEnum.selectDataset,
|
||||
[InputTypeEnum.selectDatasetParamsModal]: FlowNodeInputTypeEnum.selectDatasetParamsModal,
|
||||
[InputTypeEnum.settingDatasetQuotePrompt]: FlowNodeInputTypeEnum.settingDatasetQuotePrompt,
|
||||
[InputTypeEnum.hidden]: FlowNodeInputTypeEnum.hidden,
|
||||
[InputTypeEnum.custom]: FlowNodeInputTypeEnum.custom
|
||||
};
|
||||
let pluginId: string | undefined = undefined;
|
||||
const inputs = node.inputs
|
||||
.map<FlowNodeInputItemType>((input) => {
|
||||
const newInput: FlowNodeInputItemType = {
|
||||
...input,
|
||||
selectedTypeIndex: 0,
|
||||
renderTypeList: inputTypeMap[input.type] ? [inputTypeMap[input.type]] : [],
|
||||
|
||||
key: input.key,
|
||||
value: input.value,
|
||||
valueType: input.valueType,
|
||||
|
||||
label: input.label,
|
||||
description: input.description,
|
||||
required: input.required,
|
||||
toolDescription: input.toolDescription,
|
||||
canEdit: input.edit,
|
||||
placeholder: input.placeholder,
|
||||
list: input.list,
|
||||
markList: input.markList,
|
||||
step: input.step,
|
||||
max: input.max,
|
||||
min: input.min,
|
||||
editField: input.editField,
|
||||
dynamicParamDefaultValue: input.defaultEditField
|
||||
? {
|
||||
inputType: input.defaultEditField.inputType
|
||||
? inputTypeMap[input.defaultEditField.inputType]
|
||||
: undefined,
|
||||
valueType: input.defaultEditField.valueType,
|
||||
required: input.defaultEditField.required
|
||||
}
|
||||
: undefined,
|
||||
llmModelType: input.llmModelType
|
||||
};
|
||||
|
||||
if (input.key === 'userChatInput') {
|
||||
newInput.label = '问题输入';
|
||||
} else if (input.key === 'quoteQA') {
|
||||
newInput.label = '';
|
||||
} else if (input.key === 'pluginId') {
|
||||
pluginId = input.value;
|
||||
}
|
||||
|
||||
return newInput;
|
||||
})
|
||||
.filter((input) => input.renderTypeList.length > 0)
|
||||
.filter((input) => {
|
||||
if (input.key === 'pluginId') {
|
||||
return false;
|
||||
}
|
||||
if (input.key === 'switch') {
|
||||
return false;
|
||||
}
|
||||
if (input.key === 'pluginStart') {
|
||||
return false;
|
||||
}
|
||||
if (input.key === 'DYNAMIC_INPUT_KEY') return;
|
||||
if (input.key === 'system_addInputParam') return;
|
||||
return true;
|
||||
});
|
||||
|
||||
const outputTypeMap: Record<any, FlowNodeOutputTypeEnum> = {
|
||||
[OutputTypeEnum.addOutputParam]: FlowNodeOutputTypeEnum.dynamic,
|
||||
[OutputTypeEnum.answer]: FlowNodeOutputTypeEnum.static,
|
||||
[OutputTypeEnum.source]: FlowNodeOutputTypeEnum.static,
|
||||
[OutputTypeEnum.hidden]: FlowNodeOutputTypeEnum.hidden
|
||||
};
|
||||
|
||||
const outputs = node.outputs
|
||||
.map<FlowNodeOutputItemType>((output) => ({
|
||||
id: output.key,
|
||||
type: output.type ? outputTypeMap[output.type] : FlowNodeOutputTypeEnum.static,
|
||||
key: output.key,
|
||||
valueType: output.valueType,
|
||||
label: output.label,
|
||||
description: output.description,
|
||||
required: output.required,
|
||||
defaultValue: output.defaultValue,
|
||||
canEdit: output.edit,
|
||||
editField: output.editField
|
||||
}))
|
||||
.filter((output) => {
|
||||
if (node.flowType === FlowTypeEnum.pluginOutput) return false;
|
||||
if (output.key === 'finish') return false;
|
||||
if (output.key === 'isEmpty') return false;
|
||||
if (output.key === 'unEmpty') return false;
|
||||
if (output.key === 'pluginStart') return false;
|
||||
if (node.flowType !== FlowTypeEnum.questionInput && output.key === 'userChatInput')
|
||||
return false;
|
||||
if (
|
||||
node.flowType === FlowTypeEnum.contentExtract &&
|
||||
(output.key === 'success' || output.key === 'failed')
|
||||
)
|
||||
return;
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
// special node
|
||||
if (node.flowType === FlowTypeEnum.questionInput) {
|
||||
node.name = '流程开始';
|
||||
} else if (node.flowType === FlowTypeEnum.pluginOutput) {
|
||||
node.outputs.forEach((output) => {
|
||||
inputs.push({
|
||||
key: output.key,
|
||||
valueType: output.valueType,
|
||||
renderTypeList: [FlowNodeInputTypeEnum.reference],
|
||||
label: output.key,
|
||||
canEdit: true,
|
||||
editField: {
|
||||
key: true,
|
||||
description: true,
|
||||
valueType: true
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
nodeId: node.moduleId,
|
||||
position: node.position,
|
||||
flowNodeType: nodeTypeMap[node.flowType],
|
||||
avatar: node.flowType === FlowTypeEnum.pluginModule ? node.avatar : undefined,
|
||||
name: node.name,
|
||||
intro: node.intro,
|
||||
showStatus: node.showStatus,
|
||||
pluginId,
|
||||
pluginType: node.pluginType,
|
||||
parentId: node.parentId,
|
||||
|
||||
inputs,
|
||||
outputs
|
||||
};
|
||||
});
|
||||
let newEdges: StoreEdgeItemType[] = [];
|
||||
|
||||
// 遍历output,连线
|
||||
copyNodes.forEach((node) => {
|
||||
node.outputs.forEach((output) => {
|
||||
output.targets?.forEach((target) => {
|
||||
if (output.key === 'finish') return;
|
||||
if (output.key === 'isEmpty') return;
|
||||
if (output.key === 'unEmpty') return;
|
||||
if (node.flowType !== FlowTypeEnum.questionInput && output.key === 'userChatInput') return;
|
||||
|
||||
if (output.key === NodeOutputKeyEnum.selectedTools) {
|
||||
newEdges.push({
|
||||
source: node.moduleId,
|
||||
sourceHandle: NodeOutputKeyEnum.selectedTools,
|
||||
target: target.moduleId,
|
||||
targetHandle: NodeOutputKeyEnum.selectedTools
|
||||
});
|
||||
} else if (node.flowType === FlowTypeEnum.classifyQuestion) {
|
||||
newEdges.push({
|
||||
source: node.moduleId,
|
||||
sourceHandle: getHandleId(node.moduleId, 'source', output.key),
|
||||
target: target.moduleId,
|
||||
targetHandle: getHandleId(target.moduleId, 'target', 'left')
|
||||
});
|
||||
} else if (node.flowType === FlowTypeEnum.contentExtract) {
|
||||
} else {
|
||||
newEdges.push({
|
||||
source: node.moduleId,
|
||||
sourceHandle: getHandleId(node.moduleId, 'source', 'right'),
|
||||
target: target.moduleId,
|
||||
targetHandle: getHandleId(target.moduleId, 'target', 'left')
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// 去除相同source和target的线
|
||||
newEdges = newEdges.filter((edge, index, self) => {
|
||||
return (
|
||||
self.findIndex((item) => item.source === edge.source && item.target === edge.target) === index
|
||||
);
|
||||
});
|
||||
|
||||
const workflowStart = newNodes.find(
|
||||
(node) => node.flowNodeType === FlowNodeTypeEnum.workflowStart
|
||||
);
|
||||
|
||||
/* 更新input的取值 */
|
||||
copyNodes.forEach((node) => {
|
||||
node.outputs.forEach((output) => {
|
||||
output.targets?.forEach((target) => {
|
||||
const targetNode = newNodes.find((item) => item.nodeId === target.moduleId);
|
||||
if (!targetNode) return;
|
||||
|
||||
const targetInput = targetNode.inputs.find((item) => item.key === target.key);
|
||||
if (!targetInput) return;
|
||||
|
||||
targetInput.value = [node.moduleId, output.key];
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// 更新特殊的输入(输入全部从开始取)
|
||||
newNodes.forEach((node) => {
|
||||
node.inputs.forEach((input) => {
|
||||
if (workflowStart && input.key === NodeInputKeyEnum.userChatInput) {
|
||||
input.value = [workflowStart.nodeId, NodeOutputKeyEnum.userChatInput];
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
console.log({
|
||||
nodes: newNodes.filter((node) => node.nodeId),
|
||||
edges: newEdges
|
||||
});
|
||||
return {
|
||||
nodes: newNodes.filter((node) => node.nodeId),
|
||||
edges: newEdges
|
||||
};
|
||||
};
|
8
projects/app/src/web/core/workflow/api.ts
Normal file
8
projects/app/src/web/core/workflow/api.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { GET, POST, PUT, DELETE } from '@/web/common/api/request';
|
||||
import { PostWorkflowDebugProps, PostWorkflowDebugResponse } from '@/global/core/workflow/api';
|
||||
|
||||
export const postWorkflowDebug = (data: PostWorkflowDebugProps) =>
|
||||
POST<PostWorkflowDebugResponse>('/core/workflow/debug', {
|
||||
...data,
|
||||
mode: 'debug'
|
||||
});
|
@@ -1,47 +1,47 @@
|
||||
import { ModuleIOValueTypeEnum } from '@fastgpt/global/core/module/constants';
|
||||
import { WorkflowIOValueTypeEnum } from '@fastgpt/global/core/workflow/constants';
|
||||
|
||||
export const FlowValueTypeMap = {
|
||||
[ModuleIOValueTypeEnum.string]: {
|
||||
[WorkflowIOValueTypeEnum.string]: {
|
||||
handlerStyle: {
|
||||
borderColor: '#36ADEF'
|
||||
},
|
||||
label: 'core.module.valueType.string',
|
||||
value: ModuleIOValueTypeEnum.string,
|
||||
value: WorkflowIOValueTypeEnum.string,
|
||||
description: ''
|
||||
},
|
||||
[ModuleIOValueTypeEnum.number]: {
|
||||
[WorkflowIOValueTypeEnum.number]: {
|
||||
handlerStyle: {
|
||||
borderColor: '#FB7C3C'
|
||||
},
|
||||
label: 'core.module.valueType.number',
|
||||
value: ModuleIOValueTypeEnum.number,
|
||||
value: WorkflowIOValueTypeEnum.number,
|
||||
description: ''
|
||||
},
|
||||
[ModuleIOValueTypeEnum.boolean]: {
|
||||
[WorkflowIOValueTypeEnum.boolean]: {
|
||||
handlerStyle: {
|
||||
borderColor: '#E7D118'
|
||||
},
|
||||
label: 'core.module.valueType.boolean',
|
||||
value: ModuleIOValueTypeEnum.boolean,
|
||||
value: WorkflowIOValueTypeEnum.boolean,
|
||||
description: ''
|
||||
},
|
||||
[ModuleIOValueTypeEnum.chatHistory]: {
|
||||
[WorkflowIOValueTypeEnum.chatHistory]: {
|
||||
handlerStyle: {
|
||||
borderColor: '#00A9A6'
|
||||
},
|
||||
label: 'core.module.valueType.chatHistory',
|
||||
value: ModuleIOValueTypeEnum.chatHistory,
|
||||
value: WorkflowIOValueTypeEnum.chatHistory,
|
||||
description: `{
|
||||
obj: System | Human | AI;
|
||||
value: string;
|
||||
}[]`
|
||||
},
|
||||
[ModuleIOValueTypeEnum.datasetQuote]: {
|
||||
[WorkflowIOValueTypeEnum.datasetQuote]: {
|
||||
handlerStyle: {
|
||||
borderColor: '#A558C9'
|
||||
},
|
||||
label: 'core.module.valueType.datasetQuote',
|
||||
value: ModuleIOValueTypeEnum.datasetQuote,
|
||||
value: WorkflowIOValueTypeEnum.datasetQuote,
|
||||
description: `{
|
||||
id: string;
|
||||
datasetId: string;
|
||||
@@ -52,36 +52,44 @@ export const FlowValueTypeMap = {
|
||||
a: string
|
||||
}[]`
|
||||
},
|
||||
[ModuleIOValueTypeEnum.any]: {
|
||||
[WorkflowIOValueTypeEnum.any]: {
|
||||
handlerStyle: {
|
||||
borderColor: '#9CA2A8'
|
||||
},
|
||||
label: 'core.module.valueType.any',
|
||||
value: ModuleIOValueTypeEnum.any,
|
||||
value: WorkflowIOValueTypeEnum.any,
|
||||
description: ''
|
||||
},
|
||||
[ModuleIOValueTypeEnum.selectApp]: {
|
||||
[WorkflowIOValueTypeEnum.selectApp]: {
|
||||
handlerStyle: {
|
||||
borderColor: '#6a6efa'
|
||||
},
|
||||
label: 'core.module.valueType.selectApp',
|
||||
value: ModuleIOValueTypeEnum.selectApp,
|
||||
value: WorkflowIOValueTypeEnum.selectApp,
|
||||
description: ''
|
||||
},
|
||||
[ModuleIOValueTypeEnum.selectDataset]: {
|
||||
[WorkflowIOValueTypeEnum.selectDataset]: {
|
||||
handlerStyle: {
|
||||
borderColor: '#21ba45'
|
||||
},
|
||||
label: 'core.module.valueType.selectDataset',
|
||||
value: ModuleIOValueTypeEnum.selectDataset,
|
||||
value: WorkflowIOValueTypeEnum.selectDataset,
|
||||
description: ''
|
||||
},
|
||||
[ModuleIOValueTypeEnum.tools]: {
|
||||
[WorkflowIOValueTypeEnum.tools]: {
|
||||
handlerStyle: {
|
||||
borderColor: '#21ba45'
|
||||
},
|
||||
label: 'core.module.valueType.tools',
|
||||
value: ModuleIOValueTypeEnum.tools,
|
||||
value: WorkflowIOValueTypeEnum.tools,
|
||||
description: ''
|
||||
},
|
||||
[WorkflowIOValueTypeEnum.dynamic]: {
|
||||
handlerStyle: {
|
||||
borderColor: '#9CA2A8'
|
||||
},
|
||||
label: '动态数据',
|
||||
value: WorkflowIOValueTypeEnum.any,
|
||||
description: ''
|
||||
}
|
||||
};
|
||||
|
1
projects/app/src/web/core/workflow/constants/index.ts
Normal file
1
projects/app/src/web/core/workflow/constants/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export const VARIABLE_NODE_ID = 'VARIABLE_NODE_ID';
|
@@ -1,12 +1,10 @@
|
||||
import { create } from 'zustand';
|
||||
import { devtools, persist } from 'zustand/middleware';
|
||||
import { immer } from 'zustand/middleware/immer';
|
||||
import { FlowNodeTemplateType } from '@fastgpt/global/core/module/type';
|
||||
import { FlowNodeTemplateType } from '@fastgpt/global/core/workflow/type/index.d';
|
||||
import { getTeamPlugTemplates, getSystemPlugTemplates } from '../../plugin/api';
|
||||
|
||||
type State = {
|
||||
basicNodeTemplates: FlowNodeTemplateType[];
|
||||
setBasicNodeTemplates: (basicNodeTemplates: FlowNodeTemplateType[]) => void;
|
||||
systemNodeTemplates: FlowNodeTemplateType[];
|
||||
loadSystemNodeTemplates: (init?: boolean) => Promise<FlowNodeTemplateType[]>;
|
||||
teamPluginNodeTemplates: FlowNodeTemplateType[];
|
||||
@@ -21,12 +19,6 @@ export const useWorkflowStore = create<State>()(
|
||||
devtools(
|
||||
persist(
|
||||
immer((set, get) => ({
|
||||
basicNodeTemplates: [],
|
||||
setBasicNodeTemplates: (basicNodeTemplates) => {
|
||||
set((state) => {
|
||||
state.basicNodeTemplates = basicNodeTemplates;
|
||||
});
|
||||
},
|
||||
systemNodeTemplates: [],
|
||||
async loadSystemNodeTemplates(init) {
|
||||
if (!init && get().systemNodeTemplates.length > 0) {
|
||||
|
234
projects/app/src/web/core/workflow/utils.ts
Normal file
234
projects/app/src/web/core/workflow/utils.ts
Normal file
@@ -0,0 +1,234 @@
|
||||
import type {
|
||||
StoreNodeItemType,
|
||||
FlowNodeItemType,
|
||||
FlowNodeTemplateType
|
||||
} from '@fastgpt/global/core/workflow/type/index.d';
|
||||
import type { Edge, Node, XYPosition } from 'reactflow';
|
||||
import { moduleTemplatesFlat } from '@fastgpt/global/core/workflow/template/constants';
|
||||
import {
|
||||
EDGE_TYPE,
|
||||
FlowNodeInputTypeEnum,
|
||||
FlowNodeTypeEnum
|
||||
} from '@fastgpt/global/core/workflow/node/constant';
|
||||
import { EmptyNode } from '@fastgpt/global/core/workflow/template/system/emptyNode';
|
||||
import { StoreEdgeItemType } from '@fastgpt/global/core/workflow/type/edge';
|
||||
import { getNanoid } from '@fastgpt/global/common/string/tools';
|
||||
import { systemConfigNode2VariableNode } from './adapt';
|
||||
import { VARIABLE_NODE_ID } from './constants';
|
||||
import { NodeInputKeyEnum, NodeOutputKeyEnum } from '@fastgpt/global/core/workflow/constants';
|
||||
|
||||
export const nodeTemplate2FlowNode = ({
|
||||
template,
|
||||
position,
|
||||
selected
|
||||
}: {
|
||||
template: FlowNodeTemplateType;
|
||||
position: XYPosition;
|
||||
selected?: boolean;
|
||||
}): Node<FlowNodeItemType> => {
|
||||
// replace item data
|
||||
const moduleItem: FlowNodeItemType = {
|
||||
...template,
|
||||
nodeId: getNanoid()
|
||||
};
|
||||
|
||||
return {
|
||||
id: moduleItem.nodeId,
|
||||
type: moduleItem.flowNodeType,
|
||||
data: moduleItem,
|
||||
position: position,
|
||||
selected
|
||||
};
|
||||
};
|
||||
export const storeNode2FlowNode = ({
|
||||
item: storeNode
|
||||
}: {
|
||||
item: StoreNodeItemType;
|
||||
}): Node<FlowNodeItemType> => {
|
||||
// init some static data
|
||||
const template =
|
||||
moduleTemplatesFlat.find((template) => template.flowNodeType === storeNode.flowNodeType) ||
|
||||
EmptyNode;
|
||||
|
||||
// replace item data
|
||||
const moduleItem: FlowNodeItemType = {
|
||||
...template,
|
||||
...storeNode,
|
||||
avatar: storeNode?.avatar || template?.avatar,
|
||||
inputs: storeNode.inputs
|
||||
.map((storeInput) => {
|
||||
const templateInput =
|
||||
template.inputs.find((item) => item.key === storeInput.key) || storeInput;
|
||||
return {
|
||||
...templateInput,
|
||||
...storeInput,
|
||||
renderTypeList: templateInput.renderTypeList
|
||||
};
|
||||
})
|
||||
.concat(
|
||||
template.inputs.filter((item) => !storeNode.inputs.some((input) => input.key === item.key))
|
||||
),
|
||||
outputs: storeNode.outputs.map((storeOutput) => {
|
||||
const templateOutput =
|
||||
template.outputs.find((item) => item.key === storeOutput.key) || storeOutput;
|
||||
return {
|
||||
...storeOutput,
|
||||
...templateOutput,
|
||||
value: storeOutput.value
|
||||
};
|
||||
})
|
||||
};
|
||||
|
||||
return {
|
||||
id: storeNode.nodeId,
|
||||
type: storeNode.flowNodeType,
|
||||
data: moduleItem,
|
||||
position: storeNode.position || { x: 0, y: 0 }
|
||||
};
|
||||
};
|
||||
export const storeEdgesRenderEdge = ({ edge }: { edge: StoreEdgeItemType }) => {
|
||||
return {
|
||||
...edge,
|
||||
id: getNanoid(),
|
||||
type: EDGE_TYPE
|
||||
};
|
||||
};
|
||||
|
||||
export const computedNodeInputReference = ({
|
||||
nodeId,
|
||||
nodes,
|
||||
edges
|
||||
}: {
|
||||
nodeId: string;
|
||||
nodes: FlowNodeItemType[];
|
||||
edges: Edge[];
|
||||
}) => {
|
||||
// get current node
|
||||
const node = nodes.find((item) => item.nodeId === nodeId);
|
||||
if (!node) {
|
||||
return;
|
||||
}
|
||||
let sourceNodes: FlowNodeItemType[] = [];
|
||||
// 根据 edge 获取所有的 source 节点(source节点会继续向前递归获取)
|
||||
const findSourceNode = (nodeId: string) => {
|
||||
const targetEdges = edges.filter((item) => item.target === nodeId);
|
||||
targetEdges.forEach((edge) => {
|
||||
const sourceNode = nodes.find((item) => item.nodeId === edge.source);
|
||||
if (!sourceNode) return;
|
||||
|
||||
// 去重
|
||||
if (sourceNodes.some((item) => item.nodeId === sourceNode.nodeId)) {
|
||||
return;
|
||||
}
|
||||
sourceNodes.push(sourceNode);
|
||||
findSourceNode(sourceNode.nodeId);
|
||||
});
|
||||
};
|
||||
findSourceNode(nodeId);
|
||||
|
||||
// add system config node
|
||||
const systemConfigNode = nodes.find(
|
||||
(item) => item.flowNodeType === FlowNodeTypeEnum.systemConfig
|
||||
);
|
||||
|
||||
if (systemConfigNode) {
|
||||
sourceNodes.unshift(systemConfigNode2VariableNode(systemConfigNode));
|
||||
}
|
||||
|
||||
return sourceNodes;
|
||||
};
|
||||
|
||||
/* Connection rules */
|
||||
export const checkWorkflowNodeAndConnection = ({
|
||||
nodes,
|
||||
edges
|
||||
}: {
|
||||
nodes: Node<FlowNodeItemType, string | undefined>[];
|
||||
edges: Edge<any>[];
|
||||
}): string[] | undefined => {
|
||||
// 1. reference check. Required value
|
||||
for (const node of nodes) {
|
||||
const data = node.data;
|
||||
const inputs = data.inputs;
|
||||
const isToolNode = edges.some(
|
||||
(edge) =>
|
||||
edge.targetHandle === NodeOutputKeyEnum.selectedTools && edge.target === node.data.nodeId
|
||||
);
|
||||
|
||||
if (
|
||||
data.flowNodeType === FlowNodeTypeEnum.systemConfig ||
|
||||
data.flowNodeType === FlowNodeTypeEnum.pluginInput ||
|
||||
data.flowNodeType === FlowNodeTypeEnum.pluginOutput ||
|
||||
data.flowNodeType === FlowNodeTypeEnum.workflowStart
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// check node input
|
||||
if (
|
||||
inputs.some((input) => {
|
||||
// check is tool input
|
||||
if (isToolNode && input.toolDescription) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (input.required) {
|
||||
if (Array.isArray(input.value) && input.value.length === 0) return true;
|
||||
if (input.value === undefined) return true;
|
||||
}
|
||||
|
||||
// check reference invalid
|
||||
const renderType = input.renderTypeList[input.selectedTypeIndex || 0];
|
||||
if (renderType === FlowNodeInputTypeEnum.reference && input.required) {
|
||||
if (!input.value || !Array.isArray(input.value) || input.value.length !== 2) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// variable key not need to check
|
||||
if (input.value[0] === VARIABLE_NODE_ID) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Can not find key
|
||||
const sourceNode = nodes.find((item) => item.data.nodeId === input.value[0]);
|
||||
if (!sourceNode) {
|
||||
return true;
|
||||
}
|
||||
const sourceOutput = sourceNode.data.outputs.find((item) => item.id === input.value[1]);
|
||||
if (!sourceOutput) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
})
|
||||
) {
|
||||
return [data.nodeId];
|
||||
}
|
||||
|
||||
// check empty node(not edge)
|
||||
const hasEdge = edges.some(
|
||||
(edge) => edge.source === data.nodeId || edge.target === data.nodeId
|
||||
);
|
||||
if (!hasEdge) {
|
||||
return [data.nodeId];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const filterSensitiveNodesData = (nodes: StoreNodeItemType[]) => {
|
||||
const cloneNodes = JSON.parse(JSON.stringify(nodes)) as StoreNodeItemType[];
|
||||
|
||||
cloneNodes.forEach((node) => {
|
||||
// selected dataset
|
||||
if (node.flowNodeType === FlowNodeTypeEnum.datasetSearchNode) {
|
||||
node.inputs.forEach((input) => {
|
||||
if (input.key === NodeInputKeyEnum.datasetSelectList) {
|
||||
input.value = [];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return node;
|
||||
});
|
||||
return cloneNodes;
|
||||
};
|
Reference in New Issue
Block a user