mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-06-09 07:16:04 +00:00
backend
This commit is contained in:
@@ -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(() => {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -6,5 +6,3 @@ export {
|
||||
export { default as NodePalette } from './NodePalette';
|
||||
export { default as PropertyPanel } from './PropertyPanel';
|
||||
|
||||
// Export node configurations
|
||||
export * from './node-configs';
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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 });
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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"`).
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 = (
|
||||
|
||||
@@ -557,7 +557,7 @@ export interface WorkflowPosition {
|
||||
|
||||
export interface WorkflowPortDefinition {
|
||||
name: string;
|
||||
label?: string;
|
||||
label?: string | Record<string, string>;
|
||||
type?: string;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user