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,200 @@
1
+ <script setup lang="ts">
2
+ /**
3
+ * SparkChart — pure-SVG mini area/line chart. Used by AgentMonitorPanel so the
4
+ * package stays dependency-free (no ECharts / D3).
5
+ *
6
+ * Keeps the visual language of Dify's 监测 cards: soft area fill + 1.5px line
7
+ * + light axis grid + labels on the X axis. Y range auto-fits (0 → 1.1×max).
8
+ */
9
+ import { computed } from 'vue';
10
+
11
+ interface Props {
12
+ series: number[];
13
+ labels?: string[];
14
+ color?: string;
15
+ /** Fixed SVG viewport height. Width scales to fill container. */
16
+ height?: number;
17
+ /** Number of horizontal grid lines. */
18
+ gridLines?: number;
19
+ }
20
+
21
+ const props = withDefaults(defineProps<Props>(), {
22
+ color: '#22d3ee',
23
+ height: 140,
24
+ gridLines: 4,
25
+ labels: () => [],
26
+ });
27
+
28
+ const VIEW_W = 480;
29
+ const PAD_X = 32;
30
+ const PAD_TOP = 8;
31
+ const PAD_BOT = 22;
32
+
33
+ const chartHeight = computed(() => Math.max(80, props.height));
34
+ const chartArea = computed(() => chartHeight.value - PAD_TOP - PAD_BOT);
35
+
36
+ const maxY = computed(() => {
37
+ if (props.series.length === 0) return 1;
38
+ const m = Math.max(...props.series);
39
+ if (m <= 0) return 1;
40
+ return m * 1.15;
41
+ });
42
+
43
+ const points = computed(() => {
44
+ const n = props.series.length;
45
+ if (n === 0) return [];
46
+ const stepX = n === 1 ? 0 : (VIEW_W - PAD_X * 2) / (n - 1);
47
+ return props.series.map((v, i) => {
48
+ const x = PAD_X + stepX * i;
49
+ const y = PAD_TOP + chartArea.value * (1 - v / maxY.value);
50
+ return { x, y };
51
+ });
52
+ });
53
+
54
+ const linePath = computed(() =>
55
+ points.value
56
+ .map((p, i) => `${i === 0 ? 'M' : 'L'} ${p.x.toFixed(2)} ${p.y.toFixed(2)}`)
57
+ .join(' '),
58
+ );
59
+
60
+ const areaPath = computed(() => {
61
+ if (points.value.length === 0) return '';
62
+ const first = points.value[0]!;
63
+ const last = points.value[points.value.length - 1]!;
64
+ const baseY = PAD_TOP + chartArea.value;
65
+ return `${linePath.value} L ${last.x.toFixed(2)} ${baseY} L ${first.x.toFixed(2)} ${baseY} Z`;
66
+ });
67
+
68
+ const yTicks = computed(() => {
69
+ const ticks: Array<{ y: number; value: string }> = [];
70
+ const n = props.gridLines;
71
+ for (let i = 0; i <= n; i++) {
72
+ const value = (maxY.value / n) * (n - i);
73
+ const y = PAD_TOP + (chartArea.value / n) * i;
74
+ ticks.push({ y, value: formatTick(value) });
75
+ }
76
+ return ticks;
77
+ });
78
+
79
+ const xTicks = computed(() => {
80
+ const n = props.labels.length;
81
+ if (n === 0 || points.value.length === 0) return [];
82
+ const step = Math.max(1, Math.floor(n / 6));
83
+ const out: Array<{ x: number; label: string }> = [];
84
+ for (let i = 0; i < n; i += step) {
85
+ const p = points.value[i];
86
+ if (!p) continue;
87
+ out.push({ x: p.x, label: props.labels[i]! });
88
+ }
89
+ return out;
90
+ });
91
+
92
+ const gradientId = computed(
93
+ () => `spark-grad-${Math.random().toString(36).slice(2, 8)}`,
94
+ );
95
+
96
+ function formatTick(v: number): string {
97
+ if (v >= 1000) return `${(v / 1000).toFixed(v >= 10_000 ? 0 : 1)}k`;
98
+ if (v >= 10) return v.toFixed(0);
99
+ if (v >= 1) return v.toFixed(1);
100
+ return v.toFixed(2);
101
+ }
102
+ </script>
103
+
104
+ <template>
105
+ <div class="spark-wrap">
106
+ <svg
107
+ :viewBox="`0 0 ${VIEW_W} ${chartHeight}`"
108
+ preserveAspectRatio="none"
109
+ class="spark-svg"
110
+ >
111
+ <defs>
112
+ <linearGradient :id="gradientId" x1="0" x2="0" y1="0" y2="1">
113
+ <stop offset="0" :stop-color="color" stop-opacity="0.28" />
114
+ <stop offset="1" :stop-color="color" stop-opacity="0" />
115
+ </linearGradient>
116
+ </defs>
117
+ <!-- Grid + Y labels -->
118
+ <g class="spark-grid">
119
+ <line
120
+ v-for="t in yTicks"
121
+ :key="`g-${t.y}`"
122
+ :x1="PAD_X"
123
+ :x2="VIEW_W - PAD_X"
124
+ :y1="t.y"
125
+ :y2="t.y"
126
+ />
127
+ <text
128
+ v-for="t in yTicks"
129
+ :key="`ty-${t.y}`"
130
+ :x="PAD_X - 6"
131
+ :y="t.y + 3"
132
+ class="spark-y-label"
133
+ >
134
+ {{ t.value }}
135
+ </text>
136
+ </g>
137
+ <!-- Area + line -->
138
+ <path :d="areaPath" :fill="`url(#${gradientId})`" />
139
+ <path :d="linePath" :stroke="color" fill="none" stroke-width="1.6" />
140
+ <circle
141
+ v-for="(p, i) in points"
142
+ :key="`p-${i}`"
143
+ :cx="p.x"
144
+ :cy="p.y"
145
+ r="1.6"
146
+ :fill="color"
147
+ />
148
+ <!-- X labels -->
149
+ <g class="spark-x">
150
+ <text
151
+ v-for="t in xTicks"
152
+ :key="`x-${t.x}`"
153
+ :x="t.x"
154
+ :y="chartHeight - 6"
155
+ text-anchor="middle"
156
+ class="spark-x-label"
157
+ >
158
+ {{ t.label }}
159
+ </text>
160
+ </g>
161
+ </svg>
162
+ <div v-if="series.length === 0" class="spark-empty">暂无数据</div>
163
+ </div>
164
+ </template>
165
+
166
+ <style scoped>
167
+ .spark-wrap {
168
+ position: relative;
169
+ width: 100%;
170
+ }
171
+ .spark-svg {
172
+ width: 100%;
173
+ height: auto;
174
+ display: block;
175
+ }
176
+ .spark-grid line {
177
+ stroke: #eef2f7;
178
+ stroke-width: 1;
179
+ }
180
+ .spark-y-label {
181
+ fill: #94a3b8;
182
+ font-size: 9px;
183
+ font-family: ui-sans-serif, system-ui, sans-serif;
184
+ text-anchor: end;
185
+ }
186
+ .spark-x-label {
187
+ fill: #94a3b8;
188
+ font-size: 9px;
189
+ font-family: ui-sans-serif, system-ui, sans-serif;
190
+ }
191
+ .spark-empty {
192
+ position: absolute;
193
+ inset: 0;
194
+ display: flex;
195
+ align-items: center;
196
+ justify-content: center;
197
+ color: #cbd5e1;
198
+ font-size: 12px;
199
+ }
200
+ </style>
@@ -0,0 +1,499 @@
1
+ <script setup lang="ts">
2
+ /**
3
+ * VariableEditorModal — 应用配置的「变量」新增/编辑弹窗。
4
+ *
5
+ * 视觉上对齐 AppDesignDrawer 的自定义样式(不引入 ant-design),交互上对齐
6
+ * 流程设计里 StartNodeCard 用的 VariableModal:一次调用 `open(item?)` 就把
7
+ * 初值同步写进内部表单,避免通过 prop 中转导致的时序问题。
8
+ *
9
+ * 用法:
10
+ * const editorRef = ref<InstanceType<typeof VariableEditorModal> | null>(null);
11
+ * editorRef.value?.open(item); // 编辑
12
+ * editorRef.value?.open(); // 新增
13
+ *
14
+ * `@submit` 事件返回一个完整的 `AgentVariable` 对象,宿主自行 push / replace
15
+ * 到自己的数组里。
16
+ */
17
+ import { reactive, ref } from 'vue';
18
+
19
+ import type { AgentVariable, AgentVariableType } from '../types';
20
+
21
+ const TYPES: { value: AgentVariableType; label: string; hint: string }[] = [
22
+ { value: 'string', label: 'String', hint: '任意文本' },
23
+ { value: 'number', label: 'Number', hint: '数值' },
24
+ { value: 'boolean', label: 'Boolean', hint: '布尔' },
25
+ { value: 'array', label: 'Array', hint: '数组' },
26
+ { value: 'object', label: 'Object', hint: '对象' },
27
+ { value: 'file', label: 'File', hint: '文件' },
28
+ ];
29
+
30
+ const emit = defineEmits<{
31
+ (e: 'submit', v: AgentVariable): void;
32
+ (e: 'cancel'): void;
33
+ }>();
34
+
35
+ const open = ref(false);
36
+ const editing = ref(false);
37
+
38
+ const form = reactive<AgentVariable>({
39
+ id: undefined,
40
+ name: '',
41
+ label: '',
42
+ type: 'string',
43
+ required: false,
44
+ description: '',
45
+ defaultValue: '',
46
+ });
47
+
48
+ const errors = reactive<{ name?: string }>({});
49
+
50
+ function resetForm(src?: Partial<AgentVariable>) {
51
+ form.id = src?.id;
52
+ form.name = src?.name ?? '';
53
+ form.label = src?.label ?? '';
54
+ form.type = (src?.type as AgentVariableType) ?? 'string';
55
+ form.required = !!src?.required;
56
+ form.description = src?.description ?? '';
57
+ form.defaultValue = src?.defaultValue ?? '';
58
+ errors.name = undefined;
59
+ }
60
+
61
+ function openModal(item?: Partial<AgentVariable>) {
62
+ resetForm(item);
63
+ editing.value = !!item?.id;
64
+ open.value = true;
65
+ }
66
+
67
+ function closeModal() {
68
+ open.value = false;
69
+ emit('cancel');
70
+ }
71
+
72
+ function onMaskClick(e: MouseEvent) {
73
+ if (e.target === e.currentTarget) closeModal();
74
+ }
75
+
76
+ /**
77
+ * 变量名验证 —— 只允许字母/数字/下划线,且不能以数字开头,避免后续在
78
+ * 提示词 `{{#user.xxx#}}` 模板里踩变量插值的语法坑。
79
+ */
80
+ function validateName(name: string): string | null {
81
+ if (!name.trim()) return '变量名不能为空';
82
+ if (!/^[A-Za-z_]\w*$/.test(name.trim())) {
83
+ return '仅允许字母/数字/下划线,且不能以数字开头';
84
+ }
85
+ return null;
86
+ }
87
+
88
+ function submit() {
89
+ const err = validateName(form.name);
90
+ if (err) {
91
+ errors.name = err;
92
+ return;
93
+ }
94
+ const payload: AgentVariable = {
95
+ id: form.id ?? Date.now(),
96
+ name: form.name.trim(),
97
+ label: form.label.trim() || form.name.trim(),
98
+ type: form.type,
99
+ required: form.required,
100
+ description: form.description.trim(),
101
+ defaultValue: form.defaultValue,
102
+ };
103
+ emit('submit', payload);
104
+ open.value = false;
105
+ }
106
+
107
+ defineExpose({ open: openModal, close: closeModal });
108
+ </script>
109
+
110
+ <template>
111
+ <Teleport to="body" :disabled="!open">
112
+ <div v-if="open" class="vem-mask" @click="onMaskClick">
113
+ <div class="vem-panel" role="dialog" aria-modal="true">
114
+ <div class="vem-header">
115
+ <span class="vem-title">
116
+ {{ editing ? '编辑变量' : '新增变量' }}
117
+ </span>
118
+ <button class="vem-close" aria-label="关闭" @click="closeModal">
119
+
120
+ </button>
121
+ </div>
122
+
123
+ <div class="vem-body">
124
+ <div class="vem-field">
125
+ <label class="vem-label">
126
+ 变量名
127
+ <span class="vem-required">*</span>
128
+ </label>
129
+ <input
130
+ v-model="form.name"
131
+ class="vem-input"
132
+ :class="{ 'vem-input-error': errors.name }"
133
+ placeholder="例如 query"
134
+ @input="errors.name = undefined"
135
+ />
136
+ <div class="vem-hint">
137
+ 用于在提示词中通过
138
+ <code v-pre>{{ variable }}</code>
139
+ 引用
140
+ </div>
141
+ <div v-if="errors.name" class="vem-error">{{ errors.name }}</div>
142
+ </div>
143
+
144
+ <div class="vem-field">
145
+ <label class="vem-label">显示名</label>
146
+ <input
147
+ v-model="form.label"
148
+ class="vem-input"
149
+ placeholder="用户看到的中文标题(留空则用变量名)"
150
+ />
151
+ </div>
152
+
153
+ <div class="vem-field">
154
+ <label class="vem-label">类型</label>
155
+ <div class="vem-type-grid">
156
+ <button
157
+ v-for="t in TYPES"
158
+ :key="t.value"
159
+ type="button"
160
+ :class="['vem-type-chip', { on: form.type === t.value }]"
161
+ @click="form.type = t.value"
162
+ >
163
+ <span class="vem-type-name">{{ t.label }}</span>
164
+ <span class="vem-type-hint">{{ t.hint }}</span>
165
+ </button>
166
+ </div>
167
+ </div>
168
+
169
+ <div class="vem-field">
170
+ <label class="vem-label">默认值</label>
171
+ <input
172
+ v-model="form.defaultValue"
173
+ class="vem-input"
174
+ placeholder="留空表示无默认值"
175
+ />
176
+ </div>
177
+
178
+ <div class="vem-field">
179
+ <label class="vem-label">描述</label>
180
+ <textarea
181
+ v-model="form.description"
182
+ class="vem-textarea"
183
+ placeholder="给填写者的说明"
184
+ rows="3"
185
+ />
186
+ </div>
187
+
188
+ <label class="vem-switch-row">
189
+ <input v-model="form.required" type="checkbox" />
190
+ <span class="vem-switch-slider" />
191
+ <span class="vem-switch-text">
192
+ <span class="vem-switch-title">必填</span>
193
+ <span class="vem-switch-desc">
194
+ 运行时未填写将阻止提交
195
+ </span>
196
+ </span>
197
+ </label>
198
+ </div>
199
+
200
+ <div class="vem-footer">
201
+ <button class="vem-btn vem-btn-ghost" @click="closeModal">
202
+ 取消
203
+ </button>
204
+ <button class="vem-btn vem-btn-primary" @click="submit">
205
+ {{ editing ? '保存' : '添加' }}
206
+ </button>
207
+ </div>
208
+ </div>
209
+ </div>
210
+ </Teleport>
211
+ </template>
212
+
213
+ <style scoped>
214
+ .vem-mask {
215
+ position: fixed;
216
+ inset: 0;
217
+ z-index: 1200;
218
+ background: rgba(15, 23, 42, 0.45);
219
+ display: flex;
220
+ align-items: center;
221
+ justify-content: center;
222
+ animation: vem-fade 0.14s ease-out;
223
+ }
224
+ @keyframes vem-fade {
225
+ from {
226
+ opacity: 0;
227
+ }
228
+ to {
229
+ opacity: 1;
230
+ }
231
+ }
232
+ .vem-panel {
233
+ width: 500px;
234
+ max-width: calc(100vw - 32px);
235
+ max-height: calc(100vh - 80px);
236
+ display: flex;
237
+ flex-direction: column;
238
+ background: #fff;
239
+ border-radius: 14px;
240
+ box-shadow:
241
+ 0 20px 60px rgba(15, 23, 42, 0.24),
242
+ 0 4px 12px rgba(15, 23, 42, 0.08);
243
+ overflow: hidden;
244
+ animation: vem-in 0.16s ease-out;
245
+ }
246
+ @keyframes vem-in {
247
+ from {
248
+ opacity: 0;
249
+ transform: translateY(-6px);
250
+ }
251
+ to {
252
+ opacity: 1;
253
+ transform: translateY(0);
254
+ }
255
+ }
256
+
257
+ .vem-header {
258
+ padding: 14px 18px;
259
+ display: flex;
260
+ align-items: center;
261
+ justify-content: space-between;
262
+ border-bottom: 1px solid #f1f5f9;
263
+ }
264
+ .vem-title {
265
+ font-size: 15px;
266
+ font-weight: 600;
267
+ color: #0f172a;
268
+ }
269
+ .vem-close {
270
+ width: 24px;
271
+ height: 24px;
272
+ padding: 0;
273
+ border: none;
274
+ background: transparent;
275
+ color: #94a3b8;
276
+ font-size: 14px;
277
+ cursor: pointer;
278
+ border-radius: 4px;
279
+ }
280
+ .vem-close:hover {
281
+ background: #f1f5f9;
282
+ color: #475569;
283
+ }
284
+
285
+ .vem-body {
286
+ flex: 1;
287
+ padding: 16px 18px 8px;
288
+ overflow-y: auto;
289
+ display: flex;
290
+ flex-direction: column;
291
+ gap: 14px;
292
+ }
293
+ .vem-field {
294
+ display: flex;
295
+ flex-direction: column;
296
+ gap: 6px;
297
+ }
298
+ .vem-label {
299
+ font-size: 12px;
300
+ font-weight: 500;
301
+ color: #334155;
302
+ }
303
+ .vem-required {
304
+ color: #ef4444;
305
+ margin-left: 2px;
306
+ }
307
+ .vem-input,
308
+ .vem-textarea {
309
+ width: 100%;
310
+ padding: 8px 12px;
311
+ font-size: 13px;
312
+ color: #0f172a;
313
+ background: #fff;
314
+ border: 1px solid #d1d5db;
315
+ border-radius: 8px;
316
+ font-family: inherit;
317
+ transition:
318
+ border-color 0.15s ease,
319
+ box-shadow 0.15s ease;
320
+ }
321
+ .vem-textarea {
322
+ resize: vertical;
323
+ line-height: 1.55;
324
+ }
325
+ .vem-input:focus,
326
+ .vem-textarea:focus {
327
+ outline: none;
328
+ border-color: #6366f1;
329
+ box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.15);
330
+ }
331
+ .vem-input-error {
332
+ border-color: #ef4444;
333
+ }
334
+ .vem-input-error:focus {
335
+ box-shadow: 0 0 0 3px rgba(239, 68, 68, 0.15);
336
+ }
337
+ .vem-hint {
338
+ font-size: 11px;
339
+ color: #94a3b8;
340
+ line-height: 1.5;
341
+ }
342
+ .vem-hint code {
343
+ padding: 1px 5px;
344
+ background: #f1f5f9;
345
+ color: #4338ca;
346
+ border-radius: 4px;
347
+ font-size: 11px;
348
+ }
349
+ .vem-error {
350
+ font-size: 11px;
351
+ color: #dc2626;
352
+ }
353
+
354
+ .vem-type-grid {
355
+ display: grid;
356
+ grid-template-columns: repeat(3, minmax(0, 1fr));
357
+ gap: 6px;
358
+ }
359
+ .vem-type-chip {
360
+ display: flex;
361
+ flex-direction: column;
362
+ align-items: flex-start;
363
+ gap: 2px;
364
+ padding: 8px 10px;
365
+ background: #fff;
366
+ border: 1px solid #e5e7eb;
367
+ border-radius: 8px;
368
+ cursor: pointer;
369
+ text-align: left;
370
+ transition:
371
+ background 0.15s,
372
+ border-color 0.15s,
373
+ color 0.15s;
374
+ }
375
+ .vem-type-chip:hover {
376
+ border-color: #c7d2fe;
377
+ background: #f8fafc;
378
+ }
379
+ .vem-type-chip.on {
380
+ background: #eef2ff;
381
+ border-color: #6366f1;
382
+ }
383
+ .vem-type-name {
384
+ font-size: 12px;
385
+ font-weight: 600;
386
+ color: #334155;
387
+ }
388
+ .vem-type-chip.on .vem-type-name {
389
+ color: #4338ca;
390
+ }
391
+ .vem-type-hint {
392
+ font-size: 10px;
393
+ color: #94a3b8;
394
+ }
395
+
396
+ /* Custom switch — repurpose the drawer's dr-switch look for a full row. */
397
+ .vem-switch-row {
398
+ display: flex;
399
+ align-items: center;
400
+ gap: 10px;
401
+ padding: 10px 12px;
402
+ background: #f8fafc;
403
+ border: 1px solid #e5e7eb;
404
+ border-radius: 8px;
405
+ cursor: pointer;
406
+ }
407
+ .vem-switch-row input {
408
+ position: absolute;
409
+ opacity: 0;
410
+ pointer-events: none;
411
+ }
412
+ .vem-switch-slider {
413
+ position: relative;
414
+ width: 32px;
415
+ height: 18px;
416
+ background: #cbd5e1;
417
+ border-radius: 999px;
418
+ transition: background 0.15s;
419
+ flex-shrink: 0;
420
+ }
421
+ .vem-switch-slider::before {
422
+ content: '';
423
+ position: absolute;
424
+ top: 2px;
425
+ left: 2px;
426
+ width: 14px;
427
+ height: 14px;
428
+ background: #fff;
429
+ border-radius: 50%;
430
+ transition: transform 0.15s;
431
+ }
432
+ .vem-switch-row input:checked + .vem-switch-slider {
433
+ background: #6366f1;
434
+ }
435
+ .vem-switch-row input:checked + .vem-switch-slider::before {
436
+ transform: translateX(14px);
437
+ }
438
+ .vem-switch-text {
439
+ display: flex;
440
+ flex-direction: column;
441
+ gap: 2px;
442
+ }
443
+ .vem-switch-title {
444
+ font-size: 12px;
445
+ font-weight: 500;
446
+ color: #334155;
447
+ }
448
+ .vem-switch-desc {
449
+ font-size: 11px;
450
+ color: #94a3b8;
451
+ }
452
+
453
+ .vem-footer {
454
+ padding: 12px 18px;
455
+ border-top: 1px solid #f1f5f9;
456
+ display: flex;
457
+ justify-content: flex-end;
458
+ gap: 8px;
459
+ }
460
+ .vem-btn {
461
+ height: 32px;
462
+ padding: 0 16px;
463
+ font-size: 13px;
464
+ font-weight: 500;
465
+ line-height: 1;
466
+ border: 1px solid transparent;
467
+ border-radius: 8px;
468
+ cursor: pointer;
469
+ transition:
470
+ background 0.15s,
471
+ border-color 0.15s,
472
+ color 0.15s,
473
+ box-shadow 0.15s;
474
+ }
475
+ .vem-btn:focus-visible {
476
+ outline: none;
477
+ box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.25);
478
+ }
479
+ .vem-btn-ghost {
480
+ background: transparent;
481
+ color: #64748b;
482
+ }
483
+ .vem-btn-ghost:hover {
484
+ background: #f1f5f9;
485
+ color: #0f172a;
486
+ }
487
+ .vem-btn-primary {
488
+ background: linear-gradient(135deg, #6366f1 0%, #4f46e5 100%);
489
+ color: #fff;
490
+ box-shadow:
491
+ 0 1px 2px rgba(79, 70, 229, 0.24),
492
+ 0 1px 1px rgba(15, 23, 42, 0.06);
493
+ }
494
+ .vem-btn-primary:hover {
495
+ box-shadow:
496
+ 0 3px 8px rgba(79, 70, 229, 0.32),
497
+ 0 1px 2px rgba(15, 23, 42, 0.08);
498
+ }
499
+ </style>