vue-agent-start 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (169) hide show
  1. package/package.json +30 -0
  2. package/pnpm-workspace.yaml +3 -0
  3. package/src/agent-flow/adapter/backend.ts +99 -0
  4. package/src/agent-flow/components/ChatIframePanel.vue +773 -0
  5. package/src/agent-flow/components/DatasetItemCard.vue +313 -0
  6. package/src/agent-flow/components/MemoryWindow.vue +104 -0
  7. package/src/agent-flow/components/PromptEditor.vue +804 -0
  8. package/src/agent-flow/components/PromptEditorTagPanel.vue +460 -0
  9. package/src/agent-flow/components/SvgIcon.vue +32 -0
  10. package/src/agent-flow/components/ToolItemCard.vue +169 -0
  11. package/src/agent-flow/components/VariableCard.vue +161 -0
  12. package/src/agent-flow/components/VariableSelector.vue +162 -0
  13. package/src/agent-flow/components/chat-iframe-types.ts +35 -0
  14. package/src/agent-flow/components/stubs/DatasetChooser.vue +70 -0
  15. package/src/agent-flow/components/stubs/Embedding.vue +22 -0
  16. package/src/agent-flow/components/stubs/ModelSelect.vue +24 -0
  17. package/src/agent-flow/components/stubs/RestfulSelectCard.vue +24 -0
  18. package/src/agent-flow/components/stubs/ToolChooser.vue +96 -0
  19. package/src/agent-flow/components/stubs/TriggersItemCard.vue +126 -0
  20. package/src/agent-flow/components/stubs/VariableModal.vue +115 -0
  21. package/src/agent-flow/index.ts +73 -0
  22. package/src/agent-flow/stores/workflow.ts +72 -0
  23. package/src/agent-flow/utils/langUtils.ts +173 -0
  24. package/src/agent-flow/workflow/Ability.vue +65 -0
  25. package/src/agent-flow/workflow/AuthorizationItem.vue +101 -0
  26. package/src/agent-flow/workflow/ConversationVar.vue +42 -0
  27. package/src/agent-flow/workflow/CustomEdge.vue +94 -0
  28. package/src/agent-flow/workflow/EnvironmentVar.vue +40 -0
  29. package/src/agent-flow/workflow/FlowDesigner.vue +2802 -0
  30. package/src/agent-flow/workflow/Icon.vue +179 -0
  31. package/src/agent-flow/workflow/NodeConfig.vue +509 -0
  32. package/src/agent-flow/workflow/NodeConfigCard.vue +460 -0
  33. package/src/agent-flow/workflow/NodeHandle.vue +147 -0
  34. package/src/agent-flow/workflow/NodeHeaderToolbar.vue +77 -0
  35. package/src/agent-flow/workflow/NodeHoverToolbar.vue +176 -0
  36. package/src/agent-flow/workflow/OutputItemCard.vue +668 -0
  37. package/src/agent-flow/workflow/ParamTableItem.vue +200 -0
  38. package/src/agent-flow/workflow/StructTable.vue +202 -0
  39. package/src/agent-flow/workflow/VarInsertField.vue +517 -0
  40. package/src/agent-flow/workflow/VarSelectField.vue +223 -0
  41. package/src/agent-flow/workflow/VariableSelectorItem.vue +48 -0
  42. package/src/agent-flow/workflow/WfField.vue +206 -0
  43. package/src/agent-flow/workflow/_config-base.css +470 -0
  44. package/src/agent-flow/workflow/composables/useNode.js +55 -0
  45. package/src/agent-flow/workflow/index.vue +53 -0
  46. package/src/agent-flow/workflow/nodeCard/AgentNodeCard.vue +180 -0
  47. package/src/agent-flow/workflow/nodeCard/AnswerNodeCard.vue +65 -0
  48. package/src/agent-flow/workflow/nodeCard/ClassifierCard.vue +152 -0
  49. package/src/agent-flow/workflow/nodeCard/CodeNodeCard.vue +139 -0
  50. package/src/agent-flow/workflow/nodeCard/ConditionNodeCard.vue +323 -0
  51. package/src/agent-flow/workflow/nodeCard/DocumentExtractorCard.vue +83 -0
  52. package/src/agent-flow/workflow/nodeCard/EndNodeCard.vue +123 -0
  53. package/src/agent-flow/workflow/nodeCard/HttpNodeCard.vue +451 -0
  54. package/src/agent-flow/workflow/nodeCard/HumanInputCard.vue +152 -0
  55. package/src/agent-flow/workflow/nodeCard/IterationCard.vue +66 -0
  56. package/src/agent-flow/workflow/nodeCard/KnowledgeRetrievalCard.vue +48 -0
  57. package/src/agent-flow/workflow/nodeCard/LLMNodeCard.vue +206 -0
  58. package/src/agent-flow/workflow/nodeCard/ListOperatorCard.vue +188 -0
  59. package/src/agent-flow/workflow/nodeCard/ParameterExtractorCard.vue +352 -0
  60. package/src/agent-flow/workflow/nodeCard/ServiceApiNodeCard.vue +188 -0
  61. package/src/agent-flow/workflow/nodeCard/StartNodeCard.vue +216 -0
  62. package/src/agent-flow/workflow/nodeCard/VariableAssignerCard.vue +125 -0
  63. package/src/agent-flow/workflow/nodeCard/VariableNodeCard.vue +121 -0
  64. package/src/agent-flow/workflow/nodes/AgentNode.vue +122 -0
  65. package/src/agent-flow/workflow/nodes/AnswerNode.vue +99 -0
  66. package/src/agent-flow/workflow/nodes/ClassifierNode.vue +199 -0
  67. package/src/agent-flow/workflow/nodes/CodeNode.vue +152 -0
  68. package/src/agent-flow/workflow/nodes/ConditionNode.vue +232 -0
  69. package/src/agent-flow/workflow/nodes/DocumentExtractorNode.vue +96 -0
  70. package/src/agent-flow/workflow/nodes/EndNode.vue +119 -0
  71. package/src/agent-flow/workflow/nodes/FileUploadNode.vue +97 -0
  72. package/src/agent-flow/workflow/nodes/HttpNode.vue +204 -0
  73. package/src/agent-flow/workflow/nodes/HumanInputNode.vue +110 -0
  74. package/src/agent-flow/workflow/nodes/IterationNode.vue +98 -0
  75. package/src/agent-flow/workflow/nodes/KnowledgeRetrievalNode.vue +253 -0
  76. package/src/agent-flow/workflow/nodes/LLMNode.vue +134 -0
  77. package/src/agent-flow/workflow/nodes/ListOperatorNode.vue +111 -0
  78. package/src/agent-flow/workflow/nodes/LoopNode.vue +94 -0
  79. package/src/agent-flow/workflow/nodes/ParameterExtractorNode.vue +147 -0
  80. package/src/agent-flow/workflow/nodes/ServiceApiNode.vue +161 -0
  81. package/src/agent-flow/workflow/nodes/StartNode.vue +199 -0
  82. package/src/agent-flow/workflow/nodes/TemplateNode.vue +103 -0
  83. package/src/agent-flow/workflow/nodes/UserInputNode.vue +92 -0
  84. package/src/agent-flow/workflow/nodes/VariableAssignerNode.vue +145 -0
  85. package/src/agent-flow/workflow/nodes/VariableNode.vue +130 -0
  86. package/src/agent-flow/workflow/nodes/_node-base.css +338 -0
  87. package/src/agent-flow/workflow/utils/node_card_form.ts +335 -0
  88. package/src/agent-flow/workflow/utils/node_config.ts +63 -0
  89. package/src/agent-flow/workflow/utils/workflow_utils.ts +151 -0
  90. package/src/agent-studio/adapters/springAgentStart.ts +390 -0
  91. package/src/agent-studio/api/index.ts +1 -0
  92. package/src/agent-studio/api/types.ts +173 -0
  93. package/src/agent-studio/components/AgentApiDocs.vue +500 -0
  94. package/src/agent-studio/components/AgentAppsPage.vue +1601 -0
  95. package/src/agent-studio/components/AgentCardGrid.vue +259 -0
  96. package/src/agent-studio/components/AgentChatPage.vue +553 -0
  97. package/src/agent-studio/components/AgentDebugPanel.vue +350 -0
  98. package/src/agent-studio/components/AgentLogsPanel.vue +391 -0
  99. package/src/agent-studio/components/AgentMonitorPanel.vue +204 -0
  100. package/src/agent-studio/components/AgentOrchestrate.vue +505 -0
  101. package/src/agent-studio/components/AgentPromptEditor.vue +237 -0
  102. package/src/agent-studio/components/AgentStudioShell.vue +227 -0
  103. package/src/agent-studio/components/AgentToolsPanel.vue +394 -0
  104. package/src/agent-studio/components/AgentVariablesPanel.vue +205 -0
  105. package/src/agent-studio/components/ApiKeyManager.vue +251 -0
  106. package/src/agent-studio/components/AppDesignDrawer.vue +3391 -0
  107. package/src/agent-studio/components/CreateAppModal.vue +737 -0
  108. package/src/agent-studio/components/SparkChart.vue +200 -0
  109. package/src/agent-studio/components/VariableEditorModal.vue +499 -0
  110. package/src/agent-studio/composables/useAgentStudio.ts +347 -0
  111. package/src/agent-studio/index.ts +60 -0
  112. package/src/agent-studio/panels/DrawerFlowDesigner.vue +203 -0
  113. package/src/agent-studio/panels/LogAnnotationPanel.vue +490 -0
  114. package/src/agent-studio/panels/MonitorPanel.vue +535 -0
  115. package/src/agent-studio/types.ts +242 -0
  116. package/src/index.ts +155 -0
  117. package/src/knowledge-hub/adapters/springAgentStart.ts +472 -0
  118. package/src/knowledge-hub/components/AddDocumentsWizard.vue +1452 -0
  119. package/src/knowledge-hub/components/CreateDatasetWizard.vue +1723 -0
  120. package/src/knowledge-hub/components/DatasetCardGrid.vue +467 -0
  121. package/src/knowledge-hub/components/DatasetDetailDrawer.vue +888 -0
  122. package/src/knowledge-hub/components/DatasetPicker.vue +65 -0
  123. package/src/knowledge-hub/components/DatasetPickerModal.vue +526 -0
  124. package/src/knowledge-hub/components/DatasetSettingsPanel.vue +908 -0
  125. package/src/knowledge-hub/components/DatasetSidebar.vue +781 -0
  126. package/src/knowledge-hub/components/DocumentChunksView.vue +802 -0
  127. package/src/knowledge-hub/components/DocumentTable.vue +552 -0
  128. package/src/knowledge-hub/components/HitTestingPanel.vue +74 -0
  129. package/src/knowledge-hub/components/KnowledgeApp.vue +66 -0
  130. package/src/knowledge-hub/components/KnowledgeHubApp.vue +504 -0
  131. package/src/knowledge-hub/components/RecallTestingPanelV2.vue +515 -0
  132. package/src/knowledge-hub/components/RetrievalConfigPopover.vue +270 -0
  133. package/src/knowledge-hub/components/RetrievalMethodPicker.vue +547 -0
  134. package/src/knowledge-hub/components/RetrievedList.vue +24 -0
  135. package/src/knowledge-hub/components/internal/KhDialog.vue +195 -0
  136. package/src/knowledge-hub/composables/useKnowledge.ts +89 -0
  137. package/src/knowledge-hub/i18n/index.ts +2 -0
  138. package/src/knowledge-hub/i18n/messages.ts +422 -0
  139. package/src/knowledge-hub/i18n/useKhI18n.ts +107 -0
  140. package/src/knowledge-hub/index.ts +46 -0
  141. package/src/knowledge-hub/styles/index.css +4 -0
  142. package/src/knowledge-hub/styles/tokens.css +138 -0
  143. package/src/knowledge-hub/types/api.ts +213 -0
  144. package/src/knowledge-hub/types/dataset.ts +88 -0
  145. package/src/knowledge-hub/types/document.ts +45 -0
  146. package/src/knowledge-hub/types/index.ts +9 -0
  147. package/src/knowledge-hub/types/retrieval.ts +46 -0
  148. package/src/provider-hub/components/CredentialForm.vue +103 -0
  149. package/src/provider-hub/components/DefaultModelsPanel.vue +270 -0
  150. package/src/provider-hub/components/GroupedModelSelect.vue +445 -0
  151. package/src/provider-hub/components/ModelCardGrid.vue +332 -0
  152. package/src/provider-hub/components/ModelParameterDrawer.vue +739 -0
  153. package/src/provider-hub/components/ModelPickerPopover.vue +1282 -0
  154. package/src/provider-hub/components/ProviderApp.vue +159 -0
  155. package/src/provider-hub/components/ProviderCredentialModal.vue +321 -0
  156. package/src/provider-hub/components/ProviderGallery.vue +131 -0
  157. package/src/provider-hub/components/ProviderHubShell.vue +1992 -0
  158. package/src/provider-hub/components/ProviderIcon.vue +130 -0
  159. package/src/provider-hub/composables/useProviderHub.ts +358 -0
  160. package/src/provider-hub/index.ts +43 -0
  161. package/src/provider-hub/svg/MiniMax.svg +1 -0
  162. package/src/provider-hub/svg/Volcengine.svg +1 -0
  163. package/src/provider-hub/svg/Zhipu.svg +1 -0
  164. package/src/provider-hub/svg/deepseek.svg +1 -0
  165. package/src/provider-hub/svg/moonshot.svg +1 -0
  166. package/src/provider-hub/svg/openai.svg +1 -0
  167. package/src/provider-hub/svg/qwen.svg +1 -0
  168. package/src/provider-hub/svg/siliconflow.svg +1 -0
  169. package/src/provider-hub/types.ts +256 -0
@@ -0,0 +1,199 @@
1
+ <script setup>
2
+ import { computed, nextTick, onMounted, ref, watch } from 'vue';
3
+
4
+ import { useVueFlow } from '@vue-flow/core';
5
+
6
+ import Icon from '../Icon.vue';
7
+ import NodeHandle from '../NodeHandle.vue';
8
+ import NodeHoverToolbar from '../NodeHoverToolbar.vue';
9
+
10
+ const props = defineProps({
11
+ id: String,
12
+ data: {
13
+ type: Object,
14
+ default: () => ({
15
+ label: '分类器',
16
+ description: '文本分类节点',
17
+ config: {
18
+ categories: ['正面', '负面', '中性'],
19
+ model: 'text-classification',
20
+ },
21
+ }),
22
+ },
23
+ });
24
+
25
+ const emit = defineEmits(['duplicate', 'delete', 'connection-plus-click']);
26
+
27
+ const { updateNodeInternals } = useVueFlow();
28
+
29
+ const CATEGORY_COLORS = ['#10b981', '#f59e0b', '#ef4444', '#8b5cf6', '#06b6d4'];
30
+ const getCategoryColor = (index) => CATEGORY_COLORS[index % CATEGORY_COLORS.length];
31
+
32
+ /** 可视化用的分类列表:兼容 `data.classes`(新表单)与 `data.config.categories`(旧数据) */
33
+ const visibleCategories = computed(() => {
34
+ const fromClasses = (props.data.classes || [])
35
+ .map((c) => c.name || `类别 ${c.id}`)
36
+ .filter(Boolean);
37
+ if (fromClasses.length > 0) return fromClasses.slice(0, 5);
38
+ return (props.data.config?.categories || []).slice(0, 5);
39
+ });
40
+
41
+ /** 右侧输出 handle:每个分类一个;分类为空时组件模板会退化到单一默认 handle */
42
+ const sourceHandles = computed(() =>
43
+ visibleCategories.value.map((name, index) => ({
44
+ id: name || `case-${index}`,
45
+ color: getCategoryColor(index),
46
+ })),
47
+ );
48
+
49
+ // VueFlow 会缓存每个 handle 挂载时的 bounding rect,用这份缓存渲染边。
50
+ // 我们用自定义 top / transform 决定 handle 纵向位置,且 handle 数量随分类
51
+ // 变化 —— 若不主动通知 VueFlow 重新测量,边会从旧的默认锚点(右上或右中)
52
+ // 出发再折返到 DOM 里 handle 的真实位置,视觉上就是"从底部绕出去"。
53
+ // 与 ConditionNode 同样的处理:分类列表变化或组件挂载后强制重量。
54
+ watch(
55
+ () => sourceHandles.value.map((h) => h.id).join('|'),
56
+ async () => {
57
+ await nextTick();
58
+ updateNodeInternals([props.id]);
59
+ },
60
+ );
61
+
62
+ onMounted(async () => {
63
+ await nextTick();
64
+ updateNodeInternals([props.id]);
65
+ });
66
+
67
+ const isHovered = ref(false);
68
+
69
+ const onMouseEnter = () => (isHovered.value = true);
70
+ const onMouseLeave = () => (isHovered.value = false);
71
+ const duplicateNode = () => emit('duplicate', props.id);
72
+ const deleteNode = () => emit('delete', props.id);
73
+ // NodeHandle 发的是 (event, nodeId, handleId) 三个参数——之前这里只声明了两个,
74
+ // nodeId 被静默塞进了 handleId,结果新建的边 sourceHandle 变成了 nodeId,
75
+ // VueFlow 找不到匹配的 handle,就退回到默认锚点(节点底部),看起来就是
76
+ // "从右侧伸出后折返到底部出来"。改成和 LLMNode 等其他节点一致的三参数签名。
77
+ const onConnectionPlusClick = (event, nodeId, handleId) => {
78
+ emit('connection-plus-click', event, nodeId, handleId);
79
+ };
80
+ </script>
81
+
82
+ <template>
83
+ <div
84
+ class="wf-node classifier-node"
85
+ @mouseenter="onMouseEnter"
86
+ @mouseleave="onMouseLeave"
87
+ >
88
+ <NodeHoverToolbar
89
+ v-if="isHovered"
90
+ node-type="classifier"
91
+ @duplicate="duplicateNode"
92
+ @delete="deleteNode"
93
+ />
94
+
95
+ <div class="wf-node-header">
96
+ <div class="wf-node-icon">
97
+ <Icon name="classifier" />
98
+ </div>
99
+ <div class="wf-node-info">
100
+ <div class="wf-node-title">{{ data.label }}</div>
101
+ <div class="wf-node-subtitle">文本分类</div>
102
+ </div>
103
+ </div>
104
+
105
+ <div class="wf-node-content">
106
+ <div class="wf-node-description">{{ data.description }}</div>
107
+ <div v-if="data.config?.categories?.length" class="wf-node-preview">
108
+ <div class="wf-node-preview-label">分类标签</div>
109
+ <div class="classifier-categories">
110
+ <span
111
+ v-for="(category, index) in visibleCategories"
112
+ :key="index"
113
+ class="classifier-category"
114
+ >{{ category }}</span>
115
+ <span
116
+ v-if="data.config.categories.length > 3"
117
+ class="classifier-more"
118
+ >+{{ data.config.categories.length - 3 }} 更多</span>
119
+ </div>
120
+ </div>
121
+ <div v-else class="wf-node-empty">
122
+ <span class="wf-node-empty-text">配置分类规则</span>
123
+ </div>
124
+ </div>
125
+
126
+ <NodeHandle
127
+ :id="id"
128
+ type="target"
129
+ position="Left"
130
+ :is-hovered="isHovered"
131
+ class-name="flow-node-handle-left"
132
+ :style="{ background: '#7c3aed', border: '2px solid #ffffff' }"
133
+ />
134
+ <!-- 每个分类一个 source handle。若尚未配置分类,退化到单一默认 handle 保证能连线。
135
+ * - top: X% 决定纵向位置;分布收缩到 20%~80% 让 handle 远离 header / 底 padding。
136
+ * - transform: translate(50%, -50%) 与 VueFlow position="Right" 默认一致 ——
137
+ * translateX(50%) 让 handle 半个身位露到节点右边缘外面(否则 handle 会缩在
138
+ * border 里,边线看起来像从节点底部拉出去),translateY(-50%) 把 handle 中心
139
+ * 对齐到 top 的百分位。 -->
140
+ <template v-if="sourceHandles.length > 0">
141
+ <NodeHandle
142
+ v-for="(h, index) in sourceHandles"
143
+ :key="h.id"
144
+ :id="id"
145
+ :handle-id="h.id"
146
+ type="source"
147
+ position="Right"
148
+ :is-hovered="isHovered"
149
+ class-name="flow-node-handle-right"
150
+ :style="{
151
+ background: h.color,
152
+ border: '2px solid #ffffff',
153
+ top: `${20 + (60 * (index + 1)) / (sourceHandles.length + 1)}%`,
154
+ transform: 'translate(50%, -50%)',
155
+ }"
156
+ @connection-plus-click="onConnectionPlusClick"
157
+ />
158
+ </template>
159
+ <NodeHandle
160
+ v-else
161
+ :id="id"
162
+ type="source"
163
+ position="Right"
164
+ :is-hovered="isHovered"
165
+ class-name="flow-node-handle-right"
166
+ :style="{ background: '#7c3aed', border: '2px solid #ffffff' }"
167
+ @connection-plus-click="onConnectionPlusClick"
168
+ />
169
+ </div>
170
+ </template>
171
+
172
+ <style scoped>
173
+ .classifier-node {
174
+ --wf-accent: #7c3aed;
175
+ --wf-accent-hover: #6d28d9;
176
+ }
177
+
178
+ .classifier-categories {
179
+ display: flex;
180
+ flex-wrap: wrap;
181
+ gap: 4px;
182
+ margin-top: 4px;
183
+ }
184
+
185
+ .classifier-category {
186
+ padding: 2px 8px;
187
+ font-size: 11px;
188
+ color: #ffffff;
189
+ background: var(--wf-accent);
190
+ border-radius: 999px;
191
+ }
192
+
193
+ .classifier-more {
194
+ padding: 2px 8px;
195
+ font-size: 10px;
196
+ color: #6b7280;
197
+ font-style: italic;
198
+ }
199
+ </style>
@@ -0,0 +1,152 @@
1
+ <script setup>
2
+ import { computed, ref } from 'vue';
3
+
4
+ import Icon from '../Icon.vue';
5
+ import NodeHandle from '../NodeHandle.vue';
6
+ import NodeHoverToolbar from '../NodeHoverToolbar.vue';
7
+
8
+ const props = defineProps({
9
+ id: String,
10
+ data: {
11
+ type: Object,
12
+ default: () => ({
13
+ label: '代码执行',
14
+ description: '代码执行节点',
15
+ config: {
16
+ language: 'python',
17
+ code: '',
18
+ },
19
+ }),
20
+ },
21
+ });
22
+
23
+ const emit = defineEmits(['duplicate', 'delete', 'connection-plus-click']);
24
+
25
+ const isHovered = ref(false);
26
+
27
+ const onMouseEnter = () => (isHovered.value = true);
28
+ const onMouseLeave = () => (isHovered.value = false);
29
+ const duplicateNode = () => emit('duplicate', props.id);
30
+ const deleteNode = () => emit('delete', props.id);
31
+ const onConnectionPlusClick = (event, nodeId, handleId) => {
32
+ emit('connection-plus-click', event, nodeId, handleId);
33
+ };
34
+
35
+ const lineCount = computed(
36
+ () => props.data.config?.code?.split('\n').length || 0,
37
+ );
38
+
39
+ const truncateCode = (code, maxLength) => {
40
+ if (!code) return '';
41
+ return code.length > maxLength
42
+ ? `${code.slice(0, Math.max(0, maxLength))}...`
43
+ : code;
44
+ };
45
+ </script>
46
+
47
+ <template>
48
+ <div
49
+ class="wf-node code-node"
50
+ @mouseenter="onMouseEnter"
51
+ @mouseleave="onMouseLeave"
52
+ >
53
+ <NodeHoverToolbar
54
+ v-if="isHovered"
55
+ node-type="code"
56
+ @duplicate="duplicateNode"
57
+ @delete="deleteNode"
58
+ />
59
+
60
+ <div class="wf-node-header">
61
+ <div class="wf-node-icon">
62
+ <Icon name="code" />
63
+ </div>
64
+ <div class="wf-node-info">
65
+ <div class="wf-node-title">{{ data.label }}</div>
66
+ <div class="wf-node-subtitle">{{ data.config?.language || 'Python' }}</div>
67
+ </div>
68
+ </div>
69
+
70
+ <div class="wf-node-content">
71
+ <div class="wf-node-description">{{ data.description }}</div>
72
+ <div v-if="data.config?.code" class="code-preview">
73
+ <div class="code-preview-header">
74
+ <span class="code-lang">{{ data.config.language || 'Python' }}</span>
75
+ <span class="code-lines">{{ lineCount }} 行</span>
76
+ </div>
77
+ <pre class="code-preview-body"><code>{{ truncateCode(data.config.code, 80) }}</code></pre>
78
+ </div>
79
+ <div v-else class="wf-node-empty">
80
+ <span class="wf-node-empty-text">点击编辑代码</span>
81
+ </div>
82
+ </div>
83
+
84
+ <NodeHandle
85
+ :id="id"
86
+ type="target"
87
+ position="Left"
88
+ class-name="flow-node-handle-left"
89
+ :style="{ background: '#84cc16', border: '2px solid #ffffff' }"
90
+ />
91
+
92
+ <NodeHandle
93
+ :id="id"
94
+ type="source"
95
+ position="Right"
96
+ :is-hovered="isHovered"
97
+ class-name="flow-node-handle-right"
98
+ :style="{ background: '#84cc16', border: '2px solid #ffffff' }"
99
+ @connection-plus-click="onConnectionPlusClick"
100
+ />
101
+ </div>
102
+ </template>
103
+
104
+ <style scoped>
105
+ .code-node {
106
+ --wf-accent: #84cc16;
107
+ --wf-accent-hover: #65a30d;
108
+ }
109
+
110
+ .wf-node-subtitle {
111
+ text-transform: capitalize;
112
+ }
113
+
114
+ .code-preview {
115
+ overflow: hidden;
116
+ border: 1px solid color-mix(in srgb, var(--wf-accent) 30%, transparent);
117
+ border-radius: 6px;
118
+ background: color-mix(in srgb, var(--wf-accent) 8%, transparent);
119
+ }
120
+
121
+ .code-preview-header {
122
+ display: flex;
123
+ justify-content: space-between;
124
+ align-items: center;
125
+ padding: 4px 8px;
126
+ border-bottom: 1px solid color-mix(in srgb, var(--wf-accent) 30%, transparent);
127
+ background: color-mix(in srgb, var(--wf-accent) 14%, transparent);
128
+ }
129
+
130
+ .code-lang {
131
+ font-size: 10px;
132
+ font-weight: 600;
133
+ text-transform: uppercase;
134
+ color: color-mix(in srgb, var(--wf-accent) 90%, #000);
135
+ }
136
+
137
+ .code-lines {
138
+ font-size: 10px;
139
+ color: #6b7280;
140
+ }
141
+
142
+ .code-preview-body {
143
+ margin: 0;
144
+ padding: 8px;
145
+ font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
146
+ font-size: 11px;
147
+ line-height: 1.4;
148
+ color: #1f2937;
149
+ white-space: pre-wrap;
150
+ word-break: break-all;
151
+ }
152
+ </style>
@@ -0,0 +1,232 @@
1
+ <script setup lang="ts">
2
+ import { computed, nextTick, onMounted, ref, watch } from 'vue';
3
+
4
+ import { useVueFlow } from '@vue-flow/core';
5
+
6
+ import { useWorkflowStore } from '@/stores/workflow';
7
+ import workflow_utils from '@/workflow/utils/workflow_utils';
8
+
9
+ import Icon from '../Icon.vue';
10
+ import NodeHandle from '../NodeHandle.vue';
11
+ import NodeHoverToolbar from '../NodeHoverToolbar.vue';
12
+
13
+ const props = defineProps({
14
+ id: { type: String, default: '' },
15
+ data: {
16
+ type: Object,
17
+ default: () => ({
18
+ label: '条件分支',
19
+ description: '条件判断分支节点',
20
+ cases: [],
21
+ }),
22
+ },
23
+ });
24
+
25
+ const emit = defineEmits(['duplicate', 'delete', 'connection-plus-click']);
26
+ const workflowStore = useWorkflowStore();
27
+ const isHovered = ref(false);
28
+ const { updateNodeInternals } = useVueFlow();
29
+
30
+ // 当 cases 数量或顺序变化时,通知 vue-flow 重新测量本节点的所有 handle 边界,
31
+ // 否则新加的 handle 边界为空,边会被画到节点默认锚点(右上角)而不是对应 case。
32
+ watch(
33
+ () => props.data.cases?.map((c: any) => c.id).join(',') ?? '',
34
+ async () => {
35
+ await nextTick();
36
+ updateNodeInternals([props.id]);
37
+ },
38
+ );
39
+
40
+ onMounted(async () => {
41
+ await nextTick();
42
+ updateNodeInternals([props.id]);
43
+ });
44
+
45
+ const onMouseEnter = () => (isHovered.value = true);
46
+ const onMouseLeave = () => (isHovered.value = false);
47
+ const duplicateNode = () => emit('duplicate', props.id);
48
+ const deleteNode = () => emit('delete', props.id);
49
+ const onConnectionPlusClick = (event, nodeId, handleId) => {
50
+ emit('connection-plus-click', event, nodeId, handleId);
51
+ };
52
+
53
+ const getOperatorText = (operator) => workflow_utils.getOperator(operator);
54
+
55
+ const getCaseSummary = (item: any) => {
56
+ const list = item?.conditions || [];
57
+ if (list.length === 0) return '';
58
+ const logical = (item?.logicalOperator || 'and').toUpperCase();
59
+ return list
60
+ .map((it: any) => {
61
+ const varLabel = workflow_utils.getVariableLabel(it.variableSelector) || '?';
62
+ const op = getOperatorText(it.operator) || '';
63
+ const val = it.value ?? '';
64
+ return `${varLabel} ${op} ${val}`.trim();
65
+ })
66
+ .join(` ${logical} `);
67
+ };
68
+
69
+ const conditionCount = computed(() => props.data.cases?.length || 0);
70
+ </script>
71
+
72
+ <template>
73
+ <div
74
+ class="wf-node condition-node"
75
+ @mouseenter="onMouseEnter"
76
+ @mouseleave="onMouseLeave"
77
+ >
78
+ <NodeHoverToolbar
79
+ v-if="isHovered"
80
+ node-type="condition"
81
+ @duplicate="duplicateNode"
82
+ @delete="deleteNode"
83
+ />
84
+
85
+ <div class="wf-node-header">
86
+ <div class="wf-node-icon">
87
+ <Icon name="condition" />
88
+ </div>
89
+ <div class="wf-node-info">
90
+ <div class="wf-node-title">{{ data.label }}</div>
91
+ <div class="wf-node-subtitle">
92
+ <span>{{ conditionCount }} 个条件</span>
93
+ <span class="wf-node-description">{{ data.description }}</span>
94
+ </div>
95
+ </div>
96
+ </div>
97
+
98
+ <div class="wf-node-content">
99
+ <div v-if="!data.cases || data.cases.length === 0" class="wf-node-empty">
100
+ <span class="wf-node-empty-text">尚未配置分支条件</span>
101
+ </div>
102
+ <div
103
+ v-for="(item, idx) in data.cases"
104
+ :key="item.id"
105
+ class="condition-case"
106
+ :style="{ borderLeftColor: item.color }"
107
+ :title="getCaseSummary(item)"
108
+ >
109
+ <span class="condition-case-badge">
110
+ {{ idx === 0 ? 'IF' : 'ELIF' }}
111
+ </span>
112
+ <span class="condition-case-index">#{{ idx + 1 }}</span>
113
+ <span class="condition-case-summary">
114
+ <template v-if="item.conditions && item.conditions.length > 0">
115
+ {{ getCaseSummary(item) }}
116
+ </template>
117
+ <template v-else>
118
+ <span class="condition-case-placeholder">未设置条件</span>
119
+ </template>
120
+ </span>
121
+ <!-- 每个 case 卡片内嵌自己的 source handle。位置由卡片布局决定,
122
+ 不再依赖 JS 测量 nodeRect/caseRect,避免缩放 & 时序问题。 -->
123
+ <NodeHandle
124
+ :id="id"
125
+ :handle-id="item.id"
126
+ type="source"
127
+ position="Right"
128
+ :is-hovered="isHovered"
129
+ class-name="flow-node-handle-right condition-case-handle"
130
+ :style="{
131
+ background: item.color || '#f59e0b',
132
+ border: '2px solid #ffffff',
133
+ }"
134
+ @connection-plus-click="onConnectionPlusClick"
135
+ />
136
+ </div>
137
+ </div>
138
+
139
+ <NodeHandle
140
+ :id="id"
141
+ type="target"
142
+ position="Left"
143
+ class-name="flow-node-handle-left"
144
+ :style="{ background: '#f59e0b', border: '2px solid #ffffff' }"
145
+ />
146
+
147
+ <!-- 没有 case 时也要有一个默认 source handle,保证能连线 -->
148
+ <NodeHandle
149
+ v-if="!data.cases || data.cases.length === 0"
150
+ :id="id"
151
+ type="source"
152
+ position="Right"
153
+ :is-hovered="isHovered"
154
+ class-name="flow-node-handle-right"
155
+ :style="{ background: '#f59e0b', border: '2px solid #ffffff' }"
156
+ @connection-plus-click="onConnectionPlusClick"
157
+ />
158
+ </div>
159
+ </template>
160
+
161
+ <style scoped>
162
+ .condition-node {
163
+ --wf-accent: #f59e0b;
164
+ --wf-accent-hover: #d97706;
165
+ }
166
+
167
+ .condition-case {
168
+ position: relative;
169
+ display: flex;
170
+ align-items: center;
171
+ gap: 6px;
172
+ height: 28px;
173
+ margin: 4px 0;
174
+ padding: 0 8px;
175
+ border: 1px solid var(--wf-accent);
176
+ border-left: 3px solid var(--wf-accent);
177
+ border-radius: 6px;
178
+ background: color-mix(in srgb, var(--wf-accent) 6%, transparent);
179
+ /* 不裁剪,让 handle 能突出到卡片外,落在节点右边缘 */
180
+ overflow: visible;
181
+ }
182
+
183
+ .condition-case + .condition-case {
184
+ margin-top: 6px;
185
+ }
186
+
187
+ .condition-case-badge {
188
+ flex: 0 0 auto;
189
+ padding: 1px 6px;
190
+ font-size: 10px;
191
+ font-weight: 700;
192
+ color: #ffffff;
193
+ background: color-mix(in srgb, var(--wf-accent) 85%, #000);
194
+ border-radius: 3px;
195
+ letter-spacing: 0.3px;
196
+ line-height: 14px;
197
+ }
198
+
199
+ .condition-case-index {
200
+ flex: 0 0 auto;
201
+ font-size: 10px;
202
+ color: color-mix(in srgb, var(--wf-accent) 80%, #000);
203
+ font-weight: 500;
204
+ opacity: 0.7;
205
+ }
206
+
207
+ .condition-case-summary {
208
+ flex: 1 1 auto;
209
+ min-width: 0;
210
+ font-size: 11px;
211
+ color: #1f2937;
212
+ overflow: hidden;
213
+ text-overflow: ellipsis;
214
+ white-space: nowrap;
215
+ }
216
+
217
+ .condition-case-placeholder {
218
+ color: #9ca3af;
219
+ font-style: italic;
220
+ }
221
+
222
+ /* 内嵌到 case 卡片的 source handle:
223
+ - top: 50% + translateY(-50%) → 精确对齐卡片纵向中心
224
+ - right: -18px → 补偿节点 padding(16) + 边框(2),让 handle 中心落在节点右边框
225
+ - translateX(50%) → 让 handle 半个身位在节点外,与其他节点视觉一致
226
+ :deep 用于穿透 vue-flow 默认 .vue-flow__handle-right 优先级 */
227
+ .condition-case :deep(.condition-case-handle) {
228
+ top: 50% !important;
229
+ right: -18px !important;
230
+ transform: translate(50%, -50%) !important;
231
+ }
232
+ </style>
@@ -0,0 +1,96 @@
1
+ <script setup>
2
+ import { computed, ref } from 'vue';
3
+
4
+ import workflow_utils from '@/workflow/utils/workflow_utils';
5
+
6
+ import Icon from '../Icon.vue';
7
+ import NodeHandle from '../NodeHandle.vue';
8
+ import NodeHoverToolbar from '../NodeHoverToolbar.vue';
9
+
10
+ const props = defineProps({
11
+ id: String,
12
+ data: {
13
+ type: Object,
14
+ default: () => ({
15
+ label: '文档提取',
16
+ description: '从文件变量中提取文本内容',
17
+ variableSelector: [],
18
+ }),
19
+ },
20
+ });
21
+
22
+ const emit = defineEmits(['duplicate', 'delete', 'connection-plus-click']);
23
+
24
+ const isHovered = ref(false);
25
+ const onMouseEnter = () => (isHovered.value = true);
26
+ const onMouseLeave = () => (isHovered.value = false);
27
+ const duplicateNode = () => emit('duplicate', props.id);
28
+ const deleteNode = () => emit('delete', props.id);
29
+ const onConnectionPlusClick = (event, nodeId, handleId) => {
30
+ emit('connection-plus-click', event, nodeId, handleId);
31
+ };
32
+
33
+ const sourceLabel = computed(() =>
34
+ workflow_utils.getVariableLabel(props.data.variableSelector || []),
35
+ );
36
+ </script>
37
+
38
+ <template>
39
+ <div
40
+ class="wf-node document-extractor-node"
41
+ @mouseenter="onMouseEnter"
42
+ @mouseleave="onMouseLeave"
43
+ >
44
+ <NodeHoverToolbar
45
+ v-if="isHovered"
46
+ node-type="document_extractor"
47
+ @duplicate="duplicateNode"
48
+ @delete="deleteNode"
49
+ />
50
+
51
+ <div class="wf-node-header">
52
+ <div class="wf-node-icon">
53
+ <Icon name="file" />
54
+ </div>
55
+ <div class="wf-node-info">
56
+ <div class="wf-node-title">{{ data.label }}</div>
57
+ <div class="wf-node-subtitle">文档解析</div>
58
+ </div>
59
+ </div>
60
+
61
+ <div class="wf-node-content">
62
+ <div class="wf-node-description">{{ data.description }}</div>
63
+ <div v-if="sourceLabel" class="wf-node-preview">
64
+ <div class="wf-node-preview-label">源文件</div>
65
+ <div class="wf-node-preview-value">{{ sourceLabel }}</div>
66
+ </div>
67
+ <div v-else class="wf-node-empty">
68
+ <span class="wf-node-empty-text">选择文件变量</span>
69
+ </div>
70
+ </div>
71
+
72
+ <NodeHandle
73
+ :id="id"
74
+ type="target"
75
+ position="Left"
76
+ class-name="flow-node-handle-left"
77
+ :style="{ background: '#0891b2', border: '2px solid #ffffff' }"
78
+ />
79
+ <NodeHandle
80
+ :id="id"
81
+ type="source"
82
+ position="Right"
83
+ :is-hovered="isHovered"
84
+ class-name="flow-node-handle-right"
85
+ :style="{ background: '#0891b2', border: '2px solid #ffffff' }"
86
+ @connection-plus-click="onConnectionPlusClick"
87
+ />
88
+ </div>
89
+ </template>
90
+
91
+ <style scoped>
92
+ .document-extractor-node {
93
+ --wf-accent: #0891b2;
94
+ --wf-accent-hover: #0e7490;
95
+ }
96
+ </style>