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,517 @@
1
+ <script setup lang="ts">
2
+ import { onMounted, onUnmounted, reactive, ref, watch } from 'vue';
3
+
4
+ import PromptEditorTagPanel from '@/components/PromptEditorTagPanel.vue';
5
+ import { useWorkflowStore } from '@/stores/workflow';
6
+ import workflow_utils from '@/workflow/utils/workflow_utils';
7
+
8
+ /**
9
+ * VarInsertField —— 单行文本 + 变量胶囊
10
+ *
11
+ * 视觉/交互对齐 PromptEditor:
12
+ * - 文本 + `{{#nodeId.field#}}` 混排;已选变量渲染成彩色 pill(带类型图标、× 删除)
13
+ * - 单行模式:Enter/换行被吞掉;`\n` 不会进入 value
14
+ * - 右侧 "+" 按钮打开 PromptEditorTagPanel;选中变量后插入到光标位置
15
+ * - Backspace 光标紧跟 pill 时整颗删除
16
+ * - v-model 仍是模板字符串(含 `{{#...#}}`),与后端保持一致
17
+ *
18
+ * 场景:Headers / Query 参数 / Authorization API Key Value 等单行值。
19
+ */
20
+
21
+ interface Props {
22
+ modelValue: string;
23
+ nodeId: string;
24
+ placeholder?: string;
25
+ size?: 'small' | 'middle' | 'large';
26
+ bordered?: boolean;
27
+ }
28
+
29
+ const props = withDefaults(defineProps<Props>(), {
30
+ placeholder: '',
31
+ size: 'small',
32
+ bordered: true,
33
+ });
34
+
35
+ const emit = defineEmits<{
36
+ (e: 'update:modelValue', v: string): void;
37
+ }>();
38
+
39
+ const workflowStore = useWorkflowStore();
40
+
41
+ const editorRef = ref<HTMLDivElement | null>(null);
42
+ const insertBtnRef = ref<HTMLElement | null>(null);
43
+ const rootRef = ref<HTMLElement | null>(null);
44
+
45
+ /** 面板状态:mode='insert' 是点 + 按钮打开;contenteditable 内也允许粘贴变量文本 */
46
+ const panel = reactive<{
47
+ open: boolean;
48
+ targetElement: HTMLElement | null;
49
+ }>({
50
+ open: false,
51
+ targetElement: null,
52
+ });
53
+
54
+ /** 记录展开面板前的 selection,以便面板关闭后把插入点还原回来 */
55
+ const savedRange = ref<Range | null>(null);
56
+
57
+ // ---------- 序列化 / 反序列化 ----------
58
+
59
+ /** DOM → `{{#...#}}` 混排字符串。忽略 <br> / 多余的空白节点。 */
60
+ function serializeToText(): string {
61
+ const root = editorRef.value;
62
+ if (!root) return '';
63
+ let text = '';
64
+ const walk = (node: Node) => {
65
+ if (node.nodeType === Node.TEXT_NODE) {
66
+ text += node.textContent ?? '';
67
+ return;
68
+ }
69
+ if (node.nodeType !== Node.ELEMENT_NODE) return;
70
+ const el = node as HTMLElement;
71
+ if (el.classList?.contains('wf-vi-pill')) {
72
+ text += `{{#${el.dataset.key ?? ''}#}}`;
73
+ return;
74
+ }
75
+ if (el.tagName === 'BR') return; // 单行模式:忽略换行
76
+ for (const child of Array.from(el.childNodes)) walk(child);
77
+ };
78
+ for (const child of Array.from(root.childNodes)) walk(child);
79
+ // 去掉 zero-width space(作为光标锚点插入的)
80
+ return text.replace(/​/g, '');
81
+ }
82
+
83
+ /** 模板字符串 → DOM。空串直接清空。 */
84
+ function deserializeFromText(text: string) {
85
+ const root = editorRef.value;
86
+ if (!root) return;
87
+ root.innerHTML = '';
88
+ if (!text) return;
89
+
90
+ const re = /\{\{#([^}]+)#\}\}/g;
91
+ let lastIndex = 0;
92
+ let match: RegExpExecArray | null;
93
+ while ((match = re.exec(text)) !== null) {
94
+ if (match.index > lastIndex) {
95
+ root.appendChild(document.createTextNode(text.slice(lastIndex, match.index)));
96
+ }
97
+ root.appendChild(createPill(match[1]));
98
+ lastIndex = match.index + match[0].length;
99
+ }
100
+ if (lastIndex < text.length) {
101
+ root.appendChild(document.createTextNode(text.slice(lastIndex)));
102
+ }
103
+ }
104
+
105
+ function getPillLabel(selector: string[]): string {
106
+ return workflow_utils.getVariableLabel(selector) || selector.join('.');
107
+ }
108
+
109
+ function getPillType(selector: string[]): string {
110
+ if (!selector || selector.length < 2) return '';
111
+ const [nodeId, ...rest] = selector;
112
+ const node = workflowStore.getNodeById(nodeId);
113
+ if (!node?.data) return '';
114
+ const outputs = workflow_utils.getOutputList(node.data) || [];
115
+ let cur: any = outputs.find((o: any) => o.name === rest[0]);
116
+ for (let i = 1; i < rest.length; i++) {
117
+ if (!cur?.children) return cur?.type ?? '';
118
+ cur = cur.children.find((c: any) => c.name === rest[i]);
119
+ }
120
+ return cur?.type ?? '';
121
+ }
122
+
123
+ function createPill(key: string): HTMLElement {
124
+ const selector = key.split('.');
125
+ const label = getPillLabel(selector);
126
+ const type = getPillType(selector);
127
+
128
+ const pill = document.createElement('span');
129
+ pill.className = 'wf-vi-pill';
130
+ pill.contentEditable = 'false';
131
+ pill.dataset.key = key;
132
+ pill.dataset.type = type;
133
+ pill.title = key;
134
+
135
+ const icon = document.createElement('span');
136
+ icon.className = 'wf-vi-pill-icon';
137
+ icon.textContent = (type?.[0] ?? '·').toUpperCase();
138
+
139
+ const labelEl = document.createElement('span');
140
+ labelEl.className = 'wf-vi-pill-label';
141
+ labelEl.textContent = label;
142
+
143
+ const close = document.createElement('span');
144
+ close.className = 'wf-vi-pill-close';
145
+ close.textContent = '×';
146
+ close.title = '删除';
147
+
148
+ pill.append(icon, labelEl, close);
149
+ return pill;
150
+ }
151
+
152
+ // ---------- 输入 & 事件 ----------
153
+
154
+ let emitDebounce: ReturnType<typeof setTimeout> | null = null;
155
+ function scheduleEmit() {
156
+ if (emitDebounce) clearTimeout(emitDebounce);
157
+ emitDebounce = setTimeout(() => {
158
+ const val = serializeToText();
159
+ if (val !== props.modelValue) emit('update:modelValue', val);
160
+ }, 30);
161
+ }
162
+
163
+ function onInput() {
164
+ scheduleEmit();
165
+ }
166
+
167
+ /** 单行模式:Enter 直接吞掉,避免插入 <br> 或 <div>。 */
168
+ function onKeyDown(e: KeyboardEvent) {
169
+ if (e.key === 'Enter') {
170
+ e.preventDefault();
171
+ return;
172
+ }
173
+
174
+ // Backspace 前面是 pill 时整颗删除
175
+ if (e.key === 'Backspace' && !e.ctrlKey && !e.metaKey) {
176
+ const sel = window.getSelection();
177
+ if (sel && sel.rangeCount > 0 && sel.isCollapsed) {
178
+ const range = sel.getRangeAt(0);
179
+ const { startContainer, startOffset } = range;
180
+ if (startContainer.nodeType === Node.TEXT_NODE && startOffset === 0) {
181
+ const prev = (startContainer as Text).previousSibling;
182
+ if (prev instanceof HTMLElement && prev.classList.contains('wf-vi-pill')) {
183
+ e.preventDefault();
184
+ prev.remove();
185
+ scheduleEmit();
186
+ return;
187
+ }
188
+ }
189
+ if (startContainer === editorRef.value && startOffset > 0) {
190
+ const child = editorRef.value!.childNodes[startOffset - 1];
191
+ if (child instanceof HTMLElement && child.classList.contains('wf-vi-pill')) {
192
+ e.preventDefault();
193
+ child.remove();
194
+ scheduleEmit();
195
+ return;
196
+ }
197
+ }
198
+ }
199
+ }
200
+ }
201
+
202
+ /** 点击 pill 上的 × 删除该 pill */
203
+ function onEditorClick(e: MouseEvent) {
204
+ const target = e.target as HTMLElement;
205
+ if (target.classList?.contains('wf-vi-pill-close')) {
206
+ const pill = target.closest('.wf-vi-pill');
207
+ if (pill) {
208
+ pill.remove();
209
+ scheduleEmit();
210
+ }
211
+ e.preventDefault();
212
+ }
213
+ }
214
+
215
+ /** 只允许粘贴纯文本,避免带入 rich HTML */
216
+ function onPaste(e: ClipboardEvent) {
217
+ e.preventDefault();
218
+ const text = e.clipboardData?.getData('text/plain') ?? '';
219
+ // 单行:去掉换行
220
+ document.execCommand('insertText', false, text.replace(/[\r\n]+/g, ' '));
221
+ }
222
+
223
+ function saveSelection() {
224
+ const sel = window.getSelection();
225
+ if (
226
+ sel &&
227
+ sel.rangeCount > 0 &&
228
+ editorRef.value?.contains(sel.getRangeAt(0).startContainer)
229
+ ) {
230
+ savedRange.value = sel.getRangeAt(0).cloneRange();
231
+ }
232
+ }
233
+
234
+ function restoreSelection() {
235
+ if (!savedRange.value || !editorRef.value) return;
236
+ const sel = window.getSelection();
237
+ sel?.removeAllRanges();
238
+ sel?.addRange(savedRange.value);
239
+ editorRef.value.focus();
240
+ }
241
+
242
+ // ---------- 变量插入面板 ----------
243
+
244
+ function toggleInsertPanel(e: MouseEvent) {
245
+ e.preventDefault();
246
+ if (panel.open) {
247
+ closePanel();
248
+ return;
249
+ }
250
+ saveSelection();
251
+ panel.targetElement = insertBtnRef.value;
252
+ panel.open = true;
253
+ }
254
+
255
+ function closePanel() {
256
+ panel.open = false;
257
+ panel.targetElement = null;
258
+ }
259
+
260
+ function insertPillAtSelection(pill: HTMLElement) {
261
+ const sel = window.getSelection();
262
+ if (!sel || sel.rangeCount === 0 || !editorRef.value?.contains(sel.getRangeAt(0).startContainer)) {
263
+ editorRef.value?.appendChild(pill);
264
+ return;
265
+ }
266
+ const range = sel.getRangeAt(0);
267
+ range.deleteContents();
268
+ range.insertNode(pill);
269
+ // 光标塌到 pill 右侧:插一个 zero-width space 作为落脚点
270
+ const after = document.createTextNode('​');
271
+ pill.parentNode?.insertBefore(after, pill.nextSibling);
272
+ const newRange = document.createRange();
273
+ newRange.setStart(after, 1);
274
+ newRange.setEnd(after, 1);
275
+ sel.removeAllRanges();
276
+ sel.addRange(newRange);
277
+ }
278
+
279
+ function onPanelSelect(variableSelector: string[]) {
280
+ restoreSelection();
281
+ const pill = createPill(variableSelector.join('.'));
282
+ insertPillAtSelection(pill);
283
+ closePanel();
284
+ scheduleEmit();
285
+ editorRef.value?.focus();
286
+ }
287
+
288
+ // ---------- 生命周期 ----------
289
+
290
+ function onGlobalMouseDown(e: MouseEvent) {
291
+ if (!panel.open) return;
292
+ const t = e.target as Node;
293
+ if (editorRef.value?.contains(t)) return;
294
+ if (insertBtnRef.value?.contains(t)) return;
295
+ const panelEl = document.querySelector('.wf-tag-panel');
296
+ if (panelEl?.contains(t)) return;
297
+ closePanel();
298
+ }
299
+
300
+ onMounted(() => {
301
+ deserializeFromText(props.modelValue ?? '');
302
+ document.addEventListener('mousedown', onGlobalMouseDown);
303
+ });
304
+
305
+ onUnmounted(() => {
306
+ document.removeEventListener('mousedown', onGlobalMouseDown);
307
+ if (emitDebounce) clearTimeout(emitDebounce);
308
+ });
309
+
310
+ // 外部灌值:只有真正不同时才重刷 DOM,避免打断光标
311
+ watch(
312
+ () => props.modelValue,
313
+ (val) => {
314
+ if (!editorRef.value) return;
315
+ if ((val ?? '') === serializeToText()) return;
316
+ deserializeFromText(val ?? '');
317
+ },
318
+ );
319
+ </script>
320
+
321
+ <template>
322
+ <div
323
+ ref="rootRef"
324
+ class="wf-vi"
325
+ :class="[
326
+ `wf-vi-${size}`,
327
+ { 'wf-vi-borderless': !bordered },
328
+ ]"
329
+ >
330
+ <div
331
+ ref="editorRef"
332
+ class="wf-vi-input"
333
+ contenteditable="true"
334
+ :data-placeholder="placeholder"
335
+ @input="onInput"
336
+ @keydown="onKeyDown"
337
+ @click="onEditorClick"
338
+ @paste="onPaste"
339
+ @mouseup="saveSelection"
340
+ @keyup="saveSelection"
341
+ @blur="saveSelection"
342
+ ></div>
343
+
344
+ <a
345
+ ref="insertBtnRef"
346
+ class="wf-vi-insert-btn"
347
+ title="插入变量"
348
+ tabindex="-1"
349
+ @mousedown.prevent
350
+ @click="toggleInsertPanel"
351
+ >
352
+ <svg viewBox="0 0 24 24" width="12" height="12">
353
+ <path fill="currentColor" d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6z" />
354
+ </svg>
355
+ </a>
356
+
357
+ <PromptEditorTagPanel
358
+ :show="panel.open"
359
+ :node-id="nodeId"
360
+ :target-element="panel.targetElement"
361
+ @close="closePanel"
362
+ @select="onPanelSelect"
363
+ />
364
+ </div>
365
+ </template>
366
+
367
+ <style scoped>
368
+ .wf-vi {
369
+ display: flex;
370
+ align-items: stretch;
371
+ width: 100%;
372
+ min-height: 24px;
373
+ background: #ffffff;
374
+ border: 1px solid #e5e7eb;
375
+ border-radius: 4px;
376
+ transition: border-color 0.15s, box-shadow 0.15s;
377
+ }
378
+
379
+ .wf-vi:focus-within {
380
+ border-color: #6366f1;
381
+ box-shadow: 0 0 0 2px rgba(99, 102, 241, 0.12);
382
+ }
383
+
384
+ .wf-vi-borderless {
385
+ border-color: transparent;
386
+ background: transparent;
387
+ }
388
+
389
+ .wf-vi-borderless:focus-within {
390
+ border-color: transparent;
391
+ box-shadow: none;
392
+ }
393
+
394
+ .wf-vi-small {
395
+ min-height: 24px;
396
+ font-size: 13px;
397
+ }
398
+
399
+ .wf-vi-middle {
400
+ min-height: 32px;
401
+ font-size: 14px;
402
+ }
403
+
404
+ .wf-vi-large {
405
+ min-height: 40px;
406
+ font-size: 15px;
407
+ }
408
+
409
+ /* 单行 input:pill 与文本内联,纵向居中;超长时横向滚动 */
410
+ .wf-vi-input {
411
+ flex: 1;
412
+ min-width: 0;
413
+ padding: 2px 6px;
414
+ line-height: 1.6;
415
+ color: #1f2937;
416
+ outline: none;
417
+ overflow-x: auto;
418
+ overflow-y: hidden;
419
+ white-space: nowrap;
420
+ word-break: keep-all;
421
+ }
422
+
423
+ .wf-vi-input:empty::before {
424
+ content: attr(data-placeholder);
425
+ color: #9ca3af;
426
+ pointer-events: none;
427
+ }
428
+
429
+ .wf-vi-insert-btn {
430
+ display: inline-flex;
431
+ align-items: center;
432
+ justify-content: center;
433
+ flex: none;
434
+ width: 22px;
435
+ color: #9ca3af;
436
+ background: transparent;
437
+ cursor: pointer;
438
+ transition: color 0.12s, background 0.12s;
439
+ }
440
+
441
+ .wf-vi-insert-btn:hover {
442
+ color: #4338ca;
443
+ background: #eef2ff;
444
+ }
445
+ </style>
446
+
447
+ <style>
448
+ /* pill 全局样式(同 PromptEditor 的做法):因为 pill 是 createElement 挂进 contenteditable
449
+ * 的裸 DOM 节点,<style scoped> 的 data-v-xxx 不会自动加到它们上,所以样式必须放到
450
+ * 非 scoped 块里。类名前缀 wf-vi- 与 wf-pe- 隔离,互不影响。 */
451
+ .wf-vi-pill {
452
+ display: inline-flex;
453
+ align-items: center;
454
+ gap: 3px;
455
+ padding: 0 3px 0 2px;
456
+ margin: 0 1px;
457
+ font-size: 12px;
458
+ line-height: 1.4;
459
+ color: #1f2937;
460
+ background: #eef2ff;
461
+ border: 1px solid #c7d2fe;
462
+ border-radius: 4px;
463
+ cursor: default;
464
+ user-select: none;
465
+ vertical-align: baseline;
466
+ transition: background 0.15s, border-color 0.15s;
467
+ }
468
+
469
+ .wf-vi-pill:hover {
470
+ background: #e0e7ff;
471
+ border-color: #a5b4fc;
472
+ }
473
+
474
+ .wf-vi-pill-icon {
475
+ display: inline-flex;
476
+ align-items: center;
477
+ justify-content: center;
478
+ width: 12px;
479
+ height: 12px;
480
+ font-size: 8px;
481
+ font-weight: 700;
482
+ color: #ffffff;
483
+ background: #6366f1;
484
+ border-radius: 2px;
485
+ text-transform: uppercase;
486
+ }
487
+
488
+ .wf-vi-pill[data-type='string'] .wf-vi-pill-icon { background: #3b82f6; }
489
+ .wf-vi-pill[data-type='number'] .wf-vi-pill-icon { background: #f59e0b; }
490
+ .wf-vi-pill[data-type='boolean'] .wf-vi-pill-icon { background: #10b981; }
491
+ .wf-vi-pill[data-type='array'] .wf-vi-pill-icon { background: #a855f7; }
492
+ .wf-vi-pill[data-type='object'] .wf-vi-pill-icon { background: #6b7280; }
493
+ .wf-vi-pill[data-type='file'] .wf-vi-pill-icon { background: #ef4444; }
494
+
495
+ .wf-vi-pill-label {
496
+ color: #4338ca;
497
+ font-weight: 500;
498
+ }
499
+
500
+ .wf-vi-pill-close {
501
+ display: inline-flex;
502
+ align-items: center;
503
+ justify-content: center;
504
+ width: 12px;
505
+ height: 12px;
506
+ font-size: 12px;
507
+ line-height: 1;
508
+ color: #6366f1;
509
+ border-radius: 50%;
510
+ cursor: pointer;
511
+ transition: background 0.15s;
512
+ }
513
+
514
+ .wf-vi-pill-close:hover {
515
+ background: #c7d2fe;
516
+ }
517
+ </style>