Browse Source

画布节点正常版本

master
MSI-7950X\刘泽明 6 days ago
parent
commit
47cc2075f1
  1. 149
      frontend/src/views/flow/FlowCanvas.vue
  2. 13
      frontend/src/views/flow/FlowEditor.vue
  3. 62
      frontend/src/views/flow/FlowNode.vue
  4. 32
      frontend/src/views/flow/node-configs/ConditionConfig.vue
  5. 51
      frontend/src/views/flow/node-configs/LlmConfig.vue
  6. 33
      frontend/src/views/flow/node-configs/McpConfig.vue
  7. 34
      frontend/src/views/flow/node-configs/OutputConfig.vue
  8. 34
      frontend/src/views/flow/node-configs/RagConfig.vue
  9. 48
      frontend/src/views/flow/node-configs/ToolConfig.vue
  10. 19
      frontend/src/views/flow/node-configs/TriggerConfig.vue
  11. 31
      frontend/src/views/flow/node-configs/WecomNotifyConfig.vue

149
frontend/src/views/flow/FlowCanvas.vue

@ -1,13 +1,15 @@
<template> <template>
<div style="width:100%;height:100%">
<VueFlow <VueFlow
:nodes="localNodes" v-if="ready"
:edges="localEdges" :nodes="props.nodes"
:edges="props.edges"
@nodes-change="onNodesChange" @nodes-change="onNodesChange"
@edges-change="onEdgesChange" @edges-change="onEdgesChange"
@connect="handleConnect" @node-click="(e: any) => $emit('node-click', e)"
@node-click="$emit('node-click', $event)"
@pane-click="onPaneClickLocal" @pane-click="onPaneClickLocal"
@drop="$emit('drop', $event)" @connect="handleConnect"
@drop="(e: any) => $emit('drop', e)"
@dragover.prevent @dragover.prevent
@edge-click="onEdgeClick" @edge-click="onEdgeClick"
@contextmenu="onContextMenu" @contextmenu="onContextMenu"
@ -17,7 +19,7 @@
<MiniMap <MiniMap
position="bottom-left" position="bottom-left"
:node-stroke-color="'#409EFF'" :node-stroke-color="'#409EFF'"
:node-color="nodeColor" :node-color="props.nodeColor"
pannable pannable
zoomable zoomable
/> />
@ -31,12 +33,13 @@
/> />
</template> </template>
</VueFlow> </VueFlow>
</div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, watch, nextTick } from 'vue' import { ref, watch } from 'vue'
import { MarkerType } from '@vue-flow/core' import { MarkerType } from '@vue-flow/core'
import { VueFlow, useVueFlow } from '@vue-flow/core' import { VueFlow } from '@vue-flow/core'
import { Background } from '@vue-flow/background' import { Background } from '@vue-flow/background'
import { Controls } from '@vue-flow/controls' import { Controls } from '@vue-flow/controls'
import { MiniMap } from '@vue-flow/minimap' import { MiniMap } from '@vue-flow/minimap'
@ -57,113 +60,43 @@ const emit = defineEmits([
'update:nodes', 'update:edges', 'update:nodes', 'update:edges',
]) ])
const localNodes = ref<any[]>([]) const ready = ref(true)
const localEdges = ref<any[]>([]) const selectedEdgeId = ref<string | null>(null)
let skipNextNodesChange = false
let isExternalUpdate = false
let isInitialized = false
let pendingAdditions = new Map<string, any>()
watch(() => props.nodes, async (newNodes) => {
if (isExternalUpdate) {
isExternalUpdate = false
return
}
if (!newNodes || newNodes.length === 0) return
const existingNodeMap = new Map(localNodes.value.map(n => [n.id, { position: n.position, data: n.data }]))
const mergedNodes = newNodes.map(newNode => {
const pending = pendingAdditions.get(newNode.id)
if (pending) {
pendingAdditions.delete(newNode.id)
return {
...newNode,
position: { ...pending.position },
data: { ...newNode.data },
}
}
const existing = existingNodeMap.get(newNode.id)
if (existing) {
return {
...newNode,
position: { ...existing.position },
data: { ...newNode.data },
}
}
return { ...newNode, data: { ...newNode.data } } watch(() => props.nodes, () => {
skipNextNodesChange = true
}) })
localNodes.value = mergedNodes
if (!isInitialized) {
isInitialized = true
}
}, { immediate: true })
watch(() => props.edges, (newEdges) => {
if (isExternalUpdate) {
isExternalUpdate = false
return
}
if (!newEdges || newEdges.length === 0) return
localEdges.value = JSON.parse(JSON.stringify(newEdges))
}, { immediate: true })
function onNodesChange(changes: any[]) { function onNodesChange(changes: any[]) {
if (isExternalUpdate) { if (skipNextNodesChange) {
isExternalUpdate = false skipNextNodesChange = false
return return
} }
const positionChanges = changes.filter((c: any) => c.type === 'position') const positionChanges = changes.filter((c: any) => c.type === 'position')
if (positionChanges.length > 0 && localNodes.value.length > 0) { if (positionChanges.length === 0) return
const updatedNodes = localNodes.value.map((node: any) => {
const updatedNodes = props.nodes.map((node: any) => {
const change = positionChanges.find((c: any) => c.id === node.id) const change = positionChanges.find((c: any) => c.id === node.id)
if (change) { if (change && change.position) {
return { return { ...node, position: { x: change.position.x, y: change.position.y } }
...node,
position: {
x: change.position?.x ?? node.position?.x,
y: change.position?.y ?? node.position?.y,
},
}
} }
return node return node
}) })
localNodes.value = updatedNodes emit('update:nodes', updatedNodes)
}
} }
function onEdgesChange(changes: any[]) { function onEdgesChange(changes: any[]) {
if (isExternalUpdate) {
isExternalUpdate = false
return
}
const removeChanges = changes.filter((c: any) => c.type === 'remove') const removeChanges = changes.filter((c: any) => c.type === 'remove')
if (removeChanges.length > 0) { if (removeChanges.length > 0) {
const removeIds = new Set(removeChanges.map((c: any) => c.id)) const removeIds = new Set(removeChanges.map((c: any) => c.id))
localEdges.value = localEdges.value.filter((e: any) => !removeIds.has(e.id)) const updatedEdges = props.edges.filter((e: any) => !removeIds.has(e.id))
} emit('update:edges', updatedEdges)
const selectChanges = changes.filter((c: any) => c.type === 'select')
if (selectChanges.length > 0) {
const selected = selectChanges.find((c: any) => c.selected)
selectedEdgeId.value = selected ? selected.id : null
} }
} }
const store = useVueFlow()
const selectedEdgeId = ref<string | null>(null)
function onEdgeClick({ edge }: any) { function onEdgeClick({ edge }: any) {
selectedEdgeId.value = edge.id selectedEdgeId.value = edge.id
} }
@ -180,7 +113,8 @@ function onContextMenu(event: MouseEvent) {
event.preventDefault() event.preventDefault()
const edgeId = edgeEl.getAttribute('data-id') const edgeId = edgeEl.getAttribute('data-id')
if (edgeId) { if (edgeId) {
localEdges.value = localEdges.value.filter((e: any) => e.id !== edgeId) const updatedEdges = props.edges.filter((e: any) => e.id !== edgeId)
emit('update:edges', updatedEdges)
selectedEdgeId.value = null selectedEdgeId.value = null
} }
return return
@ -189,7 +123,7 @@ function onContextMenu(event: MouseEvent) {
function handleConnect(connection: any) { function handleConnect(connection: any) {
const sourceHandle = connection.sourceHandle || 'source' const sourceHandle = connection.sourceHandle || 'source'
const edge: any = { const newEdge: any = {
id: `edge_${connection.source}_${connection.target}_${sourceHandle}_${Date.now()}`, id: `edge_${connection.source}_${connection.target}_${sourceHandle}_${Date.now()}`,
source: connection.source, source: connection.source,
target: connection.target, target: connection.target,
@ -202,34 +136,17 @@ function handleConnect(connection: any) {
? { stroke: '#E53935', strokeWidth: 3 } ? { stroke: '#E53935', strokeWidth: 3 }
: { stroke: '#1976D2', strokeWidth: 3 }, : { stroke: '#1976D2', strokeWidth: 3 },
} }
localEdges.value.push(edge) emit('update:edges', [...props.edges, newEdge])
emit('connect', connection) emit('connect', connection)
} }
function getSnapshot() { function getSnapshot() {
return { nodes: JSON.parse(JSON.stringify(localNodes.value)), edges: JSON.parse(JSON.stringify(localEdges.value)) } return { nodes: JSON.parse(JSON.stringify(props.nodes)), edges: JSON.parse(JSON.stringify(props.edges)) }
} }
defineExpose({ defineExpose({
undo: () => (store as any).undo?.(), get canUndo() { return false },
redo: () => (store as any).redo?.(), get canRedo() { return false },
get canUndo() { return !!(store as any).canUndo },
get canRedo() { return !!(store as any).canRedo },
getSnapshot, getSnapshot,
addNodes: (newNodes: any[]) => {
newNodes.forEach(n => {
pendingAdditions.set(n.id, { position: { ...n.position } })
})
localNodes.value = [...localNodes.value, ...newNodes]
nextTick(() => {
pendingAdditions.clear()
})
},
removeNodes: (ids: string[]) => {
localNodes.value = localNodes.value.filter((n: any) => !ids.includes(n.id))
localEdges.value = localEdges.value.filter((e: any) => !ids.includes(e.source) && !ids.includes(e.target))
},
}) })
</script> </script>

13
frontend/src/views/flow/FlowEditor.vue

@ -10,8 +10,6 @@
<el-input v-model="flowDesc" placeholder="描述" style="width: 300px; margin-left: 12px" /> <el-input v-model="flowDesc" placeholder="描述" style="width: 300px; margin-left: 12px" />
<el-button type="primary" @click="saveFlow" :loading="saving">保存</el-button> <el-button type="primary" @click="saveFlow" :loading="saving">保存</el-button>
<el-button @click="testFlow">验证</el-button> <el-button @click="testFlow">验证</el-button>
<el-button @click="undo" :disabled="!canUndo">撤销</el-button>
<el-button @click="redo" :disabled="!canRedo">重做</el-button>
<el-button v-if="isEdit" type="success" @click="publishFlow">上架到企微</el-button> <el-button v-if="isEdit" type="success" @click="publishFlow">上架到企微</el-button>
</div> </div>
</el-card> </el-card>
@ -56,6 +54,8 @@
@pane-click="onPaneClick" @pane-click="onPaneClick"
@drop="onDrop" @drop="onDrop"
@node-delete="removeNode" @node-delete="removeNode"
@update:nodes="(v: any) => nodes = v"
@update:edges="(v: any) => edges = v"
/> />
</div> </div>
@ -160,11 +160,6 @@ const colorMap: Record<string, string> = {
output: '#722ed1', output: '#722ed1',
} }
function undo() { canvasRef.value?.undo() }
function redo() { canvasRef.value?.redo() }
const canUndo = computed(() => canvasRef.value?.canUndo ?? false)
const canRedo = computed(() => canvasRef.value?.canRedo ?? false)
const selectedNode = computed(() => nodes.value.find((n: any) => n.id === selectedNodeId.value) || null) const selectedNode = computed(() => nodes.value.find((n: any) => n.id === selectedNodeId.value) || null)
function getMiniMapColor(node: any) { function getMiniMapColor(node: any) {
@ -183,7 +178,7 @@ function onDrop(event: DragEvent) {
if (!dataStr) return if (!dataStr) return
const nodeData = JSON.parse(dataStr) const nodeData = JSON.parse(dataStr)
const id = `node_${++nodeCounter}` const id = `node_${++nodeCounter}`
canvasRef.value?.addNodes?.([{ nodes.value.push({
id, id,
type: 'custom', type: 'custom',
position: { x: 100 + nodeCounter * 50, y: 100 + nodeCounter * 30 }, position: { x: 100 + nodeCounter * 50, y: 100 + nodeCounter * 30 },
@ -196,7 +191,7 @@ function onDrop(event: DragEvent) {
}, },
draggable: true, draggable: true,
connectable: true, connectable: true,
}]) })
} }
function getDefaultConfig(type: string) { function getDefaultConfig(type: string) {

62
frontend/src/views/flow/FlowNode.vue

@ -5,6 +5,7 @@
</div> </div>
<div class="node-body"> <div class="node-body">
<p class="node-type-label">{{ data?.typeDesc || '' }}</p> <p class="node-type-label">{{ data?.typeDesc || '' }}</p>
<p v-if="configSummary" class="node-config-summary">{{ configSummary }}</p>
</div> </div>
<button class="node-delete-btn" @click="$emit('delete')" title="删除">×</button> <button class="node-delete-btn" @click="$emit('delete')" title="删除">×</button>
@ -24,13 +25,18 @@
<span class="handle-label-false"></span> <span class="handle-label-false"></span>
</Handle> </Handle>
</template> </template>
<template v-if="data?.type === 'wecom_notify' || data?.type === 'output'">
<Handle type="source" :position="Position.Right" id="source" class="node-handle" />
</template>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { computed } from 'vue'
import { Handle, Position } from '@vue-flow/core' import { Handle, Position } from '@vue-flow/core'
defineProps<{ const props = defineProps<{
id: string id: string
data?: any data?: any
selected?: boolean selected?: boolean
@ -39,6 +45,49 @@ defineProps<{
defineEmits<{ defineEmits<{
delete: [] delete: []
}>() }>()
const config = computed(() => props.data?.config || {})
const configSummary = computed(() => {
const cfg = config.value
switch (props.data?.type) {
case 'trigger':
return cfg.event_type ? `事件: ${eventLabel(cfg.event_type)}` : ''
case 'llm':
return cfg.model ? `${cfg.model} · T=${cfg.temperature ?? 0.7}` : ''
case 'tool':
return cfg.tool_name ? `工具: ${cfg.tool_name}` : (cfg.tool_type ? `类型: ${cfg.tool_type}` : '')
case 'mcp':
return cfg.mcp_server ? `MCP: ${cfg.mcp_server}` : ''
case 'wecom_notify':
return cfg.message_type ? `消息: ${cfg.message_type}` : ''
case 'condition':
return cfg.condition ? `条件: ${truncate(cfg.condition, 20)}` : ''
case 'rag':
return cfg.knowledge_base ? `知识库: ${truncate(cfg.knowledge_base, 12)}` : (cfg.search_mode ? `模式: ${cfg.search_mode}` : '')
case 'output':
return cfg.format ? `格式: ${cfg.format.toUpperCase()}` : ''
default:
return ''
}
})
function eventLabel(type: string): string {
const map: Record<string, string> = {
text_message: '文本消息',
button_click: '按钮点击',
enter_chat: '进入聊天',
image_message: '图片消息',
voice_message: '语音消息',
file_message: '文件消息',
}
return map[type] || type
}
function truncate(s: string, max: number): string {
if (!s) return ''
return s.length > max ? s.slice(0, max) + '...' : s
}
</script> </script>
<style scoped> <style scoped>
@ -75,6 +124,17 @@ defineEmits<{
font-size: 11px; font-size: 11px;
color: #909399; color: #909399;
} }
.node-config-summary {
margin: 2px 0 0;
font-size: 11px;
color: #606266;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
background: #f5f7fa;
border-radius: 4px;
padding: 3px 6px;
}
.node-delete-btn { .node-delete-btn {
position: absolute; position: absolute;
top: -10px; top: -10px;

32
frontend/src/views/flow/node-configs/ConditionConfig.vue

@ -3,7 +3,7 @@
<el-divider content-position="left">条件配置</el-divider> <el-divider content-position="left">条件配置</el-divider>
<el-form-item label="条件类型"> <el-form-item label="条件类型">
<el-select v-model="config.condition_type" @change="$emit('change')"> <el-select :model-value="modelValue.condition_type || 'expression'" @change="update('condition_type', $event)">
<el-option label="表达式" value="expression" /> <el-option label="表达式" value="expression" />
<el-option label="JSON路径" value="json_path" /> <el-option label="JSON路径" value="json_path" />
<el-option label="正则匹配" value="regex" /> <el-option label="正则匹配" value="regex" />
@ -11,28 +11,28 @@
</el-form-item> </el-form-item>
<el-form-item label="条件表达式"> <el-form-item label="条件表达式">
<el-input v-model="config.condition" placeholder="如: status == 'done'" @change="$emit('change')" /> <el-input :model-value="modelValue.condition" @input="(e: any) => update('condition', e)" placeholder="如: status == 'done'" />
<div class="condition-hint">支持变量: {{input_data}}, {{status}}, {{result}}</div> <div class="condition-hint">支持变量: {{input_data}}, {{status}}, {{result}}</div>
</el-form-item> </el-form-item>
<el-divider content-position="left">分支配置</el-divider> <el-divider content-position="left">分支配置</el-divider>
<el-form-item label="True分支"> <el-form-item label="True分支">
<el-input v-model="config.true_label" placeholder="条件满足时的输出标签" @change="$emit('change')" /> <el-input :model-value="modelValue.true_label || ''" @input="(e: any) => update('true_label', e)" placeholder="条件满足时的输出标签" />
</el-form-item> </el-form-item>
<el-form-item label="False分支"> <el-form-item label="False分支">
<el-input v-model="config.false_label" placeholder="条件不满足时的输出标签" @change="$emit('change')" /> <el-input :model-value="modelValue.false_label || ''" @input="(e: any) => update('false_label', e)" placeholder="条件不满足时的输出标签" />
</el-form-item> </el-form-item>
<el-divider content-position="left">高级选项</el-divider> <el-divider content-position="left">高级选项</el-divider>
<el-form-item label="短路评估"> <el-form-item label="短路评估">
<el-switch v-model="config.short_circuit" @change="$emit('change')" /> <el-switch :model-value="modelValue.short_circuit ?? true" @change="update('short_circuit', $event)" />
</el-form-item> </el-form-item>
<el-form-item label="默认分支"> <el-form-item label="默认分支">
<el-select v-model="config.default_branch" @change="$emit('change')"> <el-select :model-value="modelValue.default_branch || 'false'" @change="update('default_branch', $event)">
<el-option label="True" value="true" /> <el-option label="True" value="true" />
<el-option label="False" value="false" /> <el-option label="False" value="false" />
</el-select> </el-select>
@ -41,18 +41,16 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
const config = defineModel<any>({ const props = defineProps<{
default: () => ({ modelValue: any
condition_type: 'expression', }>()
condition: '',
true_label: '是',
false_label: '否',
short_circuit: true,
default_branch: 'false'
})
})
defineEmits(['change']) const emit = defineEmits(['change', 'update:modelValue'])
function update(key: string, val: any) {
emit('change')
emit('update:modelValue', { ...props.modelValue, [key]: val })
}
</script> </script>
<style scoped> <style scoped>

51
frontend/src/views/flow/node-configs/LlmConfig.vue

@ -3,7 +3,7 @@
<el-divider content-position="left">Agent配置</el-divider> <el-divider content-position="left">Agent配置</el-divider>
<el-form-item label="选择Agent"> <el-form-item label="选择Agent">
<el-select v-model="config.agent_id" @change="onAgentSelect" placeholder="手动配置或选择已有Agent" clearable> <el-select :model-value="modelValue.agent_id" @change="onAgentSelect" placeholder="手动配置或选择已有Agent" clearable>
<el-option v-for="a in agentList" :key="a.id" :label="a.name" :value="a.id" /> <el-option v-for="a in agentList" :key="a.id" :label="a.name" :value="a.id" />
</el-select> </el-select>
</el-form-item> </el-form-item>
@ -11,11 +11,11 @@
<el-divider content-position="left">模型配置</el-divider> <el-divider content-position="left">模型配置</el-divider>
<el-form-item label="系统提示词"> <el-form-item label="系统提示词">
<el-input v-model="config.system_prompt" type="textarea" :rows="4" @change="$emit('change')" placeholder="输入系统提示词,定义AI角色和行为" /> <el-input :model-value="modelValue.system_prompt" type="textarea" :rows="4" @input="(e: any) => update('system_prompt', e)" placeholder="输入系统提示词,定义AI角色和行为" />
</el-form-item> </el-form-item>
<el-form-item label="模型"> <el-form-item label="模型">
<el-select v-model="config.model" @change="$emit('change')"> <el-select :model-value="modelValue.model || 'gpt-4o-mini'" @change="update('model', $event)">
<el-option label="GPT-4o-mini" value="gpt-4o-mini" /> <el-option label="GPT-4o-mini" value="gpt-4o-mini" />
<el-option label="GPT-4o" value="gpt-4o" /> <el-option label="GPT-4o" value="gpt-4o" />
<el-option label="GPT-3.5-turbo" value="gpt-3.5-turbo" /> <el-option label="GPT-3.5-turbo" value="gpt-3.5-turbo" />
@ -25,21 +25,21 @@
</el-form-item> </el-form-item>
<el-form-item label="温度"> <el-form-item label="温度">
<el-slider v-model="config.temperature" :min="0" :max="2" :step="0.1" @change="$emit('change')" /> <el-slider :model-value="modelValue.temperature ?? 0.7" :min="0" :max="2" :step="0.1" @change="update('temperature', $event)" />
</el-form-item> </el-form-item>
<el-form-item label="最大Token数"> <el-form-item label="最大Token数">
<el-input-number v-model="config.max_tokens" :min="1" :max="4000" :step="100" @change="$emit('change')" /> <el-input-number :model-value="modelValue.max_tokens ?? 2000" :min="1" :max="4000" :step="100" @change="update('max_tokens', $event)" />
</el-form-item> </el-form-item>
<el-divider content-position="left">记忆配置</el-divider> <el-divider content-position="left">记忆配置</el-divider>
<el-form-item label="上下文长度"> <el-form-item label="上下文长度">
<el-input-number v-model="config.context_length" :min="1" :max="20" :step="1" @change="$emit('change')" /> <el-input-number :model-value="modelValue.context_length ?? 5" :min="1" :max="20" :step="1" @change="update('context_length', $event)" />
</el-form-item> </el-form-item>
<el-form-item label="记忆模式"> <el-form-item label="记忆模式">
<el-select v-model="config.memory_mode" @change="$emit('change')"> <el-select :model-value="modelValue.memory_mode || 'short_term'" @change="update('memory_mode', $event)">
<el-option label="无记忆" value="none" /> <el-option label="无记忆" value="none" />
<el-option label="短期记忆" value="short_term" /> <el-option label="短期记忆" value="short_term" />
<el-option label="长期记忆" value="long_term" /> <el-option label="长期记忆" value="long_term" />
@ -49,44 +49,39 @@
<el-divider content-position="left">高级选项</el-divider> <el-divider content-position="left">高级选项</el-divider>
<el-form-item label="流式输出"> <el-form-item label="流式输出">
<el-switch v-model="config.stream" @change="$emit('change')" /> <el-switch :model-value="modelValue.stream ?? true" @change="update('stream', $event)" />
</el-form-item> </el-form-item>
<el-form-item label="函数调用"> <el-form-item label="函数调用">
<el-switch v-model="config.tool_call" @change="$emit('change')" /> <el-switch :model-value="modelValue.tool_call ?? false" @change="update('tool_call', $event)" />
</el-form-item> </el-form-item>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
const config = defineModel<any>({
default: () => ({
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
})
})
const props = defineProps<{ const props = defineProps<{
modelValue: any
agentList: any[] agentList: any[]
}>() }>()
defineEmits(['change']) const emit = defineEmits(['change', 'update:modelValue'])
function update(key: string, val: any) {
emit('change')
emit('update:modelValue', { ...props.modelValue, [key]: val })
}
function onAgentSelect(val: string) { function onAgentSelect(val: string) {
if (!val) return if (!val) return
const agent = props.agentList.find(a => a.id === val) const agent = props.agentList.find(a => a.id === val)
if (agent) { if (agent) {
config.value.system_prompt = agent.system_prompt || config.value.system_prompt const updated = { ...props.modelValue }
config.value.model = agent.model || config.value.model updated.system_prompt = agent.system_prompt || updated.system_prompt
config.value.temperature = agent.temperature ?? config.value.temperature updated.model = agent.model || updated.model
config.value.max_tokens = agent.max_tokens || config.value.max_tokens updated.temperature = agent.temperature ?? updated.temperature
updated.max_tokens = agent.max_tokens || updated.max_tokens
emit('update:modelValue', updated)
} }
emit('change')
} }
</script> </script>

33
frontend/src/views/flow/node-configs/McpConfig.vue

@ -3,30 +3,30 @@
<el-divider content-position="left">MCP服务配置</el-divider> <el-divider content-position="left">MCP服务配置</el-divider>
<el-form-item label="MCP服务器"> <el-form-item label="MCP服务器">
<el-select v-model="config.mcp_server" @change="$emit('change')" placeholder="选择MCP服务"> <el-select :model-value="modelValue.mcp_server" @change="update('mcp_server', $event)" placeholder="选择MCP服务">
<el-option label="未选择" value="" /> <el-option label="未选择" value="" />
<el-option v-for="s in mcpServers" :key="s.id" :label="s.name" :value="s.name" /> <el-option v-for="s in mcpServers" :key="s.id" :label="s.name" :value="s.name" />
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label="工具名"> <el-form-item label="工具名">
<el-input v-model="config.tool_name" placeholder="如: search" @change="$emit('change')" /> <el-input :model-value="modelValue.tool_name" @input="(e: any) => update('tool_name', e)" placeholder="如: search" />
</el-form-item> </el-form-item>
<el-divider content-position="left">请求参数</el-divider> <el-divider content-position="left">请求参数</el-divider>
<el-form-item label="输入参数"> <el-form-item label="输入参数">
<el-input v-model="config.input_params" type="textarea" :rows="3" @change="$emit('change')" placeholder='{"param1": "value1"}' /> <el-input :model-value="modelValue.input_params || '{}'" type="textarea" :rows="3" @input="(e: any) => update('input_params', e)" placeholder='{"param1": "value1"}' />
</el-form-item> </el-form-item>
<el-form-item label="超时时间(秒)"> <el-form-item label="超时时间(秒)">
<el-input-number v-model="config.timeout" :min="1" :max="300" :step="10" @change="$emit('change')" /> <el-input-number :model-value="modelValue.timeout ?? 30" :min="1" :max="300" :step="10" @change="update('timeout', $event)" />
</el-form-item> </el-form-item>
<el-divider content-position="left">响应处理</el-divider> <el-divider content-position="left">响应处理</el-divider>
<el-form-item label="响应解析"> <el-form-item label="响应解析">
<el-select v-model="config.response_parser" @change="$emit('change')"> <el-select :model-value="modelValue.response_parser || 'json'" @change="update('response_parser', $event)">
<el-option label="JSON解析" value="json" /> <el-option label="JSON解析" value="json" />
<el-option label="文本提取" value="text" /> <el-option label="文本提取" value="text" />
<el-option label="自定义解析" value="custom" /> <el-option label="自定义解析" value="custom" />
@ -34,7 +34,7 @@
</el-form-item> </el-form-item>
<el-form-item label="错误处理"> <el-form-item label="错误处理">
<el-select v-model="config.error_handling" @change="$emit('change')"> <el-select :model-value="modelValue.error_handling || 'throw'" @change="update('error_handling', $event)">
<el-option label="抛出错误" value="throw" /> <el-option label="抛出错误" value="throw" />
<el-option label="返回默认值" value="default" /> <el-option label="返回默认值" value="default" />
<el-option label="跳过继续" value="skip" /> <el-option label="跳过继续" value="skip" />
@ -44,20 +44,15 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
const config = defineModel<any>({ const props = defineProps<{
default: () => ({ modelValue: any
mcp_server: '',
tool_name: '',
input_params: '{}',
timeout: 30,
response_parser: 'json',
error_handling: 'throw'
})
})
defineProps<{
mcpServers: any[] mcpServers: any[]
}>() }>()
defineEmits(['change']) const emit = defineEmits(['change', 'update:modelValue'])
function update(key: string, val: any) {
emit('change')
emit('update:modelValue', { ...props.modelValue, [key]: val })
}
</script> </script>

34
frontend/src/views/flow/node-configs/OutputConfig.vue

@ -3,7 +3,7 @@
<el-divider content-position="left">输出配置</el-divider> <el-divider content-position="left">输出配置</el-divider>
<el-form-item label="输出格式"> <el-form-item label="输出格式">
<el-select v-model="config.format" @change="$emit('change')"> <el-select :model-value="modelValue.format || 'text'" @change="update('format', $event)">
<el-option label="纯文本" value="text" /> <el-option label="纯文本" value="text" />
<el-option label="JSON" value="json" /> <el-option label="JSON" value="json" />
<el-option label="XML" value="xml" /> <el-option label="XML" value="xml" />
@ -12,17 +12,17 @@
</el-form-item> </el-form-item>
<el-form-item label="输出模板"> <el-form-item label="输出模板">
<el-input v-model="config.output_template" type="textarea" :rows="3" @change="$emit('change')" placeholder="可选,自定义输出格式" /> <el-input :model-value="modelValue.output_template" type="textarea" :rows="3" @input="(e: any) => update('output_template', e)" placeholder="可选,自定义输出格式" />
</el-form-item> </el-form-item>
<el-divider content-position="left">格式化选项</el-divider> <el-divider content-position="left">格式化选项</el-divider>
<el-form-item label="JSON缩进"> <el-form-item label="JSON缩进">
<el-input-number v-model="config.indent" :min="0" :max="4" :step="1" @change="$emit('change')" /> <el-input-number :model-value="modelValue.indent ?? 2" :min="0" :max="4" :step="1" @change="update('indent', $event)" />
</el-form-item> </el-form-item>
<el-form-item label="字符编码"> <el-form-item label="字符编码">
<el-select v-model="config.encoding" @change="$emit('change')"> <el-select :model-value="modelValue.encoding || 'utf-8'" @change="update('encoding', $event)">
<el-option label="UTF-8" value="utf-8" /> <el-option label="UTF-8" value="utf-8" />
<el-option label="GBK" value="gbk" /> <el-option label="GBK" value="gbk" />
</el-select> </el-select>
@ -31,26 +31,24 @@
<el-divider content-position="left">输出行为</el-divider> <el-divider content-position="left">输出行为</el-divider>
<el-form-item label="截断长输出"> <el-form-item label="截断长输出">
<el-switch v-model="config.truncate" @change="$emit('change')" /> <el-switch :model-value="modelValue.truncate ?? false" @change="update('truncate', $event)" />
</el-form-item> </el-form-item>
<el-form-item label="最大输出长度"> <el-form-item label="最大输出长度">
<el-input-number v-model="config.max_length" :min="100" :max="10000" :step="100" @change="$emit('change')" /> <el-input-number :model-value="modelValue.max_length ?? 2000" :min="100" :max="10000" :step="100" @change="update('max_length', $event)" />
</el-form-item> </el-form-item>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
const config = defineModel<any>({ const props = defineProps<{
default: () => ({ modelValue: any
format: 'text', }>()
output_template: '',
indent: 2, const emit = defineEmits(['change', 'update:modelValue'])
encoding: 'utf-8',
truncate: false, function update(key: string, val: any) {
max_length: 2000 emit('change')
}) emit('update:modelValue', { ...props.modelValue, [key]: val })
}) }
defineEmits(['change'])
</script> </script>

34
frontend/src/views/flow/node-configs/RagConfig.vue

@ -3,17 +3,17 @@
<el-divider content-position="left">知识库配置</el-divider> <el-divider content-position="left">知识库配置</el-divider>
<el-form-item label="知识库"> <el-form-item label="知识库">
<el-input v-model="config.knowledge_base" placeholder="知识库ID" @change="$emit('change')" /> <el-input :model-value="modelValue.knowledge_base" @input="(e: any) => update('knowledge_base', e)" placeholder="知识库ID" />
</el-form-item> </el-form-item>
<el-form-item label="TopK"> <el-form-item label="TopK">
<el-input-number v-model="config.top_k" :min="1" :max="20" :step="1" @change="$emit('change')" /> <el-input-number :model-value="modelValue.top_k ?? 5" :min="1" :max="20" :step="1" @change="update('top_k', $event)" />
</el-form-item> </el-form-item>
<el-divider content-position="left">检索配置</el-divider> <el-divider content-position="left">检索配置</el-divider>
<el-form-item label="检索模式"> <el-form-item label="检索模式">
<el-select v-model="config.search_mode" @change="$emit('change')"> <el-select :model-value="modelValue.search_mode || 'hybrid'" @change="update('search_mode', $event)">
<el-option label="向量检索" value="vector" /> <el-option label="向量检索" value="vector" />
<el-option label="关键词检索" value="keyword" /> <el-option label="关键词检索" value="keyword" />
<el-option label="混合检索" value="hybrid" /> <el-option label="混合检索" value="hybrid" />
@ -21,13 +21,13 @@
</el-form-item> </el-form-item>
<el-form-item label="相似度阈值"> <el-form-item label="相似度阈值">
<el-slider v-model="config.similarity_threshold" :min="0" :max="1" :step="0.05" @change="$emit('change')" /> <el-slider :model-value="modelValue.similarity_threshold ?? 0.7" :min="0" :max="1" :step="0.05" @change="update('similarity_threshold', $event)" />
</el-form-item> </el-form-item>
<el-divider content-position="left">结果处理</el-divider> <el-divider content-position="left">结果处理</el-divider>
<el-form-item label="结果排序"> <el-form-item label="结果排序">
<el-select v-model="config.result_sort" @change="$emit('change')"> <el-select :model-value="modelValue.result_sort || 'similarity'" @change="update('result_sort', $event)">
<el-option label="按相似度" value="similarity" /> <el-option label="按相似度" value="similarity" />
<el-option label="按相关性" value="relevance" /> <el-option label="按相关性" value="relevance" />
<el-option label="按时间" value="time" /> <el-option label="按时间" value="time" />
@ -35,22 +35,20 @@
</el-form-item> </el-form-item>
<el-form-item label="包含元数据"> <el-form-item label="包含元数据">
<el-switch v-model="config.include_metadata" @change="$emit('change')" /> <el-switch :model-value="modelValue.include_metadata ?? true" @change="update('include_metadata', $event)" />
</el-form-item> </el-form-item>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
const config = defineModel<any>({ const props = defineProps<{
default: () => ({ modelValue: any
knowledge_base: '', }>()
top_k: 5,
search_mode: 'hybrid', const emit = defineEmits(['change', 'update:modelValue'])
similarity_threshold: 0.7,
result_sort: 'similarity', function update(key: string, val: any) {
include_metadata: true emit('change')
}) emit('update:modelValue', { ...props.modelValue, [key]: val })
}) }
defineEmits(['change'])
</script> </script>

48
frontend/src/views/flow/node-configs/ToolConfig.vue

@ -3,7 +3,7 @@
<el-divider content-position="left">工具选择</el-divider> <el-divider content-position="left">工具选择</el-divider>
<el-form-item label="工具类型"> <el-form-item label="工具类型">
<el-select v-model="config.tool_type" @change="$emit('change')" placeholder="选择工具类型"> <el-select :model-value="modelValue.tool_type" @change="update('tool_type', $event)" placeholder="选择工具类型">
<el-option label="企微消息" value="wecom_message" /> <el-option label="企微消息" value="wecom_message" />
<el-option label="任务管理" value="task_management" /> <el-option label="任务管理" value="task_management" />
<el-option label="文档处理" value="document_processing" /> <el-option label="文档处理" value="document_processing" />
@ -15,35 +15,31 @@
</el-form-item> </el-form-item>
<el-form-item label="工具名称"> <el-form-item label="工具名称">
<el-select v-model="config.tool_name" @change="$emit('change')" placeholder="选择具体工具"> <el-select :model-value="modelValue.tool_name" @change="update('tool_name', $event)" placeholder="选择具体工具">
<template v-if="config.tool_type === 'wecom_message'"> <template v-if="modelValue.tool_type === 'wecom_message'">
<el-option label="发送企微通知" value="send_notification" /> <el-option label="发送企微通知" value="send_notification" />
<el-option label="查询企微用户" value="query_wecom_user" /> <el-option label="查询企微用户" value="query_wecom_user" />
<el-option label="推送任务至企微" value="push_task_to_wecom" /> <el-option label="推送任务至企微" value="push_task_to_wecom" />
<el-option label="查询下属" value="list_subordinates" /> <el-option label="查询下属" value="list_subordinates" />
</template> </template>
<template v-else-if="config.tool_type === 'task_management'"> <template v-else-if="modelValue.tool_type === 'task_management'">
<el-option label="查询任务列表" value="list_tasks" /> <el-option label="查询任务列表" value="list_tasks" />
<el-option label="创建任务" value="create_task" /> <el-option label="创建任务" value="create_task" />
<el-option label="查询任务详情" value="get_task" /> <el-option label="查询任务详情" value="get_task" />
<el-option label="更新任务" value="update_task" /> <el-option label="更新任务" value="update_task" />
</template> </template>
<template v-else-if="config.tool_type === 'document_processing'"> <template v-else-if="modelValue.tool_type === 'document_processing'">
<el-option label="解析文档" value="parse_document" /> <el-option label="解析文档" value="parse_document" />
</template> </template>
<template v-else-if="config.tool_type === 'format_processing'"> <template v-else-if="modelValue.tool_type === 'format_processing'">
<el-option label="修正格式" value="format_correction" /> <el-option label="修正格式" value="format_correction" />
</template> </template>
<template v-else-if="config.tool_type === 'data_query'"> <template v-else-if="modelValue.tool_type === 'report_generation'">
<el-option label="查询用户数据" value="query_user_data" />
<el-option label="查询部门信息" value="query_department_info" />
</template>
<template v-else-if="config.tool_type === 'report_generation'">
<el-option label="生成效率报告" value="generate_efficiency_report" /> <el-option label="生成效率报告" value="generate_efficiency_report" />
<el-option label="任务统计" value="get_task_statistics" /> <el-option label="任务统计" value="get_task_statistics" />
<el-option label="员工看板" value="get_employee_dashboard" /> <el-option label="员工看板" value="get_employee_dashboard" />
</template> </template>
<template v-else-if="config.tool_type === 'http_request'"> <template v-else-if="modelValue.tool_type === 'http_request'">
<el-option label="自定义HTTP请求" value="custom_http_request" /> <el-option label="自定义HTTP请求" value="custom_http_request" />
</template> </template>
</el-select> </el-select>
@ -52,19 +48,19 @@
<el-divider content-position="left">参数配置</el-divider> <el-divider content-position="left">参数配置</el-divider>
<el-form-item label="参数映射"> <el-form-item label="参数映射">
<el-input v-model="config.param_mapping" type="textarea" :rows="3" @change="$emit('change')" placeholder='{"input_key": "output_key"}' /> <el-input :model-value="modelValue.param_mapping || '{}'" type="textarea" :rows="3" @input="(e: any) => update('param_mapping', e)" placeholder='{"input_key": "output_key"}' />
</el-form-item> </el-form-item>
<el-form-item label="超时时间(秒)"> <el-form-item label="超时时间(秒)">
<el-input-number v-model="config.timeout" :min="1" :max="300" :step="10" @change="$emit('change')" /> <el-input-number :model-value="modelValue.timeout ?? 30" :min="1" :max="300" :step="10" @change="update('timeout', $event)" />
</el-form-item> </el-form-item>
<el-form-item label="重试次数"> <el-form-item label="重试次数">
<el-input-number v-model="config.retry_count" :min="0" :max="5" :step="1" @change="$emit('change')" /> <el-input-number :model-value="modelValue.retry_count ?? 0" :min="0" :max="5" :step="1" @change="update('retry_count', $event)" />
</el-form-item> </el-form-item>
<el-form-item label="错误处理"> <el-form-item label="错误处理">
<el-select v-model="config.error_handling" @change="$emit('change')"> <el-select :model-value="modelValue.error_handling || 'throw'" @change="update('error_handling', $event)">
<el-option label="抛出错误" value="throw" /> <el-option label="抛出错误" value="throw" />
<el-option label="返回默认值" value="default" /> <el-option label="返回默认值" value="default" />
<el-option label="跳过继续" value="skip" /> <el-option label="跳过继续" value="skip" />
@ -74,16 +70,14 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
const config = defineModel<any>({ const props = defineProps<{
default: () => ({ modelValue: any
tool_type: '', }>()
tool_name: '',
param_mapping: '{}', const emit = defineEmits(['change', 'update:modelValue'])
timeout: 30,
retry_count: 0,
error_handling: 'throw'
})
})
defineEmits(['change']) function update(key: string, val: any) {
emit('change')
emit('update:modelValue', { ...props.modelValue, [key]: val })
}
</script> </script>

19
frontend/src/views/flow/node-configs/TriggerConfig.vue

@ -1,7 +1,9 @@
<template> <template>
<div class="node-config"> <div class="node-config">
<el-divider content-position="left">触发配置</el-divider>
<el-form-item label="事件类型"> <el-form-item label="事件类型">
<el-select v-model="config.event_type" @change="$emit('change')" placeholder="企微触发事件"> <el-select :model-value="modelValue.event_type" @change="update('event_type', $event)" placeholder="企微触发事件">
<el-option label="文本消息" value="text_message" /> <el-option label="文本消息" value="text_message" />
<el-option label="按钮点击" value="button_click" /> <el-option label="按钮点击" value="button_click" />
<el-option label="进入聊天" value="enter_chat" /> <el-option label="进入聊天" value="enter_chat" />
@ -10,13 +12,22 @@
<el-option label="文件消息" value="file_message" /> <el-option label="文件消息" value="file_message" />
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label="回调地址"> <el-form-item label="回调地址">
<el-input v-model="config.callback_url" @change="$emit('change')" placeholder="可选,默认使用系统回调" /> <el-input :model-value="modelValue.callback_url" @change="update('callback_url', $event)" placeholder="可选,默认使用系统回调" />
</el-form-item> </el-form-item>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
const config = defineModel<any>({ default: () => ({}) }) const props = defineProps<{
defineEmits(['change']) modelValue: any
}>()
const emit = defineEmits(['change', 'update:modelValue'])
function update(key: string, val: any) {
emit('change')
emit('update:modelValue', { ...props.modelValue, [key]: val })
}
</script> </script>

31
frontend/src/views/flow/node-configs/WecomNotifyConfig.vue

@ -3,15 +3,15 @@
<el-divider content-position="left">消息配置</el-divider> <el-divider content-position="left">消息配置</el-divider>
<el-form-item label="消息模板"> <el-form-item label="消息模板">
<el-input v-model="config.message_template" type="textarea" :rows="3" @change="$emit('change')" placeholder="支持变量: {user_name}, {task_name}, {status}" /> <el-input :model-value="modelValue.message_template" type="textarea" :rows="3" @input="(e: any) => update('message_template', e)" placeholder="支持变量: {user_name}, {task_name}, {status}" />
</el-form-item> </el-form-item>
<el-form-item label="目标用户"> <el-form-item label="目标用户">
<el-input v-model="config.target" placeholder="@all 或用户ID" @change="$emit('change')" /> <el-input :model-value="modelValue.target" @input="(e: any) => update('target', e)" placeholder="@all 或用户ID" />
</el-form-item> </el-form-item>
<el-form-item label="消息类型"> <el-form-item label="消息类型">
<el-select v-model="config.message_type" @change="$emit('change')"> <el-select :model-value="modelValue.message_type || 'text'" @change="update('message_type', $event)">
<el-option label="文本消息" value="text" /> <el-option label="文本消息" value="text" />
<el-option label="Markdown消息" value="markdown" /> <el-option label="Markdown消息" value="markdown" />
<el-option label="卡片消息" value="card" /> <el-option label="卡片消息" value="card" />
@ -21,11 +21,11 @@
<el-divider content-position="left">发送选项</el-divider> <el-divider content-position="left">发送选项</el-divider>
<el-form-item label="异步发送"> <el-form-item label="异步发送">
<el-switch v-model="config.async_send" @change="$emit('change')" /> <el-switch :model-value="modelValue.async_send ?? false" @change="update('async_send', $event)" />
</el-form-item> </el-form-item>
<el-form-item label="发送失败处理"> <el-form-item label="发送失败处理">
<el-select v-model="config.error_handling" @change="$emit('change')"> <el-select :model-value="modelValue.error_handling || 'throw'" @change="update('error_handling', $event)">
<el-option label="抛出错误" value="throw" /> <el-option label="抛出错误" value="throw" />
<el-option label="记录日志" value="log" /> <el-option label="记录日志" value="log" />
<el-option label="重试" value="retry" /> <el-option label="重试" value="retry" />
@ -35,15 +35,14 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
const config = defineModel<any>({ const props = defineProps<{
default: () => ({ modelValue: any
message_template: '', }>()
target: '',
message_type: 'text', const emit = defineEmits(['change', 'update:modelValue'])
async_send: false,
error_handling: 'throw' function update(key: string, val: any) {
}) emit('change')
}) emit('update:modelValue', { ...props.modelValue, [key]: val })
}
defineEmits(['change'])
</script> </script>

Loading…
Cancel
Save