= {
+ trigger: { event_type: 'text_message', callback_url: '' },
+ llm: { system_prompt: '', model: 'gpt-4o-mini', temperature: 0.7, agent_id: '', max_tokens: 2000, context_length: 5, memory_mode: 'short_term', stream: true, tool_call: false },
+ tool: { tool_type: '', tool_name: '', param_mapping: '{}', timeout: 30, retry_count: 0, error_handling: 'throw' },
+ mcp: { mcp_server: '', tool_name: '', input_params: '{}', timeout: 30, response_parser: 'json', error_handling: 'throw' },
+ wecom_notify: { message_template: '', target: '', message_type: 'text', async_send: false, error_handling: 'throw' },
+ condition: { condition_type: 'expression', condition: '', true_label: '是', false_label: '否', short_circuit: true, default_branch: 'false' },
+ rag: { knowledge_base: '', top_k: 5, search_mode: 'hybrid', similarity_threshold: 0.7, result_sort: 'similarity', include_metadata: true },
+ output: { format: 'text', output_template: '', indent: 2, encoding: 'utf-8', truncate: false, max_length: 2000 },
+ }
+ return defaults[type] || {}
+}
+
function onNodeClick({ node }: any) {
selectedNodeId.value = node.id
const d = node.data || {}
- selectedNodeData.value = { label: d.label || '', typeDesc: d.typeDesc || '', ...(d.config || {}) }
+ selectedNodeData.value = { label: d.label || '', typeDesc: d.typeDesc || '', config: { ...(d.config || {}) } }
}
function onPaneClick() { selectedNodeId.value = '' }
@@ -279,38 +230,18 @@ function onConfigLabelChange() {
}
}
-function onAgentSelect(val: string) {
- if (!val) return
- const agent = agentList.value.find(a => a.id === val)
- if (agent) {
- selectedNodeData.value.system_prompt = agent.system_prompt || ''
- selectedNodeData.value.model = agent.model || 'gpt-4o-mini'
- selectedNodeData.value.temperature = agent.temperature ?? 0.7
- }
- onConfigChange()
-}
-
function onConfigChange() {
const idx = nodes.value.findIndex((n: any) => n.id === selectedNodeId.value)
if (idx === -1) return
const found = nodes.value[idx]
- const nodeType = found.data.type
- const cfg: any = {}
- if (nodeType === 'trigger') cfg.event_type = selectedNodeData.value.event_type
- else if (nodeType === 'llm') { cfg.system_prompt = selectedNodeData.value.system_prompt; cfg.model = selectedNodeData.value.model; cfg.temperature = selectedNodeData.value.temperature; cfg.agent_id = selectedNodeData.value.agent_id }
- else if (nodeType === 'tool') cfg.tool_name = selectedNodeData.value.tool_name
- else if (nodeType === 'mcp') { cfg.mcp_server = selectedNodeData.value.mcp_server; cfg.tool_name = selectedNodeData.value.tool_name }
- else if (nodeType === 'wecom_notify') { cfg.message_template = selectedNodeData.value.message_template; cfg.target = selectedNodeData.value.target }
- else if (nodeType === 'condition') cfg.condition = selectedNodeData.value.condition
- else if (nodeType === 'rag') { cfg.knowledge_base = selectedNodeData.value.knowledge_base; cfg.top_k = selectedNodeData.value.top_k }
- else if (nodeType === 'output') cfg.format = selectedNodeData.value.format
const updated = { ...found }
- updated.data = { ...found.data, config: cfg }
+ updated.data = { ...found.data, config: { ...selectedNodeData.value.config } }
nodes.value[idx] = updated
}
function removeNode(id: string) {
- canvasRef.value?.removeNodes?.([id])
+ nodes.value = nodes.value.filter((n: any) => n.id !== id)
+ edges.value = edges.value.filter((e: any) => e.source !== id && e.target !== id)
if (selectedNodeId.value === id) selectedNodeId.value = ''
}
@@ -344,7 +275,7 @@ async function loadFlow() {
const source = e.source || e.from
const target = e.target || e.to
const cond = e.condition || e.sourceHandle
- loadedEdges.push({ id: e.id || `edge_${source}_${target}`, source, target, sourceHandle: cond || 'source', type: 'smoothstep', animated: true, style: cond === 'false' ? { stroke: '#F56C6C', strokeWidth: 2 } : { stroke: '#409EFF', strokeWidth: 2 } })
+ loadedEdges.push({ id: e.id || `edge_${source}_${target}`, source, target, sourceHandle: cond || 'source', type: 'smoothstep', animated: true, markerEnd: true, style: cond === 'false' ? { stroke: '#E53935', strokeWidth: 3 } : { stroke: '#1976D2', strokeWidth: 3 } })
}
nodes.value = loadedNodes
edges.value = loadedEdges
@@ -367,8 +298,9 @@ async function saveFlow() {
saving.value = true
try {
const { flowApi } = await import('@/api')
- const serializedNodes = nodes.value.map((n: any) => ({ id: n.id, type: n.data?.type || n.type, label: n.data?.label || n.id, config: n.data?.config || {}, position: n.position }))
- const serializedEdges = edges.value.map((e: any) => ({ source: e.source, target: e.target, sourceHandle: e.sourceHandle || 'source', condition: e.sourceHandle === 'false' ? 'false' : (e.sourceHandle === 'true' ? 'true' : undefined) }))
+ const snapshot = canvasRef.value?.getSnapshot() || { nodes: [], edges: [] }
+ const serializedNodes = snapshot.nodes.map((n: any) => ({ id: n.id, type: n.data?.type || n.type, label: n.data?.label || n.id, config: n.data?.config || {}, position: n.position }))
+ const serializedEdges = snapshot.edges.map((e: any) => ({ source: e.source, target: e.target, sourceHandle: e.sourceHandle || 'source', condition: e.sourceHandle === 'false' ? 'false' : (e.sourceHandle === 'true' ? 'true' : undefined) }))
const payload = { name: flowName.value, description: flowDesc.value, nodes: serializedNodes, edges: serializedEdges, trigger: {} }
if (isEdit.value) { await flowApi.updateFlow(flowId.value, payload); ElMessage.success('保存成功') }
else { const res: any = await flowApi.createFlow(payload); const data = res?.data || res || {}; if (data.id) { router.replace(`/admin/flow/editor/${data.id}`); ElMessage.success('创建成功') } }
diff --git a/frontend/src/views/flow/node-configs/ConditionConfig.vue b/frontend/src/views/flow/node-configs/ConditionConfig.vue
new file mode 100644
index 0000000..47a53bf
--- /dev/null
+++ b/frontend/src/views/flow/node-configs/ConditionConfig.vue
@@ -0,0 +1,64 @@
+
+
+
条件配置
+
+
+
+
+
+
+
+
+
+
+
+ 支持变量: {{input_data}}, {{status}}, {{result}}
+
+
+
分支配置
+
+
+
+
+
+
+
+
+
+
高级选项
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/frontend/src/views/flow/node-configs/LlmConfig.vue b/frontend/src/views/flow/node-configs/LlmConfig.vue
new file mode 100644
index 0000000..2e76b0c
--- /dev/null
+++ b/frontend/src/views/flow/node-configs/LlmConfig.vue
@@ -0,0 +1,92 @@
+
+
+ Agent配置
+
+
+
+
+
+
+
+ 模型配置
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 记忆配置
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 高级选项
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/frontend/src/views/flow/node-configs/McpConfig.vue b/frontend/src/views/flow/node-configs/McpConfig.vue
new file mode 100644
index 0000000..e1f3668
--- /dev/null
+++ b/frontend/src/views/flow/node-configs/McpConfig.vue
@@ -0,0 +1,63 @@
+
+
+ MCP服务配置
+
+
+
+
+
+
+
+
+
+
+
+
+ 请求参数
+
+
+
+
+
+
+
+
+
+ 响应处理
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/frontend/src/views/flow/node-configs/OutputConfig.vue b/frontend/src/views/flow/node-configs/OutputConfig.vue
new file mode 100644
index 0000000..2f505e1
--- /dev/null
+++ b/frontend/src/views/flow/node-configs/OutputConfig.vue
@@ -0,0 +1,56 @@
+
+
+ 输出配置
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 格式化选项
+
+
+
+
+
+
+
+
+
+
+
+
+ 输出行为
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/frontend/src/views/flow/node-configs/RagConfig.vue b/frontend/src/views/flow/node-configs/RagConfig.vue
new file mode 100644
index 0000000..d4ba4a9
--- /dev/null
+++ b/frontend/src/views/flow/node-configs/RagConfig.vue
@@ -0,0 +1,56 @@
+
+
+ 知识库配置
+
+
+
+
+
+
+
+
+
+ 检索配置
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 结果处理
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/frontend/src/views/flow/node-configs/ToolConfig.vue b/frontend/src/views/flow/node-configs/ToolConfig.vue
new file mode 100644
index 0000000..4ffc76b
--- /dev/null
+++ b/frontend/src/views/flow/node-configs/ToolConfig.vue
@@ -0,0 +1,89 @@
+
+
+ 工具选择
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 参数配置
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/frontend/src/views/flow/node-configs/TriggerConfig.vue b/frontend/src/views/flow/node-configs/TriggerConfig.vue
new file mode 100644
index 0000000..8bbed60
--- /dev/null
+++ b/frontend/src/views/flow/node-configs/TriggerConfig.vue
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/frontend/src/views/flow/node-configs/WecomNotifyConfig.vue b/frontend/src/views/flow/node-configs/WecomNotifyConfig.vue
new file mode 100644
index 0000000..1eb0488
--- /dev/null
+++ b/frontend/src/views/flow/node-configs/WecomNotifyConfig.vue
@@ -0,0 +1,49 @@
+
+
+ 消息配置
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 发送选项
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts
index 9d00db7..a3a43aa 100644
--- a/frontend/vite.config.ts
+++ b/frontend/vite.config.ts
@@ -10,10 +10,11 @@ export default defineConfig({
},
},
server: {
- port: 3000,
+ host: '0.0.0.0',
+ port: 5173,
proxy: {
'/api': {
- target: 'http://localhost:8000',
+ target: 'http://backend:8000',
changeOrigin: true,
},
},
diff --git a/hg-agents.zip b/hg-agents.zip
deleted file mode 100644
index a01520c..0000000
Binary files a/hg-agents.zip and /dev/null differ
diff --git a/nginx/nginx.dev.conf b/nginx/nginx.dev.conf
new file mode 100644
index 0000000..fda2f4f
--- /dev/null
+++ b/nginx/nginx.dev.conf
@@ -0,0 +1,52 @@
+events {
+ worker_connections 1024;
+}
+
+http {
+ include /etc/nginx/mime.types;
+ default_type application/octet-stream;
+
+ upstream backend_api {
+ server backend:8000;
+ }
+
+ upstream frontend_app {
+ server frontend:80;
+ }
+
+ server {
+ listen 80;
+
+ location / {
+ proxy_pass http://frontend_app;
+ proxy_set_header Host $host;
+ proxy_set_header X-Real-IP $remote_addr;
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+
+ # WebSocket support for Vite HMR
+ proxy_http_version 1.1;
+ proxy_set_header Upgrade $http_upgrade;
+ proxy_set_header Connection "upgrade";
+ proxy_read_timeout 86400s;
+ proxy_send_timeout 86400s;
+ }
+
+ location /api/ {
+ proxy_pass http://backend_api/api/;
+ proxy_set_header Host $host;
+ proxy_set_header X-Real-IP $remote_addr;
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+
+ proxy_http_version 1.1;
+ proxy_set_header Upgrade $http_upgrade;
+ proxy_set_header Connection "upgrade";
+ proxy_read_timeout 86400s;
+ }
+
+ location /wecom/ {
+ proxy_pass http://backend_api/wecom/;
+ proxy_set_header Host $host;
+ proxy_set_header X-Real-IP $remote_addr;
+ }
+ }
+}
diff --git a/nginx/nginx.prod.conf b/nginx/nginx.prod.conf
new file mode 100644
index 0000000..a846cc2
--- /dev/null
+++ b/nginx/nginx.prod.conf
@@ -0,0 +1,48 @@
+events {
+ worker_connections 1024;
+}
+
+http {
+ include /etc/nginx/mime.types;
+ default_type application/octet-stream;
+
+ upstream backend_api {
+ server backend:8000;
+ }
+
+ server {
+ listen 80;
+
+ root /usr/share/nginx/html;
+ index index.html;
+
+ # SPA fallback - try file first, then fallback to index.html
+ location / {
+ try_files $uri $uri/ /index.html;
+ }
+
+ location /api/ {
+ proxy_pass http://backend_api/api/;
+ proxy_set_header Host $host;
+ proxy_set_header X-Real-IP $remote_addr;
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+
+ proxy_http_version 1.1;
+ proxy_set_header Upgrade $http_upgrade;
+ proxy_set_header Connection "upgrade";
+ proxy_read_timeout 86400s;
+ }
+
+ location /wecom/ {
+ proxy_pass http://backend_api/wecom/;
+ proxy_set_header Host $host;
+ proxy_set_header X-Real-IP $remote_addr;
+ }
+
+ # Cache static assets
+ location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
+ expires 1y;
+ add_header Cache-Control "public, immutable";
+ }
+ }
+}