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,472 @@
1
+ /**
2
+ * createSpringAgentStartAdapter — returns a fully-typed KnowledgeHubApi backed
3
+ * by the spring-agent-start REST endpoints. External consumers of the backend
4
+ * don't have to hand-write the 20-ish methods of the interface.
5
+ *
6
+ * import { KnowledgeHubApp, createSpringAgentStartAdapter } from '@agent-start/knowledge-hub'
7
+ *
8
+ * const api = createSpringAgentStartAdapter({
9
+ * baseUrl: '/api', // 宿主自己的代理前缀;/agent-start 命名空间由适配器内部拼
10
+ * fetch: window.fetch, // or your host's fetch wrapper
11
+ * headers: () => ({ Authorization: `Bearer ${getToken()}` }),
12
+ * onError: (msg) => antdMessage.error(msg),
13
+ * onSuccess: (msg) => antdMessage.success(msg),
14
+ * })
15
+ *
16
+ * <KnowledgeHubApp :api="api" />
17
+ *
18
+ * Hosts with a different backend implement KnowledgeHubApi directly.
19
+ */
20
+ import type {
21
+ Chunk,
22
+ DatasetCardItem,
23
+ DatasetSummary,
24
+ DocMetadata,
25
+ DocumentRow,
26
+ IndexingTechnique,
27
+ KnowledgeHubApi,
28
+ RecallHit,
29
+ RecentQuery,
30
+ } from '../types';
31
+
32
+ type FetchLike = typeof fetch;
33
+ type HeadersProvider = () => Record<string, string> | Promise<Record<string, string>>;
34
+
35
+ export interface SpringAgentStartAdapterOptions {
36
+ /**
37
+ * 宿主的代理前缀(默认 `/api`)。适配器内部会自动拼上 `/agent-start`
38
+ * 命名空间 —— 那是 spring-agent-web 给控制器加的固定前缀,属于组件与
39
+ * 后端约定的实现细节,宿主不用关心。所以最终请求 URL 是
40
+ * `${baseUrl}/agent-start/xxx`。若代理不叫 /api,改成 `/xxx`;若完全直连
41
+ * 后端(无代理),传 `''` 即可,最终就是 `/agent-start/xxx`。
42
+ */
43
+ baseUrl?: string;
44
+ /** Optional custom fetch (e.g. host's axios wrapper turned into a fetch). */
45
+ fetch?: FetchLike;
46
+ /**
47
+ * Extra headers to send on every call. Called for each request — return a
48
+ * fresh token, tenant id, correlation id, etc. Can be async.
49
+ */
50
+ headers?: HeadersProvider;
51
+ /** Called with the human-readable error message on any 4xx/5xx. */
52
+ onError?(msg: string): void;
53
+ /** Called with success messages emitted by the adapter itself. */
54
+ onSuccess?(msg: string): void;
55
+ /** Override the ⌘ 访问 API button behaviour. Default copies the dataset URL. */
56
+ onCopyApi?(datasetId: string): void;
57
+ /**
58
+ * Wired to {@code KnowledgeHubApp}'s built-in "先注册 Embedding 模型"
59
+ * empty-state nudge. The hub renders the card whenever the initial fetch
60
+ * returns zero embedding models; clicking "去配置模型" fires this callback
61
+ * so the host can navigate to its model-management route. Leaving it
62
+ * undefined hides the button but keeps the informational card.
63
+ */
64
+ onGoToEmbeddingSetup?(): void;
65
+ /**
66
+ * Request timeout in ms. Default: 60_000. Uploads use `uploadTimeoutMs`.
67
+ */
68
+ timeoutMs?: number;
69
+ uploadTimeoutMs?: number;
70
+ }
71
+
72
+ interface Envelope<T> {
73
+ code: string;
74
+ message?: string;
75
+ data: T;
76
+ }
77
+
78
+ const AGENT_START_NAMESPACE = '/agent-start';
79
+
80
+ export function createSpringAgentStartAdapter(
81
+ opts: SpringAgentStartAdapterOptions = {},
82
+ ): KnowledgeHubApi {
83
+ // Compose {proxy}{namespace}. Namespace is fixed; only proxy is host-configurable.
84
+ const baseUrl = `${(opts.baseUrl ?? '/api').replace(/\/+$/, '')}${AGENT_START_NAMESPACE}`;
85
+ const doFetch: FetchLike = opts.fetch ?? ((...args) => fetch(...args));
86
+ const timeoutMs = opts.timeoutMs ?? 60_000;
87
+ const uploadTimeoutMs = opts.uploadTimeoutMs ?? 300_000;
88
+
89
+ async function extraHeaders(): Promise<Record<string, string>> {
90
+ return opts.headers ? await opts.headers() : {};
91
+ }
92
+
93
+ async function call<T>(
94
+ path: string,
95
+ init: RequestInit = {},
96
+ timeout = timeoutMs,
97
+ ): Promise<T> {
98
+ const controller = new AbortController();
99
+ const timer = setTimeout(() => controller.abort(), timeout);
100
+ try {
101
+ const headers = {
102
+ 'Content-Type': 'application/json',
103
+ ...(await extraHeaders()),
104
+ ...(init.headers ?? {}),
105
+ };
106
+ const res = await doFetch(`${baseUrl}${path}`, {
107
+ ...init,
108
+ headers,
109
+ signal: controller.signal,
110
+ });
111
+ if (!res.ok) {
112
+ // Uniform-envelope error body if the backend emitted one
113
+ let msg = `${res.status} ${res.statusText}`;
114
+ try {
115
+ const body = (await res.json()) as { message?: string };
116
+ if (body?.message) msg = body.message;
117
+ } catch {
118
+ // ignore parse errors
119
+ }
120
+ const err = new Error(msg);
121
+ opts.onError?.(msg);
122
+ throw err;
123
+ }
124
+ const env = (await res.json()) as Envelope<T>;
125
+ if (env.code !== 'ok') {
126
+ const msg = env.message ?? env.code;
127
+ opts.onError?.(msg);
128
+ throw new Error(msg);
129
+ }
130
+ return env.data;
131
+ } finally {
132
+ clearTimeout(timer);
133
+ }
134
+ }
135
+
136
+ async function upload(datasetId: string, file: File): Promise<void> {
137
+ const controller = new AbortController();
138
+ const timer = setTimeout(() => controller.abort(), uploadTimeoutMs);
139
+ try {
140
+ const form = new FormData();
141
+ form.append('file', file);
142
+ const extra = await extraHeaders();
143
+ // Don't set Content-Type on multipart — the browser fills it with the boundary.
144
+ const res = await doFetch(
145
+ `${baseUrl}/datasets/${datasetId}/documents/upload`,
146
+ {
147
+ body: form,
148
+ headers: extra,
149
+ method: 'POST',
150
+ signal: controller.signal,
151
+ },
152
+ );
153
+ if (!res.ok) {
154
+ let msg = `${res.status} ${res.statusText}`;
155
+ try {
156
+ const body = (await res.json()) as { message?: string };
157
+ if (body?.message) msg = body.message;
158
+ } catch {
159
+ // ignore
160
+ }
161
+ opts.onError?.(msg);
162
+ throw new Error(msg);
163
+ }
164
+ } finally {
165
+ clearTimeout(timer);
166
+ }
167
+ }
168
+
169
+ /**
170
+ * POST /datasets/preview-chunks — multipart body: file + rule (JSON string).
171
+ * Backend runs extract → clean → chunk on the file with the caller's rule
172
+ * and returns the first {@code limit} chunks plus the full count. Nothing
173
+ * persists. Same reader / cleaner / chunker as real ingestion, so the
174
+ * preview is authoritative.
175
+ */
176
+ async function previewChunks(
177
+ file: File,
178
+ rule: unknown,
179
+ limit = 10,
180
+ ): Promise<{
181
+ totalChunks: number;
182
+ chunks: Array<{ index: number; text: string; tokens: number }>;
183
+ }> {
184
+ const controller = new AbortController();
185
+ const timer = setTimeout(() => controller.abort(), uploadTimeoutMs);
186
+ try {
187
+ const form = new FormData();
188
+ form.append('file', file);
189
+ form.append(
190
+ 'rule',
191
+ new Blob([JSON.stringify(rule ?? {})], { type: 'application/json' }),
192
+ );
193
+ const extra = await extraHeaders();
194
+ const res = await doFetch(
195
+ `${baseUrl}/datasets/preview-chunks?limit=${limit}`,
196
+ {
197
+ body: form,
198
+ headers: extra,
199
+ method: 'POST',
200
+ signal: controller.signal,
201
+ },
202
+ );
203
+ if (!res.ok) {
204
+ let msg = `${res.status} ${res.statusText}`;
205
+ try {
206
+ const body = (await res.json()) as { message?: string };
207
+ if (body?.message) msg = body.message;
208
+ } catch {
209
+ // ignore
210
+ }
211
+ opts.onError?.(msg);
212
+ throw new Error(msg);
213
+ }
214
+ const env = (await res.json()) as {
215
+ code: string;
216
+ message?: string;
217
+ data: {
218
+ totalChunks: number;
219
+ chunks: Array<{ index: number; text: string; tokens: number }>;
220
+ };
221
+ };
222
+ if (env.code !== 'ok') {
223
+ opts.onError?.(env.message ?? env.code);
224
+ throw new Error(env.message ?? env.code);
225
+ }
226
+ return env.data;
227
+ } finally {
228
+ clearTimeout(timer);
229
+ }
230
+ }
231
+
232
+ // -- adapters that need to shape the backend response into the hub types
233
+ function normalizeDoc(d: any): DocumentRow {
234
+ return {
235
+ id: d.id,
236
+ name: d.name,
237
+ chunkMode: d.sourceType === 'text' ? '自定义' : d.sourceType,
238
+ wordCount: d.wordCount ?? 0,
239
+ hitCount: 0,
240
+ uploadedAt: d.createdAt,
241
+ status:
242
+ d.status === 'COMPLETED'
243
+ ? 'AVAILABLE'
244
+ : d.status === 'FAILED'
245
+ ? 'FAILED'
246
+ : 'PROCESSING',
247
+ enabled: !!d.enabled,
248
+ };
249
+ }
250
+
251
+ function normalizeChunk(s: any): Chunk {
252
+ return {
253
+ id: s.id,
254
+ position: s.position,
255
+ content: s.content,
256
+ tokenCount: s.tokenCount,
257
+ charCount: s.content?.length ?? 0,
258
+ hitCount: 0,
259
+ enabled: !!s.enabled,
260
+ keywords: s.keywords
261
+ ? String(s.keywords)
262
+ .split(/\s+/)
263
+ .filter((k: string) => k.length > 0)
264
+ : [],
265
+ };
266
+ }
267
+
268
+ const api: KnowledgeHubApi = {
269
+ // ---- datasets
270
+ listDatasets: () =>
271
+ call<DatasetCardItem[]>('/datasets').then((rows) =>
272
+ rows.map((r: any) => ({
273
+ id: r.id,
274
+ name: r.name,
275
+ description: r.description,
276
+ documentCount: r.documentCount ?? 0,
277
+ segmentCount: r.segmentCount ?? 0,
278
+ indexingTechnique: r.indexingTechnique as IndexingTechnique,
279
+ updatedAt: r.updatedAt,
280
+ })),
281
+ ),
282
+ getDataset: (id) => call<DatasetSummary>(`/datasets/${id}`),
283
+ createDataset: (req) =>
284
+ call<DatasetCardItem>('/datasets', {
285
+ body: JSON.stringify(req),
286
+ method: 'POST',
287
+ }),
288
+ updateDataset: (id, patch) =>
289
+ call<DatasetSummary>(`/datasets/${id}`, {
290
+ body: JSON.stringify(patch),
291
+ method: 'PUT',
292
+ }),
293
+ deleteDataset: (id) =>
294
+ call<void>(`/datasets/${id}`, { method: 'DELETE' }),
295
+
296
+ // ---- documents
297
+ listDocuments: async (datasetId) => {
298
+ const rows = await call<any[]>(`/datasets/${datasetId}/documents`);
299
+ return rows.map(normalizeDoc);
300
+ },
301
+ uploadDocument: (datasetId, file) => upload(datasetId, file),
302
+ deleteDocument: (datasetId, docId) =>
303
+ call<void>(`/datasets/${datasetId}/documents/${docId}`, {
304
+ method: 'DELETE',
305
+ }),
306
+ previewChunks: (file, rule, limit) => previewChunks(file, rule, limit),
307
+
308
+ // ---- segments
309
+ listSegments: async (datasetId, docId, page = 1, pageSize = 20) => {
310
+ const rows = await call<any[]>(
311
+ `/datasets/${datasetId}/documents/${docId}/segments?page=${page}&pageSize=${pageSize}`,
312
+ );
313
+ return rows.map(normalizeChunk);
314
+ },
315
+ loadDocumentMetadata: async (datasetId, docId) => {
316
+ const [doc, ds, segRows] = await Promise.all([
317
+ call<any>(`/datasets/${datasetId}/documents/${docId}`),
318
+ call<any>(`/datasets/${datasetId}`),
319
+ call<any[]>(
320
+ `/datasets/${datasetId}/documents/${docId}/segments?pageSize=200`,
321
+ ),
322
+ ]);
323
+ let processRule: any = {};
324
+ try {
325
+ if (ds?.processRuleJson) processRule = JSON.parse(ds.processRuleJson);
326
+ } catch {
327
+ processRule = {};
328
+ }
329
+ const totalChars = segRows.reduce(
330
+ (n, c) => n + (c.content?.length ?? 0),
331
+ 0,
332
+ );
333
+ const totalTokens = segRows.reduce(
334
+ (n, c) => n + (c.tokenCount ?? 0),
335
+ 0,
336
+ );
337
+ const meta: DocMetadata = {
338
+ fileName: doc?.name,
339
+ kind: doc?.sourceType,
340
+ uploadedAt: doc?.createdAt,
341
+ parsedAt: doc?.updatedAt,
342
+ embeddedAt: doc?.updatedAt,
343
+ chunkMode:
344
+ processRule.template === 'PARENT_CHILD' ? '父子分段' : '自定义',
345
+ chunkMaxSize: processRule.chunkTokens ?? 1024,
346
+ totalChars,
347
+ totalChunks: segRows.length,
348
+ avgChunkChars:
349
+ segRows.length > 0 ? Math.round(totalChars / segRows.length) : 0,
350
+ avgEmbedMs: 2.5,
351
+ totalTokens,
352
+ };
353
+ return meta;
354
+ },
355
+ updateSegment: (datasetId, docId, segId, content) =>
356
+ call<any>(
357
+ `/datasets/${datasetId}/documents/${docId}/segments/${segId}`,
358
+ {
359
+ body: JSON.stringify({ content }),
360
+ method: 'PUT',
361
+ },
362
+ ).then(normalizeChunk),
363
+ deleteSegment: (datasetId, docId, segId) =>
364
+ call<void>(
365
+ `/datasets/${datasetId}/documents/${docId}/segments/${segId}`,
366
+ { method: 'DELETE' },
367
+ ),
368
+ setSegmentEnabled: (datasetId, docId, segId, enabled) =>
369
+ call<any>(
370
+ `/datasets/${datasetId}/documents/${docId}/segments/${segId}/enabled`,
371
+ {
372
+ body: JSON.stringify({ enabled }),
373
+ method: 'PUT',
374
+ },
375
+ ).then(normalizeChunk),
376
+ appendSegment: (datasetId, docId, content) =>
377
+ call<any>(
378
+ `/datasets/${datasetId}/documents/${docId}/segments`,
379
+ {
380
+ body: JSON.stringify({ content }),
381
+ method: 'POST',
382
+ },
383
+ ).then(normalizeChunk),
384
+
385
+ // ---- retrieval
386
+ retrieve: async (datasetId, req) => {
387
+ const rows = await call<any[]>(`/datasets/${datasetId}/retrieve`, {
388
+ body: JSON.stringify({
389
+ method: req.method,
390
+ query: req.query,
391
+ topK: req.topK ?? 10,
392
+ }),
393
+ method: 'POST',
394
+ });
395
+ return rows.map<RecallHit>((r) => ({
396
+ segmentId: r.segmentId,
397
+ content: r.content,
398
+ position: r.position,
399
+ score: r.score,
400
+ }));
401
+ },
402
+ listRecallHistory: async (datasetId, limit) => {
403
+ const rows = await call<any[]>(
404
+ `/datasets/${datasetId}/hit-testing/history?limit=${limit ?? 20}`,
405
+ );
406
+ return rows.map<RecentQuery>((r) => ({
407
+ id: r.id,
408
+ query: r.query,
409
+ method: r.method ?? 'HYBRID',
410
+ hitCount: r.hitCount ?? 0,
411
+ at: r.createdAt ?? '',
412
+ }));
413
+ },
414
+
415
+ // ---- models
416
+ listEmbeddingModels: async () => {
417
+ const rows = await call<any[]>('/models?type=TEXT_EMBEDDING');
418
+ // /models/defaults returns { TEXT_EMBEDDING: ModelEntity | null } — fetch
419
+ // in parallel with the list so we can flag the default row inline.
420
+ // Failing softly means non-default UX still works on backends that don't
421
+ // publish defaults.
422
+ let defaultId: null | string = null;
423
+ try {
424
+ const defaults = await call<Record<string, any>>('/models/defaults');
425
+ defaultId = defaults?.TEXT_EMBEDDING?.id ?? null;
426
+ } catch {
427
+ // ignore — panel just won't preselect anything
428
+ }
429
+ return rows.map((m) => ({
430
+ id: m.id,
431
+ label: `${m.providerName} · ${m.modelName}`,
432
+ providerName: m.providerName,
433
+ providerLabel: m.providerName,
434
+ isDefault: defaultId != null && m.id === defaultId,
435
+ }));
436
+ },
437
+ getDefaultEmbeddingModelId: async () => {
438
+ try {
439
+ const defaults = await call<Record<string, any>>('/models/defaults');
440
+ return defaults?.TEXT_EMBEDDING?.id ?? null;
441
+ } catch {
442
+ return null;
443
+ }
444
+ },
445
+ listRerankModels: async () => {
446
+ // Rerank model registry is optional — treat any 404/error as "none registered"
447
+ // rather than a fatal so the wizard still opens on fresh installs.
448
+ try {
449
+ const rows = await call<any[]>('/models?type=RERANK');
450
+ return rows.map((m) => ({
451
+ id: m.id,
452
+ label: `${m.providerName} · ${m.modelName}`,
453
+ }));
454
+ } catch {
455
+ return [];
456
+ }
457
+ },
458
+
459
+ // ---- hooks (delegate to host)
460
+ onSuccess: opts.onSuccess,
461
+ onError: opts.onError,
462
+ onCopyApi:
463
+ opts.onCopyApi ??
464
+ ((id) => {
465
+ const url = `${window.location.origin}${baseUrl}/datasets/${id}`;
466
+ void navigator.clipboard.writeText(url);
467
+ }),
468
+ onGoToEmbeddingSetup: opts.onGoToEmbeddingSetup,
469
+ };
470
+
471
+ return api;
472
+ }