Browse Source

画布节点正常版本

master
MSI-7950X\刘泽明 6 days ago
parent
commit
47cc2075f1
  1. 151
      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

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

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

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

@ -10,8 +10,6 @@
<el-input v-model="flowDesc" placeholder="描述" style="width: 300px; margin-left: 12px" />
<el-button type="primary" @click="saveFlow" :loading="saving">保存</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>
</div>
</el-card>
@ -56,6 +54,8 @@
@pane-click="onPaneClick"
@drop="onDrop"
@node-delete="removeNode"
@update:nodes="(v: any) => nodes = v"
@update:edges="(v: any) => edges = v"
/>
</div>
@ -160,11 +160,6 @@ const colorMap: Record<string, string> = {
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)
function getMiniMapColor(node: any) {
@ -183,7 +178,7 @@ function onDrop(event: DragEvent) {
if (!dataStr) return
const nodeData = JSON.parse(dataStr)
const id = `node_${++nodeCounter}`
canvasRef.value?.addNodes?.([{
nodes.value.push({
id,
type: 'custom',
position: { x: 100 + nodeCounter * 50, y: 100 + nodeCounter * 30 },
@ -196,7 +191,7 @@ function onDrop(event: DragEvent) {
},
draggable: true,
connectable: true,
}])
})
}
function getDefaultConfig(type: string) {

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

@ -5,6 +5,7 @@
</div>
<div class="node-body">
<p class="node-type-label">{{ data?.typeDesc || '' }}</p>
<p v-if="configSummary" class="node-config-summary">{{ configSummary }}</p>
</div>
<button class="node-delete-btn" @click="$emit('delete')" title="删除">×</button>
@ -24,13 +25,18 @@
<span class="handle-label-false"></span>
</Handle>
</template>
<template v-if="data?.type === 'wecom_notify' || data?.type === 'output'">
<Handle type="source" :position="Position.Right" id="source" class="node-handle" />
</template>
</div>
</template>
<script setup lang="ts">
import { computed } from 'vue'
import { Handle, Position } from '@vue-flow/core'
defineProps<{
const props = defineProps<{
id: string
data?: any
selected?: boolean
@ -39,6 +45,49 @@ defineProps<{
defineEmits<{
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>
<style scoped>
@ -75,6 +124,17 @@ defineEmits<{
font-size: 11px;
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 {
position: absolute;
top: -10px;

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

@ -3,7 +3,7 @@
<el-divider content-position="left">条件配置</el-divider>
<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="JSON路径" value="json_path" />
<el-option label="正则匹配" value="regex" />
@ -11,28 +11,28 @@
</el-form-item>
<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>
</el-form-item>
<el-divider content-position="left">分支配置</el-divider>
<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 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-divider content-position="left">高级选项</el-divider>
<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 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="False" value="false" />
</el-select>
@ -41,18 +41,16 @@
</template>
<script setup lang="ts">
const config = defineModel<any>({
default: () => ({
condition_type: 'expression',
condition: '',
true_label: '是',
false_label: '否',
short_circuit: true,
default_branch: 'false'
})
})
const props = defineProps<{
modelValue: 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>
<style scoped>

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

@ -3,7 +3,7 @@
<el-divider content-position="left">Agent配置</el-divider>
<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-select>
</el-form-item>
@ -11,11 +11,11 @@
<el-divider content-position="left">模型配置</el-divider>
<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 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" value="gpt-4o" />
<el-option label="GPT-3.5-turbo" value="gpt-3.5-turbo" />
@ -25,21 +25,21 @@
</el-form-item>
<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 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-divider content-position="left">记忆配置</el-divider>
<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 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="short_term" />
<el-option label="长期记忆" value="long_term" />
@ -49,44 +49,39 @@
<el-divider content-position="left">高级选项</el-divider>
<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 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>
</div>
</template>
<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<{
modelValue: 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) {
if (!val) return
const agent = props.agentList.find(a => a.id === val)
if (agent) {
config.value.system_prompt = agent.system_prompt || config.value.system_prompt
config.value.model = agent.model || config.value.model
config.value.temperature = agent.temperature ?? config.value.temperature
config.value.max_tokens = agent.max_tokens || config.value.max_tokens
const updated = { ...props.modelValue }
updated.system_prompt = agent.system_prompt || updated.system_prompt
updated.model = agent.model || updated.model
updated.temperature = agent.temperature ?? updated.temperature
updated.max_tokens = agent.max_tokens || updated.max_tokens
emit('update:modelValue', updated)
}
emit('change')
}
</script>

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

@ -3,30 +3,30 @@
<el-divider content-position="left">MCP服务配置</el-divider>
<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 v-for="s in mcpServers" :key="s.id" :label="s.name" :value="s.name" />
</el-select>
</el-form-item>
<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-divider content-position="left">请求参数</el-divider>
<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 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-divider content-position="left">响应处理</el-divider>
<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="文本提取" value="text" />
<el-option label="自定义解析" value="custom" />
@ -34,7 +34,7 @@
</el-form-item>
<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="default" />
<el-option label="跳过继续" value="skip" />
@ -44,20 +44,15 @@
</template>
<script setup lang="ts">
const config = defineModel<any>({
default: () => ({
mcp_server: '',
tool_name: '',
input_params: '{}',
timeout: 30,
response_parser: 'json',
error_handling: 'throw'
})
})
defineProps<{
const props = defineProps<{
modelValue: 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>

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

@ -3,7 +3,7 @@
<el-divider content-position="left">输出配置</el-divider>
<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="JSON" value="json" />
<el-option label="XML" value="xml" />
@ -12,17 +12,17 @@
</el-form-item>
<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-divider content-position="left">格式化选项</el-divider>
<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 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="GBK" value="gbk" />
</el-select>
@ -31,26 +31,24 @@
<el-divider content-position="left">输出行为</el-divider>
<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 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>
</div>
</template>
<script setup lang="ts">
const config = defineModel<any>({
default: () => ({
format: 'text',
output_template: '',
indent: 2,
encoding: 'utf-8',
truncate: false,
max_length: 2000
})
})
defineEmits(['change'])
const props = defineProps<{
modelValue: any
}>()
const emit = defineEmits(['change', 'update:modelValue'])
function update(key: string, val: any) {
emit('change')
emit('update:modelValue', { ...props.modelValue, [key]: val })
}
</script>

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

@ -3,17 +3,17 @@
<el-divider content-position="left">知识库配置</el-divider>
<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 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-divider content-position="left">检索配置</el-divider>
<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="keyword" />
<el-option label="混合检索" value="hybrid" />
@ -21,13 +21,13 @@
</el-form-item>
<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-divider content-position="left">结果处理</el-divider>
<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="relevance" />
<el-option label="按时间" value="time" />
@ -35,22 +35,20 @@
</el-form-item>
<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>
</div>
</template>
<script setup lang="ts">
const config = defineModel<any>({
default: () => ({
knowledge_base: '',
top_k: 5,
search_mode: 'hybrid',
similarity_threshold: 0.7,
result_sort: 'similarity',
include_metadata: true
})
})
defineEmits(['change'])
const props = defineProps<{
modelValue: any
}>()
const emit = defineEmits(['change', 'update:modelValue'])
function update(key: string, val: any) {
emit('change')
emit('update:modelValue', { ...props.modelValue, [key]: val })
}
</script>

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

@ -3,7 +3,7 @@
<el-divider content-position="left">工具选择</el-divider>
<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="task_management" />
<el-option label="文档处理" value="document_processing" />
@ -15,35 +15,31 @@
</el-form-item>
<el-form-item label="工具名称">
<el-select v-model="config.tool_name" @change="$emit('change')" placeholder="选择具体工具">
<template v-if="config.tool_type === 'wecom_message'">
<el-select :model-value="modelValue.tool_name" @change="update('tool_name', $event)" placeholder="选择具体工具">
<template v-if="modelValue.tool_type === 'wecom_message'">
<el-option label="发送企微通知" value="send_notification" />
<el-option label="查询企微用户" value="query_wecom_user" />
<el-option label="推送任务至企微" value="push_task_to_wecom" />
<el-option label="查询下属" value="list_subordinates" />
</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="create_task" />
<el-option label="查询任务详情" value="get_task" />
<el-option label="更新任务" value="update_task" />
</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" />
</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" />
</template>
<template v-else-if="config.tool_type === 'data_query'">
<el-option label="查询用户数据" value="query_user_data" />
<el-option label="查询部门信息" value="query_department_info" />
</template>
<template v-else-if="config.tool_type === 'report_generation'">
<template v-else-if="modelValue.tool_type === 'report_generation'">
<el-option label="生成效率报告" value="generate_efficiency_report" />
<el-option label="任务统计" value="get_task_statistics" />
<el-option label="员工看板" value="get_employee_dashboard" />
</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" />
</template>
</el-select>
@ -52,19 +48,19 @@
<el-divider content-position="left">参数配置</el-divider>
<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 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 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 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="default" />
<el-option label="跳过继续" value="skip" />
@ -74,16 +70,14 @@
</template>
<script setup lang="ts">
const config = defineModel<any>({
default: () => ({
tool_type: '',
tool_name: '',
param_mapping: '{}',
timeout: 30,
retry_count: 0,
error_handling: 'throw'
})
})
const props = defineProps<{
modelValue: any
}>()
const emit = defineEmits(['change', 'update:modelValue'])
defineEmits(['change'])
function update(key: string, val: any) {
emit('change')
emit('update:modelValue', { ...props.modelValue, [key]: val })
}
</script>

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

@ -1,7 +1,9 @@
<template>
<div class="node-config">
<el-divider content-position="left">触发配置</el-divider>
<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="button_click" />
<el-option label="进入聊天" value="enter_chat" />
@ -10,13 +12,22 @@
<el-option label="文件消息" value="file_message" />
</el-select>
</el-form-item>
<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>
</div>
</template>
<script setup lang="ts">
const config = defineModel<any>({ default: () => ({}) })
defineEmits(['change'])
const props = defineProps<{
modelValue: any
}>()
const emit = defineEmits(['change', 'update:modelValue'])
function update(key: string, val: any) {
emit('change')
emit('update:modelValue', { ...props.modelValue, [key]: val })
}
</script>

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

@ -3,15 +3,15 @@
<el-divider content-position="left">消息配置</el-divider>
<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 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 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="Markdown消息" value="markdown" />
<el-option label="卡片消息" value="card" />
@ -21,11 +21,11 @@
<el-divider content-position="left">发送选项</el-divider>
<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 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="log" />
<el-option label="重试" value="retry" />
@ -35,15 +35,14 @@
</template>
<script setup lang="ts">
const config = defineModel<any>({
default: () => ({
message_template: '',
target: '',
message_type: 'text',
async_send: false,
error_handling: 'throw'
})
})
defineEmits(['change'])
const props = defineProps<{
modelValue: any
}>()
const emit = defineEmits(['change', 'update:modelValue'])
function update(key: string, val: any) {
emit('change')
emit('update:modelValue', { ...props.modelValue, [key]: val })
}
</script>

Loading…
Cancel
Save