This commit is contained in:
Typer_Body
2026-05-18 01:47:13 +08:00
parent 27c0d344bf
commit bb7db53447
89 changed files with 1202 additions and 6883 deletions

View File

@@ -79,19 +79,17 @@ export default function WorkflowDetailContent({ id }: { id: string }) {
return () => setDetailEntityName(null);
}, [id, isCreateMode, workflows, setDetailEntityName, t]);
// Load node types
// Load node types - always fetch from backend to ensure fresh metadata
useEffect(() => {
if (nodeTypes.length === 0) {
backendClient
.getWorkflowNodeTypes()
.then((resp) => {
setNodeTypes(resp.node_types, resp.categories);
})
.catch((err) => {
console.error('Failed to load node types:', err);
});
}
}, [nodeTypes.length, setNodeTypes]);
backendClient
.getWorkflowNodeTypes()
.then((resp) => {
setNodeTypes(resp.node_types, resp.categories);
})
.catch((err) => {
console.error('Failed to load node types:', err);
});
}, [setNodeTypes]);
// Load workflow data
useEffect(() => {

View File

@@ -14,8 +14,6 @@ import {
import { Input } from '@/components/ui/input';
import { Badge } from '@/components/ui/badge';
import {
NODE_ICONS,
NODE_TYPE_I18N_KEYS,
CATEGORY_I18N_KEYS,
PALETTE_CATEGORY_COLORS as categoryColors,
PALETTE_CATEGORY_BG as categoryBgColors,
@@ -26,9 +24,6 @@ import {
} from './workflow-constants';
import { resolveI18nLabel } from './workflow-i18n';
// Use shared icon mapping
const nodeIcons = NODE_ICONS;
// Use shared category i18n keys
const categoryI18nKeys = CATEGORY_I18N_KEYS;
@@ -44,16 +39,6 @@ interface NodeTypeForUI {
description?: Record<string, string>;
}
// Default node types generated from shared constants
const defaultNodeTypes: NodeTypeForUI[] = Object.entries(
NODE_TYPE_I18N_KEYS,
).map(([type, keys]) => ({
type,
category: type.split('.')[0],
labelKey: keys.labelKey,
descriptionKey: keys.descriptionKey,
}));
export default function NodePalette() {
const { t, i18n } = useTranslation();
const { nodeTypes: backendNodeTypes, nodeCategories } = useWorkflowStore();
@@ -96,24 +81,25 @@ export default function NodePalette() {
[t],
);
// Use backend node types if available, otherwise use defaults
// Backend node types are the only source of palette node definitions.
const nodeTypes = useMemo((): NodeTypeForUI[] => {
if (backendNodeTypes && backendNodeTypes.length > 0) {
return backendNodeTypes.map((node) => {
const i18nKeys = findNodeI18nKeys(node.type);
return {
type: node.type,
category: node.category,
labelKey: i18nKeys?.labelKey,
descriptionKey: i18nKeys?.descriptionKey,
// Keep raw label dict as fallback for unknown nodes
label: i18nKeys ? undefined : node.label,
description: i18nKeys ? undefined : node.description,
};
});
if (!backendNodeTypes || backendNodeTypes.length === 0) {
return [];
}
return defaultNodeTypes;
return backendNodeTypes.map((node) => {
const i18nKeys = findNodeI18nKeys(node.type);
return {
type: node.type,
category: node.category,
icon: node.icon,
labelKey: i18nKeys?.labelKey,
descriptionKey: i18nKeys?.descriptionKey,
label: i18nKeys ? undefined : node.label,
description: i18nKeys ? undefined : node.description,
};
});
}, [backendNodeTypes]);
// Filter nodes based on search query

View File

@@ -20,7 +20,6 @@ import {
} from 'lucide-react';
import DynamicFormComponent from '@/app/home/components/dynamic-form/DynamicFormComponent';
import { IDynamicFormItemSchema } from '@/app/infra/entities/form/dynamic';
import { getNodeConfig } from './node-configs';
import i18n from 'i18next';
import { I18nObject } from '@/app/infra/entities/common';
import { normalizeWorkflowNodeTypeMeta } from './workflow-node-metadata';
@@ -232,58 +231,38 @@ export default function PropertyPanel({
}, [edges, selectedEdgeId]);
// Get node type metadata for selected node
// Priority: API metadata first, local registry as normalized fallback
// Supports both full type (category.name) and short name matching
const nodeTypeMeta = useMemo(() => {
if (!selectedNode) return null;
const nodeType = selectedNode.data.type;
return normalizeWorkflowNodeTypeMeta(
nodeType,
nodeTypes.find((t) => t.type === nodeType),
);
const shortName = nodeType.includes('.') ? nodeType.split('.').pop()! : nodeType;
const matched = nodeTypes.find((t) => {
if (t.type === nodeType) return true;
const tShort = t.type.includes('.') ? t.type.split('.').pop()! : t.type;
return tShort === shortName;
});
return normalizeWorkflowNodeTypeMeta(nodeType, matched);
}, [selectedNode, nodeTypes]);
// Get local node config for additional metadata not carried by backend schema
const localNodeConfig = useMemo(() => {
if (!selectedNode) return null;
const nodeType = selectedNode.data.type;
return getNodeConfig(nodeType) || null;
}, [selectedNode]);
// Prefer local registry config schema so workflow editor can reuse the existing
// form item definitions, i18n labels/descriptions and option labels consistently.
// Fall back to backend metadata for nodes that do not exist in the local registry.
// Backend YAML is the single source of truth for node configuration schema.
const configSchema = useMemo(() => {
const localConfigSchema =
(localNodeConfig?.configSchema as IDynamicFormItemSchema[]) || [];
const backendConfigSchema =
(nodeTypeMeta?.config_schema as IDynamicFormItemSchema[]) || [];
const rawConfigSchema =
localConfigSchema.length > 0 ? localConfigSchema : backendConfigSchema;
return rawConfigSchema.map((item) => {
const backendItem = backendConfigSchema.find(
(candidate) => candidate.name === item.name || candidate.id === item.id,
);
return {
...(backendItem || {}),
...item,
label: item.label ||
backendItem?.label || {
en_US: item.name,
zh_Hans: item.name,
},
description: item.description ||
backendItem?.description || {
en_US: '',
zh_Hans: '',
},
options: item.options || backendItem?.options,
show_if: item.show_if || backendItem?.show_if,
};
});
}, [localNodeConfig?.configSchema, nodeTypeMeta?.config_schema]);
return backendConfigSchema.map((item) => ({
...item,
id: item.id || item.name,
label: item.label || {
en_US: item.name,
zh_Hans: item.name,
},
description: item.description || {
en_US: '',
zh_Hans: '',
},
}));
}, [nodeTypeMeta?.config_schema]);
// Get available input variables from connected upstream nodes
const availableInputVariables = useMemo(() => {
@@ -555,9 +534,6 @@ export default function PropertyPanel({
? extractI18nLabel(nodeTypeMeta.description)
: undefined;
// Get node category color from local config
const nodeColor = localNodeConfig?.color || nodeTypeMeta?.color;
return (
<TooltipProvider>
<div className="h-full w-full flex flex-col overflow-hidden">

View File

@@ -6,5 +6,3 @@ export {
export { default as NodePalette } from './NodePalette';
export { default as PropertyPanel } from './PropertyPanel';
// Export node configurations
export * from './node-configs';

View File

@@ -1,774 +0,0 @@
/**
* AI Node Configurations
*
* Defines configurations for all AI-related node types:
* - llm_call: Call a large language model
* - question_classifier: Classify user questions into categories
* - parameter_extractor: Extract structured parameters from text
* - knowledge_retrieval: Retrieve information from knowledge bases
* - text_embedding: Generate text embeddings
* - intent_recognition: Recognize user intent
*/
import { DynamicFormItemType } from '@/app/infra/entities/form/dynamic';
import { NodeConfigMeta, createInput, createOutput } from './types';
/**
* LLM Call Node
* Makes a call to a large language model
*/
export const llmCallConfig: NodeConfigMeta = {
nodeType: 'llm_call',
label: {
en_US: 'LLM Call',
zh_Hans: 'LLM 调用',
},
description: {
en_US: 'Call a large language model to generate responses',
zh_Hans: '调用大语言模型生成响应',
},
icon: 'Brain',
category: 'process',
color: '#8b5cf6',
inputs: [
createInput('input', 'string', {
description: 'Input text to send to the model',
label: { en_US: 'Input', zh_Hans: '输入' },
}),
createInput('context', 'object', {
description: 'Additional context data',
label: { en_US: 'Context', zh_Hans: '上下文' },
required: false,
}),
],
outputs: [
createOutput('response', 'string', {
description: 'Model response text',
label: { en_US: 'Response', zh_Hans: '响应' },
}),
createOutput('usage', 'object', {
description: 'Token usage information',
label: { en_US: 'Usage', zh_Hans: '使用量' },
}),
createOutput('parsed', 'object', {
description: 'Parsed output (if output format is JSON)',
label: { en_US: 'Parsed', zh_Hans: '解析结果' },
}),
],
configSchema: [
{
id: 'model',
name: 'model',
type: DynamicFormItemType.LLM_MODEL_SELECTOR,
label: {
en_US: 'Model',
zh_Hans: '模型',
},
description: {
en_US: 'Select the LLM model to use',
zh_Hans: '选择要使用的 LLM 模型',
},
required: true,
default: '',
},
{
id: 'system_prompt',
name: 'system_prompt',
type: DynamicFormItemType.TEXT,
label: {
en_US: 'System Prompt',
zh_Hans: '系统提示词',
},
description: {
en_US:
'System prompt to set the model behavior (supports variable interpolation with {{variable}})',
zh_Hans:
'设置模型行为的系统提示词(支持使用 {{variable}} 进行变量插值)',
},
required: false,
default: '',
},
{
id: 'user_prompt_template',
name: 'user_prompt_template',
type: DynamicFormItemType.TEXT,
label: {
en_US: 'User Prompt Template',
zh_Hans: '用户提示词模板',
},
description: {
en_US:
'User prompt template with variable placeholders (e.g., {{input}}, {{context.key}})',
zh_Hans:
'带有变量占位符的用户提示词模板(例如 {{input}}、{{context.key}}',
},
required: true,
default: '{{input}}',
},
{
id: 'temperature',
name: 'temperature',
type: DynamicFormItemType.FLOAT,
label: {
en_US: 'Temperature',
zh_Hans: '温度',
},
description: {
en_US:
'Controls randomness in responses (0.0 = deterministic, 2.0 = very random)',
zh_Hans: '控制响应的随机性0.0 = 确定性2.0 = 非常随机)',
},
required: false,
default: 0.7,
},
{
id: 'max_tokens',
name: 'max_tokens',
type: DynamicFormItemType.INT,
label: {
en_US: 'Max Tokens',
zh_Hans: '最大令牌数',
},
description: {
en_US:
'Maximum number of tokens to generate (leave 0 for model default)',
zh_Hans: '生成的最大令牌数(设为 0 使用模型默认值)',
},
required: false,
default: 0,
},
{
id: 'output_format',
name: 'output_format',
type: DynamicFormItemType.SELECT,
label: {
en_US: 'Output Format',
zh_Hans: '输出格式',
},
description: {
en_US: 'Expected format of the model output',
zh_Hans: '模型输出的预期格式',
},
required: false,
default: 'text',
options: [
{ name: 'text', label: { en_US: 'Plain Text', zh_Hans: '纯文本' } },
{ name: 'json', label: { en_US: 'JSON', zh_Hans: 'JSON' } },
{
name: 'markdown',
label: { en_US: 'Markdown', zh_Hans: 'Markdown 文本' },
},
],
},
{
id: 'json_schema',
name: 'json_schema',
type: DynamicFormItemType.TEXT,
label: {
en_US: 'JSON Schema',
zh_Hans: 'JSON Schema',
},
description: {
en_US: 'JSON schema for structured output validation (optional)',
zh_Hans: '用于结构化输出验证的 JSON Schema可选',
},
required: false,
default: '',
show_if: {
field: 'output_format',
operator: 'eq',
value: 'json',
},
},
{
id: 'enable_tools',
name: 'enable_tools',
type: DynamicFormItemType.BOOLEAN,
label: {
en_US: 'Enable Tools',
zh_Hans: '启用工具',
},
description: {
en_US: 'Allow the model to use function calling tools',
zh_Hans: '允许模型使用函数调用工具',
},
required: false,
default: false,
},
{
id: 'tools',
name: 'tools',
type: DynamicFormItemType.TOOLS_SELECTOR,
label: {
en_US: 'Tools',
zh_Hans: '工具',
},
description: {
en_US: 'Select tools that the model can use',
zh_Hans: '选择模型可以使用的工具',
},
required: false,
default: [],
show_if: {
field: 'enable_tools',
operator: 'eq',
value: true,
},
},
],
defaultConfig: {
model: '',
system_prompt: '',
user_prompt_template: '{{input}}',
temperature: 0.7,
max_tokens: 0,
output_format: 'text',
json_schema: '',
enable_tools: false,
tools: [],
},
};
/**
* Question Classifier Node
* Classifies user questions into predefined categories
*/
export const questionClassifierConfig: NodeConfigMeta = {
nodeType: 'question_classifier',
label: {
en_US: 'Question Classifier',
zh_Hans: '问题分类器',
},
description: {
en_US: 'Classify user questions into predefined categories using AI',
zh_Hans: '使用 AI 将用户问题分类到预定义的类别中',
},
icon: 'Tags',
category: 'process',
color: '#8b5cf6',
inputs: [
createInput('question', 'string', {
description: 'The question to classify',
label: { en_US: 'Question', zh_Hans: '问题' },
}),
],
outputs: [
createOutput('category', 'string', {
description: 'The classified category',
label: { en_US: 'Category', zh_Hans: '分类' },
}),
createOutput('confidence', 'number', {
description: 'Classification confidence score (0-1)',
label: { en_US: 'Confidence', zh_Hans: '置信度' },
}),
createOutput('all_scores', 'object', {
description: 'Scores for all categories',
label: { en_US: 'All Scores', zh_Hans: '所有分数' },
}),
],
configSchema: [
{
id: 'model',
name: 'model',
type: DynamicFormItemType.LLM_MODEL_SELECTOR,
label: {
en_US: 'Classification Model',
zh_Hans: '分类模型',
},
description: {
en_US: 'Select the model to use for classification',
zh_Hans: '选择用于分类的模型',
},
required: true,
default: '',
},
{
id: 'categories',
name: 'categories',
type: DynamicFormItemType.TEXT,
label: {
en_US: 'Categories Definition',
zh_Hans: '分类定义',
},
description: {
en_US:
'Define categories in JSON format: [{"name": "category1", "description": "...", "examples": ["..."]}]',
zh_Hans:
'使用 JSON 格式定义分类: [{"name": "分类1", "description": "...", "examples": ["..."]}]',
},
required: true,
default: '[]',
},
{
id: 'confidence_threshold',
name: 'confidence_threshold',
type: DynamicFormItemType.FLOAT,
label: {
en_US: 'Confidence Threshold',
zh_Hans: '置信度阈值',
},
description: {
en_US: 'Minimum confidence score required (0.0-1.0)',
zh_Hans: '所需的最小置信度分数0.0-1.0',
},
required: false,
default: 0.7,
},
{
id: 'fallback_category',
name: 'fallback_category',
type: DynamicFormItemType.STRING,
label: {
en_US: 'Fallback Category',
zh_Hans: '默认分类',
},
description: {
en_US: 'Category to use when confidence is below threshold',
zh_Hans: '当置信度低于阈值时使用的分类',
},
required: false,
default: 'other',
},
],
defaultConfig: {
model: '',
categories: '[]',
confidence_threshold: 0.7,
fallback_category: 'other',
},
};
/**
* Parameter Extractor Node
* Extracts structured parameters from natural language
*/
export const parameterExtractorConfig: NodeConfigMeta = {
nodeType: 'parameter_extractor',
label: {
en_US: 'Parameter Extractor',
zh_Hans: '参数提取器',
},
description: {
en_US: 'Extract structured parameters from natural language text using AI',
zh_Hans: '使用 AI 从自然语言文本中提取结构化参数',
},
icon: 'FileSearch',
category: 'process',
color: '#8b5cf6',
inputs: [
createInput('text', 'string', {
description: 'Text to extract parameters from',
label: { en_US: 'Text', zh_Hans: '文本' },
}),
],
outputs: [
createOutput('parameters', 'object', {
description: 'Extracted parameters as key-value pairs',
label: { en_US: 'Parameters', zh_Hans: '参数' },
}),
createOutput('missing', 'array', {
description: 'List of required parameters that could not be extracted',
label: { en_US: 'Missing', zh_Hans: '缺失项' },
}),
createOutput('success', 'boolean', {
description: 'Whether all required parameters were extracted',
label: { en_US: 'Success', zh_Hans: '成功' },
}),
],
configSchema: [
{
id: 'model',
name: 'model',
type: DynamicFormItemType.LLM_MODEL_SELECTOR,
label: {
en_US: 'Extraction Model',
zh_Hans: '提取模型',
},
description: {
en_US: 'Select the model to use for parameter extraction',
zh_Hans: '选择用于参数提取的模型',
},
required: true,
default: '',
},
{
id: 'parameters',
name: 'parameters',
type: DynamicFormItemType.TEXT,
label: {
en_US: 'Parameters Schema',
zh_Hans: '参数架构',
},
description: {
en_US:
'JSON array defining expected parameters: [{"name": "date", "type": "string", "description": "Meeting date", "required": true}]',
zh_Hans:
'定义期望参数的 JSON 数组: [{"name": "日期", "type": "string", "description": "会议日期", "required": true}]',
},
required: true,
default: '[]',
},
{
id: 'extraction_prompt',
name: 'extraction_prompt',
type: DynamicFormItemType.TEXT,
label: {
en_US: 'Extraction Prompt',
zh_Hans: '提取提示',
},
description: {
en_US: 'Additional instructions for the extraction model',
zh_Hans: '提取模型的额外指令',
},
required: false,
default: '',
},
{
id: 'strict_mode',
name: 'strict_mode',
type: DynamicFormItemType.BOOLEAN,
label: {
en_US: 'Strict Mode',
zh_Hans: '严格模式',
},
description: {
en_US: 'Fail if any required parameter cannot be extracted',
zh_Hans: '如果任何必需参数无法提取则失败',
},
required: false,
default: true,
},
],
defaultConfig: {
model: '',
parameters_definition: '[]',
extraction_prompt: '',
strict_mode: true,
},
};
/**
* Knowledge Retrieval Node
* Retrieves relevant information from knowledge bases
*/
export const knowledgeRetrievalConfig: NodeConfigMeta = {
nodeType: 'knowledge_retrieval',
label: {
en_US: 'Knowledge Retrieval',
zh_Hans: '知识检索',
},
description: {
en_US:
'Retrieve relevant information from knowledge bases using semantic search',
zh_Hans: '使用语义搜索从知识库中检索相关信息',
},
icon: 'BookOpen',
category: 'process',
color: '#8b5cf6',
inputs: [
createInput('query', 'string', {
description: 'Query text to search for',
label: { en_US: 'Query', zh_Hans: '查询' },
}),
],
outputs: [
createOutput('results', 'array', {
description: 'Retrieved documents/chunks',
label: { en_US: 'Results', zh_Hans: '结果' },
}),
createOutput('context', 'string', {
description: 'Concatenated text from all results',
label: { en_US: 'Context', zh_Hans: '上下文' },
}),
createOutput('scores', 'array', {
description: 'Similarity scores for each result',
label: { en_US: 'Scores', zh_Hans: '分数' },
}),
],
configSchema: [
{
id: 'knowledge_bases',
name: 'knowledge_bases',
type: DynamicFormItemType.KNOWLEDGE_BASE_MULTI_SELECTOR,
label: {
en_US: 'Knowledge Bases',
zh_Hans: '知识库',
},
description: {
en_US: 'Select knowledge bases to search',
zh_Hans: '选择要搜索的知识库',
},
required: true,
default: [],
},
{
id: 'top_k',
name: 'top_k',
type: DynamicFormItemType.INT,
label: {
en_US: 'Top K Results',
zh_Hans: '返回数量 (Top K)',
},
description: {
en_US: 'Number of top results to retrieve',
zh_Hans: '返回的最相关结果数量',
},
required: false,
default: 5,
},
{
id: 'similarity_threshold',
name: 'similarity_threshold',
type: DynamicFormItemType.FLOAT,
label: {
en_US: 'Similarity Threshold',
zh_Hans: '相似度阈值',
},
description: {
en_US: 'Minimum similarity score (0.0-1.0) for results to be included',
zh_Hans: '结果被包含的最小相似度分数0.0-1.0',
},
required: false,
default: 0.5,
},
{
id: 'retrieval_mode',
name: 'retrieval_mode',
type: DynamicFormItemType.SELECT,
label: {
en_US: 'Retrieval Mode',
zh_Hans: '检索模式',
},
description: {
en_US: 'Method used for retrieving documents',
zh_Hans: '用于检索文档的方法',
},
required: false,
default: 'vector',
options: [
{
name: 'vector',
label: { en_US: 'Vector Search', zh_Hans: '向量检索' },
},
{
name: 'hybrid',
label: { en_US: 'Hybrid Search', zh_Hans: '混合检索' },
},
{
name: 'keyword',
label: { en_US: 'Keyword Search', zh_Hans: '关键词检索' },
},
],
},
{
id: 'rerank_enabled',
name: 'rerank_enabled',
type: DynamicFormItemType.BOOLEAN,
label: {
en_US: 'Enable Reranking',
zh_Hans: '启用重排序',
},
description: {
en_US: 'Use a reranking model to improve result relevance',
zh_Hans: '使用重排序模型提高结果相关性',
},
required: false,
default: false,
},
{
id: 'rerank_model',
name: 'rerank_model',
type: DynamicFormItemType.RERANK_MODEL_SELECTOR,
label: {
en_US: 'Rerank Model',
zh_Hans: '重排序模型',
},
description: {
en_US: 'Model to use for reranking results',
zh_Hans: '用于结果重排序的模型',
},
required: false,
default: '',
show_if: {
field: 'rerank_enabled',
operator: 'eq',
value: true,
},
},
],
defaultConfig: {
knowledge_bases: [],
top_k: 5,
similarity_threshold: 0.5,
retrieval_mode: 'vector',
rerank_enabled: false,
rerank_model: '',
},
};
/**
* Text Embedding Node
* Generates vector embeddings for text
*/
export const textEmbeddingConfig: NodeConfigMeta = {
nodeType: 'text_embedding',
label: {
en_US: 'Text Embedding',
zh_Hans: '文本嵌入',
},
description: {
en_US: 'Generate vector embeddings for text using an embedding model',
zh_Hans: '使用嵌入模型为文本生成向量嵌入',
},
icon: 'Binary',
category: 'process',
color: '#8b5cf6',
inputs: [
createInput('text', 'string', {
description: 'Text to embed',
label: { en_US: 'Text', zh_Hans: '文本' },
}),
],
outputs: [
createOutput('embedding', 'array', {
description: 'Vector embedding array',
label: { en_US: 'Embedding', zh_Hans: '嵌入向量' },
}),
createOutput('dimensions', 'number', {
description: 'Number of dimensions in the embedding',
label: { en_US: 'Dimensions', zh_Hans: '维度数' },
}),
],
configSchema: [
{
id: 'model',
name: 'model',
type: DynamicFormItemType.EMBEDDING_MODEL_SELECTOR,
label: {
en_US: 'Embedding Model',
zh_Hans: '嵌入模型',
},
description: {
en_US: 'Select the embedding model to use',
zh_Hans: '选择要使用的嵌入模型',
},
required: true,
default: '',
},
],
defaultConfig: {
model: '',
},
};
/**
* Intent Recognition Node
* Recognizes user intent from natural language
*/
export const intentRecognitionConfig: NodeConfigMeta = {
nodeType: 'intent_recognition',
label: {
en_US: 'Intent Recognition',
zh_Hans: '意图识别',
},
description: {
en_US: 'Recognize user intent from natural language using AI',
zh_Hans: '使用 AI 从自然语言中识别用户意图',
},
icon: 'Target',
category: 'process',
color: '#8b5cf6',
inputs: [
createInput('text', 'string', {
description: 'Text to analyze',
label: { en_US: 'Text', zh_Hans: '文本' },
}),
],
outputs: [
createOutput('intent', 'string', {
description: 'Recognized intent',
label: { en_US: 'Intent', zh_Hans: '意图' },
}),
createOutput('confidence', 'number', {
description: 'Recognition confidence score',
label: { en_US: 'Confidence', zh_Hans: '置信度' },
}),
createOutput('entities', 'object', {
description: 'Extracted entities from the text',
label: { en_US: 'Entities', zh_Hans: '实体' },
}),
],
configSchema: [
{
id: 'model',
name: 'model',
type: DynamicFormItemType.LLM_MODEL_SELECTOR,
label: {
en_US: 'Recognition Model',
zh_Hans: '识别模型',
},
description: {
en_US: 'Select the model for intent recognition',
zh_Hans: '选择用于意图识别的模型',
},
required: true,
default: '',
},
{
id: 'intents_definition',
name: 'intents_definition',
type: DynamicFormItemType.TEXT,
label: {
en_US: 'Intents Definition',
zh_Hans: '意图定义',
},
description: {
en_US:
'Define intents in JSON format: [{"name": "intent1", "description": "...", "examples": ["..."]}]',
zh_Hans:
'使用 JSON 格式定义意图: [{"name": "意图1", "description": "...", "examples": ["..."]}]',
},
required: true,
default: '[]',
},
{
id: 'extract_entities',
name: 'extract_entities',
type: DynamicFormItemType.BOOLEAN,
label: {
en_US: 'Extract Entities',
zh_Hans: '提取实体',
},
description: {
en_US: 'Also extract named entities from the text',
zh_Hans: '同时从文本中提取命名实体',
},
required: false,
default: true,
},
],
defaultConfig: {
model: '',
intents_definition: '[]',
extract_entities: true,
},
};
/**
* All AI node configurations
*/
export const aiConfigs: NodeConfigMeta[] = [
llmCallConfig,
questionClassifierConfig,
parameterExtractorConfig,
knowledgeRetrievalConfig,
textEmbeddingConfig,
intentRecognitionConfig,
];
/**
* Get AI config by type
*/
export function getAIConfig(nodeType: string): NodeConfigMeta | undefined {
return aiConfigs.find((config) => config.nodeType === nodeType);
}

View File

@@ -1,998 +0,0 @@
/**
* Control Node Configurations
*
* Defines configurations for flow control node types:
* - condition: Conditional branching
* - switch_case: Multi-way branching
* - loop: Loop/iteration
* - parallel: Parallel execution
* - wait: Wait/delay
* - end: End workflow
*/
import { DynamicFormItemType } from '@/app/infra/entities/form/dynamic';
import { NodeConfigMeta, createInput, createOutput } from './types';
/**
* Condition Node
* Conditional branching based on expression
*/
export const conditionConfig: NodeConfigMeta = {
nodeType: 'condition',
label: {
en_US: 'Condition',
zh_Hans: '条件分支',
},
description: {
en_US: 'Branch workflow based on a condition',
zh_Hans: '根据条件分支工作流',
},
icon: 'GitBranch',
category: 'control',
color: '#8b5cf6',
inputs: [
createInput('input', 'any', {
description: 'Input data for condition evaluation',
label: { en_US: 'Input', zh_Hans: '输入' },
}),
],
outputs: [
createOutput('true', 'any', {
description: 'Output when condition is true',
label: { en_US: 'True', zh_Hans: '真' },
}),
createOutput('false', 'any', {
description: 'Output when condition is false',
label: { en_US: 'False', zh_Hans: '假' },
}),
],
configSchema: [
{
id: 'condition_type',
name: 'condition_type',
type: DynamicFormItemType.SELECT,
label: {
en_US: 'Condition Type',
zh_Hans: '条件类型',
},
description: {
en_US: 'Type of condition to evaluate',
zh_Hans: '要评估的条件类型',
},
required: true,
default: 'expression',
options: [
{
name: 'expression',
label: { en_US: 'Expression', zh_Hans: '表达式' },
},
{ name: 'comparison', label: { en_US: 'Comparison', zh_Hans: '比较' } },
{ name: 'exists', label: { en_US: 'Value Exists', zh_Hans: '值存在' } },
{
name: 'type_check',
label: { en_US: 'Type Check', zh_Hans: '类型检查' },
},
],
},
{
id: 'expression',
name: 'expression',
type: DynamicFormItemType.STRING,
label: {
en_US: 'Expression',
zh_Hans: '表达式',
},
description: {
en_US: 'JavaScript expression that evaluates to true/false',
zh_Hans: '评估为 true/false 的 JavaScript 表达式',
},
required: true,
default: '',
show_if: {
field: 'condition_type',
operator: 'eq',
value: 'expression',
},
},
{
id: 'left_value',
name: 'left_value',
type: DynamicFormItemType.STRING,
label: {
en_US: 'Left Value',
zh_Hans: '左值',
},
description: {
en_US: 'Left side of comparison (supports variable references)',
zh_Hans: '比较的左侧(支持变量引用)',
},
required: true,
default: '{{input}}',
show_if: {
field: 'condition_type',
operator: 'in',
value: ['comparison', 'exists', 'type_check'],
},
},
{
id: 'operator',
name: 'operator',
type: DynamicFormItemType.SELECT,
label: {
en_US: 'Operator',
zh_Hans: '运算符',
},
description: {
en_US: 'Comparison operator',
zh_Hans: '比较运算符',
},
required: true,
default: 'eq',
options: [
{ name: 'eq', label: { en_US: 'Equals (==)', zh_Hans: '等于 (==)' } },
{
name: 'neq',
label: { en_US: 'Not Equals (!=)', zh_Hans: '不等于 (!=)' },
},
{
name: 'gt',
label: { en_US: 'Greater Than (>)', zh_Hans: '大于 (>)' },
},
{
name: 'gte',
label: { en_US: 'Greater or Equal (>=)', zh_Hans: '大于等于 (>=)' },
},
{ name: 'lt', label: { en_US: 'Less Than (<)', zh_Hans: '小于 (<)' } },
{
name: 'lte',
label: { en_US: 'Less or Equal (<=)', zh_Hans: '小于等于 (<=)' },
},
{ name: 'contains', label: { en_US: 'Contains', zh_Hans: '包含' } },
{
name: 'starts_with',
label: { en_US: 'Starts With', zh_Hans: '以...开头' },
},
{
name: 'ends_with',
label: { en_US: 'Ends With', zh_Hans: '以...结尾' },
},
{
name: 'matches',
label: { en_US: 'Matches Regex', zh_Hans: '匹配正则' },
},
],
show_if: {
field: 'condition_type',
operator: 'eq',
value: 'comparison',
},
},
{
id: 'right_value',
name: 'right_value',
type: DynamicFormItemType.STRING,
label: {
en_US: 'Right Value',
zh_Hans: '右值',
},
description: {
en_US: 'Right side of comparison',
zh_Hans: '比较的右侧',
},
required: true,
default: '',
show_if: {
field: 'condition_type',
operator: 'eq',
value: 'comparison',
},
},
{
id: 'expected_type',
name: 'expected_type',
type: DynamicFormItemType.SELECT,
label: {
en_US: 'Expected Type',
zh_Hans: '期望类型',
},
description: {
en_US: 'The type to check for',
zh_Hans: '要检查的类型',
},
required: true,
default: 'string',
options: [
{ name: 'string', label: { en_US: 'String', zh_Hans: '字符串' } },
{ name: 'number', label: { en_US: 'Number', zh_Hans: '数字' } },
{ name: 'boolean', label: { en_US: 'Boolean', zh_Hans: '布尔' } },
{ name: 'object', label: { en_US: 'Object', zh_Hans: '对象' } },
{ name: 'array', label: { en_US: 'Array', zh_Hans: '数组' } },
{ name: 'null', label: { en_US: 'Null', zh_Hans: '空' } },
],
show_if: {
field: 'condition_type',
operator: 'eq',
value: 'type_check',
},
},
],
defaultConfig: {
condition_type: 'expression',
expression: '',
left_value: '{{input}}',
operator: 'eq',
right_value: '',
expected_type: 'string',
},
};
/**
* Switch Case Node
* Multi-way branching based on value
*/
export const switchCaseConfig: NodeConfigMeta = {
nodeType: 'switch_case',
label: {
en_US: 'Switch',
zh_Hans: '多路分支',
},
description: {
en_US: 'Branch workflow based on multiple cases',
zh_Hans: '根据多个条件分支工作流',
},
icon: 'GitFork',
category: 'control',
color: '#8b5cf6',
inputs: [
createInput('input', 'any', {
description: 'Value to switch on',
label: { en_US: 'Input', zh_Hans: '输入' },
}),
],
outputs: [
createOutput('case_1', 'any', {
description: 'Branch 1 output',
label: { en_US: 'Branch 1', zh_Hans: '分支 1' },
}),
createOutput('case_2', 'any', {
description: 'Branch 2 output',
label: { en_US: 'Branch 2', zh_Hans: '分支 2' },
}),
createOutput('default', 'any', {
description: 'Default branch output',
label: { en_US: 'Default Branch', zh_Hans: '默认分支' },
}),
],
configSchema: [
{
id: 'switch_expression',
name: 'switch_expression',
type: DynamicFormItemType.STRING,
label: {
en_US: 'Switch Expression',
zh_Hans: '开关表达式',
},
description: {
en_US: 'Expression to evaluate for switching (e.g., {{input.type}})',
zh_Hans: '用于切换的表达式(例如 {{input.type}}',
},
required: true,
default: '{{input}}',
},
{
id: 'cases',
name: 'cases',
type: DynamicFormItemType.TEXT,
label: {
en_US: 'Cases',
zh_Hans: '情况',
},
description: {
en_US:
'Define cases as JSON array: [{"name": "case_1", "value": "value1"}, {"name": "case_2", "values": ["v1", "v2"]}]',
zh_Hans:
'使用 JSON 数组定义情况: [{"name": "case_1", "value": "value1"}, {"name": "case_2", "values": ["v1", "v2"]}]',
},
required: true,
default:
'[{"name": "case_1", "value": ""}, {"name": "case_2", "value": ""}]',
},
{
id: 'case_sensitive',
name: 'case_sensitive',
type: DynamicFormItemType.BOOLEAN,
label: {
en_US: 'Case Sensitive',
zh_Hans: '区分大小写',
},
description: {
en_US: 'Whether string comparisons are case-sensitive',
zh_Hans: '字符串比较是否区分大小写',
},
required: false,
default: true,
},
],
defaultConfig: {
switch_expression: '{{input}}',
cases: '[{"name": "case_1", "value": ""}, {"name": "case_2", "value": ""}]',
case_sensitive: true,
},
};
/**
* Loop Node
* Iterates over items or until condition
*/
export const loopConfig: NodeConfigMeta = {
nodeType: 'loop',
label: {
en_US: 'Loop',
zh_Hans: '循环',
},
description: {
en_US: 'Iterate over items or repeat until condition',
zh_Hans: '遍历项目或重复直到满足条件',
},
icon: 'Repeat',
category: 'control',
color: '#8b5cf6',
inputs: [
createInput('items', 'array', {
description: 'Items to iterate over (for each loop)',
label: { en_US: 'Items', zh_Hans: '项目' },
required: false,
}),
],
outputs: [
createOutput('item', 'any', {
description: 'Current item in iteration',
label: { en_US: 'Item', zh_Hans: '当前项' },
}),
createOutput('index', 'number', {
description: 'Current iteration index',
label: { en_US: 'Index', zh_Hans: '索引' },
}),
createOutput('completed', 'any', {
description: 'Output after loop completes',
label: { en_US: 'Completed', zh_Hans: '完成' },
}),
],
configSchema: [
{
id: 'loop_type',
name: 'loop_type',
type: DynamicFormItemType.SELECT,
label: {
en_US: 'Loop Type',
zh_Hans: '循环类型',
},
description: {
en_US: 'Type of loop to execute',
zh_Hans: '要执行的循环类型',
},
required: true,
default: 'foreach',
options: [
{ name: 'foreach', label: { en_US: 'For Each', zh_Hans: '逐项遍历' } },
{ name: 'while', label: { en_US: 'While', zh_Hans: '条件循环' } },
{ name: 'count', label: { en_US: 'Count', zh_Hans: '计数' } },
],
},
{
id: 'max_iterations',
name: 'max_iterations',
type: DynamicFormItemType.INT,
label: {
en_US: 'Max Iterations',
zh_Hans: '最大迭代次数',
},
description: {
en_US: 'Maximum number of iterations (safety limit)',
zh_Hans: '最大迭代次数(安全限制)',
},
required: false,
default: 100,
},
{
id: 'count',
name: 'count',
type: DynamicFormItemType.INT,
label: {
en_US: 'Count',
zh_Hans: '计数',
},
description: {
en_US: 'Number of times to iterate',
zh_Hans: '迭代次数',
},
required: true,
default: 10,
show_if: {
field: 'loop_type',
operator: 'eq',
value: 'count',
},
},
{
id: 'while_condition',
name: 'while_condition',
type: DynamicFormItemType.STRING,
label: {
en_US: 'While Condition',
zh_Hans: 'While 条件',
},
description: {
en_US: 'Condition expression to continue looping',
zh_Hans: '继续循环的条件表达式',
},
required: true,
default: '',
show_if: {
field: 'loop_type',
operator: 'eq',
value: 'while',
},
},
{
id: 'parallel',
name: 'parallel',
type: DynamicFormItemType.BOOLEAN,
label: {
en_US: 'Parallel Execution',
zh_Hans: '并行执行',
},
description: {
en_US: 'Execute iterations in parallel',
zh_Hans: '并行执行迭代',
},
required: false,
default: false,
show_if: {
field: 'loop_type',
operator: 'eq',
value: 'foreach',
},
},
{
id: 'parallel_limit',
name: 'parallel_limit',
type: DynamicFormItemType.INT,
label: {
en_US: 'Parallel Limit',
zh_Hans: '并行限制',
},
description: {
en_US: 'Maximum number of parallel executions',
zh_Hans: '最大并行执行数',
},
required: false,
default: 5,
show_if: {
field: 'parallel',
operator: 'eq',
value: true,
},
},
],
defaultConfig: {
loop_type: 'foreach',
max_iterations: 100,
count: 10,
while_condition: '',
parallel: false,
parallel_limit: 5,
},
};
/**
* Parallel Node
* Execute multiple branches in parallel
*/
export const parallelConfig: NodeConfigMeta = {
nodeType: 'parallel',
label: {
en_US: 'Parallel',
zh_Hans: '并行执行',
},
description: {
en_US: 'Execute multiple branches in parallel',
zh_Hans: '并行执行多个分支',
},
icon: 'GitMerge',
category: 'control',
color: '#8b5cf6',
inputs: [
createInput('input', 'any', {
description: 'Input data for all branches',
label: { en_US: 'Input', zh_Hans: '输入' },
}),
],
outputs: [
createOutput('branch_1', 'any', {
description: 'Branch 1 output',
label: { en_US: 'Branch 1', zh_Hans: '分支 1' },
}),
createOutput('branch_2', 'any', {
description: 'Branch 2 output',
label: { en_US: 'Branch 2', zh_Hans: '分支 2' },
}),
createOutput('results', 'object', {
description: 'Combined results from all branches',
label: { en_US: 'Results', zh_Hans: '结果' },
}),
],
configSchema: [
{
id: 'branches',
name: 'branches',
type: DynamicFormItemType.TEXT,
label: {
en_US: 'Branches',
zh_Hans: '分支',
},
description: {
en_US:
'Define branches as JSON array: [{"name": "branch_1"}, {"name": "branch_2"}]',
zh_Hans:
'使用 JSON 数组定义分支: [{"name": "branch_1"}, {"name": "branch_2"}]',
},
required: true,
default: '[{"name": "branch_1"}, {"name": "branch_2"}]',
},
{
id: 'wait_for_all',
name: 'wait_for_all',
type: DynamicFormItemType.BOOLEAN,
label: {
en_US: 'Wait for All',
zh_Hans: '等待全部完成',
},
description: {
en_US: 'Wait for all branches to complete before continuing',
zh_Hans: '等待所有分支完成后再继续',
},
required: false,
default: true,
},
{
id: 'fail_fast',
name: 'fail_fast',
type: DynamicFormItemType.BOOLEAN,
label: {
en_US: 'Fail Fast',
zh_Hans: '快速失败',
},
description: {
en_US: 'Stop all branches if any one fails',
zh_Hans: '如果任何一个分支失败则停止所有分支',
},
required: false,
default: false,
},
],
defaultConfig: {
branches: '[{"name": "branch_1"}, {"name": "branch_2"}]',
wait_for_all: true,
fail_fast: false,
},
};
/**
* Wait Node
* Pause workflow execution
*/
export const waitConfig: NodeConfigMeta = {
nodeType: 'wait',
label: {
en_US: 'Wait',
zh_Hans: '等待',
},
description: {
en_US: 'Pause workflow execution for a specified duration or condition',
zh_Hans: '暂停工作流执行指定的时间或等待条件满足',
},
icon: 'Clock',
category: 'control',
color: '#8b5cf6',
inputs: [
createInput('input', 'any', {
description: 'Input to pass through',
label: { en_US: 'Input', zh_Hans: '输入' },
required: false,
}),
],
outputs: [
createOutput('output', 'any', {
description: 'Passed through input',
label: { en_US: 'Output', zh_Hans: '输出' },
}),
],
configSchema: [
{
id: 'wait_type',
name: 'wait_type',
type: DynamicFormItemType.SELECT,
label: {
en_US: 'Wait Type',
zh_Hans: '等待类型',
},
description: {
en_US: 'Type of wait operation',
zh_Hans: '等待操作的类型',
},
required: true,
default: 'duration',
options: [
{ name: 'duration', label: { en_US: 'Duration', zh_Hans: '时长' } },
{ name: 'until', label: { en_US: 'Until Time', zh_Hans: '直到时间' } },
],
},
{
id: 'duration',
name: 'duration',
type: DynamicFormItemType.INT,
label: {
en_US: 'Duration (seconds)',
zh_Hans: '时长(秒)',
},
description: {
en_US: 'Number of seconds to wait',
zh_Hans: '等待的秒数',
},
required: true,
default: 5,
show_if: {
field: 'wait_type',
operator: 'eq',
value: 'duration',
},
},
{
id: 'until_time',
name: 'until_time',
type: DynamicFormItemType.STRING,
label: {
en_US: 'Until Time',
zh_Hans: '直到时间',
},
description: {
en_US: 'Wait until this time (ISO 8601 format or expression)',
zh_Hans: '等待直到此时间ISO 8601 格式或表达式)',
},
required: true,
default: '',
show_if: {
field: 'wait_type',
operator: 'eq',
value: 'until',
},
},
],
defaultConfig: {
wait_type: 'duration',
duration: 5,
until_time: '',
},
};
/**
* End Node
* Terminates workflow execution
*/
export const endConfig: NodeConfigMeta = {
nodeType: 'end',
label: {
en_US: 'End',
zh_Hans: '结束',
},
description: {
en_US: 'End the workflow execution',
zh_Hans: '结束工作流执行',
},
icon: 'CircleStop',
category: 'control',
color: '#8b5cf6',
inputs: [
createInput('input', 'any', {
description: 'Final output data',
label: { en_US: 'Input', zh_Hans: '输入' },
required: false,
}),
],
outputs: [],
configSchema: [
{
id: 'status',
name: 'status',
type: DynamicFormItemType.SELECT,
label: {
en_US: 'End Status',
zh_Hans: '结束状态',
},
description: {
en_US: 'Status to report when workflow ends',
zh_Hans: '工作流结束时报告的状态',
},
required: true,
default: 'success',
options: [
{ name: 'success', label: { en_US: 'Success', zh_Hans: '成功' } },
{ name: 'failed', label: { en_US: 'Failed', zh_Hans: '失败' } },
{ name: 'cancelled', label: { en_US: 'Cancelled', zh_Hans: '取消' } },
],
},
{
id: 'message',
name: 'message',
type: DynamicFormItemType.STRING,
label: {
en_US: 'Message',
zh_Hans: '消息',
},
description: {
en_US: 'Optional message to include with the end status',
zh_Hans: '与结束状态一起包含的可选消息',
},
required: false,
default: '',
},
],
defaultConfig: {
status: 'success',
message: '',
},
};
/**
* Iterator Node
* Iterates over array items one by one
*/
export const iteratorConfig: NodeConfigMeta = {
nodeType: 'iterator',
label: {
en_US: 'Iterator',
zh_Hans: '迭代器',
},
description: {
en_US: 'Iterate over array elements one by one',
zh_Hans: '逐个遍历数组元素',
},
icon: 'Repeat',
category: 'control',
color: '#8b5cf6',
inputs: [
createInput('items', 'array', {
description: 'Array to iterate over',
label: { en_US: 'Items', zh_Hans: '项目' },
}),
],
outputs: [
createOutput('item', 'any', {
description: 'Current item',
label: { en_US: 'Item', zh_Hans: '当前项' },
}),
createOutput('index', 'number', {
description: 'Current index',
label: { en_US: 'Index', zh_Hans: '索引' },
}),
createOutput('is_first', 'boolean', {
description: 'Whether this is the first item',
label: { en_US: 'Is First', zh_Hans: '是否第一个' },
}),
createOutput('is_last', 'boolean', {
description: 'Whether this is the last item',
label: { en_US: 'Is Last', zh_Hans: '是否最后一个' },
}),
createOutput('completed', 'any', {
description: 'Output after iteration completes',
label: { en_US: 'Completed', zh_Hans: '完成' },
}),
],
configSchema: [
{
id: 'parallel',
name: 'parallel',
type: DynamicFormItemType.BOOLEAN,
label: { en_US: 'Parallel Processing', zh_Hans: '并行处理' },
description: {
en_US: 'Process items in parallel',
zh_Hans: '并行处理项目',
},
required: false,
default: false,
},
{
id: 'max_concurrency',
name: 'max_concurrency',
type: DynamicFormItemType.INT,
label: { en_US: 'Max Concurrency', zh_Hans: '最大并发数' },
description: {
en_US: 'Maximum number of concurrent iterations',
zh_Hans: '最大并发迭代数',
},
required: false,
default: 5,
show_if: { field: 'parallel', operator: 'eq', value: true },
},
{
id: 'max_iterations',
name: 'max_iterations',
type: DynamicFormItemType.INT,
label: { en_US: 'Max Iterations', zh_Hans: '最大迭代次数' },
description: {
en_US: 'Safety limit on iterations',
zh_Hans: '迭代次数安全限制',
},
required: false,
default: 1000,
},
],
defaultConfig: { parallel: false, max_concurrency: 5, max_iterations: 1000 },
};
/**
* Merge Node
* Merges multiple branches back together
*/
export const mergeConfig: NodeConfigMeta = {
nodeType: 'merge',
label: {
en_US: 'Merge',
zh_Hans: '合并',
},
description: {
en_US: 'Merge multiple branches back together',
zh_Hans: '将多个分支合并在一起',
},
icon: 'GitMerge',
category: 'control',
color: '#8b5cf6',
inputs: [
createInput('branch_1', 'any', {
description: 'Input from branch 1',
label: { en_US: 'Branch 1', zh_Hans: '分支 1' },
required: false,
}),
createInput('branch_2', 'any', {
description: 'Input from branch 2',
label: { en_US: 'Branch 2', zh_Hans: '分支 2' },
required: false,
}),
],
outputs: [
createOutput('output', 'any', {
description: 'Merged output',
label: { en_US: 'Output', zh_Hans: '输出' },
}),
],
configSchema: [
{
id: 'merge_strategy',
name: 'merge_strategy',
type: DynamicFormItemType.SELECT,
label: { en_US: 'Merge Strategy', zh_Hans: '合并策略' },
description: {
en_US: 'How to merge inputs from branches',
zh_Hans: '如何合并分支输入',
},
required: true,
default: 'wait_all',
options: [
{
name: 'wait_all',
label: { en_US: 'Wait for All', zh_Hans: '等待全部' },
},
{
name: 'first_completed',
label: { en_US: 'First Completed', zh_Hans: '第一个完成' },
},
{
name: 'combine',
label: { en_US: 'Combine to Object', zh_Hans: '合并为对象' },
},
{
name: 'array',
label: { en_US: 'Collect to Array', zh_Hans: '收集为数组' },
},
],
},
],
defaultConfig: { merge_strategy: 'wait_all' },
};
/**
* Variable Aggregator Node
* Aggregates variable outputs from multiple branches
*/
export const variableAggregatorConfig: NodeConfigMeta = {
nodeType: 'variable_aggregator',
label: {
en_US: 'Variable Aggregator',
zh_Hans: '变量聚合器',
},
description: {
en_US: 'Aggregate variable outputs from multiple branches',
zh_Hans: '聚合多个分支的变量输出',
},
icon: 'GitMerge',
category: 'control',
color: '#8b5cf6',
inputs: [
createInput('input', 'any', {
description: 'Input data',
label: { en_US: 'Input', zh_Hans: '输入' },
required: false,
}),
],
outputs: [
createOutput('output', 'any', {
description: 'Aggregated output',
label: { en_US: 'Output', zh_Hans: '输出' },
}),
],
configSchema: [
{
id: 'variable_mappings',
name: 'variable_mappings',
type: DynamicFormItemType.TEXT,
label: { en_US: 'Variable Mappings', zh_Hans: '变量映射' },
description: {
en_US:
'JSON mapping of output variables: {"out_key": "{{nodes.xxx.value}}"}',
zh_Hans: 'JSON 格式的输出变量映射: {"out_key": "{{nodes.xxx.value}}"}',
},
required: true,
default: '{}',
},
{
id: 'aggregation_mode',
name: 'aggregation_mode',
type: DynamicFormItemType.SELECT,
label: { en_US: 'Aggregation Mode', zh_Hans: '聚合模式' },
description: {
en_US: 'How to aggregate the variables',
zh_Hans: '如何聚合变量',
},
required: true,
default: 'merge',
options: [
{
name: 'merge',
label: { en_US: 'Merge Objects', zh_Hans: '合并对象' },
},
{
name: 'array',
label: { en_US: 'Collect to Array', zh_Hans: '收集为数组' },
},
{
name: 'first',
label: { en_US: 'First Non-null', zh_Hans: '第一个非空' },
},
],
},
],
defaultConfig: { variable_mappings: '{}', aggregation_mode: 'merge' },
};
/**
* All control node configurations
*/
export const controlConfigs: NodeConfigMeta[] = [
conditionConfig,
switchCaseConfig,
loopConfig,
iteratorConfig,
parallelConfig,
waitConfig,
mergeConfig,
variableAggregatorConfig,
endConfig,
];
/**
* Get control config by type
*/
export function getControlConfig(nodeType: string): NodeConfigMeta | undefined {
return controlConfigs.find((config) => config.nodeType === nodeType);
}

View File

@@ -1,261 +0,0 @@
/**
* Node Configurations Index
*
* This module exports all node configuration metadata and provides
* utility functions for accessing node configurations.
*/
// Types
export * from './types';
// Trigger Nodes
export {
triggerConfigs,
getTriggerConfig,
messageTriggerConfig,
cronTriggerConfig,
webhookTriggerConfig,
eventTriggerConfig,
} from './trigger-configs';
// AI Nodes
export {
aiConfigs,
getAIConfig,
llmCallConfig,
questionClassifierConfig,
parameterExtractorConfig,
knowledgeRetrievalConfig,
textEmbeddingConfig,
intentRecognitionConfig,
} from './ai-configs';
// Process Nodes
export {
processConfigs,
getProcessConfig,
textTemplateConfig,
jsonTransformConfig,
codeExecutorConfig,
dataAggregatorConfig,
textSplitterConfig,
variableAssignmentConfig,
dataTransformConfig,
} from './process-configs';
// Control Nodes
export {
controlConfigs,
getControlConfig,
conditionConfig,
switchCaseConfig,
loopConfig,
iteratorConfig,
parallelConfig,
waitConfig,
mergeConfig,
variableAggregatorConfig,
endConfig,
} from './control-configs';
// Action Nodes
export {
actionConfigs,
getActionConfig,
sendMessageConfig,
replyMessageConfig,
httpRequestConfig,
storeDataConfig,
callPipelineConfig,
setVariableConfig,
openingStatementConfig,
botInvokeConfig,
workflowInvokeConfig,
notificationConfig,
} from './action-configs';
// Integration Nodes
export {
integrationConfigs,
getIntegrationConfig,
difyWorkflowConfig,
difyKnowledgeQueryConfig,
n8nWorkflowConfig,
langflowFlowConfig,
cozeBotConfig,
databaseQueryConfig,
redisOperationConfig,
mcpToolConfig,
memoryStoreConfig,
} from './integration-configs';
import { NodeConfigMeta, NodeConfigRegistry } from './types';
import { triggerConfigs } from './trigger-configs';
import { aiConfigs } from './ai-configs';
import { processConfigs } from './process-configs';
import { controlConfigs } from './control-configs';
import { actionConfigs } from './action-configs';
import { integrationConfigs } from './integration-configs';
import { NodeCategory } from '@/app/infra/entities/workflow';
/**
* All node configurations combined
*/
export const allNodeConfigs: NodeConfigMeta[] = [
...triggerConfigs,
...aiConfigs,
...processConfigs,
...controlConfigs,
...actionConfigs,
...integrationConfigs,
];
/**
* Node configuration registry by type
* Registers each config under both its short name (e.g. "message_trigger")
* and its full category-prefixed name (e.g. "trigger.message_trigger")
* so lookups from PropertyPanel / useWorkflowStore always succeed.
*/
export const nodeConfigRegistry: NodeConfigRegistry = (() => {
const registry: NodeConfigRegistry = {};
for (const config of allNodeConfigs) {
// Short name
registry[config.nodeType] = config;
// Full category.name
registry[`${config.category}.${config.nodeType}`] = config;
}
// Aliases for nodes whose palette type differs from config nodeType
// control.switch -> switch_case config
if (registry['switch_case']) {
registry['switch'] = registry['switch_case'];
registry['control.switch'] = registry['switch_case'];
}
// action.end also points to the end config in control
if (registry['end']) {
registry['action.end'] = registry['end'];
}
return registry;
})();
/**
* Get node configuration by type
*/
export function getNodeConfig(nodeType: string): NodeConfigMeta | undefined {
return nodeConfigRegistry[nodeType];
}
/**
* Get all node configurations for a category
*/
export function getNodeConfigsByCategory(
category: NodeCategory,
): NodeConfigMeta[] {
return allNodeConfigs.filter((config) => config.category === category);
}
/**
* Get all entry point node configurations (trigger nodes)
*/
export function getEntryPointConfigs(): NodeConfigMeta[] {
return allNodeConfigs.filter((config) => config.isEntryPoint);
}
/**
* Check if a node type exists
*/
export function isValidNodeType(nodeType: string): boolean {
return nodeType in nodeConfigRegistry;
}
/**
* Get default configuration for a node type
*/
export function getDefaultConfig(nodeType: string): Record<string, unknown> {
const config = getNodeConfig(nodeType);
if (!config) return {};
// Build default config from schema defaults
const defaults: Record<string, unknown> = {};
for (const field of config.configSchema) {
defaults[field.name] = field.default;
}
// Override with explicit defaultConfig if provided
if (config.defaultConfig) {
Object.assign(defaults, config.defaultConfig);
}
return defaults;
}
/**
* Validate node configuration against schema
*/
export function validateNodeConfig(
nodeType: string,
config: Record<string, unknown>,
): { valid: boolean; errors: string[] } {
const nodeConfig = getNodeConfig(nodeType);
if (!nodeConfig) {
return { valid: false, errors: [`Unknown node type: ${nodeType}`] };
}
const errors: string[] = [];
for (const field of nodeConfig.configSchema) {
const value = config[field.name];
// Check required fields
if (
field.required &&
(value === undefined || value === null || value === '')
) {
errors.push(`Field "${field.name}" is required`);
continue;
}
// Skip validation for optional empty fields
if (!field.required && (value === undefined || value === null)) {
continue;
}
// Type-specific validation could be added here
}
return { valid: errors.length === 0, errors };
}
/**
* Convert node config metadata to NodeTypeMetadata format
* (for compatibility with existing workflow store)
*/
export function toNodeTypeMetadata(config: NodeConfigMeta) {
return {
type: config.nodeType,
name: config.label,
description: config.description,
category: config.category,
icon: config.icon,
color: config.color,
inputs: config.inputs.map((input) => ({
name: input.name,
type: input.type,
description: input.description,
required: input.required,
})),
outputs: config.outputs.map((output) => ({
name: output.name,
type: output.type,
description: output.description,
required: output.required,
})),
config_schema: config.configSchema,
};
}
/**
* Convert all node configs to NodeTypeMetadata format
*/
export function getAllNodeTypeMetadata() {
return allNodeConfigs.map(toNodeTypeMetadata);
}

View File

@@ -1,912 +0,0 @@
/**
* Integration Node Configurations
*
* Defines configurations for integration node types:
* - database_query: Query databases
* - redis_operation: Redis operations
* - mcp_tool: MCP tool invocation
*/
import { DynamicFormItemType } from '@/app/infra/entities/form/dynamic';
import { NodeConfigMeta, createInput, createOutput } from './types';
/**
* Database Query Node
* Executes database queries
*/
export const databaseQueryConfig: NodeConfigMeta = {
nodeType: 'database_query',
label: {
en_US: 'Database Query',
zh_Hans: '数据库查询',
},
description: {
en_US: 'Execute database queries',
zh_Hans: '执行数据库查询',
},
icon: 'Database',
category: 'integration',
color: '#ec4899',
inputs: [
createInput('parameters', 'object', {
description: 'Query parameters',
label: { en_US: 'Parameters', zh_Hans: '参数' },
required: false,
}),
],
outputs: [
createOutput('results', 'array', {
description: 'Query results',
label: { en_US: 'Results', zh_Hans: '结果' },
}),
createOutput('row_count', 'number', {
description: 'Number of rows affected/returned',
label: { en_US: 'Row Count', zh_Hans: '行数' },
}),
createOutput('success', 'boolean', {
description: 'Whether query was successful',
label: { en_US: 'Success', zh_Hans: '成功' },
}),
],
configSchema: [
{
id: 'connection_type',
name: 'connection_type',
type: DynamicFormItemType.SELECT,
label: {
en_US: 'Database Type',
zh_Hans: '数据库类型',
},
description: {
en_US: 'Type of database to connect to',
zh_Hans: '要连接的数据库类型',
},
required: true,
default: 'postgresql',
options: [
{
name: 'postgresql',
label: { en_US: 'PostgreSQL', zh_Hans: 'PostgreSQL' },
},
{ name: 'mysql', label: { en_US: 'MySQL', zh_Hans: 'MySQL' } },
{ name: 'sqlite', label: { en_US: 'SQLite', zh_Hans: 'SQLite' } },
],
},
{
id: 'connection_string',
name: 'connection_string',
type: DynamicFormItemType.SECRET,
label: {
en_US: 'Connection String',
zh_Hans: '连接字符串',
},
description: {
en_US: 'Database connection string',
zh_Hans: '数据库连接字符串',
},
required: true,
default: '',
},
{
id: 'query',
name: 'query',
type: DynamicFormItemType.TEXT,
label: {
en_US: 'SQL Query',
zh_Hans: 'SQL 查询',
},
description: {
en_US: 'SQL query to execute (use $1, $2, etc. for parameters)',
zh_Hans: '要执行的 SQL 查询(使用 $1、$2 等作为参数占位符)',
},
required: true,
default: '',
},
{
id: 'query_type',
name: 'query_type',
type: DynamicFormItemType.SELECT,
label: {
en_US: 'Query Type',
zh_Hans: '查询类型',
},
description: {
en_US: 'Type of query operation',
zh_Hans: '查询操作的类型',
},
required: true,
default: 'select',
options: [
{ name: 'select', label: { en_US: 'SELECT', zh_Hans: 'SELECT' } },
{ name: 'insert', label: { en_US: 'INSERT', zh_Hans: 'INSERT' } },
{ name: 'update', label: { en_US: 'UPDATE', zh_Hans: 'UPDATE' } },
{ name: 'delete', label: { en_US: 'DELETE', zh_Hans: 'DELETE' } },
],
},
{
id: 'timeout',
name: 'timeout',
type: DynamicFormItemType.INT,
label: {
en_US: 'Timeout (seconds)',
zh_Hans: '超时时间(秒)',
},
description: {
en_US: 'Query timeout',
zh_Hans: '查询超时时间',
},
required: false,
default: 30,
},
],
defaultConfig: {
connection_type: 'postgresql',
connection_string: '',
query: '',
query_type: 'select',
timeout: 30,
},
};
/**
* Redis Operation Node
* Performs Redis operations
*/
export const redisOperationConfig: NodeConfigMeta = {
nodeType: 'redis_operation',
label: {
en_US: 'Redis Operation',
zh_Hans: 'Redis 操作',
},
description: {
en_US: 'Perform Redis cache operations',
zh_Hans: '执行 Redis 缓存操作',
},
icon: 'Server',
category: 'integration',
color: '#ec4899',
inputs: [
createInput('key', 'string', {
description: 'Redis key',
label: { en_US: 'Key', zh_Hans: '键' },
required: false,
}),
createInput('value', 'any', {
description: 'Value to store',
label: { en_US: 'Value', zh_Hans: '值' },
required: false,
}),
],
outputs: [
createOutput('result', 'any', {
description: 'Operation result',
label: { en_US: 'Result', zh_Hans: '结果' },
}),
createOutput('success', 'boolean', {
description: 'Whether operation was successful',
label: { en_US: 'Success', zh_Hans: '成功' },
}),
],
configSchema: [
{
id: 'connection_url',
name: 'connection_url',
type: DynamicFormItemType.SECRET,
label: {
en_US: 'Redis URL',
zh_Hans: 'Redis URL',
},
description: {
en_US: 'Redis connection URL (e.g., redis://localhost:6379)',
zh_Hans: 'Redis 连接 URL例如 redis://localhost:6379',
},
required: true,
default: 'redis://localhost:6379',
},
{
id: 'operation',
name: 'operation',
type: DynamicFormItemType.SELECT,
label: {
en_US: 'Operation',
zh_Hans: '操作',
},
description: {
en_US: 'Redis operation to perform',
zh_Hans: '要执行的 Redis 操作',
},
required: true,
default: 'get',
options: [
{ name: 'get', label: { en_US: 'GET', zh_Hans: 'GET' } },
{ name: 'set', label: { en_US: 'SET', zh_Hans: 'SET' } },
{ name: 'delete', label: { en_US: 'DELETE', zh_Hans: 'DELETE' } },
{ name: 'exists', label: { en_US: 'EXISTS', zh_Hans: 'EXISTS' } },
{ name: 'incr', label: { en_US: 'INCR', zh_Hans: 'INCR' } },
{ name: 'decr', label: { en_US: 'DECR', zh_Hans: 'DECR' } },
{ name: 'hget', label: { en_US: 'HGET', zh_Hans: 'HGET' } },
{ name: 'hset', label: { en_US: 'HSET', zh_Hans: 'HSET' } },
{ name: 'lpush', label: { en_US: 'LPUSH', zh_Hans: 'LPUSH' } },
{ name: 'rpush', label: { en_US: 'RPUSH', zh_Hans: 'RPUSH' } },
{ name: 'lpop', label: { en_US: 'LPOP', zh_Hans: 'LPOP' } },
{ name: 'rpop', label: { en_US: 'RPOP', zh_Hans: 'RPOP' } },
],
},
{
id: 'key_template',
name: 'key_template',
type: DynamicFormItemType.STRING,
label: {
en_US: 'Key Template',
zh_Hans: '键模板',
},
description: {
en_US: 'Redis key (supports variable interpolation)',
zh_Hans: 'Redis 键(支持变量插值)',
},
required: false,
default: '',
},
{
id: 'hash_field',
name: 'hash_field',
type: DynamicFormItemType.STRING,
label: {
en_US: 'Hash Field',
zh_Hans: '哈希字段',
},
description: {
en_US: 'Field name for hash operations',
zh_Hans: '哈希操作的字段名',
},
required: false,
default: '',
show_if: {
field: 'operation',
operator: 'in',
value: ['hget', 'hset'],
},
},
{
id: 'ttl',
name: 'ttl',
type: DynamicFormItemType.INT,
label: {
en_US: 'TTL (seconds)',
zh_Hans: 'TTL',
},
description: {
en_US: 'Time to live for SET operations (0 = no expiry)',
zh_Hans: 'SET 操作的过期时间0 = 不过期)',
},
required: false,
default: 0,
show_if: {
field: 'operation',
operator: 'eq',
value: 'set',
},
},
],
defaultConfig: {
connection_url: 'redis://localhost:6379',
operation: 'get',
key_template: '',
hash_field: '',
ttl: 0,
},
};
/**
* MCP Tool Node
* Invokes MCP (Model Context Protocol) tools
*/
export const mcpToolConfig: NodeConfigMeta = {
nodeType: 'mcp_tool',
label: {
en_US: 'MCP Tool',
zh_Hans: 'MCP 工具',
},
description: {
en_US: 'Invoke an MCP (Model Context Protocol) tool',
zh_Hans: '调用 MCP模型上下文协议工具',
},
icon: 'Wrench',
category: 'integration',
color: '#ec4899',
inputs: [
createInput('arguments', 'object', {
description: 'Tool arguments',
label: { en_US: 'Arguments', zh_Hans: '参数' },
required: false,
}),
],
outputs: [
createOutput('result', 'any', {
description: 'Tool execution result',
label: { en_US: 'Result', zh_Hans: '结果' },
}),
createOutput('success', 'boolean', {
description: 'Whether tool call was successful',
label: { en_US: 'Success', zh_Hans: '成功' },
}),
createOutput('error', 'string', {
description: 'Error message if failed',
label: { en_US: 'Error', zh_Hans: '错误' },
}),
],
configSchema: [
{
id: 'server_name',
name: 'server_name',
type: DynamicFormItemType.STRING,
label: {
en_US: 'MCP Server',
zh_Hans: 'MCP 服务器',
},
description: {
en_US: 'Name of the MCP server',
zh_Hans: 'MCP 服务器名称',
},
required: true,
default: '',
},
{
id: 'tool_name',
name: 'tool_name',
type: DynamicFormItemType.STRING,
label: {
en_US: 'Tool Name',
zh_Hans: '工具名称',
},
description: {
en_US: 'Name of the MCP tool to invoke',
zh_Hans: '要调用的 MCP 工具名称',
},
required: true,
default: '',
},
{
id: 'arguments_template',
name: 'arguments_template',
type: DynamicFormItemType.TEXT,
label: {
en_US: 'Arguments Template',
zh_Hans: '参数模板',
},
description: {
en_US:
'Tool arguments as JSON (supports variable interpolation). Leave empty to use input.',
zh_Hans: '工具参数JSON 格式,支持变量插值)。留空则使用输入。',
},
required: false,
default: '',
},
{
id: 'timeout',
name: 'timeout',
type: DynamicFormItemType.INT,
label: {
en_US: 'Timeout (seconds)',
zh_Hans: '超时时间(秒)',
},
description: {
en_US: 'Maximum execution time',
zh_Hans: '最大执行时间',
},
required: false,
default: 30,
},
],
defaultConfig: {
server_name: '',
tool_name: '',
arguments_template: '',
timeout: 30,
},
};
/**
* Memory Store Node
* Store and retrieve from workflow memory
*/
export const memoryStoreConfig: NodeConfigMeta = {
nodeType: 'memory_store',
label: {
en_US: 'Memory Store',
zh_Hans: '记忆存储',
},
description: {
en_US: 'Store and retrieve data from workflow memory',
zh_Hans: '从工作流记忆中存储和检索数据',
},
icon: 'HardDrive',
category: 'integration',
color: '#ec4899',
inputs: [
createInput('value', 'any', {
description: 'Value to store',
label: { en_US: 'Value', zh_Hans: '值' },
required: false,
}),
],
outputs: [
createOutput('result', 'any', {
description: 'Retrieved or stored value',
label: { en_US: 'Result', zh_Hans: '结果' },
}),
createOutput('success', 'boolean', {
description: 'Whether operation was successful',
label: { en_US: 'Success', zh_Hans: '成功' },
}),
],
configSchema: [
{
id: 'operation',
name: 'operation',
type: DynamicFormItemType.SELECT,
label: {
en_US: 'Operation',
zh_Hans: '操作',
},
description: {
en_US: 'Memory operation to perform',
zh_Hans: '要执行的记忆操作',
},
required: true,
default: 'get',
options: [
{ name: 'get', label: { en_US: 'Get', zh_Hans: '获取' } },
{ name: 'set', label: { en_US: 'Set', zh_Hans: '设置' } },
{ name: 'delete', label: { en_US: 'Delete', zh_Hans: '删除' } },
{ name: 'append', label: { en_US: 'Append', zh_Hans: '追加' } },
{ name: 'list', label: { en_US: 'List All', zh_Hans: '列出全部' } },
],
},
{
id: 'key',
name: 'key',
type: DynamicFormItemType.STRING,
label: {
en_US: 'Key',
zh_Hans: '键',
},
description: {
en_US: 'Memory key (supports variable interpolation)',
zh_Hans: '记忆键(支持变量插值)',
},
required: true,
default: '',
show_if: {
field: 'operation',
operator: 'neq',
value: 'list',
},
},
{
id: 'scope',
name: 'scope',
type: DynamicFormItemType.SELECT,
label: {
en_US: 'Scope',
zh_Hans: '作用域',
},
description: {
en_US: 'Scope of the memory storage',
zh_Hans: '记忆存储的作用域',
},
required: true,
default: 'execution',
options: [
{ name: 'execution', label: { en_US: 'Execution', zh_Hans: '执行' } },
{ name: 'workflow', label: { en_US: 'Workflow', zh_Hans: '工作流' } },
{ name: 'session', label: { en_US: 'Session', zh_Hans: '会话' } },
{ name: 'user', label: { en_US: 'User', zh_Hans: '用户' } },
{ name: 'global', label: { en_US: 'Global', zh_Hans: '全局' } },
],
},
{
id: 'ttl',
name: 'ttl',
type: DynamicFormItemType.INT,
label: {
en_US: 'TTL (seconds)',
zh_Hans: 'TTL',
},
description: {
en_US: 'Time to live (0 = no expiry)',
zh_Hans: '过期时间0 = 不过期)',
},
required: false,
default: 0,
show_if: {
field: 'operation',
operator: 'eq',
value: 'set',
},
},
],
defaultConfig: {
operation: 'get',
key: '',
scope: 'execution',
ttl: 0,
},
};
/**
* Dify Workflow Node
* Calls Dify platform workflow
*/
export const difyWorkflowConfig: NodeConfigMeta = {
nodeType: 'dify_workflow',
label: { en_US: 'Dify Workflow', zh_Hans: 'Dify 工作流' },
description: {
en_US: 'Call a Dify platform workflow',
zh_Hans: '调用 Dify 平台工作流',
},
icon: 'Bot',
category: 'integration',
color: '#ec4899',
inputs: [
createInput('input', 'any', {
description: 'Input data',
label: { en_US: 'Input', zh_Hans: '输入' },
required: false,
}),
],
outputs: [
createOutput('result', 'any', {
description: 'Workflow result',
label: { en_US: 'Result', zh_Hans: '结果' },
}),
createOutput('success', 'boolean', {
description: 'Whether call was successful',
label: { en_US: 'Success', zh_Hans: '成功' },
}),
],
configSchema: [
{
id: 'base-url',
name: 'base-url',
type: DynamicFormItemType.STRING,
label: { en_US: 'Base URL', zh_Hans: 'Base URL' },
description: { en_US: 'Dify API base URL', zh_Hans: 'Dify API 基础 URL' },
required: true,
default: '',
},
{
id: 'api-key',
name: 'api-key',
type: DynamicFormItemType.STRING,
label: { en_US: 'API Key', zh_Hans: 'API Key' },
description: { en_US: 'Dify API key', zh_Hans: 'Dify API 密钥' },
required: true,
default: '',
},
{
id: 'app-type',
name: 'app-type',
type: DynamicFormItemType.SELECT,
label: { en_US: 'App Type', zh_Hans: '应用类型' },
description: { en_US: 'Dify application type', zh_Hans: 'Dify 应用类型' },
required: true,
default: 'workflow',
options: [
{ name: 'workflow', label: { en_US: 'Workflow', zh_Hans: '工作流' } },
{ name: 'chatbot', label: { en_US: 'Chatbot', zh_Hans: '聊天机器人' } },
],
},
{
id: 'timeout',
name: 'timeout',
type: DynamicFormItemType.INT,
label: { en_US: 'Timeout (seconds)', zh_Hans: '超时时间(秒)' },
description: { en_US: 'Request timeout', zh_Hans: '请求超时时间' },
required: false,
default: 60,
},
],
defaultConfig: {
'base-url': '',
'api-key': '',
'app-type': 'workflow',
timeout: 60,
},
};
/**
* Dify Knowledge Query Node
*/
export const difyKnowledgeQueryConfig: NodeConfigMeta = {
nodeType: 'dify_knowledge_query',
label: { en_US: 'Dify Knowledge Query', zh_Hans: 'Dify 知识库查询' },
description: {
en_US: 'Query Dify knowledge base',
zh_Hans: '查询 Dify 知识库',
},
icon: 'Search',
category: 'integration',
color: '#ec4899',
inputs: [
createInput('query', 'string', {
description: 'Search query',
label: { en_US: 'Query', zh_Hans: '查询' },
}),
],
outputs: [
createOutput('results', 'array', {
description: 'Search results',
label: { en_US: 'Results', zh_Hans: '结果' },
}),
createOutput('success', 'boolean', {
description: 'Whether query was successful',
label: { en_US: 'Success', zh_Hans: '成功' },
}),
],
configSchema: [
{
id: 'base-url',
name: 'base-url',
type: DynamicFormItemType.STRING,
label: { en_US: 'Base URL', zh_Hans: 'Base URL' },
description: { en_US: 'Dify API base URL', zh_Hans: 'Dify API 基础 URL' },
required: true,
default: '',
},
{
id: 'api-key',
name: 'api-key',
type: DynamicFormItemType.STRING,
label: { en_US: 'API Key', zh_Hans: 'API Key' },
description: { en_US: 'Dify API key', zh_Hans: 'Dify API 密钥' },
required: true,
default: '',
},
{
id: 'dataset_id',
name: 'dataset_id',
type: DynamicFormItemType.STRING,
label: { en_US: 'Dataset ID', zh_Hans: '数据集 ID' },
description: { en_US: 'Dify dataset ID', zh_Hans: 'Dify 数据集 ID' },
required: true,
default: '',
},
{
id: 'top_k',
name: 'top_k',
type: DynamicFormItemType.INT,
label: { en_US: 'Top K', zh_Hans: 'Top K' },
description: {
en_US: 'Number of results to return',
zh_Hans: '返回结果数量',
},
required: false,
default: 5,
},
],
defaultConfig: { 'base-url': '', 'api-key': '', dataset_id: '', top_k: 5 },
};
/**
* N8n Workflow Node
*/
export const n8nWorkflowConfig: NodeConfigMeta = {
nodeType: 'n8n_workflow',
label: { en_US: 'N8n Workflow', zh_Hans: 'n8n 工作流' },
description: {
en_US: 'Call an n8n workflow via webhook',
zh_Hans: '通过 webhook 调用 n8n 工作流',
},
icon: 'Settings',
category: 'integration',
color: '#ec4899',
inputs: [
createInput('input', 'any', {
description: 'Input data',
label: { en_US: 'Input', zh_Hans: '输入' },
required: false,
}),
],
outputs: [
createOutput('result', 'any', {
description: 'Workflow result',
label: { en_US: 'Result', zh_Hans: '结果' },
}),
createOutput('success', 'boolean', {
description: 'Whether call was successful',
label: { en_US: 'Success', zh_Hans: '成功' },
}),
],
configSchema: [
{
id: 'webhook-url',
name: 'webhook-url',
type: DynamicFormItemType.STRING,
label: { en_US: 'Webhook URL', zh_Hans: 'Webhook URL' },
description: { en_US: 'N8n webhook URL', zh_Hans: 'n8n Webhook URL' },
required: true,
default: '',
},
{
id: 'timeout',
name: 'timeout',
type: DynamicFormItemType.INT,
label: { en_US: 'Timeout (seconds)', zh_Hans: '超时时间(秒)' },
description: { en_US: 'Request timeout', zh_Hans: '请求超时时间' },
required: false,
default: 60,
},
],
defaultConfig: { 'webhook-url': '', timeout: 60 },
};
/**
* Langflow Flow Node
*/
export const langflowFlowConfig: NodeConfigMeta = {
nodeType: 'langflow_flow',
label: { en_US: 'Langflow Flow', zh_Hans: 'Langflow 流程' },
description: { en_US: 'Call a Langflow flow', zh_Hans: '调用 Langflow 流程' },
icon: 'Workflow',
category: 'integration',
color: '#ec4899',
inputs: [
createInput('input', 'any', {
description: 'Input data',
label: { en_US: 'Input', zh_Hans: '输入' },
required: false,
}),
],
outputs: [
createOutput('result', 'any', {
description: 'Flow result',
label: { en_US: 'Result', zh_Hans: '结果' },
}),
createOutput('success', 'boolean', {
description: 'Whether call was successful',
label: { en_US: 'Success', zh_Hans: '成功' },
}),
],
configSchema: [
{
id: 'base-url',
name: 'base-url',
type: DynamicFormItemType.STRING,
label: { en_US: 'Base URL', zh_Hans: 'Base URL' },
description: {
en_US: 'Langflow API base URL',
zh_Hans: 'Langflow API 基础 URL',
},
required: true,
default: '',
},
{
id: 'flow-id',
name: 'flow-id',
type: DynamicFormItemType.STRING,
label: { en_US: 'Flow ID', zh_Hans: '流程 ID' },
description: { en_US: 'Langflow flow ID', zh_Hans: 'Langflow 流程 ID' },
required: true,
default: '',
},
{
id: 'api-key',
name: 'api-key',
type: DynamicFormItemType.STRING,
label: { en_US: 'API Key', zh_Hans: 'API Key' },
description: {
en_US: 'Langflow API key (optional)',
zh_Hans: 'Langflow API 密钥(可选)',
},
required: false,
default: '',
},
{
id: 'timeout',
name: 'timeout',
type: DynamicFormItemType.INT,
label: { en_US: 'Timeout (seconds)', zh_Hans: '超时时间(秒)' },
description: { en_US: 'Request timeout', zh_Hans: '请求超时时间' },
required: false,
default: 60,
},
],
defaultConfig: { 'base-url': '', 'flow-id': '', 'api-key': '', timeout: 60 },
};
/**
* Coze Bot Node
*/
export const cozeBotConfig: NodeConfigMeta = {
nodeType: 'coze_bot',
label: { en_US: 'Coze Bot', zh_Hans: 'Coze Bot' },
description: { en_US: 'Call a Coze Bot', zh_Hans: '调用扣子 Bot' },
icon: 'Bot',
category: 'integration',
color: '#ec4899',
inputs: [
createInput('message', 'string', {
description: 'Message to send',
label: { en_US: 'Message', zh_Hans: '消息' },
}),
],
outputs: [
createOutput('result', 'any', {
description: 'Bot response',
label: { en_US: 'Result', zh_Hans: '结果' },
}),
createOutput('success', 'boolean', {
description: 'Whether call was successful',
label: { en_US: 'Success', zh_Hans: '成功' },
}),
],
configSchema: [
{
id: 'api-base',
name: 'api-base',
type: DynamicFormItemType.STRING,
label: { en_US: 'API Base URL', zh_Hans: 'API 基础 URL' },
description: { en_US: 'Coze API base URL', zh_Hans: 'Coze API 基础 URL' },
required: true,
default: 'https://api.coze.com',
},
{
id: 'bot-id',
name: 'bot-id',
type: DynamicFormItemType.STRING,
label: { en_US: 'Bot ID', zh_Hans: 'Bot ID' },
description: { en_US: 'Coze Bot ID', zh_Hans: 'Coze Bot ID' },
required: true,
default: '',
},
{
id: 'api-key',
name: 'api-key',
type: DynamicFormItemType.STRING,
label: { en_US: 'API Key', zh_Hans: 'API Key' },
description: { en_US: 'Coze API key', zh_Hans: 'Coze API 密钥' },
required: true,
default: '',
},
{
id: 'timeout',
name: 'timeout',
type: DynamicFormItemType.INT,
label: { en_US: 'Timeout (seconds)', zh_Hans: '超时时间(秒)' },
description: { en_US: 'Request timeout', zh_Hans: '请求超时时间' },
required: false,
default: 60,
},
],
defaultConfig: {
'api-base': 'https://api.coze.com',
'bot-id': '',
'api-key': '',
timeout: 60,
},
};
/**
* All integration node configurations
*/
export const integrationConfigs: NodeConfigMeta[] = [
difyWorkflowConfig,
difyKnowledgeQueryConfig,
n8nWorkflowConfig,
langflowFlowConfig,
cozeBotConfig,
databaseQueryConfig,
redisOperationConfig,
mcpToolConfig,
memoryStoreConfig,
];
/**
* Get integration config by type
*/
export function getIntegrationConfig(
nodeType: string,
): NodeConfigMeta | undefined {
return integrationConfigs.find((config) => config.nodeType === nodeType);
}

View File

@@ -1,833 +0,0 @@
/**
* Process Node Configurations
*
* Defines configurations for general processing node types:
* - text_template: Generate text using templates
* - json_transform: Transform JSON data
* - code_executor: Execute custom code
* - data_aggregator: Aggregate data from multiple sources
* - text_splitter: Split text into chunks
*/
import { DynamicFormItemType } from '@/app/infra/entities/form/dynamic';
import { NodeConfigMeta, createInput, createOutput } from './types';
/**
* Text Template Node
* Generates text using variable interpolation
*/
export const textTemplateConfig: NodeConfigMeta = {
nodeType: 'text_template',
label: {
en_US: 'Text Template',
zh_Hans: '文本模板',
},
description: {
en_US: 'Generate text using templates with variable interpolation',
zh_Hans: '使用带有变量插值的模板生成文本',
},
icon: 'FileText',
category: 'process',
color: '#3b82f6',
inputs: [
createInput('variables', 'object', {
description: 'Variables to use in the template',
label: { en_US: 'Variables', zh_Hans: '变量' },
required: false,
}),
],
outputs: [
createOutput('text', 'string', {
description: 'Generated text',
label: { en_US: 'Text', zh_Hans: '文本' },
}),
],
configSchema: [
{
id: 'template',
name: 'template',
type: DynamicFormItemType.TEXT,
label: {
en_US: 'Template',
zh_Hans: '模板',
},
description: {
en_US:
'Text template with variable placeholders (e.g., {{variable_name}})',
zh_Hans: '带有变量占位符的文本模板(例如 {{variable_name}}',
},
required: true,
default: '',
},
{
id: 'escape_html',
name: 'escape_html',
type: DynamicFormItemType.BOOLEAN,
label: {
en_US: 'Escape HTML',
zh_Hans: '转义 HTML',
},
description: {
en_US: 'Escape HTML characters in variable values',
zh_Hans: '转义变量值中的 HTML 字符',
},
required: false,
default: false,
},
{
id: 'trim_whitespace',
name: 'trim_whitespace',
type: DynamicFormItemType.BOOLEAN,
label: {
en_US: 'Trim Whitespace',
zh_Hans: '去除空白',
},
description: {
en_US: 'Remove leading and trailing whitespace from output',
zh_Hans: '去除输出的前后空白',
},
required: false,
default: true,
},
],
defaultConfig: {
template: '',
escape_html: false,
trim_whitespace: true,
},
};
/**
* JSON Transform Node
* Transforms JSON data using JSONPath or JMESPath expressions
*/
export const jsonTransformConfig: NodeConfigMeta = {
nodeType: 'json_transform',
label: {
en_US: 'JSON Transform',
zh_Hans: 'JSON 转换',
},
description: {
en_US: 'Transform JSON data using expressions or mappings',
zh_Hans: '使用表达式或映射转换 JSON 数据',
},
icon: 'Braces',
category: 'process',
color: '#3b82f6',
inputs: [
createInput('input', 'object', {
description: 'JSON data to transform',
label: { en_US: 'Input', zh_Hans: '输入' },
}),
],
outputs: [
createOutput('output', 'any', {
description: 'Transformed data',
label: { en_US: 'Output', zh_Hans: '输出' },
}),
],
configSchema: [
{
id: 'transform_type',
name: 'transform_type',
type: DynamicFormItemType.SELECT,
label: {
en_US: 'Transform Type',
zh_Hans: '转换类型',
},
description: {
en_US: 'Method of transformation',
zh_Hans: '转换方法',
},
required: true,
default: 'jmespath',
options: [
{
name: 'jmespath',
label: { en_US: 'JMESPath Expression', zh_Hans: 'JMESPath 表达式' },
},
{
name: 'jsonpath',
label: { en_US: 'JSONPath Expression', zh_Hans: 'JSONPath 表达式' },
},
{
name: 'mapping',
label: { en_US: 'Field Mapping', zh_Hans: '字段映射' },
},
],
},
{
id: 'expression',
name: 'expression',
type: DynamicFormItemType.STRING,
label: {
en_US: 'Expression',
zh_Hans: '表达式',
},
description: {
en_US: 'JMESPath or JSONPath expression',
zh_Hans: 'JMESPath 或 JSONPath 表达式',
},
required: true,
default: '',
show_if: {
field: 'transform_type',
operator: 'in',
value: ['jmespath', 'jsonpath'],
},
},
{
id: 'mapping',
name: 'mapping',
type: DynamicFormItemType.TEXT,
label: {
en_US: 'Field Mapping',
zh_Hans: '字段映射',
},
description: {
en_US:
'JSON object defining field mappings: {"output_field": "input.path.to.field"}',
zh_Hans:
'定义字段映射的 JSON 对象: {"output_field": "input.path.to.field"}',
},
required: true,
default: '{}',
show_if: {
field: 'transform_type',
operator: 'eq',
value: 'mapping',
},
},
],
defaultConfig: {
transform_type: 'jmespath',
expression: '',
mapping: '{}',
},
};
/**
* Code Executor Node
* Executes custom code (JavaScript/Python)
*/
export const codeExecutorConfig: NodeConfigMeta = {
nodeType: 'code_executor',
label: {
en_US: 'Code Executor',
zh_Hans: '代码执行',
},
description: {
en_US: 'Execute custom code to process data',
zh_Hans: '执行自定义代码处理数据',
},
icon: 'Code',
category: 'process',
color: '#3b82f6',
inputs: [
createInput('input', 'any', {
description: 'Input data for the code',
label: { en_US: 'Input', zh_Hans: '输入' },
}),
],
outputs: [
createOutput('output', 'any', {
description: 'Code execution result',
label: { en_US: 'Output', zh_Hans: '输出' },
}),
createOutput('logs', 'array', {
description: 'Console logs from code execution',
label: { en_US: 'Logs', zh_Hans: '日志' },
}),
],
configSchema: [
{
id: 'language',
name: 'language',
type: DynamicFormItemType.SELECT,
label: {
en_US: 'Language',
zh_Hans: '语言',
},
description: {
en_US: 'Programming language to use',
zh_Hans: '要使用的编程语言',
},
required: true,
default: 'javascript',
options: [
{
name: 'javascript',
label: { en_US: 'JavaScript', zh_Hans: 'JavaScript' },
},
{ name: 'python', label: { en_US: 'Python', zh_Hans: 'Python' } },
],
},
{
id: 'code',
name: 'code',
type: DynamicFormItemType.TEXT,
label: {
en_US: 'Code',
zh_Hans: '代码',
},
description: {
en_US:
'Code to execute. Use `input` to access input data and return the result.',
zh_Hans: '要执行的代码。使用 `input` 访问输入数据,并返回结果。',
},
required: true,
default:
'// Access input with: input\n// Return result with: return result;\n\nreturn input;',
},
{
id: 'timeout',
name: 'timeout',
type: DynamicFormItemType.INT,
label: {
en_US: 'Timeout (ms)',
zh_Hans: '超时时间 (毫秒)',
},
description: {
en_US: 'Maximum execution time in milliseconds',
zh_Hans: '最大执行时间(毫秒)',
},
required: false,
default: 5000,
},
],
defaultConfig: {
language: 'javascript',
code: '// Access input with: input\n// Return result with: return result;\n\nreturn input;',
timeout: 5000,
},
};
/**
* Data Aggregator Node
* Aggregates data from multiple inputs
*/
export const dataAggregatorConfig: NodeConfigMeta = {
nodeType: 'data_aggregator',
label: {
en_US: 'Data Aggregator',
zh_Hans: '数据聚合',
},
description: {
en_US: 'Aggregate and combine data from multiple sources',
zh_Hans: '聚合和组合来自多个来源的数据',
},
icon: 'Layers',
category: 'process',
color: '#3b82f6',
inputs: [
createInput('items', 'array', {
description: 'Array of items to aggregate',
label: { en_US: 'Items', zh_Hans: '项目' },
}),
],
outputs: [
createOutput('result', 'any', {
description: 'Aggregated result',
label: { en_US: 'Result', zh_Hans: '结果' },
}),
createOutput('count', 'number', {
description: 'Number of items aggregated',
label: { en_US: 'Count', zh_Hans: '数量' },
}),
],
configSchema: [
{
id: 'aggregation_type',
name: 'aggregation_type',
type: DynamicFormItemType.SELECT,
label: {
en_US: 'Aggregation Type',
zh_Hans: '聚合类型',
},
description: {
en_US: 'How to aggregate the data',
zh_Hans: '如何聚合数据',
},
required: true,
default: 'array',
options: [
{
name: 'array',
label: { en_US: 'Collect to Array', zh_Hans: '收集为数组' },
},
{
name: 'concat',
label: { en_US: 'Concatenate Strings', zh_Hans: '连接字符串' },
},
{ name: 'sum', label: { en_US: 'Sum Numbers', zh_Hans: '求和' } },
{
name: 'average',
label: { en_US: 'Average Numbers', zh_Hans: '求平均' },
},
{ name: 'min', label: { en_US: 'Minimum', zh_Hans: '最小值' } },
{ name: 'max', label: { en_US: 'Maximum', zh_Hans: '最大值' } },
{
name: 'merge',
label: { en_US: 'Merge Objects', zh_Hans: '合并对象' },
},
{ name: 'first', label: { en_US: 'First Item', zh_Hans: '第一项' } },
{ name: 'last', label: { en_US: 'Last Item', zh_Hans: '最后一项' } },
],
},
{
id: 'separator',
name: 'separator',
type: DynamicFormItemType.STRING,
label: {
en_US: 'Separator',
zh_Hans: '分隔符',
},
description: {
en_US: 'Separator for string concatenation',
zh_Hans: '字符串连接的分隔符',
},
required: false,
default: '\n',
show_if: {
field: 'aggregation_type',
operator: 'eq',
value: 'concat',
},
},
{
id: 'field_path',
name: 'field_path',
type: DynamicFormItemType.STRING,
label: {
en_US: 'Field Path',
zh_Hans: '字段路径',
},
description: {
en_US: 'Path to the field to aggregate (for objects)',
zh_Hans: '要聚合的字段路径(用于对象)',
},
required: false,
default: '',
},
],
defaultConfig: {
aggregation_type: 'array',
separator: '\n',
field_path: '',
},
};
/**
* Text Splitter Node
* Splits text into chunks
*/
export const textSplitterConfig: NodeConfigMeta = {
nodeType: 'text_splitter',
label: {
en_US: 'Text Splitter',
zh_Hans: '文本分割',
},
description: {
en_US: 'Split text into smaller chunks',
zh_Hans: '将文本分割成较小的块',
},
icon: 'Scissors',
category: 'process',
color: '#3b82f6',
inputs: [
createInput('text', 'string', {
description: 'Text to split',
label: { en_US: 'Text', zh_Hans: '文本' },
}),
],
outputs: [
createOutput('chunks', 'array', {
description: 'Array of text chunks',
label: { en_US: 'Chunks', zh_Hans: '块' },
}),
createOutput('count', 'number', {
description: 'Number of chunks',
label: { en_US: 'Count', zh_Hans: '数量' },
}),
],
configSchema: [
{
id: 'split_type',
name: 'split_type',
type: DynamicFormItemType.SELECT,
label: {
en_US: 'Split Type',
zh_Hans: '分割类型',
},
description: {
en_US: 'How to split the text',
zh_Hans: '如何分割文本',
},
required: true,
default: 'separator',
options: [
{
name: 'separator',
label: { en_US: 'By Separator', zh_Hans: '按分隔符' },
},
{ name: 'length', label: { en_US: 'By Length', zh_Hans: '按长度' } },
{
name: 'sentences',
label: { en_US: 'By Sentences', zh_Hans: '按句子' },
},
{
name: 'paragraphs',
label: { en_US: 'By Paragraphs', zh_Hans: '按段落' },
},
{
name: 'regex',
label: { en_US: 'By Regex', zh_Hans: '按正则表达式' },
},
],
},
{
id: 'separator',
name: 'separator',
type: DynamicFormItemType.STRING,
label: {
en_US: 'Separator',
zh_Hans: '分隔符',
},
description: {
en_US: 'String to split on',
zh_Hans: '用于分割的字符串',
},
required: false,
default: '\n',
show_if: {
field: 'split_type',
operator: 'eq',
value: 'separator',
},
},
{
id: 'chunk_size',
name: 'chunk_size',
type: DynamicFormItemType.INT,
label: {
en_US: 'Chunk Size',
zh_Hans: '块大小',
},
description: {
en_US: 'Maximum characters per chunk',
zh_Hans: '每块的最大字符数',
},
required: false,
default: 1000,
show_if: {
field: 'split_type',
operator: 'eq',
value: 'length',
},
},
{
id: 'chunk_overlap',
name: 'chunk_overlap',
type: DynamicFormItemType.INT,
label: {
en_US: 'Chunk Overlap',
zh_Hans: '块重叠',
},
description: {
en_US: 'Number of characters to overlap between chunks',
zh_Hans: '块之间重叠的字符数',
},
required: false,
default: 100,
show_if: {
field: 'split_type',
operator: 'eq',
value: 'length',
},
},
{
id: 'regex_pattern',
name: 'regex_pattern',
type: DynamicFormItemType.STRING,
label: {
en_US: 'Regex Pattern',
zh_Hans: '正则表达式模式',
},
description: {
en_US: 'Regular expression pattern to split on',
zh_Hans: '用于分割的正则表达式模式',
},
required: false,
default: '',
show_if: {
field: 'split_type',
operator: 'eq',
value: 'regex',
},
},
{
id: 'remove_empty',
name: 'remove_empty',
type: DynamicFormItemType.BOOLEAN,
label: {
en_US: 'Remove Empty',
zh_Hans: '移除空块',
},
description: {
en_US: 'Remove empty chunks from result',
zh_Hans: '从结果中移除空块',
},
required: false,
default: true,
},
],
defaultConfig: {
split_type: 'separator',
separator: '\n',
chunk_size: 1000,
chunk_overlap: 100,
regex_pattern: '',
remove_empty: true,
},
};
/**
* Variable Assignment Node
* Assigns values to workflow variables
*/
export const variableAssignmentConfig: NodeConfigMeta = {
nodeType: 'variable_assignment',
label: {
en_US: 'Variable Assignment',
zh_Hans: '变量赋值',
},
description: {
en_US: 'Assign values to workflow variables',
zh_Hans: '为工作流变量赋值',
},
icon: 'Variable',
category: 'process',
color: '#3b82f6',
inputs: [
createInput('value', 'any', {
description: 'Value to assign',
label: { en_US: 'Value', zh_Hans: '值' },
required: false,
}),
],
outputs: [
createOutput('output', 'any', {
description: 'The assigned value',
label: { en_US: 'Output', zh_Hans: '输出' },
}),
],
configSchema: [
{
id: 'variable_name',
name: 'variable_name',
type: DynamicFormItemType.STRING,
label: {
en_US: 'Variable Name',
zh_Hans: '变量名',
},
description: {
en_US: 'Name of the variable to assign',
zh_Hans: '要赋值的变量名',
},
required: true,
default: '',
},
{
id: 'value_type',
name: 'value_type',
type: DynamicFormItemType.SELECT,
label: {
en_US: 'Value Type',
zh_Hans: '值类型',
},
description: {
en_US: 'Type of value to assign',
zh_Hans: '要赋的值类型',
},
required: true,
default: 'input',
options: [
{ name: 'input', label: { en_US: 'From Input', zh_Hans: '来自输入' } },
{ name: 'static', label: { en_US: 'Static Value', zh_Hans: '静态值' } },
{
name: 'expression',
label: { en_US: 'Expression', zh_Hans: '表达式' },
},
],
},
{
id: 'static_value',
name: 'static_value',
type: DynamicFormItemType.TEXT,
label: {
en_US: 'Static Value',
zh_Hans: '静态值',
},
description: {
en_US: 'Value to assign (as JSON)',
zh_Hans: '要赋的值JSON 格式)',
},
required: false,
default: '',
show_if: {
field: 'value_type',
operator: 'eq',
value: 'static',
},
},
{
id: 'expression',
name: 'expression',
type: DynamicFormItemType.STRING,
label: {
en_US: 'Expression',
zh_Hans: '表达式',
},
description: {
en_US: 'Expression to evaluate (e.g., {{input}} + 1)',
zh_Hans: '要计算的表达式(例如 {{input}} + 1',
},
required: false,
default: '',
show_if: {
field: 'value_type',
operator: 'eq',
value: 'expression',
},
},
],
defaultConfig: {
variable_name: '',
value_type: 'input',
static_value: '',
expression: '',
},
};
/**
* Data Transform Node
* Transform and extract data using templates or JSONPath
*/
export const dataTransformConfig: NodeConfigMeta = {
nodeType: 'data_transform',
label: {
en_US: 'Data Transform',
zh_Hans: '数据转换',
},
description: {
en_US: 'Transform and extract data using templates or JSONPath',
zh_Hans: '使用模板或 JSONPath 转换和提取数据',
},
icon: 'RefreshCw',
category: 'process',
color: '#3b82f6',
inputs: [
createInput('data', 'any', {
description: 'Input data',
label: { en_US: 'Data', zh_Hans: '数据' },
required: true,
}),
],
outputs: [
createOutput('result', 'any', {
description: 'Transform result',
label: { en_US: 'Result', zh_Hans: '结果' },
}),
],
configSchema: [
{
id: 'transform_type',
name: 'transform_type',
type: DynamicFormItemType.SELECT,
label: {
en_US: 'Transform Type',
zh_Hans: '转换类型',
},
description: {
en_US: 'Type of transformation to perform',
zh_Hans: '要执行的转换类型',
},
required: true,
default: 'template',
options: [
{ name: 'template', label: { en_US: 'Template', zh_Hans: '模板' } },
{ name: 'jsonpath', label: { en_US: 'JSONPath', zh_Hans: 'JSONPath' } },
{ name: 'jmespath', label: { en_US: 'JMESPath', zh_Hans: 'JMESPath' } },
{
name: 'expression',
label: { en_US: 'Expression', zh_Hans: '表达式' },
},
],
},
{
id: 'template',
name: 'template',
type: DynamicFormItemType.TEXT,
label: {
en_US: 'Template',
zh_Hans: '模板',
},
description: {
en_US: 'Template with {{variable}} syntax',
zh_Hans: '支持 {{variable}} 语法的模板',
},
required: false,
default: '',
show_if: {
field: 'transform_type',
operator: 'eq',
value: 'template',
},
},
{
id: 'expression',
name: 'expression',
type: DynamicFormItemType.STRING,
label: {
en_US: 'Expression',
zh_Hans: '表达式',
},
description: {
en_US: 'JSONPath/JMESPath expression',
zh_Hans: 'JSONPath/JMESPath 表达式',
},
required: false,
default: '',
show_if: {
field: 'transform_type',
operator: 'in',
value: ['jsonpath', 'jmespath', 'expression'],
},
},
],
defaultConfig: {
transform_type: 'template',
template: '',
expression: '',
},
};
/**
* All process node configurations
*/
export const processConfigs: NodeConfigMeta[] = [
textTemplateConfig,
jsonTransformConfig,
codeExecutorConfig,
dataAggregatorConfig,
textSplitterConfig,
variableAssignmentConfig,
dataTransformConfig,
];
/**
* Get process config by type
*/
export function getProcessConfig(nodeType: string): NodeConfigMeta | undefined {
return processConfigs.find((config) => config.nodeType === nodeType);
}

View File

@@ -1,542 +0,0 @@
/**
* Trigger Node Configurations
*
* Defines configurations for all trigger node types:
* - message_trigger: Triggered by incoming messages
* - cron_trigger: Triggered by scheduled time
* - webhook_trigger: Triggered by HTTP webhook calls
* - event_trigger: Triggered by system events
*/
import { DynamicFormItemType } from '@/app/infra/entities/form/dynamic';
import { NodeConfigMeta, createOutput } from './types';
/**
* Message Trigger Node
* Triggers workflow when a message matches specified conditions
*/
export const messageTriggerConfig: NodeConfigMeta = {
nodeType: 'message_trigger',
label: {
en_US: 'Message Trigger',
zh_Hans: '消息触发',
},
description: {
en_US: 'Trigger workflow when a message matches the specified conditions',
zh_Hans: '当收到匹配指定条件的消息时触发工作流',
},
icon: 'MessageSquare',
category: 'trigger',
color: '#f59e0b',
isEntryPoint: true,
maxInstances: 1,
inputs: [],
outputs: [
createOutput('message', 'object', {
description: 'The received message object',
label: { en_US: 'Message', zh_Hans: '消息' },
}),
createOutput('sender', 'object', {
description: 'Message sender information',
label: { en_US: 'Sender', zh_Hans: '发送者' },
}),
createOutput('context', 'object', {
description: 'Message context information',
label: { en_US: 'Context', zh_Hans: '上下文' },
}),
],
configSchema: [
{
id: 'match_type',
name: 'match_type',
type: DynamicFormItemType.SELECT,
label: {
en_US: 'Match Type',
zh_Hans: '匹配类型',
},
description: {
en_US: 'How to match the incoming message',
zh_Hans: '如何匹配收到的消息',
},
required: true,
default: 'all',
options: [
{ name: 'all', label: { en_US: 'All Messages', zh_Hans: '所有消息' } },
{
name: 'prefix',
label: { en_US: 'Prefix Match', zh_Hans: '前缀匹配' },
},
{ name: 'regex', label: { en_US: 'Regex Match', zh_Hans: '正则匹配' } },
{
name: 'contains',
label: { en_US: 'Contains Keyword', zh_Hans: '包含关键词' },
},
{ name: 'exact', label: { en_US: 'Exact Match', zh_Hans: '精确匹配' } },
],
},
{
id: 'match_pattern',
name: 'match_pattern',
type: DynamicFormItemType.STRING,
label: {
en_US: 'Match Pattern',
zh_Hans: '匹配模式',
},
description: {
en_US:
'The pattern to match against the message (prefix, regex, keyword, or exact text)',
zh_Hans: '用于匹配消息的模式(前缀、正则表达式、关键词或精确文本)',
},
required: false,
default: '',
show_if: {
field: 'match_type',
operator: 'neq',
value: 'all',
},
},
{
id: 'ignore_bot_messages',
name: 'ignore_bot_messages',
type: DynamicFormItemType.BOOLEAN,
label: {
en_US: 'Ignore Bot Messages',
zh_Hans: '忽略机器人消息',
},
description: {
en_US: 'Do not trigger for messages sent by bots',
zh_Hans: '不对机器人发送的消息触发',
},
required: false,
default: true,
},
],
defaultConfig: {
match_type: 'all',
match_pattern: '',
ignore_bot_messages: true,
},
};
/**
* Cron Trigger Node
* Triggers workflow on a schedule
*/
export const cronTriggerConfig: NodeConfigMeta = {
nodeType: 'cron_trigger',
label: {
en_US: 'Scheduled Trigger',
zh_Hans: '定时触发',
},
description: {
en_US: 'Trigger workflow on a scheduled time using cron expression',
zh_Hans: '使用 Cron 表达式按计划时间触发工作流',
},
icon: 'Clock',
category: 'trigger',
color: '#f59e0b',
isEntryPoint: true,
inputs: [],
outputs: [
createOutput('trigger_time', 'datetime', {
description: 'The time when the trigger fired',
label: { en_US: 'Trigger Time', zh_Hans: '触发时间' },
}),
createOutput('context', 'object', {
description: 'Trigger context information',
label: { en_US: 'Context', zh_Hans: '上下文' },
}),
],
configSchema: [
{
id: 'cron_expression',
name: 'cron_expression',
type: DynamicFormItemType.STRING,
label: {
en_US: 'Cron Expression',
zh_Hans: 'Cron 表达式',
},
description: {
en_US: 'Standard cron expression (e.g., "0 9 * * *" for 9 AM daily)',
zh_Hans: '标准 Cron 表达式(例如 "0 9 * * *" 表示每天上午 9 点)',
},
required: true,
default: '0 9 * * *',
},
{
id: 'timezone',
name: 'timezone',
type: DynamicFormItemType.SELECT,
label: {
en_US: 'Timezone',
zh_Hans: '时区',
},
description: {
en_US: 'Timezone for the cron schedule',
zh_Hans: 'Cron 计划的时区',
},
required: true,
default: 'Asia/Shanghai',
options: [
{ name: 'UTC', label: { en_US: 'UTC', zh_Hans: 'UTC' } },
{
name: 'Asia/Shanghai',
label: {
en_US: 'Asia/Shanghai (UTC+8)',
zh_Hans: '亚洲/上海 (UTC+8)',
},
},
{
name: 'Asia/Tokyo',
label: { en_US: 'Asia/Tokyo (UTC+9)', zh_Hans: '亚洲/东京 (UTC+9)' },
},
{
name: 'America/New_York',
label: {
en_US: 'America/New_York (EST)',
zh_Hans: '美国/纽约 (EST)',
},
},
{
name: 'America/Los_Angeles',
label: {
en_US: 'America/Los_Angeles (PST)',
zh_Hans: '美国/洛杉矶 (PST)',
},
},
{
name: 'Europe/London',
label: { en_US: 'Europe/London (GMT)', zh_Hans: '欧洲/伦敦 (GMT)' },
},
{
name: 'Europe/Berlin',
label: { en_US: 'Europe/Berlin (CET)', zh_Hans: '欧洲/柏林 (CET)' },
},
],
},
{
id: 'description',
name: 'description',
type: DynamicFormItemType.STRING,
label: {
en_US: 'Description',
zh_Hans: '描述',
},
description: {
en_US: 'Optional description for this scheduled trigger',
zh_Hans: '此定时触发器的可选描述',
},
required: false,
default: '',
},
{
id: 'enabled',
name: 'enabled',
type: DynamicFormItemType.BOOLEAN,
label: {
en_US: 'Enabled',
zh_Hans: '启用',
},
description: {
en_US: 'Whether this scheduled trigger is active',
zh_Hans: '此定时触发器是否激活',
},
required: false,
default: true,
},
],
defaultConfig: {
cron_expression: '0 9 * * *',
timezone: 'Asia/Shanghai',
description: '',
enabled: true,
},
};
/**
* Webhook Trigger Node
* Triggers workflow via HTTP webhook
*/
export const webhookTriggerConfig: NodeConfigMeta = {
nodeType: 'webhook_trigger',
label: {
en_US: 'Webhook Trigger',
zh_Hans: 'Webhook 触发',
},
description: {
en_US:
'Trigger workflow when an HTTP request is received at the webhook URL',
zh_Hans: '当在 Webhook URL 收到 HTTP 请求时触发工作流',
},
icon: 'Webhook',
category: 'trigger',
color: '#f59e0b',
isEntryPoint: true,
inputs: [],
outputs: [
createOutput('body', 'object', {
description: 'Request body data',
label: { en_US: 'Body', zh_Hans: '请求体' },
}),
createOutput('headers', 'object', {
description: 'Request headers',
label: { en_US: 'Headers', zh_Hans: '请求头' },
}),
createOutput('query', 'object', {
description: 'Query parameters',
label: { en_US: 'Query', zh_Hans: '查询参数' },
}),
createOutput('method', 'string', {
description: 'HTTP method',
label: { en_US: 'Method', zh_Hans: 'HTTP 方法' },
}),
],
configSchema: [
{
id: 'webhook_path',
name: 'webhook_path',
type: DynamicFormItemType.STRING,
label: {
en_US: 'Webhook Path',
zh_Hans: 'Webhook 路径',
},
description: {
en_US: 'Unique path for this webhook (e.g., "my-workflow")',
zh_Hans: '此 Webhook 的唯一路径(例如 "my-workflow"',
},
required: true,
default: '',
},
{
id: 'auth_type',
name: 'auth_type',
type: DynamicFormItemType.SELECT,
label: {
en_US: 'Authentication',
zh_Hans: '认证方式',
},
description: {
en_US: 'How to authenticate incoming webhook requests',
zh_Hans: '如何验证传入的 Webhook 请求',
},
required: true,
default: 'none',
options: [
{ name: 'none', label: { en_US: 'None', zh_Hans: '无' } },
{
name: 'token',
label: { en_US: 'Bearer Token', zh_Hans: 'Bearer 令牌' },
},
{
name: 'signature',
label: { en_US: 'Signature', zh_Hans: '签名验证' },
},
{ name: 'basic', label: { en_US: 'Basic Auth', zh_Hans: '基本认证' } },
],
},
{
id: 'auth_token',
name: 'auth_token',
type: DynamicFormItemType.SECRET,
label: {
en_US: 'Auth Token',
zh_Hans: '认证令牌',
},
description: {
en_US: 'Token or secret for authentication',
zh_Hans: '用于认证的令牌或密钥',
},
required: true,
default: '',
show_if: {
field: 'auth_type',
operator: 'in',
value: ['token', 'signature', 'basic'],
},
},
{
id: 'content_type',
name: 'content_type',
type: DynamicFormItemType.SELECT,
label: {
en_US: 'Content Type',
zh_Hans: '内容类型',
},
description: {
en_US: 'Expected Content-Type of the request',
zh_Hans: '请求预期的 Content-Type',
},
required: false,
default: 'application/json',
options: [
{
name: 'application/json',
label: { en_US: 'application/json', zh_Hans: 'JSON' },
},
{
name: 'application/x-www-form-urlencoded',
label: {
en_US: 'application/x-www-form-urlencoded',
zh_Hans: '表单编码',
},
},
{
name: 'multipart/form-data',
label: { en_US: 'multipart/form-data', zh_Hans: '表单数据' },
},
{
name: 'text/plain',
label: { en_US: 'text/plain', zh_Hans: '纯文本' },
},
],
},
{
id: 'validation',
name: 'validation',
type: DynamicFormItemType.TEXT,
label: {
en_US: 'Validation Rules',
zh_Hans: '验证规则',
},
description: {
en_US: 'JSON validation rules for request body (optional)',
zh_Hans: '请求体的 JSON 验证规则(可选)',
},
required: false,
default: '{}',
},
{
id: 'timeout',
name: 'timeout',
type: DynamicFormItemType.INT,
label: {
en_US: 'Timeout (seconds)',
zh_Hans: '超时时间(秒)',
},
description: {
en_US: 'Request timeout in seconds',
zh_Hans: '请求超时时间(秒)',
},
required: false,
default: 30,
},
],
defaultConfig: {
webhook_path: '',
auth_type: 'none',
auth_token: '',
content_type: 'application/json',
validation: '{}',
timeout: 30,
},
};
/**
* Event Trigger Node
* Triggers workflow on system events
*/
export const eventTriggerConfig: NodeConfigMeta = {
nodeType: 'event_trigger',
label: {
en_US: 'Event Trigger',
zh_Hans: '事件触发',
},
description: {
en_US: 'Trigger workflow when a system event occurs',
zh_Hans: '当系统事件发生时触发工作流',
},
icon: 'Zap',
category: 'trigger',
color: '#f59e0b',
isEntryPoint: true,
inputs: [],
outputs: [
createOutput('event', 'object', {
description: 'The event data',
label: { en_US: 'Event', zh_Hans: '事件' },
}),
createOutput('event_type', 'string', {
description: 'Type of the event',
label: { en_US: 'Event Type', zh_Hans: '事件类型' },
}),
createOutput('context', 'object', {
description: 'Event context information',
label: { en_US: 'Context', zh_Hans: '上下文' },
}),
],
configSchema: [
{
id: 'event_type',
name: 'event_type',
type: DynamicFormItemType.SELECT,
label: {
en_US: 'Event Type',
zh_Hans: '事件类型',
},
description: {
en_US: 'The type of system event to listen for',
zh_Hans: '要监听的系统事件类型',
},
required: true,
default: 'member_join',
options: [
{
name: 'member_join',
label: { en_US: 'Member Join', zh_Hans: '成员加入' },
},
{
name: 'member_leave',
label: { en_US: 'Member Leave', zh_Hans: '成员离开' },
},
{
name: 'message_recall',
label: { en_US: 'Message Recall', zh_Hans: '消息撤回' },
},
{
name: 'group_created',
label: { en_US: 'Group Created', zh_Hans: '群组创建' },
},
{
name: 'group_disbanded',
label: { en_US: 'Group Disbanded', zh_Hans: '群组解散' },
},
{
name: 'bot_added',
label: { en_US: 'Bot Added to Group', zh_Hans: '机器人被添加到群' },
},
{
name: 'bot_removed',
label: { en_US: 'Bot Removed from Group', zh_Hans: '机器人被移出群' },
},
{
name: 'friend_request',
label: { en_US: 'Friend Request', zh_Hans: '好友请求' },
},
{
name: 'group_request',
label: { en_US: 'Group Join Request', zh_Hans: '入群请求' },
},
],
},
],
defaultConfig: {
event_type: 'member_join',
},
};
/**
* All trigger node configurations
*/
export const triggerConfigs: NodeConfigMeta[] = [
messageTriggerConfig,
cronTriggerConfig,
webhookTriggerConfig,
eventTriggerConfig,
];
/**
* Get trigger config by type
*/
export function getTriggerConfig(nodeType: string): NodeConfigMeta | undefined {
return triggerConfigs.find((config) => config.nodeType === nodeType);
}

View File

@@ -1,120 +0,0 @@
/**
* Workflow Node Configuration Types
*
* This module defines the types used for node configuration metadata.
* It extends the existing dynamic form system to support workflow-specific features.
*/
import { IDynamicFormItemSchema } from '@/app/infra/entities/form/dynamic';
import { I18nObject } from '@/app/infra/entities/common';
import { NodeCategory, PortDefinition } from '@/app/infra/entities/workflow';
/**
* Extended port configuration with additional metadata
*/
export interface ExtendedPortDefinition extends PortDefinition {
label?: I18nObject;
}
/**
* Node configuration metadata
* Defines all aspects of a node type including its appearance, ports, and configuration options
*/
export interface NodeConfigMeta {
/** Unique node type identifier */
nodeType: string;
/** Display name for the node */
label: I18nObject;
/** Description of what the node does */
description: I18nObject;
/** Icon name (from lucide-react) */
icon: string;
/** Node category for organization */
category: NodeCategory;
/** Color for the node header */
color?: string;
/** Input port definitions */
inputs: ExtendedPortDefinition[];
/** Output port definitions */
outputs: ExtendedPortDefinition[];
/** Configuration schema using the dynamic form system */
configSchema: IDynamicFormItemSchema[];
/** Default configuration values */
defaultConfig?: Record<string, unknown>;
/** Whether this node can be the starting point of a workflow */
isEntryPoint?: boolean;
/** Maximum number of this node type allowed in a workflow (undefined = unlimited) */
maxInstances?: number;
/** Documentation URL */
docsUrl?: string;
}
/**
* Registry of all node configurations by type
*/
export type NodeConfigRegistry = Record<string, NodeConfigMeta>;
/**
* Helper function to create a consistent port definition
*/
export function createPort(
name: string,
type: string,
options?: {
description?: string;
required?: boolean;
label?: I18nObject;
},
): ExtendedPortDefinition {
return {
name,
type,
description: options?.description,
required: options?.required ?? false,
label: options?.label,
};
}
/**
* Helper function to create input port
*/
export function createInput(
name: string,
type: string,
options?: {
description?: string;
required?: boolean;
label?: I18nObject;
},
): ExtendedPortDefinition {
return createPort(name, type, {
...options,
required: options?.required ?? true,
});
}
/**
* Helper function to create output port
*/
export function createOutput(
name: string,
type: string,
options?: {
description?: string;
label?: I18nObject;
},
): ExtendedPortDefinition {
return createPort(name, type, { ...options, required: false });
}

View File

@@ -38,7 +38,12 @@ import {
Play,
Plug,
ExternalLink,
BookOpen,
HardDrive,
Server,
Wrench,
} from 'lucide-react';
import type { LucideIcon } from 'lucide-react';
import i18n from 'i18next';
import { resolveI18nLabel, maybeTranslateKey } from './workflow-i18n';
@@ -424,23 +429,56 @@ export function getNodeTypeLabel(
// ─── Dynamic Icon Resolution ────────────────────────────────────────
import * as LucideIcons from 'lucide-react';
/**
* Explicit icon registry mapping PascalCase icon names to their components.
*
* We use an explicit map instead of `import * as LucideIcons` because
* Next.js/webpack tree-shaking removes unused exports from barrel imports,
* causing dynamic lookups like `LucideIcons[name]` to fail at runtime.
*/
const LUCIDE_ICON_REGISTRY: Record<string, LucideIcon> = {
ArrowRightLeft,
Bell,
BookOpen,
Bot,
Brain,
Clock,
Code,
Cpu,
Database,
ExternalLink,
FileText,
GitBranch,
GitMerge,
Globe,
HardDrive,
Layers,
ListFilter,
MessageCircle,
MessageSquare,
PauseCircle,
Play,
Plug,
Repeat,
Search,
Send,
Server,
Settings,
Split,
Timer,
Variable,
Webhook,
Workflow,
Wrench,
Zap,
};
/**
* Dynamically get Lucide icon component from backend icon name.
*
* This function enables the frontend to use icon names provided by the backend,
* eliminating the need to maintain a hardcoded NODE_ICONS mapping.
*
* @param iconName - Lucide icon name from backend (e.g., 'MessageSquare', 'Brain')
* @param nodeType - Node type for fallback to hardcoded mapping (backward compatibility)
* @returns React component for the icon
*
* @example
* ```tsx
* const Icon = getIconComponent('MessageSquare');
* return <Icon className="size-5" />;
* ```
*/
export function getIconComponent(
iconName: string | undefined,
@@ -448,24 +486,24 @@ export function getIconComponent(
): React.ElementType {
// 1. Priority: Use backend-provided icon name
if (iconName) {
const IconComponent = (LucideIcons as any)[iconName];
if (IconComponent && typeof IconComponent === 'function') {
const IconComponent = LUCIDE_ICON_REGISTRY[iconName];
if (IconComponent) {
return IconComponent;
}
// Warn if icon name is invalid
// Warn if icon name is not in registry
if (process.env.NODE_ENV === 'development') {
console.warn(
`[Workflow] Icon "${iconName}" not found in Lucide icons. ` +
`Falling back to default. Check: https://lucide.dev/icons/`
`[Workflow] Icon "${iconName}" not found in LUCIDE_ICON_REGISTRY. ` +
`Add it to workflow-constants.ts. Check: https://lucide.dev/icons/`
);
}
}
// 2. Fallback: Use hardcoded NODE_ICONS mapping (backward compatibility)
if (nodeType && NODE_ICONS[nodeType]) {
return NODE_ICONS[nodeType];
}
// 3. Final fallback: Default Settings icon
return LucideIcons.Settings;
return Settings;
}

View File

@@ -2,8 +2,8 @@
* Unified i18n utilities for the Workflow module.
*
* The backend API returns label dicts with keys like `zh-CN`, `en`,
* while node-configs use `zh_Hans`, `en_US`, and the i18next system
* uses `zh-Hans`, `en-US`. This module normalises **all** variants
* while some legacy persisted data may still use `zh_Hans`, `en_US`,
* and the i18next system uses `zh-Hans`, `en-US`. This module normalises **all** variants
* into a single lookup so every consumer gets the right value without
* maintaining its own fallback chain.
*/
@@ -26,7 +26,7 @@ const EN_KEYS = ['en-US', 'en_US', 'en'] as const;
* combination of `zh-CN`, `zh_Hans`, `en`, `en-US`, `en_US` etc.
*
* Works with both `Record<string, string>` (backend) and the typed
* `I18nObject` (node-configs).
* `I18nObject` used by legacy persisted workflow data.
*
* Optionally falls through to `i18n.t(value)` when the stored value
* itself looks like an i18n key (e.g. `"workflows.nodes.llmCall"`).

View File

@@ -2,8 +2,6 @@ import type {
WorkflowNodeTypeMetadata,
WorkflowPortDefinition,
} from '@/app/infra/entities/api';
import type { I18nObject } from '@/app/infra/entities/common';
import { getNodeConfig, type NodeConfigMeta } from './node-configs';
export const WORKFLOW_NODE_CATEGORIES = [
'trigger',
@@ -11,75 +9,35 @@ export const WORKFLOW_NODE_CATEGORIES = [
'control',
'action',
'integration',
'misc',
] as const;
const DEFAULT_INPUT_PORT: WorkflowPortDefinition = {
name: 'input',
type: 'any',
label: 'workflows.nodeInputs.input',
label: {
en_US: 'Input',
en: 'Input',
'en-US': 'Input',
zh_Hans: '输入',
'zh-Hans': '输入',
'zh-CN': '输入',
},
};
const DEFAULT_OUTPUT_PORT: WorkflowPortDefinition = {
name: 'output',
type: 'any',
label: 'workflows.nodeOutputs.output',
label: {
en_US: 'Output',
en: 'Output',
'en-US': 'Output',
zh_Hans: '输出',
'zh-Hans': '输出',
'zh-CN': '输出',
},
};
function ensurePortLabelKey(
prefix: 'workflows.nodeInputs' | 'workflows.nodeOutputs',
portName: string,
label?: string | Record<string, string>,
): string {
const key = `${prefix}.${portName}`;
if (typeof label === 'string') {
return label.startsWith(prefix) ? label : key;
}
if (label && typeof label === 'object') {
const existing = Object.values(label).find(
(value) => typeof value === 'string' && value.startsWith(prefix),
);
if (existing) return existing;
}
return key;
}
function normalizePort(
prefix: 'workflows.nodeInputs' | 'workflows.nodeOutputs',
port: WorkflowPortDefinition,
): WorkflowPortDefinition {
return {
...port,
label: ensurePortLabelKey(prefix, port.name, port.label),
};
}
function toBackendI18nObject(
value?: I18nObject,
): Record<string, string> | undefined {
if (!value) return undefined;
return {
'en-US': value.en_US,
en: value.en_US,
'zh-Hans': value.zh_Hans,
'zh-CN': value.zh_Hans,
};
}
function toWorkflowPortDefinition(
prefix: 'workflows.nodeInputs' | 'workflows.nodeOutputs',
port: NodeConfigMeta['inputs'][number] | NodeConfigMeta['outputs'][number],
): WorkflowPortDefinition {
return normalizePort(prefix, {
name: port.name,
type: port.type,
label: `${prefix}.${port.name}`,
});
}
function resolveNodeTypeCategory(type: string): string {
if (type.includes('.')) {
return type.split('.')[0];
@@ -87,47 +45,12 @@ function resolveNodeTypeCategory(type: string): string {
return 'process';
}
function getLocalConfigVariants(type: string): string[] {
const variants = new Set<string>([type]);
if (type.includes('.')) {
variants.add(type.split('.').slice(1).join('.'));
variants.add(type.replace(/\./g, '_'));
} else {
for (const category of WORKFLOW_NODE_CATEGORIES) {
variants.add(`${category}.${type}`);
}
}
return [...variants];
}
export function getLocalNodeTypeMeta(
type: string,
): WorkflowNodeTypeMetadata | null {
let localConfig: NodeConfigMeta | undefined;
for (const variant of getLocalConfigVariants(type)) {
localConfig = getNodeConfig(variant);
if (localConfig) break;
}
if (!localConfig) return null;
function normalizePort(
port: WorkflowPortDefinition,
): WorkflowPortDefinition {
return {
type,
category: localConfig.category,
label: toBackendI18nObject(localConfig.label) ?? {},
description: toBackendI18nObject(localConfig.description),
icon: localConfig.icon,
color: localConfig.color,
config_schema: localConfig.configSchema,
inputs: localConfig.inputs.map((input) =>
toWorkflowPortDefinition('workflows.nodeInputs', input),
),
outputs: localConfig.outputs.map((output) =>
toWorkflowPortDefinition('workflows.nodeOutputs', output),
),
...port,
type: port.type || 'any',
};
}
@@ -135,40 +58,24 @@ export function normalizeWorkflowNodeTypeMeta(
type: string,
nodeType?: WorkflowNodeTypeMetadata | null,
): WorkflowNodeTypeMetadata {
const localMeta = getLocalNodeTypeMeta(type);
const category =
nodeType?.category || localMeta?.category || resolveNodeTypeCategory(type);
const category = nodeType?.category || resolveNodeTypeCategory(type);
const inputs = nodeType?.inputs?.length
? nodeType.inputs.map((input) =>
normalizePort('workflows.nodeInputs', input),
)
: localMeta?.inputs?.length
? localMeta.inputs
: [DEFAULT_INPUT_PORT];
? nodeType.inputs.map(normalizePort)
: [DEFAULT_INPUT_PORT];
const outputs = nodeType?.outputs?.length
? nodeType.outputs.map((output) =>
normalizePort('workflows.nodeOutputs', output),
)
: localMeta?.outputs?.length
? localMeta.outputs
: [DEFAULT_OUTPUT_PORT];
const configSchema = nodeType?.config_schema?.length
? nodeType.config_schema
: localMeta?.config_schema?.length
? localMeta.config_schema
: [];
? nodeType.outputs.map(normalizePort)
: [DEFAULT_OUTPUT_PORT];
return {
type,
category,
label: nodeType?.label || localMeta?.label || {},
description: nodeType?.description || localMeta?.description,
icon: nodeType?.icon || localMeta?.icon,
color: nodeType?.color || localMeta?.color,
config_schema: configSchema,
label: nodeType?.label || {},
description: nodeType?.description,
icon: nodeType?.icon,
color: nodeType?.color,
config_schema: nodeType?.config_schema || [],
config_schema_source: nodeType?.config_schema_source,
config_stages: nodeType?.config_stages,
inputs,

View File

@@ -25,8 +25,8 @@ export interface WorkflowNode extends Node {
label: string;
type: string;
config: Record<string, unknown>;
inputs?: { name: string; label?: string; type?: string }[];
outputs?: { name: string; label?: string; type?: string }[];
inputs?: { name: string; label?: string | Record<string, string>; type?: string }[];
outputs?: { name: string; label?: string | Record<string, string>; type?: string }[];
};
}
@@ -270,9 +270,14 @@ export const useWorkflowStore = create<WorkflowState>((set, get) => ({
// Add new node
addNode: (type, position) => {
const { nodeTypes } = get();
const shortName = type.includes('.') ? type.split('.').pop()! : type;
const nodeType = normalizeWorkflowNodeTypeMeta(
type,
nodeTypes.find((t) => t.type === type),
nodeTypes.find((t) => {
if (t.type === type) return true;
const tShort = t.type.includes('.') ? t.type.split('.').pop()! : t.type;
return tShort === shortName;
}),
);
const getNodeLabel = (

View File

@@ -557,7 +557,7 @@ export interface WorkflowPosition {
export interface WorkflowPortDefinition {
name: string;
label?: string;
label?: string | Record<string, string>;
type?: string;
}