mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-08-24 19:17:14 +00:00
ruff
This commit is contained in:
@@ -74,15 +74,24 @@ class ConditionNode(WorkflowNode):
|
|||||||
left_num = float(left)
|
left_num = float(left)
|
||||||
right_num = float(right)
|
right_num = float(right)
|
||||||
|
|
||||||
if operator == "==": return left_num == right_num
|
if operator == "==":
|
||||||
elif operator == "!=": return left_num != right_num
|
return left_num == right_num
|
||||||
elif operator == ">": return left_num > right_num
|
elif operator == "!=":
|
||||||
elif operator == "<": return left_num < right_num
|
return left_num != right_num
|
||||||
elif operator == ">=": return left_num >= right_num
|
elif operator == ">":
|
||||||
elif operator == "<=": return left_num <= right_num
|
return left_num > right_num
|
||||||
|
elif operator == "<":
|
||||||
|
return left_num < right_num
|
||||||
|
elif operator == ">=":
|
||||||
|
return left_num >= right_num
|
||||||
|
elif operator == "<=":
|
||||||
|
return left_num <= right_num
|
||||||
except ValueError:
|
except ValueError:
|
||||||
if operator == "==": return left == right
|
if operator == "==":
|
||||||
elif operator == "!=": return left != right
|
return left == right
|
||||||
elif operator in (">", "<", ">=", "<="): return False
|
elif operator == "!=":
|
||||||
|
return left != right
|
||||||
|
elif operator in (">", "<", ">=", "<="):
|
||||||
|
return False
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|||||||
@@ -31,7 +31,6 @@ class DatabaseQueryNode(WorkflowNode):
|
|||||||
|
|
||||||
async def execute(self, inputs: dict[str, Any], context: ExecutionContext) -> dict[str, Any]:
|
async def execute(self, inputs: dict[str, Any], context: ExecutionContext) -> dict[str, Any]:
|
||||||
connection_type = self.get_config("connection_type", "postgresql")
|
connection_type = self.get_config("connection_type", "postgresql")
|
||||||
connection_string = self.get_config("connection_string", "")
|
|
||||||
query = self.get_config("query", "")
|
query = self.get_config("query", "")
|
||||||
query_type = self.get_config("query_type", "select")
|
query_type = self.get_config("query_type", "select")
|
||||||
timeout = self.get_config("timeout", 30)
|
timeout = self.get_config("timeout", 30)
|
||||||
|
|||||||
@@ -30,7 +30,6 @@ class ParameterExtractorNode(WorkflowNode):
|
|||||||
config_schema: ClassVar[list[NodeConfig]] = []
|
config_schema: ClassVar[list[NodeConfig]] = []
|
||||||
|
|
||||||
async def execute(self, inputs: dict[str, Any], context: ExecutionContext) -> dict[str, Any]:
|
async def execute(self, inputs: dict[str, Any], context: ExecutionContext) -> dict[str, Any]:
|
||||||
text = inputs.get("text", "")
|
|
||||||
param_defs = self.get_config("parameters", [])
|
param_defs = self.get_config("parameters", [])
|
||||||
|
|
||||||
extracted = {}
|
extracted = {}
|
||||||
|
|||||||
@@ -30,7 +30,6 @@ class QuestionClassifierNode(WorkflowNode):
|
|||||||
config_schema: ClassVar[list[NodeConfig]] = []
|
config_schema: ClassVar[list[NodeConfig]] = []
|
||||||
|
|
||||||
async def execute(self, inputs: dict[str, Any], context: ExecutionContext) -> dict[str, Any]:
|
async def execute(self, inputs: dict[str, Any], context: ExecutionContext) -> dict[str, Any]:
|
||||||
question = inputs.get("question", "")
|
|
||||||
categories = self.get_config("categories", [])
|
categories = self.get_config("categories", [])
|
||||||
|
|
||||||
if categories:
|
if categories:
|
||||||
|
|||||||
@@ -30,7 +30,4 @@ class SendMessageNode(WorkflowNode):
|
|||||||
config_schema: ClassVar[list[NodeConfig]] = []
|
config_schema: ClassVar[list[NodeConfig]] = []
|
||||||
|
|
||||||
async def execute(self, inputs: dict[str, Any], context: ExecutionContext) -> dict[str, Any]:
|
async def execute(self, inputs: dict[str, Any], context: ExecutionContext) -> dict[str, Any]:
|
||||||
message = inputs.get("message", "")
|
|
||||||
target = inputs.get("target") or self.get_config("target_id", "")
|
|
||||||
|
|
||||||
return {"status": "sent", "message_id": f"msg_{context.execution_id}"}
|
return {"status": "sent", "message_id": f"msg_{context.execution_id}"}
|
||||||
|
|||||||
@@ -161,7 +161,12 @@ function isEntityCategory(id: string): id is EntityCategoryId {
|
|||||||
// Map sidebar config IDs to SidebarDataContext keys
|
// Map sidebar config IDs to SidebarDataContext keys
|
||||||
const ENTITY_KEY_MAP: Record<
|
const ENTITY_KEY_MAP: Record<
|
||||||
EntityCategoryId,
|
EntityCategoryId,
|
||||||
'bots' | 'pipelines' | 'workflows' | 'knowledgeBases' | 'plugins' | 'mcpServers'
|
| 'bots'
|
||||||
|
| 'pipelines'
|
||||||
|
| 'workflows'
|
||||||
|
| 'knowledgeBases'
|
||||||
|
| 'plugins'
|
||||||
|
| 'mcpServers'
|
||||||
> = {
|
> = {
|
||||||
bots: 'bots',
|
bots: 'bots',
|
||||||
pipelines: 'pipelines',
|
pipelines: 'pipelines',
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useSidebarData, SidebarEntityItem } from '../home-sidebar/SidebarDataContext';
|
import {
|
||||||
|
useSidebarData,
|
||||||
|
SidebarEntityItem,
|
||||||
|
} from '../home-sidebar/SidebarDataContext';
|
||||||
import { Label } from '@/components/ui/label';
|
import { Label } from '@/components/ui/label';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import {
|
import {
|
||||||
@@ -43,7 +46,8 @@ export default function UnifiedBindingSelector({
|
|||||||
className,
|
className,
|
||||||
}: UnifiedBindingSelectorProps) {
|
}: UnifiedBindingSelectorProps) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { pipelines, workflows, refreshPipelines, refreshWorkflows } = useSidebarData();
|
const { pipelines, workflows, refreshPipelines, refreshWorkflows } =
|
||||||
|
useSidebarData();
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -137,7 +141,9 @@ export default function UnifiedBindingSelector({
|
|||||||
{/* Entity selection */}
|
{/* Entity selection */}
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label>
|
<Label>
|
||||||
{value.type === 'pipeline' ? t('bots.selectPipeline') : t('bots.selectWorkflow')}
|
{value.type === 'pipeline'
|
||||||
|
? t('bots.selectPipeline')
|
||||||
|
: t('bots.selectWorkflow')}
|
||||||
</Label>
|
</Label>
|
||||||
<Popover open={open} onOpenChange={setOpen}>
|
<Popover open={open} onOpenChange={setOpen}>
|
||||||
<PopoverTrigger asChild>
|
<PopoverTrigger asChild>
|
||||||
@@ -171,7 +177,9 @@ export default function UnifiedBindingSelector({
|
|||||||
<Check
|
<Check
|
||||||
className={cn(
|
className={cn(
|
||||||
'mr-2 h-4 w-4',
|
'mr-2 h-4 w-4',
|
||||||
value.id === pipeline.id ? 'opacity-100' : 'opacity-0'
|
value.id === pipeline.id
|
||||||
|
? 'opacity-100'
|
||||||
|
: 'opacity-0',
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<div className="flex items-center gap-2 flex-1 text-left">
|
<div className="flex items-center gap-2 flex-1 text-left">
|
||||||
@@ -186,37 +194,37 @@ export default function UnifiedBindingSelector({
|
|||||||
</Button>
|
</Button>
|
||||||
))
|
))
|
||||||
)
|
)
|
||||||
|
) : workflows.length === 0 ? (
|
||||||
|
<div className="py-6 text-center text-sm text-muted-foreground">
|
||||||
|
{t('bots.noWorkflowsFound')}
|
||||||
|
</div>
|
||||||
) : (
|
) : (
|
||||||
workflows.length === 0 ? (
|
workflows.map((workflow) => (
|
||||||
<div className="py-6 text-center text-sm text-muted-foreground">
|
<Button
|
||||||
{t('bots.noWorkflowsFound')}
|
key={workflow.id}
|
||||||
</div>
|
variant="ghost"
|
||||||
) : (
|
className="w-full justify-start"
|
||||||
workflows.map((workflow) => (
|
onClick={() => handleSelect(workflow.id, 'workflow')}
|
||||||
<Button
|
>
|
||||||
key={workflow.id}
|
<Check
|
||||||
variant="ghost"
|
className={cn(
|
||||||
className="w-full justify-start"
|
'mr-2 h-4 w-4',
|
||||||
onClick={() => handleSelect(workflow.id, 'workflow')}
|
value.id === workflow.id
|
||||||
>
|
? 'opacity-100'
|
||||||
<Check
|
: 'opacity-0',
|
||||||
className={cn(
|
|
||||||
'mr-2 h-4 w-4',
|
|
||||||
value.id === workflow.id ? 'opacity-100' : 'opacity-0'
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<div className="flex items-center gap-2 flex-1 text-left">
|
|
||||||
{workflow.emoji && <span>{workflow.emoji}</span>}
|
|
||||||
<span className="truncate">{workflow.name}</span>
|
|
||||||
</div>
|
|
||||||
{workflow.description && (
|
|
||||||
<span className="text-xs text-muted-foreground truncate max-w-[120px]">
|
|
||||||
{workflow.description}
|
|
||||||
</span>
|
|
||||||
)}
|
)}
|
||||||
</Button>
|
/>
|
||||||
))
|
<div className="flex items-center gap-2 flex-1 text-left">
|
||||||
)
|
{workflow.emoji && <span>{workflow.emoji}</span>}
|
||||||
|
<span className="truncate">{workflow.name}</span>
|
||||||
|
</div>
|
||||||
|
{workflow.description && (
|
||||||
|
<span className="text-xs text-muted-foreground truncate max-w-[120px]">
|
||||||
|
{workflow.description}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
))
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</ScrollArea>
|
</ScrollArea>
|
||||||
|
|||||||
@@ -2,7 +2,13 @@ import { useState, useEffect, useCallback, useRef } from 'react';
|
|||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { Tabs, TabsList, TabsTrigger, TabsContent } from '@/components/ui/tabs';
|
import { Tabs, TabsList, TabsTrigger, TabsContent } from '@/components/ui/tabs';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
import {
|
||||||
|
Card,
|
||||||
|
CardContent,
|
||||||
|
CardDescription,
|
||||||
|
CardHeader,
|
||||||
|
CardTitle,
|
||||||
|
} from '@/components/ui/card';
|
||||||
import { Label } from '@/components/ui/label';
|
import { Label } from '@/components/ui/label';
|
||||||
import { Input } from '@/components/ui/input';
|
import { Input } from '@/components/ui/input';
|
||||||
import { Textarea } from '@/components/ui/textarea';
|
import { Textarea } from '@/components/ui/textarea';
|
||||||
@@ -12,7 +18,15 @@ import WorkflowExecutionsTab from './components/workflow-executions/WorkflowExec
|
|||||||
import WorkflowDebugDialog from './components/workflow-debug-dialog/WorkflowDebugDialog';
|
import WorkflowDebugDialog from './components/workflow-debug-dialog/WorkflowDebugDialog';
|
||||||
import { useSidebarData } from '@/app/home/components/home-sidebar/SidebarDataContext';
|
import { useSidebarData } from '@/app/home/components/home-sidebar/SidebarDataContext';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Settings, Play, BarChart3, GitBranch, Download, Upload, Bug } from 'lucide-react';
|
import {
|
||||||
|
Settings,
|
||||||
|
Play,
|
||||||
|
BarChart3,
|
||||||
|
GitBranch,
|
||||||
|
Download,
|
||||||
|
Upload,
|
||||||
|
Bug,
|
||||||
|
} from 'lucide-react';
|
||||||
import { backendClient } from '@/app/infra/http';
|
import { backendClient } from '@/app/infra/http';
|
||||||
import { Workflow } from '@/app/infra/entities/api';
|
import { Workflow } from '@/app/infra/entities/api';
|
||||||
import { useWorkflowStore } from './store/useWorkflowStore';
|
import { useWorkflowStore } from './store/useWorkflowStore';
|
||||||
@@ -24,7 +38,7 @@ export default function WorkflowDetailContent({ id }: { id: string }) {
|
|||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { refreshWorkflows, workflows, setDetailEntityName } = useSidebarData();
|
const { refreshWorkflows, workflows, setDetailEntityName } = useSidebarData();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
currentWorkflow,
|
currentWorkflow,
|
||||||
setCurrentWorkflow,
|
setCurrentWorkflow,
|
||||||
@@ -43,7 +57,11 @@ export default function WorkflowDetailContent({ id }: { id: string }) {
|
|||||||
const [activeTab, setActiveTab] = useState('editor');
|
const [activeTab, setActiveTab] = useState('editor');
|
||||||
const [workflow, setWorkflow] = useState<Workflow | null>(null);
|
const [workflow, setWorkflow] = useState<Workflow | null>(null);
|
||||||
const [createStep, setCreateStep] = useState<'basic' | 'editor'>('basic');
|
const [createStep, setCreateStep] = useState<'basic' | 'editor'>('basic');
|
||||||
const [basicInfo, setBasicInfo] = useState<{ name: string; description: string; emoji: string }>({
|
const [basicInfo, setBasicInfo] = useState<{
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
emoji: string;
|
||||||
|
}>({
|
||||||
name: '',
|
name: '',
|
||||||
description: '',
|
description: '',
|
||||||
emoji: '🔄',
|
emoji: '🔄',
|
||||||
@@ -65,11 +83,14 @@ export default function WorkflowDetailContent({ id }: { id: string }) {
|
|||||||
// Load node types
|
// Load node types
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (nodeTypes.length === 0) {
|
if (nodeTypes.length === 0) {
|
||||||
backendClient.getWorkflowNodeTypes().then((resp) => {
|
backendClient
|
||||||
setNodeTypes(resp.node_types, resp.categories);
|
.getWorkflowNodeTypes()
|
||||||
}).catch((err) => {
|
.then((resp) => {
|
||||||
console.error('Failed to load node types:', err);
|
setNodeTypes(resp.node_types, resp.categories);
|
||||||
});
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.error('Failed to load node types:', err);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}, [nodeTypes.length, setNodeTypes]);
|
}, [nodeTypes.length, setNodeTypes]);
|
||||||
|
|
||||||
@@ -82,16 +103,23 @@ export default function WorkflowDetailContent({ id }: { id: string }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
backendClient.getWorkflow(id).then((resp) => {
|
backendClient
|
||||||
setWorkflow(resp.workflow);
|
.getWorkflow(id)
|
||||||
setCurrentWorkflow(resp.workflow);
|
.then((resp) => {
|
||||||
fromWorkflowDefinition(resp.workflow.nodes || [], resp.workflow.edges || []);
|
setWorkflow(resp.workflow);
|
||||||
}).catch((err) => {
|
setCurrentWorkflow(resp.workflow);
|
||||||
console.error('Failed to load workflow:', err);
|
fromWorkflowDefinition(
|
||||||
toast.error(t('workflows.loadError'));
|
resp.workflow.nodes || [],
|
||||||
}).finally(() => {
|
resp.workflow.edges || [],
|
||||||
setLoading(false);
|
);
|
||||||
});
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.error('Failed to load workflow:', err);
|
||||||
|
toast.error(t('workflows.loadError'));
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
setLoading(false);
|
||||||
|
});
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
reset();
|
reset();
|
||||||
@@ -105,7 +133,7 @@ export default function WorkflowDetailContent({ id }: { id: string }) {
|
|||||||
setSaving(true);
|
setSaving(true);
|
||||||
try {
|
try {
|
||||||
const { nodes, edges } = toWorkflowDefinition();
|
const { nodes, edges } = toWorkflowDefinition();
|
||||||
|
|
||||||
if (isCreateMode) {
|
if (isCreateMode) {
|
||||||
const resp = await backendClient.createWorkflow({
|
const resp = await backendClient.createWorkflow({
|
||||||
name: basicInfo.name || t('workflows.newWorkflow'),
|
name: basicInfo.name || t('workflows.newWorkflow'),
|
||||||
@@ -138,12 +166,22 @@ export default function WorkflowDetailContent({ id }: { id: string }) {
|
|||||||
} finally {
|
} finally {
|
||||||
setSaving(false);
|
setSaving(false);
|
||||||
}
|
}
|
||||||
}, [id, isCreateMode, workflow, isSaving, toWorkflowDefinition, refreshWorkflows, navigate, t, basicInfo]);
|
}, [
|
||||||
|
id,
|
||||||
|
isCreateMode,
|
||||||
|
workflow,
|
||||||
|
isSaving,
|
||||||
|
toWorkflowDefinition,
|
||||||
|
refreshWorkflows,
|
||||||
|
navigate,
|
||||||
|
t,
|
||||||
|
basicInfo,
|
||||||
|
]);
|
||||||
|
|
||||||
// Export workflow handler
|
// Export workflow handler
|
||||||
const handleExport = useCallback(() => {
|
const handleExport = useCallback(() => {
|
||||||
const { nodes, edges } = toWorkflowDefinition();
|
const { nodes, edges } = toWorkflowDefinition();
|
||||||
|
|
||||||
const exportData = {
|
const exportData = {
|
||||||
name: workflow?.name || t('workflows.newWorkflow'),
|
name: workflow?.name || t('workflows.newWorkflow'),
|
||||||
description: workflow?.description || '',
|
description: workflow?.description || '',
|
||||||
@@ -155,8 +193,10 @@ export default function WorkflowDetailContent({ id }: { id: string }) {
|
|||||||
version: '1.0',
|
version: '1.0',
|
||||||
exportedAt: new Date().toISOString(),
|
exportedAt: new Date().toISOString(),
|
||||||
};
|
};
|
||||||
|
|
||||||
const blob = new Blob([JSON.stringify(exportData, null, 2)], { type: 'application/json' });
|
const blob = new Blob([JSON.stringify(exportData, null, 2)], {
|
||||||
|
type: 'application/json',
|
||||||
|
});
|
||||||
const url = URL.createObjectURL(blob);
|
const url = URL.createObjectURL(blob);
|
||||||
const a = document.createElement('a');
|
const a = document.createElement('a');
|
||||||
a.href = url;
|
a.href = url;
|
||||||
@@ -165,84 +205,103 @@ export default function WorkflowDetailContent({ id }: { id: string }) {
|
|||||||
a.click();
|
a.click();
|
||||||
document.body.removeChild(a);
|
document.body.removeChild(a);
|
||||||
URL.revokeObjectURL(url);
|
URL.revokeObjectURL(url);
|
||||||
|
|
||||||
toast.success(t('workflows.exportSuccess'));
|
toast.success(t('workflows.exportSuccess'));
|
||||||
}, [workflow, toWorkflowDefinition, t]);
|
}, [workflow, toWorkflowDefinition, t]);
|
||||||
|
|
||||||
// Import workflow handler
|
// Import workflow handler
|
||||||
const handleImport = useCallback((file: File) => {
|
const handleImport = useCallback(
|
||||||
const reader = new FileReader();
|
(file: File) => {
|
||||||
reader.onload = (e) => {
|
const reader = new FileReader();
|
||||||
try {
|
reader.onload = (e) => {
|
||||||
const importData = JSON.parse(e.target?.result as string);
|
try {
|
||||||
|
const importData = JSON.parse(e.target?.result as string);
|
||||||
// Validate imported data structure
|
|
||||||
if (!importData.nodes || !Array.isArray(importData.nodes)) {
|
// Validate imported data structure
|
||||||
throw new Error('Invalid workflow file: missing nodes');
|
if (!importData.nodes || !Array.isArray(importData.nodes)) {
|
||||||
}
|
throw new Error('Invalid workflow file: missing nodes');
|
||||||
if (!importData.edges || !Array.isArray(importData.edges)) {
|
|
||||||
throw new Error('Invalid workflow file: missing edges');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate each node has required fields
|
|
||||||
const nodeIds = new Set<string>();
|
|
||||||
for (const node of importData.nodes) {
|
|
||||||
if (!node.id || !node.type) {
|
|
||||||
throw new Error(`Invalid node: missing id or type`);
|
|
||||||
}
|
}
|
||||||
if (!node.position || typeof node.position.x !== 'number' || typeof node.position.y !== 'number') {
|
if (!importData.edges || !Array.isArray(importData.edges)) {
|
||||||
throw new Error(`Invalid node "${node.id}": missing or invalid position`);
|
throw new Error('Invalid workflow file: missing edges');
|
||||||
}
|
}
|
||||||
nodeIds.add(node.id);
|
|
||||||
|
// Validate each node has required fields
|
||||||
|
const nodeIds = new Set<string>();
|
||||||
|
for (const node of importData.nodes) {
|
||||||
|
if (!node.id || !node.type) {
|
||||||
|
throw new Error(`Invalid node: missing id or type`);
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
!node.position ||
|
||||||
|
typeof node.position.x !== 'number' ||
|
||||||
|
typeof node.position.y !== 'number'
|
||||||
|
) {
|
||||||
|
throw new Error(
|
||||||
|
`Invalid node "${node.id}": missing or invalid position`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
nodeIds.add(node.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate each edge has required fields and references existing nodes
|
||||||
|
for (const edge of importData.edges) {
|
||||||
|
if (!edge.id || !edge.source || !edge.target) {
|
||||||
|
throw new Error(`Invalid edge: missing id, source, or target`);
|
||||||
|
}
|
||||||
|
if (!nodeIds.has(edge.source)) {
|
||||||
|
throw new Error(
|
||||||
|
`Edge "${edge.id}" references unknown source node "${edge.source}"`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (!nodeIds.has(edge.target)) {
|
||||||
|
throw new Error(
|
||||||
|
`Edge "${edge.id}" references unknown target node "${edge.target}"`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load nodes and edges into the store
|
||||||
|
fromWorkflowDefinition(importData.nodes, importData.edges);
|
||||||
|
|
||||||
|
// Update workflow metadata if available
|
||||||
|
if (
|
||||||
|
workflow &&
|
||||||
|
(importData.name || importData.description || importData.emoji)
|
||||||
|
) {
|
||||||
|
setWorkflow({
|
||||||
|
...workflow,
|
||||||
|
name: importData.name || workflow.name,
|
||||||
|
description: importData.description || workflow.description,
|
||||||
|
emoji: importData.emoji || workflow.emoji,
|
||||||
|
variables: importData.variables || workflow.variables,
|
||||||
|
settings: importData.settings || workflow.settings,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
setDirty(true);
|
||||||
|
toast.success(t('workflows.importSuccess'));
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to import workflow:', error);
|
||||||
|
toast.error(t('workflows.importError'));
|
||||||
}
|
}
|
||||||
|
};
|
||||||
// Validate each edge has required fields and references existing nodes
|
reader.readAsText(file);
|
||||||
for (const edge of importData.edges) {
|
},
|
||||||
if (!edge.id || !edge.source || !edge.target) {
|
[workflow, fromWorkflowDefinition, setDirty, t],
|
||||||
throw new Error(`Invalid edge: missing id, source, or target`);
|
);
|
||||||
}
|
|
||||||
if (!nodeIds.has(edge.source)) {
|
|
||||||
throw new Error(`Edge "${edge.id}" references unknown source node "${edge.source}"`);
|
|
||||||
}
|
|
||||||
if (!nodeIds.has(edge.target)) {
|
|
||||||
throw new Error(`Edge "${edge.id}" references unknown target node "${edge.target}"`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load nodes and edges into the store
|
|
||||||
fromWorkflowDefinition(importData.nodes, importData.edges);
|
|
||||||
|
|
||||||
// Update workflow metadata if available
|
|
||||||
if (workflow && (importData.name || importData.description || importData.emoji)) {
|
|
||||||
setWorkflow({
|
|
||||||
...workflow,
|
|
||||||
name: importData.name || workflow.name,
|
|
||||||
description: importData.description || workflow.description,
|
|
||||||
emoji: importData.emoji || workflow.emoji,
|
|
||||||
variables: importData.variables || workflow.variables,
|
|
||||||
settings: importData.settings || workflow.settings,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
setDirty(true);
|
|
||||||
toast.success(t('workflows.importSuccess'));
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed to import workflow:', error);
|
|
||||||
toast.error(t('workflows.importError'));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
reader.readAsText(file);
|
|
||||||
}, [workflow, fromWorkflowDefinition, setDirty, t]);
|
|
||||||
|
|
||||||
// Handle file input change
|
// Handle file input change
|
||||||
const handleFileChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
|
const handleFileChange = useCallback(
|
||||||
const file = e.target.files?.[0];
|
(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
if (file) {
|
const file = e.target.files?.[0];
|
||||||
handleImport(file);
|
if (file) {
|
||||||
// Reset file input
|
handleImport(file);
|
||||||
e.target.value = '';
|
// Reset file input
|
||||||
}
|
e.target.value = '';
|
||||||
}, [handleImport]);
|
}
|
||||||
|
},
|
||||||
|
[handleImport],
|
||||||
|
);
|
||||||
|
|
||||||
// Publish handler
|
// Publish handler
|
||||||
const handlePublish = useCallback(async () => {
|
const handlePublish = useCallback(async () => {
|
||||||
@@ -289,7 +348,10 @@ export default function WorkflowDetailContent({ id }: { id: string }) {
|
|||||||
style={{ display: 'none' }}
|
style={{ display: 'none' }}
|
||||||
ref={fileInputRef}
|
ref={fileInputRef}
|
||||||
/>
|
/>
|
||||||
<Button variant="outline" onClick={() => fileInputRef.current?.click()}>
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
onClick={() => fileInputRef.current?.click()}
|
||||||
|
>
|
||||||
<Upload className="size-4 mr-1" />
|
<Upload className="size-4 mr-1" />
|
||||||
{t('workflows.import')}
|
{t('workflows.import')}
|
||||||
</Button>
|
</Button>
|
||||||
@@ -307,17 +369,26 @@ export default function WorkflowDetailContent({ id }: { id: string }) {
|
|||||||
<Card>
|
<Card>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle>{t('workflows.basicInfo')}</CardTitle>
|
<CardTitle>{t('workflows.basicInfo')}</CardTitle>
|
||||||
<CardDescription>{t('workflows.basicInfoDesc')}</CardDescription>
|
<CardDescription>
|
||||||
|
{t('workflows.basicInfoDesc')}
|
||||||
|
</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="space-y-4">
|
<CardContent className="space-y-4">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="workflow-name">{t('workflows.name')}</Label>
|
<Label htmlFor="workflow-name">{t('workflows.name')}</Label>
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<EmojiPicker value={basicInfo.emoji} onChange={(emoji: string) => setBasicInfo({ ...basicInfo, emoji })} />
|
<EmojiPicker
|
||||||
|
value={basicInfo.emoji}
|
||||||
|
onChange={(emoji: string) =>
|
||||||
|
setBasicInfo({ ...basicInfo, emoji })
|
||||||
|
}
|
||||||
|
/>
|
||||||
<Input
|
<Input
|
||||||
id="workflow-name"
|
id="workflow-name"
|
||||||
value={basicInfo.name}
|
value={basicInfo.name}
|
||||||
onChange={(e) => setBasicInfo({ ...basicInfo, name: e.target.value })}
|
onChange={(e) =>
|
||||||
|
setBasicInfo({ ...basicInfo, name: e.target.value })
|
||||||
|
}
|
||||||
placeholder={t('workflows.namePlaceholder')}
|
placeholder={t('workflows.namePlaceholder')}
|
||||||
className="flex-1"
|
className="flex-1"
|
||||||
/>
|
/>
|
||||||
@@ -325,11 +396,18 @@ export default function WorkflowDetailContent({ id }: { id: string }) {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="workflow-description">{t('workflows.description')}</Label>
|
<Label htmlFor="workflow-description">
|
||||||
|
{t('workflows.description')}
|
||||||
|
</Label>
|
||||||
<Textarea
|
<Textarea
|
||||||
id="workflow-description"
|
id="workflow-description"
|
||||||
value={basicInfo.description}
|
value={basicInfo.description}
|
||||||
onChange={(e) => setBasicInfo({ ...basicInfo, description: e.target.value })}
|
onChange={(e) =>
|
||||||
|
setBasicInfo({
|
||||||
|
...basicInfo,
|
||||||
|
description: e.target.value,
|
||||||
|
})
|
||||||
|
}
|
||||||
placeholder={t('workflows.descriptionPlaceholder')}
|
placeholder={t('workflows.descriptionPlaceholder')}
|
||||||
rows={3}
|
rows={3}
|
||||||
/>
|
/>
|
||||||
@@ -377,12 +455,15 @@ export default function WorkflowDetailContent({ id }: { id: string }) {
|
|||||||
style={{ display: 'none' }}
|
style={{ display: 'none' }}
|
||||||
ref={fileInputRef}
|
ref={fileInputRef}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Sticky Header: title + save button */}
|
{/* Sticky Header: title + save button */}
|
||||||
<div className="flex items-center justify-between pb-4 shrink-0">
|
<div className="flex items-center justify-between pb-4 shrink-0">
|
||||||
<h1 className="text-xl font-semibold">{t('workflows.editWorkflow')}</h1>
|
<h1 className="text-xl font-semibold">{t('workflows.editWorkflow')}</h1>
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<Button variant="outline" onClick={() => fileInputRef.current?.click()}>
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
onClick={() => fileInputRef.current?.click()}
|
||||||
|
>
|
||||||
<Upload className="size-4 mr-1" />
|
<Upload className="size-4 mr-1" />
|
||||||
{t('workflows.import')}
|
{t('workflows.import')}
|
||||||
</Button>
|
</Button>
|
||||||
@@ -434,10 +515,7 @@ export default function WorkflowDetailContent({ id }: { id: string }) {
|
|||||||
</TabsList>
|
</TabsList>
|
||||||
|
|
||||||
{/* Tab: Editor */}
|
{/* Tab: Editor */}
|
||||||
<TabsContent
|
<TabsContent value="editor" className="flex-1 min-h-0 mt-4">
|
||||||
value="editor"
|
|
||||||
className="flex-1 min-h-0 mt-4"
|
|
||||||
>
|
|
||||||
<WorkflowEditorComponent />
|
<WorkflowEditorComponent />
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
import { useState, useCallback, useEffect, useRef } from 'react';
|
import { useState, useCallback, useEffect, useRef } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useWorkflowStore, DebugLog, NodeExecutionResult } from '../../store/useWorkflowStore';
|
import {
|
||||||
|
useWorkflowStore,
|
||||||
|
DebugLog,
|
||||||
|
NodeExecutionResult,
|
||||||
|
} from '../../store/useWorkflowStore';
|
||||||
import { backendClient } from '@/app/infra/http';
|
import { backendClient } from '@/app/infra/http';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Input } from '@/components/ui/input';
|
import { Input } from '@/components/ui/input';
|
||||||
@@ -10,7 +14,13 @@ import { Label } from '@/components/ui/label';
|
|||||||
import { Switch } from '@/components/ui/switch';
|
import { Switch } from '@/components/ui/switch';
|
||||||
import { ScrollArea } from '@/components/ui/scroll-area';
|
import { ScrollArea } from '@/components/ui/scroll-area';
|
||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
||||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
import {
|
||||||
|
Card,
|
||||||
|
CardContent,
|
||||||
|
CardDescription,
|
||||||
|
CardHeader,
|
||||||
|
CardTitle,
|
||||||
|
} from '@/components/ui/card';
|
||||||
import {
|
import {
|
||||||
Collapsible,
|
Collapsible,
|
||||||
CollapsibleContent,
|
CollapsibleContent,
|
||||||
@@ -66,7 +76,10 @@ const logLevelColors: Record<string, string> = {
|
|||||||
debug: 'text-gray-400',
|
debug: 'text-gray-400',
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function WorkflowDebugger({ workflowId, onClose }: WorkflowDebuggerProps) {
|
export default function WorkflowDebugger({
|
||||||
|
workflowId,
|
||||||
|
onClose,
|
||||||
|
}: WorkflowDebuggerProps) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const logsEndRef = useRef<HTMLDivElement>(null);
|
const logsEndRef = useRef<HTMLDivElement>(null);
|
||||||
const [activeTab, setActiveTab] = useState<string>('context');
|
const [activeTab, setActiveTab] = useState<string>('context');
|
||||||
@@ -123,9 +136,9 @@ export default function WorkflowDebugger({ workflowId, onClose }: WorkflowDebugg
|
|||||||
setDebugState('running');
|
setDebugState('running');
|
||||||
clearNodeExecutionResults();
|
clearNodeExecutionResults();
|
||||||
clearDebugLogs();
|
clearDebugLogs();
|
||||||
|
|
||||||
addDebugLog({ level: 'info', message: t('workflows.debug.starting') });
|
addDebugLog({ level: 'info', message: t('workflows.debug.starting') });
|
||||||
|
|
||||||
const response = await backendClient.startWorkflowDebug(workflowId, {
|
const response = await backendClient.startWorkflowDebug(workflowId, {
|
||||||
context: {
|
context: {
|
||||||
message_content: debugContext.messageContent,
|
message_content: debugContext.messageContent,
|
||||||
@@ -136,125 +149,169 @@ export default function WorkflowDebugger({ workflowId, onClose }: WorkflowDebugg
|
|||||||
is_group: debugContext.isGroup,
|
is_group: debugContext.isGroup,
|
||||||
},
|
},
|
||||||
variables: debugContext.customVariables,
|
variables: debugContext.customVariables,
|
||||||
breakpoints: Object.keys(breakpoints).filter(k => breakpoints[k]),
|
breakpoints: Object.keys(breakpoints).filter((k) => breakpoints[k]),
|
||||||
});
|
});
|
||||||
|
|
||||||
setDebugExecutionId(response.execution_id);
|
setDebugExecutionId(response.execution_id);
|
||||||
addDebugLog({ level: 'info', message: t('workflows.debug.started', { id: response.execution_id }) });
|
addDebugLog({
|
||||||
|
level: 'info',
|
||||||
|
message: t('workflows.debug.started', { id: response.execution_id }),
|
||||||
|
});
|
||||||
|
|
||||||
// Start polling for state updates
|
// Start polling for state updates
|
||||||
pollDebugState(response.execution_id);
|
pollDebugState(response.execution_id);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
setDebugState('error');
|
setDebugState('error');
|
||||||
addDebugLog({ level: 'error', message: `${t('workflows.debug.startError')}: ${error}` });
|
addDebugLog({
|
||||||
|
level: 'error',
|
||||||
|
message: `${t('workflows.debug.startError')}: ${error}`,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}, [workflowId, debugContext, breakpoints, t]);
|
}, [workflowId, debugContext, breakpoints, t]);
|
||||||
|
|
||||||
// Poll debug state
|
// Poll debug state
|
||||||
const pollDebugState = useCallback(async (executionId: string) => {
|
const pollDebugState = useCallback(
|
||||||
pollCancelledRef.current = false;
|
async (executionId: string) => {
|
||||||
const poll = async () => {
|
pollCancelledRef.current = false;
|
||||||
if (pollCancelledRef.current) return;
|
const poll = async () => {
|
||||||
try {
|
|
||||||
const state = await backendClient.getWorkflowDebugState(workflowId, executionId);
|
|
||||||
if (pollCancelledRef.current) return;
|
if (pollCancelledRef.current) return;
|
||||||
|
try {
|
||||||
setDebugState(state.status as typeof debugState);
|
const state = await backendClient.getWorkflowDebugState(
|
||||||
setCurrentNodeId(state.current_node_id || null);
|
workflowId,
|
||||||
|
executionId,
|
||||||
// Update node execution results
|
);
|
||||||
if (state.node_states) {
|
if (pollCancelledRef.current) return;
|
||||||
for (const [nodeId, nodeState] of Object.entries(state.node_states)) {
|
|
||||||
updateNodeExecutionResult(nodeId, nodeState as Partial<NodeExecutionResult>);
|
setDebugState(state.status as typeof debugState);
|
||||||
|
setCurrentNodeId(state.current_node_id || null);
|
||||||
|
|
||||||
|
// Update node execution results
|
||||||
|
if (state.node_states) {
|
||||||
|
for (const [nodeId, nodeState] of Object.entries(
|
||||||
|
state.node_states,
|
||||||
|
)) {
|
||||||
|
updateNodeExecutionResult(
|
||||||
|
nodeId,
|
||||||
|
nodeState as Partial<NodeExecutionResult>,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
// Add new logs
|
||||||
// Add new logs
|
if (state.new_logs) {
|
||||||
if (state.new_logs) {
|
for (const log of state.new_logs) {
|
||||||
for (const log of state.new_logs) {
|
addDebugLog(log);
|
||||||
addDebugLog(log);
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Continue polling if still running or paused
|
||||||
|
if (
|
||||||
|
!pollCancelledRef.current &&
|
||||||
|
(state.status === 'running' || state.status === 'paused')
|
||||||
|
) {
|
||||||
|
setTimeout(poll, 500);
|
||||||
|
} else if (state.status === 'completed') {
|
||||||
|
addDebugLog({
|
||||||
|
level: 'info',
|
||||||
|
message: t('workflows.debug.completed'),
|
||||||
|
});
|
||||||
|
} else if (state.status === 'error') {
|
||||||
|
addDebugLog({
|
||||||
|
level: 'error',
|
||||||
|
message: state.error || t('workflows.debug.unknownError'),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to poll debug state:', error);
|
||||||
}
|
}
|
||||||
|
};
|
||||||
// Continue polling if still running or paused
|
|
||||||
if (!pollCancelledRef.current && (state.status === 'running' || state.status === 'paused')) {
|
poll();
|
||||||
setTimeout(poll, 500);
|
},
|
||||||
} else if (state.status === 'completed') {
|
[workflowId, t],
|
||||||
addDebugLog({ level: 'info', message: t('workflows.debug.completed') });
|
);
|
||||||
} else if (state.status === 'error') {
|
|
||||||
addDebugLog({ level: 'error', message: state.error || t('workflows.debug.unknownError') });
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed to poll debug state:', error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
poll();
|
|
||||||
}, [workflowId, t]);
|
|
||||||
|
|
||||||
// Pause execution
|
// Pause execution
|
||||||
const handlePause = useCallback(async () => {
|
const handlePause = useCallback(async () => {
|
||||||
if (!debugExecutionId) return;
|
if (!debugExecutionId) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await backendClient.pauseWorkflowDebug(workflowId, debugExecutionId);
|
await backendClient.pauseWorkflowDebug(workflowId, debugExecutionId);
|
||||||
setDebugState('paused');
|
setDebugState('paused');
|
||||||
addDebugLog({ level: 'info', message: t('workflows.debug.paused') });
|
addDebugLog({ level: 'info', message: t('workflows.debug.paused') });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
addDebugLog({ level: 'error', message: `${t('workflows.debug.pauseError')}: ${error}` });
|
addDebugLog({
|
||||||
|
level: 'error',
|
||||||
|
message: `${t('workflows.debug.pauseError')}: ${error}`,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}, [workflowId, debugExecutionId, t]);
|
}, [workflowId, debugExecutionId, t]);
|
||||||
|
|
||||||
// Resume execution
|
// Resume execution
|
||||||
const handleResume = useCallback(async () => {
|
const handleResume = useCallback(async () => {
|
||||||
if (!debugExecutionId) return;
|
if (!debugExecutionId) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await backendClient.resumeWorkflowDebug(workflowId, debugExecutionId);
|
await backendClient.resumeWorkflowDebug(workflowId, debugExecutionId);
|
||||||
setDebugState('running');
|
setDebugState('running');
|
||||||
addDebugLog({ level: 'info', message: t('workflows.debug.resumed') });
|
addDebugLog({ level: 'info', message: t('workflows.debug.resumed') });
|
||||||
pollDebugState(debugExecutionId);
|
pollDebugState(debugExecutionId);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
addDebugLog({ level: 'error', message: `${t('workflows.debug.resumeError')}: ${error}` });
|
addDebugLog({
|
||||||
|
level: 'error',
|
||||||
|
message: `${t('workflows.debug.resumeError')}: ${error}`,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}, [workflowId, debugExecutionId, t, pollDebugState]);
|
}, [workflowId, debugExecutionId, t, pollDebugState]);
|
||||||
|
|
||||||
// Step execution
|
// Step execution
|
||||||
const handleStep = useCallback(async () => {
|
const handleStep = useCallback(async () => {
|
||||||
if (!debugExecutionId) return;
|
if (!debugExecutionId) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await backendClient.stepWorkflowDebug(workflowId, debugExecutionId);
|
const result = await backendClient.stepWorkflowDebug(
|
||||||
|
workflowId,
|
||||||
|
debugExecutionId,
|
||||||
|
);
|
||||||
|
|
||||||
if (result.node_id) {
|
if (result.node_id) {
|
||||||
setCurrentNodeId(result.node_id);
|
setCurrentNodeId(result.node_id);
|
||||||
updateNodeExecutionResult(result.node_id, result.node_state as Partial<NodeExecutionResult>);
|
updateNodeExecutionResult(
|
||||||
addDebugLog({
|
result.node_id,
|
||||||
level: 'info',
|
result.node_state as Partial<NodeExecutionResult>,
|
||||||
|
);
|
||||||
|
addDebugLog({
|
||||||
|
level: 'info',
|
||||||
message: t('workflows.debug.steppedTo', { node: result.node_id }),
|
message: t('workflows.debug.steppedTo', { node: result.node_id }),
|
||||||
nodeId: result.node_id,
|
nodeId: result.node_id,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (result.completed) {
|
if (result.completed) {
|
||||||
setDebugState('completed');
|
setDebugState('completed');
|
||||||
addDebugLog({ level: 'info', message: t('workflows.debug.completed') });
|
addDebugLog({ level: 'info', message: t('workflows.debug.completed') });
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
addDebugLog({ level: 'error', message: `${t('workflows.debug.stepError')}: ${error}` });
|
addDebugLog({
|
||||||
|
level: 'error',
|
||||||
|
message: `${t('workflows.debug.stepError')}: ${error}`,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}, [workflowId, debugExecutionId, t]);
|
}, [workflowId, debugExecutionId, t]);
|
||||||
|
|
||||||
// Stop execution
|
// Stop execution
|
||||||
const handleStop = useCallback(async () => {
|
const handleStop = useCallback(async () => {
|
||||||
if (!debugExecutionId) return;
|
if (!debugExecutionId) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await backendClient.stopWorkflowDebug(workflowId, debugExecutionId);
|
await backendClient.stopWorkflowDebug(workflowId, debugExecutionId);
|
||||||
resetDebugState();
|
resetDebugState();
|
||||||
addDebugLog({ level: 'info', message: t('workflows.debug.stopped') });
|
addDebugLog({ level: 'info', message: t('workflows.debug.stopped') });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
addDebugLog({ level: 'error', message: `${t('workflows.debug.stopError')}: ${error}` });
|
addDebugLog({
|
||||||
|
level: 'error',
|
||||||
|
message: `${t('workflows.debug.stopError')}: ${error}`,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}, [workflowId, debugExecutionId, t, resetDebugState]);
|
}, [workflowId, debugExecutionId, t, resetDebugState]);
|
||||||
|
|
||||||
@@ -303,7 +360,10 @@ export default function WorkflowDebugger({ workflowId, onClose }: WorkflowDebugg
|
|||||||
|
|
||||||
const isRunning = debugState === 'running';
|
const isRunning = debugState === 'running';
|
||||||
const isPaused = debugState === 'paused';
|
const isPaused = debugState === 'paused';
|
||||||
const canStart = debugState === 'idle' || debugState === 'completed' || debugState === 'error';
|
const canStart =
|
||||||
|
debugState === 'idle' ||
|
||||||
|
debugState === 'completed' ||
|
||||||
|
debugState === 'error';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col h-full bg-background border-l">
|
<div className="flex flex-col h-full bg-background border-l">
|
||||||
@@ -313,7 +373,11 @@ export default function WorkflowDebugger({ workflowId, onClose }: WorkflowDebugg
|
|||||||
<Bug className="size-5 text-primary" />
|
<Bug className="size-5 text-primary" />
|
||||||
<span className="font-semibold">{t('workflows.debug.title')}</span>
|
<span className="font-semibold">{t('workflows.debug.title')}</span>
|
||||||
{debugState !== 'idle' && (
|
{debugState !== 'idle' && (
|
||||||
<Badge variant={isRunning ? 'default' : isPaused ? 'secondary' : 'outline'}>
|
<Badge
|
||||||
|
variant={
|
||||||
|
isRunning ? 'default' : isPaused ? 'secondary' : 'outline'
|
||||||
|
}
|
||||||
|
>
|
||||||
{t(`workflows.debug.state.${debugState}`)}
|
{t(`workflows.debug.state.${debugState}`)}
|
||||||
</Badge>
|
</Badge>
|
||||||
)}
|
)}
|
||||||
@@ -335,7 +399,12 @@ export default function WorkflowDebugger({ workflowId, onClose }: WorkflowDebugg
|
|||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
{isRunning ? (
|
{isRunning ? (
|
||||||
<Button size="sm" variant="secondary" onClick={handlePause} className="gap-1">
|
<Button
|
||||||
|
size="sm"
|
||||||
|
variant="secondary"
|
||||||
|
onClick={handlePause}
|
||||||
|
className="gap-1"
|
||||||
|
>
|
||||||
<Pause className="size-4" />
|
<Pause className="size-4" />
|
||||||
{t('workflows.debug.pause')}
|
{t('workflows.debug.pause')}
|
||||||
</Button>
|
</Button>
|
||||||
@@ -345,29 +414,54 @@ export default function WorkflowDebugger({ workflowId, onClose }: WorkflowDebugg
|
|||||||
{t('workflows.debug.resume')}
|
{t('workflows.debug.resume')}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
<Button size="sm" variant="outline" onClick={handleStep} disabled={isRunning} className="gap-1">
|
<Button
|
||||||
|
size="sm"
|
||||||
|
variant="outline"
|
||||||
|
onClick={handleStep}
|
||||||
|
disabled={isRunning}
|
||||||
|
className="gap-1"
|
||||||
|
>
|
||||||
<StepForward className="size-4" />
|
<StepForward className="size-4" />
|
||||||
{t('workflows.debug.step')}
|
{t('workflows.debug.step')}
|
||||||
</Button>
|
</Button>
|
||||||
<Button size="sm" variant="destructive" onClick={handleStop} className="gap-1">
|
<Button
|
||||||
|
size="sm"
|
||||||
|
variant="destructive"
|
||||||
|
onClick={handleStop}
|
||||||
|
className="gap-1"
|
||||||
|
>
|
||||||
<Square className="size-4" />
|
<Square className="size-4" />
|
||||||
{t('workflows.debug.stop')}
|
{t('workflows.debug.stop')}
|
||||||
</Button>
|
</Button>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="flex-1" />
|
<div className="flex-1" />
|
||||||
|
|
||||||
<Button size="sm" variant="ghost" onClick={clearBreakpoints} title={t('workflows.debug.clearBreakpoints')}>
|
<Button
|
||||||
|
size="sm"
|
||||||
|
variant="ghost"
|
||||||
|
onClick={clearBreakpoints}
|
||||||
|
title={t('workflows.debug.clearBreakpoints')}
|
||||||
|
>
|
||||||
<Circle className="size-4" />
|
<Circle className="size-4" />
|
||||||
</Button>
|
</Button>
|
||||||
<Button size="sm" variant="ghost" onClick={clearDebugLogs} title={t('workflows.debug.clearLogs')}>
|
<Button
|
||||||
|
size="sm"
|
||||||
|
variant="ghost"
|
||||||
|
onClick={clearDebugLogs}
|
||||||
|
title={t('workflows.debug.clearLogs')}
|
||||||
|
>
|
||||||
<Trash2 className="size-4" />
|
<Trash2 className="size-4" />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Main Content */}
|
{/* Main Content */}
|
||||||
<Tabs value={activeTab} onValueChange={setActiveTab} className="flex-1 flex flex-col min-h-0">
|
<Tabs
|
||||||
|
value={activeTab}
|
||||||
|
onValueChange={setActiveTab}
|
||||||
|
className="flex-1 flex flex-col min-h-0"
|
||||||
|
>
|
||||||
<TabsList className="mx-4 mt-2 justify-start">
|
<TabsList className="mx-4 mt-2 justify-start">
|
||||||
<TabsTrigger value="context" className="gap-1">
|
<TabsTrigger value="context" className="gap-1">
|
||||||
<Terminal className="size-4" />
|
<Terminal className="size-4" />
|
||||||
@@ -399,19 +493,23 @@ export default function WorkflowDebugger({ workflowId, onClose }: WorkflowDebugg
|
|||||||
<Label>{t('workflows.debug.messageContent')}</Label>
|
<Label>{t('workflows.debug.messageContent')}</Label>
|
||||||
<Textarea
|
<Textarea
|
||||||
value={debugContext.messageContent}
|
value={debugContext.messageContent}
|
||||||
onChange={(e) => setDebugContext({ messageContent: e.target.value })}
|
onChange={(e) =>
|
||||||
|
setDebugContext({ messageContent: e.target.value })
|
||||||
|
}
|
||||||
placeholder={t('workflows.debug.messageContentPlaceholder')}
|
placeholder={t('workflows.debug.messageContentPlaceholder')}
|
||||||
className="min-h-[80px]"
|
className="min-h-[80px]"
|
||||||
disabled={!canStart}
|
disabled={!canStart}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-2 gap-4">
|
<div className="grid grid-cols-2 gap-4">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label>{t('workflows.debug.senderId')}</Label>
|
<Label>{t('workflows.debug.senderId')}</Label>
|
||||||
<Input
|
<Input
|
||||||
value={debugContext.senderId}
|
value={debugContext.senderId}
|
||||||
onChange={(e) => setDebugContext({ senderId: e.target.value })}
|
onChange={(e) =>
|
||||||
|
setDebugContext({ senderId: e.target.value })
|
||||||
|
}
|
||||||
placeholder={t('workflows.debug.senderIdPlaceholder')}
|
placeholder={t('workflows.debug.senderIdPlaceholder')}
|
||||||
disabled={!canStart}
|
disabled={!canStart}
|
||||||
/>
|
/>
|
||||||
@@ -420,19 +518,23 @@ export default function WorkflowDebugger({ workflowId, onClose }: WorkflowDebugg
|
|||||||
<Label>{t('workflows.debug.senderName')}</Label>
|
<Label>{t('workflows.debug.senderName')}</Label>
|
||||||
<Input
|
<Input
|
||||||
value={debugContext.senderName}
|
value={debugContext.senderName}
|
||||||
onChange={(e) => setDebugContext({ senderName: e.target.value })}
|
onChange={(e) =>
|
||||||
|
setDebugContext({ senderName: e.target.value })
|
||||||
|
}
|
||||||
placeholder={t('workflows.debug.senderNamePlaceholder')}
|
placeholder={t('workflows.debug.senderNamePlaceholder')}
|
||||||
disabled={!canStart}
|
disabled={!canStart}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-2 gap-4">
|
<div className="grid grid-cols-2 gap-4">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label>{t('workflows.debug.platform')}</Label>
|
<Label>{t('workflows.debug.platform')}</Label>
|
||||||
<Input
|
<Input
|
||||||
value={debugContext.platform}
|
value={debugContext.platform}
|
||||||
onChange={(e) => setDebugContext({ platform: e.target.value })}
|
onChange={(e) =>
|
||||||
|
setDebugContext({ platform: e.target.value })
|
||||||
|
}
|
||||||
placeholder={t('workflows.debug.platformPlaceholder')}
|
placeholder={t('workflows.debug.platformPlaceholder')}
|
||||||
disabled={!canStart}
|
disabled={!canStart}
|
||||||
/>
|
/>
|
||||||
@@ -441,17 +543,21 @@ export default function WorkflowDebugger({ workflowId, onClose }: WorkflowDebugg
|
|||||||
<Label>{t('workflows.debug.conversationId')}</Label>
|
<Label>{t('workflows.debug.conversationId')}</Label>
|
||||||
<Input
|
<Input
|
||||||
value={debugContext.conversationId}
|
value={debugContext.conversationId}
|
||||||
onChange={(e) => setDebugContext({ conversationId: e.target.value })}
|
onChange={(e) =>
|
||||||
|
setDebugContext({ conversationId: e.target.value })
|
||||||
|
}
|
||||||
placeholder={t('workflows.debug.conversationIdPlaceholder')}
|
placeholder={t('workflows.debug.conversationIdPlaceholder')}
|
||||||
disabled={!canStart}
|
disabled={!canStart}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Switch
|
<Switch
|
||||||
checked={debugContext.isGroup}
|
checked={debugContext.isGroup}
|
||||||
onCheckedChange={(checked) => setDebugContext({ isGroup: checked })}
|
onCheckedChange={(checked) =>
|
||||||
|
setDebugContext({ isGroup: checked })
|
||||||
|
}
|
||||||
disabled={!canStart}
|
disabled={!canStart}
|
||||||
/>
|
/>
|
||||||
<Label>{t('workflows.debug.isGroup')}</Label>
|
<Label>{t('workflows.debug.isGroup')}</Label>
|
||||||
@@ -460,42 +566,53 @@ export default function WorkflowDebugger({ workflowId, onClose }: WorkflowDebugg
|
|||||||
{/* Custom Variables */}
|
{/* Custom Variables */}
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader className="py-3">
|
<CardHeader className="py-3">
|
||||||
<CardTitle className="text-sm">{t('workflows.debug.customVariables')}</CardTitle>
|
<CardTitle className="text-sm">
|
||||||
|
{t('workflows.debug.customVariables')}
|
||||||
|
</CardTitle>
|
||||||
<CardDescription className="text-xs">
|
<CardDescription className="text-xs">
|
||||||
{t('workflows.debug.customVariablesDesc')}
|
{t('workflows.debug.customVariablesDesc')}
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="py-2">
|
<CardContent className="py-2">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
{Object.entries(debugContext.customVariables).map(([key, value]) => (
|
{Object.entries(debugContext.customVariables).map(
|
||||||
<div key={key} className="flex items-center gap-2">
|
([key, value]) => (
|
||||||
<code className="text-xs bg-muted px-2 py-1 rounded flex-1">
|
<div key={key} className="flex items-center gap-2">
|
||||||
{key}: {JSON.stringify(value)}
|
<code className="text-xs bg-muted px-2 py-1 rounded flex-1">
|
||||||
</code>
|
{key}: {JSON.stringify(value)}
|
||||||
<Button
|
</code>
|
||||||
size="icon"
|
<Button
|
||||||
variant="ghost"
|
size="icon"
|
||||||
className="size-6"
|
variant="ghost"
|
||||||
onClick={() => handleRemoveVariable(key)}
|
className="size-6"
|
||||||
disabled={!canStart}
|
onClick={() => handleRemoveVariable(key)}
|
||||||
>
|
disabled={!canStart}
|
||||||
<X className="size-3" />
|
>
|
||||||
</Button>
|
<X className="size-3" />
|
||||||
</div>
|
</Button>
|
||||||
))}
|
</div>
|
||||||
|
),
|
||||||
|
)}
|
||||||
|
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<Input
|
<Input
|
||||||
placeholder={t('workflows.debug.variableKey')}
|
placeholder={t('workflows.debug.variableKey')}
|
||||||
value={newVariable.key}
|
value={newVariable.key}
|
||||||
onChange={(e) => setNewVariable({ ...newVariable, key: e.target.value })}
|
onChange={(e) =>
|
||||||
|
setNewVariable({ ...newVariable, key: e.target.value })
|
||||||
|
}
|
||||||
className="text-xs"
|
className="text-xs"
|
||||||
disabled={!canStart}
|
disabled={!canStart}
|
||||||
/>
|
/>
|
||||||
<Input
|
<Input
|
||||||
placeholder={t('workflows.debug.variableValue')}
|
placeholder={t('workflows.debug.variableValue')}
|
||||||
value={newVariable.value}
|
value={newVariable.value}
|
||||||
onChange={(e) => setNewVariable({ ...newVariable, value: e.target.value })}
|
onChange={(e) =>
|
||||||
|
setNewVariable({
|
||||||
|
...newVariable,
|
||||||
|
value: e.target.value,
|
||||||
|
})
|
||||||
|
}
|
||||||
className="text-xs"
|
className="text-xs"
|
||||||
disabled={!canStart}
|
disabled={!canStart}
|
||||||
/>
|
/>
|
||||||
@@ -512,7 +629,12 @@ export default function WorkflowDebugger({ workflowId, onClose }: WorkflowDebugg
|
|||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
<Button variant="outline" size="sm" onClick={resetDebugContext} disabled={!canStart}>
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={resetDebugContext}
|
||||||
|
disabled={!canStart}
|
||||||
|
>
|
||||||
<RefreshCw className="size-4 mr-2" />
|
<RefreshCw className="size-4 mr-2" />
|
||||||
{t('workflows.debug.resetContext')}
|
{t('workflows.debug.resetContext')}
|
||||||
</Button>
|
</Button>
|
||||||
@@ -524,7 +646,9 @@ export default function WorkflowDebugger({ workflowId, onClose }: WorkflowDebugg
|
|||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader className="py-3">
|
<CardHeader className="py-3">
|
||||||
<CardTitle className="text-sm">{t('workflows.debug.watchedVariables')}</CardTitle>
|
<CardTitle className="text-sm">
|
||||||
|
{t('workflows.debug.watchedVariables')}
|
||||||
|
</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="py-2">
|
<CardContent className="py-2">
|
||||||
{watchedVariables.length === 0 ? (
|
{watchedVariables.length === 0 ? (
|
||||||
@@ -549,7 +673,10 @@ export default function WorkflowDebugger({ workflowId, onClose }: WorkflowDebugg
|
|||||||
return (
|
return (
|
||||||
<div key={variable} className="flex items-center gap-2">
|
<div key={variable} className="flex items-center gap-2">
|
||||||
<code className="text-xs bg-muted px-2 py-1 rounded flex-1 font-mono">
|
<code className="text-xs bg-muted px-2 py-1 rounded flex-1 font-mono">
|
||||||
{variable} = {value !== undefined ? JSON.stringify(value) : 'undefined'}
|
{variable} ={' '}
|
||||||
|
{value !== undefined
|
||||||
|
? JSON.stringify(value)
|
||||||
|
: 'undefined'}
|
||||||
</code>
|
</code>
|
||||||
<Button
|
<Button
|
||||||
size="icon"
|
size="icon"
|
||||||
@@ -570,7 +697,9 @@ export default function WorkflowDebugger({ workflowId, onClose }: WorkflowDebugg
|
|||||||
{/* Node Outputs */}
|
{/* Node Outputs */}
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader className="py-3">
|
<CardHeader className="py-3">
|
||||||
<CardTitle className="text-sm">{t('workflows.debug.nodeOutputs')}</CardTitle>
|
<CardTitle className="text-sm">
|
||||||
|
{t('workflows.debug.nodeOutputs')}
|
||||||
|
</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="py-2">
|
<CardContent className="py-2">
|
||||||
{Object.keys(nodeExecutionResults).length === 0 ? (
|
{Object.keys(nodeExecutionResults).length === 0 ? (
|
||||||
@@ -579,33 +708,41 @@ export default function WorkflowDebugger({ workflowId, onClose }: WorkflowDebugg
|
|||||||
</p>
|
</p>
|
||||||
) : (
|
) : (
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
{Object.entries(nodeExecutionResults).map(([nodeId, result]) => {
|
{Object.entries(nodeExecutionResults).map(
|
||||||
const node = nodes.find((n) => n.id === nodeId);
|
([nodeId, result]) => {
|
||||||
return (
|
const node = nodes.find((n) => n.id === nodeId);
|
||||||
<Collapsible key={nodeId}>
|
return (
|
||||||
<CollapsibleTrigger className="flex items-center gap-2 w-full text-left">
|
<Collapsible key={nodeId}>
|
||||||
<ChevronRight className="size-4 transition-transform data-[state=open]:rotate-90" />
|
<CollapsibleTrigger className="flex items-center gap-2 w-full text-left">
|
||||||
<span className="text-sm font-medium">
|
<ChevronRight className="size-4 transition-transform data-[state=open]:rotate-90" />
|
||||||
{node?.data.label || nodeId}
|
<span className="text-sm font-medium">
|
||||||
</span>
|
{node?.data.label || nodeId}
|
||||||
{result.outputs && Object.keys(result.outputs).length > 0 && (
|
</span>
|
||||||
<Badge variant="secondary" className="text-xs">
|
{result.outputs &&
|
||||||
{Object.keys(result.outputs).length} outputs
|
Object.keys(result.outputs).length > 0 && (
|
||||||
</Badge>
|
<Badge
|
||||||
)}
|
variant="secondary"
|
||||||
</CollapsibleTrigger>
|
className="text-xs"
|
||||||
<CollapsibleContent className="pl-6 pt-2">
|
>
|
||||||
{result.outputs ? (
|
{Object.keys(result.outputs).length} outputs
|
||||||
<pre className="text-xs bg-muted p-2 rounded overflow-auto max-h-40">
|
</Badge>
|
||||||
{JSON.stringify(result.outputs, null, 2)}
|
)}
|
||||||
</pre>
|
</CollapsibleTrigger>
|
||||||
) : (
|
<CollapsibleContent className="pl-6 pt-2">
|
||||||
<p className="text-xs text-muted-foreground">No outputs</p>
|
{result.outputs ? (
|
||||||
)}
|
<pre className="text-xs bg-muted p-2 rounded overflow-auto max-h-40">
|
||||||
</CollapsibleContent>
|
{JSON.stringify(result.outputs, null, 2)}
|
||||||
</Collapsible>
|
</pre>
|
||||||
);
|
) : (
|
||||||
})}
|
<p className="text-xs text-muted-foreground">
|
||||||
|
No outputs
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</CollapsibleContent>
|
||||||
|
</Collapsible>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</CardContent>
|
</CardContent>
|
||||||
@@ -618,8 +755,12 @@ export default function WorkflowDebugger({ workflowId, onClose }: WorkflowDebugg
|
|||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
{nodes.map((node) => {
|
{nodes.map((node) => {
|
||||||
const result = nodeExecutionResults[node.id];
|
const result = nodeExecutionResults[node.id];
|
||||||
const StatusIcon = result ? statusIcons[result.status] || Clock : Clock;
|
const StatusIcon = result
|
||||||
const statusColor = result ? statusColors[result.status] || '' : 'text-gray-400';
|
? statusIcons[result.status] || Clock
|
||||||
|
: Clock;
|
||||||
|
const statusColor = result
|
||||||
|
? statusColors[result.status] || ''
|
||||||
|
: 'text-gray-400';
|
||||||
const isCurrentNode = currentNodeId === node.id;
|
const isCurrentNode = currentNodeId === node.id;
|
||||||
const hasBreakpoint = !!breakpoints[node.id];
|
const hasBreakpoint = !!breakpoints[node.id];
|
||||||
|
|
||||||
@@ -641,7 +782,9 @@ export default function WorkflowDebugger({ workflowId, onClose }: WorkflowDebugg
|
|||||||
title={t('workflows.debug.toggleBreakpoint')}
|
title={t('workflows.debug.toggleBreakpoint')}
|
||||||
/>
|
/>
|
||||||
<StatusIcon className={`size-4 ${statusColor}`} />
|
<StatusIcon className={`size-4 ${statusColor}`} />
|
||||||
<span className="text-sm font-medium">{node.data.label}</span>
|
<span className="text-sm font-medium">
|
||||||
|
{node.data.label}
|
||||||
|
</span>
|
||||||
<Badge variant="outline" className="text-xs">
|
<Badge variant="outline" className="text-xs">
|
||||||
{node.data.type}
|
{node.data.type}
|
||||||
</Badge>
|
</Badge>
|
||||||
@@ -675,7 +818,9 @@ export default function WorkflowDebugger({ workflowId, onClose }: WorkflowDebugg
|
|||||||
onCheckedChange={setAutoScroll}
|
onCheckedChange={setAutoScroll}
|
||||||
className="scale-75"
|
className="scale-75"
|
||||||
/>
|
/>
|
||||||
<Label className="text-xs">{t('workflows.debug.autoScroll')}</Label>
|
<Label className="text-xs">
|
||||||
|
{t('workflows.debug.autoScroll')}
|
||||||
|
</Label>
|
||||||
</div>
|
</div>
|
||||||
<span className="text-xs text-muted-foreground">
|
<span className="text-xs text-muted-foreground">
|
||||||
{debugLogs.length} {t('workflows.debug.logEntries')}
|
{debugLogs.length} {t('workflows.debug.logEntries')}
|
||||||
@@ -684,18 +829,24 @@ export default function WorkflowDebugger({ workflowId, onClose }: WorkflowDebugg
|
|||||||
<ScrollArea className="flex-1 p-4">
|
<ScrollArea className="flex-1 p-4">
|
||||||
<div className="space-y-1 font-mono text-xs">
|
<div className="space-y-1 font-mono text-xs">
|
||||||
{debugLogs.length === 0 ? (
|
{debugLogs.length === 0 ? (
|
||||||
<p className="text-muted-foreground">{t('workflows.debug.noLogs')}</p>
|
<p className="text-muted-foreground">
|
||||||
|
{t('workflows.debug.noLogs')}
|
||||||
|
</p>
|
||||||
) : (
|
) : (
|
||||||
debugLogs.map((log) => (
|
debugLogs.map((log) => (
|
||||||
<div key={log.id} className="flex gap-2">
|
<div key={log.id} className="flex gap-2">
|
||||||
<span className="text-muted-foreground shrink-0">
|
<span className="text-muted-foreground shrink-0">
|
||||||
{new Date(log.timestamp).toLocaleTimeString()}
|
{new Date(log.timestamp).toLocaleTimeString()}
|
||||||
</span>
|
</span>
|
||||||
<span className={`shrink-0 uppercase ${logLevelColors[log.level]}`}>
|
<span
|
||||||
|
className={`shrink-0 uppercase ${logLevelColors[log.level]}`}
|
||||||
|
>
|
||||||
[{log.level}]
|
[{log.level}]
|
||||||
</span>
|
</span>
|
||||||
{log.nodeId && (
|
{log.nodeId && (
|
||||||
<span className="text-purple-400 shrink-0">[{log.nodeId}]</span>
|
<span className="text-purple-400 shrink-0">
|
||||||
|
[{log.nodeId}]
|
||||||
|
</span>
|
||||||
)}
|
)}
|
||||||
<span className="text-foreground">{log.message}</span>
|
<span className="text-foreground">{log.message}</span>
|
||||||
{log.data && (
|
{log.data && (
|
||||||
|
|||||||
@@ -46,7 +46,9 @@ interface NodeTypeForUI {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Default node types generated from shared constants
|
// Default node types generated from shared constants
|
||||||
const defaultNodeTypes: NodeTypeForUI[] = Object.entries(NODE_TYPE_I18N_KEYS).map(([type, keys]) => ({
|
const defaultNodeTypes: NodeTypeForUI[] = Object.entries(
|
||||||
|
NODE_TYPE_I18N_KEYS,
|
||||||
|
).map(([type, keys]) => ({
|
||||||
type,
|
type,
|
||||||
category: type.split('.')[0],
|
category: type.split('.')[0],
|
||||||
labelKey: keys.labelKey,
|
labelKey: keys.labelKey,
|
||||||
@@ -56,45 +58,51 @@ const defaultNodeTypes: NodeTypeForUI[] = Object.entries(NODE_TYPE_I18N_KEYS).ma
|
|||||||
export default function NodePalette() {
|
export default function NodePalette() {
|
||||||
const { t, i18n } = useTranslation();
|
const { t, i18n } = useTranslation();
|
||||||
const { nodeTypes: backendNodeTypes, nodeCategories } = useWorkflowStore();
|
const { nodeTypes: backendNodeTypes, nodeCategories } = useWorkflowStore();
|
||||||
|
|
||||||
// Search state
|
// Search state
|
||||||
const [searchQuery, setSearchQuery] = useState('');
|
const [searchQuery, setSearchQuery] = useState('');
|
||||||
|
|
||||||
// Expanded categories state
|
// Expanded categories state
|
||||||
const [expandedCategories, setExpandedCategories] = useState<Set<string>>(
|
const [expandedCategories, setExpandedCategories] = useState<Set<string>>(
|
||||||
new Set(['trigger', 'process', 'control', 'action', 'integration'])
|
new Set(['trigger', 'process', 'control', 'action', 'integration']),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Helper: get label string for a node using i18n
|
// Helper: get label string for a node using i18n
|
||||||
const getNodeLabel = useCallback((node: NodeTypeForUI): string => {
|
const getNodeLabel = useCallback(
|
||||||
if (node.labelKey) {
|
(node: NodeTypeForUI): string => {
|
||||||
return t(node.labelKey, { defaultValue: node.labelKey });
|
if (node.labelKey) {
|
||||||
}
|
return t(node.labelKey, { defaultValue: node.labelKey });
|
||||||
if (node.label) {
|
}
|
||||||
const labelValue = resolveI18nLabel(node.label);
|
if (node.label) {
|
||||||
return labelValue || node.type;
|
const labelValue = resolveI18nLabel(node.label);
|
||||||
}
|
return labelValue || node.type;
|
||||||
return node.type;
|
}
|
||||||
}, [t]);
|
return node.type;
|
||||||
|
},
|
||||||
|
[t],
|
||||||
|
);
|
||||||
|
|
||||||
// Helper: get description string for a node using i18n
|
// Helper: get description string for a node using i18n
|
||||||
const getNodeDescription = useCallback((node: NodeTypeForUI): string => {
|
const getNodeDescription = useCallback(
|
||||||
if (node.descriptionKey) {
|
(node: NodeTypeForUI): string => {
|
||||||
return t(node.descriptionKey, { defaultValue: '' });
|
if (node.descriptionKey) {
|
||||||
}
|
return t(node.descriptionKey, { defaultValue: '' });
|
||||||
if (node.description) {
|
}
|
||||||
const descValue = resolveI18nLabel(node.description);
|
if (node.description) {
|
||||||
return descValue || '';
|
const descValue = resolveI18nLabel(node.description);
|
||||||
}
|
return descValue || '';
|
||||||
return '';
|
}
|
||||||
}, [t]);
|
return '';
|
||||||
|
},
|
||||||
|
[t],
|
||||||
|
);
|
||||||
|
|
||||||
// Use backend node types if available, otherwise use defaults
|
// Use backend node types if available, otherwise use defaults
|
||||||
const nodeTypes = useMemo((): NodeTypeForUI[] => {
|
const nodeTypes = useMemo((): NodeTypeForUI[] => {
|
||||||
if (backendNodeTypes && backendNodeTypes.length > 0) {
|
if (backendNodeTypes && backendNodeTypes.length > 0) {
|
||||||
return backendNodeTypes.map((node) => {
|
return backendNodeTypes.map((node) => {
|
||||||
const i18nKeys = findNodeI18nKeys(node.type);
|
const i18nKeys = findNodeI18nKeys(node.type);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
type: node.type,
|
type: node.type,
|
||||||
category: node.category,
|
category: node.category,
|
||||||
@@ -129,13 +137,19 @@ export default function NodePalette() {
|
|||||||
// Group filtered nodes by category
|
// Group filtered nodes by category
|
||||||
const groupedNodes = useMemo(() => {
|
const groupedNodes = useMemo(() => {
|
||||||
const groups: Record<string, typeof nodeTypes> = {};
|
const groups: Record<string, typeof nodeTypes> = {};
|
||||||
const categoryOrder = ['trigger', 'process', 'control', 'action', 'integration'];
|
const categoryOrder = [
|
||||||
|
'trigger',
|
||||||
|
'process',
|
||||||
|
'control',
|
||||||
|
'action',
|
||||||
|
'integration',
|
||||||
|
];
|
||||||
|
|
||||||
// Initialize groups in order
|
// Initialize groups in order
|
||||||
categoryOrder.forEach((cat) => {
|
categoryOrder.forEach((cat) => {
|
||||||
groups[cat] = [];
|
groups[cat] = [];
|
||||||
});
|
});
|
||||||
|
|
||||||
for (const node of filteredNodes) {
|
for (const node of filteredNodes) {
|
||||||
const category = node.category || node.type.split('.')[0];
|
const category = node.category || node.type.split('.')[0];
|
||||||
if (!groups[category]) {
|
if (!groups[category]) {
|
||||||
@@ -143,14 +157,14 @@ export default function NodePalette() {
|
|||||||
}
|
}
|
||||||
groups[category].push(node);
|
groups[category].push(node);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove empty categories
|
// Remove empty categories
|
||||||
Object.keys(groups).forEach((key) => {
|
Object.keys(groups).forEach((key) => {
|
||||||
if (groups[key].length === 0) {
|
if (groups[key].length === 0) {
|
||||||
delete groups[key];
|
delete groups[key];
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return groups;
|
return groups;
|
||||||
}, [filteredNodes]);
|
}, [filteredNodes]);
|
||||||
|
|
||||||
@@ -174,7 +188,7 @@ export default function NodePalette() {
|
|||||||
event.dataTransfer.effectAllowed = 'move';
|
event.dataTransfer.effectAllowed = 'move';
|
||||||
event.dataTransfer.setData('text/plain', nodeType);
|
event.dataTransfer.setData('text/plain', nodeType);
|
||||||
},
|
},
|
||||||
[]
|
[],
|
||||||
);
|
);
|
||||||
|
|
||||||
// Get category label using i18n
|
// Get category label using i18n
|
||||||
@@ -189,13 +203,18 @@ export default function NodePalette() {
|
|||||||
if (category?.label) {
|
if (category?.label) {
|
||||||
const lang = i18n.language;
|
const lang = i18n.language;
|
||||||
if (lang.startsWith('zh')) {
|
if (lang.startsWith('zh')) {
|
||||||
return category.label['zh-CN'] || category.label['zh-Hans'] || category.label['en'] || categoryName;
|
return (
|
||||||
|
category.label['zh-CN'] ||
|
||||||
|
category.label['zh-Hans'] ||
|
||||||
|
category.label['en'] ||
|
||||||
|
categoryName
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return category.label['en'] || category.label['en-US'] || categoryName;
|
return category.label['en'] || category.label['en-US'] || categoryName;
|
||||||
}
|
}
|
||||||
return categoryName;
|
return categoryName;
|
||||||
},
|
},
|
||||||
[nodeCategories, t, i18n.language]
|
[nodeCategories, t, i18n.language],
|
||||||
);
|
);
|
||||||
|
|
||||||
// Clear search
|
// Clear search
|
||||||
@@ -204,131 +223,142 @@ export default function NodePalette() {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="h-full flex flex-col">
|
<div className="h-full flex flex-col">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="p-3 border-b">
|
<div className="p-3 border-b">
|
||||||
<h3 className="font-semibold text-sm mb-3 flex items-center gap-2">
|
<h3 className="font-semibold text-sm mb-3 flex items-center gap-2">
|
||||||
<Layers className="size-4" />
|
<Layers className="size-4" />
|
||||||
{t('workflows.nodePalette')}
|
{t('workflows.nodePalette')}
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
{/* Search input */}
|
{/* Search input */}
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<Search className="absolute left-2.5 top-1/2 -translate-y-1/2 size-4 text-muted-foreground" />
|
<Search className="absolute left-2.5 top-1/2 -translate-y-1/2 size-4 text-muted-foreground" />
|
||||||
<Input
|
<Input
|
||||||
type="text"
|
type="text"
|
||||||
placeholder={t('workflows.searchNodes')}
|
placeholder={t('workflows.searchNodes')}
|
||||||
value={searchQuery}
|
value={searchQuery}
|
||||||
onChange={(e) => setSearchQuery(e.target.value)}
|
onChange={(e) => setSearchQuery(e.target.value)}
|
||||||
className="pl-8 pr-8 h-8 text-sm"
|
className="pl-8 pr-8 h-8 text-sm"
|
||||||
/>
|
/>
|
||||||
{searchQuery && (
|
{searchQuery && (
|
||||||
|
<button
|
||||||
|
onClick={clearSearch}
|
||||||
|
className="absolute right-2 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground"
|
||||||
|
>
|
||||||
|
×
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Node list */}
|
||||||
|
<div className="flex-1 min-h-0 overflow-y-auto">
|
||||||
|
<div className="p-2 space-y-1">
|
||||||
|
{/* Show loading state if no nodes */}
|
||||||
|
{Object.keys(groupedNodes).length === 0 && !searchQuery && (
|
||||||
|
<div className="text-sm text-muted-foreground text-center py-8">
|
||||||
|
<Cpu className="size-8 mx-auto mb-2 opacity-50" />
|
||||||
|
<p>{t('workflows.loadingNodeTypes')}</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Show no results message */}
|
||||||
|
{Object.keys(groupedNodes).length === 0 && searchQuery && (
|
||||||
|
<div className="text-sm text-muted-foreground text-center py-8">
|
||||||
|
<Search className="size-8 mx-auto mb-2 opacity-50" />
|
||||||
|
<p>{t('workflows.noNodesFound')}</p>
|
||||||
<button
|
<button
|
||||||
onClick={clearSearch}
|
onClick={clearSearch}
|
||||||
className="absolute right-2 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground"
|
className="text-primary hover:underline mt-2"
|
||||||
>
|
>
|
||||||
×
|
{t('workflows.clearSearch')}
|
||||||
</button>
|
</button>
|
||||||
)}
|
</div>
|
||||||
</div>
|
)}
|
||||||
</div>
|
|
||||||
|
{/* Category groups */}
|
||||||
{/* Node list */}
|
{Object.entries(groupedNodes).map(([category, nodes]) => {
|
||||||
<div className="flex-1 min-h-0 overflow-y-auto">
|
const isExpanded = expandedCategories.has(category);
|
||||||
<div className="p-2 space-y-1">
|
const CategoryIcon = categoryIcons[category] || Settings;
|
||||||
{/* Show loading state if no nodes */}
|
const ChevronIcon = isExpanded ? ChevronDown : ChevronRight;
|
||||||
{Object.keys(groupedNodes).length === 0 && !searchQuery && (
|
|
||||||
<div className="text-sm text-muted-foreground text-center py-8">
|
return (
|
||||||
<Cpu className="size-8 mx-auto mb-2 opacity-50" />
|
<div key={category} className="mb-1">
|
||||||
<p>{t('workflows.loadingNodeTypes')}</p>
|
{/* Category header */}
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Show no results message */}
|
|
||||||
{Object.keys(groupedNodes).length === 0 && searchQuery && (
|
|
||||||
<div className="text-sm text-muted-foreground text-center py-8">
|
|
||||||
<Search className="size-8 mx-auto mb-2 opacity-50" />
|
|
||||||
<p>{t('workflows.noNodesFound')}</p>
|
|
||||||
<button
|
<button
|
||||||
onClick={clearSearch}
|
onClick={() => toggleCategory(category)}
|
||||||
className="text-primary hover:underline mt-2"
|
className={cn(
|
||||||
|
'w-full flex items-center gap-2 px-2 py-1.5 rounded-md text-sm font-medium transition-colors',
|
||||||
|
categoryBgColors[category],
|
||||||
|
categoryColors[category],
|
||||||
|
)}
|
||||||
>
|
>
|
||||||
{t('workflows.clearSearch')}
|
<ChevronIcon className="size-4 flex-shrink-0" />
|
||||||
|
<CategoryIcon className="size-4 flex-shrink-0" />
|
||||||
|
<span className="flex-1 text-left">
|
||||||
|
{getCategoryLabel(category)}
|
||||||
|
</span>
|
||||||
|
<Badge variant="secondary" className="text-xs px-1.5 py-0">
|
||||||
|
{nodes.length}
|
||||||
|
</Badge>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Category groups */}
|
|
||||||
{Object.entries(groupedNodes).map(([category, nodes]) => {
|
|
||||||
const isExpanded = expandedCategories.has(category);
|
|
||||||
const CategoryIcon = categoryIcons[category] || Settings;
|
|
||||||
const ChevronIcon = isExpanded ? ChevronDown : ChevronRight;
|
|
||||||
|
|
||||||
return (
|
{/* Node list */}
|
||||||
<div key={category} className="mb-1">
|
{isExpanded && (
|
||||||
{/* Category header */}
|
<div className="mt-1 space-y-0.5 ml-2">
|
||||||
<button
|
{nodes.map((node) => {
|
||||||
onClick={() => toggleCategory(category)}
|
const Icon = nodeIcons[node.type] || Settings;
|
||||||
className={cn(
|
const label = getNodeLabel(node);
|
||||||
'w-full flex items-center gap-2 px-2 py-1.5 rounded-md text-sm font-medium transition-colors',
|
const description = getNodeDescription(node);
|
||||||
categoryBgColors[category],
|
|
||||||
categoryColors[category]
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<ChevronIcon className="size-4 flex-shrink-0" />
|
|
||||||
<CategoryIcon className="size-4 flex-shrink-0" />
|
|
||||||
<span className="flex-1 text-left">{getCategoryLabel(category)}</span>
|
|
||||||
<Badge variant="secondary" className="text-xs px-1.5 py-0">
|
|
||||||
{nodes.length}
|
|
||||||
</Badge>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
{/* Node list */}
|
return (
|
||||||
{isExpanded && (
|
<div
|
||||||
<div className="mt-1 space-y-0.5 ml-2">
|
key={node.type}
|
||||||
{nodes.map((node) => {
|
draggable
|
||||||
const Icon = nodeIcons[node.type] || Settings;
|
onDragStart={(e) => onDragStart(e, node.type)}
|
||||||
const label = getNodeLabel(node);
|
className={cn(
|
||||||
const description = getNodeDescription(node);
|
'flex items-center gap-2 px-2 py-1.5 rounded-md cursor-grab select-none',
|
||||||
|
'hover:bg-muted/80 active:cursor-grabbing transition-colors',
|
||||||
return (
|
'border border-transparent hover:border-border',
|
||||||
|
'group',
|
||||||
|
)}
|
||||||
|
title={description || label}
|
||||||
|
>
|
||||||
<div
|
<div
|
||||||
key={node.type}
|
|
||||||
draggable
|
|
||||||
onDragStart={(e) => onDragStart(e, node.type)}
|
|
||||||
className={cn(
|
className={cn(
|
||||||
'flex items-center gap-2 px-2 py-1.5 rounded-md cursor-grab select-none',
|
|
||||||
'hover:bg-muted/80 active:cursor-grabbing transition-colors',
|
|
||||||
'border border-transparent hover:border-border',
|
|
||||||
'group'
|
|
||||||
)}
|
|
||||||
title={description || label}
|
|
||||||
>
|
|
||||||
<div className={cn(
|
|
||||||
'p-1 rounded',
|
'p-1 rounded',
|
||||||
categoryBgColors[category],
|
categoryBgColors[category],
|
||||||
categoryBorderColors[category],
|
categoryBorderColors[category],
|
||||||
'border'
|
'border',
|
||||||
)}>
|
)}
|
||||||
<Icon className={cn('size-3.5', categoryColors[category])} />
|
>
|
||||||
</div>
|
<Icon
|
||||||
<span className="text-sm truncate flex-1">{label}</span>
|
className={cn(
|
||||||
<ExternalLink className="size-3 opacity-0 group-hover:opacity-50 transition-opacity" />
|
'size-3.5',
|
||||||
|
categoryColors[category],
|
||||||
|
)}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
<span className="text-sm truncate flex-1">
|
||||||
})}
|
{label}
|
||||||
</div>
|
</span>
|
||||||
)}
|
<ExternalLink className="size-3 opacity-0 group-hover:opacity-50 transition-opacity" />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
)}
|
||||||
|
</div>
|
||||||
{/* Footer hint */}
|
);
|
||||||
<div className="p-2 border-t text-xs text-muted-foreground text-center">
|
})}
|
||||||
{t('workflows.dragToAdd')}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Footer hint */}
|
||||||
|
<div className="p-2 border-t text-xs text-muted-foreground text-center">
|
||||||
|
{t('workflows.dragToAdd')}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,7 +60,9 @@ const translateIfKey = (value: string | undefined): string | undefined => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Delegate to shared utility
|
// Delegate to shared utility
|
||||||
const extractI18nLabel = (obj: Record<string, string> | I18nObject | undefined): string | undefined => {
|
const extractI18nLabel = (
|
||||||
|
obj: Record<string, string> | I18nObject | undefined,
|
||||||
|
): string | undefined => {
|
||||||
if (!obj) return undefined;
|
if (!obj) return undefined;
|
||||||
const result = resolveI18nLabel(obj as Record<string, string>);
|
const result = resolveI18nLabel(obj as Record<string, string>);
|
||||||
return result || undefined;
|
return result || undefined;
|
||||||
@@ -112,23 +114,28 @@ const resolvePortDisplayLabel = (
|
|||||||
|
|
||||||
function VariableReference({
|
function VariableReference({
|
||||||
variable,
|
variable,
|
||||||
onCopy
|
onCopy,
|
||||||
}: {
|
}: {
|
||||||
variable: { name: string; label?: string; type?: string };
|
variable: { name: string; label?: string; type?: string };
|
||||||
onCopy: (ref: string) => void;
|
onCopy: (ref: string) => void;
|
||||||
}) {
|
}) {
|
||||||
const ref = `{{${variable.name}}}`;
|
const ref = `{{${variable.name}}}`;
|
||||||
const displayName = variable.label || formatVariableName(variable.name);
|
const displayName = variable.label || formatVariableName(variable.name);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger asChild>
|
<TooltipTrigger asChild>
|
||||||
<div className="flex items-center justify-between py-1 px-2 rounded bg-muted/50 text-sm group overflow-hidden w-full cursor-default">
|
<div className="flex items-center justify-between py-1 px-2 rounded bg-muted/50 text-sm group overflow-hidden w-full cursor-default">
|
||||||
<div className="flex items-center gap-2 min-w-0 flex-1 overflow-hidden">
|
<div className="flex items-center gap-2 min-w-0 flex-1 overflow-hidden">
|
||||||
<Variable className="size-3.5 text-muted-foreground flex-shrink-0" />
|
<Variable className="size-3.5 text-muted-foreground flex-shrink-0" />
|
||||||
<span className="truncate font-mono text-xs min-w-0">{displayName}</span>
|
<span className="truncate font-mono text-xs min-w-0">
|
||||||
|
{displayName}
|
||||||
|
</span>
|
||||||
{variable.type && (
|
{variable.type && (
|
||||||
<Badge variant="outline" className="text-[10px] px-1 py-0 flex-shrink-0">
|
<Badge
|
||||||
|
variant="outline"
|
||||||
|
className="text-[10px] px-1 py-0 flex-shrink-0"
|
||||||
|
>
|
||||||
{getTypeLabel(variable.type)}
|
{getTypeLabel(variable.type)}
|
||||||
</Badge>
|
</Badge>
|
||||||
)}
|
)}
|
||||||
@@ -165,9 +172,13 @@ function CollapsibleSection({
|
|||||||
badge?: React.ReactNode;
|
badge?: React.ReactNode;
|
||||||
}) {
|
}) {
|
||||||
const [isOpen, setIsOpen] = useState(defaultOpen);
|
const [isOpen, setIsOpen] = useState(defaultOpen);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Collapsible open={isOpen} onOpenChange={setIsOpen} className="w-full overflow-hidden">
|
<Collapsible
|
||||||
|
open={isOpen}
|
||||||
|
onOpenChange={setIsOpen}
|
||||||
|
className="w-full overflow-hidden"
|
||||||
|
>
|
||||||
<CollapsibleTrigger asChild>
|
<CollapsibleTrigger asChild>
|
||||||
<button className="w-full flex items-center gap-2 py-2 px-1 hover:bg-muted/50 rounded transition-colors min-w-0 overflow-hidden">
|
<button className="w-full flex items-center gap-2 py-2 px-1 hover:bg-muted/50 rounded transition-colors min-w-0 overflow-hidden">
|
||||||
{isOpen ? (
|
{isOpen ? (
|
||||||
@@ -176,14 +187,14 @@ function CollapsibleSection({
|
|||||||
<ChevronRight className="size-4 text-muted-foreground flex-shrink-0" />
|
<ChevronRight className="size-4 text-muted-foreground flex-shrink-0" />
|
||||||
)}
|
)}
|
||||||
<Icon className="size-4 text-muted-foreground flex-shrink-0" />
|
<Icon className="size-4 text-muted-foreground flex-shrink-0" />
|
||||||
<span className="font-medium text-sm flex-1 text-left truncate min-w-0">{title}</span>
|
<span className="font-medium text-sm flex-1 text-left truncate min-w-0">
|
||||||
|
{title}
|
||||||
|
</span>
|
||||||
{badge}
|
{badge}
|
||||||
</button>
|
</button>
|
||||||
</CollapsibleTrigger>
|
</CollapsibleTrigger>
|
||||||
<CollapsibleContent className="pl-6 pr-1 pb-2 w-full overflow-hidden">
|
<CollapsibleContent className="pl-6 pr-1 pb-2 w-full overflow-hidden">
|
||||||
<div className="w-full overflow-hidden">
|
<div className="w-full overflow-hidden">{children}</div>
|
||||||
{children}
|
|
||||||
</div>
|
|
||||||
</CollapsibleContent>
|
</CollapsibleContent>
|
||||||
</Collapsible>
|
</Collapsible>
|
||||||
);
|
);
|
||||||
@@ -243,9 +254,12 @@ export default function PropertyPanel({
|
|||||||
// form item definitions, i18n labels/descriptions and option labels consistently.
|
// 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.
|
// Fall back to backend metadata for nodes that do not exist in the local registry.
|
||||||
const configSchema = useMemo(() => {
|
const configSchema = useMemo(() => {
|
||||||
const localConfigSchema = (localNodeConfig?.configSchema as IDynamicFormItemSchema[]) || [];
|
const localConfigSchema =
|
||||||
const backendConfigSchema = (nodeTypeMeta?.config_schema as IDynamicFormItemSchema[]) || [];
|
(localNodeConfig?.configSchema as IDynamicFormItemSchema[]) || [];
|
||||||
const rawConfigSchema = localConfigSchema.length > 0 ? localConfigSchema : backendConfigSchema;
|
const backendConfigSchema =
|
||||||
|
(nodeTypeMeta?.config_schema as IDynamicFormItemSchema[]) || [];
|
||||||
|
const rawConfigSchema =
|
||||||
|
localConfigSchema.length > 0 ? localConfigSchema : backendConfigSchema;
|
||||||
|
|
||||||
return rawConfigSchema.map((item) => {
|
return rawConfigSchema.map((item) => {
|
||||||
const backendItem = backendConfigSchema.find(
|
const backendItem = backendConfigSchema.find(
|
||||||
@@ -255,14 +269,16 @@ export default function PropertyPanel({
|
|||||||
return {
|
return {
|
||||||
...(backendItem || {}),
|
...(backendItem || {}),
|
||||||
...item,
|
...item,
|
||||||
label: item.label || backendItem?.label || {
|
label: item.label ||
|
||||||
en_US: item.name,
|
backendItem?.label || {
|
||||||
zh_Hans: item.name,
|
en_US: item.name,
|
||||||
},
|
zh_Hans: item.name,
|
||||||
description: item.description || backendItem?.description || {
|
},
|
||||||
en_US: '',
|
description: item.description ||
|
||||||
zh_Hans: '',
|
backendItem?.description || {
|
||||||
},
|
en_US: '',
|
||||||
|
zh_Hans: '',
|
||||||
|
},
|
||||||
options: item.options || backendItem?.options,
|
options: item.options || backendItem?.options,
|
||||||
show_if: item.show_if || backendItem?.show_if,
|
show_if: item.show_if || backendItem?.show_if,
|
||||||
};
|
};
|
||||||
@@ -272,13 +288,17 @@ export default function PropertyPanel({
|
|||||||
// Get available input variables from connected upstream nodes
|
// Get available input variables from connected upstream nodes
|
||||||
const availableInputVariables = useMemo(() => {
|
const availableInputVariables = useMemo(() => {
|
||||||
if (!selectedNode) return [];
|
if (!selectedNode) return [];
|
||||||
|
|
||||||
const variables: { nodeId: string; nodeLabel: string; outputs: { name: string; label?: string; type?: string }[] }[] = [];
|
const variables: {
|
||||||
|
nodeId: string;
|
||||||
|
nodeLabel: string;
|
||||||
|
outputs: { name: string; label?: string; type?: string }[];
|
||||||
|
}[] = [];
|
||||||
|
|
||||||
// Find all upstream nodes
|
// Find all upstream nodes
|
||||||
const incomingEdges = edges.filter((e) => e.target === selectedNode.id);
|
const incomingEdges = edges.filter((e) => e.target === selectedNode.id);
|
||||||
const upstreamNodeIds = incomingEdges.map((e) => e.source);
|
const upstreamNodeIds = incomingEdges.map((e) => e.source);
|
||||||
|
|
||||||
for (const nodeId of upstreamNodeIds) {
|
for (const nodeId of upstreamNodeIds) {
|
||||||
const node = nodes.find((n) => n.id === nodeId);
|
const node = nodes.find((n) => n.id === nodeId);
|
||||||
if (node) {
|
if (node) {
|
||||||
@@ -294,20 +314,40 @@ export default function PropertyPanel({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add global variables
|
// Add global variables
|
||||||
variables.push({
|
variables.push({
|
||||||
nodeId: '__global__',
|
nodeId: '__global__',
|
||||||
nodeLabel: t('workflows.globalVariables'),
|
nodeLabel: t('workflows.globalVariables'),
|
||||||
outputs: [
|
outputs: [
|
||||||
{ name: 'message.content', label: t('workflows.messageContent'), type: 'string' },
|
{
|
||||||
{ name: 'message.sender', label: t('workflows.messageSender'), type: 'string' },
|
name: 'message.content',
|
||||||
{ name: 'context.platform', label: t('workflows.platform'), type: 'string' },
|
label: t('workflows.messageContent'),
|
||||||
{ name: 'context.session_id', label: t('workflows.sessionId'), type: 'string' },
|
type: 'string',
|
||||||
{ name: 'context.timestamp', label: t('workflows.timestamp'), type: 'datetime' },
|
},
|
||||||
|
{
|
||||||
|
name: 'message.sender',
|
||||||
|
label: t('workflows.messageSender'),
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'context.platform',
|
||||||
|
label: t('workflows.platform'),
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'context.session_id',
|
||||||
|
label: t('workflows.sessionId'),
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'context.timestamp',
|
||||||
|
label: t('workflows.timestamp'),
|
||||||
|
type: 'datetime',
|
||||||
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
return variables;
|
return variables;
|
||||||
}, [selectedNode, edges, nodes, t]);
|
}, [selectedNode, edges, nodes, t]);
|
||||||
|
|
||||||
@@ -318,7 +358,7 @@ export default function PropertyPanel({
|
|||||||
updateNodeLabel(selectedNodeId, e.target.value);
|
updateNodeLabel(selectedNodeId, e.target.value);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[selectedNodeId, updateNodeLabel]
|
[selectedNodeId, updateNodeLabel],
|
||||||
);
|
);
|
||||||
|
|
||||||
// Handle config change from dynamic form
|
// Handle config change from dynamic form
|
||||||
@@ -330,7 +370,7 @@ export default function PropertyPanel({
|
|||||||
}
|
}
|
||||||
return undefined;
|
return undefined;
|
||||||
},
|
},
|
||||||
[selectedNodeId, updateNodeConfig, pushHistory]
|
[selectedNodeId, updateNodeConfig, pushHistory],
|
||||||
);
|
);
|
||||||
|
|
||||||
// Handle node delete
|
// Handle node delete
|
||||||
@@ -354,14 +394,17 @@ export default function PropertyPanel({
|
|||||||
updateEdgeCondition(selectedEdgeId, e.target.value);
|
updateEdgeCondition(selectedEdgeId, e.target.value);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[selectedEdgeId, updateEdgeCondition]
|
[selectedEdgeId, updateEdgeCondition],
|
||||||
);
|
);
|
||||||
|
|
||||||
// Copy variable reference
|
// Copy variable reference
|
||||||
const handleCopyVariable = useCallback((ref: string) => {
|
const handleCopyVariable = useCallback(
|
||||||
navigator.clipboard.writeText(ref);
|
(ref: string) => {
|
||||||
toast.success(t('common.copySuccess'));
|
navigator.clipboard.writeText(ref);
|
||||||
}, [t]);
|
toast.success(t('common.copySuccess'));
|
||||||
|
},
|
||||||
|
[t],
|
||||||
|
);
|
||||||
|
|
||||||
// No selection
|
// No selection
|
||||||
if (!selectedNodeId && !selectedEdgeId) {
|
if (!selectedNodeId && !selectedEdgeId) {
|
||||||
@@ -408,11 +451,17 @@ export default function PropertyPanel({
|
|||||||
{/* Connection info */}
|
{/* Connection info */}
|
||||||
<div className="bg-muted/50 p-3 rounded-lg w-full overflow-hidden">
|
<div className="bg-muted/50 p-3 rounded-lg w-full overflow-hidden">
|
||||||
<div className="flex items-center gap-2 text-sm min-w-0 w-full overflow-hidden">
|
<div className="flex items-center gap-2 text-sm min-w-0 w-full overflow-hidden">
|
||||||
<Badge variant="outline" className="font-mono text-xs truncate max-w-[35%] flex-shrink min-w-0">
|
<Badge
|
||||||
|
variant="outline"
|
||||||
|
className="font-mono text-xs truncate max-w-[35%] flex-shrink min-w-0"
|
||||||
|
>
|
||||||
{sourceNode?.data.label || selectedEdge.source}
|
{sourceNode?.data.label || selectedEdge.source}
|
||||||
</Badge>
|
</Badge>
|
||||||
<ArrowRight className="size-4 text-muted-foreground flex-shrink-0" />
|
<ArrowRight className="size-4 text-muted-foreground flex-shrink-0" />
|
||||||
<Badge variant="outline" className="font-mono text-xs truncate max-w-[35%] flex-shrink min-w-0">
|
<Badge
|
||||||
|
variant="outline"
|
||||||
|
className="font-mono text-xs truncate max-w-[35%] flex-shrink min-w-0"
|
||||||
|
>
|
||||||
{targetNode?.data.label || selectedEdge.target}
|
{targetNode?.data.label || selectedEdge.target}
|
||||||
</Badge>
|
</Badge>
|
||||||
</div>
|
</div>
|
||||||
@@ -422,11 +471,16 @@ export default function PropertyPanel({
|
|||||||
<CollapsibleSection
|
<CollapsibleSection
|
||||||
title={t('workflows.condition')}
|
title={t('workflows.condition')}
|
||||||
icon={Code}
|
icon={Code}
|
||||||
badge={selectedEdge.data?.condition ? (
|
badge={
|
||||||
<Badge variant="secondary" className="text-xs flex-shrink-0">
|
selectedEdge.data?.condition ? (
|
||||||
{t('workflows.hasCondition')}
|
<Badge
|
||||||
</Badge>
|
variant="secondary"
|
||||||
) : null}
|
className="text-xs flex-shrink-0"
|
||||||
|
>
|
||||||
|
{t('workflows.hasCondition')}
|
||||||
|
</Badge>
|
||||||
|
) : null
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<div className="space-y-2 w-full overflow-hidden">
|
<div className="space-y-2 w-full overflow-hidden">
|
||||||
<Textarea
|
<Textarea
|
||||||
@@ -451,19 +505,28 @@ export default function PropertyPanel({
|
|||||||
<AlertDialogTrigger asChild>
|
<AlertDialogTrigger asChild>
|
||||||
<Button variant="destructive" size="sm" className="w-full">
|
<Button variant="destructive" size="sm" className="w-full">
|
||||||
<Trash2 className="size-4 mr-2 flex-shrink-0" />
|
<Trash2 className="size-4 mr-2 flex-shrink-0" />
|
||||||
<span className="truncate">{t('workflows.deleteEdge')}</span>
|
<span className="truncate">
|
||||||
|
{t('workflows.deleteEdge')}
|
||||||
|
</span>
|
||||||
</Button>
|
</Button>
|
||||||
</AlertDialogTrigger>
|
</AlertDialogTrigger>
|
||||||
<AlertDialogContent>
|
<AlertDialogContent>
|
||||||
<AlertDialogHeader>
|
<AlertDialogHeader>
|
||||||
<AlertDialogTitle>{t('workflows.deleteEdgeConfirm')}</AlertDialogTitle>
|
<AlertDialogTitle>
|
||||||
|
{t('workflows.deleteEdgeConfirm')}
|
||||||
|
</AlertDialogTitle>
|
||||||
<AlertDialogDescription>
|
<AlertDialogDescription>
|
||||||
{t('workflows.deleteEdgeConfirmDesc')}
|
{t('workflows.deleteEdgeConfirmDesc')}
|
||||||
</AlertDialogDescription>
|
</AlertDialogDescription>
|
||||||
</AlertDialogHeader>
|
</AlertDialogHeader>
|
||||||
<AlertDialogFooter className="flex-wrap gap-2">
|
<AlertDialogFooter className="flex-wrap gap-2">
|
||||||
<AlertDialogCancel className="flex-1 min-w-[80px]">{t('common.cancel')}</AlertDialogCancel>
|
<AlertDialogCancel className="flex-1 min-w-[80px]">
|
||||||
<AlertDialogAction onClick={handleDeleteEdge} className="flex-1 min-w-[80px]">
|
{t('common.cancel')}
|
||||||
|
</AlertDialogCancel>
|
||||||
|
<AlertDialogAction
|
||||||
|
onClick={handleDeleteEdge}
|
||||||
|
className="flex-1 min-w-[80px]"
|
||||||
|
>
|
||||||
{t('common.delete')}
|
{t('common.delete')}
|
||||||
</AlertDialogAction>
|
</AlertDialogAction>
|
||||||
</AlertDialogFooter>
|
</AlertDialogFooter>
|
||||||
@@ -479,9 +542,11 @@ export default function PropertyPanel({
|
|||||||
|
|
||||||
// Node selected
|
// Node selected
|
||||||
if (selectedNode) {
|
if (selectedNode) {
|
||||||
const nodeInputs = selectedNode.data.inputs || nodeTypeMeta?.inputs || [{ name: 'input', type: 'any' }];
|
const nodeInputs = selectedNode.data.inputs ||
|
||||||
const nodeOutputs = selectedNode.data.outputs || nodeTypeMeta?.outputs || [{ name: 'output', type: 'any' }];
|
nodeTypeMeta?.inputs || [{ name: 'input', type: 'any' }];
|
||||||
|
const nodeOutputs = selectedNode.data.outputs ||
|
||||||
|
nodeTypeMeta?.outputs || [{ name: 'output', type: 'any' }];
|
||||||
|
|
||||||
// Extract i18n labels using extractI18nLabel
|
// Extract i18n labels using extractI18nLabel
|
||||||
const nodeLabel = nodeTypeMeta?.label
|
const nodeLabel = nodeTypeMeta?.label
|
||||||
? extractI18nLabel(nodeTypeMeta.label)
|
? extractI18nLabel(nodeTypeMeta.label)
|
||||||
@@ -489,7 +554,7 @@ export default function PropertyPanel({
|
|||||||
const nodeDescription = nodeTypeMeta?.description
|
const nodeDescription = nodeTypeMeta?.description
|
||||||
? extractI18nLabel(nodeTypeMeta.description)
|
? extractI18nLabel(nodeTypeMeta.description)
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
// Get node category color from local config
|
// Get node category color from local config
|
||||||
const nodeColor = localNodeConfig?.color || nodeTypeMeta?.color;
|
const nodeColor = localNodeConfig?.color || nodeTypeMeta?.color;
|
||||||
|
|
||||||
@@ -514,7 +579,9 @@ export default function PropertyPanel({
|
|||||||
</Badge>
|
</Badge>
|
||||||
</div>
|
</div>
|
||||||
{nodeDescription && (
|
{nodeDescription && (
|
||||||
<p className="text-xs text-muted-foreground">{nodeDescription}</p>
|
<p className="text-xs text-muted-foreground">
|
||||||
|
{nodeDescription}
|
||||||
|
</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -536,7 +603,7 @@ export default function PropertyPanel({
|
|||||||
className="h-8 w-full"
|
className="h-8 w-full"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-1.5 w-full overflow-hidden">
|
<div className="space-y-1.5 w-full overflow-hidden">
|
||||||
<Label className="text-xs text-muted-foreground">
|
<Label className="text-xs text-muted-foreground">
|
||||||
{t('workflows.nodeId')}
|
{t('workflows.nodeId')}
|
||||||
@@ -583,10 +650,16 @@ export default function PropertyPanel({
|
|||||||
>
|
>
|
||||||
<Variable className="size-3.5 text-blue-600 dark:text-blue-400 flex-shrink-0" />
|
<Variable className="size-3.5 text-blue-600 dark:text-blue-400 flex-shrink-0" />
|
||||||
<span className="font-mono text-xs truncate min-w-0 flex-1">
|
<span className="font-mono text-xs truncate min-w-0 flex-1">
|
||||||
{resolvePortDisplayLabel(input, 'workflows.nodeInputs')}
|
{resolvePortDisplayLabel(
|
||||||
|
input,
|
||||||
|
'workflows.nodeInputs',
|
||||||
|
)}
|
||||||
</span>
|
</span>
|
||||||
{input.type && (
|
{input.type && (
|
||||||
<Badge variant="outline" className="text-[10px] px-1 py-0 flex-shrink-0">
|
<Badge
|
||||||
|
variant="outline"
|
||||||
|
className="text-[10px] px-1 py-0 flex-shrink-0"
|
||||||
|
>
|
||||||
{getTypeLabel(input.type)}
|
{getTypeLabel(input.type)}
|
||||||
</Badge>
|
</Badge>
|
||||||
)}
|
)}
|
||||||
@@ -607,7 +680,10 @@ export default function PropertyPanel({
|
|||||||
key={output.name}
|
key={output.name}
|
||||||
variable={{
|
variable={{
|
||||||
name: `nodes.${selectedNode.id}.${output.name}`,
|
name: `nodes.${selectedNode.id}.${output.name}`,
|
||||||
label: resolvePortDisplayLabel(output, 'workflows.nodeOutputs'),
|
label: resolvePortDisplayLabel(
|
||||||
|
output,
|
||||||
|
'workflows.nodeOutputs',
|
||||||
|
),
|
||||||
type: output.type,
|
type: output.type,
|
||||||
}}
|
}}
|
||||||
onCopy={handleCopyVariable}
|
onCopy={handleCopyVariable}
|
||||||
@@ -627,7 +703,10 @@ export default function PropertyPanel({
|
|||||||
>
|
>
|
||||||
<div className="space-y-3 w-full overflow-hidden">
|
<div className="space-y-3 w-full overflow-hidden">
|
||||||
{availableInputVariables.map((group) => (
|
{availableInputVariables.map((group) => (
|
||||||
<div key={group.nodeId} className="space-y-1.5 w-full overflow-hidden">
|
<div
|
||||||
|
key={group.nodeId}
|
||||||
|
className="space-y-1.5 w-full overflow-hidden"
|
||||||
|
>
|
||||||
<div className="text-xs font-medium text-muted-foreground">
|
<div className="text-xs font-medium text-muted-foreground">
|
||||||
{group.nodeLabel}
|
{group.nodeLabel}
|
||||||
</div>
|
</div>
|
||||||
@@ -652,7 +731,10 @@ export default function PropertyPanel({
|
|||||||
title={t('workflows.nodeConfig')}
|
title={t('workflows.nodeConfig')}
|
||||||
icon={Settings}
|
icon={Settings}
|
||||||
badge={
|
badge={
|
||||||
<Badge variant="secondary" className="text-xs flex-shrink-0">
|
<Badge
|
||||||
|
variant="secondary"
|
||||||
|
className="text-xs flex-shrink-0"
|
||||||
|
>
|
||||||
{configSchema.length}
|
{configSchema.length}
|
||||||
</Badge>
|
</Badge>
|
||||||
}
|
}
|
||||||
@@ -660,7 +742,9 @@ export default function PropertyPanel({
|
|||||||
<div className="space-y-2 w-full overflow-hidden box-border">
|
<div className="space-y-2 w-full overflow-hidden box-border">
|
||||||
<DynamicFormComponent
|
<DynamicFormComponent
|
||||||
itemConfigList={configSchema}
|
itemConfigList={configSchema}
|
||||||
initialValues={selectedNode.data.config as Record<string, object>}
|
initialValues={
|
||||||
|
selectedNode.data.config as Record<string, object>
|
||||||
|
}
|
||||||
onSubmit={handleConfigChange}
|
onSubmit={handleConfigChange}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -683,19 +767,28 @@ export default function PropertyPanel({
|
|||||||
<AlertDialogTrigger asChild>
|
<AlertDialogTrigger asChild>
|
||||||
<Button variant="destructive" size="sm" className="w-full">
|
<Button variant="destructive" size="sm" className="w-full">
|
||||||
<Trash2 className="size-4 mr-2 flex-shrink-0" />
|
<Trash2 className="size-4 mr-2 flex-shrink-0" />
|
||||||
<span className="truncate">{t('workflows.deleteNode')}</span>
|
<span className="truncate">
|
||||||
|
{t('workflows.deleteNode')}
|
||||||
|
</span>
|
||||||
</Button>
|
</Button>
|
||||||
</AlertDialogTrigger>
|
</AlertDialogTrigger>
|
||||||
<AlertDialogContent>
|
<AlertDialogContent>
|
||||||
<AlertDialogHeader>
|
<AlertDialogHeader>
|
||||||
<AlertDialogTitle>{t('workflows.deleteNodeConfirm')}</AlertDialogTitle>
|
<AlertDialogTitle>
|
||||||
|
{t('workflows.deleteNodeConfirm')}
|
||||||
|
</AlertDialogTitle>
|
||||||
<AlertDialogDescription>
|
<AlertDialogDescription>
|
||||||
{t('workflows.deleteNodeConfirmDesc')}
|
{t('workflows.deleteNodeConfirmDesc')}
|
||||||
</AlertDialogDescription>
|
</AlertDialogDescription>
|
||||||
</AlertDialogHeader>
|
</AlertDialogHeader>
|
||||||
<AlertDialogFooter className="flex-wrap gap-2">
|
<AlertDialogFooter className="flex-wrap gap-2">
|
||||||
<AlertDialogCancel className="flex-1 min-w-[80px]">{t('common.cancel')}</AlertDialogCancel>
|
<AlertDialogCancel className="flex-1 min-w-[80px]">
|
||||||
<AlertDialogAction onClick={handleDeleteNode} className="flex-1 min-w-[80px]">
|
{t('common.cancel')}
|
||||||
|
</AlertDialogCancel>
|
||||||
|
<AlertDialogAction
|
||||||
|
onClick={handleDeleteNode}
|
||||||
|
className="flex-1 min-w-[80px]"
|
||||||
|
>
|
||||||
{t('common.delete')}
|
{t('common.delete')}
|
||||||
</AlertDialogAction>
|
</AlertDialogAction>
|
||||||
</AlertDialogFooter>
|
</AlertDialogFooter>
|
||||||
|
|||||||
@@ -18,7 +18,11 @@ import {
|
|||||||
TooltipProvider,
|
TooltipProvider,
|
||||||
TooltipTrigger,
|
TooltipTrigger,
|
||||||
} from '@/components/ui/tooltip';
|
} from '@/components/ui/tooltip';
|
||||||
import { NODE_ICONS, NODE_TYPE_I18N_KEYS, getNodeTypeLabel } from './workflow-constants';
|
import {
|
||||||
|
NODE_ICONS,
|
||||||
|
NODE_TYPE_I18N_KEYS,
|
||||||
|
getNodeTypeLabel,
|
||||||
|
} from './workflow-constants';
|
||||||
import { resolveI18nLabel, maybeTranslateKey } from './workflow-i18n';
|
import { resolveI18nLabel, maybeTranslateKey } from './workflow-i18n';
|
||||||
import type { I18nObject } from '@/app/infra/entities/common';
|
import type { I18nObject } from '@/app/infra/entities/common';
|
||||||
|
|
||||||
@@ -31,14 +35,17 @@ const nodeTypeI18nKeys: Record<string, string> = Object.fromEntries(
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Category colors with improved design
|
// Category colors with improved design
|
||||||
const categoryColors: Record<string, {
|
const categoryColors: Record<
|
||||||
bg: string;
|
string,
|
||||||
border: string;
|
{
|
||||||
text: string;
|
bg: string;
|
||||||
icon: string;
|
border: string;
|
||||||
gradient: string;
|
text: string;
|
||||||
handleBg: string;
|
icon: string;
|
||||||
}> = {
|
gradient: string;
|
||||||
|
handleBg: string;
|
||||||
|
}
|
||||||
|
> = {
|
||||||
trigger: {
|
trigger: {
|
||||||
bg: 'bg-amber-50 dark:bg-amber-950/40',
|
bg: 'bg-amber-50 dark:bg-amber-950/40',
|
||||||
border: 'border-amber-400 dark:border-amber-600',
|
border: 'border-amber-400 dark:border-amber-600',
|
||||||
@@ -82,15 +89,24 @@ const categoryColors: Record<string, {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Node execution status
|
// Node execution status
|
||||||
export type NodeExecutionStatus = 'idle' | 'pending' | 'running' | 'completed' | 'failed' | 'skipped';
|
export type NodeExecutionStatus =
|
||||||
|
| 'idle'
|
||||||
|
| 'pending'
|
||||||
|
| 'running'
|
||||||
|
| 'completed'
|
||||||
|
| 'failed'
|
||||||
|
| 'skipped';
|
||||||
|
|
||||||
// Status colors and icons
|
// Status colors and icons
|
||||||
const statusConfig: Record<NodeExecutionStatus, {
|
const statusConfig: Record<
|
||||||
icon: React.ElementType;
|
NodeExecutionStatus,
|
||||||
color: string;
|
{
|
||||||
bgColor: string;
|
icon: React.ElementType;
|
||||||
animate?: boolean;
|
color: string;
|
||||||
}> = {
|
bgColor: string;
|
||||||
|
animate?: boolean;
|
||||||
|
}
|
||||||
|
> = {
|
||||||
idle: {
|
idle: {
|
||||||
icon: Play,
|
icon: Play,
|
||||||
color: 'text-muted-foreground',
|
color: 'text-muted-foreground',
|
||||||
@@ -128,8 +144,16 @@ export interface WorkflowNodeData extends Record<string, unknown> {
|
|||||||
label: string;
|
label: string;
|
||||||
type: string;
|
type: string;
|
||||||
config: Record<string, unknown>;
|
config: Record<string, unknown>;
|
||||||
inputs?: { name: string; label?: string | Record<string, string> | I18nObject; type?: string }[];
|
inputs?: {
|
||||||
outputs?: { name: string; label?: string | Record<string, string> | I18nObject; type?: string }[];
|
name: string;
|
||||||
|
label?: string | Record<string, string> | I18nObject;
|
||||||
|
type?: string;
|
||||||
|
}[];
|
||||||
|
outputs?: {
|
||||||
|
name: string;
|
||||||
|
label?: string | Record<string, string> | I18nObject;
|
||||||
|
type?: string;
|
||||||
|
}[];
|
||||||
// Execution state
|
// Execution state
|
||||||
executionStatus?: NodeExecutionStatus;
|
executionStatus?: NodeExecutionStatus;
|
||||||
executionError?: string;
|
executionError?: string;
|
||||||
@@ -152,7 +176,10 @@ function getPortLabel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (typeof label === 'string' && label) {
|
if (typeof label === 'string' && label) {
|
||||||
if (label.startsWith('workflows.nodeOutputs.') || label.startsWith('workflows.nodeInputs.')) {
|
if (
|
||||||
|
label.startsWith('workflows.nodeOutputs.') ||
|
||||||
|
label.startsWith('workflows.nodeInputs.')
|
||||||
|
) {
|
||||||
return t(label, { defaultValue: fallbackName });
|
return t(label, { defaultValue: fallbackName });
|
||||||
}
|
}
|
||||||
return label;
|
return label;
|
||||||
@@ -164,17 +191,22 @@ function getPortLabel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Helper function to extract i18n value from I18nObject (delegates to shared utility)
|
// Helper function to extract i18n value from I18nObject (delegates to shared utility)
|
||||||
function extractI18nValue(i18nObj: Record<string, string> | undefined, _t: (key: string) => string): string {
|
function extractI18nValue(
|
||||||
|
i18nObj: Record<string, string> | undefined,
|
||||||
|
_t: (key: string) => string,
|
||||||
|
): string {
|
||||||
return resolveI18nLabel(i18nObj);
|
return resolveI18nLabel(i18nObj);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper function to get node type description: show the raw type name after the dot
|
// Helper function to get node type description: show the raw type name after the dot
|
||||||
function getNodeTypeDescription(
|
function getNodeTypeDescription(
|
||||||
nodeType: string,
|
nodeType: string,
|
||||||
t: (key: string, options?: { defaultValue: string }) => string,
|
t: (key: string, options?: { defaultValue: string }) => string,
|
||||||
nodeTypeLabel?: Record<string, string>
|
nodeTypeLabel?: Record<string, string>,
|
||||||
): string {
|
): string {
|
||||||
return nodeType.includes('.') ? nodeType.split('.').slice(1).join('.') : nodeType;
|
return nodeType.includes('.')
|
||||||
|
? nodeType.split('.').slice(1).join('.')
|
||||||
|
: nodeType;
|
||||||
}
|
}
|
||||||
|
|
||||||
function WorkflowNodeComponent({ data, selected }: NodeProps) {
|
function WorkflowNodeComponent({ data, selected }: NodeProps) {
|
||||||
@@ -191,11 +223,19 @@ function WorkflowNodeComponent({ data, selected }: NodeProps) {
|
|||||||
|
|
||||||
// Get inputs and outputs with defaults (use i18n keys for default labels)
|
// Get inputs and outputs with defaults (use i18n keys for default labels)
|
||||||
const inputs = useMemo(() => {
|
const inputs = useMemo(() => {
|
||||||
return nodeData.inputs || [{ name: 'input', label: 'workflows.nodeInputs.input', type: 'any' }];
|
return (
|
||||||
|
nodeData.inputs || [
|
||||||
|
{ name: 'input', label: 'workflows.nodeInputs.input', type: 'any' },
|
||||||
|
]
|
||||||
|
);
|
||||||
}, [nodeData.inputs]);
|
}, [nodeData.inputs]);
|
||||||
|
|
||||||
const outputs = useMemo(() => {
|
const outputs = useMemo(() => {
|
||||||
return nodeData.outputs || [{ name: 'output', label: 'workflows.nodeOutputs.output', type: 'any' }];
|
return (
|
||||||
|
nodeData.outputs || [
|
||||||
|
{ name: 'output', label: 'workflows.nodeOutputs.output', type: 'any' },
|
||||||
|
]
|
||||||
|
);
|
||||||
}, [nodeData.outputs]);
|
}, [nodeData.outputs]);
|
||||||
|
|
||||||
// Determine if this is a trigger node (no inputs)
|
// Determine if this is a trigger node (no inputs)
|
||||||
@@ -217,73 +257,95 @@ function WorkflowNodeComponent({ data, selected }: NodeProps) {
|
|||||||
'min-w-[200px] max-w-[280px] rounded-xl border-2 shadow-lg transition-all duration-200',
|
'min-w-[200px] max-w-[280px] rounded-xl border-2 shadow-lg transition-all duration-200',
|
||||||
colors.bg,
|
colors.bg,
|
||||||
colors.border,
|
colors.border,
|
||||||
selected && 'ring-2 ring-primary ring-offset-2 ring-offset-background shadow-xl scale-[1.02]',
|
selected &&
|
||||||
|
'ring-2 ring-primary ring-offset-2 ring-offset-background shadow-xl scale-[1.02]',
|
||||||
status === 'running' && 'shadow-blue-200 dark:shadow-blue-900/50',
|
status === 'running' && 'shadow-blue-200 dark:shadow-blue-900/50',
|
||||||
status === 'failed' && 'shadow-red-200 dark:shadow-red-900/50 border-red-500',
|
status === 'failed' &&
|
||||||
|
'shadow-red-200 dark:shadow-red-900/50 border-red-500',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{/* Input handles - only show if not trigger */}
|
{/* Input handles - only show if not trigger */}
|
||||||
{!isTrigger && inputs.map((input, index) => (
|
{!isTrigger &&
|
||||||
<Tooltip key={`input-${input.name}`}>
|
inputs.map((input, index) => (
|
||||||
<TooltipTrigger asChild>
|
<Tooltip key={`input-${input.name}`}>
|
||||||
<Handle
|
<TooltipTrigger asChild>
|
||||||
type="target"
|
<Handle
|
||||||
position={Position.Left}
|
type="target"
|
||||||
id={input.name}
|
position={Position.Left}
|
||||||
style={{
|
id={input.name}
|
||||||
top: inputs.length === 1 ? '50%' : `${((index + 1) / (inputs.length + 1)) * 100}%`,
|
style={{
|
||||||
background: colors.handleBg,
|
top:
|
||||||
width: 12,
|
inputs.length === 1
|
||||||
height: 12,
|
? '50%'
|
||||||
border: '2px solid white',
|
: `${((index + 1) / (inputs.length + 1)) * 100}%`,
|
||||||
boxShadow: '0 2px 4px rgba(0,0,0,0.1)',
|
background: colors.handleBg,
|
||||||
}}
|
width: 12,
|
||||||
className="!transition-transform hover:!scale-125"
|
height: 12,
|
||||||
/>
|
border: '2px solid white',
|
||||||
</TooltipTrigger>
|
boxShadow: '0 2px 4px rgba(0,0,0,0.1)',
|
||||||
<TooltipContent side="left">
|
}}
|
||||||
<p className="font-medium">{getPortLabel(input.label, input.name, 'workflows.nodeInputs', t)}</p>
|
className="!transition-transform hover:!scale-125"
|
||||||
{input.type && <p className="text-xs text-muted-foreground">{input.type}</p>}
|
/>
|
||||||
</TooltipContent>
|
</TooltipTrigger>
|
||||||
</Tooltip>
|
<TooltipContent side="left">
|
||||||
))}
|
<p className="font-medium">
|
||||||
|
{getPortLabel(
|
||||||
|
input.label,
|
||||||
|
input.name,
|
||||||
|
'workflows.nodeInputs',
|
||||||
|
t,
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
{input.type && (
|
||||||
|
<p className="text-xs text-muted-foreground">{input.type}</p>
|
||||||
|
)}
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
))}
|
||||||
|
|
||||||
{/* Node content */}
|
{/* Node content */}
|
||||||
<div className={cn('p-3 bg-gradient-to-b', colors.gradient)}>
|
<div className={cn('p-3 bg-gradient-to-b', colors.gradient)}>
|
||||||
{/* Header row with icon and status */}
|
{/* Header row with icon and status */}
|
||||||
<div className="flex items-start gap-2.5">
|
<div className="flex items-start gap-2.5">
|
||||||
{/* Node icon */}
|
{/* Node icon */}
|
||||||
<div className={cn(
|
<div
|
||||||
'p-2 rounded-lg shrink-0',
|
className={cn(
|
||||||
colors.icon,
|
'p-2 rounded-lg shrink-0',
|
||||||
'bg-white/60 dark:bg-black/20',
|
colors.icon,
|
||||||
'shadow-sm'
|
'bg-white/60 dark:bg-black/20',
|
||||||
)}>
|
'shadow-sm',
|
||||||
|
)}
|
||||||
|
>
|
||||||
<Icon className="size-5" />
|
<Icon className="size-5" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Title and type */}
|
{/* Title and type */}
|
||||||
<div className="flex-1 min-w-0 pt-0.5">
|
<div className="flex-1 min-w-0 pt-0.5">
|
||||||
<div className={cn('font-semibold text-sm truncate', colors.text)}>
|
<div
|
||||||
|
className={cn('font-semibold text-sm truncate', colors.text)}
|
||||||
|
>
|
||||||
{nodeData.label}
|
{nodeData.label}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-xs text-muted-foreground truncate mt-0.5">
|
<div className="text-xs text-muted-foreground truncate mt-0.5">
|
||||||
{getNodeTypeDescription(nodeData.type, t, nodeData.nodeTypeLabel)}
|
{getNodeTypeDescription(
|
||||||
|
nodeData.type,
|
||||||
|
t,
|
||||||
|
nodeData.nodeTypeLabel,
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Status indicator */}
|
{/* Status indicator */}
|
||||||
{status !== 'idle' && (
|
{status !== 'idle' && (
|
||||||
<div className={cn(
|
<div
|
||||||
'p-1 rounded-full shrink-0',
|
className={cn('p-1 rounded-full shrink-0', statusInfo.bgColor)}
|
||||||
statusInfo.bgColor
|
>
|
||||||
)}>
|
<StatusIcon
|
||||||
<StatusIcon
|
|
||||||
className={cn(
|
className={cn(
|
||||||
'size-4',
|
'size-4',
|
||||||
statusInfo.color,
|
statusInfo.color,
|
||||||
statusInfo.animate && 'animate-spin'
|
statusInfo.animate && 'animate-spin',
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -291,16 +353,20 @@ function WorkflowNodeComponent({ data, selected }: NodeProps) {
|
|||||||
|
|
||||||
{/* Execution info */}
|
{/* Execution info */}
|
||||||
{(status === 'completed' || status === 'failed') && (
|
{(status === 'completed' || status === 'failed') && (
|
||||||
<div className={cn(
|
<div
|
||||||
'mt-2 pt-2 border-t flex items-center justify-between text-xs',
|
className={cn(
|
||||||
'border-black/5 dark:border-white/5'
|
'mt-2 pt-2 border-t flex items-center justify-between text-xs',
|
||||||
)}>
|
'border-black/5 dark:border-white/5',
|
||||||
|
)}
|
||||||
|
>
|
||||||
<div className={cn('flex items-center gap-1', statusInfo.color)}>
|
<div className={cn('flex items-center gap-1', statusInfo.color)}>
|
||||||
<StatusIcon className="size-3" />
|
<StatusIcon className="size-3" />
|
||||||
<span className="capitalize">{status}</span>
|
<span className="capitalize">{status}</span>
|
||||||
</div>
|
</div>
|
||||||
{formattedDuration && (
|
{formattedDuration && (
|
||||||
<span className="text-muted-foreground">{formattedDuration}</span>
|
<span className="text-muted-foreground">
|
||||||
|
{formattedDuration}
|
||||||
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -329,7 +395,10 @@ function WorkflowNodeComponent({ data, selected }: NodeProps) {
|
|||||||
position={Position.Right}
|
position={Position.Right}
|
||||||
id={output.name}
|
id={output.name}
|
||||||
style={{
|
style={{
|
||||||
top: outputs.length === 1 ? '50%' : `${((index + 1) / (outputs.length + 1)) * 100}%`,
|
top:
|
||||||
|
outputs.length === 1
|
||||||
|
? '50%'
|
||||||
|
: `${((index + 1) / (outputs.length + 1)) * 100}%`,
|
||||||
background: colors.handleBg,
|
background: colors.handleBg,
|
||||||
width: 12,
|
width: 12,
|
||||||
height: 12,
|
height: 12,
|
||||||
@@ -340,12 +409,20 @@ function WorkflowNodeComponent({ data, selected }: NodeProps) {
|
|||||||
/>
|
/>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent side="right">
|
<TooltipContent side="right">
|
||||||
<p className="font-medium">{getPortLabel(output.label, output.name, 'workflows.nodeOutputs', t)}</p>
|
<p className="font-medium">
|
||||||
{output.type && <p className="text-xs text-muted-foreground">{output.type}</p>}
|
{getPortLabel(
|
||||||
|
output.label,
|
||||||
|
output.name,
|
||||||
|
'workflows.nodeOutputs',
|
||||||
|
t,
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
{output.type && (
|
||||||
|
<p className="text-xs text-muted-foreground">{output.type}</p>
|
||||||
|
)}
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</TooltipProvider>
|
</TooltipProvider>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
export { default as WorkflowEditorComponent } from './WorkflowEditorComponent';
|
export { default as WorkflowEditorComponent } from './WorkflowEditorComponent';
|
||||||
export { default as WorkflowNodeComponent, type WorkflowNodeData } from './WorkflowNodeComponent';
|
export {
|
||||||
|
default as WorkflowNodeComponent,
|
||||||
|
type WorkflowNodeData,
|
||||||
|
} from './WorkflowNodeComponent';
|
||||||
export { default as NodePalette } from './NodePalette';
|
export { default as NodePalette } from './NodePalette';
|
||||||
export { default as PropertyPanel } from './PropertyPanel';
|
export { default as PropertyPanel } from './PropertyPanel';
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/**
|
/**
|
||||||
* AI Node Configurations
|
* AI Node Configurations
|
||||||
*
|
*
|
||||||
* Defines configurations for all AI-related node types:
|
* Defines configurations for all AI-related node types:
|
||||||
* - llm_call: Call a large language model
|
* - llm_call: Call a large language model
|
||||||
* - question_classifier: Classify user questions into categories
|
* - question_classifier: Classify user questions into categories
|
||||||
@@ -80,8 +80,10 @@ export const llmCallConfig: NodeConfigMeta = {
|
|||||||
zh_Hans: '系统提示词',
|
zh_Hans: '系统提示词',
|
||||||
},
|
},
|
||||||
description: {
|
description: {
|
||||||
en_US: 'System prompt to set the model behavior (supports variable interpolation with {{variable}})',
|
en_US:
|
||||||
zh_Hans: '设置模型行为的系统提示词(支持使用 {{variable}} 进行变量插值)',
|
'System prompt to set the model behavior (supports variable interpolation with {{variable}})',
|
||||||
|
zh_Hans:
|
||||||
|
'设置模型行为的系统提示词(支持使用 {{variable}} 进行变量插值)',
|
||||||
},
|
},
|
||||||
required: false,
|
required: false,
|
||||||
default: '',
|
default: '',
|
||||||
@@ -95,8 +97,10 @@ export const llmCallConfig: NodeConfigMeta = {
|
|||||||
zh_Hans: '用户提示词模板',
|
zh_Hans: '用户提示词模板',
|
||||||
},
|
},
|
||||||
description: {
|
description: {
|
||||||
en_US: 'User prompt template with variable placeholders (e.g., {{input}}, {{context.key}})',
|
en_US:
|
||||||
zh_Hans: '带有变量占位符的用户提示词模板(例如 {{input}}、{{context.key}})',
|
'User prompt template with variable placeholders (e.g., {{input}}, {{context.key}})',
|
||||||
|
zh_Hans:
|
||||||
|
'带有变量占位符的用户提示词模板(例如 {{input}}、{{context.key}})',
|
||||||
},
|
},
|
||||||
required: true,
|
required: true,
|
||||||
default: '{{input}}',
|
default: '{{input}}',
|
||||||
@@ -110,7 +114,8 @@ export const llmCallConfig: NodeConfigMeta = {
|
|||||||
zh_Hans: '温度',
|
zh_Hans: '温度',
|
||||||
},
|
},
|
||||||
description: {
|
description: {
|
||||||
en_US: 'Controls randomness in responses (0.0 = deterministic, 2.0 = very random)',
|
en_US:
|
||||||
|
'Controls randomness in responses (0.0 = deterministic, 2.0 = very random)',
|
||||||
zh_Hans: '控制响应的随机性(0.0 = 确定性,2.0 = 非常随机)',
|
zh_Hans: '控制响应的随机性(0.0 = 确定性,2.0 = 非常随机)',
|
||||||
},
|
},
|
||||||
required: false,
|
required: false,
|
||||||
@@ -125,7 +130,8 @@ export const llmCallConfig: NodeConfigMeta = {
|
|||||||
zh_Hans: '最大令牌数',
|
zh_Hans: '最大令牌数',
|
||||||
},
|
},
|
||||||
description: {
|
description: {
|
||||||
en_US: 'Maximum number of tokens to generate (leave 0 for model default)',
|
en_US:
|
||||||
|
'Maximum number of tokens to generate (leave 0 for model default)',
|
||||||
zh_Hans: '生成的最大令牌数(设为 0 使用模型默认值)',
|
zh_Hans: '生成的最大令牌数(设为 0 使用模型默认值)',
|
||||||
},
|
},
|
||||||
required: false,
|
required: false,
|
||||||
@@ -148,7 +154,10 @@ export const llmCallConfig: NodeConfigMeta = {
|
|||||||
options: [
|
options: [
|
||||||
{ name: 'text', label: { en_US: 'Plain Text', zh_Hans: '纯文本' } },
|
{ name: 'text', label: { en_US: 'Plain Text', zh_Hans: '纯文本' } },
|
||||||
{ name: 'json', label: { en_US: 'JSON', zh_Hans: 'JSON' } },
|
{ name: 'json', label: { en_US: 'JSON', zh_Hans: 'JSON' } },
|
||||||
{ name: 'markdown', label: { en_US: 'Markdown', zh_Hans: 'Markdown 文本' } },
|
{
|
||||||
|
name: 'markdown',
|
||||||
|
label: { en_US: 'Markdown', zh_Hans: 'Markdown 文本' },
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -282,8 +291,10 @@ export const questionClassifierConfig: NodeConfigMeta = {
|
|||||||
zh_Hans: '分类定义',
|
zh_Hans: '分类定义',
|
||||||
},
|
},
|
||||||
description: {
|
description: {
|
||||||
en_US: 'Define categories in JSON format: [{"name": "category1", "description": "...", "examples": ["..."]}]',
|
en_US:
|
||||||
zh_Hans: '使用 JSON 格式定义分类: [{"name": "分类1", "description": "...", "examples": ["..."]}]',
|
'Define categories in JSON format: [{"name": "category1", "description": "...", "examples": ["..."]}]',
|
||||||
|
zh_Hans:
|
||||||
|
'使用 JSON 格式定义分类: [{"name": "分类1", "description": "...", "examples": ["..."]}]',
|
||||||
},
|
},
|
||||||
required: true,
|
required: true,
|
||||||
default: '[]',
|
default: '[]',
|
||||||
@@ -389,8 +400,10 @@ export const parameterExtractorConfig: NodeConfigMeta = {
|
|||||||
zh_Hans: '参数架构',
|
zh_Hans: '参数架构',
|
||||||
},
|
},
|
||||||
description: {
|
description: {
|
||||||
en_US: 'JSON array defining expected parameters: [{"name": "date", "type": "string", "description": "Meeting date", "required": true}]',
|
en_US:
|
||||||
zh_Hans: '定义期望参数的 JSON 数组: [{"name": "日期", "type": "string", "description": "会议日期", "required": true}]',
|
'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,
|
required: true,
|
||||||
default: '[]',
|
default: '[]',
|
||||||
@@ -445,7 +458,8 @@ export const knowledgeRetrievalConfig: NodeConfigMeta = {
|
|||||||
zh_Hans: '知识检索',
|
zh_Hans: '知识检索',
|
||||||
},
|
},
|
||||||
description: {
|
description: {
|
||||||
en_US: 'Retrieve relevant information from knowledge bases using semantic search',
|
en_US:
|
||||||
|
'Retrieve relevant information from knowledge bases using semantic search',
|
||||||
zh_Hans: '使用语义搜索从知识库中检索相关信息',
|
zh_Hans: '使用语义搜索从知识库中检索相关信息',
|
||||||
},
|
},
|
||||||
icon: 'BookOpen',
|
icon: 'BookOpen',
|
||||||
@@ -532,9 +546,18 @@ export const knowledgeRetrievalConfig: NodeConfigMeta = {
|
|||||||
required: false,
|
required: false,
|
||||||
default: 'vector',
|
default: 'vector',
|
||||||
options: [
|
options: [
|
||||||
{ name: 'vector', label: { en_US: 'Vector Search', zh_Hans: '向量检索' } },
|
{
|
||||||
{ name: 'hybrid', label: { en_US: 'Hybrid Search', zh_Hans: '混合检索' } },
|
name: 'vector',
|
||||||
{ name: 'keyword', label: { en_US: 'Keyword Search', zh_Hans: '关键词检索' } },
|
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: '关键词检索' },
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -700,8 +723,10 @@ export const intentRecognitionConfig: NodeConfigMeta = {
|
|||||||
zh_Hans: '意图定义',
|
zh_Hans: '意图定义',
|
||||||
},
|
},
|
||||||
description: {
|
description: {
|
||||||
en_US: 'Define intents in JSON format: [{"name": "intent1", "description": "...", "examples": ["..."]}]',
|
en_US:
|
||||||
zh_Hans: '使用 JSON 格式定义意图: [{"name": "意图1", "description": "...", "examples": ["..."]}]',
|
'Define intents in JSON format: [{"name": "intent1", "description": "...", "examples": ["..."]}]',
|
||||||
|
zh_Hans:
|
||||||
|
'使用 JSON 格式定义意图: [{"name": "意图1", "description": "...", "examples": ["..."]}]',
|
||||||
},
|
},
|
||||||
required: true,
|
required: true,
|
||||||
default: '[]',
|
default: '[]',
|
||||||
|
|||||||
+100
-28
@@ -1,6 +1,6 @@
|
|||||||
/**
|
/**
|
||||||
* Control Node Configurations
|
* Control Node Configurations
|
||||||
*
|
*
|
||||||
* Defines configurations for flow control node types:
|
* Defines configurations for flow control node types:
|
||||||
* - condition: Conditional branching
|
* - condition: Conditional branching
|
||||||
* - switch_case: Multi-way branching
|
* - switch_case: Multi-way branching
|
||||||
@@ -62,10 +62,16 @@ export const conditionConfig: NodeConfigMeta = {
|
|||||||
required: true,
|
required: true,
|
||||||
default: 'expression',
|
default: 'expression',
|
||||||
options: [
|
options: [
|
||||||
{ name: 'expression', label: { en_US: 'Expression', zh_Hans: '表达式' } },
|
{
|
||||||
|
name: 'expression',
|
||||||
|
label: { en_US: 'Expression', zh_Hans: '表达式' },
|
||||||
|
},
|
||||||
{ name: 'comparison', label: { en_US: 'Comparison', zh_Hans: '比较' } },
|
{ name: 'comparison', label: { en_US: 'Comparison', zh_Hans: '比较' } },
|
||||||
{ name: 'exists', label: { en_US: 'Value Exists', zh_Hans: '值存在' } },
|
{ name: 'exists', label: { en_US: 'Value Exists', zh_Hans: '值存在' } },
|
||||||
{ name: 'type_check', label: { en_US: 'Type Check', zh_Hans: '类型检查' } },
|
{
|
||||||
|
name: 'type_check',
|
||||||
|
label: { en_US: 'Type Check', zh_Hans: '类型检查' },
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -124,15 +130,36 @@ export const conditionConfig: NodeConfigMeta = {
|
|||||||
default: 'eq',
|
default: 'eq',
|
||||||
options: [
|
options: [
|
||||||
{ name: 'eq', label: { en_US: 'Equals (==)', zh_Hans: '等于 (==)' } },
|
{ 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: 'neq',
|
||||||
{ name: 'gte', label: { en_US: 'Greater or Equal (>=)', zh_Hans: '大于等于 (>=)' } },
|
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: 'lt', label: { en_US: 'Less Than (<)', zh_Hans: '小于 (<)' } },
|
||||||
{ name: 'lte', label: { en_US: 'Less or Equal (<=)', zh_Hans: '小于等于 (<=)' } },
|
{
|
||||||
|
name: 'lte',
|
||||||
|
label: { en_US: 'Less or Equal (<=)', zh_Hans: '小于等于 (<=)' },
|
||||||
|
},
|
||||||
{ name: 'contains', label: { en_US: 'Contains', 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: 'starts_with',
|
||||||
{ name: 'matches', label: { en_US: 'Matches Regex', zh_Hans: '匹配正则' } },
|
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: {
|
show_if: {
|
||||||
field: 'condition_type',
|
field: 'condition_type',
|
||||||
@@ -261,11 +288,14 @@ export const switchCaseConfig: NodeConfigMeta = {
|
|||||||
zh_Hans: '情况',
|
zh_Hans: '情况',
|
||||||
},
|
},
|
||||||
description: {
|
description: {
|
||||||
en_US: 'Define cases as JSON array: [{"name": "case_1", "value": "value1"}, {"name": "case_2", "values": ["v1", "v2"]}]',
|
en_US:
|
||||||
zh_Hans: '使用 JSON 数组定义情况: [{"name": "case_1", "value": "value1"}, {"name": "case_2", "values": ["v1", "v2"]}]',
|
'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,
|
required: true,
|
||||||
default: '[{"name": "case_1", "value": ""}, {"name": "case_2", "value": ""}]',
|
default:
|
||||||
|
'[{"name": "case_1", "value": ""}, {"name": "case_2", "value": ""}]',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'case_sensitive',
|
id: 'case_sensitive',
|
||||||
@@ -502,8 +532,10 @@ export const parallelConfig: NodeConfigMeta = {
|
|||||||
zh_Hans: '分支',
|
zh_Hans: '分支',
|
||||||
},
|
},
|
||||||
description: {
|
description: {
|
||||||
en_US: 'Define branches as JSON array: [{"name": "branch_1"}, {"name": "branch_2"}]',
|
en_US:
|
||||||
zh_Hans: '使用 JSON 数组定义分支: [{"name": "branch_1"}, {"name": "branch_2"}]',
|
'Define branches as JSON array: [{"name": "branch_1"}, {"name": "branch_2"}]',
|
||||||
|
zh_Hans:
|
||||||
|
'使用 JSON 数组定义分支: [{"name": "branch_1"}, {"name": "branch_2"}]',
|
||||||
},
|
},
|
||||||
required: true,
|
required: true,
|
||||||
default: '[{"name": "branch_1"}, {"name": "branch_2"}]',
|
default: '[{"name": "branch_1"}, {"name": "branch_2"}]',
|
||||||
@@ -763,7 +795,10 @@ export const iteratorConfig: NodeConfigMeta = {
|
|||||||
name: 'parallel',
|
name: 'parallel',
|
||||||
type: DynamicFormItemType.BOOLEAN,
|
type: DynamicFormItemType.BOOLEAN,
|
||||||
label: { en_US: 'Parallel Processing', zh_Hans: '并行处理' },
|
label: { en_US: 'Parallel Processing', zh_Hans: '并行处理' },
|
||||||
description: { en_US: 'Process items in parallel', zh_Hans: '并行处理项目' },
|
description: {
|
||||||
|
en_US: 'Process items in parallel',
|
||||||
|
zh_Hans: '并行处理项目',
|
||||||
|
},
|
||||||
required: false,
|
required: false,
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
@@ -772,7 +807,10 @@ export const iteratorConfig: NodeConfigMeta = {
|
|||||||
name: 'max_concurrency',
|
name: 'max_concurrency',
|
||||||
type: DynamicFormItemType.INT,
|
type: DynamicFormItemType.INT,
|
||||||
label: { en_US: 'Max Concurrency', zh_Hans: '最大并发数' },
|
label: { en_US: 'Max Concurrency', zh_Hans: '最大并发数' },
|
||||||
description: { en_US: 'Maximum number of concurrent iterations', zh_Hans: '最大并发迭代数' },
|
description: {
|
||||||
|
en_US: 'Maximum number of concurrent iterations',
|
||||||
|
zh_Hans: '最大并发迭代数',
|
||||||
|
},
|
||||||
required: false,
|
required: false,
|
||||||
default: 5,
|
default: 5,
|
||||||
show_if: { field: 'parallel', operator: 'eq', value: true },
|
show_if: { field: 'parallel', operator: 'eq', value: true },
|
||||||
@@ -782,7 +820,10 @@ export const iteratorConfig: NodeConfigMeta = {
|
|||||||
name: 'max_iterations',
|
name: 'max_iterations',
|
||||||
type: DynamicFormItemType.INT,
|
type: DynamicFormItemType.INT,
|
||||||
label: { en_US: 'Max Iterations', zh_Hans: '最大迭代次数' },
|
label: { en_US: 'Max Iterations', zh_Hans: '最大迭代次数' },
|
||||||
description: { en_US: 'Safety limit on iterations', zh_Hans: '迭代次数安全限制' },
|
description: {
|
||||||
|
en_US: 'Safety limit on iterations',
|
||||||
|
zh_Hans: '迭代次数安全限制',
|
||||||
|
},
|
||||||
required: false,
|
required: false,
|
||||||
default: 1000,
|
default: 1000,
|
||||||
},
|
},
|
||||||
@@ -831,14 +872,29 @@ export const mergeConfig: NodeConfigMeta = {
|
|||||||
name: 'merge_strategy',
|
name: 'merge_strategy',
|
||||||
type: DynamicFormItemType.SELECT,
|
type: DynamicFormItemType.SELECT,
|
||||||
label: { en_US: 'Merge Strategy', zh_Hans: '合并策略' },
|
label: { en_US: 'Merge Strategy', zh_Hans: '合并策略' },
|
||||||
description: { en_US: 'How to merge inputs from branches', zh_Hans: '如何合并分支输入' },
|
description: {
|
||||||
|
en_US: 'How to merge inputs from branches',
|
||||||
|
zh_Hans: '如何合并分支输入',
|
||||||
|
},
|
||||||
required: true,
|
required: true,
|
||||||
default: 'wait_all',
|
default: 'wait_all',
|
||||||
options: [
|
options: [
|
||||||
{ name: 'wait_all', label: { en_US: 'Wait for All', zh_Hans: '等待全部' } },
|
{
|
||||||
{ name: 'first_completed', label: { en_US: 'First Completed', zh_Hans: '第一个完成' } },
|
name: 'wait_all',
|
||||||
{ name: 'combine', label: { en_US: 'Combine to Object', zh_Hans: '合并为对象' } },
|
label: { en_US: 'Wait for All', zh_Hans: '等待全部' },
|
||||||
{ name: 'array', label: { en_US: 'Collect to Array', 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: '收集为数组' },
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -881,7 +937,11 @@ export const variableAggregatorConfig: NodeConfigMeta = {
|
|||||||
name: 'variable_mappings',
|
name: 'variable_mappings',
|
||||||
type: DynamicFormItemType.TEXT,
|
type: DynamicFormItemType.TEXT,
|
||||||
label: { en_US: 'Variable Mappings', zh_Hans: '变量映射' },
|
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}}"}' },
|
description: {
|
||||||
|
en_US:
|
||||||
|
'JSON mapping of output variables: {"out_key": "{{nodes.xxx.value}}"}',
|
||||||
|
zh_Hans: 'JSON 格式的输出变量映射: {"out_key": "{{nodes.xxx.value}}"}',
|
||||||
|
},
|
||||||
required: true,
|
required: true,
|
||||||
default: '{}',
|
default: '{}',
|
||||||
},
|
},
|
||||||
@@ -890,13 +950,25 @@ export const variableAggregatorConfig: NodeConfigMeta = {
|
|||||||
name: 'aggregation_mode',
|
name: 'aggregation_mode',
|
||||||
type: DynamicFormItemType.SELECT,
|
type: DynamicFormItemType.SELECT,
|
||||||
label: { en_US: 'Aggregation Mode', zh_Hans: '聚合模式' },
|
label: { en_US: 'Aggregation Mode', zh_Hans: '聚合模式' },
|
||||||
description: { en_US: 'How to aggregate the variables', zh_Hans: '如何聚合变量' },
|
description: {
|
||||||
|
en_US: 'How to aggregate the variables',
|
||||||
|
zh_Hans: '如何聚合变量',
|
||||||
|
},
|
||||||
required: true,
|
required: true,
|
||||||
default: 'merge',
|
default: 'merge',
|
||||||
options: [
|
options: [
|
||||||
{ name: 'merge', label: { en_US: 'Merge Objects', zh_Hans: '合并对象' } },
|
{
|
||||||
{ name: 'array', label: { en_US: 'Collect to Array', zh_Hans: '收集为数组' } },
|
name: 'merge',
|
||||||
{ name: 'first', label: { en_US: 'First Non-null', zh_Hans: '第一个非空' } },
|
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: '第一个非空' },
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/**
|
/**
|
||||||
* Node Configurations Index
|
* Node Configurations Index
|
||||||
*
|
*
|
||||||
* This module exports all node configuration metadata and provides
|
* This module exports all node configuration metadata and provides
|
||||||
* utility functions for accessing node configurations.
|
* utility functions for accessing node configurations.
|
||||||
*/
|
*/
|
||||||
@@ -9,7 +9,7 @@
|
|||||||
export * from './types';
|
export * from './types';
|
||||||
|
|
||||||
// Trigger Nodes
|
// Trigger Nodes
|
||||||
export {
|
export {
|
||||||
triggerConfigs,
|
triggerConfigs,
|
||||||
getTriggerConfig,
|
getTriggerConfig,
|
||||||
messageTriggerConfig,
|
messageTriggerConfig,
|
||||||
@@ -147,7 +147,9 @@ export function getNodeConfig(nodeType: string): NodeConfigMeta | undefined {
|
|||||||
/**
|
/**
|
||||||
* Get all node configurations for a category
|
* Get all node configurations for a category
|
||||||
*/
|
*/
|
||||||
export function getNodeConfigsByCategory(category: NodeCategory): NodeConfigMeta[] {
|
export function getNodeConfigsByCategory(
|
||||||
|
category: NodeCategory,
|
||||||
|
): NodeConfigMeta[] {
|
||||||
return allNodeConfigs.filter((config) => config.category === category);
|
return allNodeConfigs.filter((config) => config.category === category);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -171,18 +173,18 @@ export function isValidNodeType(nodeType: string): boolean {
|
|||||||
export function getDefaultConfig(nodeType: string): Record<string, unknown> {
|
export function getDefaultConfig(nodeType: string): Record<string, unknown> {
|
||||||
const config = getNodeConfig(nodeType);
|
const config = getNodeConfig(nodeType);
|
||||||
if (!config) return {};
|
if (!config) return {};
|
||||||
|
|
||||||
// Build default config from schema defaults
|
// Build default config from schema defaults
|
||||||
const defaults: Record<string, unknown> = {};
|
const defaults: Record<string, unknown> = {};
|
||||||
for (const field of config.configSchema) {
|
for (const field of config.configSchema) {
|
||||||
defaults[field.name] = field.default;
|
defaults[field.name] = field.default;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Override with explicit defaultConfig if provided
|
// Override with explicit defaultConfig if provided
|
||||||
if (config.defaultConfig) {
|
if (config.defaultConfig) {
|
||||||
Object.assign(defaults, config.defaultConfig);
|
Object.assign(defaults, config.defaultConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
return defaults;
|
return defaults;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -191,32 +193,35 @@ export function getDefaultConfig(nodeType: string): Record<string, unknown> {
|
|||||||
*/
|
*/
|
||||||
export function validateNodeConfig(
|
export function validateNodeConfig(
|
||||||
nodeType: string,
|
nodeType: string,
|
||||||
config: Record<string, unknown>
|
config: Record<string, unknown>,
|
||||||
): { valid: boolean; errors: string[] } {
|
): { valid: boolean; errors: string[] } {
|
||||||
const nodeConfig = getNodeConfig(nodeType);
|
const nodeConfig = getNodeConfig(nodeType);
|
||||||
if (!nodeConfig) {
|
if (!nodeConfig) {
|
||||||
return { valid: false, errors: [`Unknown node type: ${nodeType}`] };
|
return { valid: false, errors: [`Unknown node type: ${nodeType}`] };
|
||||||
}
|
}
|
||||||
|
|
||||||
const errors: string[] = [];
|
const errors: string[] = [];
|
||||||
|
|
||||||
for (const field of nodeConfig.configSchema) {
|
for (const field of nodeConfig.configSchema) {
|
||||||
const value = config[field.name];
|
const value = config[field.name];
|
||||||
|
|
||||||
// Check required fields
|
// Check required fields
|
||||||
if (field.required && (value === undefined || value === null || value === '')) {
|
if (
|
||||||
|
field.required &&
|
||||||
|
(value === undefined || value === null || value === '')
|
||||||
|
) {
|
||||||
errors.push(`Field "${field.name}" is required`);
|
errors.push(`Field "${field.name}" is required`);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Skip validation for optional empty fields
|
// Skip validation for optional empty fields
|
||||||
if (!field.required && (value === undefined || value === null)) {
|
if (!field.required && (value === undefined || value === null)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Type-specific validation could be added here
|
// Type-specific validation could be added here
|
||||||
}
|
}
|
||||||
|
|
||||||
return { valid: errors.length === 0, errors };
|
return { valid: errors.length === 0, errors };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+272
-45
@@ -1,6 +1,6 @@
|
|||||||
/**
|
/**
|
||||||
* Integration Node Configurations
|
* Integration Node Configurations
|
||||||
*
|
*
|
||||||
* Defines configurations for integration node types:
|
* Defines configurations for integration node types:
|
||||||
* - database_query: Query databases
|
* - database_query: Query databases
|
||||||
* - redis_operation: Redis operations
|
* - redis_operation: Redis operations
|
||||||
@@ -64,7 +64,10 @@ export const databaseQueryConfig: NodeConfigMeta = {
|
|||||||
required: true,
|
required: true,
|
||||||
default: 'postgresql',
|
default: 'postgresql',
|
||||||
options: [
|
options: [
|
||||||
{ name: 'postgresql', label: { en_US: 'PostgreSQL', zh_Hans: 'PostgreSQL' } },
|
{
|
||||||
|
name: 'postgresql',
|
||||||
|
label: { en_US: 'PostgreSQL', zh_Hans: 'PostgreSQL' },
|
||||||
|
},
|
||||||
{ name: 'mysql', label: { en_US: 'MySQL', zh_Hans: 'MySQL' } },
|
{ name: 'mysql', label: { en_US: 'MySQL', zh_Hans: 'MySQL' } },
|
||||||
{ name: 'sqlite', label: { en_US: 'SQLite', zh_Hans: 'SQLite' } },
|
{ name: 'sqlite', label: { en_US: 'SQLite', zh_Hans: 'SQLite' } },
|
||||||
],
|
],
|
||||||
@@ -372,7 +375,8 @@ export const mcpToolConfig: NodeConfigMeta = {
|
|||||||
zh_Hans: '参数模板',
|
zh_Hans: '参数模板',
|
||||||
},
|
},
|
||||||
description: {
|
description: {
|
||||||
en_US: 'Tool arguments as JSON (supports variable interpolation). Leave empty to use input.',
|
en_US:
|
||||||
|
'Tool arguments as JSON (supports variable interpolation). Leave empty to use input.',
|
||||||
zh_Hans: '工具参数(JSON 格式,支持变量插值)。留空则使用输入。',
|
zh_Hans: '工具参数(JSON 格式,支持变量插值)。留空则使用输入。',
|
||||||
},
|
},
|
||||||
required: false,
|
required: false,
|
||||||
@@ -537,27 +541,78 @@ export const memoryStoreConfig: NodeConfigMeta = {
|
|||||||
export const difyWorkflowConfig: NodeConfigMeta = {
|
export const difyWorkflowConfig: NodeConfigMeta = {
|
||||||
nodeType: 'dify_workflow',
|
nodeType: 'dify_workflow',
|
||||||
label: { en_US: 'Dify Workflow', zh_Hans: 'Dify 工作流' },
|
label: { en_US: 'Dify Workflow', zh_Hans: 'Dify 工作流' },
|
||||||
description: { en_US: 'Call a Dify platform workflow', zh_Hans: '调用 Dify 平台工作流' },
|
description: {
|
||||||
|
en_US: 'Call a Dify platform workflow',
|
||||||
|
zh_Hans: '调用 Dify 平台工作流',
|
||||||
|
},
|
||||||
icon: 'Bot',
|
icon: 'Bot',
|
||||||
category: 'integration',
|
category: 'integration',
|
||||||
color: '#ec4899',
|
color: '#ec4899',
|
||||||
inputs: [
|
inputs: [
|
||||||
createInput('input', 'any', { description: 'Input data', label: { en_US: 'Input', zh_Hans: '输入' }, required: false }),
|
createInput('input', 'any', {
|
||||||
|
description: 'Input data',
|
||||||
|
label: { en_US: 'Input', zh_Hans: '输入' },
|
||||||
|
required: false,
|
||||||
|
}),
|
||||||
],
|
],
|
||||||
outputs: [
|
outputs: [
|
||||||
createOutput('result', 'any', { description: 'Workflow result', label: { en_US: 'Result', zh_Hans: '结果' } }),
|
createOutput('result', 'any', {
|
||||||
createOutput('success', 'boolean', { description: 'Whether call was successful', label: { en_US: 'Success', zh_Hans: '成功' } }),
|
description: 'Workflow result',
|
||||||
|
label: { en_US: 'Result', zh_Hans: '结果' },
|
||||||
|
}),
|
||||||
|
createOutput('success', 'boolean', {
|
||||||
|
description: 'Whether call was successful',
|
||||||
|
label: { en_US: 'Success', zh_Hans: '成功' },
|
||||||
|
}),
|
||||||
],
|
],
|
||||||
configSchema: [
|
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: 'base-url',
|
||||||
{ 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: 'base-url',
|
||||||
{ name: 'workflow', label: { en_US: 'Workflow', zh_Hans: '工作流' } },
|
type: DynamicFormItemType.STRING,
|
||||||
{ name: 'chatbot', label: { en_US: 'Chatbot', zh_Hans: '聊天机器人' } },
|
label: { en_US: 'Base URL', zh_Hans: 'Base URL' },
|
||||||
] },
|
description: { en_US: 'Dify API base URL', zh_Hans: 'Dify API 基础 URL' },
|
||||||
{ 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 },
|
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 },
|
defaultConfig: {
|
||||||
|
'base-url': '',
|
||||||
|
'api-key': '',
|
||||||
|
'app-type': 'workflow',
|
||||||
|
timeout: 60,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -566,22 +621,69 @@ export const difyWorkflowConfig: NodeConfigMeta = {
|
|||||||
export const difyKnowledgeQueryConfig: NodeConfigMeta = {
|
export const difyKnowledgeQueryConfig: NodeConfigMeta = {
|
||||||
nodeType: 'dify_knowledge_query',
|
nodeType: 'dify_knowledge_query',
|
||||||
label: { en_US: 'Dify Knowledge Query', zh_Hans: 'Dify 知识库查询' },
|
label: { en_US: 'Dify Knowledge Query', zh_Hans: 'Dify 知识库查询' },
|
||||||
description: { en_US: 'Query Dify knowledge base', zh_Hans: '查询 Dify 知识库' },
|
description: {
|
||||||
|
en_US: 'Query Dify knowledge base',
|
||||||
|
zh_Hans: '查询 Dify 知识库',
|
||||||
|
},
|
||||||
icon: 'Search',
|
icon: 'Search',
|
||||||
category: 'integration',
|
category: 'integration',
|
||||||
color: '#ec4899',
|
color: '#ec4899',
|
||||||
inputs: [
|
inputs: [
|
||||||
createInput('query', 'string', { description: 'Search query', label: { en_US: 'Query', zh_Hans: '查询' } }),
|
createInput('query', 'string', {
|
||||||
|
description: 'Search query',
|
||||||
|
label: { en_US: 'Query', zh_Hans: '查询' },
|
||||||
|
}),
|
||||||
],
|
],
|
||||||
outputs: [
|
outputs: [
|
||||||
createOutput('results', 'array', { description: 'Search results', label: { en_US: 'Results', zh_Hans: '结果' } }),
|
createOutput('results', 'array', {
|
||||||
createOutput('success', 'boolean', { description: 'Whether query was successful', label: { en_US: 'Success', zh_Hans: '成功' } }),
|
description: 'Search results',
|
||||||
|
label: { en_US: 'Results', zh_Hans: '结果' },
|
||||||
|
}),
|
||||||
|
createOutput('success', 'boolean', {
|
||||||
|
description: 'Whether query was successful',
|
||||||
|
label: { en_US: 'Success', zh_Hans: '成功' },
|
||||||
|
}),
|
||||||
],
|
],
|
||||||
configSchema: [
|
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: 'base-url',
|
||||||
{ 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: '' },
|
name: 'base-url',
|
||||||
{ 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 },
|
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 },
|
defaultConfig: { 'base-url': '', 'api-key': '', dataset_id: '', top_k: 5 },
|
||||||
};
|
};
|
||||||
@@ -592,20 +694,49 @@ export const difyKnowledgeQueryConfig: NodeConfigMeta = {
|
|||||||
export const n8nWorkflowConfig: NodeConfigMeta = {
|
export const n8nWorkflowConfig: NodeConfigMeta = {
|
||||||
nodeType: 'n8n_workflow',
|
nodeType: 'n8n_workflow',
|
||||||
label: { en_US: 'N8n Workflow', zh_Hans: 'n8n 工作流' },
|
label: { en_US: 'N8n Workflow', zh_Hans: 'n8n 工作流' },
|
||||||
description: { en_US: 'Call an n8n workflow via webhook', zh_Hans: '通过 webhook 调用 n8n 工作流' },
|
description: {
|
||||||
|
en_US: 'Call an n8n workflow via webhook',
|
||||||
|
zh_Hans: '通过 webhook 调用 n8n 工作流',
|
||||||
|
},
|
||||||
icon: 'Settings',
|
icon: 'Settings',
|
||||||
category: 'integration',
|
category: 'integration',
|
||||||
color: '#ec4899',
|
color: '#ec4899',
|
||||||
inputs: [
|
inputs: [
|
||||||
createInput('input', 'any', { description: 'Input data', label: { en_US: 'Input', zh_Hans: '输入' }, required: false }),
|
createInput('input', 'any', {
|
||||||
|
description: 'Input data',
|
||||||
|
label: { en_US: 'Input', zh_Hans: '输入' },
|
||||||
|
required: false,
|
||||||
|
}),
|
||||||
],
|
],
|
||||||
outputs: [
|
outputs: [
|
||||||
createOutput('result', 'any', { description: 'Workflow result', label: { en_US: 'Result', zh_Hans: '结果' } }),
|
createOutput('result', 'any', {
|
||||||
createOutput('success', 'boolean', { description: 'Whether call was successful', label: { en_US: 'Success', zh_Hans: '成功' } }),
|
description: 'Workflow result',
|
||||||
|
label: { en_US: 'Result', zh_Hans: '结果' },
|
||||||
|
}),
|
||||||
|
createOutput('success', 'boolean', {
|
||||||
|
description: 'Whether call was successful',
|
||||||
|
label: { en_US: 'Success', zh_Hans: '成功' },
|
||||||
|
}),
|
||||||
],
|
],
|
||||||
configSchema: [
|
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 },
|
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 },
|
defaultConfig: { 'webhook-url': '', timeout: 60 },
|
||||||
};
|
};
|
||||||
@@ -621,17 +752,65 @@ export const langflowFlowConfig: NodeConfigMeta = {
|
|||||||
category: 'integration',
|
category: 'integration',
|
||||||
color: '#ec4899',
|
color: '#ec4899',
|
||||||
inputs: [
|
inputs: [
|
||||||
createInput('input', 'any', { description: 'Input data', label: { en_US: 'Input', zh_Hans: '输入' }, required: false }),
|
createInput('input', 'any', {
|
||||||
|
description: 'Input data',
|
||||||
|
label: { en_US: 'Input', zh_Hans: '输入' },
|
||||||
|
required: false,
|
||||||
|
}),
|
||||||
],
|
],
|
||||||
outputs: [
|
outputs: [
|
||||||
createOutput('result', 'any', { description: 'Flow result', label: { en_US: 'Result', zh_Hans: '结果' } }),
|
createOutput('result', 'any', {
|
||||||
createOutput('success', 'boolean', { description: 'Whether call was successful', label: { en_US: 'Success', zh_Hans: '成功' } }),
|
description: 'Flow result',
|
||||||
|
label: { en_US: 'Result', zh_Hans: '结果' },
|
||||||
|
}),
|
||||||
|
createOutput('success', 'boolean', {
|
||||||
|
description: 'Whether call was successful',
|
||||||
|
label: { en_US: 'Success', zh_Hans: '成功' },
|
||||||
|
}),
|
||||||
],
|
],
|
||||||
configSchema: [
|
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: 'base-url',
|
||||||
{ 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: '' },
|
name: 'base-url',
|
||||||
{ 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 },
|
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 },
|
defaultConfig: { 'base-url': '', 'flow-id': '', 'api-key': '', timeout: 60 },
|
||||||
};
|
};
|
||||||
@@ -647,19 +826,65 @@ export const cozeBotConfig: NodeConfigMeta = {
|
|||||||
category: 'integration',
|
category: 'integration',
|
||||||
color: '#ec4899',
|
color: '#ec4899',
|
||||||
inputs: [
|
inputs: [
|
||||||
createInput('message', 'string', { description: 'Message to send', label: { en_US: 'Message', zh_Hans: '消息' } }),
|
createInput('message', 'string', {
|
||||||
|
description: 'Message to send',
|
||||||
|
label: { en_US: 'Message', zh_Hans: '消息' },
|
||||||
|
}),
|
||||||
],
|
],
|
||||||
outputs: [
|
outputs: [
|
||||||
createOutput('result', 'any', { description: 'Bot response', label: { en_US: 'Result', zh_Hans: '结果' } }),
|
createOutput('result', 'any', {
|
||||||
createOutput('success', 'boolean', { description: 'Whether call was successful', label: { en_US: 'Success', zh_Hans: '成功' } }),
|
description: 'Bot response',
|
||||||
|
label: { en_US: 'Result', zh_Hans: '结果' },
|
||||||
|
}),
|
||||||
|
createOutput('success', 'boolean', {
|
||||||
|
description: 'Whether call was successful',
|
||||||
|
label: { en_US: 'Success', zh_Hans: '成功' },
|
||||||
|
}),
|
||||||
],
|
],
|
||||||
configSchema: [
|
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-base',
|
||||||
{ 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: '' },
|
name: 'api-base',
|
||||||
{ 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 },
|
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 },
|
defaultConfig: {
|
||||||
|
'api-base': 'https://api.coze.com',
|
||||||
|
'bot-id': '',
|
||||||
|
'api-key': '',
|
||||||
|
timeout: 60,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -680,6 +905,8 @@ export const integrationConfigs: NodeConfigMeta[] = [
|
|||||||
/**
|
/**
|
||||||
* Get integration config by type
|
* Get integration config by type
|
||||||
*/
|
*/
|
||||||
export function getIntegrationConfig(nodeType: string): NodeConfigMeta | undefined {
|
export function getIntegrationConfig(
|
||||||
|
nodeType: string,
|
||||||
|
): NodeConfigMeta | undefined {
|
||||||
return integrationConfigs.find((config) => config.nodeType === nodeType);
|
return integrationConfigs.find((config) => config.nodeType === nodeType);
|
||||||
}
|
}
|
||||||
|
|||||||
+67
-20
@@ -1,6 +1,6 @@
|
|||||||
/**
|
/**
|
||||||
* Process Node Configurations
|
* Process Node Configurations
|
||||||
*
|
*
|
||||||
* Defines configurations for general processing node types:
|
* Defines configurations for general processing node types:
|
||||||
* - text_template: Generate text using templates
|
* - text_template: Generate text using templates
|
||||||
* - json_transform: Transform JSON data
|
* - json_transform: Transform JSON data
|
||||||
@@ -52,7 +52,8 @@ export const textTemplateConfig: NodeConfigMeta = {
|
|||||||
zh_Hans: '模板',
|
zh_Hans: '模板',
|
||||||
},
|
},
|
||||||
description: {
|
description: {
|
||||||
en_US: 'Text template with variable placeholders (e.g., {{variable_name}})',
|
en_US:
|
||||||
|
'Text template with variable placeholders (e.g., {{variable_name}})',
|
||||||
zh_Hans: '带有变量占位符的文本模板(例如 {{variable_name}})',
|
zh_Hans: '带有变量占位符的文本模板(例如 {{variable_name}})',
|
||||||
},
|
},
|
||||||
required: true,
|
required: true,
|
||||||
@@ -141,9 +142,18 @@ export const jsonTransformConfig: NodeConfigMeta = {
|
|||||||
required: true,
|
required: true,
|
||||||
default: 'jmespath',
|
default: 'jmespath',
|
||||||
options: [
|
options: [
|
||||||
{ name: 'jmespath', label: { en_US: 'JMESPath Expression', zh_Hans: 'JMESPath 表达式' } },
|
{
|
||||||
{ name: 'jsonpath', label: { en_US: 'JSONPath Expression', zh_Hans: 'JSONPath 表达式' } },
|
name: 'jmespath',
|
||||||
{ name: 'mapping', label: { en_US: 'Field Mapping', zh_Hans: '字段映射' } },
|
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: '字段映射' },
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -175,8 +185,10 @@ export const jsonTransformConfig: NodeConfigMeta = {
|
|||||||
zh_Hans: '字段映射',
|
zh_Hans: '字段映射',
|
||||||
},
|
},
|
||||||
description: {
|
description: {
|
||||||
en_US: 'JSON object defining field mappings: {"output_field": "input.path.to.field"}',
|
en_US:
|
||||||
zh_Hans: '定义字段映射的 JSON 对象: {"output_field": "input.path.to.field"}',
|
'JSON object defining field mappings: {"output_field": "input.path.to.field"}',
|
||||||
|
zh_Hans:
|
||||||
|
'定义字段映射的 JSON 对象: {"output_field": "input.path.to.field"}',
|
||||||
},
|
},
|
||||||
required: true,
|
required: true,
|
||||||
default: '{}',
|
default: '{}',
|
||||||
@@ -243,7 +255,10 @@ export const codeExecutorConfig: NodeConfigMeta = {
|
|||||||
required: true,
|
required: true,
|
||||||
default: 'javascript',
|
default: 'javascript',
|
||||||
options: [
|
options: [
|
||||||
{ name: 'javascript', label: { en_US: 'JavaScript', zh_Hans: 'JavaScript' } },
|
{
|
||||||
|
name: 'javascript',
|
||||||
|
label: { en_US: 'JavaScript', zh_Hans: 'JavaScript' },
|
||||||
|
},
|
||||||
{ name: 'python', label: { en_US: 'Python', zh_Hans: 'Python' } },
|
{ name: 'python', label: { en_US: 'Python', zh_Hans: 'Python' } },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@@ -256,11 +271,13 @@ export const codeExecutorConfig: NodeConfigMeta = {
|
|||||||
zh_Hans: '代码',
|
zh_Hans: '代码',
|
||||||
},
|
},
|
||||||
description: {
|
description: {
|
||||||
en_US: 'Code to execute. Use `input` to access input data and return the result.',
|
en_US:
|
||||||
|
'Code to execute. Use `input` to access input data and return the result.',
|
||||||
zh_Hans: '要执行的代码。使用 `input` 访问输入数据,并返回结果。',
|
zh_Hans: '要执行的代码。使用 `input` 访问输入数据,并返回结果。',
|
||||||
},
|
},
|
||||||
required: true,
|
required: true,
|
||||||
default: '// Access input with: input\n// Return result with: return result;\n\nreturn input;',
|
default:
|
||||||
|
'// Access input with: input\n// Return result with: return result;\n\nreturn input;',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'timeout',
|
id: 'timeout',
|
||||||
@@ -334,13 +351,25 @@ export const dataAggregatorConfig: NodeConfigMeta = {
|
|||||||
required: true,
|
required: true,
|
||||||
default: 'array',
|
default: 'array',
|
||||||
options: [
|
options: [
|
||||||
{ name: 'array', label: { en_US: 'Collect to Array', zh_Hans: '收集为数组' } },
|
{
|
||||||
{ name: 'concat', label: { en_US: 'Concatenate Strings', zh_Hans: '连接字符串' } },
|
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: 'sum', label: { en_US: 'Sum Numbers', zh_Hans: '求和' } },
|
||||||
{ name: 'average', label: { en_US: 'Average Numbers', zh_Hans: '求平均' } },
|
{
|
||||||
|
name: 'average',
|
||||||
|
label: { en_US: 'Average Numbers', zh_Hans: '求平均' },
|
||||||
|
},
|
||||||
{ name: 'min', label: { en_US: 'Minimum', zh_Hans: '最小值' } },
|
{ name: 'min', label: { en_US: 'Minimum', zh_Hans: '最小值' } },
|
||||||
{ name: 'max', label: { en_US: 'Maximum', zh_Hans: '最大值' } },
|
{ name: 'max', label: { en_US: 'Maximum', zh_Hans: '最大值' } },
|
||||||
{ name: 'merge', label: { en_US: 'Merge Objects', zh_Hans: '合并对象' } },
|
{
|
||||||
|
name: 'merge',
|
||||||
|
label: { en_US: 'Merge Objects', zh_Hans: '合并对象' },
|
||||||
|
},
|
||||||
{ name: 'first', label: { en_US: 'First Item', zh_Hans: '第一项' } },
|
{ name: 'first', label: { en_US: 'First Item', zh_Hans: '第一项' } },
|
||||||
{ name: 'last', label: { en_US: 'Last Item', zh_Hans: '最后一项' } },
|
{ name: 'last', label: { en_US: 'Last Item', zh_Hans: '最后一项' } },
|
||||||
],
|
],
|
||||||
@@ -437,11 +466,23 @@ export const textSplitterConfig: NodeConfigMeta = {
|
|||||||
required: true,
|
required: true,
|
||||||
default: 'separator',
|
default: 'separator',
|
||||||
options: [
|
options: [
|
||||||
{ name: 'separator', label: { en_US: 'By Separator', zh_Hans: '按分隔符' } },
|
{
|
||||||
|
name: 'separator',
|
||||||
|
label: { en_US: 'By Separator', zh_Hans: '按分隔符' },
|
||||||
|
},
|
||||||
{ name: 'length', label: { en_US: 'By Length', 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: 'sentences',
|
||||||
{ name: 'regex', label: { en_US: 'By Regex', zh_Hans: '按正则表达式' } },
|
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: '按正则表达式' },
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -613,7 +654,10 @@ export const variableAssignmentConfig: NodeConfigMeta = {
|
|||||||
options: [
|
options: [
|
||||||
{ name: 'input', label: { en_US: 'From Input', zh_Hans: '来自输入' } },
|
{ name: 'input', label: { en_US: 'From Input', zh_Hans: '来自输入' } },
|
||||||
{ name: 'static', label: { en_US: 'Static Value', zh_Hans: '静态值' } },
|
{ name: 'static', label: { en_US: 'Static Value', zh_Hans: '静态值' } },
|
||||||
{ name: 'expression', label: { en_US: 'Expression', zh_Hans: '表达式' } },
|
{
|
||||||
|
name: 'expression',
|
||||||
|
label: { en_US: 'Expression', zh_Hans: '表达式' },
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -714,7 +758,10 @@ export const dataTransformConfig: NodeConfigMeta = {
|
|||||||
{ name: 'template', label: { en_US: 'Template', zh_Hans: '模板' } },
|
{ name: 'template', label: { en_US: 'Template', zh_Hans: '模板' } },
|
||||||
{ name: 'jsonpath', label: { en_US: 'JSONPath', zh_Hans: 'JSONPath' } },
|
{ name: 'jsonpath', label: { en_US: 'JSONPath', zh_Hans: 'JSONPath' } },
|
||||||
{ name: 'jmespath', label: { en_US: 'JMESPath', zh_Hans: 'JMESPath' } },
|
{ name: 'jmespath', label: { en_US: 'JMESPath', zh_Hans: 'JMESPath' } },
|
||||||
{ name: 'expression', label: { en_US: 'Expression', zh_Hans: '表达式' } },
|
{
|
||||||
|
name: 'expression',
|
||||||
|
label: { en_US: 'Expression', zh_Hans: '表达式' },
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
+109
-26
@@ -1,6 +1,6 @@
|
|||||||
/**
|
/**
|
||||||
* Trigger Node Configurations
|
* Trigger Node Configurations
|
||||||
*
|
*
|
||||||
* Defines configurations for all trigger node types:
|
* Defines configurations for all trigger node types:
|
||||||
* - message_trigger: Triggered by incoming messages
|
* - message_trigger: Triggered by incoming messages
|
||||||
* - cron_trigger: Triggered by scheduled time
|
* - cron_trigger: Triggered by scheduled time
|
||||||
@@ -62,9 +62,15 @@ export const messageTriggerConfig: NodeConfigMeta = {
|
|||||||
default: 'all',
|
default: 'all',
|
||||||
options: [
|
options: [
|
||||||
{ name: 'all', label: { en_US: 'All Messages', zh_Hans: '所有消息' } },
|
{ name: 'all', label: { en_US: 'All Messages', zh_Hans: '所有消息' } },
|
||||||
{ name: 'prefix', label: { en_US: 'Prefix Match', zh_Hans: '前缀匹配' } },
|
{
|
||||||
|
name: 'prefix',
|
||||||
|
label: { en_US: 'Prefix Match', zh_Hans: '前缀匹配' },
|
||||||
|
},
|
||||||
{ name: 'regex', label: { en_US: 'Regex Match', zh_Hans: '正则匹配' } },
|
{ name: 'regex', label: { en_US: 'Regex Match', zh_Hans: '正则匹配' } },
|
||||||
{ name: 'contains', label: { en_US: 'Contains Keyword', zh_Hans: '包含关键词' } },
|
{
|
||||||
|
name: 'contains',
|
||||||
|
label: { en_US: 'Contains Keyword', zh_Hans: '包含关键词' },
|
||||||
|
},
|
||||||
{ name: 'exact', label: { en_US: 'Exact Match', zh_Hans: '精确匹配' } },
|
{ name: 'exact', label: { en_US: 'Exact Match', zh_Hans: '精确匹配' } },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@@ -77,7 +83,8 @@ export const messageTriggerConfig: NodeConfigMeta = {
|
|||||||
zh_Hans: '匹配模式',
|
zh_Hans: '匹配模式',
|
||||||
},
|
},
|
||||||
description: {
|
description: {
|
||||||
en_US: 'The pattern to match against the message (prefix, regex, keyword, or exact text)',
|
en_US:
|
||||||
|
'The pattern to match against the message (prefix, regex, keyword, or exact text)',
|
||||||
zh_Hans: '用于匹配消息的模式(前缀、正则表达式、关键词或精确文本)',
|
zh_Hans: '用于匹配消息的模式(前缀、正则表达式、关键词或精确文本)',
|
||||||
},
|
},
|
||||||
required: false,
|
required: false,
|
||||||
@@ -172,12 +179,39 @@ export const cronTriggerConfig: NodeConfigMeta = {
|
|||||||
default: 'Asia/Shanghai',
|
default: 'Asia/Shanghai',
|
||||||
options: [
|
options: [
|
||||||
{ name: 'UTC', label: { en_US: 'UTC', zh_Hans: 'UTC' } },
|
{ 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: 'Asia/Shanghai',
|
||||||
{ name: 'America/New_York', label: { en_US: 'America/New_York (EST)', zh_Hans: '美国/纽约 (EST)' } },
|
label: {
|
||||||
{ name: 'America/Los_Angeles', label: { en_US: 'America/Los_Angeles (PST)', zh_Hans: '美国/洛杉矶 (PST)' } },
|
en_US: 'Asia/Shanghai (UTC+8)',
|
||||||
{ name: 'Europe/London', label: { en_US: 'Europe/London (GMT)', zh_Hans: '欧洲/伦敦 (GMT)' } },
|
zh_Hans: '亚洲/上海 (UTC+8)',
|
||||||
{ name: 'Europe/Berlin', label: { en_US: 'Europe/Berlin (CET)', zh_Hans: '欧洲/柏林 (CET)' } },
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
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)' },
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -230,7 +264,8 @@ export const webhookTriggerConfig: NodeConfigMeta = {
|
|||||||
zh_Hans: 'Webhook 触发',
|
zh_Hans: 'Webhook 触发',
|
||||||
},
|
},
|
||||||
description: {
|
description: {
|
||||||
en_US: 'Trigger workflow when an HTTP request is received at the webhook URL',
|
en_US:
|
||||||
|
'Trigger workflow when an HTTP request is received at the webhook URL',
|
||||||
zh_Hans: '当在 Webhook URL 收到 HTTP 请求时触发工作流',
|
zh_Hans: '当在 Webhook URL 收到 HTTP 请求时触发工作流',
|
||||||
},
|
},
|
||||||
icon: 'Webhook',
|
icon: 'Webhook',
|
||||||
@@ -288,8 +323,14 @@ export const webhookTriggerConfig: NodeConfigMeta = {
|
|||||||
default: 'none',
|
default: 'none',
|
||||||
options: [
|
options: [
|
||||||
{ name: 'none', label: { en_US: 'None', zh_Hans: '无' } },
|
{ 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: '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: '基本认证' } },
|
{ name: 'basic', label: { en_US: 'Basic Auth', zh_Hans: '基本认证' } },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@@ -328,10 +369,25 @@ export const webhookTriggerConfig: NodeConfigMeta = {
|
|||||||
required: false,
|
required: false,
|
||||||
default: 'application/json',
|
default: 'application/json',
|
||||||
options: [
|
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: 'application/json',
|
||||||
{ name: 'multipart/form-data', label: { en_US: 'multipart/form-data', zh_Hans: '表单数据' } },
|
label: { en_US: 'application/json', zh_Hans: 'JSON' },
|
||||||
{ name: 'text/plain', label: { en_US: 'text/plain', zh_Hans: '纯文本' } },
|
},
|
||||||
|
{
|
||||||
|
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: '纯文本' },
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -424,15 +480,42 @@ export const eventTriggerConfig: NodeConfigMeta = {
|
|||||||
required: true,
|
required: true,
|
||||||
default: 'member_join',
|
default: 'member_join',
|
||||||
options: [
|
options: [
|
||||||
{ name: 'member_join', label: { en_US: 'Member Join', zh_Hans: '成员加入' } },
|
{
|
||||||
{ name: 'member_leave', label: { en_US: 'Member Leave', zh_Hans: '成员离开' } },
|
name: 'member_join',
|
||||||
{ name: 'message_recall', label: { en_US: 'Message Recall', zh_Hans: '消息撤回' } },
|
label: { en_US: 'Member Join', 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: 'member_leave',
|
||||||
{ name: 'bot_removed', label: { en_US: 'Bot Removed from Group', zh_Hans: '机器人被移出群' } },
|
label: { en_US: 'Member Leave', zh_Hans: '成员离开' },
|
||||||
{ name: 'friend_request', label: { en_US: 'Friend Request', zh_Hans: '好友请求' } },
|
},
|
||||||
{ name: 'group_request', label: { en_US: 'Group Join Request', 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: '入群请求' },
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/**
|
/**
|
||||||
* Workflow Node Configuration Types
|
* Workflow Node Configuration Types
|
||||||
*
|
*
|
||||||
* This module defines the types used for node configuration metadata.
|
* This module defines the types used for node configuration metadata.
|
||||||
* It extends the existing dynamic form system to support workflow-specific features.
|
* It extends the existing dynamic form system to support workflow-specific features.
|
||||||
*/
|
*/
|
||||||
@@ -23,40 +23,40 @@ export interface ExtendedPortDefinition extends PortDefinition {
|
|||||||
export interface NodeConfigMeta {
|
export interface NodeConfigMeta {
|
||||||
/** Unique node type identifier */
|
/** Unique node type identifier */
|
||||||
nodeType: string;
|
nodeType: string;
|
||||||
|
|
||||||
/** Display name for the node */
|
/** Display name for the node */
|
||||||
label: I18nObject;
|
label: I18nObject;
|
||||||
|
|
||||||
/** Description of what the node does */
|
/** Description of what the node does */
|
||||||
description: I18nObject;
|
description: I18nObject;
|
||||||
|
|
||||||
/** Icon name (from lucide-react) */
|
/** Icon name (from lucide-react) */
|
||||||
icon: string;
|
icon: string;
|
||||||
|
|
||||||
/** Node category for organization */
|
/** Node category for organization */
|
||||||
category: NodeCategory;
|
category: NodeCategory;
|
||||||
|
|
||||||
/** Color for the node header */
|
/** Color for the node header */
|
||||||
color?: string;
|
color?: string;
|
||||||
|
|
||||||
/** Input port definitions */
|
/** Input port definitions */
|
||||||
inputs: ExtendedPortDefinition[];
|
inputs: ExtendedPortDefinition[];
|
||||||
|
|
||||||
/** Output port definitions */
|
/** Output port definitions */
|
||||||
outputs: ExtendedPortDefinition[];
|
outputs: ExtendedPortDefinition[];
|
||||||
|
|
||||||
/** Configuration schema using the dynamic form system */
|
/** Configuration schema using the dynamic form system */
|
||||||
configSchema: IDynamicFormItemSchema[];
|
configSchema: IDynamicFormItemSchema[];
|
||||||
|
|
||||||
/** Default configuration values */
|
/** Default configuration values */
|
||||||
defaultConfig?: Record<string, unknown>;
|
defaultConfig?: Record<string, unknown>;
|
||||||
|
|
||||||
/** Whether this node can be the starting point of a workflow */
|
/** Whether this node can be the starting point of a workflow */
|
||||||
isEntryPoint?: boolean;
|
isEntryPoint?: boolean;
|
||||||
|
|
||||||
/** Maximum number of this node type allowed in a workflow (undefined = unlimited) */
|
/** Maximum number of this node type allowed in a workflow (undefined = unlimited) */
|
||||||
maxInstances?: number;
|
maxInstances?: number;
|
||||||
|
|
||||||
/** Documentation URL */
|
/** Documentation URL */
|
||||||
docsUrl?: string;
|
docsUrl?: string;
|
||||||
}
|
}
|
||||||
@@ -76,7 +76,7 @@ export function createPort(
|
|||||||
description?: string;
|
description?: string;
|
||||||
required?: boolean;
|
required?: boolean;
|
||||||
label?: I18nObject;
|
label?: I18nObject;
|
||||||
}
|
},
|
||||||
): ExtendedPortDefinition {
|
): ExtendedPortDefinition {
|
||||||
return {
|
return {
|
||||||
name,
|
name,
|
||||||
@@ -97,9 +97,12 @@ export function createInput(
|
|||||||
description?: string;
|
description?: string;
|
||||||
required?: boolean;
|
required?: boolean;
|
||||||
label?: I18nObject;
|
label?: I18nObject;
|
||||||
}
|
},
|
||||||
): ExtendedPortDefinition {
|
): ExtendedPortDefinition {
|
||||||
return createPort(name, type, { ...options, required: options?.required ?? true });
|
return createPort(name, type, {
|
||||||
|
...options,
|
||||||
|
required: options?.required ?? true,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -111,7 +114,7 @@ export function createOutput(
|
|||||||
options?: {
|
options?: {
|
||||||
description?: string;
|
description?: string;
|
||||||
label?: I18nObject;
|
label?: I18nObject;
|
||||||
}
|
},
|
||||||
): ExtendedPortDefinition {
|
): ExtendedPortDefinition {
|
||||||
return createPort(name, type, { ...options, required: false });
|
return createPort(name, type, { ...options, required: false });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,53 +48,176 @@ import { resolveI18nLabel, maybeTranslateKey } from './workflow-i18n';
|
|||||||
// Single source of truth. Used by WorkflowNodeComponent,
|
// Single source of truth. Used by WorkflowNodeComponent,
|
||||||
// NodePalette, and useWorkflowStore.
|
// NodePalette, and useWorkflowStore.
|
||||||
|
|
||||||
export const NODE_TYPE_I18N_KEYS: Record<string, { labelKey: string; descriptionKey: string }> = {
|
export const NODE_TYPE_I18N_KEYS: Record<
|
||||||
|
string,
|
||||||
|
{ labelKey: string; descriptionKey: string }
|
||||||
|
> = {
|
||||||
// Trigger
|
// Trigger
|
||||||
'trigger.message_trigger': { labelKey: 'workflows.nodes.messageTrigger', descriptionKey: 'workflows.nodes.messageTriggerDescription' },
|
'trigger.message_trigger': {
|
||||||
'trigger.cron_trigger': { labelKey: 'workflows.nodes.cronTrigger', descriptionKey: 'workflows.nodes.cronTriggerDescription' },
|
labelKey: 'workflows.nodes.messageTrigger',
|
||||||
'trigger.webhook_trigger': { labelKey: 'workflows.nodes.webhookTrigger', descriptionKey: 'workflows.nodes.webhookTriggerDescription' },
|
descriptionKey: 'workflows.nodes.messageTriggerDescription',
|
||||||
'trigger.event_trigger': { labelKey: 'workflows.nodes.eventTrigger', descriptionKey: 'workflows.nodes.eventTriggerDescription' },
|
},
|
||||||
|
'trigger.cron_trigger': {
|
||||||
|
labelKey: 'workflows.nodes.cronTrigger',
|
||||||
|
descriptionKey: 'workflows.nodes.cronTriggerDescription',
|
||||||
|
},
|
||||||
|
'trigger.webhook_trigger': {
|
||||||
|
labelKey: 'workflows.nodes.webhookTrigger',
|
||||||
|
descriptionKey: 'workflows.nodes.webhookTriggerDescription',
|
||||||
|
},
|
||||||
|
'trigger.event_trigger': {
|
||||||
|
labelKey: 'workflows.nodes.eventTrigger',
|
||||||
|
descriptionKey: 'workflows.nodes.eventTriggerDescription',
|
||||||
|
},
|
||||||
// Process / AI
|
// Process / AI
|
||||||
'process.llm_call': { labelKey: 'workflows.nodes.llmCall', descriptionKey: 'workflows.nodes.llmCallDescription' },
|
'process.llm_call': {
|
||||||
'process.question_classifier': { labelKey: 'workflows.nodes.questionClassifier', descriptionKey: 'workflows.nodes.questionClassifierDescription' },
|
labelKey: 'workflows.nodes.llmCall',
|
||||||
'process.parameter_extractor': { labelKey: 'workflows.nodes.parameterExtractor', descriptionKey: 'workflows.nodes.parameterExtractorDescription' },
|
descriptionKey: 'workflows.nodes.llmCallDescription',
|
||||||
'process.knowledge_retrieval': { labelKey: 'workflows.nodes.knowledgeRetrieval', descriptionKey: 'workflows.nodes.knowledgeRetrievalDescription' },
|
},
|
||||||
'process.code_executor': { labelKey: 'workflows.nodes.codeExecutor', descriptionKey: 'workflows.nodes.codeExecutorDescription' },
|
'process.question_classifier': {
|
||||||
'process.http_request': { labelKey: 'workflows.nodes.httpRequest', descriptionKey: 'workflows.nodes.httpRequestDescription' },
|
labelKey: 'workflows.nodes.questionClassifier',
|
||||||
'process.data_transform': { labelKey: 'workflows.nodes.dataTransform', descriptionKey: 'workflows.nodes.dataTransformDescription' },
|
descriptionKey: 'workflows.nodes.questionClassifierDescription',
|
||||||
'process.text_template': { labelKey: 'workflows.nodes.textTemplate', descriptionKey: 'workflows.nodes.textTemplateDescription' },
|
},
|
||||||
'process.json_transform': { labelKey: 'workflows.nodes.jsonTransform', descriptionKey: 'workflows.nodes.jsonTransformDescription' },
|
'process.parameter_extractor': {
|
||||||
'process.data_aggregator': { labelKey: 'workflows.nodes.dataAggregator', descriptionKey: 'workflows.nodes.dataAggregatorDescription' },
|
labelKey: 'workflows.nodes.parameterExtractor',
|
||||||
'process.text_splitter': { labelKey: 'workflows.nodes.textSplitter', descriptionKey: 'workflows.nodes.textSplitterDescription' },
|
descriptionKey: 'workflows.nodes.parameterExtractorDescription',
|
||||||
'process.variable_assignment': { labelKey: 'workflows.nodes.variableAssignment', descriptionKey: 'workflows.nodes.variableAssignmentDescription' },
|
},
|
||||||
|
'process.knowledge_retrieval': {
|
||||||
|
labelKey: 'workflows.nodes.knowledgeRetrieval',
|
||||||
|
descriptionKey: 'workflows.nodes.knowledgeRetrievalDescription',
|
||||||
|
},
|
||||||
|
'process.code_executor': {
|
||||||
|
labelKey: 'workflows.nodes.codeExecutor',
|
||||||
|
descriptionKey: 'workflows.nodes.codeExecutorDescription',
|
||||||
|
},
|
||||||
|
'process.http_request': {
|
||||||
|
labelKey: 'workflows.nodes.httpRequest',
|
||||||
|
descriptionKey: 'workflows.nodes.httpRequestDescription',
|
||||||
|
},
|
||||||
|
'process.data_transform': {
|
||||||
|
labelKey: 'workflows.nodes.dataTransform',
|
||||||
|
descriptionKey: 'workflows.nodes.dataTransformDescription',
|
||||||
|
},
|
||||||
|
'process.text_template': {
|
||||||
|
labelKey: 'workflows.nodes.textTemplate',
|
||||||
|
descriptionKey: 'workflows.nodes.textTemplateDescription',
|
||||||
|
},
|
||||||
|
'process.json_transform': {
|
||||||
|
labelKey: 'workflows.nodes.jsonTransform',
|
||||||
|
descriptionKey: 'workflows.nodes.jsonTransformDescription',
|
||||||
|
},
|
||||||
|
'process.data_aggregator': {
|
||||||
|
labelKey: 'workflows.nodes.dataAggregator',
|
||||||
|
descriptionKey: 'workflows.nodes.dataAggregatorDescription',
|
||||||
|
},
|
||||||
|
'process.text_splitter': {
|
||||||
|
labelKey: 'workflows.nodes.textSplitter',
|
||||||
|
descriptionKey: 'workflows.nodes.textSplitterDescription',
|
||||||
|
},
|
||||||
|
'process.variable_assignment': {
|
||||||
|
labelKey: 'workflows.nodes.variableAssignment',
|
||||||
|
descriptionKey: 'workflows.nodes.variableAssignmentDescription',
|
||||||
|
},
|
||||||
// Control
|
// Control
|
||||||
'control.condition': { labelKey: 'workflows.nodes.condition', descriptionKey: 'workflows.nodes.conditionDescription' },
|
'control.condition': {
|
||||||
'control.switch': { labelKey: 'workflows.nodes.switch', descriptionKey: 'workflows.nodes.switchDescription' },
|
labelKey: 'workflows.nodes.condition',
|
||||||
'control.loop': { labelKey: 'workflows.nodes.loop', descriptionKey: 'workflows.nodes.loopDescription' },
|
descriptionKey: 'workflows.nodes.conditionDescription',
|
||||||
'control.iterator': { labelKey: 'workflows.nodes.iterator', descriptionKey: 'workflows.nodes.iteratorDescription' },
|
},
|
||||||
'control.parallel': { labelKey: 'workflows.nodes.parallel', descriptionKey: 'workflows.nodes.parallelDescription' },
|
'control.switch': {
|
||||||
'control.wait': { labelKey: 'workflows.nodes.wait', descriptionKey: 'workflows.nodes.waitDescription' },
|
labelKey: 'workflows.nodes.switch',
|
||||||
'control.merge': { labelKey: 'workflows.nodes.merge', descriptionKey: 'workflows.nodes.mergeDescription' },
|
descriptionKey: 'workflows.nodes.switchDescription',
|
||||||
'control.variable_aggregator': { labelKey: 'workflows.nodes.variableAggregator', descriptionKey: 'workflows.nodes.variableAggregatorDescription' },
|
},
|
||||||
|
'control.loop': {
|
||||||
|
labelKey: 'workflows.nodes.loop',
|
||||||
|
descriptionKey: 'workflows.nodes.loopDescription',
|
||||||
|
},
|
||||||
|
'control.iterator': {
|
||||||
|
labelKey: 'workflows.nodes.iterator',
|
||||||
|
descriptionKey: 'workflows.nodes.iteratorDescription',
|
||||||
|
},
|
||||||
|
'control.parallel': {
|
||||||
|
labelKey: 'workflows.nodes.parallel',
|
||||||
|
descriptionKey: 'workflows.nodes.parallelDescription',
|
||||||
|
},
|
||||||
|
'control.wait': {
|
||||||
|
labelKey: 'workflows.nodes.wait',
|
||||||
|
descriptionKey: 'workflows.nodes.waitDescription',
|
||||||
|
},
|
||||||
|
'control.merge': {
|
||||||
|
labelKey: 'workflows.nodes.merge',
|
||||||
|
descriptionKey: 'workflows.nodes.mergeDescription',
|
||||||
|
},
|
||||||
|
'control.variable_aggregator': {
|
||||||
|
labelKey: 'workflows.nodes.variableAggregator',
|
||||||
|
descriptionKey: 'workflows.nodes.variableAggregatorDescription',
|
||||||
|
},
|
||||||
// Action
|
// Action
|
||||||
'action.send_message': { labelKey: 'workflows.nodes.sendMessage', descriptionKey: 'workflows.nodes.sendMessageDescription' },
|
'action.send_message': {
|
||||||
'action.reply_message': { labelKey: 'workflows.nodes.replyMessage', descriptionKey: 'workflows.nodes.replyMessageDescription' },
|
labelKey: 'workflows.nodes.sendMessage',
|
||||||
'action.store_data': { labelKey: 'workflows.nodes.storeData', descriptionKey: 'workflows.nodes.storeDataDescription' },
|
descriptionKey: 'workflows.nodes.sendMessageDescription',
|
||||||
'action.call_pipeline': { labelKey: 'workflows.nodes.callPipeline', descriptionKey: 'workflows.nodes.callPipelineDescription' },
|
},
|
||||||
'action.set_variable': { labelKey: 'workflows.nodes.setVariable', descriptionKey: 'workflows.nodes.setVariableDescription' },
|
'action.reply_message': {
|
||||||
'action.opening_statement': { labelKey: 'workflows.nodes.openingStatement', descriptionKey: 'workflows.nodes.openingStatementDescription' },
|
labelKey: 'workflows.nodes.replyMessage',
|
||||||
'action.end': { labelKey: 'workflows.nodes.end', descriptionKey: 'workflows.nodes.endDescription' },
|
descriptionKey: 'workflows.nodes.replyMessageDescription',
|
||||||
|
},
|
||||||
|
'action.store_data': {
|
||||||
|
labelKey: 'workflows.nodes.storeData',
|
||||||
|
descriptionKey: 'workflows.nodes.storeDataDescription',
|
||||||
|
},
|
||||||
|
'action.call_pipeline': {
|
||||||
|
labelKey: 'workflows.nodes.callPipeline',
|
||||||
|
descriptionKey: 'workflows.nodes.callPipelineDescription',
|
||||||
|
},
|
||||||
|
'action.set_variable': {
|
||||||
|
labelKey: 'workflows.nodes.setVariable',
|
||||||
|
descriptionKey: 'workflows.nodes.setVariableDescription',
|
||||||
|
},
|
||||||
|
'action.opening_statement': {
|
||||||
|
labelKey: 'workflows.nodes.openingStatement',
|
||||||
|
descriptionKey: 'workflows.nodes.openingStatementDescription',
|
||||||
|
},
|
||||||
|
'action.end': {
|
||||||
|
labelKey: 'workflows.nodes.end',
|
||||||
|
descriptionKey: 'workflows.nodes.endDescription',
|
||||||
|
},
|
||||||
// Integration – external services
|
// Integration – external services
|
||||||
'integration.dify_workflow': { labelKey: 'workflows.nodes.difyWorkflow', descriptionKey: 'workflows.nodes.difyWorkflowDescription' },
|
'integration.dify_workflow': {
|
||||||
'integration.dify_knowledge_query': { labelKey: 'workflows.nodes.difyKnowledgeQuery', descriptionKey: 'workflows.nodes.difyKnowledgeQueryDescription' },
|
labelKey: 'workflows.nodes.difyWorkflow',
|
||||||
'integration.n8n_workflow': { labelKey: 'workflows.nodes.n8nWorkflow', descriptionKey: 'workflows.nodes.n8nWorkflowDescription' },
|
descriptionKey: 'workflows.nodes.difyWorkflowDescription',
|
||||||
'integration.langflow_flow': { labelKey: 'workflows.nodes.langflowFlow', descriptionKey: 'workflows.nodes.langflowFlowDescription' },
|
},
|
||||||
'integration.coze_bot': { labelKey: 'workflows.nodes.cozeBot', descriptionKey: 'workflows.nodes.cozeBotDescription' },
|
'integration.dify_knowledge_query': {
|
||||||
|
labelKey: 'workflows.nodes.difyKnowledgeQuery',
|
||||||
|
descriptionKey: 'workflows.nodes.difyKnowledgeQueryDescription',
|
||||||
|
},
|
||||||
|
'integration.n8n_workflow': {
|
||||||
|
labelKey: 'workflows.nodes.n8nWorkflow',
|
||||||
|
descriptionKey: 'workflows.nodes.n8nWorkflowDescription',
|
||||||
|
},
|
||||||
|
'integration.langflow_flow': {
|
||||||
|
labelKey: 'workflows.nodes.langflowFlow',
|
||||||
|
descriptionKey: 'workflows.nodes.langflowFlowDescription',
|
||||||
|
},
|
||||||
|
'integration.coze_bot': {
|
||||||
|
labelKey: 'workflows.nodes.cozeBot',
|
||||||
|
descriptionKey: 'workflows.nodes.cozeBotDescription',
|
||||||
|
},
|
||||||
// Integration – data & tools
|
// Integration – data & tools
|
||||||
'integration.database_query': { labelKey: 'workflows.nodes.databaseQuery', descriptionKey: 'workflows.nodes.databaseQueryDescription' },
|
'integration.database_query': {
|
||||||
'integration.redis_operation': { labelKey: 'workflows.nodes.redisOperation', descriptionKey: 'workflows.nodes.redisOperationDescription' },
|
labelKey: 'workflows.nodes.databaseQuery',
|
||||||
'integration.mcp_tool': { labelKey: 'workflows.nodes.mcpTool', descriptionKey: 'workflows.nodes.mcpToolDescription' },
|
descriptionKey: 'workflows.nodes.databaseQueryDescription',
|
||||||
'integration.memory_store': { labelKey: 'workflows.nodes.memoryStore', descriptionKey: 'workflows.nodes.memoryStoreDescription' },
|
},
|
||||||
|
'integration.redis_operation': {
|
||||||
|
labelKey: 'workflows.nodes.redisOperation',
|
||||||
|
descriptionKey: 'workflows.nodes.redisOperationDescription',
|
||||||
|
},
|
||||||
|
'integration.mcp_tool': {
|
||||||
|
labelKey: 'workflows.nodes.mcpTool',
|
||||||
|
descriptionKey: 'workflows.nodes.mcpToolDescription',
|
||||||
|
},
|
||||||
|
'integration.memory_store': {
|
||||||
|
labelKey: 'workflows.nodes.memoryStore',
|
||||||
|
descriptionKey: 'workflows.nodes.memoryStoreDescription',
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
// Flat version: type → label key only (convenience for store / node component)
|
// Flat version: type → label key only (convenience for store / node component)
|
||||||
@@ -104,10 +227,10 @@ export const NODE_TYPE_LABEL_KEYS: Record<string, string> = Object.fromEntries(
|
|||||||
|
|
||||||
// Category i18n
|
// Category i18n
|
||||||
export const CATEGORY_I18N_KEYS: Record<string, { labelKey: string }> = {
|
export const CATEGORY_I18N_KEYS: Record<string, { labelKey: string }> = {
|
||||||
trigger: { labelKey: 'workflows.nodes.trigger' },
|
trigger: { labelKey: 'workflows.nodes.trigger' },
|
||||||
process: { labelKey: 'workflows.nodes.process' },
|
process: { labelKey: 'workflows.nodes.process' },
|
||||||
control: { labelKey: 'workflows.nodes.control' },
|
control: { labelKey: 'workflows.nodes.control' },
|
||||||
action: { labelKey: 'workflows.nodes.action' },
|
action: { labelKey: 'workflows.nodes.action' },
|
||||||
integration: { labelKey: 'workflows.nodes.integration' },
|
integration: { labelKey: 'workflows.nodes.integration' },
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -195,11 +318,16 @@ export const PALETTE_CATEGORY_COLORS: Record<string, string> = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const PALETTE_CATEGORY_BG: Record<string, string> = {
|
export const PALETTE_CATEGORY_BG: Record<string, string> = {
|
||||||
trigger: 'bg-amber-100 dark:bg-amber-900/30 hover:bg-amber-200 dark:hover:bg-amber-900/50',
|
trigger:
|
||||||
process: 'bg-blue-100 dark:bg-blue-900/30 hover:bg-blue-200 dark:hover:bg-blue-900/50',
|
'bg-amber-100 dark:bg-amber-900/30 hover:bg-amber-200 dark:hover:bg-amber-900/50',
|
||||||
control: 'bg-purple-100 dark:bg-purple-900/30 hover:bg-purple-200 dark:hover:bg-purple-900/50',
|
process:
|
||||||
action: 'bg-green-100 dark:bg-green-900/30 hover:bg-green-200 dark:hover:bg-green-900/50',
|
'bg-blue-100 dark:bg-blue-900/30 hover:bg-blue-200 dark:hover:bg-blue-900/50',
|
||||||
integration: 'bg-pink-100 dark:bg-pink-900/30 hover:bg-pink-200 dark:hover:bg-pink-900/50',
|
control:
|
||||||
|
'bg-purple-100 dark:bg-purple-900/30 hover:bg-purple-200 dark:hover:bg-purple-900/50',
|
||||||
|
action:
|
||||||
|
'bg-green-100 dark:bg-green-900/30 hover:bg-green-200 dark:hover:bg-green-900/50',
|
||||||
|
integration:
|
||||||
|
'bg-pink-100 dark:bg-pink-900/30 hover:bg-pink-200 dark:hover:bg-pink-900/50',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const PALETTE_CATEGORY_BORDER: Record<string, string> = {
|
export const PALETTE_CATEGORY_BORDER: Record<string, string> = {
|
||||||
@@ -224,7 +352,9 @@ export const CATEGORY_ICONS: Record<string, React.ElementType> = {
|
|||||||
* Find the i18n keys for a node type, with fuzzy matching so both
|
* Find the i18n keys for a node type, with fuzzy matching so both
|
||||||
* "trigger.message_trigger" and "message_trigger" work.
|
* "trigger.message_trigger" and "message_trigger" work.
|
||||||
*/
|
*/
|
||||||
export function findNodeI18nKeys(type: string): { labelKey: string; descriptionKey: string } | undefined {
|
export function findNodeI18nKeys(
|
||||||
|
type: string,
|
||||||
|
): { labelKey: string; descriptionKey: string } | undefined {
|
||||||
let keys = NODE_TYPE_I18N_KEYS[type];
|
let keys = NODE_TYPE_I18N_KEYS[type];
|
||||||
if (keys) return keys;
|
if (keys) return keys;
|
||||||
|
|
||||||
@@ -234,7 +364,13 @@ export function findNodeI18nKeys(type: string): { labelKey: string; descriptionK
|
|||||||
if (parts.length > 1) {
|
if (parts.length > 1) {
|
||||||
keys = NODE_TYPE_I18N_KEYS[type]; // already tried
|
keys = NODE_TYPE_I18N_KEYS[type]; // already tried
|
||||||
} else {
|
} else {
|
||||||
for (const cat of ['trigger', 'process', 'control', 'action', 'integration']) {
|
for (const cat of [
|
||||||
|
'trigger',
|
||||||
|
'process',
|
||||||
|
'control',
|
||||||
|
'action',
|
||||||
|
'integration',
|
||||||
|
]) {
|
||||||
keys = NODE_TYPE_I18N_KEYS[`${cat}.${typeName}`];
|
keys = NODE_TYPE_I18N_KEYS[`${cat}.${typeName}`];
|
||||||
if (keys) return keys;
|
if (keys) return keys;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,10 +3,7 @@ import type {
|
|||||||
WorkflowPortDefinition,
|
WorkflowPortDefinition,
|
||||||
} from '@/app/infra/entities/api';
|
} from '@/app/infra/entities/api';
|
||||||
import type { I18nObject } from '@/app/infra/entities/common';
|
import type { I18nObject } from '@/app/infra/entities/common';
|
||||||
import {
|
import { getNodeConfig, type NodeConfigMeta } from './node-configs';
|
||||||
getNodeConfig,
|
|
||||||
type NodeConfigMeta,
|
|
||||||
} from './node-configs';
|
|
||||||
|
|
||||||
export const WORKFLOW_NODE_CATEGORIES = [
|
export const WORKFLOW_NODE_CATEGORIES = [
|
||||||
'trigger',
|
'trigger',
|
||||||
@@ -59,7 +56,9 @@ function normalizePort(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function toBackendI18nObject(value?: I18nObject): Record<string, string> | undefined {
|
function toBackendI18nObject(
|
||||||
|
value?: I18nObject,
|
||||||
|
): Record<string, string> | undefined {
|
||||||
if (!value) return undefined;
|
if (!value) return undefined;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -103,7 +102,9 @@ function getLocalConfigVariants(type: string): string[] {
|
|||||||
return [...variants];
|
return [...variants];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getLocalNodeTypeMeta(type: string): WorkflowNodeTypeMetadata | null {
|
export function getLocalNodeTypeMeta(
|
||||||
|
type: string,
|
||||||
|
): WorkflowNodeTypeMetadata | null {
|
||||||
let localConfig: NodeConfigMeta | undefined;
|
let localConfig: NodeConfigMeta | undefined;
|
||||||
|
|
||||||
for (const variant of getLocalConfigVariants(type)) {
|
for (const variant of getLocalConfigVariants(type)) {
|
||||||
@@ -138,30 +139,27 @@ export function normalizeWorkflowNodeTypeMeta(
|
|||||||
const category =
|
const category =
|
||||||
nodeType?.category || localMeta?.category || resolveNodeTypeCategory(type);
|
nodeType?.category || localMeta?.category || resolveNodeTypeCategory(type);
|
||||||
|
|
||||||
const inputs =
|
const inputs = nodeType?.inputs?.length
|
||||||
nodeType?.inputs?.length
|
? nodeType.inputs.map((input) =>
|
||||||
? nodeType.inputs.map((input) =>
|
normalizePort('workflows.nodeInputs', input),
|
||||||
normalizePort('workflows.nodeInputs', input),
|
)
|
||||||
)
|
: localMeta?.inputs?.length
|
||||||
: localMeta?.inputs?.length
|
? localMeta.inputs
|
||||||
? localMeta.inputs
|
: [DEFAULT_INPUT_PORT];
|
||||||
: [DEFAULT_INPUT_PORT];
|
|
||||||
|
|
||||||
const outputs =
|
const outputs = nodeType?.outputs?.length
|
||||||
nodeType?.outputs?.length
|
? nodeType.outputs.map((output) =>
|
||||||
? nodeType.outputs.map((output) =>
|
normalizePort('workflows.nodeOutputs', output),
|
||||||
normalizePort('workflows.nodeOutputs', output),
|
)
|
||||||
)
|
: localMeta?.outputs?.length
|
||||||
: localMeta?.outputs?.length
|
? localMeta.outputs
|
||||||
? localMeta.outputs
|
: [DEFAULT_OUTPUT_PORT];
|
||||||
: [DEFAULT_OUTPUT_PORT];
|
|
||||||
|
|
||||||
const configSchema =
|
const configSchema = nodeType?.config_schema?.length
|
||||||
nodeType?.config_schema?.length
|
? nodeType.config_schema
|
||||||
? nodeType.config_schema
|
: localMeta?.config_schema?.length
|
||||||
: localMeta?.config_schema?.length
|
? localMeta.config_schema
|
||||||
? localMeta.config_schema
|
: [];
|
||||||
: [];
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
type,
|
type,
|
||||||
|
|||||||
@@ -6,7 +6,13 @@ import { Label } from '@/components/ui/label';
|
|||||||
import { Textarea } from '@/components/ui/textarea';
|
import { Textarea } from '@/components/ui/textarea';
|
||||||
import { Switch } from '@/components/ui/switch';
|
import { Switch } from '@/components/ui/switch';
|
||||||
import { Workflow } from '@/app/infra/entities/api';
|
import { Workflow } from '@/app/infra/entities/api';
|
||||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
import {
|
||||||
|
Card,
|
||||||
|
CardContent,
|
||||||
|
CardDescription,
|
||||||
|
CardHeader,
|
||||||
|
CardTitle,
|
||||||
|
} from '@/components/ui/card';
|
||||||
import { Trash2, AlertTriangle } from 'lucide-react';
|
import { Trash2, AlertTriangle } from 'lucide-react';
|
||||||
import {
|
import {
|
||||||
AlertDialog,
|
AlertDialog,
|
||||||
@@ -101,7 +107,9 @@ export default function WorkflowFormComponent({
|
|||||||
|
|
||||||
{/* Description */}
|
{/* Description */}
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="workflow-description">{t('workflows.description')}</Label>
|
<Label htmlFor="workflow-description">
|
||||||
|
{t('workflows.description')}
|
||||||
|
</Label>
|
||||||
<Textarea
|
<Textarea
|
||||||
id="workflow-description"
|
id="workflow-description"
|
||||||
value={description}
|
value={description}
|
||||||
@@ -119,10 +127,7 @@ export default function WorkflowFormComponent({
|
|||||||
{t('workflows.enabledDesc')}
|
{t('workflows.enabledDesc')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<Switch
|
<Switch checked={isEnabled} onCheckedChange={setIsEnabled} />
|
||||||
checked={isEnabled}
|
|
||||||
onCheckedChange={setIsEnabled}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
@@ -135,15 +140,21 @@ export default function WorkflowFormComponent({
|
|||||||
<CardContent>
|
<CardContent>
|
||||||
<div className="grid grid-cols-2 gap-4 text-sm">
|
<div className="grid grid-cols-2 gap-4 text-sm">
|
||||||
<div>
|
<div>
|
||||||
<span className="text-muted-foreground">{t('workflows.uuid')}:</span>
|
<span className="text-muted-foreground">
|
||||||
|
{t('workflows.uuid')}:
|
||||||
|
</span>
|
||||||
<span className="ml-2 font-mono">{workflow.uuid}</span>
|
<span className="ml-2 font-mono">{workflow.uuid}</span>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<span className="text-muted-foreground">{t('workflows.version')}:</span>
|
<span className="text-muted-foreground">
|
||||||
|
{t('workflows.version')}:
|
||||||
|
</span>
|
||||||
<span className="ml-2">{workflow.version || 1}</span>
|
<span className="ml-2">{workflow.version || 1}</span>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<span className="text-muted-foreground">{t('workflows.createdAt')}:</span>
|
<span className="text-muted-foreground">
|
||||||
|
{t('workflows.createdAt')}:
|
||||||
|
</span>
|
||||||
<span className="ml-2">
|
<span className="ml-2">
|
||||||
{workflow.created_at
|
{workflow.created_at
|
||||||
? new Date(workflow.created_at).toLocaleString()
|
? new Date(workflow.created_at).toLocaleString()
|
||||||
@@ -151,7 +162,9 @@ export default function WorkflowFormComponent({
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<span className="text-muted-foreground">{t('workflows.updatedAt')}:</span>
|
<span className="text-muted-foreground">
|
||||||
|
{t('workflows.updatedAt')}:
|
||||||
|
</span>
|
||||||
<span className="ml-2">
|
<span className="ml-2">
|
||||||
{workflow.updated_at
|
{workflow.updated_at
|
||||||
? new Date(workflow.updated_at).toLocaleString()
|
? new Date(workflow.updated_at).toLocaleString()
|
||||||
@@ -181,7 +194,9 @@ export default function WorkflowFormComponent({
|
|||||||
</AlertDialogTrigger>
|
</AlertDialogTrigger>
|
||||||
<AlertDialogContent>
|
<AlertDialogContent>
|
||||||
<AlertDialogHeader>
|
<AlertDialogHeader>
|
||||||
<AlertDialogTitle>{t('workflows.deleteConfirm')}</AlertDialogTitle>
|
<AlertDialogTitle>
|
||||||
|
{t('workflows.deleteConfirm')}
|
||||||
|
</AlertDialogTitle>
|
||||||
<AlertDialogDescription>
|
<AlertDialogDescription>
|
||||||
{t('workflows.deleteConfirmDesc', { name: workflow.name })}
|
{t('workflows.deleteConfirmDesc', { name: workflow.name })}
|
||||||
</AlertDialogDescription>
|
</AlertDialogDescription>
|
||||||
|
|||||||
@@ -1,5 +1,14 @@
|
|||||||
import { create } from 'zustand';
|
import { create } from 'zustand';
|
||||||
import { Node, Edge, Connection, addEdge, applyNodeChanges, applyEdgeChanges, NodeChange, EdgeChange } from '@xyflow/react';
|
import {
|
||||||
|
Node,
|
||||||
|
Edge,
|
||||||
|
Connection,
|
||||||
|
addEdge,
|
||||||
|
applyNodeChanges,
|
||||||
|
applyEdgeChanges,
|
||||||
|
NodeChange,
|
||||||
|
EdgeChange,
|
||||||
|
} from '@xyflow/react';
|
||||||
import {
|
import {
|
||||||
Workflow,
|
Workflow,
|
||||||
WorkflowNodeDefinition,
|
WorkflowNodeDefinition,
|
||||||
@@ -64,28 +73,28 @@ export interface DebugContext {
|
|||||||
interface WorkflowState {
|
interface WorkflowState {
|
||||||
// Current workflow being edited
|
// Current workflow being edited
|
||||||
currentWorkflow: Workflow | null;
|
currentWorkflow: Workflow | null;
|
||||||
|
|
||||||
// React Flow nodes and edges
|
// React Flow nodes and edges
|
||||||
nodes: WorkflowNode[];
|
nodes: WorkflowNode[];
|
||||||
edges: WorkflowEdge[];
|
edges: WorkflowEdge[];
|
||||||
|
|
||||||
// Node type metadata from backend
|
// Node type metadata from backend
|
||||||
nodeTypes: WorkflowNodeTypeMetadata[];
|
nodeTypes: WorkflowNodeTypeMetadata[];
|
||||||
nodeCategories: WorkflowNodeCategory[];
|
nodeCategories: WorkflowNodeCategory[];
|
||||||
|
|
||||||
// Selection state
|
// Selection state
|
||||||
selectedNodeId: string | null;
|
selectedNodeId: string | null;
|
||||||
selectedEdgeId: string | null;
|
selectedEdgeId: string | null;
|
||||||
|
|
||||||
// UI state
|
// UI state
|
||||||
isDirty: boolean;
|
isDirty: boolean;
|
||||||
isSaving: boolean;
|
isSaving: boolean;
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
|
|
||||||
// Undo/Redo history
|
// Undo/Redo history
|
||||||
history: { nodes: WorkflowNode[]; edges: WorkflowEdge[] }[];
|
history: { nodes: WorkflowNode[]; edges: WorkflowEdge[] }[];
|
||||||
historyIndex: number;
|
historyIndex: number;
|
||||||
|
|
||||||
// Debug state
|
// Debug state
|
||||||
debugMode: boolean;
|
debugMode: boolean;
|
||||||
debugState: DebugState;
|
debugState: DebugState;
|
||||||
@@ -96,55 +105,67 @@ interface WorkflowState {
|
|||||||
debugLogs: DebugLog[];
|
debugLogs: DebugLog[];
|
||||||
debugContext: DebugContext;
|
debugContext: DebugContext;
|
||||||
watchedVariables: string[];
|
watchedVariables: string[];
|
||||||
|
|
||||||
// Actions
|
// Actions
|
||||||
setCurrentWorkflow: (workflow: Workflow | null) => void;
|
setCurrentWorkflow: (workflow: Workflow | null) => void;
|
||||||
setNodes: (nodes: WorkflowNode[]) => void;
|
setNodes: (nodes: WorkflowNode[]) => void;
|
||||||
setEdges: (edges: WorkflowEdge[]) => void;
|
setEdges: (edges: WorkflowEdge[]) => void;
|
||||||
setNodeTypes: (types: WorkflowNodeTypeMetadata[], categories: WorkflowNodeCategory[]) => void;
|
setNodeTypes: (
|
||||||
|
types: WorkflowNodeTypeMetadata[],
|
||||||
|
categories: WorkflowNodeCategory[],
|
||||||
|
) => void;
|
||||||
|
|
||||||
// Node operations
|
// Node operations
|
||||||
onNodesChange: (changes: NodeChange<WorkflowNode>[]) => void;
|
onNodesChange: (changes: NodeChange<WorkflowNode>[]) => void;
|
||||||
onEdgesChange: (changes: EdgeChange<WorkflowEdge>[]) => void;
|
onEdgesChange: (changes: EdgeChange<WorkflowEdge>[]) => void;
|
||||||
onConnect: (connection: Connection) => void;
|
onConnect: (connection: Connection) => void;
|
||||||
|
|
||||||
addNode: (type: string, position: { x: number; y: number }) => void;
|
addNode: (type: string, position: { x: number; y: number }) => void;
|
||||||
updateNodeConfig: (nodeId: string, config: Record<string, unknown>) => void;
|
updateNodeConfig: (nodeId: string, config: Record<string, unknown>) => void;
|
||||||
updateNodeLabel: (nodeId: string, label: string) => void;
|
updateNodeLabel: (nodeId: string, label: string) => void;
|
||||||
deleteNode: (nodeId: string) => void;
|
deleteNode: (nodeId: string) => void;
|
||||||
|
|
||||||
// Edge operations
|
// Edge operations
|
||||||
deleteEdge: (edgeId: string) => void;
|
deleteEdge: (edgeId: string) => void;
|
||||||
updateEdgeCondition: (edgeId: string, condition: string) => void;
|
updateEdgeCondition: (edgeId: string, condition: string) => void;
|
||||||
|
|
||||||
// Selection
|
// Selection
|
||||||
selectNode: (nodeId: string | null) => void;
|
selectNode: (nodeId: string | null) => void;
|
||||||
selectEdge: (edgeId: string | null) => void;
|
selectEdge: (edgeId: string | null) => void;
|
||||||
clearSelection: () => void;
|
clearSelection: () => void;
|
||||||
|
|
||||||
// State management
|
// State management
|
||||||
setDirty: (dirty: boolean) => void;
|
setDirty: (dirty: boolean) => void;
|
||||||
setSaving: (saving: boolean) => void;
|
setSaving: (saving: boolean) => void;
|
||||||
setLoading: (loading: boolean) => void;
|
setLoading: (loading: boolean) => void;
|
||||||
|
|
||||||
// Undo/Redo
|
// Undo/Redo
|
||||||
undo: () => void;
|
undo: () => void;
|
||||||
redo: () => void;
|
redo: () => void;
|
||||||
pushHistory: () => void;
|
pushHistory: () => void;
|
||||||
|
|
||||||
// Convert to/from API format
|
// Convert to/from API format
|
||||||
toWorkflowDefinition: () => { nodes: WorkflowNodeDefinition[]; edges: WorkflowEdgeDefinition[] };
|
toWorkflowDefinition: () => {
|
||||||
fromWorkflowDefinition: (nodes: WorkflowNodeDefinition[], edges: WorkflowEdgeDefinition[]) => void;
|
nodes: WorkflowNodeDefinition[];
|
||||||
|
edges: WorkflowEdgeDefinition[];
|
||||||
|
};
|
||||||
|
fromWorkflowDefinition: (
|
||||||
|
nodes: WorkflowNodeDefinition[],
|
||||||
|
edges: WorkflowEdgeDefinition[],
|
||||||
|
) => void;
|
||||||
|
|
||||||
// Reset
|
// Reset
|
||||||
reset: () => void;
|
reset: () => void;
|
||||||
|
|
||||||
// Debug actions
|
// Debug actions
|
||||||
setDebugMode: (enabled: boolean) => void;
|
setDebugMode: (enabled: boolean) => void;
|
||||||
setDebugState: (state: DebugState) => void;
|
setDebugState: (state: DebugState) => void;
|
||||||
setDebugExecutionId: (executionId: string | null) => void;
|
setDebugExecutionId: (executionId: string | null) => void;
|
||||||
setCurrentNodeId: (nodeId: string | null) => void;
|
setCurrentNodeId: (nodeId: string | null) => void;
|
||||||
updateNodeExecutionResult: (nodeId: string, result: Partial<NodeExecutionResult>) => void;
|
updateNodeExecutionResult: (
|
||||||
|
nodeId: string,
|
||||||
|
result: Partial<NodeExecutionResult>,
|
||||||
|
) => void;
|
||||||
clearNodeExecutionResults: () => void;
|
clearNodeExecutionResults: () => void;
|
||||||
toggleBreakpoint: (nodeId: string) => void;
|
toggleBreakpoint: (nodeId: string) => void;
|
||||||
clearBreakpoints: () => void;
|
clearBreakpoints: () => void;
|
||||||
@@ -159,7 +180,10 @@ interface WorkflowState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const generateUuidLikeId = () => {
|
const generateUuidLikeId = () => {
|
||||||
if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {
|
if (
|
||||||
|
typeof crypto !== 'undefined' &&
|
||||||
|
typeof crypto.randomUUID === 'function'
|
||||||
|
) {
|
||||||
return crypto.randomUUID();
|
return crypto.randomUUID();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -195,7 +219,7 @@ export const useWorkflowStore = create<WorkflowState>((set, get) => ({
|
|||||||
isLoading: false,
|
isLoading: false,
|
||||||
history: [],
|
history: [],
|
||||||
historyIndex: -1,
|
historyIndex: -1,
|
||||||
|
|
||||||
// Debug initial state
|
// Debug initial state
|
||||||
debugMode: false,
|
debugMode: false,
|
||||||
debugState: 'idle',
|
debugState: 'idle',
|
||||||
@@ -206,13 +230,14 @@ export const useWorkflowStore = create<WorkflowState>((set, get) => ({
|
|||||||
debugLogs: [],
|
debugLogs: [],
|
||||||
debugContext: { ...defaultDebugContext },
|
debugContext: { ...defaultDebugContext },
|
||||||
watchedVariables: [],
|
watchedVariables: [],
|
||||||
|
|
||||||
// Setters
|
// Setters
|
||||||
setCurrentWorkflow: (workflow) => set({ currentWorkflow: workflow }),
|
setCurrentWorkflow: (workflow) => set({ currentWorkflow: workflow }),
|
||||||
setNodes: (nodes) => set({ nodes, isDirty: true }),
|
setNodes: (nodes) => set({ nodes, isDirty: true }),
|
||||||
setEdges: (edges) => set({ edges, isDirty: true }),
|
setEdges: (edges) => set({ edges, isDirty: true }),
|
||||||
setNodeTypes: (types, categories) => set({ nodeTypes: types, nodeCategories: categories }),
|
setNodeTypes: (types, categories) =>
|
||||||
|
set({ nodeTypes: types, nodeCategories: categories }),
|
||||||
|
|
||||||
// Node change handlers
|
// Node change handlers
|
||||||
onNodesChange: (changes) => {
|
onNodesChange: (changes) => {
|
||||||
set((state) => ({
|
set((state) => ({
|
||||||
@@ -220,28 +245,28 @@ export const useWorkflowStore = create<WorkflowState>((set, get) => ({
|
|||||||
isDirty: true,
|
isDirty: true,
|
||||||
}));
|
}));
|
||||||
},
|
},
|
||||||
|
|
||||||
onEdgesChange: (changes) => {
|
onEdgesChange: (changes) => {
|
||||||
set((state) => ({
|
set((state) => ({
|
||||||
edges: applyEdgeChanges(changes, state.edges) as WorkflowEdge[],
|
edges: applyEdgeChanges(changes, state.edges) as WorkflowEdge[],
|
||||||
isDirty: true,
|
isDirty: true,
|
||||||
}));
|
}));
|
||||||
},
|
},
|
||||||
|
|
||||||
onConnect: (connection) => {
|
onConnect: (connection) => {
|
||||||
const newEdge: WorkflowEdge = {
|
const newEdge: WorkflowEdge = {
|
||||||
...connection,
|
...connection,
|
||||||
id: generateEdgeId(),
|
id: generateEdgeId(),
|
||||||
type: 'smoothstep',
|
type: 'smoothstep',
|
||||||
} as WorkflowEdge;
|
} as WorkflowEdge;
|
||||||
|
|
||||||
set((state) => ({
|
set((state) => ({
|
||||||
edges: addEdge(newEdge, state.edges) as WorkflowEdge[],
|
edges: addEdge(newEdge, state.edges) as WorkflowEdge[],
|
||||||
isDirty: true,
|
isDirty: true,
|
||||||
}));
|
}));
|
||||||
get().pushHistory();
|
get().pushHistory();
|
||||||
},
|
},
|
||||||
|
|
||||||
// Add new node
|
// Add new node
|
||||||
addNode: (type, position) => {
|
addNode: (type, position) => {
|
||||||
const { nodeTypes } = get();
|
const { nodeTypes } = get();
|
||||||
@@ -277,83 +302,81 @@ export const useWorkflowStore = create<WorkflowState>((set, get) => ({
|
|||||||
})),
|
})),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
set((state) => ({
|
set((state) => ({
|
||||||
nodes: [...state.nodes, newNode],
|
nodes: [...state.nodes, newNode],
|
||||||
isDirty: true,
|
isDirty: true,
|
||||||
}));
|
}));
|
||||||
get().pushHistory();
|
get().pushHistory();
|
||||||
},
|
},
|
||||||
|
|
||||||
// Update node config
|
// Update node config
|
||||||
updateNodeConfig: (nodeId, config) => {
|
updateNodeConfig: (nodeId, config) => {
|
||||||
set((state) => ({
|
set((state) => ({
|
||||||
nodes: state.nodes.map((node) =>
|
nodes: state.nodes.map((node) =>
|
||||||
node.id === nodeId
|
node.id === nodeId ? { ...node, data: { ...node.data, config } } : node,
|
||||||
? { ...node, data: { ...node.data, config } }
|
|
||||||
: node
|
|
||||||
),
|
),
|
||||||
isDirty: true,
|
isDirty: true,
|
||||||
}));
|
}));
|
||||||
},
|
},
|
||||||
|
|
||||||
// Update node label
|
// Update node label
|
||||||
updateNodeLabel: (nodeId, label) => {
|
updateNodeLabel: (nodeId, label) => {
|
||||||
set((state) => ({
|
set((state) => ({
|
||||||
nodes: state.nodes.map((node) =>
|
nodes: state.nodes.map((node) =>
|
||||||
node.id === nodeId
|
node.id === nodeId ? { ...node, data: { ...node.data, label } } : node,
|
||||||
? { ...node, data: { ...node.data, label } }
|
|
||||||
: node
|
|
||||||
),
|
),
|
||||||
isDirty: true,
|
isDirty: true,
|
||||||
}));
|
}));
|
||||||
},
|
},
|
||||||
|
|
||||||
// Delete node
|
// Delete node
|
||||||
deleteNode: (nodeId) => {
|
deleteNode: (nodeId) => {
|
||||||
set((state) => ({
|
set((state) => ({
|
||||||
nodes: state.nodes.filter((node) => node.id !== nodeId),
|
nodes: state.nodes.filter((node) => node.id !== nodeId),
|
||||||
edges: state.edges.filter(
|
edges: state.edges.filter(
|
||||||
(edge) => edge.source !== nodeId && edge.target !== nodeId
|
(edge) => edge.source !== nodeId && edge.target !== nodeId,
|
||||||
),
|
),
|
||||||
selectedNodeId: state.selectedNodeId === nodeId ? null : state.selectedNodeId,
|
selectedNodeId:
|
||||||
|
state.selectedNodeId === nodeId ? null : state.selectedNodeId,
|
||||||
isDirty: true,
|
isDirty: true,
|
||||||
}));
|
}));
|
||||||
get().pushHistory();
|
get().pushHistory();
|
||||||
},
|
},
|
||||||
|
|
||||||
// Delete edge
|
// Delete edge
|
||||||
deleteEdge: (edgeId) => {
|
deleteEdge: (edgeId) => {
|
||||||
set((state) => ({
|
set((state) => ({
|
||||||
edges: state.edges.filter((edge) => edge.id !== edgeId),
|
edges: state.edges.filter((edge) => edge.id !== edgeId),
|
||||||
selectedEdgeId: state.selectedEdgeId === edgeId ? null : state.selectedEdgeId,
|
selectedEdgeId:
|
||||||
|
state.selectedEdgeId === edgeId ? null : state.selectedEdgeId,
|
||||||
isDirty: true,
|
isDirty: true,
|
||||||
}));
|
}));
|
||||||
get().pushHistory();
|
get().pushHistory();
|
||||||
},
|
},
|
||||||
|
|
||||||
// Update edge condition
|
// Update edge condition
|
||||||
updateEdgeCondition: (edgeId, condition) => {
|
updateEdgeCondition: (edgeId, condition) => {
|
||||||
set((state) => ({
|
set((state) => ({
|
||||||
edges: state.edges.map((edge) =>
|
edges: state.edges.map((edge) =>
|
||||||
edge.id === edgeId
|
edge.id === edgeId
|
||||||
? { ...edge, data: { ...edge.data, condition } }
|
? { ...edge, data: { ...edge.data, condition } }
|
||||||
: edge
|
: edge,
|
||||||
),
|
),
|
||||||
isDirty: true,
|
isDirty: true,
|
||||||
}));
|
}));
|
||||||
},
|
},
|
||||||
|
|
||||||
// Selection
|
// Selection
|
||||||
selectNode: (nodeId) => set({ selectedNodeId: nodeId, selectedEdgeId: null }),
|
selectNode: (nodeId) => set({ selectedNodeId: nodeId, selectedEdgeId: null }),
|
||||||
selectEdge: (edgeId) => set({ selectedEdgeId: edgeId, selectedNodeId: null }),
|
selectEdge: (edgeId) => set({ selectedEdgeId: edgeId, selectedNodeId: null }),
|
||||||
clearSelection: () => set({ selectedNodeId: null, selectedEdgeId: null }),
|
clearSelection: () => set({ selectedNodeId: null, selectedEdgeId: null }),
|
||||||
|
|
||||||
// State management
|
// State management
|
||||||
setDirty: (dirty) => set({ isDirty: dirty }),
|
setDirty: (dirty) => set({ isDirty: dirty }),
|
||||||
setSaving: (saving) => set({ isSaving: saving }),
|
setSaving: (saving) => set({ isSaving: saving }),
|
||||||
setLoading: (loading) => set({ isLoading: loading }),
|
setLoading: (loading) => set({ isLoading: loading }),
|
||||||
|
|
||||||
// Undo
|
// Undo
|
||||||
undo: () => {
|
undo: () => {
|
||||||
const { history, historyIndex } = get();
|
const { history, historyIndex } = get();
|
||||||
@@ -363,7 +386,7 @@ export const useWorkflowStore = create<WorkflowState>((set, get) => ({
|
|||||||
set({ nodes, edges, historyIndex: newIndex, isDirty: true });
|
set({ nodes, edges, historyIndex: newIndex, isDirty: true });
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// Redo
|
// Redo
|
||||||
redo: () => {
|
redo: () => {
|
||||||
const { history, historyIndex } = get();
|
const { history, historyIndex } = get();
|
||||||
@@ -373,25 +396,25 @@ export const useWorkflowStore = create<WorkflowState>((set, get) => ({
|
|||||||
set({ nodes, edges, historyIndex: newIndex, isDirty: true });
|
set({ nodes, edges, historyIndex: newIndex, isDirty: true });
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// Push current state to history
|
// Push current state to history
|
||||||
pushHistory: () => {
|
pushHistory: () => {
|
||||||
const { nodes, edges, history, historyIndex } = get();
|
const { nodes, edges, history, historyIndex } = get();
|
||||||
const newHistory = history.slice(0, historyIndex + 1);
|
const newHistory = history.slice(0, historyIndex + 1);
|
||||||
newHistory.push({ nodes: [...nodes], edges: [...edges] });
|
newHistory.push({ nodes: [...nodes], edges: [...edges] });
|
||||||
|
|
||||||
// Keep history limited to 50 entries
|
// Keep history limited to 50 entries
|
||||||
if (newHistory.length > 50) {
|
if (newHistory.length > 50) {
|
||||||
newHistory.shift();
|
newHistory.shift();
|
||||||
}
|
}
|
||||||
|
|
||||||
set({ history: newHistory, historyIndex: newHistory.length - 1 });
|
set({ history: newHistory, historyIndex: newHistory.length - 1 });
|
||||||
},
|
},
|
||||||
|
|
||||||
// Convert to API format
|
// Convert to API format
|
||||||
toWorkflowDefinition: () => {
|
toWorkflowDefinition: () => {
|
||||||
const { nodes, edges } = get();
|
const { nodes, edges } = get();
|
||||||
|
|
||||||
const workflowNodes: WorkflowNodeDefinition[] = nodes.map((node) => ({
|
const workflowNodes: WorkflowNodeDefinition[] = nodes.map((node) => ({
|
||||||
id: node.id,
|
id: node.id,
|
||||||
type: node.data.type,
|
type: node.data.type,
|
||||||
@@ -401,7 +424,7 @@ export const useWorkflowStore = create<WorkflowState>((set, get) => ({
|
|||||||
inputs: node.data.inputs,
|
inputs: node.data.inputs,
|
||||||
outputs: node.data.outputs,
|
outputs: node.data.outputs,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const workflowEdges: WorkflowEdgeDefinition[] = edges.map((edge) => ({
|
const workflowEdges: WorkflowEdgeDefinition[] = edges.map((edge) => ({
|
||||||
id: edge.id,
|
id: edge.id,
|
||||||
source: edge.source,
|
source: edge.source,
|
||||||
@@ -411,10 +434,10 @@ export const useWorkflowStore = create<WorkflowState>((set, get) => ({
|
|||||||
label: edge.data?.label,
|
label: edge.data?.label,
|
||||||
condition: edge.data?.condition,
|
condition: edge.data?.condition,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
return { nodes: workflowNodes, edges: workflowEdges };
|
return { nodes: workflowNodes, edges: workflowEdges };
|
||||||
},
|
},
|
||||||
|
|
||||||
// Load from API format
|
// Load from API format
|
||||||
fromWorkflowDefinition: (apiNodes, apiEdges) => {
|
fromWorkflowDefinition: (apiNodes, apiEdges) => {
|
||||||
const nodes: WorkflowNode[] = apiNodes.map((node) => ({
|
const nodes: WorkflowNode[] = apiNodes.map((node) => ({
|
||||||
@@ -429,7 +452,7 @@ export const useWorkflowStore = create<WorkflowState>((set, get) => ({
|
|||||||
outputs: node.outputs,
|
outputs: node.outputs,
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const edges: WorkflowEdge[] = apiEdges.map((edge) => ({
|
const edges: WorkflowEdge[] = apiEdges.map((edge) => ({
|
||||||
id: edge.id,
|
id: edge.id,
|
||||||
source: edge.source,
|
source: edge.source,
|
||||||
@@ -442,58 +465,62 @@ export const useWorkflowStore = create<WorkflowState>((set, get) => ({
|
|||||||
condition: edge.condition,
|
condition: edge.condition,
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
set({ nodes, edges, isDirty: false });
|
set({ nodes, edges, isDirty: false });
|
||||||
get().pushHistory();
|
get().pushHistory();
|
||||||
},
|
},
|
||||||
|
|
||||||
// Reset store
|
// Reset store
|
||||||
reset: () => set({
|
reset: () =>
|
||||||
currentWorkflow: null,
|
set({
|
||||||
nodes: [],
|
currentWorkflow: null,
|
||||||
edges: [],
|
nodes: [],
|
||||||
selectedNodeId: null,
|
edges: [],
|
||||||
selectedEdgeId: null,
|
selectedNodeId: null,
|
||||||
isDirty: false,
|
selectedEdgeId: null,
|
||||||
isSaving: false,
|
isDirty: false,
|
||||||
isLoading: false,
|
isSaving: false,
|
||||||
history: [],
|
isLoading: false,
|
||||||
historyIndex: -1,
|
history: [],
|
||||||
// Reset debug state
|
historyIndex: -1,
|
||||||
debugMode: false,
|
// Reset debug state
|
||||||
debugState: 'idle',
|
debugMode: false,
|
||||||
debugExecutionId: null,
|
debugState: 'idle',
|
||||||
currentNodeId: null,
|
debugExecutionId: null,
|
||||||
nodeExecutionResults: {},
|
currentNodeId: null,
|
||||||
breakpoints: {} as Record<string, boolean>,
|
nodeExecutionResults: {},
|
||||||
debugLogs: [],
|
breakpoints: {} as Record<string, boolean>,
|
||||||
debugContext: { ...defaultDebugContext },
|
debugLogs: [],
|
||||||
watchedVariables: [],
|
debugContext: { ...defaultDebugContext },
|
||||||
}),
|
watchedVariables: [],
|
||||||
|
}),
|
||||||
|
|
||||||
// Debug actions
|
// Debug actions
|
||||||
setDebugMode: (enabled) => set({ debugMode: enabled }),
|
setDebugMode: (enabled) => set({ debugMode: enabled }),
|
||||||
|
|
||||||
setDebugState: (state) => set({ debugState: state }),
|
setDebugState: (state) => set({ debugState: state }),
|
||||||
|
|
||||||
setDebugExecutionId: (executionId) => set({ debugExecutionId: executionId }),
|
setDebugExecutionId: (executionId) => set({ debugExecutionId: executionId }),
|
||||||
|
|
||||||
setCurrentNodeId: (nodeId) => set({ currentNodeId: nodeId }),
|
setCurrentNodeId: (nodeId) => set({ currentNodeId: nodeId }),
|
||||||
|
|
||||||
updateNodeExecutionResult: (nodeId, result) => {
|
updateNodeExecutionResult: (nodeId, result) => {
|
||||||
set((state) => ({
|
set((state) => ({
|
||||||
nodeExecutionResults: {
|
nodeExecutionResults: {
|
||||||
...state.nodeExecutionResults,
|
...state.nodeExecutionResults,
|
||||||
[nodeId]: {
|
[nodeId]: {
|
||||||
...(state.nodeExecutionResults[nodeId] || { nodeId, status: 'pending' }),
|
...(state.nodeExecutionResults[nodeId] || {
|
||||||
|
nodeId,
|
||||||
|
status: 'pending',
|
||||||
|
}),
|
||||||
...result,
|
...result,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
},
|
},
|
||||||
|
|
||||||
clearNodeExecutionResults: () => set({ nodeExecutionResults: {} }),
|
clearNodeExecutionResults: () => set({ nodeExecutionResults: {} }),
|
||||||
|
|
||||||
toggleBreakpoint: (nodeId) => {
|
toggleBreakpoint: (nodeId) => {
|
||||||
set((state) => {
|
set((state) => {
|
||||||
const newBreakpoints = { ...state.breakpoints };
|
const newBreakpoints = { ...state.breakpoints };
|
||||||
@@ -505,9 +532,9 @@ export const useWorkflowStore = create<WorkflowState>((set, get) => ({
|
|||||||
return { breakpoints: newBreakpoints };
|
return { breakpoints: newBreakpoints };
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
clearBreakpoints: () => set({ breakpoints: {} as Record<string, boolean> }),
|
clearBreakpoints: () => set({ breakpoints: {} as Record<string, boolean> }),
|
||||||
|
|
||||||
addDebugLog: (log) => {
|
addDebugLog: (log) => {
|
||||||
set((state) => ({
|
set((state) => ({
|
||||||
debugLogs: [
|
debugLogs: [
|
||||||
@@ -520,27 +547,28 @@ export const useWorkflowStore = create<WorkflowState>((set, get) => ({
|
|||||||
].slice(-500), // Keep only last 500 logs
|
].slice(-500), // Keep only last 500 logs
|
||||||
}));
|
}));
|
||||||
},
|
},
|
||||||
|
|
||||||
clearDebugLogs: () => set({ debugLogs: [] }),
|
clearDebugLogs: () => set({ debugLogs: [] }),
|
||||||
|
|
||||||
setDebugContext: (context) => {
|
setDebugContext: (context) => {
|
||||||
set((state) => ({
|
set((state) => ({
|
||||||
debugContext: { ...state.debugContext, ...context },
|
debugContext: { ...state.debugContext, ...context },
|
||||||
}));
|
}));
|
||||||
},
|
},
|
||||||
|
|
||||||
resetDebugContext: () => set({
|
resetDebugContext: () =>
|
||||||
debugContext: {
|
set({
|
||||||
messageContent: '',
|
debugContext: {
|
||||||
senderId: `user_${Date.now().toString(36)}`,
|
messageContent: '',
|
||||||
senderName: '',
|
senderId: `user_${Date.now().toString(36)}`,
|
||||||
platform: '',
|
senderName: '',
|
||||||
conversationId: `session_${Date.now().toString(36)}`,
|
platform: '',
|
||||||
isGroup: false,
|
conversationId: `session_${Date.now().toString(36)}`,
|
||||||
customVariables: {},
|
isGroup: false,
|
||||||
},
|
customVariables: {},
|
||||||
}),
|
},
|
||||||
|
}),
|
||||||
|
|
||||||
addWatchedVariable: (variable) => {
|
addWatchedVariable: (variable) => {
|
||||||
set((state) => ({
|
set((state) => ({
|
||||||
watchedVariables: state.watchedVariables.includes(variable)
|
watchedVariables: state.watchedVariables.includes(variable)
|
||||||
@@ -548,20 +576,21 @@ export const useWorkflowStore = create<WorkflowState>((set, get) => ({
|
|||||||
: [...state.watchedVariables, variable],
|
: [...state.watchedVariables, variable],
|
||||||
}));
|
}));
|
||||||
},
|
},
|
||||||
|
|
||||||
removeWatchedVariable: (variable) => {
|
removeWatchedVariable: (variable) => {
|
||||||
set((state) => ({
|
set((state) => ({
|
||||||
watchedVariables: state.watchedVariables.filter((v) => v !== variable),
|
watchedVariables: state.watchedVariables.filter((v) => v !== variable),
|
||||||
}));
|
}));
|
||||||
},
|
},
|
||||||
|
|
||||||
clearWatchedVariables: () => set({ watchedVariables: [] }),
|
clearWatchedVariables: () => set({ watchedVariables: [] }),
|
||||||
|
|
||||||
resetDebugState: () => set({
|
resetDebugState: () =>
|
||||||
debugState: 'idle',
|
set({
|
||||||
debugExecutionId: null,
|
debugState: 'idle',
|
||||||
currentNodeId: null,
|
debugExecutionId: null,
|
||||||
nodeExecutionResults: {},
|
currentNodeId: null,
|
||||||
debugLogs: [],
|
nodeExecutionResults: {},
|
||||||
}),
|
debugLogs: [],
|
||||||
|
}),
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -82,7 +82,12 @@ export interface NodeTypeMetadata {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 节点类别
|
// 节点类别
|
||||||
export type NodeCategory = 'trigger' | 'process' | 'control' | 'action' | 'integration';
|
export type NodeCategory =
|
||||||
|
| 'trigger'
|
||||||
|
| 'process'
|
||||||
|
| 'control'
|
||||||
|
| 'action'
|
||||||
|
| 'integration';
|
||||||
|
|
||||||
// 节点类别信息
|
// 节点类别信息
|
||||||
export interface NodeCategoryInfo {
|
export interface NodeCategoryInfo {
|
||||||
@@ -93,7 +98,12 @@ export interface NodeCategoryInfo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 工作流执行状态
|
// 工作流执行状态
|
||||||
export type ExecutionStatus = 'pending' | 'running' | 'completed' | 'failed' | 'cancelled';
|
export type ExecutionStatus =
|
||||||
|
| 'pending'
|
||||||
|
| 'running'
|
||||||
|
| 'completed'
|
||||||
|
| 'failed'
|
||||||
|
| 'cancelled';
|
||||||
|
|
||||||
// 工作流执行记录
|
// 工作流执行记录
|
||||||
export interface WorkflowExecution {
|
export interface WorkflowExecution {
|
||||||
|
|||||||
@@ -1290,11 +1290,17 @@ export class BackendClient extends BaseHttpClient {
|
|||||||
return this.post(`/api/v1/workflows/${uuid}/debug/start`, options);
|
return this.post(`/api/v1/workflows/${uuid}/debug/start`, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
public pauseWorkflowDebug(uuid: string, executionId: string): Promise<object> {
|
public pauseWorkflowDebug(
|
||||||
|
uuid: string,
|
||||||
|
executionId: string,
|
||||||
|
): Promise<object> {
|
||||||
return this.post(`/api/v1/workflows/${uuid}/debug/${executionId}/pause`);
|
return this.post(`/api/v1/workflows/${uuid}/debug/${executionId}/pause`);
|
||||||
}
|
}
|
||||||
|
|
||||||
public resumeWorkflowDebug(uuid: string, executionId: string): Promise<object> {
|
public resumeWorkflowDebug(
|
||||||
|
uuid: string,
|
||||||
|
executionId: string,
|
||||||
|
): Promise<object> {
|
||||||
return this.post(`/api/v1/workflows/${uuid}/debug/${executionId}/resume`);
|
return this.post(`/api/v1/workflows/${uuid}/debug/${executionId}/resume`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,9 @@ export default function I18nProvider({ children }: I18nProviderProps) {
|
|||||||
// return i18nLabel.en_US;
|
// return i18nLabel.en_US;
|
||||||
// }
|
// }
|
||||||
|
|
||||||
export const extractI18nObject = (i18nObject: I18nObject | undefined | null): string => {
|
export const extractI18nObject = (
|
||||||
|
i18nObject: I18nObject | undefined | null,
|
||||||
|
): string => {
|
||||||
// 根据当前语言返回对应的值, fallback优先级:en_US、zh_Hans、zh_Hant、ja_JP
|
// 根据当前语言返回对应的值, fallback优先级:en_US、zh_Hans、zh_Hant、ja_JP
|
||||||
if (!i18nObject || typeof i18nObject !== 'object') {
|
if (!i18nObject || typeof i18nObject !== 'object') {
|
||||||
return '';
|
return '';
|
||||||
|
|||||||
@@ -380,14 +380,17 @@ const enUS = {
|
|||||||
sessionTypeGroup: 'Group Chat',
|
sessionTypeGroup: 'Group Chat',
|
||||||
// Unified binding (replacing routing rules)
|
// Unified binding (replacing routing rules)
|
||||||
bindTarget: 'Bind Target',
|
bindTarget: 'Bind Target',
|
||||||
bindTargetDescription: 'Select the Pipeline or Workflow to process messages for this bot',
|
bindTargetDescription:
|
||||||
|
'Select the Pipeline or Workflow to process messages for this bot',
|
||||||
bindingType: 'Binding Type',
|
bindingType: 'Binding Type',
|
||||||
selectBinding: 'Select binding target',
|
selectBinding: 'Select binding target',
|
||||||
selectWorkflow: 'Select Workflow',
|
selectWorkflow: 'Select Workflow',
|
||||||
noPipelinesFound: 'No pipelines available',
|
noPipelinesFound: 'No pipelines available',
|
||||||
noWorkflowsFound: 'No workflows available',
|
noWorkflowsFound: 'No workflows available',
|
||||||
pipelineBindingHelp: 'Pipeline is the traditional message processing method using predefined stages.',
|
pipelineBindingHelp:
|
||||||
workflowBindingHelp: 'Workflow provides visual node orchestration for more flexible message processing.',
|
'Pipeline is the traditional message processing method using predefined stages.',
|
||||||
|
workflowBindingHelp:
|
||||||
|
'Workflow provides visual node orchestration for more flexible message processing.',
|
||||||
adapterConfigDescription: 'Configure the selected platform adapter',
|
adapterConfigDescription: 'Configure the selected platform adapter',
|
||||||
dangerZone: 'Danger Zone',
|
dangerZone: 'Danger Zone',
|
||||||
dangerZoneDescription: 'Irreversible and destructive actions',
|
dangerZoneDescription: 'Irreversible and destructive actions',
|
||||||
@@ -1355,7 +1358,8 @@ const enUS = {
|
|||||||
},
|
},
|
||||||
workflows: {
|
workflows: {
|
||||||
title: 'Workflows',
|
title: 'Workflows',
|
||||||
description: 'Create and manage visual workflows for complex message processing logic',
|
description:
|
||||||
|
'Create and manage visual workflows for complex message processing logic',
|
||||||
createWorkflow: 'Create Workflow',
|
createWorkflow: 'Create Workflow',
|
||||||
selectFromSidebar: 'Select a workflow from the sidebar',
|
selectFromSidebar: 'Select a workflow from the sidebar',
|
||||||
editWorkflow: 'Edit Workflow',
|
editWorkflow: 'Edit Workflow',
|
||||||
@@ -1395,10 +1399,12 @@ const enUS = {
|
|||||||
dangerZoneDesc: 'Irreversible operations',
|
dangerZoneDesc: 'Irreversible operations',
|
||||||
dangerZoneDescription: 'Irreversible operations',
|
dangerZoneDescription: 'Irreversible operations',
|
||||||
deleteWorkflowAction: 'Delete this workflow',
|
deleteWorkflowAction: 'Delete this workflow',
|
||||||
deleteWorkflowHint: 'Once deleted, all associated configurations will be permanently removed and cannot be recovered.',
|
deleteWorkflowHint:
|
||||||
|
'Once deleted, all associated configurations will be permanently removed and cannot be recovered.',
|
||||||
deleteWorkflow: 'Delete Workflow',
|
deleteWorkflow: 'Delete Workflow',
|
||||||
deleteConfirm: 'Confirm Delete',
|
deleteConfirm: 'Confirm Delete',
|
||||||
deleteConfirmDesc: 'Are you sure you want to delete workflow "{{name}}"? This action cannot be undone.',
|
deleteConfirmDesc:
|
||||||
|
'Are you sure you want to delete workflow "{{name}}"? This action cannot be undone.',
|
||||||
// Form component
|
// Form component
|
||||||
name: 'Name',
|
name: 'Name',
|
||||||
namePlaceholder: 'Enter workflow name',
|
namePlaceholder: 'Enter workflow name',
|
||||||
@@ -1461,13 +1467,16 @@ const enUS = {
|
|||||||
dragToAdd: 'Drag nodes to add to canvas',
|
dragToAdd: 'Drag nodes to add to canvas',
|
||||||
// Property panel
|
// Property panel
|
||||||
selectNodeOrEdge: 'Select a node or edge',
|
selectNodeOrEdge: 'Select a node or edge',
|
||||||
selectNodeOrEdgeHint: 'Click on a node or edge in the canvas to view and edit its properties',
|
selectNodeOrEdgeHint:
|
||||||
|
'Click on a node or edge in the canvas to view and edit its properties',
|
||||||
edgeProperties: 'Edge Properties',
|
edgeProperties: 'Edge Properties',
|
||||||
nodeProperties: 'Node Properties',
|
nodeProperties: 'Node Properties',
|
||||||
condition: 'Condition',
|
condition: 'Condition',
|
||||||
hasCondition: 'Set',
|
hasCondition: 'Set',
|
||||||
conditionPlaceholder: 'Enter condition expression, e.g. output.success == true',
|
conditionPlaceholder:
|
||||||
conditionHelp: 'When condition is empty, this edge will always be executed. Use {{variable}} to reference context variables.',
|
'Enter condition expression, e.g. output.success == true',
|
||||||
|
conditionHelp:
|
||||||
|
'When condition is empty, this edge will always be executed. Use {{variable}} to reference context variables.',
|
||||||
deleteEdge: 'Delete Edge',
|
deleteEdge: 'Delete Edge',
|
||||||
deleteEdgeConfirm: 'Confirm Delete Edge',
|
deleteEdgeConfirm: 'Confirm Delete Edge',
|
||||||
deleteEdgeConfirmDesc: 'This edge will be permanently removed.',
|
deleteEdgeConfirmDesc: 'This edge will be permanently removed.',
|
||||||
@@ -1488,7 +1497,8 @@ const enUS = {
|
|||||||
noConfigOptions: 'No configuration options for this node type',
|
noConfigOptions: 'No configuration options for this node type',
|
||||||
deleteNode: 'Delete Node',
|
deleteNode: 'Delete Node',
|
||||||
deleteNodeConfirm: 'Confirm Delete Node',
|
deleteNodeConfirm: 'Confirm Delete Node',
|
||||||
deleteNodeConfirmDesc: 'This node and all its connections will be permanently removed.',
|
deleteNodeConfirmDesc:
|
||||||
|
'This node and all its connections will be permanently removed.',
|
||||||
// Node inputs/outputs i18n (for port labels)
|
// Node inputs/outputs i18n (for port labels)
|
||||||
nodeInputs: {
|
nodeInputs: {
|
||||||
// Common inputs
|
// Common inputs
|
||||||
@@ -1551,7 +1561,8 @@ const enUS = {
|
|||||||
aiProcess: 'AI Processing',
|
aiProcess: 'AI Processing',
|
||||||
aiProcessDescription: 'Process messages using AI models',
|
aiProcessDescription: 'Process messages using AI models',
|
||||||
llmCall: 'LLM Call',
|
llmCall: 'LLM Call',
|
||||||
llmCallDescription: 'Invoke large language model for conversation or generation',
|
llmCallDescription:
|
||||||
|
'Invoke large language model for conversation or generation',
|
||||||
codeProcess: 'Code Processing',
|
codeProcess: 'Code Processing',
|
||||||
codeProcessDescription: 'Execute custom code',
|
codeProcessDescription: 'Execute custom code',
|
||||||
codeExecutor: 'Code Executor',
|
codeExecutor: 'Code Executor',
|
||||||
@@ -1563,13 +1574,17 @@ const enUS = {
|
|||||||
dataTransform: 'Data Transform',
|
dataTransform: 'Data Transform',
|
||||||
dataTransformDescription: 'Transform data format',
|
dataTransformDescription: 'Transform data format',
|
||||||
questionClassifier: 'Question Classifier',
|
questionClassifier: 'Question Classifier',
|
||||||
questionClassifierDescription: 'Classify user questions into predefined categories using LLM',
|
questionClassifierDescription:
|
||||||
|
'Classify user questions into predefined categories using LLM',
|
||||||
parameterExtractor: 'Parameter Extractor',
|
parameterExtractor: 'Parameter Extractor',
|
||||||
parameterExtractorDescription: 'Extract structured parameters from text using LLM',
|
parameterExtractorDescription:
|
||||||
|
'Extract structured parameters from text using LLM',
|
||||||
knowledgeRetrieval: 'Knowledge Retrieval',
|
knowledgeRetrieval: 'Knowledge Retrieval',
|
||||||
knowledgeRetrievalDescription: 'Retrieve relevant content from knowledge base',
|
knowledgeRetrievalDescription:
|
||||||
|
'Retrieve relevant content from knowledge base',
|
||||||
textTemplate: 'Text Template',
|
textTemplate: 'Text Template',
|
||||||
textTemplateDescription: 'Generate text using templates with variable interpolation',
|
textTemplateDescription:
|
||||||
|
'Generate text using templates with variable interpolation',
|
||||||
jsonTransform: 'JSON Transform',
|
jsonTransform: 'JSON Transform',
|
||||||
jsonTransformDescription: 'Transform JSON data using expressions',
|
jsonTransformDescription: 'Transform JSON data using expressions',
|
||||||
dataAggregator: 'Data Aggregator',
|
dataAggregator: 'Data Aggregator',
|
||||||
@@ -1597,13 +1612,15 @@ const enUS = {
|
|||||||
merge: 'Merge',
|
merge: 'Merge',
|
||||||
mergeDescription: 'Merge multiple branches',
|
mergeDescription: 'Merge multiple branches',
|
||||||
variableAggregator: 'Variable Aggregator',
|
variableAggregator: 'Variable Aggregator',
|
||||||
variableAggregatorDescription: 'Aggregate variable outputs from multiple branches',
|
variableAggregatorDescription:
|
||||||
|
'Aggregate variable outputs from multiple branches',
|
||||||
action: 'Actions',
|
action: 'Actions',
|
||||||
actionDescription: 'Action execution nodes',
|
actionDescription: 'Action execution nodes',
|
||||||
sendMessage: 'Send Message',
|
sendMessage: 'Send Message',
|
||||||
sendMessageDescription: 'Send message to platform',
|
sendMessageDescription: 'Send message to platform',
|
||||||
replyMessage: 'Reply Message',
|
replyMessage: 'Reply Message',
|
||||||
replyMessageDescription: 'Reply to the message that triggered the workflow',
|
replyMessageDescription:
|
||||||
|
'Reply to the message that triggered the workflow',
|
||||||
storeData: 'Store Data',
|
storeData: 'Store Data',
|
||||||
storeDataDescription: 'Store data to database',
|
storeDataDescription: 'Store data to database',
|
||||||
callPipeline: 'Call Pipeline',
|
callPipeline: 'Call Pipeline',
|
||||||
@@ -1611,7 +1628,8 @@ const enUS = {
|
|||||||
setVariable: 'Set Variable',
|
setVariable: 'Set Variable',
|
||||||
setVariableDescription: 'Set context variable',
|
setVariableDescription: 'Set context variable',
|
||||||
openingStatement: 'Opening Statement',
|
openingStatement: 'Opening Statement',
|
||||||
openingStatementDescription: 'Provide conversation opener and suggested questions',
|
openingStatementDescription:
|
||||||
|
'Provide conversation opener and suggested questions',
|
||||||
end: 'End',
|
end: 'End',
|
||||||
endDescription: 'Mark the end of workflow execution',
|
endDescription: 'Mark the end of workflow execution',
|
||||||
log: 'Log',
|
log: 'Log',
|
||||||
@@ -1657,7 +1675,8 @@ const enUS = {
|
|||||||
title: 'Version History',
|
title: 'Version History',
|
||||||
current: 'Current Version',
|
current: 'Current Version',
|
||||||
rollback: 'Rollback to this version',
|
rollback: 'Rollback to this version',
|
||||||
rollbackConfirm: 'Are you sure you want to rollback to this version? Current changes will be lost.',
|
rollbackConfirm:
|
||||||
|
'Are you sure you want to rollback to this version? Current changes will be lost.',
|
||||||
rollbackSuccess: 'Rollback successful',
|
rollbackSuccess: 'Rollback successful',
|
||||||
rollbackError: 'Failed to rollback: ',
|
rollbackError: 'Failed to rollback: ',
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1373,7 +1373,8 @@ const esES = {
|
|||||||
},
|
},
|
||||||
workflows: {
|
workflows: {
|
||||||
title: 'Workflows',
|
title: 'Workflows',
|
||||||
description: 'Create and manage visual workflows for complex message processing logic',
|
description:
|
||||||
|
'Create and manage visual workflows for complex message processing logic',
|
||||||
createWorkflow: 'Create Workflow',
|
createWorkflow: 'Create Workflow',
|
||||||
selectFromSidebar: 'Select a workflow from the sidebar',
|
selectFromSidebar: 'Select a workflow from the sidebar',
|
||||||
editWorkflow: 'Edit Workflow',
|
editWorkflow: 'Edit Workflow',
|
||||||
@@ -1413,10 +1414,12 @@ const esES = {
|
|||||||
dangerZoneDesc: 'Irreversible operations',
|
dangerZoneDesc: 'Irreversible operations',
|
||||||
dangerZoneDescription: 'Irreversible operations',
|
dangerZoneDescription: 'Irreversible operations',
|
||||||
deleteWorkflowAction: 'Delete this workflow',
|
deleteWorkflowAction: 'Delete this workflow',
|
||||||
deleteWorkflowHint: 'Once deleted, all associated configurations will be permanently removed and cannot be recovered.',
|
deleteWorkflowHint:
|
||||||
|
'Once deleted, all associated configurations will be permanently removed and cannot be recovered.',
|
||||||
deleteWorkflow: 'Delete Workflow',
|
deleteWorkflow: 'Delete Workflow',
|
||||||
deleteConfirm: 'Confirm Delete',
|
deleteConfirm: 'Confirm Delete',
|
||||||
deleteConfirmDesc: 'Are you sure you want to delete workflow "{{name}}"? This action cannot be undone.',
|
deleteConfirmDesc:
|
||||||
|
'Are you sure you want to delete workflow "{{name}}"? This action cannot be undone.',
|
||||||
// Form component
|
// Form component
|
||||||
name: 'Name',
|
name: 'Name',
|
||||||
namePlaceholder: 'Enter workflow name',
|
namePlaceholder: 'Enter workflow name',
|
||||||
@@ -1479,13 +1482,16 @@ const esES = {
|
|||||||
dragToAdd: 'Drag nodes to add to canvas',
|
dragToAdd: 'Drag nodes to add to canvas',
|
||||||
// Property panel
|
// Property panel
|
||||||
selectNodeOrEdge: 'Select a node or edge',
|
selectNodeOrEdge: 'Select a node or edge',
|
||||||
selectNodeOrEdgeHint: 'Click on a node or edge in the canvas to view and edit its properties',
|
selectNodeOrEdgeHint:
|
||||||
|
'Click on a node or edge in the canvas to view and edit its properties',
|
||||||
edgeProperties: 'Edge Properties',
|
edgeProperties: 'Edge Properties',
|
||||||
nodeProperties: 'Node Properties',
|
nodeProperties: 'Node Properties',
|
||||||
condition: 'Condition',
|
condition: 'Condition',
|
||||||
hasCondition: 'Set',
|
hasCondition: 'Set',
|
||||||
conditionPlaceholder: 'Enter condition expression, e.g. output.success == true',
|
conditionPlaceholder:
|
||||||
conditionHelp: 'When condition is empty, this edge will always be executed. Use {{variable}} to reference context variables.',
|
'Enter condition expression, e.g. output.success == true',
|
||||||
|
conditionHelp:
|
||||||
|
'When condition is empty, this edge will always be executed. Use {{variable}} to reference context variables.',
|
||||||
deleteEdge: 'Delete Edge',
|
deleteEdge: 'Delete Edge',
|
||||||
deleteEdgeConfirm: 'Confirm Delete Edge',
|
deleteEdgeConfirm: 'Confirm Delete Edge',
|
||||||
deleteEdgeConfirmDesc: 'This edge will be permanently removed.',
|
deleteEdgeConfirmDesc: 'This edge will be permanently removed.',
|
||||||
@@ -1506,7 +1512,8 @@ const esES = {
|
|||||||
noConfigOptions: 'No configuration options for this node type',
|
noConfigOptions: 'No configuration options for this node type',
|
||||||
deleteNode: 'Delete Node',
|
deleteNode: 'Delete Node',
|
||||||
deleteNodeConfirm: 'Confirm Delete Node',
|
deleteNodeConfirm: 'Confirm Delete Node',
|
||||||
deleteNodeConfirmDesc: 'This node and all its connections will be permanently removed.',
|
deleteNodeConfirmDesc:
|
||||||
|
'This node and all its connections will be permanently removed.',
|
||||||
// Node inputs/outputs i18n (for port labels)
|
// Node inputs/outputs i18n (for port labels)
|
||||||
nodeInputs: {
|
nodeInputs: {
|
||||||
// Common inputs
|
// Common inputs
|
||||||
@@ -1569,7 +1576,8 @@ const esES = {
|
|||||||
aiProcess: 'AI Processing',
|
aiProcess: 'AI Processing',
|
||||||
aiProcessDescription: 'Process messages using AI models',
|
aiProcessDescription: 'Process messages using AI models',
|
||||||
llmCall: 'LLM Call',
|
llmCall: 'LLM Call',
|
||||||
llmCallDescription: 'Invoke large language model for conversation or generation',
|
llmCallDescription:
|
||||||
|
'Invoke large language model for conversation or generation',
|
||||||
codeProcess: 'Code Processing',
|
codeProcess: 'Code Processing',
|
||||||
codeProcessDescription: 'Execute custom code',
|
codeProcessDescription: 'Execute custom code',
|
||||||
codeExecutor: 'Code Executor',
|
codeExecutor: 'Code Executor',
|
||||||
@@ -1581,13 +1589,17 @@ const esES = {
|
|||||||
dataTransform: 'Data Transform',
|
dataTransform: 'Data Transform',
|
||||||
dataTransformDescription: 'Transform data format',
|
dataTransformDescription: 'Transform data format',
|
||||||
questionClassifier: 'Question Classifier',
|
questionClassifier: 'Question Classifier',
|
||||||
questionClassifierDescription: 'Classify user questions into predefined categories using LLM',
|
questionClassifierDescription:
|
||||||
|
'Classify user questions into predefined categories using LLM',
|
||||||
parameterExtractor: 'Parameter Extractor',
|
parameterExtractor: 'Parameter Extractor',
|
||||||
parameterExtractorDescription: 'Extract structured parameters from text using LLM',
|
parameterExtractorDescription:
|
||||||
|
'Extract structured parameters from text using LLM',
|
||||||
knowledgeRetrieval: 'Knowledge Retrieval',
|
knowledgeRetrieval: 'Knowledge Retrieval',
|
||||||
knowledgeRetrievalDescription: 'Retrieve relevant content from knowledge base',
|
knowledgeRetrievalDescription:
|
||||||
|
'Retrieve relevant content from knowledge base',
|
||||||
textTemplate: 'Text Template',
|
textTemplate: 'Text Template',
|
||||||
textTemplateDescription: 'Generate text using templates with variable interpolation',
|
textTemplateDescription:
|
||||||
|
'Generate text using templates with variable interpolation',
|
||||||
jsonTransform: 'JSON Transform',
|
jsonTransform: 'JSON Transform',
|
||||||
jsonTransformDescription: 'Transform JSON data using expressions',
|
jsonTransformDescription: 'Transform JSON data using expressions',
|
||||||
dataAggregator: 'Data Aggregator',
|
dataAggregator: 'Data Aggregator',
|
||||||
@@ -1615,13 +1627,15 @@ const esES = {
|
|||||||
merge: 'Merge',
|
merge: 'Merge',
|
||||||
mergeDescription: 'Merge multiple branches',
|
mergeDescription: 'Merge multiple branches',
|
||||||
variableAggregator: 'Variable Aggregator',
|
variableAggregator: 'Variable Aggregator',
|
||||||
variableAggregatorDescription: 'Aggregate variable outputs from multiple branches',
|
variableAggregatorDescription:
|
||||||
|
'Aggregate variable outputs from multiple branches',
|
||||||
action: 'Actions',
|
action: 'Actions',
|
||||||
actionDescription: 'Action execution nodes',
|
actionDescription: 'Action execution nodes',
|
||||||
sendMessage: 'Send Message',
|
sendMessage: 'Send Message',
|
||||||
sendMessageDescription: 'Send message to platform',
|
sendMessageDescription: 'Send message to platform',
|
||||||
replyMessage: 'Reply Message',
|
replyMessage: 'Reply Message',
|
||||||
replyMessageDescription: 'Reply to the message that triggered the workflow',
|
replyMessageDescription:
|
||||||
|
'Reply to the message that triggered the workflow',
|
||||||
storeData: 'Store Data',
|
storeData: 'Store Data',
|
||||||
storeDataDescription: 'Store data to database',
|
storeDataDescription: 'Store data to database',
|
||||||
callPipeline: 'Call Pipeline',
|
callPipeline: 'Call Pipeline',
|
||||||
@@ -1629,7 +1643,8 @@ const esES = {
|
|||||||
setVariable: 'Set Variable',
|
setVariable: 'Set Variable',
|
||||||
setVariableDescription: 'Set context variable',
|
setVariableDescription: 'Set context variable',
|
||||||
openingStatement: 'Opening Statement',
|
openingStatement: 'Opening Statement',
|
||||||
openingStatementDescription: 'Provide conversation opener and suggested questions',
|
openingStatementDescription:
|
||||||
|
'Provide conversation opener and suggested questions',
|
||||||
end: 'End',
|
end: 'End',
|
||||||
endDescription: 'Mark the end of workflow execution',
|
endDescription: 'Mark the end of workflow execution',
|
||||||
log: 'Log',
|
log: 'Log',
|
||||||
@@ -1675,7 +1690,8 @@ const esES = {
|
|||||||
title: 'Version History',
|
title: 'Version History',
|
||||||
current: 'Current Version',
|
current: 'Current Version',
|
||||||
rollback: 'Rollback to this version',
|
rollback: 'Rollback to this version',
|
||||||
rollbackConfirm: 'Are you sure you want to rollback to this version? Current changes will be lost.',
|
rollbackConfirm:
|
||||||
|
'Are you sure you want to rollback to this version? Current changes will be lost.',
|
||||||
rollbackSuccess: 'Rollback successful',
|
rollbackSuccess: 'Rollback successful',
|
||||||
rollbackError: 'Failed to rollback: ',
|
rollbackError: 'Failed to rollback: ',
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1341,7 +1341,8 @@
|
|||||||
},
|
},
|
||||||
workflows: {
|
workflows: {
|
||||||
title: 'Workflows',
|
title: 'Workflows',
|
||||||
description: 'Create and manage visual workflows for complex message processing logic',
|
description:
|
||||||
|
'Create and manage visual workflows for complex message processing logic',
|
||||||
createWorkflow: 'Create Workflow',
|
createWorkflow: 'Create Workflow',
|
||||||
selectFromSidebar: 'Select a workflow from the sidebar',
|
selectFromSidebar: 'Select a workflow from the sidebar',
|
||||||
editWorkflow: 'Edit Workflow',
|
editWorkflow: 'Edit Workflow',
|
||||||
@@ -1381,10 +1382,12 @@
|
|||||||
dangerZoneDesc: 'Irreversible operations',
|
dangerZoneDesc: 'Irreversible operations',
|
||||||
dangerZoneDescription: 'Irreversible operations',
|
dangerZoneDescription: 'Irreversible operations',
|
||||||
deleteWorkflowAction: 'Delete this workflow',
|
deleteWorkflowAction: 'Delete this workflow',
|
||||||
deleteWorkflowHint: 'Once deleted, all associated configurations will be permanently removed and cannot be recovered.',
|
deleteWorkflowHint:
|
||||||
|
'Once deleted, all associated configurations will be permanently removed and cannot be recovered.',
|
||||||
deleteWorkflow: 'Delete Workflow',
|
deleteWorkflow: 'Delete Workflow',
|
||||||
deleteConfirm: 'Confirm Delete',
|
deleteConfirm: 'Confirm Delete',
|
||||||
deleteConfirmDesc: 'Are you sure you want to delete workflow "{{name}}"? This action cannot be undone.',
|
deleteConfirmDesc:
|
||||||
|
'Are you sure you want to delete workflow "{{name}}"? This action cannot be undone.',
|
||||||
// Form component
|
// Form component
|
||||||
name: 'Name',
|
name: 'Name',
|
||||||
namePlaceholder: 'Enter workflow name',
|
namePlaceholder: 'Enter workflow name',
|
||||||
@@ -1447,13 +1450,16 @@
|
|||||||
dragToAdd: 'Drag nodes to add to canvas',
|
dragToAdd: 'Drag nodes to add to canvas',
|
||||||
// Property panel
|
// Property panel
|
||||||
selectNodeOrEdge: 'Select a node or edge',
|
selectNodeOrEdge: 'Select a node or edge',
|
||||||
selectNodeOrEdgeHint: 'Click on a node or edge in the canvas to view and edit its properties',
|
selectNodeOrEdgeHint:
|
||||||
|
'Click on a node or edge in the canvas to view and edit its properties',
|
||||||
edgeProperties: 'Edge Properties',
|
edgeProperties: 'Edge Properties',
|
||||||
nodeProperties: 'Node Properties',
|
nodeProperties: 'Node Properties',
|
||||||
condition: 'Condition',
|
condition: 'Condition',
|
||||||
hasCondition: 'Set',
|
hasCondition: 'Set',
|
||||||
conditionPlaceholder: 'Enter condition expression, e.g. output.success == true',
|
conditionPlaceholder:
|
||||||
conditionHelp: 'When condition is empty, this edge will always be executed. Use {{variable}} to reference context variables.',
|
'Enter condition expression, e.g. output.success == true',
|
||||||
|
conditionHelp:
|
||||||
|
'When condition is empty, this edge will always be executed. Use {{variable}} to reference context variables.',
|
||||||
deleteEdge: 'Delete Edge',
|
deleteEdge: 'Delete Edge',
|
||||||
deleteEdgeConfirm: 'Confirm Delete Edge',
|
deleteEdgeConfirm: 'Confirm Delete Edge',
|
||||||
deleteEdgeConfirmDesc: 'This edge will be permanently removed.',
|
deleteEdgeConfirmDesc: 'This edge will be permanently removed.',
|
||||||
@@ -1474,7 +1480,8 @@
|
|||||||
noConfigOptions: 'No configuration options for this node type',
|
noConfigOptions: 'No configuration options for this node type',
|
||||||
deleteNode: 'Delete Node',
|
deleteNode: 'Delete Node',
|
||||||
deleteNodeConfirm: 'Confirm Delete Node',
|
deleteNodeConfirm: 'Confirm Delete Node',
|
||||||
deleteNodeConfirmDesc: 'This node and all its connections will be permanently removed.',
|
deleteNodeConfirmDesc:
|
||||||
|
'This node and all its connections will be permanently removed.',
|
||||||
// Node inputs/outputs i18n (for port labels)
|
// Node inputs/outputs i18n (for port labels)
|
||||||
nodeInputs: {
|
nodeInputs: {
|
||||||
// Common inputs
|
// Common inputs
|
||||||
@@ -1537,7 +1544,8 @@
|
|||||||
aiProcess: 'AI Processing',
|
aiProcess: 'AI Processing',
|
||||||
aiProcessDescription: 'Process messages using AI models',
|
aiProcessDescription: 'Process messages using AI models',
|
||||||
llmCall: 'LLM Call',
|
llmCall: 'LLM Call',
|
||||||
llmCallDescription: 'Invoke large language model for conversation or generation',
|
llmCallDescription:
|
||||||
|
'Invoke large language model for conversation or generation',
|
||||||
codeProcess: 'Code Processing',
|
codeProcess: 'Code Processing',
|
||||||
codeProcessDescription: 'Execute custom code',
|
codeProcessDescription: 'Execute custom code',
|
||||||
codeExecutor: 'Code Executor',
|
codeExecutor: 'Code Executor',
|
||||||
@@ -1549,13 +1557,17 @@
|
|||||||
dataTransform: 'Data Transform',
|
dataTransform: 'Data Transform',
|
||||||
dataTransformDescription: 'Transform data format',
|
dataTransformDescription: 'Transform data format',
|
||||||
questionClassifier: 'Question Classifier',
|
questionClassifier: 'Question Classifier',
|
||||||
questionClassifierDescription: 'Classify user questions into predefined categories using LLM',
|
questionClassifierDescription:
|
||||||
|
'Classify user questions into predefined categories using LLM',
|
||||||
parameterExtractor: 'Parameter Extractor',
|
parameterExtractor: 'Parameter Extractor',
|
||||||
parameterExtractorDescription: 'Extract structured parameters from text using LLM',
|
parameterExtractorDescription:
|
||||||
|
'Extract structured parameters from text using LLM',
|
||||||
knowledgeRetrieval: 'Knowledge Retrieval',
|
knowledgeRetrieval: 'Knowledge Retrieval',
|
||||||
knowledgeRetrievalDescription: 'Retrieve relevant content from knowledge base',
|
knowledgeRetrievalDescription:
|
||||||
|
'Retrieve relevant content from knowledge base',
|
||||||
textTemplate: 'Text Template',
|
textTemplate: 'Text Template',
|
||||||
textTemplateDescription: 'Generate text using templates with variable interpolation',
|
textTemplateDescription:
|
||||||
|
'Generate text using templates with variable interpolation',
|
||||||
jsonTransform: 'JSON Transform',
|
jsonTransform: 'JSON Transform',
|
||||||
jsonTransformDescription: 'Transform JSON data using expressions',
|
jsonTransformDescription: 'Transform JSON data using expressions',
|
||||||
dataAggregator: 'Data Aggregator',
|
dataAggregator: 'Data Aggregator',
|
||||||
@@ -1583,13 +1595,15 @@
|
|||||||
merge: 'Merge',
|
merge: 'Merge',
|
||||||
mergeDescription: 'Merge multiple branches',
|
mergeDescription: 'Merge multiple branches',
|
||||||
variableAggregator: 'Variable Aggregator',
|
variableAggregator: 'Variable Aggregator',
|
||||||
variableAggregatorDescription: 'Aggregate variable outputs from multiple branches',
|
variableAggregatorDescription:
|
||||||
|
'Aggregate variable outputs from multiple branches',
|
||||||
action: 'Actions',
|
action: 'Actions',
|
||||||
actionDescription: 'Action execution nodes',
|
actionDescription: 'Action execution nodes',
|
||||||
sendMessage: 'Send Message',
|
sendMessage: 'Send Message',
|
||||||
sendMessageDescription: 'Send message to platform',
|
sendMessageDescription: 'Send message to platform',
|
||||||
replyMessage: 'Reply Message',
|
replyMessage: 'Reply Message',
|
||||||
replyMessageDescription: 'Reply to the message that triggered the workflow',
|
replyMessageDescription:
|
||||||
|
'Reply to the message that triggered the workflow',
|
||||||
storeData: 'Store Data',
|
storeData: 'Store Data',
|
||||||
storeDataDescription: 'Store data to database',
|
storeDataDescription: 'Store data to database',
|
||||||
callPipeline: 'Call Pipeline',
|
callPipeline: 'Call Pipeline',
|
||||||
@@ -1597,7 +1611,8 @@
|
|||||||
setVariable: 'Set Variable',
|
setVariable: 'Set Variable',
|
||||||
setVariableDescription: 'Set context variable',
|
setVariableDescription: 'Set context variable',
|
||||||
openingStatement: 'Opening Statement',
|
openingStatement: 'Opening Statement',
|
||||||
openingStatementDescription: 'Provide conversation opener and suggested questions',
|
openingStatementDescription:
|
||||||
|
'Provide conversation opener and suggested questions',
|
||||||
end: 'End',
|
end: 'End',
|
||||||
endDescription: 'Mark the end of workflow execution',
|
endDescription: 'Mark the end of workflow execution',
|
||||||
log: 'Log',
|
log: 'Log',
|
||||||
@@ -1643,7 +1658,8 @@
|
|||||||
title: 'Version History',
|
title: 'Version History',
|
||||||
current: 'Current Version',
|
current: 'Current Version',
|
||||||
rollback: 'Rollback to this version',
|
rollback: 'Rollback to this version',
|
||||||
rollbackConfirm: 'Are you sure you want to rollback to this version? Current changes will be lost.',
|
rollbackConfirm:
|
||||||
|
'Are you sure you want to rollback to this version? Current changes will be lost.',
|
||||||
rollbackSuccess: 'Rollback successful',
|
rollbackSuccess: 'Rollback successful',
|
||||||
rollbackError: 'Failed to rollback: ',
|
rollbackError: 'Failed to rollback: ',
|
||||||
},
|
},
|
||||||
@@ -1719,7 +1735,8 @@
|
|||||||
disconnected: 'WebSocket未接続',
|
disconnected: 'WebSocket未接続',
|
||||||
connectionError: 'WebSocket接続エラー',
|
connectionError: 'WebSocket接続エラー',
|
||||||
connectionFailed: 'WebSocket接続失敗',
|
connectionFailed: 'WebSocket接続失敗',
|
||||||
notConnected: 'WebSocket未接続です。しばらくしてからもう一度お試しください',
|
notConnected:
|
||||||
|
'WebSocket未接続です。しばらくしてからもう一度お試しください',
|
||||||
imageUploadFailed: '画像アップロード失敗',
|
imageUploadFailed: '画像アップロード失敗',
|
||||||
reply: '返信',
|
reply: '返信',
|
||||||
replyTo: '返信先',
|
replyTo: '返信先',
|
||||||
|
|||||||
@@ -1344,7 +1344,8 @@ const ruRU = {
|
|||||||
},
|
},
|
||||||
workflows: {
|
workflows: {
|
||||||
title: 'Workflows',
|
title: 'Workflows',
|
||||||
description: 'Create and manage visual workflows for complex message processing logic',
|
description:
|
||||||
|
'Create and manage visual workflows for complex message processing logic',
|
||||||
createWorkflow: 'Create Workflow',
|
createWorkflow: 'Create Workflow',
|
||||||
selectFromSidebar: 'Select a workflow from the sidebar',
|
selectFromSidebar: 'Select a workflow from the sidebar',
|
||||||
editWorkflow: 'Edit Workflow',
|
editWorkflow: 'Edit Workflow',
|
||||||
@@ -1384,10 +1385,12 @@ const ruRU = {
|
|||||||
dangerZoneDesc: 'Irreversible operations',
|
dangerZoneDesc: 'Irreversible operations',
|
||||||
dangerZoneDescription: 'Irreversible operations',
|
dangerZoneDescription: 'Irreversible operations',
|
||||||
deleteWorkflowAction: 'Delete this workflow',
|
deleteWorkflowAction: 'Delete this workflow',
|
||||||
deleteWorkflowHint: 'Once deleted, all associated configurations will be permanently removed and cannot be recovered.',
|
deleteWorkflowHint:
|
||||||
|
'Once deleted, all associated configurations will be permanently removed and cannot be recovered.',
|
||||||
deleteWorkflow: 'Delete Workflow',
|
deleteWorkflow: 'Delete Workflow',
|
||||||
deleteConfirm: 'Confirm Delete',
|
deleteConfirm: 'Confirm Delete',
|
||||||
deleteConfirmDesc: 'Are you sure you want to delete workflow "{{name}}"? This action cannot be undone.',
|
deleteConfirmDesc:
|
||||||
|
'Are you sure you want to delete workflow "{{name}}"? This action cannot be undone.',
|
||||||
// Form component
|
// Form component
|
||||||
name: 'Name',
|
name: 'Name',
|
||||||
namePlaceholder: 'Enter workflow name',
|
namePlaceholder: 'Enter workflow name',
|
||||||
@@ -1450,13 +1453,16 @@ const ruRU = {
|
|||||||
dragToAdd: 'Drag nodes to add to canvas',
|
dragToAdd: 'Drag nodes to add to canvas',
|
||||||
// Property panel
|
// Property panel
|
||||||
selectNodeOrEdge: 'Select a node or edge',
|
selectNodeOrEdge: 'Select a node or edge',
|
||||||
selectNodeOrEdgeHint: 'Click on a node or edge in the canvas to view and edit its properties',
|
selectNodeOrEdgeHint:
|
||||||
|
'Click on a node or edge in the canvas to view and edit its properties',
|
||||||
edgeProperties: 'Edge Properties',
|
edgeProperties: 'Edge Properties',
|
||||||
nodeProperties: 'Node Properties',
|
nodeProperties: 'Node Properties',
|
||||||
condition: 'Condition',
|
condition: 'Condition',
|
||||||
hasCondition: 'Set',
|
hasCondition: 'Set',
|
||||||
conditionPlaceholder: 'Enter condition expression, e.g. output.success == true',
|
conditionPlaceholder:
|
||||||
conditionHelp: 'When condition is empty, this edge will always be executed. Use {{variable}} to reference context variables.',
|
'Enter condition expression, e.g. output.success == true',
|
||||||
|
conditionHelp:
|
||||||
|
'When condition is empty, this edge will always be executed. Use {{variable}} to reference context variables.',
|
||||||
deleteEdge: 'Delete Edge',
|
deleteEdge: 'Delete Edge',
|
||||||
deleteEdgeConfirm: 'Confirm Delete Edge',
|
deleteEdgeConfirm: 'Confirm Delete Edge',
|
||||||
deleteEdgeConfirmDesc: 'This edge will be permanently removed.',
|
deleteEdgeConfirmDesc: 'This edge will be permanently removed.',
|
||||||
@@ -1477,7 +1483,8 @@ const ruRU = {
|
|||||||
noConfigOptions: 'No configuration options for this node type',
|
noConfigOptions: 'No configuration options for this node type',
|
||||||
deleteNode: 'Delete Node',
|
deleteNode: 'Delete Node',
|
||||||
deleteNodeConfirm: 'Confirm Delete Node',
|
deleteNodeConfirm: 'Confirm Delete Node',
|
||||||
deleteNodeConfirmDesc: 'This node and all its connections will be permanently removed.',
|
deleteNodeConfirmDesc:
|
||||||
|
'This node and all its connections will be permanently removed.',
|
||||||
// Node inputs/outputs i18n (for port labels)
|
// Node inputs/outputs i18n (for port labels)
|
||||||
nodeInputs: {
|
nodeInputs: {
|
||||||
// Common inputs
|
// Common inputs
|
||||||
@@ -1540,7 +1547,8 @@ const ruRU = {
|
|||||||
aiProcess: 'AI Processing',
|
aiProcess: 'AI Processing',
|
||||||
aiProcessDescription: 'Process messages using AI models',
|
aiProcessDescription: 'Process messages using AI models',
|
||||||
llmCall: 'LLM Call',
|
llmCall: 'LLM Call',
|
||||||
llmCallDescription: 'Invoke large language model for conversation or generation',
|
llmCallDescription:
|
||||||
|
'Invoke large language model for conversation or generation',
|
||||||
codeProcess: 'Code Processing',
|
codeProcess: 'Code Processing',
|
||||||
codeProcessDescription: 'Execute custom code',
|
codeProcessDescription: 'Execute custom code',
|
||||||
codeExecutor: 'Code Executor',
|
codeExecutor: 'Code Executor',
|
||||||
@@ -1552,13 +1560,17 @@ const ruRU = {
|
|||||||
dataTransform: 'Data Transform',
|
dataTransform: 'Data Transform',
|
||||||
dataTransformDescription: 'Transform data format',
|
dataTransformDescription: 'Transform data format',
|
||||||
questionClassifier: 'Question Classifier',
|
questionClassifier: 'Question Classifier',
|
||||||
questionClassifierDescription: 'Classify user questions into predefined categories using LLM',
|
questionClassifierDescription:
|
||||||
|
'Classify user questions into predefined categories using LLM',
|
||||||
parameterExtractor: 'Parameter Extractor',
|
parameterExtractor: 'Parameter Extractor',
|
||||||
parameterExtractorDescription: 'Extract structured parameters from text using LLM',
|
parameterExtractorDescription:
|
||||||
|
'Extract structured parameters from text using LLM',
|
||||||
knowledgeRetrieval: 'Knowledge Retrieval',
|
knowledgeRetrieval: 'Knowledge Retrieval',
|
||||||
knowledgeRetrievalDescription: 'Retrieve relevant content from knowledge base',
|
knowledgeRetrievalDescription:
|
||||||
|
'Retrieve relevant content from knowledge base',
|
||||||
textTemplate: 'Text Template',
|
textTemplate: 'Text Template',
|
||||||
textTemplateDescription: 'Generate text using templates with variable interpolation',
|
textTemplateDescription:
|
||||||
|
'Generate text using templates with variable interpolation',
|
||||||
jsonTransform: 'JSON Transform',
|
jsonTransform: 'JSON Transform',
|
||||||
jsonTransformDescription: 'Transform JSON data using expressions',
|
jsonTransformDescription: 'Transform JSON data using expressions',
|
||||||
dataAggregator: 'Data Aggregator',
|
dataAggregator: 'Data Aggregator',
|
||||||
@@ -1586,13 +1598,15 @@ const ruRU = {
|
|||||||
merge: 'Merge',
|
merge: 'Merge',
|
||||||
mergeDescription: 'Merge multiple branches',
|
mergeDescription: 'Merge multiple branches',
|
||||||
variableAggregator: 'Variable Aggregator',
|
variableAggregator: 'Variable Aggregator',
|
||||||
variableAggregatorDescription: 'Aggregate variable outputs from multiple branches',
|
variableAggregatorDescription:
|
||||||
|
'Aggregate variable outputs from multiple branches',
|
||||||
action: 'Actions',
|
action: 'Actions',
|
||||||
actionDescription: 'Action execution nodes',
|
actionDescription: 'Action execution nodes',
|
||||||
sendMessage: 'Send Message',
|
sendMessage: 'Send Message',
|
||||||
sendMessageDescription: 'Send message to platform',
|
sendMessageDescription: 'Send message to platform',
|
||||||
replyMessage: 'Reply Message',
|
replyMessage: 'Reply Message',
|
||||||
replyMessageDescription: 'Reply to the message that triggered the workflow',
|
replyMessageDescription:
|
||||||
|
'Reply to the message that triggered the workflow',
|
||||||
storeData: 'Store Data',
|
storeData: 'Store Data',
|
||||||
storeDataDescription: 'Store data to database',
|
storeDataDescription: 'Store data to database',
|
||||||
callPipeline: 'Call Pipeline',
|
callPipeline: 'Call Pipeline',
|
||||||
@@ -1600,7 +1614,8 @@ const ruRU = {
|
|||||||
setVariable: 'Set Variable',
|
setVariable: 'Set Variable',
|
||||||
setVariableDescription: 'Set context variable',
|
setVariableDescription: 'Set context variable',
|
||||||
openingStatement: 'Opening Statement',
|
openingStatement: 'Opening Statement',
|
||||||
openingStatementDescription: 'Provide conversation opener and suggested questions',
|
openingStatementDescription:
|
||||||
|
'Provide conversation opener and suggested questions',
|
||||||
end: 'End',
|
end: 'End',
|
||||||
endDescription: 'Mark the end of workflow execution',
|
endDescription: 'Mark the end of workflow execution',
|
||||||
log: 'Log',
|
log: 'Log',
|
||||||
@@ -1646,7 +1661,8 @@ const ruRU = {
|
|||||||
title: 'Version History',
|
title: 'Version History',
|
||||||
current: 'Current Version',
|
current: 'Current Version',
|
||||||
rollback: 'Rollback to this version',
|
rollback: 'Rollback to this version',
|
||||||
rollbackConfirm: 'Are you sure you want to rollback to this version? Current changes will be lost.',
|
rollbackConfirm:
|
||||||
|
'Are you sure you want to rollback to this version? Current changes will be lost.',
|
||||||
rollbackSuccess: 'Rollback successful',
|
rollbackSuccess: 'Rollback successful',
|
||||||
rollbackError: 'Failed to rollback: ',
|
rollbackError: 'Failed to rollback: ',
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1313,7 +1313,8 @@ const thTH = {
|
|||||||
},
|
},
|
||||||
workflows: {
|
workflows: {
|
||||||
title: 'Workflows',
|
title: 'Workflows',
|
||||||
description: 'Create and manage visual workflows for complex message processing logic',
|
description:
|
||||||
|
'Create and manage visual workflows for complex message processing logic',
|
||||||
createWorkflow: 'Create Workflow',
|
createWorkflow: 'Create Workflow',
|
||||||
selectFromSidebar: 'Select a workflow from the sidebar',
|
selectFromSidebar: 'Select a workflow from the sidebar',
|
||||||
editWorkflow: 'Edit Workflow',
|
editWorkflow: 'Edit Workflow',
|
||||||
@@ -1353,10 +1354,12 @@ const thTH = {
|
|||||||
dangerZoneDesc: 'Irreversible operations',
|
dangerZoneDesc: 'Irreversible operations',
|
||||||
dangerZoneDescription: 'Irreversible operations',
|
dangerZoneDescription: 'Irreversible operations',
|
||||||
deleteWorkflowAction: 'Delete this workflow',
|
deleteWorkflowAction: 'Delete this workflow',
|
||||||
deleteWorkflowHint: 'Once deleted, all associated configurations will be permanently removed and cannot be recovered.',
|
deleteWorkflowHint:
|
||||||
|
'Once deleted, all associated configurations will be permanently removed and cannot be recovered.',
|
||||||
deleteWorkflow: 'Delete Workflow',
|
deleteWorkflow: 'Delete Workflow',
|
||||||
deleteConfirm: 'Confirm Delete',
|
deleteConfirm: 'Confirm Delete',
|
||||||
deleteConfirmDesc: 'Are you sure you want to delete workflow "{{name}}"? This action cannot be undone.',
|
deleteConfirmDesc:
|
||||||
|
'Are you sure you want to delete workflow "{{name}}"? This action cannot be undone.',
|
||||||
// Form component
|
// Form component
|
||||||
name: 'Name',
|
name: 'Name',
|
||||||
namePlaceholder: 'Enter workflow name',
|
namePlaceholder: 'Enter workflow name',
|
||||||
@@ -1419,13 +1422,16 @@ const thTH = {
|
|||||||
dragToAdd: 'Drag nodes to add to canvas',
|
dragToAdd: 'Drag nodes to add to canvas',
|
||||||
// Property panel
|
// Property panel
|
||||||
selectNodeOrEdge: 'Select a node or edge',
|
selectNodeOrEdge: 'Select a node or edge',
|
||||||
selectNodeOrEdgeHint: 'Click on a node or edge in the canvas to view and edit its properties',
|
selectNodeOrEdgeHint:
|
||||||
|
'Click on a node or edge in the canvas to view and edit its properties',
|
||||||
edgeProperties: 'Edge Properties',
|
edgeProperties: 'Edge Properties',
|
||||||
nodeProperties: 'Node Properties',
|
nodeProperties: 'Node Properties',
|
||||||
condition: 'Condition',
|
condition: 'Condition',
|
||||||
hasCondition: 'Set',
|
hasCondition: 'Set',
|
||||||
conditionPlaceholder: 'Enter condition expression, e.g. output.success == true',
|
conditionPlaceholder:
|
||||||
conditionHelp: 'When condition is empty, this edge will always be executed. Use {{variable}} to reference context variables.',
|
'Enter condition expression, e.g. output.success == true',
|
||||||
|
conditionHelp:
|
||||||
|
'When condition is empty, this edge will always be executed. Use {{variable}} to reference context variables.',
|
||||||
deleteEdge: 'Delete Edge',
|
deleteEdge: 'Delete Edge',
|
||||||
deleteEdgeConfirm: 'Confirm Delete Edge',
|
deleteEdgeConfirm: 'Confirm Delete Edge',
|
||||||
deleteEdgeConfirmDesc: 'This edge will be permanently removed.',
|
deleteEdgeConfirmDesc: 'This edge will be permanently removed.',
|
||||||
@@ -1446,7 +1452,8 @@ const thTH = {
|
|||||||
noConfigOptions: 'No configuration options for this node type',
|
noConfigOptions: 'No configuration options for this node type',
|
||||||
deleteNode: 'Delete Node',
|
deleteNode: 'Delete Node',
|
||||||
deleteNodeConfirm: 'Confirm Delete Node',
|
deleteNodeConfirm: 'Confirm Delete Node',
|
||||||
deleteNodeConfirmDesc: 'This node and all its connections will be permanently removed.',
|
deleteNodeConfirmDesc:
|
||||||
|
'This node and all its connections will be permanently removed.',
|
||||||
// Node inputs/outputs i18n (for port labels)
|
// Node inputs/outputs i18n (for port labels)
|
||||||
nodeInputs: {
|
nodeInputs: {
|
||||||
// Common inputs
|
// Common inputs
|
||||||
@@ -1509,7 +1516,8 @@ const thTH = {
|
|||||||
aiProcess: 'AI Processing',
|
aiProcess: 'AI Processing',
|
||||||
aiProcessDescription: 'Process messages using AI models',
|
aiProcessDescription: 'Process messages using AI models',
|
||||||
llmCall: 'LLM Call',
|
llmCall: 'LLM Call',
|
||||||
llmCallDescription: 'Invoke large language model for conversation or generation',
|
llmCallDescription:
|
||||||
|
'Invoke large language model for conversation or generation',
|
||||||
codeProcess: 'Code Processing',
|
codeProcess: 'Code Processing',
|
||||||
codeProcessDescription: 'Execute custom code',
|
codeProcessDescription: 'Execute custom code',
|
||||||
codeExecutor: 'Code Executor',
|
codeExecutor: 'Code Executor',
|
||||||
@@ -1521,13 +1529,17 @@ const thTH = {
|
|||||||
dataTransform: 'Data Transform',
|
dataTransform: 'Data Transform',
|
||||||
dataTransformDescription: 'Transform data format',
|
dataTransformDescription: 'Transform data format',
|
||||||
questionClassifier: 'Question Classifier',
|
questionClassifier: 'Question Classifier',
|
||||||
questionClassifierDescription: 'Classify user questions into predefined categories using LLM',
|
questionClassifierDescription:
|
||||||
|
'Classify user questions into predefined categories using LLM',
|
||||||
parameterExtractor: 'Parameter Extractor',
|
parameterExtractor: 'Parameter Extractor',
|
||||||
parameterExtractorDescription: 'Extract structured parameters from text using LLM',
|
parameterExtractorDescription:
|
||||||
|
'Extract structured parameters from text using LLM',
|
||||||
knowledgeRetrieval: 'Knowledge Retrieval',
|
knowledgeRetrieval: 'Knowledge Retrieval',
|
||||||
knowledgeRetrievalDescription: 'Retrieve relevant content from knowledge base',
|
knowledgeRetrievalDescription:
|
||||||
|
'Retrieve relevant content from knowledge base',
|
||||||
textTemplate: 'Text Template',
|
textTemplate: 'Text Template',
|
||||||
textTemplateDescription: 'Generate text using templates with variable interpolation',
|
textTemplateDescription:
|
||||||
|
'Generate text using templates with variable interpolation',
|
||||||
jsonTransform: 'JSON Transform',
|
jsonTransform: 'JSON Transform',
|
||||||
jsonTransformDescription: 'Transform JSON data using expressions',
|
jsonTransformDescription: 'Transform JSON data using expressions',
|
||||||
dataAggregator: 'Data Aggregator',
|
dataAggregator: 'Data Aggregator',
|
||||||
@@ -1555,13 +1567,15 @@ const thTH = {
|
|||||||
merge: 'Merge',
|
merge: 'Merge',
|
||||||
mergeDescription: 'Merge multiple branches',
|
mergeDescription: 'Merge multiple branches',
|
||||||
variableAggregator: 'Variable Aggregator',
|
variableAggregator: 'Variable Aggregator',
|
||||||
variableAggregatorDescription: 'Aggregate variable outputs from multiple branches',
|
variableAggregatorDescription:
|
||||||
|
'Aggregate variable outputs from multiple branches',
|
||||||
action: 'Actions',
|
action: 'Actions',
|
||||||
actionDescription: 'Action execution nodes',
|
actionDescription: 'Action execution nodes',
|
||||||
sendMessage: 'Send Message',
|
sendMessage: 'Send Message',
|
||||||
sendMessageDescription: 'Send message to platform',
|
sendMessageDescription: 'Send message to platform',
|
||||||
replyMessage: 'Reply Message',
|
replyMessage: 'Reply Message',
|
||||||
replyMessageDescription: 'Reply to the message that triggered the workflow',
|
replyMessageDescription:
|
||||||
|
'Reply to the message that triggered the workflow',
|
||||||
storeData: 'Store Data',
|
storeData: 'Store Data',
|
||||||
storeDataDescription: 'Store data to database',
|
storeDataDescription: 'Store data to database',
|
||||||
callPipeline: 'Call Pipeline',
|
callPipeline: 'Call Pipeline',
|
||||||
@@ -1569,7 +1583,8 @@ const thTH = {
|
|||||||
setVariable: 'Set Variable',
|
setVariable: 'Set Variable',
|
||||||
setVariableDescription: 'Set context variable',
|
setVariableDescription: 'Set context variable',
|
||||||
openingStatement: 'Opening Statement',
|
openingStatement: 'Opening Statement',
|
||||||
openingStatementDescription: 'Provide conversation opener and suggested questions',
|
openingStatementDescription:
|
||||||
|
'Provide conversation opener and suggested questions',
|
||||||
end: 'End',
|
end: 'End',
|
||||||
endDescription: 'Mark the end of workflow execution',
|
endDescription: 'Mark the end of workflow execution',
|
||||||
log: 'Log',
|
log: 'Log',
|
||||||
@@ -1615,7 +1630,8 @@ const thTH = {
|
|||||||
title: 'Version History',
|
title: 'Version History',
|
||||||
current: 'Current Version',
|
current: 'Current Version',
|
||||||
rollback: 'Rollback to this version',
|
rollback: 'Rollback to this version',
|
||||||
rollbackConfirm: 'Are you sure you want to rollback to this version? Current changes will be lost.',
|
rollbackConfirm:
|
||||||
|
'Are you sure you want to rollback to this version? Current changes will be lost.',
|
||||||
rollbackSuccess: 'Rollback successful',
|
rollbackSuccess: 'Rollback successful',
|
||||||
rollbackError: 'Failed to rollback: ',
|
rollbackError: 'Failed to rollback: ',
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1335,7 +1335,8 @@ const viVN = {
|
|||||||
},
|
},
|
||||||
workflows: {
|
workflows: {
|
||||||
title: 'Workflows',
|
title: 'Workflows',
|
||||||
description: 'Create and manage visual workflows for complex message processing logic',
|
description:
|
||||||
|
'Create and manage visual workflows for complex message processing logic',
|
||||||
createWorkflow: 'Create Workflow',
|
createWorkflow: 'Create Workflow',
|
||||||
selectFromSidebar: 'Select a workflow from the sidebar',
|
selectFromSidebar: 'Select a workflow from the sidebar',
|
||||||
editWorkflow: 'Edit Workflow',
|
editWorkflow: 'Edit Workflow',
|
||||||
@@ -1375,10 +1376,12 @@ const viVN = {
|
|||||||
dangerZoneDesc: 'Irreversible operations',
|
dangerZoneDesc: 'Irreversible operations',
|
||||||
dangerZoneDescription: 'Irreversible operations',
|
dangerZoneDescription: 'Irreversible operations',
|
||||||
deleteWorkflowAction: 'Delete this workflow',
|
deleteWorkflowAction: 'Delete this workflow',
|
||||||
deleteWorkflowHint: 'Once deleted, all associated configurations will be permanently removed and cannot be recovered.',
|
deleteWorkflowHint:
|
||||||
|
'Once deleted, all associated configurations will be permanently removed and cannot be recovered.',
|
||||||
deleteWorkflow: 'Delete Workflow',
|
deleteWorkflow: 'Delete Workflow',
|
||||||
deleteConfirm: 'Confirm Delete',
|
deleteConfirm: 'Confirm Delete',
|
||||||
deleteConfirmDesc: 'Are you sure you want to delete workflow "{{name}}"? This action cannot be undone.',
|
deleteConfirmDesc:
|
||||||
|
'Are you sure you want to delete workflow "{{name}}"? This action cannot be undone.',
|
||||||
// Form component
|
// Form component
|
||||||
name: 'Name',
|
name: 'Name',
|
||||||
namePlaceholder: 'Enter workflow name',
|
namePlaceholder: 'Enter workflow name',
|
||||||
@@ -1441,13 +1444,16 @@ const viVN = {
|
|||||||
dragToAdd: 'Drag nodes to add to canvas',
|
dragToAdd: 'Drag nodes to add to canvas',
|
||||||
// Property panel
|
// Property panel
|
||||||
selectNodeOrEdge: 'Select a node or edge',
|
selectNodeOrEdge: 'Select a node or edge',
|
||||||
selectNodeOrEdgeHint: 'Click on a node or edge in the canvas to view and edit its properties',
|
selectNodeOrEdgeHint:
|
||||||
|
'Click on a node or edge in the canvas to view and edit its properties',
|
||||||
edgeProperties: 'Edge Properties',
|
edgeProperties: 'Edge Properties',
|
||||||
nodeProperties: 'Node Properties',
|
nodeProperties: 'Node Properties',
|
||||||
condition: 'Condition',
|
condition: 'Condition',
|
||||||
hasCondition: 'Set',
|
hasCondition: 'Set',
|
||||||
conditionPlaceholder: 'Enter condition expression, e.g. output.success == true',
|
conditionPlaceholder:
|
||||||
conditionHelp: 'When condition is empty, this edge will always be executed. Use {{variable}} to reference context variables.',
|
'Enter condition expression, e.g. output.success == true',
|
||||||
|
conditionHelp:
|
||||||
|
'When condition is empty, this edge will always be executed. Use {{variable}} to reference context variables.',
|
||||||
deleteEdge: 'Delete Edge',
|
deleteEdge: 'Delete Edge',
|
||||||
deleteEdgeConfirm: 'Confirm Delete Edge',
|
deleteEdgeConfirm: 'Confirm Delete Edge',
|
||||||
deleteEdgeConfirmDesc: 'This edge will be permanently removed.',
|
deleteEdgeConfirmDesc: 'This edge will be permanently removed.',
|
||||||
@@ -1468,7 +1474,8 @@ const viVN = {
|
|||||||
noConfigOptions: 'No configuration options for this node type',
|
noConfigOptions: 'No configuration options for this node type',
|
||||||
deleteNode: 'Delete Node',
|
deleteNode: 'Delete Node',
|
||||||
deleteNodeConfirm: 'Confirm Delete Node',
|
deleteNodeConfirm: 'Confirm Delete Node',
|
||||||
deleteNodeConfirmDesc: 'This node and all its connections will be permanently removed.',
|
deleteNodeConfirmDesc:
|
||||||
|
'This node and all its connections will be permanently removed.',
|
||||||
// Node inputs/outputs i18n (for port labels)
|
// Node inputs/outputs i18n (for port labels)
|
||||||
nodeInputs: {
|
nodeInputs: {
|
||||||
// Common inputs
|
// Common inputs
|
||||||
@@ -1531,7 +1538,8 @@ const viVN = {
|
|||||||
aiProcess: 'AI Processing',
|
aiProcess: 'AI Processing',
|
||||||
aiProcessDescription: 'Process messages using AI models',
|
aiProcessDescription: 'Process messages using AI models',
|
||||||
llmCall: 'LLM Call',
|
llmCall: 'LLM Call',
|
||||||
llmCallDescription: 'Invoke large language model for conversation or generation',
|
llmCallDescription:
|
||||||
|
'Invoke large language model for conversation or generation',
|
||||||
codeProcess: 'Code Processing',
|
codeProcess: 'Code Processing',
|
||||||
codeProcessDescription: 'Execute custom code',
|
codeProcessDescription: 'Execute custom code',
|
||||||
codeExecutor: 'Code Executor',
|
codeExecutor: 'Code Executor',
|
||||||
@@ -1543,13 +1551,17 @@ const viVN = {
|
|||||||
dataTransform: 'Data Transform',
|
dataTransform: 'Data Transform',
|
||||||
dataTransformDescription: 'Transform data format',
|
dataTransformDescription: 'Transform data format',
|
||||||
questionClassifier: 'Question Classifier',
|
questionClassifier: 'Question Classifier',
|
||||||
questionClassifierDescription: 'Classify user questions into predefined categories using LLM',
|
questionClassifierDescription:
|
||||||
|
'Classify user questions into predefined categories using LLM',
|
||||||
parameterExtractor: 'Parameter Extractor',
|
parameterExtractor: 'Parameter Extractor',
|
||||||
parameterExtractorDescription: 'Extract structured parameters from text using LLM',
|
parameterExtractorDescription:
|
||||||
|
'Extract structured parameters from text using LLM',
|
||||||
knowledgeRetrieval: 'Knowledge Retrieval',
|
knowledgeRetrieval: 'Knowledge Retrieval',
|
||||||
knowledgeRetrievalDescription: 'Retrieve relevant content from knowledge base',
|
knowledgeRetrievalDescription:
|
||||||
|
'Retrieve relevant content from knowledge base',
|
||||||
textTemplate: 'Text Template',
|
textTemplate: 'Text Template',
|
||||||
textTemplateDescription: 'Generate text using templates with variable interpolation',
|
textTemplateDescription:
|
||||||
|
'Generate text using templates with variable interpolation',
|
||||||
jsonTransform: 'JSON Transform',
|
jsonTransform: 'JSON Transform',
|
||||||
jsonTransformDescription: 'Transform JSON data using expressions',
|
jsonTransformDescription: 'Transform JSON data using expressions',
|
||||||
dataAggregator: 'Data Aggregator',
|
dataAggregator: 'Data Aggregator',
|
||||||
@@ -1577,13 +1589,15 @@ const viVN = {
|
|||||||
merge: 'Merge',
|
merge: 'Merge',
|
||||||
mergeDescription: 'Merge multiple branches',
|
mergeDescription: 'Merge multiple branches',
|
||||||
variableAggregator: 'Variable Aggregator',
|
variableAggregator: 'Variable Aggregator',
|
||||||
variableAggregatorDescription: 'Aggregate variable outputs from multiple branches',
|
variableAggregatorDescription:
|
||||||
|
'Aggregate variable outputs from multiple branches',
|
||||||
action: 'Actions',
|
action: 'Actions',
|
||||||
actionDescription: 'Action execution nodes',
|
actionDescription: 'Action execution nodes',
|
||||||
sendMessage: 'Send Message',
|
sendMessage: 'Send Message',
|
||||||
sendMessageDescription: 'Send message to platform',
|
sendMessageDescription: 'Send message to platform',
|
||||||
replyMessage: 'Reply Message',
|
replyMessage: 'Reply Message',
|
||||||
replyMessageDescription: 'Reply to the message that triggered the workflow',
|
replyMessageDescription:
|
||||||
|
'Reply to the message that triggered the workflow',
|
||||||
storeData: 'Store Data',
|
storeData: 'Store Data',
|
||||||
storeDataDescription: 'Store data to database',
|
storeDataDescription: 'Store data to database',
|
||||||
callPipeline: 'Call Pipeline',
|
callPipeline: 'Call Pipeline',
|
||||||
@@ -1591,7 +1605,8 @@ const viVN = {
|
|||||||
setVariable: 'Set Variable',
|
setVariable: 'Set Variable',
|
||||||
setVariableDescription: 'Set context variable',
|
setVariableDescription: 'Set context variable',
|
||||||
openingStatement: 'Opening Statement',
|
openingStatement: 'Opening Statement',
|
||||||
openingStatementDescription: 'Provide conversation opener and suggested questions',
|
openingStatementDescription:
|
||||||
|
'Provide conversation opener and suggested questions',
|
||||||
end: 'End',
|
end: 'End',
|
||||||
endDescription: 'Mark the end of workflow execution',
|
endDescription: 'Mark the end of workflow execution',
|
||||||
log: 'Log',
|
log: 'Log',
|
||||||
@@ -1637,7 +1652,8 @@ const viVN = {
|
|||||||
title: 'Version History',
|
title: 'Version History',
|
||||||
current: 'Current Version',
|
current: 'Current Version',
|
||||||
rollback: 'Rollback to this version',
|
rollback: 'Rollback to this version',
|
||||||
rollbackConfirm: 'Are you sure you want to rollback to this version? Current changes will be lost.',
|
rollbackConfirm:
|
||||||
|
'Are you sure you want to rollback to this version? Current changes will be lost.',
|
||||||
rollbackSuccess: 'Rollback successful',
|
rollbackSuccess: 'Rollback successful',
|
||||||
rollbackError: 'Failed to rollback: ',
|
rollbackError: 'Failed to rollback: ',
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1276,7 +1276,8 @@ const zhHant = {
|
|||||||
},
|
},
|
||||||
workflows: {
|
workflows: {
|
||||||
title: 'Workflows',
|
title: 'Workflows',
|
||||||
description: 'Create and manage visual workflows for complex message processing logic',
|
description:
|
||||||
|
'Create and manage visual workflows for complex message processing logic',
|
||||||
createWorkflow: 'Create Workflow',
|
createWorkflow: 'Create Workflow',
|
||||||
selectFromSidebar: 'Select a workflow from the sidebar',
|
selectFromSidebar: 'Select a workflow from the sidebar',
|
||||||
editWorkflow: 'Edit Workflow',
|
editWorkflow: 'Edit Workflow',
|
||||||
@@ -1316,10 +1317,12 @@ const zhHant = {
|
|||||||
dangerZoneDesc: 'Irreversible operations',
|
dangerZoneDesc: 'Irreversible operations',
|
||||||
dangerZoneDescription: 'Irreversible operations',
|
dangerZoneDescription: 'Irreversible operations',
|
||||||
deleteWorkflowAction: 'Delete this workflow',
|
deleteWorkflowAction: 'Delete this workflow',
|
||||||
deleteWorkflowHint: 'Once deleted, all associated configurations will be permanently removed and cannot be recovered.',
|
deleteWorkflowHint:
|
||||||
|
'Once deleted, all associated configurations will be permanently removed and cannot be recovered.',
|
||||||
deleteWorkflow: 'Delete Workflow',
|
deleteWorkflow: 'Delete Workflow',
|
||||||
deleteConfirm: 'Confirm Delete',
|
deleteConfirm: 'Confirm Delete',
|
||||||
deleteConfirmDesc: 'Are you sure you want to delete workflow "{{name}}"? This action cannot be undone.',
|
deleteConfirmDesc:
|
||||||
|
'Are you sure you want to delete workflow "{{name}}"? This action cannot be undone.',
|
||||||
// Form component
|
// Form component
|
||||||
name: 'Name',
|
name: 'Name',
|
||||||
namePlaceholder: 'Enter workflow name',
|
namePlaceholder: 'Enter workflow name',
|
||||||
@@ -1382,13 +1385,16 @@ const zhHant = {
|
|||||||
dragToAdd: 'Drag nodes to add to canvas',
|
dragToAdd: 'Drag nodes to add to canvas',
|
||||||
// Property panel
|
// Property panel
|
||||||
selectNodeOrEdge: 'Select a node or edge',
|
selectNodeOrEdge: 'Select a node or edge',
|
||||||
selectNodeOrEdgeHint: 'Click on a node or edge in the canvas to view and edit its properties',
|
selectNodeOrEdgeHint:
|
||||||
|
'Click on a node or edge in the canvas to view and edit its properties',
|
||||||
edgeProperties: 'Edge Properties',
|
edgeProperties: 'Edge Properties',
|
||||||
nodeProperties: 'Node Properties',
|
nodeProperties: 'Node Properties',
|
||||||
condition: 'Condition',
|
condition: 'Condition',
|
||||||
hasCondition: 'Set',
|
hasCondition: 'Set',
|
||||||
conditionPlaceholder: 'Enter condition expression, e.g. output.success == true',
|
conditionPlaceholder:
|
||||||
conditionHelp: 'When condition is empty, this edge will always be executed. Use {{variable}} to reference context variables.',
|
'Enter condition expression, e.g. output.success == true',
|
||||||
|
conditionHelp:
|
||||||
|
'When condition is empty, this edge will always be executed. Use {{variable}} to reference context variables.',
|
||||||
deleteEdge: 'Delete Edge',
|
deleteEdge: 'Delete Edge',
|
||||||
deleteEdgeConfirm: 'Confirm Delete Edge',
|
deleteEdgeConfirm: 'Confirm Delete Edge',
|
||||||
deleteEdgeConfirmDesc: 'This edge will be permanently removed.',
|
deleteEdgeConfirmDesc: 'This edge will be permanently removed.',
|
||||||
@@ -1409,7 +1415,8 @@ const zhHant = {
|
|||||||
noConfigOptions: 'No configuration options for this node type',
|
noConfigOptions: 'No configuration options for this node type',
|
||||||
deleteNode: 'Delete Node',
|
deleteNode: 'Delete Node',
|
||||||
deleteNodeConfirm: 'Confirm Delete Node',
|
deleteNodeConfirm: 'Confirm Delete Node',
|
||||||
deleteNodeConfirmDesc: 'This node and all its connections will be permanently removed.',
|
deleteNodeConfirmDesc:
|
||||||
|
'This node and all its connections will be permanently removed.',
|
||||||
// Node inputs/outputs i18n (for port labels)
|
// Node inputs/outputs i18n (for port labels)
|
||||||
nodeInputs: {
|
nodeInputs: {
|
||||||
// Common inputs
|
// Common inputs
|
||||||
@@ -1472,7 +1479,8 @@ const zhHant = {
|
|||||||
aiProcess: 'AI Processing',
|
aiProcess: 'AI Processing',
|
||||||
aiProcessDescription: 'Process messages using AI models',
|
aiProcessDescription: 'Process messages using AI models',
|
||||||
llmCall: 'LLM Call',
|
llmCall: 'LLM Call',
|
||||||
llmCallDescription: 'Invoke large language model for conversation or generation',
|
llmCallDescription:
|
||||||
|
'Invoke large language model for conversation or generation',
|
||||||
codeProcess: 'Code Processing',
|
codeProcess: 'Code Processing',
|
||||||
codeProcessDescription: 'Execute custom code',
|
codeProcessDescription: 'Execute custom code',
|
||||||
codeExecutor: 'Code Executor',
|
codeExecutor: 'Code Executor',
|
||||||
@@ -1484,13 +1492,17 @@ const zhHant = {
|
|||||||
dataTransform: 'Data Transform',
|
dataTransform: 'Data Transform',
|
||||||
dataTransformDescription: 'Transform data format',
|
dataTransformDescription: 'Transform data format',
|
||||||
questionClassifier: 'Question Classifier',
|
questionClassifier: 'Question Classifier',
|
||||||
questionClassifierDescription: 'Classify user questions into predefined categories using LLM',
|
questionClassifierDescription:
|
||||||
|
'Classify user questions into predefined categories using LLM',
|
||||||
parameterExtractor: 'Parameter Extractor',
|
parameterExtractor: 'Parameter Extractor',
|
||||||
parameterExtractorDescription: 'Extract structured parameters from text using LLM',
|
parameterExtractorDescription:
|
||||||
|
'Extract structured parameters from text using LLM',
|
||||||
knowledgeRetrieval: 'Knowledge Retrieval',
|
knowledgeRetrieval: 'Knowledge Retrieval',
|
||||||
knowledgeRetrievalDescription: 'Retrieve relevant content from knowledge base',
|
knowledgeRetrievalDescription:
|
||||||
|
'Retrieve relevant content from knowledge base',
|
||||||
textTemplate: 'Text Template',
|
textTemplate: 'Text Template',
|
||||||
textTemplateDescription: 'Generate text using templates with variable interpolation',
|
textTemplateDescription:
|
||||||
|
'Generate text using templates with variable interpolation',
|
||||||
jsonTransform: 'JSON Transform',
|
jsonTransform: 'JSON Transform',
|
||||||
jsonTransformDescription: 'Transform JSON data using expressions',
|
jsonTransformDescription: 'Transform JSON data using expressions',
|
||||||
dataAggregator: 'Data Aggregator',
|
dataAggregator: 'Data Aggregator',
|
||||||
@@ -1518,13 +1530,15 @@ const zhHant = {
|
|||||||
merge: 'Merge',
|
merge: 'Merge',
|
||||||
mergeDescription: 'Merge multiple branches',
|
mergeDescription: 'Merge multiple branches',
|
||||||
variableAggregator: 'Variable Aggregator',
|
variableAggregator: 'Variable Aggregator',
|
||||||
variableAggregatorDescription: 'Aggregate variable outputs from multiple branches',
|
variableAggregatorDescription:
|
||||||
|
'Aggregate variable outputs from multiple branches',
|
||||||
action: 'Actions',
|
action: 'Actions',
|
||||||
actionDescription: 'Action execution nodes',
|
actionDescription: 'Action execution nodes',
|
||||||
sendMessage: 'Send Message',
|
sendMessage: 'Send Message',
|
||||||
sendMessageDescription: 'Send message to platform',
|
sendMessageDescription: 'Send message to platform',
|
||||||
replyMessage: 'Reply Message',
|
replyMessage: 'Reply Message',
|
||||||
replyMessageDescription: 'Reply to the message that triggered the workflow',
|
replyMessageDescription:
|
||||||
|
'Reply to the message that triggered the workflow',
|
||||||
storeData: 'Store Data',
|
storeData: 'Store Data',
|
||||||
storeDataDescription: 'Store data to database',
|
storeDataDescription: 'Store data to database',
|
||||||
callPipeline: 'Call Pipeline',
|
callPipeline: 'Call Pipeline',
|
||||||
@@ -1532,7 +1546,8 @@ const zhHant = {
|
|||||||
setVariable: 'Set Variable',
|
setVariable: 'Set Variable',
|
||||||
setVariableDescription: 'Set context variable',
|
setVariableDescription: 'Set context variable',
|
||||||
openingStatement: 'Opening Statement',
|
openingStatement: 'Opening Statement',
|
||||||
openingStatementDescription: 'Provide conversation opener and suggested questions',
|
openingStatementDescription:
|
||||||
|
'Provide conversation opener and suggested questions',
|
||||||
end: 'End',
|
end: 'End',
|
||||||
endDescription: 'Mark the end of workflow execution',
|
endDescription: 'Mark the end of workflow execution',
|
||||||
log: 'Log',
|
log: 'Log',
|
||||||
@@ -1578,7 +1593,8 @@ const zhHant = {
|
|||||||
title: 'Version History',
|
title: 'Version History',
|
||||||
current: 'Current Version',
|
current: 'Current Version',
|
||||||
rollback: 'Rollback to this version',
|
rollback: 'Rollback to this version',
|
||||||
rollbackConfirm: 'Are you sure you want to rollback to this version? Current changes will be lost.',
|
rollbackConfirm:
|
||||||
|
'Are you sure you want to rollback to this version? Current changes will be lost.',
|
||||||
rollbackSuccess: 'Rollback successful',
|
rollbackSuccess: 'Rollback successful',
|
||||||
rollbackError: 'Failed to rollback: ',
|
rollbackError: 'Failed to rollback: ',
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user