Files
MemoryBear/web/src/views/Workflow/constant.ts
2026-04-03 20:45:37 +08:00

1016 lines
24 KiB
TypeScript

/*
* @Author: ZhaoYing
* @Date: 2026-02-03 15:06:18
* @Last Modified by: ZhaoYing
* @Last Modified time: 2026-04-03 20:28:08
*/
import LoopNode from './components/Nodes/LoopNode';
import NormalNode from './components/Nodes/NormalNode';
import ConditionNode from './components/Nodes/ConditionNode';
import GroupStartNode from './components/Nodes/GroupStartNode';
import AddNode from './components/Nodes/AddNode'
import NoteNode from './components/Nodes/NoteNode';
import type { PortMetadata, GroupMetadata } from '@antv/x6/lib/model/port';
import type { ReactShapeConfig } from '@antv/x6-react-shape';
import { memoryConfigListUrl } from '@/api/memory'
import type { NodeLibrary } from './types'
/**
* Workflow node library configuration
* Defines all available node types, their icons, and configuration schemas
*/
export const nodeLibrary: NodeLibrary[] = [
{
category: "coreNode",
nodes: [
{ type: "start", icon: 'rb:bg-[url("@/assets/images/workflow/start.svg")]',
config: {
variables: {
type: 'define',
sys: [
{
name: "message",
type: "string",
readonly: true
},
{
name: "conversation_id",
type: "string",
readonly: true
},
{
name: "execution_id",
type: "string",
readonly: true
},
{
name: "workspace_id",
type: "string",
readonly: true
},
{
name: "user_id",
type: "string",
readonly: true
},
{
name: "files",
type: "array[file]",
readonly: true
},
],
defaultValue: []
}
}
},
{
type: "end", icon: 'rb:bg-[url("@/assets/images/workflow/end.svg")]',
config: {
output: {
type: 'editor'
}
}
},
// { type: "answer", icon: answerIcon },
]
},
{
category: "aiAndCognitiveProcessing",
nodes: [
{ type: "llm", icon: 'rb:bg-[url("@/assets/images/workflow/llm.svg")]',
config: {
model_id: {
type: 'define',
params: { type: 'llm,chat' }, // llm/chat
valueKey: 'id',
labelKey: 'name',
},
temperature: {
type: 'define',
max: 2,
min: 0,
step: 0.1,
defaultValue: 0.7
},
max_tokens: {
type: 'define',
max: 32000,
min: 256,
step: 1,
defaultValue: 2000
},
context: {
type: 'variableList',
placeholder: 'workflow.config.llm.contextPlaceholder'
},
messages: {
type: 'define',
defaultValue: [
{
role: 'SYSTEM',
content: undefined,
readonly: true
},
],
placeholder: 'workflow.config.llm.messagesPlaceholder'
},
memory: {
type: 'memoryConfig',
defaultValue: {
enable: false,
enable_window: false,
window_size: 20
}
},
vision: {
type: 'switch'
},
vision_input: {
type: 'variableList',
onFilterVariableNames: ['sys.files']
}
}
},
{ type: "knowledge-retrieval", icon: 'rb:bg-[url("@/assets/images/workflow/rag.svg")]',
config: {
query: {
type: 'variableList',
},
knowledge_retrieval: {
type: 'knowledge'
}
}
},
{ type: "parameter-extractor", icon: 'rb:bg-[url("@/assets/images/workflow/parameter_extraction.svg")]',
config: {
model_id: {
type: 'modelSelect',
params: { type: 'llm,chat' }, // llm/chat
},
text: {
type: 'variableList',
filterLoopIterationVars: true,
placeholder: 'workflow.config.parameter-extractor.textPlaceholder'
},
params: {
type: 'paramList',
},
prompt: {
type: 'messageEditor',
isArray: false,
titleVariant: 'borderless',
placeholder: 'workflow.config.parameter-extractor.promptPlaceholder'
},
}
}
]
},
{
category: "cognitiveUpgrading",
nodes: [
{ type: "memory-read", icon: 'rb:bg-[url("@/assets/images/workflow/memory-read.svg")]',
config: {
message: {
type: 'editor',
isArray: false
},
config_id: {
type: 'customSelect',
url: memoryConfigListUrl,
valueKey: 'config_id',
labelKey: 'config_name'
},
search_switch: {
type: 'select',
options: [
{ value: '0', label: 'memoryConversation.deepThinking' },
{ value: '1', label: 'memoryConversation.normalReply' },
{ value: '2', label: 'memoryConversation.quickReply' },
],
needTranslation: true
}
}
},
{ type: "memory-write", icon: 'rb:bg-[url("@/assets/images/workflow/memory-write.svg")]',
config: {
message: {
type: 'editor',
isArray: false,
hidden: true,
},
messages: {
type: 'messageEditor',
defaultValue: [],
placeholder: 'workflow.config.llm.messagesPlaceholder',
isArray: true
},
config_id: {
type: 'customSelect',
url: memoryConfigListUrl,
valueKey: 'config_id',
labelKey: 'config_name'
}
}
},
]
},
{
category: "flowControl",
nodes: [
{ type: "if-else", icon: 'rb:bg-[url("@/assets/images/workflow/condition.svg")]',
config: {
cases: {
type: 'caseList',
defaultValue: [
{
logical_operator: 'and',
expressions: []
}
]
}
}
},
{ type: "question-classifier", icon: 'rb:bg-[url("@/assets/images/workflow/question-classifier.svg")]',
config: {
model_id: {
type: 'modelSelect',
params: { type: 'llm,chat' }, // llm/chat
},
input_variable: {
type: 'variableList',
},
categories: {
type: 'categoryList',
defaultValue: [
{},
{}
]
},
user_supplement_prompt: {
type: 'messageEditor',
isArray: false,
titleVariant: 'borderless',
placeholder: 'common.pleaseEnter'
}
}
},
{ type: "iteration", icon: 'rb:bg-[url("@/assets/images/workflow/iteration.svg")]',
config: {
input: {
type: 'variableList',
filterNodeTypes: ['knowledge-retrieval', 'iteration', 'loop', 'parameter-extractor', 'code', 'CONVERSATION'],
filterVariableNames: ['message']
},
parallel: {
type: 'switch',
defaultValue: false
},
parallel_count: {
type: 'slider',
min: 1,
max: 10,
step: 1,
defaultValue: 10,
dependsOn: 'parallel',
dependsOnValue: true
},
flatten: { // Flatten output
type: 'switch',
defaultValue: false
},
output: {
type: 'variableList',
filterChildNodes: true
},
output_type: {
type: 'define',
}
},
},
{ type: "loop", icon: 'rb:bg-[url("@/assets/images/workflow/loop.svg")]',
config: {
cycle_vars: {
type: 'cycleVarsList',
defaultValue: []
},
condition: {
type: 'conditionList',
showLabel: true,
defaultValue: {
logical_operator: 'and',
expressions: []
}
},
max_loop: {
type: 'slider',
min: 1,
max: 100,
step: 1,
defaultValue: 10
},
}
},
{ type: "cycle-start", icon: 'rb:bg-[url("@/assets/images/workflow/start.svg")]'},
{ type: "break", icon: 'rb:bg-[url("@/assets/images/workflow/break.svg")]'},
{ type: "var-aggregator", icon: 'rb:bg-[url("@/assets/images/workflow/aggregator.svg")]',
config: {
group: {
type: 'switch',
defaultValue: false
},
group_variables: {
type: 'groupVariableList',
defaultValue: [],
},
group_type: {
type: 'define',
}
}
},
{ type: "assigner", icon: 'rb:bg-[url("@/assets/images/workflow/assigner.svg")]',
config: {
assignments: {
type: 'assignmentList',
filterLoopIterationVars: true
}
}
},
]
},
{
category: "externalInteraction",
nodes: [
{ type: "http-request", icon: 'rb:bg-[url("@/assets/images/workflow/http_request.svg")]',
config: {
method: {
type: 'select',
options: [
{ label: 'GET', value: 'GET' },
{ label: 'POST', value: 'POST' },
{ label: 'HEAD', value: 'HEAD' },
{ label: 'PATCH', value: 'PATCH' },
{ label: 'PUT', value: 'PUT' },
{ label: 'DELETE', value: 'DELETE' },
],
defaultValue: 'GET'
},
url: {
type: 'messageEditor',
isArray: false,
},
auth: {
type: 'define',
defaultValue: {
auth_type: 'none'
}
},
headers: {
type: 'define',
defaultValue: []
},
params: {
type: 'define',
defaultValue: []
},
body: {
type: 'define',
defaultValue: {
'content_type': 'none'
}
},
verify_ssl: {
type: 'switch',
defaultValue: false
},
timeouts: {
type: 'define',
defaultValue: {}
},
retry: {
type: 'switch',
defaultValue: {
enable: false
}
},
error_handle: {
type: 'define',
defaultValue: {
method: 'none'
}
}
}
},
{ type: "tool", icon: 'rb:bg-[url("@/assets/images/workflow/tools.svg")]',
config: {
tool_id: {
type: 'cascader'
},
tool_parameters: {
type: 'define'
}
}
},
{ type: "code", icon: 'rb:bg-[url("@/assets/images/workflow/code_execution.svg")]',
config: {
input_variables: {
type: 'inputList',
defaultValue: [{ name: 'arg1' }, { name: 'arg2' }]
},
language: {
type: 'select',
defaultValue: 'python3'
},
code: {
type: 'messageEditor',
isArray: false,
language: ['python3', 'javascript'],
titleVariant: 'borderless',
defaultValue: `def main(arg1: str, arg2: str):
return {
"result": arg1 + arg2,
}`
},
output_variables: {
type: 'outputList',
defaultValue: [{name: 'result', type: 'string'}]
},
}
},
{ type: "jinja-render", icon: 'rb:bg-[url("@/assets/images/workflow/template_rendering.svg")]',
config: {
mapping: {
type: 'mappingList',
defaultValue: [{name: 'arg1'}]
},
template: {
type: 'messageEditor',
isArray: false,
language: 'jinja2',
titleVariant: 'borderless',
defaultValue: "{{arg1}}"
},
}
},
{ type: "document-extractor", icon: 'rb:bg-[url("@/assets/images/workflow/document-extractor.svg")]',
config: {
file_selector: {
type: 'variableList',
placeholder: 'common.pleaseSelect',
onFilterVariableNames: ['sys.files']
}
}
},
{ type: "list-operator", icon: 'rb:bg-[url("@/assets/images/workflow/list-operator.svg")]',
config: {
input_list: {
type: 'variableList',
},
filter_by: {
type: 'define',
defaultValue: {
enabled: false,
conditions: [{}]
}
},
order_by: {
type: 'define',
defaultValue: {
"enabled": false,
"key": "",
"value": "asc"
}
},
limit: {
type: 'define',
defaultValue: {
"enabled": false,
"size": 1
}
},
extract_by: {
type: 'define',
defaultValue: {
"enabled": false,
"serial": ""
}
},
}
},
]
},
];
export const THEME_MAP: Record<string, { outer: string; title: string; bg: string; border: string }> = {
blue: {
outer: '#2E90FA',
title: '#D1E9FF',
bg: '#EFF8FF',
border: '#84CAFF',
},
cyan: {
outer: '#06AED4',
title: '#CFF9FE',
bg: '#ECFDFF',
border: '#67E3F9',
},
green: {
outer: '#16B364',
title: '#D3F8DF',
bg: '#EDFCF2',
border: '#73E2A3',
},
yellow: {
outer: '#EAAA08',
title: '#FEF7C3',
bg: '#FEFBE8',
border: '#FDE272',
},
pink: {
outer: '#EE46BC',
title: '#FCE7F6',
bg: '#FDF2FA',
border: '#FAA7E0',
},
violet: {
outer: '#875BF7',
title: '#ECE9FE',
bg: '#F5F3FF',
border: '#C3B5FD',
},
}
export const notesConfig = {
type: "notes",
icon: 'rb:bg-[url("@/assets/images/workflow/unknown.svg")]',
config: {
text: {
type: 'define',
},
theme: {
type: 'define',
defaultValue: 'blue',
},
width: {
type: 'define',
width: 240,
},
height: {
type: 'define',
height: 120,
},
author: {
type: 'define',
},
show_author: {
type: 'define',
defaultValue: true
}
}
}
export const unknownNode = {
type: 'unknown',
icon: 'rb:bg-[url("@/assets/images/workflow/unknown.svg")]'
}
export const noteNode = {
type: 'notes',
icon: 'rb:bg-[url("@/assets/images/workflow/unknown.svg")]'
}
export const nodeWidth = 240;
export const conditionNodePortItemArgsY = 60;
export const conditionNodeItemHeight = 26;
export const conditionNodeHeight = 110;
/**
* Node registration library for X6 graph
* Maps node shapes to their React components
*/
export const nodeRegisterLibrary: ReactShapeConfig[] = [
{
shape: 'loop-node',
width: nodeWidth,
height: 120,
component: LoopNode,
},
{
shape: 'iteration-node',
width: nodeWidth,
height: 120,
component: LoopNode,
},
{
shape: 'normal-node',
width: 120,
height: 40,
component: NormalNode,
},
{
shape: 'condition-node',
width: nodeWidth,
height: conditionNodeHeight,
component: ConditionNode,
},
{
shape: 'cycle-start',
width: 36,
height: 36,
component: GroupStartNode,
},
{
shape: 'add-node',
width: 100,
height: 28,
component: AddNode,
},
{
shape: 'notes-node',
width: nodeWidth,
height: 120,
component: NoteNode,
},
];
/**
* Port configuration interface
*/
interface PortsConfig {
/** Port group metadata */
groups?: GroupMetadata;
/** Port item metadata array */
items?: PortMetadata[];
}
/**
* Node configuration interface
*/
interface NodeConfig {
/** Node width in pixels */
width: number;
/** Node height in pixels */
height: number;
/** Node shape type */
shape: string;
/** Port configuration */
ports?: PortsConfig;
}
/** Edge color for normal state */
export const edge_color = '#D4D5D9';
/** Edge color for selected state */
export const edge_selected_color = '#171719'
export const edge_width = 2;
/** Port color */
export const port_color = '#171719'
/**
* Unified port markup configuration
* Defines SVG elements for port rendering
*/
export const portMarkup = [
{
tagName: 'circle',
selector: 'body',
},
{
tagName: 'text',
selector: 'label',
},
];
/**
* Unified port attributes configuration
* Defines visual styling for ports
*/
export const portAttrs = {
body: {
r: 6,
magnet: true,
stroke: port_color,
strokeWidth: edge_width,
fill: port_color,
},
label: {
text: '+',
fontSize: 12,
fontWeight: 'bold',
fill: '#FFFFFF',
textAnchor: 'middle',
textVerticalAnchor: 'middle',
pointerEvents: 'none',
},
}
export const portTextAttrs = { fontSize: 12, fill: '#5B6167' }
/**
* Port position arguments
*/
export const portItemArgsY = 26;
export const portArgs = { x: nodeWidth, y: portItemArgsY }
const defaultPortGroup = {
position: { name: 'absolute' },
markup: [
{ tagName: 'rect', selector: 'body' },
{ tagName: 'circle', selector: 'hoverBody' },
{ tagName: 'text', selector: 'label' },
],
attrs: {
body: {
width: 1,
height: 8,
x: 0.75,
magnet: true,
stroke: port_color,
strokeWidth: edge_width,
fill: port_color,
},
hoverBody: {
r: 6,
cy: 2,
magnet: true,
stroke: port_color,
strokeWidth: edge_width,
fill: port_color,
opacity: 0,
},
label: {
text: '+',
fontSize: 12,
fontWeight: 'bold',
fill: '#FFFFFF',
textAnchor: 'middle',
textVerticalAnchor: 'middle',
pointerEvents: 'none',
y: '0.15em',
opacity: 0,
},
},
}
const leftPortGroup = {
position: { name: 'absolute' },
markup: [{ tagName: 'rect', selector: 'body' }],
attrs: {
body: {
width: 1,
height: 8,
x: -1.75,
y: -4,
magnet: true,
stroke: port_color,
strokeWidth: edge_width,
fill: port_color,
},
},
}
/**
* Unified port group configuration
* Defines port positions and attributes for different sides
*/
export const defaultAbsolutePortGroups = {
right: defaultPortGroup,
left: leftPortGroup,
}
/**
* Default port items for standard nodes
*/
export const defaultPortItems = [
{ group: 'left', args: { x: 0, y: portItemArgsY }, },
{ group: 'right', args: { x: nodeWidth, y: portItemArgsY }, },
];
/**
* Graph node library configuration
* Maps node types to their visual and structural properties
*/
export const graphNodeLibrary: Record<string, NodeConfig> = {
iteration: {
width: nodeWidth,
height: 140,
shape: 'iteration-node',
ports: {
groups: defaultAbsolutePortGroups,
items: defaultPortItems,
},
},
loop: {
width: nodeWidth,
height: 140,
shape: 'loop-node',
ports: {
groups: defaultAbsolutePortGroups,
items: defaultPortItems,
},
},
'if-else': {
width: nodeWidth,
height: conditionNodeHeight,
shape: 'condition-node',
ports: {
groups: defaultAbsolutePortGroups,
items: [
defaultPortItems[0],
...(['IF', 'ELSE'].map((_, index) => ({
group: 'right',
id: `CASE${index + 1}`,
args: {
...portArgs,
y: portItemArgsY * index + conditionNodePortItemArgsY,
},
}))),
],
},
},
'question-classifier': {
width: nodeWidth,
height: conditionNodeHeight,
shape: 'condition-node',
ports: {
groups: defaultAbsolutePortGroups,
items: [
defaultPortItems[0],
...(['分类1', '分类2'].map((_text, index) => ({
group: 'right',
id: `CASE${index + 1}`,
args: {
...portArgs,
y: portItemArgsY * index + conditionNodePortItemArgsY,
},
}))),
],
},
},
start: {
width: nodeWidth,
height: 76,
shape: 'normal-node',
ports: {
groups: { right: defaultPortGroup},
items: [defaultPortItems[1]],
},
},
'cycle-start': {
width: 36,
height: 36,
shape: 'cycle-start',
ports: {
groups: { right: defaultPortGroup },
items: [{ group: 'right', args: { x: 36, y: 18 } }],
},
},
'add-node': {
width: 100,
height: 28,
shape: 'add-node',
ports: {
groups: { left: leftPortGroup },
items: [{ group: 'left', args: { x: 0, y: 18 }}],
},
},
default: {
width: nodeWidth,
height: 76,
shape: 'normal-node',
ports: {
groups: defaultAbsolutePortGroups,
items: defaultPortItems,
},
},
cycleStart: {
width: 36,
height: 36,
shape: 'cycle-start',
ports: {
groups: { right: defaultPortGroup },
items: [{ group: 'right', args: { x: 36, y: 18 }}],
},
},
addStart: {
width: 100,
height: 28,
shape: 'add-node',
ports: {
groups: { left: leftPortGroup },
items: [{ group: 'left', args: { x: 0, y: 14 } }],
},
},
break: {
width: nodeWidth,
height: 76,
shape: 'normal-node',
ports: {
groups: { left: leftPortGroup },
items: [defaultPortItems[0]],
},
},
notes: {
width: nodeWidth,
height: 120,
shape: 'notes-node',
}
}
/**
* Output variable configuration interface
*/
export interface OutputVariable {
/** Default output variables */
default?: Array<{
name: string;
type: string;
}>;
/** Dynamically defined variable keys */
define?: string[];
/** System-level output variables */
sys?: Array<{
name: string;
type: string;
}>;
/** Error-related output variables */
error?: Array<{
name: string;
type: string;
}>;
}
/**
* Default edge attributes configuration
* Defines visual styling for edges/connections
*/
export const edgeAttrs = {
attrs: {
line: {
stroke: edge_color,
strokeWidth: edge_width,
targetMarker: null,
sourceMarker: null,
},
},
}
/**
* Edge hover tool: circular "+" button shown at midpoint on hover
*/
export const edgeHoverTool = {
name: 'button',
args: {
markup: [
{
tagName: 'circle',
selector: 'button',
attrs: {
r: 6,
stroke: port_color,
strokeWidth: edge_width,
fill: port_color,
cursor: 'pointer',
},
},
{
tagName: 'text',
textContent: '+',
selector: 'icon',
attrs: {
fontSize: 12,
fontWeight: 'bold',
fill: '#FFFFFF',
textAnchor: 'middle',
textVerticalAnchor: 'middle',
pointerEvents: 'none',
y: '0.3em',
},
},
],
distance: 0.5,
offset: { x: 0, y: 0 },
onClick({ e, cell: edge }: any) {
e.stopPropagation();
const graph = edge.model?.graph;
if (!graph) return;
const sourceCell = graph.getCellById(edge.getSourceCellId());
const targetCell = graph.getCellById(edge.getTargetCellId());
const sourcePort = edge.getSourcePortId();
const targetPort = edge.getTargetPortId();
if (!sourceCell || !targetCell) return;
const rect = (e.target as HTMLElement).getBoundingClientRect();
const tempDiv = document.createElement('div');
tempDiv.style.position = 'fixed';
tempDiv.style.left = rect.left + 'px';
tempDiv.style.top = rect.top + 'px';
tempDiv.style.width = '1px';
tempDiv.style.height = '1px';
tempDiv.style.zIndex = '9999';
document.body.appendChild(tempDiv);
window.dispatchEvent(new CustomEvent('port:click', {
detail: {
node: sourceCell,
port: sourcePort,
element: tempDiv,
rect,
edgeInsertion: { edge, sourceCell, targetCell, sourcePort, targetPort }
}
}));
},
},
}