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,739 @@
1
+ <script setup lang="ts">
2
+ /**
3
+ * ModelParameterDrawer — Dify-parity per-model parameter panel.
4
+ *
5
+ * When a user clicks the ⚙ on a pill in ProviderHubShell we open this drawer,
6
+ * which:
7
+ * 1. GETs /models/{id}/parameters → server returns the {rules, values} pair.
8
+ * 2. Renders one control per rule (slider for FLOAT, number for INT, switch
9
+ * for BOOLEAN, input for STRING). Falls back to sensible defaults.
10
+ * 3. On "保存" PATCHes only the entries the user actually touched, so a saved
11
+ * partial change never wipes out unrelated params.
12
+ * 4. Bottom actions cover the three lifecycle moves that used to be buried in
13
+ * the advanced panel: 设为默认 · 测试连接 · 删除模型.
14
+ *
15
+ * The drawer is intentionally standalone: it does NOT know about the shell's
16
+ * catalog list state. Parent handles refresh via the emitted `saved`/`deleted`.
17
+ */
18
+ import { computed, reactive, ref, watch } from 'vue';
19
+
20
+ import { useProviderHub } from '../composables/useProviderHub';
21
+ import type {
22
+ ModelEntity,
23
+ ModelParameterRule,
24
+ ModelTestResult,
25
+ ProviderView,
26
+ } from '../types';
27
+
28
+ interface Props {
29
+ open: boolean;
30
+ model: ModelEntity | null;
31
+ provider: ProviderView | null;
32
+ }
33
+
34
+ const props = defineProps<Props>();
35
+
36
+ const emit = defineEmits<{
37
+ (e: 'update:open', v: boolean): void;
38
+ (e: 'saved'): void;
39
+ (e: 'deleted'): void;
40
+ (e: 'defaulted'): void;
41
+ }>();
42
+
43
+ const {
44
+ getModelParameters,
45
+ saveModelParameters,
46
+ setDefault,
47
+ deleteModel,
48
+ testModel,
49
+ } = useProviderHub();
50
+
51
+ const loading = ref(false);
52
+ const saving = ref(false);
53
+ const testing = ref(false);
54
+ const testResult = ref<ModelTestResult | null>(null);
55
+ const rules = ref<ModelParameterRule[]>([]);
56
+ const values = reactive<Record<string, boolean | null | number | string>>({});
57
+ // Track which fields the user has actually touched so we PATCH only those.
58
+ const touched = ref<Set<string>>(new Set());
59
+ const error = ref<string | null>(null);
60
+
61
+ // Where to inherit the rule set from when the drawer opens. The server already
62
+ // does this reconciliation but we mirror it locally so the drawer can be used
63
+ // standalone without a round-trip when the model has no saved parameters yet.
64
+ const inheritedRules = computed<ModelParameterRule[]>(() => {
65
+ if (!props.model || !props.provider) return [];
66
+ const preset = props.provider.predefinedModels.find(
67
+ (p) =>
68
+ p.model.toLowerCase() === props.model!.modelName.toLowerCase() &&
69
+ p.modelType === props.model!.modelType,
70
+ );
71
+ return (
72
+ preset?.parameterRules ??
73
+ props.provider.defaultParameterRules?.[props.model.modelType] ??
74
+ []
75
+ );
76
+ });
77
+
78
+ async function load() {
79
+ if (!props.model) return;
80
+ loading.value = true;
81
+ error.value = null;
82
+ testResult.value = null;
83
+ touched.value = new Set();
84
+ // Clear stale values before merging fresh
85
+ for (const key of Object.keys(values)) delete values[key];
86
+ try {
87
+ const data = await getModelParameters(props.model.id);
88
+ rules.value = data.rules?.length ? data.rules : inheritedRules.value;
89
+ // Seed: server's saved value → rule default → empty.
90
+ for (const r of rules.value) {
91
+ const stored = data.parameters?.[r.name];
92
+ values[r.name] =
93
+ stored !== undefined && stored !== null
94
+ ? (stored as boolean | number | string)
95
+ : ((r.defaultValue as boolean | number | string) ?? '');
96
+ }
97
+ } catch (e: any) {
98
+ error.value = e?.message ?? String(e);
99
+ rules.value = inheritedRules.value;
100
+ for (const r of rules.value) {
101
+ values[r.name] = (r.defaultValue as boolean | number | string) ?? '';
102
+ }
103
+ } finally {
104
+ loading.value = false;
105
+ }
106
+ }
107
+
108
+ watch(
109
+ () => [props.open, props.model?.id],
110
+ ([o]) => {
111
+ if (o) load();
112
+ },
113
+ { immediate: true },
114
+ );
115
+
116
+ function markTouched(name: string) {
117
+ touched.value = new Set([...touched.value, name]);
118
+ }
119
+
120
+ function onNumberInput(rule: ModelParameterRule, raw: string) {
121
+ markTouched(rule.name);
122
+ if (raw === '' || raw === null) {
123
+ values[rule.name] = '';
124
+ return;
125
+ }
126
+ const n = Number(raw);
127
+ if (!Number.isFinite(n)) return;
128
+ values[rule.name] = n;
129
+ }
130
+
131
+ function onSliderInput(rule: ModelParameterRule, e: Event) {
132
+ const v = Number((e.target as HTMLInputElement).value);
133
+ markTouched(rule.name);
134
+ values[rule.name] = v;
135
+ }
136
+
137
+ function onTextInput(rule: ModelParameterRule, e: Event) {
138
+ markTouched(rule.name);
139
+ values[rule.name] = (e.target as HTMLInputElement).value;
140
+ }
141
+
142
+ function onBoolChange(rule: ModelParameterRule, e: Event) {
143
+ markTouched(rule.name);
144
+ values[rule.name] = (e.target as HTMLInputElement).checked;
145
+ }
146
+
147
+ function reset(rule: ModelParameterRule) {
148
+ markTouched(rule.name);
149
+ // Setting to null explicitly on the server means "remove the override" — but
150
+ // for a nicer UI we set it back to the rule default here and the save path
151
+ // sends null only if the user hits "重置为默认" AND the default is undefined.
152
+ values[rule.name] = (rule.defaultValue as boolean | number | string) ?? '';
153
+ }
154
+
155
+ async function save() {
156
+ if (!props.model) return;
157
+ const patch: Record<string, boolean | null | number | string> = {};
158
+ for (const name of touched.value) {
159
+ const v = values[name];
160
+ // Empty string on a numeric rule → treat as "clear".
161
+ const rule = rules.value.find((r) => r.name === name);
162
+ if (rule && (rule.type === 'FLOAT' || rule.type === 'INT') && v === '') {
163
+ patch[name] = null;
164
+ } else {
165
+ patch[name] = v;
166
+ }
167
+ }
168
+ if (Object.keys(patch).length === 0) {
169
+ emit('update:open', false);
170
+ return;
171
+ }
172
+ saving.value = true;
173
+ error.value = null;
174
+ try {
175
+ await saveModelParameters(props.model.id, patch);
176
+ emit('saved');
177
+ emit('update:open', false);
178
+ } catch (e: any) {
179
+ error.value = e?.message ?? String(e);
180
+ } finally {
181
+ saving.value = false;
182
+ }
183
+ }
184
+
185
+ async function onSetDefault() {
186
+ if (!props.model) return;
187
+ await setDefault(props.model.id);
188
+ emit('defaulted');
189
+ emit('saved');
190
+ }
191
+
192
+ async function onDelete() {
193
+ if (!props.model) return;
194
+ if (
195
+ !confirm(
196
+ `确认删除「${props.model.providerName} / ${props.model.modelName}」?依赖它的知识库/智能体运行时会失败。`,
197
+ )
198
+ )
199
+ return;
200
+ await deleteModel(props.model.id);
201
+ emit('deleted');
202
+ emit('update:open', false);
203
+ }
204
+
205
+ async function onTest() {
206
+ if (!props.model) return;
207
+ testing.value = true;
208
+ testResult.value = null;
209
+ try {
210
+ testResult.value = await testModel(props.model.id);
211
+ } catch (e: any) {
212
+ testResult.value = {
213
+ ok: false,
214
+ latencyMs: 0,
215
+ error: e?.message ?? String(e),
216
+ };
217
+ } finally {
218
+ testing.value = false;
219
+ }
220
+ }
221
+
222
+ function stepFor(r: ModelParameterRule): number {
223
+ if (r.step) return r.step;
224
+ return r.type === 'INT' ? 1 : 0.1;
225
+ }
226
+ </script>
227
+
228
+ <template>
229
+ <transition name="mpd-fade">
230
+ <div v-if="open" class="mpd-mask" @click.self="emit('update:open', false)" />
231
+ </transition>
232
+ <transition name="mpd-slide">
233
+ <aside v-if="open" class="mpd-drawer" role="dialog" aria-label="模型参数">
234
+ <header class="mpd-head">
235
+ <div class="mpd-title-block">
236
+ <div class="mpd-title">模型参数</div>
237
+ <div v-if="model" class="mpd-subtitle" :title="model.modelName">
238
+ <span class="mpd-mono">{{ model.modelName }}</span>
239
+ <span class="mpd-dot">·</span>
240
+ <span>{{ model.modelType }}</span>
241
+ <span
242
+ v-if="model.isDefault"
243
+ class="mpd-badge mpd-badge--default"
244
+ >
245
+ 默认
246
+ </span>
247
+ </div>
248
+ </div>
249
+ <button class="mpd-close" @click="emit('update:open', false)">✕</button>
250
+ </header>
251
+
252
+ <div class="mpd-body">
253
+ <div v-if="loading" class="mpd-loading">加载中…</div>
254
+ <template v-else>
255
+ <div v-if="rules.length === 0" class="mpd-empty">
256
+ 该模型没有可配置的参数。
257
+ </div>
258
+ <div v-for="r in rules" :key="r.name" class="mpd-field">
259
+ <div class="mpd-label-row">
260
+ <label class="mpd-label">
261
+ <span>{{ r.label }}</span>
262
+ <span class="mpd-mono mpd-key">{{ r.name }}</span>
263
+ <span v-if="r.required" class="mpd-req">*</span>
264
+ </label>
265
+ <button
266
+ v-if="touched.has(r.name)"
267
+ class="mpd-reset"
268
+ title="恢复默认"
269
+ @click="reset(r)"
270
+ >
271
+
272
+ </button>
273
+ </div>
274
+
275
+ <!-- FLOAT: slider + input number -->
276
+ <template v-if="r.type === 'FLOAT'">
277
+ <div class="mpd-slider-row">
278
+ <input
279
+ type="range"
280
+ class="mpd-slider"
281
+ :min="r.min ?? 0"
282
+ :max="r.max ?? 1"
283
+ :step="stepFor(r)"
284
+ :value="Number(values[r.name] ?? r.defaultValue ?? 0)"
285
+ @input="(e) => onSliderInput(r, e)"
286
+ />
287
+ <input
288
+ type="number"
289
+ class="mpd-num"
290
+ :min="r.min"
291
+ :max="r.max"
292
+ :step="stepFor(r)"
293
+ :value="values[r.name]"
294
+ @input="(e) => onNumberInput(r, (e.target as HTMLInputElement).value)"
295
+ />
296
+ </div>
297
+ </template>
298
+
299
+ <!-- INT: number input only -->
300
+ <template v-else-if="r.type === 'INT'">
301
+ <input
302
+ type="number"
303
+ class="mpd-num mpd-num--wide"
304
+ :min="r.min"
305
+ :max="r.max"
306
+ :step="stepFor(r)"
307
+ :placeholder="r.placeholder"
308
+ :value="values[r.name]"
309
+ @input="(e) => onNumberInput(r, (e.target as HTMLInputElement).value)"
310
+ />
311
+ </template>
312
+
313
+ <!-- BOOLEAN: switch -->
314
+ <template v-else-if="r.type === 'BOOLEAN'">
315
+ <label class="mpd-switch">
316
+ <input
317
+ type="checkbox"
318
+ :checked="!!values[r.name]"
319
+ @change="(e) => onBoolChange(r, e)"
320
+ />
321
+ <span class="mpd-switch-slider" />
322
+ </label>
323
+ </template>
324
+
325
+ <!-- STRING: text -->
326
+ <template v-else>
327
+ <input
328
+ type="text"
329
+ class="mpd-text"
330
+ :placeholder="r.placeholder"
331
+ :value="values[r.name]"
332
+ @input="(e) => onTextInput(r, e)"
333
+ />
334
+ </template>
335
+
336
+ <div v-if="r.help" class="mpd-help">{{ r.help }}</div>
337
+ </div>
338
+
339
+ <div v-if="error" class="mpd-error">{{ error }}</div>
340
+ <div
341
+ v-if="testResult"
342
+ class="mpd-test-result"
343
+ :class="{ 'mpd-test-result--fail': !testResult.ok }"
344
+ >
345
+ <template v-if="testResult.ok">
346
+ ✓ 连接成功 · {{ testResult.latencyMs }}ms
347
+ <span v-if="testResult.snippet">· “{{ testResult.snippet }}”</span>
348
+ <span v-if="testResult.dimensions">
349
+ · dim {{ testResult.dimensions }}
350
+ </span>
351
+ </template>
352
+ <template v-else>
353
+ ✗ 连接失败: {{ testResult.error ?? testResult.message }}
354
+ </template>
355
+ </div>
356
+ </template>
357
+ </div>
358
+
359
+ <footer class="mpd-foot">
360
+ <div class="mpd-foot-left">
361
+ <button
362
+ class="mpd-btn mpd-btn--ghost"
363
+ :disabled="testing || !model"
364
+ @click="onTest"
365
+ >
366
+ {{ testing ? '测试中…' : '🔍 测试连接' }}
367
+ </button>
368
+ <button
369
+ v-if="model && !model.isDefault"
370
+ class="mpd-btn mpd-btn--ghost"
371
+ @click="onSetDefault"
372
+ >
373
+ 设为默认
374
+ </button>
375
+ <button
376
+ class="mpd-btn mpd-btn--ghost mpd-btn--danger"
377
+ :disabled="!model"
378
+ @click="onDelete"
379
+ >
380
+ 删除模型
381
+ </button>
382
+ </div>
383
+ <div class="mpd-foot-right">
384
+ <button class="mpd-btn" @click="emit('update:open', false)">
385
+ 取消
386
+ </button>
387
+ <button
388
+ class="mpd-btn mpd-btn--primary"
389
+ :disabled="saving || touched.size === 0"
390
+ @click="save"
391
+ >
392
+ {{
393
+ saving
394
+ ? '保存中…'
395
+ : touched.size === 0
396
+ ? '未修改'
397
+ : `保存 ${touched.size} 项`
398
+ }}
399
+ </button>
400
+ </div>
401
+ </footer>
402
+ </aside>
403
+ </transition>
404
+ </template>
405
+
406
+ <style scoped>
407
+ .mpd-mask {
408
+ position: fixed;
409
+ inset: 0;
410
+ background: rgba(15, 23, 42, 0.35);
411
+ z-index: 950;
412
+ }
413
+ .mpd-drawer {
414
+ position: fixed;
415
+ top: 0;
416
+ right: 0;
417
+ bottom: 0;
418
+ width: 460px;
419
+ max-width: 92vw;
420
+ background: #fff;
421
+ box-shadow: -8px 0 24px rgba(15, 23, 42, 0.15);
422
+ z-index: 951;
423
+ display: flex;
424
+ flex-direction: column;
425
+ }
426
+ :global(.dark) .mpd-drawer {
427
+ background: #1a1a1a;
428
+ color: #f3f4f6;
429
+ }
430
+
431
+ .mpd-head {
432
+ padding: 14px 18px;
433
+ border-bottom: 1px solid #e5e7eb;
434
+ display: flex;
435
+ align-items: flex-start;
436
+ justify-content: space-between;
437
+ gap: 12px;
438
+ }
439
+ :global(.dark) .mpd-head {
440
+ border-bottom-color: #2d2d2d;
441
+ }
442
+ .mpd-title {
443
+ font-size: 15px;
444
+ font-weight: 600;
445
+ color: #111827;
446
+ }
447
+ :global(.dark) .mpd-title {
448
+ color: #f3f4f6;
449
+ }
450
+ .mpd-subtitle {
451
+ margin-top: 2px;
452
+ font-size: 12px;
453
+ color: #6b7280;
454
+ display: flex;
455
+ align-items: center;
456
+ gap: 6px;
457
+ min-width: 0;
458
+ }
459
+ .mpd-dot {
460
+ color: #9ca3af;
461
+ }
462
+ .mpd-mono {
463
+ font-family:
464
+ ui-monospace, SFMono-Regular, Menlo, Consolas, 'Liberation Mono', monospace;
465
+ overflow: hidden;
466
+ text-overflow: ellipsis;
467
+ white-space: nowrap;
468
+ }
469
+ .mpd-badge {
470
+ padding: 0 6px;
471
+ font-size: 10px;
472
+ border-radius: 999px;
473
+ background: #eef2ff;
474
+ color: #4338ca;
475
+ }
476
+ .mpd-badge--default {
477
+ background: #dbeafe;
478
+ color: #1e40af;
479
+ }
480
+ .mpd-close {
481
+ background: transparent;
482
+ border: none;
483
+ font-size: 16px;
484
+ color: #6b7280;
485
+ cursor: pointer;
486
+ padding: 4px 8px;
487
+ border-radius: 6px;
488
+ flex-shrink: 0;
489
+ }
490
+ .mpd-close:hover {
491
+ background: #f3f4f6;
492
+ }
493
+
494
+ .mpd-body {
495
+ flex: 1;
496
+ overflow-y: auto;
497
+ padding: 14px 18px;
498
+ display: flex;
499
+ flex-direction: column;
500
+ gap: 14px;
501
+ }
502
+ .mpd-loading,
503
+ .mpd-empty {
504
+ padding: 40px 0;
505
+ text-align: center;
506
+ color: #9ca3af;
507
+ font-size: 13px;
508
+ }
509
+
510
+ .mpd-field {
511
+ display: flex;
512
+ flex-direction: column;
513
+ gap: 4px;
514
+ }
515
+ .mpd-label-row {
516
+ display: flex;
517
+ align-items: center;
518
+ justify-content: space-between;
519
+ gap: 8px;
520
+ }
521
+ .mpd-label {
522
+ display: flex;
523
+ align-items: center;
524
+ gap: 6px;
525
+ font-size: 13px;
526
+ font-weight: 500;
527
+ color: #374151;
528
+ min-width: 0;
529
+ }
530
+ :global(.dark) .mpd-label {
531
+ color: #d1d5db;
532
+ }
533
+ .mpd-key {
534
+ font-size: 10.5px;
535
+ color: #9ca3af;
536
+ font-weight: 400;
537
+ }
538
+ .mpd-req {
539
+ color: #dc2626;
540
+ }
541
+ .mpd-reset {
542
+ background: transparent;
543
+ border: none;
544
+ cursor: pointer;
545
+ color: #6366f1;
546
+ font-size: 13px;
547
+ padding: 0 4px;
548
+ border-radius: 4px;
549
+ }
550
+ .mpd-reset:hover {
551
+ background: #eef2ff;
552
+ }
553
+ .mpd-help {
554
+ font-size: 11.5px;
555
+ color: #9ca3af;
556
+ line-height: 1.5;
557
+ }
558
+
559
+ .mpd-slider-row {
560
+ display: flex;
561
+ align-items: center;
562
+ gap: 10px;
563
+ }
564
+ .mpd-slider {
565
+ flex: 1;
566
+ height: 4px;
567
+ cursor: pointer;
568
+ }
569
+ .mpd-num {
570
+ width: 70px;
571
+ padding: 4px 8px;
572
+ font-size: 12px;
573
+ border: 1px solid #d1d5db;
574
+ border-radius: 6px;
575
+ text-align: right;
576
+ background: #fff;
577
+ color: #111827;
578
+ }
579
+ .mpd-num--wide {
580
+ width: 100%;
581
+ max-width: 220px;
582
+ text-align: left;
583
+ }
584
+ :global(.dark) .mpd-num {
585
+ background: #2d2d2d;
586
+ border-color: #3d3d3d;
587
+ color: #f3f4f6;
588
+ }
589
+ .mpd-num:focus {
590
+ outline: none;
591
+ border-color: #6366f1;
592
+ }
593
+
594
+ .mpd-text {
595
+ padding: 6px 10px;
596
+ font-size: 13px;
597
+ border: 1px solid #d1d5db;
598
+ border-radius: 6px;
599
+ background: #fff;
600
+ color: #111827;
601
+ }
602
+ :global(.dark) .mpd-text {
603
+ background: #2d2d2d;
604
+ border-color: #3d3d3d;
605
+ color: #f3f4f6;
606
+ }
607
+ .mpd-text:focus {
608
+ outline: none;
609
+ border-color: #6366f1;
610
+ }
611
+
612
+ .mpd-switch {
613
+ position: relative;
614
+ display: inline-block;
615
+ width: 34px;
616
+ height: 18px;
617
+ }
618
+ .mpd-switch input {
619
+ opacity: 0;
620
+ width: 0;
621
+ height: 0;
622
+ }
623
+ .mpd-switch-slider {
624
+ position: absolute;
625
+ inset: 0;
626
+ background: #cbd5e1;
627
+ border-radius: 999px;
628
+ cursor: pointer;
629
+ transition: 0.15s;
630
+ }
631
+ .mpd-switch-slider:before {
632
+ position: absolute;
633
+ content: '';
634
+ height: 14px;
635
+ width: 14px;
636
+ left: 2px;
637
+ bottom: 2px;
638
+ background: #fff;
639
+ border-radius: 50%;
640
+ transition: 0.15s;
641
+ }
642
+ .mpd-switch input:checked + .mpd-switch-slider {
643
+ background: #6366f1;
644
+ }
645
+ .mpd-switch input:checked + .mpd-switch-slider:before {
646
+ transform: translateX(16px);
647
+ }
648
+
649
+ .mpd-error {
650
+ padding: 8px 12px;
651
+ background: #fef2f2;
652
+ border: 1px solid #fecaca;
653
+ border-radius: 6px;
654
+ color: #b91c1c;
655
+ font-size: 12px;
656
+ }
657
+ .mpd-test-result {
658
+ padding: 8px 12px;
659
+ border-radius: 6px;
660
+ background: #ecfdf5;
661
+ color: #065f46;
662
+ font-size: 12px;
663
+ border: 1px solid #a7f3d0;
664
+ }
665
+ .mpd-test-result--fail {
666
+ background: #fef2f2;
667
+ color: #b91c1c;
668
+ border-color: #fecaca;
669
+ }
670
+
671
+ .mpd-foot {
672
+ padding: 12px 18px;
673
+ border-top: 1px solid #e5e7eb;
674
+ display: flex;
675
+ justify-content: space-between;
676
+ align-items: center;
677
+ gap: 8px;
678
+ flex-wrap: wrap;
679
+ }
680
+ :global(.dark) .mpd-foot {
681
+ border-top-color: #2d2d2d;
682
+ }
683
+ .mpd-foot-left,
684
+ .mpd-foot-right {
685
+ display: flex;
686
+ gap: 6px;
687
+ }
688
+ .mpd-btn {
689
+ padding: 6px 12px;
690
+ font-size: 12.5px;
691
+ border-radius: 6px;
692
+ border: 1px solid #d1d5db;
693
+ background: #fff;
694
+ color: #374151;
695
+ cursor: pointer;
696
+ white-space: nowrap;
697
+ }
698
+ .mpd-btn:hover:not(:disabled) {
699
+ background: #f9fafb;
700
+ }
701
+ .mpd-btn:disabled {
702
+ opacity: 0.5;
703
+ cursor: not-allowed;
704
+ }
705
+ .mpd-btn--ghost {
706
+ background: transparent;
707
+ }
708
+ .mpd-btn--primary {
709
+ background: #6366f1;
710
+ color: #fff;
711
+ border-color: #6366f1;
712
+ }
713
+ .mpd-btn--primary:hover:not(:disabled) {
714
+ background: #4f46e5;
715
+ }
716
+ .mpd-btn--danger {
717
+ color: #dc2626;
718
+ }
719
+ .mpd-btn--danger:hover:not(:disabled) {
720
+ background: #fef2f2;
721
+ }
722
+
723
+ .mpd-fade-enter-active,
724
+ .mpd-fade-leave-active {
725
+ transition: opacity 0.18s ease;
726
+ }
727
+ .mpd-fade-enter-from,
728
+ .mpd-fade-leave-to {
729
+ opacity: 0;
730
+ }
731
+ .mpd-slide-enter-active,
732
+ .mpd-slide-leave-active {
733
+ transition: transform 0.22s cubic-bezier(0.16, 1, 0.3, 1);
734
+ }
735
+ .mpd-slide-enter-from,
736
+ .mpd-slide-leave-to {
737
+ transform: translateX(100%);
738
+ }
739
+ </style>