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:
Archer
2024-04-25 17:51:20 +08:00
committed by GitHub
parent b08d81f887
commit 439c819ff1
505 changed files with 23570 additions and 18215 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -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 || ''

View File

@@ -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 });

View 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
};
};

View 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'
});

View File

@@ -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: ''
}
};

View File

@@ -0,0 +1 @@
export const VARIABLE_NODE_ID = 'VARIABLE_NODE_ID';

View File

@@ -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) {

View 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;
};