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,802 @@
1
+ <script setup lang="ts">
2
+ /**
3
+ * DocumentChunksView — chunks browser for a single document (screenshot 7).
4
+ *
5
+ * Header: doc name + enabled toggle + more menu.
6
+ * Left main: filter dropdown + search + view-toggle, then a paginated grid of
7
+ * chunk cards each showing SEG-NN tag, char count, hit count, content, tags.
8
+ * Right metadata pane (collapsible-ish): 元数据 header with 开放智能标签 button;
9
+ * 文档信息 (name/kind/size/upload/parse/embed dates), 技术参数 (chunk mode,
10
+ * max size, char count, chunk count, avg time, tokens).
11
+ */
12
+ import { computed, ref } from 'vue';
13
+
14
+ import type { Chunk, DocMetadata } from '../types';
15
+
16
+ interface Props {
17
+ documentName: string;
18
+ documentEnabled: boolean;
19
+ chunks: Chunk[];
20
+ metadata?: DocMetadata;
21
+ loading?: boolean;
22
+ /** If false, the "+ 添加分段" button is hidden (hub has no appendSegment). */
23
+ canAppend?: boolean;
24
+ /** 1-indexed page number the parent is currently displaying. */
25
+ page?: number;
26
+ pageSize?: number;
27
+ /** Server-reported total number of segments in the document. */
28
+ total?: number;
29
+ }
30
+
31
+ const props = withDefaults(defineProps<Props>(), {
32
+ loading: false,
33
+ metadata: () => ({}),
34
+ canAppend: true,
35
+ page: 1,
36
+ pageSize: 20,
37
+ total: 0,
38
+ });
39
+
40
+ const emit = defineEmits<{
41
+ (e: 'toggle-doc-enabled', v: boolean): void;
42
+ (e: 'edit-chunk', c: Chunk): void;
43
+ (e: 'toggle-chunk', c: Chunk, next: boolean): void;
44
+ (e: 'add-chunk'): void;
45
+ (e: 'delete-chunk', c: Chunk): void;
46
+ (e: 'update:page', p: number): void;
47
+ (e: 'update:pageSize', s: number): void;
48
+ }>();
49
+
50
+ const search = ref('');
51
+ const filter = ref<'ALL' | 'DISABLED' | 'ENABLED'>('ALL');
52
+ const showMeta = ref(true);
53
+
54
+ /**
55
+ * Ids of chunks the user asked to see in full. Client-side only.
56
+ *
57
+ * `allExpanded` is a separate toggle so "全部展开" doesn't need to enumerate
58
+ * every id up-front (which is expensive on large docs) — when it is on, we
59
+ * treat every card as expanded regardless of the set. Toggling an individual
60
+ * card while `allExpanded` is on flips it back to false + seeds `expanded`
61
+ * with everything currently visible so the interaction stays intuitive.
62
+ */
63
+ const expanded = ref<Set<string>>(new Set());
64
+ const allExpanded = ref(false);
65
+ function isExpanded(id: string): boolean {
66
+ return allExpanded.value || expanded.value.has(id);
67
+ }
68
+ function toggleExpand(id: string) {
69
+ if (allExpanded.value) {
70
+ // switch to per-card mode with everything but this one still expanded
71
+ const next = new Set(filtered.value.map((c) => c.id));
72
+ next.delete(id);
73
+ expanded.value = next;
74
+ allExpanded.value = false;
75
+ return;
76
+ }
77
+ if (expanded.value.has(id)) expanded.value.delete(id);
78
+ else expanded.value.add(id);
79
+ expanded.value = new Set(expanded.value);
80
+ }
81
+ function expandAll() {
82
+ allExpanded.value = true;
83
+ expanded.value = new Set();
84
+ }
85
+ function collapseAll() {
86
+ allExpanded.value = false;
87
+ expanded.value = new Set();
88
+ }
89
+
90
+ const filtered = computed(() => {
91
+ const q = search.value.trim().toLowerCase();
92
+ return props.chunks.filter((c) => {
93
+ if (filter.value === 'ENABLED' && !c.enabled) return false;
94
+ if (filter.value === 'DISABLED' && c.enabled) return false;
95
+ if (q && !c.content.toLowerCase().includes(q)) return false;
96
+ return true;
97
+ });
98
+ });
99
+
100
+ /**
101
+ * Rough cutoff — anything longer than this needs the "展开" affordance. Kept in
102
+ * sync with the CSS `.kh-chunk-body:not(.kh-chunk-body-open)` max-height so the
103
+ * button doesn't appear on chunks that already fit.
104
+ */
105
+ const COLLAPSED_CHAR_THRESHOLD = 260;
106
+ function needsToggle(c: Chunk): boolean {
107
+ return (c.content?.length ?? 0) > COLLAPSED_CHAR_THRESHOLD;
108
+ }
109
+
110
+ function docToggle(next: boolean) {
111
+ emit('toggle-doc-enabled', next);
112
+ }
113
+
114
+ function fmtNum(n?: number): string {
115
+ if (n == null) return '';
116
+ if (n >= 10_000) return `${(n / 1000).toFixed(1)}k`;
117
+ return String(n);
118
+ }
119
+
120
+ // ---- pagination
121
+ const totalPages = computed(() =>
122
+ Math.max(1, Math.ceil((props.total || props.chunks.length) / props.pageSize)),
123
+ );
124
+ function goPage(p: number) {
125
+ const next = Math.max(1, Math.min(p, totalPages.value));
126
+ if (next !== props.page) emit('update:page', next);
127
+ }
128
+ function changePageSize(s: number) {
129
+ if (s !== props.pageSize) emit('update:pageSize', s);
130
+ }
131
+ </script>
132
+
133
+ <template>
134
+ <div class="kh-chunks">
135
+ <!-- Body: chunks + metadata pane (no separate action bar — everything
136
+ above the list lives in the compact toolbar row inside .kh-chunks-main
137
+ so the chunks start right at the top of the drawer content). -->
138
+ <div class="kh-chunks-body">
139
+ <!-- LEFT: chunks -->
140
+ <div class="kh-chunks-main">
141
+ <div class="kh-chunks-toolbar">
142
+ <div class="kh-chunks-toolbar-left">
143
+ <span class="kh-chunks-count">
144
+ {{ total || chunks.length }} 分段
145
+ <span v-if="metadata.totalChars">
146
+ · {{ fmtNum(metadata.totalChars) }} 字符
147
+ · 平均分段大小 {{ Math.round(metadata.avgChunkChars ?? 0) }}
148
+ </span>
149
+ </span>
150
+ </div>
151
+ <div class="kh-chunks-toolbar-right">
152
+ <select v-model="filter" class="kh-doctab-select">
153
+ <option value="ALL">全部</option>
154
+ <option value="ENABLED">已启用</option>
155
+ <option value="DISABLED">已停用</option>
156
+ </select>
157
+ <div class="kh-doctab-search">
158
+ <span class="kh-doctab-search-icon">🔍</span>
159
+ <input
160
+ v-model="search"
161
+ class="kh-doctab-search-input"
162
+ placeholder="搜索"
163
+ />
164
+ </div>
165
+ <!-- Text-style bulk actions — kept compact per user request so
166
+ they don't visually compete with the primary "+ 添加分段" CTA. -->
167
+ <button class="kh-chunks-link" @click="expandAll">全部展开</button>
168
+ <span class="kh-chunks-link-sep">·</span>
169
+ <button class="kh-chunks-link" @click="collapseAll">全部收起</button>
170
+ <button
171
+ v-if="canAppend"
172
+ class="kh-btn kh-btn-primary"
173
+ @click="emit('add-chunk')"
174
+ >
175
+ + 添加分段
176
+ </button>
177
+ <label
178
+ class="kh-switch"
179
+ :title="documentEnabled ? '已启用 · 点击停用' : '已停用 · 点击启用'"
180
+ >
181
+ <input
182
+ type="checkbox"
183
+ :checked="documentEnabled"
184
+ @change="(e) => docToggle((e.target as HTMLInputElement).checked)"
185
+ />
186
+ <span class="kh-switch-slider" />
187
+ </label>
188
+ <span
189
+ class="kh-chunks-status"
190
+ :style="{ color: documentEnabled ? '#059669' : '#dc2626' }"
191
+ >
192
+ {{ documentEnabled ? '可用' : '已停用' }}
193
+ </span>
194
+ </div>
195
+ </div>
196
+
197
+ <div v-if="loading" class="kh-chunks-empty">加载中...</div>
198
+ <div v-else-if="filtered.length === 0" class="kh-chunks-empty">
199
+ 没有匹配的分段
200
+ </div>
201
+
202
+ <div class="kh-chunk-grid">
203
+ <div
204
+ v-for="c in filtered"
205
+ :key="c.id"
206
+ class="kh-chunk-card"
207
+ @click="emit('edit-chunk', c)"
208
+ >
209
+ <div class="kh-chunk-head">
210
+ <span class="kh-chunk-tag">
211
+ SEG-{{ String(c.position + 1).padStart(2, '0') }}
212
+ </span>
213
+ <span class="kh-chunk-meta">
214
+ {{ c.charCount ?? c.content.length }} 字符
215
+ </span>
216
+ <span class="kh-chunk-meta">
217
+ {{ c.hitCount ?? 0 }} 命中数
218
+ </span>
219
+ <span class="kh-chunk-flex" />
220
+ <!-- Inline expand affordance — sits with the "已启用"/字符 metadata
221
+ so it doesn't eat a full row per card. Only shown when the
222
+ body would actually be clipped. -->
223
+ <button
224
+ v-if="needsToggle(c)"
225
+ class="kh-chunk-expand-inline"
226
+ :title="isExpanded(c.id) ? '收起' : '展开'"
227
+ @click.stop="toggleExpand(c.id)"
228
+ >
229
+ {{ isExpanded(c.id) ? '收起 ▲' : '展开 ▼' }}
230
+ </button>
231
+ <span
232
+ v-if="c.enabled"
233
+ class="kh-chunk-badge kh-chunk-badge-on"
234
+ >
235
+ 已启用
236
+ </span>
237
+ <span v-else class="kh-chunk-badge kh-chunk-badge-off">
238
+ 已停用
239
+ </span>
240
+ </div>
241
+ <div
242
+ class="kh-chunk-body"
243
+ :class="{ 'kh-chunk-body-open': isExpanded(c.id) }"
244
+ >
245
+ {{ c.content }}
246
+ </div>
247
+ <div
248
+ v-if="c.keywords && c.keywords.length > 0"
249
+ class="kh-chunk-tags"
250
+ >
251
+ <span
252
+ v-for="k in c.keywords.slice(0, 6)"
253
+ :key="k"
254
+ class="kh-chunk-keyword"
255
+ >
256
+ {{ k }}
257
+ </span>
258
+ </div>
259
+ </div>
260
+ </div>
261
+
262
+ <!-- Pagination footer -->
263
+ <div v-if="total > 0" class="kh-chunks-foot">
264
+ <div class="kh-chunks-pager">
265
+ <button
266
+ class="kh-pager-btn"
267
+ :disabled="page <= 1"
268
+ @click="goPage(page - 1)"
269
+ >
270
+
271
+ </button>
272
+ <span class="kh-pager-page">{{ page }} / {{ totalPages }}</span>
273
+ <button
274
+ class="kh-pager-btn"
275
+ :disabled="page >= totalPages"
276
+ @click="goPage(page + 1)"
277
+ >
278
+
279
+ </button>
280
+ </div>
281
+ <div class="kh-pager-info">共 {{ total }} 条</div>
282
+ <div class="kh-pager-sizes">
283
+ <span class="kh-pager-sizes-label">每页</span>
284
+ <button
285
+ v-for="s in [10, 20, 50]"
286
+ :key="s"
287
+ class="kh-pager-size"
288
+ :class="{ 'kh-pager-size-active': pageSize === s }"
289
+ @click="changePageSize(s)"
290
+ >
291
+ {{ s }}
292
+ </button>
293
+ </div>
294
+ </div>
295
+ </div>
296
+
297
+ <!-- RIGHT: metadata pane -->
298
+ <div v-if="showMeta" class="kh-chunks-meta">
299
+ <div class="kh-meta-header">
300
+ <div class="kh-meta-title-row">
301
+ <span class="kh-meta-title">元数据</span>
302
+ <button class="kh-meta-collapse" @click="showMeta = false">›</button>
303
+ </div>
304
+ <div class="kh-meta-sub">
305
+ 元数据能对文档进行结构化描述、辅助过滤检索、支持精细化数据管理。
306
+ </div>
307
+ <button class="kh-meta-btn">开放智能标签</button>
308
+ </div>
309
+
310
+ <div class="kh-meta-group">
311
+ <div class="kh-meta-group-title">文档信息</div>
312
+ <div class="kh-meta-row">
313
+ <span class="kh-meta-key">原始文件名</span>
314
+ <span class="kh-meta-val">{{ metadata.fileName ?? '-' }}</span>
315
+ </div>
316
+ <div class="kh-meta-row">
317
+ <span class="kh-meta-key">上传日期</span>
318
+ <span class="kh-meta-val">{{ metadata.uploadedAt ?? '-' }}</span>
319
+ </div>
320
+ <div class="kh-meta-row">
321
+ <span class="kh-meta-key">最后更新</span>
322
+ <span class="kh-meta-val">{{ metadata.embeddedAt ?? metadata.parsedAt ?? '-' }}</span>
323
+ </div>
324
+ <div class="kh-meta-row">
325
+ <span class="kh-meta-key">数据来源</span>
326
+ <span class="kh-meta-val">{{ metadata.kind ?? '文本上传' }}</span>
327
+ </div>
328
+ </div>
329
+
330
+ <div class="kh-meta-group">
331
+ <div class="kh-meta-group-title">技术参数</div>
332
+ <div class="kh-meta-row">
333
+ <span class="kh-meta-key">分段模式</span>
334
+ <span class="kh-meta-val">{{ metadata.chunkMode ?? '自定义' }}</span>
335
+ </div>
336
+ <div class="kh-meta-row">
337
+ <span class="kh-meta-key">分段最大长度</span>
338
+ <span class="kh-meta-val">{{ metadata.chunkMaxSize ?? '-' }}</span>
339
+ </div>
340
+ <div class="kh-meta-row">
341
+ <span class="kh-meta-key">字符数</span>
342
+ <span class="kh-meta-val">{{ fmtNum(metadata.totalChars) }} characters</span>
343
+ </div>
344
+ <div class="kh-meta-row">
345
+ <span class="kh-meta-key">平均分段大小</span>
346
+ <span class="kh-meta-val">
347
+ {{ metadata.totalChunks }} paragraphs
348
+ </span>
349
+ </div>
350
+ <div class="kh-meta-row">
351
+ <span class="kh-meta-key">平均嵌入时间</span>
352
+ <span class="kh-meta-val">{{ metadata.avgEmbedMs ?? '-' }} ms</span>
353
+ </div>
354
+ <div class="kh-meta-row">
355
+ <span class="kh-meta-key">嵌入耗费</span>
356
+ <span class="kh-meta-val">
357
+ {{ fmtNum(metadata.totalTokens) }} tokens
358
+ </span>
359
+ </div>
360
+ </div>
361
+ </div>
362
+ <button
363
+ v-else
364
+ class="kh-meta-toggle"
365
+ @click="showMeta = true"
366
+ >
367
+ ‹ 元数据
368
+ </button>
369
+ </div>
370
+ </div>
371
+ </template>
372
+
373
+ <style scoped>
374
+ .kh-chunks {
375
+ display: flex;
376
+ flex-direction: column;
377
+ height: 100%;
378
+ }
379
+
380
+ .kh-chunks-status {
381
+ font-size: 12px;
382
+ }
383
+
384
+ .kh-btn {
385
+ padding: 5px 12px;
386
+ border: 1px solid #e2e8f0;
387
+ border-radius: 6px;
388
+ background: #fff;
389
+ font-size: 12px;
390
+ color: #475569;
391
+ cursor: pointer;
392
+ }
393
+ .kh-btn-secondary:hover {
394
+ background: #f8fafc;
395
+ }
396
+ .kh-btn-primary {
397
+ border-color: transparent;
398
+ background: linear-gradient(135deg, #6366f1, #4f46e5);
399
+ color: #fff;
400
+ box-shadow: 0 2px 4px rgba(79, 70, 229, 0.25);
401
+ }
402
+ .kh-btn-primary:hover {
403
+ transform: translateY(-1px);
404
+ }
405
+
406
+ /* Switch */
407
+ .kh-switch {
408
+ position: relative;
409
+ display: inline-block;
410
+ width: 32px;
411
+ height: 18px;
412
+ cursor: pointer;
413
+ }
414
+ .kh-switch input {
415
+ display: none;
416
+ }
417
+ .kh-switch-slider {
418
+ position: absolute;
419
+ inset: 0;
420
+ background: #cbd5e1;
421
+ border-radius: 9px;
422
+ transition: background 0.15s;
423
+ }
424
+ .kh-switch-slider::before {
425
+ content: '';
426
+ position: absolute;
427
+ top: 2px;
428
+ left: 2px;
429
+ width: 14px;
430
+ height: 14px;
431
+ background: #fff;
432
+ border-radius: 50%;
433
+ transition: transform 0.15s;
434
+ }
435
+ .kh-switch input:checked + .kh-switch-slider {
436
+ background: #4f46e5;
437
+ }
438
+ .kh-switch input:checked + .kh-switch-slider::before {
439
+ transform: translateX(14px);
440
+ }
441
+ /* Body — starts flush with the drawer topbar (no wasted whitespace on top). */
442
+ .kh-chunks-body {
443
+ flex: 1;
444
+ display: grid;
445
+ grid-template-columns: minmax(0, 1fr) 320px;
446
+ gap: 12px;
447
+ padding: 8px 20px 16px;
448
+ overflow: hidden;
449
+ }
450
+
451
+ /* Main pane */
452
+ .kh-chunks-main {
453
+ min-width: 0;
454
+ display: flex;
455
+ flex-direction: column;
456
+ gap: 12px;
457
+ overflow-y: auto;
458
+ }
459
+ .kh-chunks-toolbar {
460
+ display: flex;
461
+ justify-content: space-between;
462
+ align-items: center;
463
+ gap: 12px;
464
+ flex-wrap: wrap;
465
+ }
466
+ .kh-chunks-count {
467
+ font-size: 12px;
468
+ color: #64748b;
469
+ }
470
+ .kh-chunks-toolbar-right {
471
+ display: flex;
472
+ gap: 8px;
473
+ align-items: center;
474
+ flex-wrap: wrap;
475
+ justify-content: flex-end;
476
+ }
477
+ .kh-doctab-select {
478
+ padding: 5px 22px 5px 10px;
479
+ border: 1px solid #e2e8f0;
480
+ border-radius: 6px;
481
+ background: #fff;
482
+ font-size: 12px;
483
+ color: #475569;
484
+ outline: none;
485
+ cursor: pointer;
486
+ }
487
+ .kh-doctab-select:focus {
488
+ border-color: #6366f1;
489
+ }
490
+ .kh-doctab-search {
491
+ position: relative;
492
+ }
493
+ .kh-doctab-search-icon {
494
+ position: absolute;
495
+ left: 8px;
496
+ top: 50%;
497
+ transform: translateY(-50%);
498
+ font-size: 11px;
499
+ color: #94a3b8;
500
+ }
501
+ .kh-doctab-search-input {
502
+ padding: 5px 10px 5px 26px;
503
+ border: 1px solid #e2e8f0;
504
+ border-radius: 6px;
505
+ background: #fff;
506
+ font-size: 12px;
507
+ outline: none;
508
+ width: 160px;
509
+ }
510
+ .kh-doctab-search-input:focus {
511
+ border-color: #6366f1;
512
+ }
513
+ .kh-chunks-empty {
514
+ padding: 40px 20px;
515
+ text-align: center;
516
+ color: #94a3b8;
517
+ font-size: 12px;
518
+ }
519
+
520
+ .kh-chunk-grid {
521
+ display: flex;
522
+ flex-direction: column;
523
+ gap: 10px;
524
+ }
525
+ .kh-chunk-card {
526
+ padding: 12px 14px;
527
+ background: #fff;
528
+ border: 1px solid #e2e8f0;
529
+ border-radius: 10px;
530
+ cursor: pointer;
531
+ transition:
532
+ border-color 0.15s,
533
+ box-shadow 0.15s;
534
+ }
535
+ .kh-chunk-card:hover {
536
+ border-color: #cbd5e1;
537
+ box-shadow: 0 3px 10px rgba(15, 23, 42, 0.06);
538
+ }
539
+ .kh-chunk-head {
540
+ display: flex;
541
+ align-items: center;
542
+ gap: 8px;
543
+ font-size: 11px;
544
+ color: #64748b;
545
+ margin-bottom: 8px;
546
+ }
547
+ .kh-chunk-tag {
548
+ padding: 2px 6px;
549
+ background: #eef2ff;
550
+ color: #4338ca;
551
+ border-radius: 4px;
552
+ font-weight: 600;
553
+ font-size: 10px;
554
+ }
555
+ .kh-chunk-meta {
556
+ color: #94a3b8;
557
+ font-size: 10px;
558
+ }
559
+ .kh-chunk-flex {
560
+ flex: 1;
561
+ }
562
+ .kh-chunk-badge {
563
+ padding: 1px 6px;
564
+ border-radius: 10px;
565
+ font-size: 10px;
566
+ }
567
+ .kh-chunk-badge-on {
568
+ background: #dcfce7;
569
+ color: #059669;
570
+ }
571
+ .kh-chunk-badge-off {
572
+ background: #fef2f2;
573
+ color: #dc2626;
574
+ }
575
+ .kh-chunk-body {
576
+ font-size: 12px;
577
+ color: #334155;
578
+ line-height: 1.6;
579
+ white-space: pre-wrap;
580
+ word-break: break-word;
581
+ max-height: 5.4em;
582
+ overflow: hidden;
583
+ position: relative;
584
+ }
585
+ .kh-chunk-body:not(.kh-chunk-body-open)::after {
586
+ content: '…';
587
+ position: absolute;
588
+ right: 0;
589
+ bottom: 0;
590
+ padding-left: 12px;
591
+ background: linear-gradient(90deg, rgba(255, 255, 255, 0), #fff 40%);
592
+ color: #94a3b8;
593
+ }
594
+ .kh-chunk-body-open {
595
+ max-height: none;
596
+ overflow: visible;
597
+ }
598
+ .kh-chunk-expand-inline {
599
+ padding: 0 6px;
600
+ border: none;
601
+ background: transparent;
602
+ font-size: 10px;
603
+ color: #4338ca;
604
+ cursor: pointer;
605
+ border-radius: 4px;
606
+ line-height: 1.4;
607
+ }
608
+ .kh-chunk-expand-inline:hover {
609
+ background: #eef2ff;
610
+ }
611
+
612
+ /* Text-style bulk actions in the toolbar row */
613
+ .kh-chunks-link {
614
+ padding: 2px 4px;
615
+ border: none;
616
+ background: transparent;
617
+ font-size: 12px;
618
+ color: #4338ca;
619
+ cursor: pointer;
620
+ border-radius: 4px;
621
+ }
622
+ .kh-chunks-link:hover {
623
+ background: #eef2ff;
624
+ text-decoration: underline;
625
+ }
626
+ .kh-chunks-link-sep {
627
+ color: #cbd5e1;
628
+ font-size: 12px;
629
+ user-select: none;
630
+ }
631
+ .kh-chunk-tags {
632
+ margin-top: 8px;
633
+ display: flex;
634
+ gap: 4px;
635
+ flex-wrap: wrap;
636
+ }
637
+ .kh-chunk-keyword {
638
+ padding: 1px 6px;
639
+ border-radius: 4px;
640
+ background: #f1f5f9;
641
+ color: #64748b;
642
+ font-size: 10px;
643
+ }
644
+
645
+ /* Meta pane */
646
+ .kh-chunks-meta {
647
+ padding: 12px 14px;
648
+ background: #f8fafc;
649
+ border: 1px solid #f1f5f9;
650
+ border-radius: 10px;
651
+ overflow-y: auto;
652
+ min-width: 0;
653
+ }
654
+ .kh-meta-header {
655
+ padding-bottom: 10px;
656
+ border-bottom: 1px solid #f1f5f9;
657
+ margin-bottom: 12px;
658
+ }
659
+ .kh-meta-title-row {
660
+ display: flex;
661
+ align-items: center;
662
+ justify-content: space-between;
663
+ }
664
+ .kh-meta-title {
665
+ font-size: 13px;
666
+ font-weight: 600;
667
+ color: #0f172a;
668
+ }
669
+ .kh-meta-collapse {
670
+ padding: 0 6px;
671
+ border: none;
672
+ background: transparent;
673
+ font-size: 14px;
674
+ color: #94a3b8;
675
+ cursor: pointer;
676
+ }
677
+ .kh-meta-sub {
678
+ margin-top: 4px;
679
+ font-size: 10px;
680
+ color: #94a3b8;
681
+ line-height: 1.4;
682
+ }
683
+ .kh-meta-btn {
684
+ margin-top: 6px;
685
+ padding: 4px 10px;
686
+ border: 1px solid #6366f1;
687
+ border-radius: 6px;
688
+ background: #eef2ff;
689
+ color: #4338ca;
690
+ font-size: 11px;
691
+ cursor: pointer;
692
+ }
693
+
694
+ .kh-meta-group {
695
+ margin-top: 10px;
696
+ }
697
+ .kh-meta-group-title {
698
+ margin-bottom: 6px;
699
+ font-size: 11px;
700
+ font-weight: 600;
701
+ color: #475569;
702
+ }
703
+ .kh-meta-row {
704
+ display: grid;
705
+ grid-template-columns: 90px 1fr;
706
+ padding: 4px 0;
707
+ font-size: 11px;
708
+ }
709
+ .kh-meta-key {
710
+ color: #94a3b8;
711
+ }
712
+ .kh-meta-val {
713
+ color: #334155;
714
+ text-align: right;
715
+ overflow: hidden;
716
+ text-overflow: ellipsis;
717
+ white-space: nowrap;
718
+ }
719
+
720
+ .kh-meta-toggle {
721
+ align-self: flex-start;
722
+ padding: 4px 6px;
723
+ border: 1px solid #e2e8f0;
724
+ border-radius: 6px;
725
+ background: #fff;
726
+ color: #64748b;
727
+ font-size: 11px;
728
+ cursor: pointer;
729
+ writing-mode: vertical-rl;
730
+ text-orientation: mixed;
731
+ }
732
+
733
+ /* Pagination footer */
734
+ .kh-chunks-foot {
735
+ display: grid;
736
+ grid-template-columns: auto 1fr auto;
737
+ align-items: center;
738
+ gap: 12px;
739
+ padding: 10px 4px 0;
740
+ border-top: 1px solid #f1f5f9;
741
+ margin-top: 4px;
742
+ }
743
+ .kh-chunks-pager {
744
+ display: flex;
745
+ align-items: center;
746
+ gap: 6px;
747
+ font-size: 12px;
748
+ color: #64748b;
749
+ }
750
+ .kh-pager-btn {
751
+ padding: 2px 10px;
752
+ border: 1px solid #e2e8f0;
753
+ border-radius: 4px;
754
+ background: #fff;
755
+ cursor: pointer;
756
+ color: #64748b;
757
+ }
758
+ .kh-pager-btn:hover:not(:disabled) {
759
+ background: #f1f5f9;
760
+ }
761
+ .kh-pager-btn:disabled {
762
+ color: #cbd5e1;
763
+ cursor: not-allowed;
764
+ }
765
+ .kh-pager-page {
766
+ min-width: 60px;
767
+ text-align: center;
768
+ }
769
+ .kh-pager-info {
770
+ text-align: center;
771
+ font-size: 12px;
772
+ color: #94a3b8;
773
+ }
774
+ .kh-pager-sizes {
775
+ display: flex;
776
+ justify-content: flex-end;
777
+ align-items: center;
778
+ gap: 4px;
779
+ font-size: 11px;
780
+ color: #94a3b8;
781
+ }
782
+ .kh-pager-sizes-label {
783
+ margin-right: 4px;
784
+ }
785
+ .kh-pager-size {
786
+ padding: 2px 8px;
787
+ border: none;
788
+ background: transparent;
789
+ font-size: 11px;
790
+ color: #94a3b8;
791
+ cursor: pointer;
792
+ border-radius: 4px;
793
+ }
794
+ .kh-pager-size:hover {
795
+ background: #f1f5f9;
796
+ color: #475569;
797
+ }
798
+ .kh-pager-size-active {
799
+ background: #4f46e5;
800
+ color: #fff;
801
+ }
802
+ </style>