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,781 @@
1
+ <script setup lang="ts">
2
+ /**
3
+ * DatasetSidebar — left rail shared across the four dataset-detail screens
4
+ * (screenshots 4-7). Dataset icon + name + description at the top, 4 nav
5
+ * items in the middle, doc/word count footer + Access API link at the bottom.
6
+ *
7
+ * The identity block is a hoverable card; a top-right `⋯` trigger opens a
8
+ * basic-info popover (Dify-parity with AppDesignDrawer's info card) that
9
+ * emits `edit-info` / `duplicate` / `export` / `delete` — actual mutation is
10
+ * delegated to the host so the sidebar stays presentational.
11
+ */
12
+ import { computed, onBeforeUnmount, onMounted, ref } from 'vue';
13
+
14
+ import type { DatasetTab } from '../types';
15
+
16
+ interface Props {
17
+ dataset: {
18
+ id: string;
19
+ name: string;
20
+ description?: string;
21
+ icon?: string;
22
+ iconBg?: string;
23
+ documentCount?: number;
24
+ segmentCount?: number;
25
+ /** Optional — displayed as a meta row in the popover if provided. */
26
+ createdAt?: string | number | Date;
27
+ /** Optional — indexing technique badge (HIGH_QUALITY / ECONOMY). */
28
+ indexingTechnique?: string;
29
+ };
30
+ active: DatasetTab;
31
+ }
32
+
33
+ const props = defineProps<Props>();
34
+
35
+ const emit = defineEmits<{
36
+ (e: 'nav', tab: DatasetTab): void;
37
+ (e: 'copy-api'): void;
38
+ /** Open the "编辑基本信息" surface — host owns the actual edit modal. */
39
+ (e: 'edit-info', payload: { datasetId: string }): void;
40
+ (e: 'duplicate', payload: { datasetId: string }): void;
41
+ (e: 'export', payload: { datasetId: string }): void;
42
+ (e: 'delete', payload: { datasetId: string }): void;
43
+ }>();
44
+
45
+ const NAV: Array<{ id: DatasetTab; icon: string; label: string }> = [
46
+ { id: 'documents', icon: '📄', label: '文档' },
47
+ { id: 'recall', icon: '⊕', label: '召回测试' },
48
+ { id: 'settings', icon: '⚙', label: '设置' },
49
+ ];
50
+
51
+ const icon = computed(() => props.dataset.icon || '📙');
52
+ const iconBg = computed(() => props.dataset.iconBg || '#FFEAD5');
53
+
54
+ const techniqueLabel = computed(() => {
55
+ const t = props.dataset.indexingTechnique;
56
+ if (!t) return null;
57
+ return t === 'ECONOMY' ? '经济' : '高质量';
58
+ });
59
+
60
+ const createdAgo = computed(() => {
61
+ const ts = props.dataset.createdAt;
62
+ if (!ts) return null;
63
+ const d = new Date(ts);
64
+ const t = d.getTime();
65
+ if (Number.isNaN(t)) return String(ts);
66
+ const diff = Date.now() - t;
67
+ if (diff < 60_000) return '刚刚';
68
+ const mins = Math.floor(diff / 60_000);
69
+ if (mins < 60) return `${mins} 分钟前`;
70
+ const hours = Math.floor(diff / 3_600_000);
71
+ if (hours < 24) return `${hours} 小时前`;
72
+ const days = Math.floor(diff / 86_400_000);
73
+ if (days < 30) return `${days} 天前`;
74
+ const months = Math.floor(days / 30);
75
+ if (months < 12) return `${months} 个月前`;
76
+ return `${Math.floor(months / 12)} 年前`;
77
+ });
78
+
79
+ // ---------------------------------------------------------------------------
80
+ // Basic-info popover — same UX as AppDesignDrawer's info card. Trigger sits
81
+ // in the top-right of the head card and only appears on hover / when open.
82
+ // ---------------------------------------------------------------------------
83
+ const infoOpen = ref(false);
84
+ const headRef = ref<HTMLElement | null>(null);
85
+
86
+ function toggleInfo() {
87
+ infoOpen.value = !infoOpen.value;
88
+ }
89
+ function closeInfo() {
90
+ infoOpen.value = false;
91
+ }
92
+
93
+ function onEdit() {
94
+ emit('edit-info', { datasetId: props.dataset.id });
95
+ closeInfo();
96
+ }
97
+ function onDuplicate() {
98
+ emit('duplicate', { datasetId: props.dataset.id });
99
+ closeInfo();
100
+ }
101
+ function onExport() {
102
+ emit('export', { datasetId: props.dataset.id });
103
+ closeInfo();
104
+ }
105
+ function onDelete() {
106
+ emit('delete', { datasetId: props.dataset.id });
107
+ closeInfo();
108
+ }
109
+
110
+ function onDocClick(e: MouseEvent) {
111
+ if (!infoOpen.value) return;
112
+ const wrap = headRef.value;
113
+ if (wrap && !wrap.contains(e.target as Node)) closeInfo();
114
+ }
115
+ function onDocKeydown(e: KeyboardEvent) {
116
+ if (e.key === 'Escape' && infoOpen.value) closeInfo();
117
+ }
118
+
119
+ onMounted(() => {
120
+ document.addEventListener('mousedown', onDocClick);
121
+ document.addEventListener('keydown', onDocKeydown);
122
+ });
123
+ onBeforeUnmount(() => {
124
+ document.removeEventListener('mousedown', onDocClick);
125
+ document.removeEventListener('keydown', onDocKeydown);
126
+ });
127
+ </script>
128
+
129
+ <template>
130
+ <div class="kh-side">
131
+ <!-- header: dataset identity — hoverable card, ⋯ opens basic info popover -->
132
+ <div
133
+ ref="headRef"
134
+ class="kh-side-head"
135
+ :class="{ 'kh-side-head-active': infoOpen }"
136
+ >
137
+ <button
138
+ type="button"
139
+ class="kh-side-trigger"
140
+ :class="{ 'kh-side-trigger-open': infoOpen }"
141
+ :title="infoOpen ? '收起基本信息' : '展开基本信息'"
142
+ @click.stop="toggleInfo"
143
+ >
144
+
145
+ </button>
146
+
147
+ <div class="kh-side-icon" :style="{ background: iconBg }">
148
+ {{ icon }}
149
+ </div>
150
+ <div class="kh-side-name" :title="dataset.name">{{ dataset.name }}</div>
151
+ <div v-if="dataset.description" class="kh-side-desc" :title="dataset.description">
152
+ {{ dataset.description }}
153
+ </div>
154
+
155
+ <!-- Basic-info popover -->
156
+ <div v-if="infoOpen" class="kh-info-panel" @click.stop>
157
+ <!-- Header: icon + name + type chip -->
158
+ <div class="kh-ip-head">
159
+ <div class="kh-ip-icon" :style="{ background: iconBg }">
160
+ {{ icon }}
161
+ </div>
162
+ <div class="kh-ip-title-wrap">
163
+ <div class="kh-ip-title">{{ dataset.name }}</div>
164
+ <div class="kh-ip-chips">
165
+ <span class="kh-ip-chip">知识库</span>
166
+ <span v-if="techniqueLabel" class="kh-ip-chip kh-ip-chip-muted">
167
+ {{ techniqueLabel }}
168
+ </span>
169
+ </div>
170
+ </div>
171
+ </div>
172
+
173
+ <!-- Quick action tiles -->
174
+ <div class="kh-ip-actions">
175
+ <button class="kh-ip-action" @click="onEdit">
176
+ <span class="kh-ip-action-icon">✎</span>
177
+ <span>编辑信息</span>
178
+ </button>
179
+ <button class="kh-ip-action" @click="onDuplicate">
180
+ <span class="kh-ip-action-icon">⧉</span>
181
+ <span>复制</span>
182
+ </button>
183
+ <button class="kh-ip-action" @click="onExport">
184
+ <span class="kh-ip-action-icon">⇩</span>
185
+ <span>导出</span>
186
+ </button>
187
+ <button class="kh-ip-action kh-ip-action-danger" @click="onDelete">
188
+ <span class="kh-ip-action-icon">🗑</span>
189
+ <span>删除</span>
190
+ </button>
191
+ </div>
192
+
193
+ <!-- Description section -->
194
+ <section v-if="dataset.description" class="kh-ip-section">
195
+ <div class="kh-ip-section-head">
196
+ <span class="kh-ip-section-icon kh-ip-section-icon-desc">📝</span>
197
+ <span class="kh-ip-section-title">描述</span>
198
+ </div>
199
+ <div class="kh-ip-desc-text">{{ dataset.description }}</div>
200
+ </section>
201
+
202
+ <!-- Stats section -->
203
+ <section class="kh-ip-section">
204
+ <div class="kh-ip-section-head">
205
+ <span class="kh-ip-section-icon kh-ip-section-icon-stat">📊</span>
206
+ <span class="kh-ip-section-title">统计</span>
207
+ </div>
208
+ <div class="kh-ip-stats">
209
+ <div class="kh-ip-stat">
210
+ <div class="kh-ip-stat-val">{{ dataset.documentCount ?? 0 }}</div>
211
+ <div class="kh-ip-stat-lbl">文档</div>
212
+ </div>
213
+ <div class="kh-ip-stat">
214
+ <div class="kh-ip-stat-val">{{ dataset.segmentCount ?? 0 }}</div>
215
+ <div class="kh-ip-stat-lbl">关联应用</div>
216
+ </div>
217
+ <div v-if="createdAgo" class="kh-ip-stat">
218
+ <div class="kh-ip-stat-val">{{ createdAgo }}</div>
219
+ <div class="kh-ip-stat-lbl">创建于</div>
220
+ </div>
221
+ </div>
222
+ </section>
223
+
224
+ <!-- ID / API section -->
225
+ <section class="kh-ip-section">
226
+ <div class="kh-ip-section-head">
227
+ <span class="kh-ip-section-icon kh-ip-section-icon-api">🔌</span>
228
+ <span class="kh-ip-section-title">API</span>
229
+ </div>
230
+ <div class="kh-ip-section-label">数据集 ID</div>
231
+ <div class="kh-ip-url-row">
232
+ <span class="kh-ip-url" :title="dataset.id">{{ dataset.id }}</span>
233
+ <button
234
+ class="kh-ip-icon-btn"
235
+ title="复制 API 基址"
236
+ @click="emit('copy-api')"
237
+ >
238
+
239
+ </button>
240
+ </div>
241
+ <div class="kh-ip-links">
242
+ <button class="kh-ip-link" @click="emit('copy-api')">
243
+ 🔑 API 密钥
244
+ </button>
245
+ <button class="kh-ip-link">📄 查看 API 文档</button>
246
+ </div>
247
+ </section>
248
+ </div>
249
+ </div>
250
+
251
+ <!-- nav items -->
252
+ <div class="kh-side-nav">
253
+ <button
254
+ v-for="n in NAV"
255
+ :key="n.id"
256
+ class="kh-side-nav-item"
257
+ :class="{ 'kh-side-nav-active': active === n.id }"
258
+ @click="emit('nav', n.id)"
259
+ >
260
+ <span class="kh-side-nav-icon">{{ n.icon }}</span>
261
+ <span>{{ n.label }}</span>
262
+ </button>
263
+ </div>
264
+
265
+ <!-- footer: counts + api -->
266
+ <div class="kh-side-foot">
267
+ <div class="kh-side-counts">
268
+ <div class="kh-side-count">
269
+ <div class="kh-side-count-val">{{ dataset.documentCount ?? 0 }}</div>
270
+ <div class="kh-side-count-lbl">文档</div>
271
+ </div>
272
+ <div class="kh-side-count">
273
+ <div class="kh-side-count-val">{{ dataset.segmentCount ?? 0 }}</div>
274
+ <div class="kh-side-count-lbl">个关联应用</div>
275
+ </div>
276
+ </div>
277
+ <button class="kh-side-api" @click="emit('copy-api')">
278
+ ⌘ 访问 API
279
+ </button>
280
+ </div>
281
+ </div>
282
+ </template>
283
+
284
+ <style scoped>
285
+ .kh-side {
286
+ width: 220px;
287
+ flex-shrink: 0;
288
+ display: flex;
289
+ flex-direction: column;
290
+ padding: 10px 10px 16px;
291
+ background: #fff;
292
+ border-right: 1px solid #f1f5f9;
293
+ height: 100%;
294
+ /* Note: overflow is intentionally visible so the basic-info popover can
295
+ * escape to the right. Scrolling for a long nav is handled by
296
+ * `.kh-side-nav` which owns its own overflow-y. */
297
+ overflow: visible;
298
+ }
299
+
300
+ /* ---- Header — hoverable card with a top-right ⋯ trigger ---- */
301
+ .kh-side-head {
302
+ position: relative;
303
+ margin-bottom: 6px;
304
+ padding: 14px 12px 12px;
305
+ border: 1px solid transparent;
306
+ border-radius: 10px;
307
+ transition:
308
+ background 0.15s ease,
309
+ border-color 0.15s ease,
310
+ box-shadow 0.15s ease;
311
+ }
312
+ .kh-side-head:hover {
313
+ background: #f8fafc;
314
+ border-color: #e2e8f0;
315
+ }
316
+ .kh-side-head:hover .kh-side-trigger {
317
+ opacity: 1;
318
+ }
319
+ .kh-side-head-active {
320
+ background: #eef2ff;
321
+ border-color: #c7d2fe;
322
+ box-shadow: 0 1px 2px rgba(99, 102, 241, 0.08);
323
+ }
324
+ .kh-side-head-active .kh-side-trigger {
325
+ opacity: 1;
326
+ color: #4338ca;
327
+ background: #e0e7ff;
328
+ }
329
+ .kh-side-trigger {
330
+ position: absolute;
331
+ top: 8px;
332
+ right: 8px;
333
+ width: 22px;
334
+ height: 22px;
335
+ padding: 0;
336
+ border: none;
337
+ background: transparent;
338
+ border-radius: 6px;
339
+ color: #94a3b8;
340
+ font-size: 16px;
341
+ line-height: 1;
342
+ cursor: pointer;
343
+ display: inline-flex;
344
+ align-items: center;
345
+ justify-content: center;
346
+ opacity: 0;
347
+ transition:
348
+ background 0.15s ease,
349
+ color 0.15s ease,
350
+ opacity 0.15s ease;
351
+ z-index: 2;
352
+ }
353
+ .kh-side-trigger:hover {
354
+ background: #eef2ff;
355
+ color: #4338ca;
356
+ }
357
+ .kh-side-trigger-open {
358
+ opacity: 1;
359
+ }
360
+
361
+ .kh-side-icon {
362
+ width: 48px;
363
+ height: 48px;
364
+ display: flex;
365
+ align-items: center;
366
+ justify-content: center;
367
+ border-radius: 10px;
368
+ font-size: 26px;
369
+ box-shadow: inset 0 0 0 1px rgba(15, 23, 42, 0.04);
370
+ }
371
+ .kh-side-name {
372
+ margin-top: 10px;
373
+ font-size: 14px;
374
+ font-weight: 600;
375
+ color: #0f172a;
376
+ line-height: 1.35;
377
+ overflow: hidden;
378
+ text-overflow: ellipsis;
379
+ white-space: nowrap;
380
+ }
381
+ .kh-side-desc {
382
+ margin-top: 4px;
383
+ font-size: 11px;
384
+ color: #94a3b8;
385
+ line-height: 1.5;
386
+ display: -webkit-box;
387
+ -webkit-line-clamp: 3;
388
+ -webkit-box-orient: vertical;
389
+ overflow: hidden;
390
+ }
391
+
392
+ /* ---- Basic-info popover (mirrors AppDesignDrawer's brand panel) ---- */
393
+ .kh-info-panel {
394
+ position: absolute;
395
+ top: 0;
396
+ left: calc(100% + 12px);
397
+ width: 400px;
398
+ padding: 14px;
399
+ background: linear-gradient(180deg, #ffffff 0%, #fbfbfe 100%);
400
+ border: 1px solid #e5e7eb;
401
+ border-radius: 14px;
402
+ box-shadow:
403
+ 0 20px 40px -12px rgba(15, 23, 42, 0.16),
404
+ 0 4px 12px rgba(15, 23, 42, 0.06);
405
+ z-index: 60;
406
+ display: flex;
407
+ flex-direction: column;
408
+ gap: 12px;
409
+ text-align: left;
410
+ animation: kh-info-fade 0.14s ease-out;
411
+ }
412
+ @keyframes kh-info-fade {
413
+ from {
414
+ opacity: 0;
415
+ transform: translateX(-4px);
416
+ }
417
+ to {
418
+ opacity: 1;
419
+ transform: translateX(0);
420
+ }
421
+ }
422
+
423
+ /* Header inside panel */
424
+ .kh-ip-head {
425
+ display: flex;
426
+ align-items: center;
427
+ gap: 12px;
428
+ padding: 2px 2px 12px;
429
+ border-bottom: 1px solid #f1f5f9;
430
+ }
431
+ .kh-ip-icon {
432
+ width: 40px;
433
+ height: 40px;
434
+ display: flex;
435
+ align-items: center;
436
+ justify-content: center;
437
+ border-radius: 10px;
438
+ font-size: 22px;
439
+ flex-shrink: 0;
440
+ box-shadow: inset 0 0 0 1px rgba(15, 23, 42, 0.04);
441
+ }
442
+ .kh-ip-title-wrap {
443
+ min-width: 0;
444
+ flex: 1;
445
+ display: flex;
446
+ flex-direction: column;
447
+ gap: 4px;
448
+ }
449
+ .kh-ip-title {
450
+ font-size: 14px;
451
+ font-weight: 600;
452
+ color: #0f172a;
453
+ line-height: 1.2;
454
+ white-space: nowrap;
455
+ overflow: hidden;
456
+ text-overflow: ellipsis;
457
+ }
458
+ .kh-ip-chips {
459
+ display: inline-flex;
460
+ gap: 4px;
461
+ flex-wrap: wrap;
462
+ }
463
+ .kh-ip-chip {
464
+ padding: 1px 7px;
465
+ font-size: 10px;
466
+ font-weight: 600;
467
+ letter-spacing: 0.06em;
468
+ color: #4338ca;
469
+ background: #eef2ff;
470
+ border-radius: 4px;
471
+ line-height: 1.5;
472
+ }
473
+ .kh-ip-chip-muted {
474
+ color: #64748b;
475
+ background: #f1f5f9;
476
+ letter-spacing: 0;
477
+ }
478
+
479
+ /* Quick action tiles */
480
+ .kh-ip-actions {
481
+ display: grid;
482
+ grid-template-columns: repeat(4, 1fr);
483
+ gap: 6px;
484
+ }
485
+ .kh-ip-action {
486
+ display: flex;
487
+ flex-direction: column;
488
+ align-items: center;
489
+ justify-content: center;
490
+ gap: 6px;
491
+ padding: 10px 4px;
492
+ border: 1px solid #f1f5f9;
493
+ background: #fafbfc;
494
+ border-radius: 10px;
495
+ color: #475569;
496
+ font-size: 11px;
497
+ font-weight: 500;
498
+ cursor: pointer;
499
+ transition:
500
+ background 0.15s ease,
501
+ border-color 0.15s ease,
502
+ color 0.15s ease,
503
+ transform 0.15s ease,
504
+ box-shadow 0.15s ease;
505
+ }
506
+ .kh-ip-action:hover {
507
+ border-color: #c7d2fe;
508
+ background: #eef2ff;
509
+ color: #4338ca;
510
+ transform: translateY(-1px);
511
+ box-shadow: 0 3px 8px rgba(99, 102, 241, 0.1);
512
+ }
513
+ .kh-ip-action-icon {
514
+ width: 24px;
515
+ height: 24px;
516
+ display: inline-flex;
517
+ align-items: center;
518
+ justify-content: center;
519
+ border-radius: 7px;
520
+ background: #eef2ff;
521
+ color: #4338ca;
522
+ font-size: 12px;
523
+ line-height: 1;
524
+ transition:
525
+ background 0.15s ease,
526
+ color 0.15s ease;
527
+ }
528
+ .kh-ip-action:hover .kh-ip-action-icon {
529
+ background: #4338ca;
530
+ color: #fff;
531
+ }
532
+ .kh-ip-action-danger .kh-ip-action-icon {
533
+ background: #fee2e2;
534
+ color: #b91c1c;
535
+ }
536
+ .kh-ip-action-danger:hover {
537
+ border-color: #fecaca;
538
+ background: #fef2f2;
539
+ color: #b91c1c;
540
+ }
541
+ .kh-ip-action-danger:hover .kh-ip-action-icon {
542
+ background: #b91c1c;
543
+ color: #fff;
544
+ }
545
+
546
+ /* Section cards */
547
+ .kh-ip-section {
548
+ padding: 12px;
549
+ background: #fafbfc;
550
+ border: 1px solid #f1f5f9;
551
+ border-radius: 10px;
552
+ transition: background 0.15s ease, border-color 0.15s ease;
553
+ }
554
+ .kh-ip-section:hover {
555
+ background: #f8fafc;
556
+ border-color: #e2e8f0;
557
+ }
558
+ .kh-ip-section-head {
559
+ display: flex;
560
+ align-items: center;
561
+ gap: 8px;
562
+ margin-bottom: 10px;
563
+ }
564
+ .kh-ip-section-icon {
565
+ width: 24px;
566
+ height: 24px;
567
+ display: inline-flex;
568
+ align-items: center;
569
+ justify-content: center;
570
+ border-radius: 6px;
571
+ font-size: 12px;
572
+ flex-shrink: 0;
573
+ }
574
+ .kh-ip-section-icon-desc {
575
+ background: #fef3c7;
576
+ color: #b45309;
577
+ }
578
+ .kh-ip-section-icon-stat {
579
+ background: #dbeafe;
580
+ color: #1d4ed8;
581
+ }
582
+ .kh-ip-section-icon-api {
583
+ background: #e0e7ff;
584
+ color: #4338ca;
585
+ }
586
+ .kh-ip-section-title {
587
+ font-size: 13px;
588
+ font-weight: 600;
589
+ color: #0f172a;
590
+ }
591
+ .kh-ip-section-label {
592
+ margin-bottom: 6px;
593
+ font-size: 11px;
594
+ font-weight: 500;
595
+ color: #64748b;
596
+ }
597
+ .kh-ip-desc-text {
598
+ font-size: 12px;
599
+ color: #334155;
600
+ line-height: 1.6;
601
+ white-space: pre-wrap;
602
+ word-break: break-word;
603
+ }
604
+
605
+ /* Stats grid */
606
+ .kh-ip-stats {
607
+ display: grid;
608
+ grid-template-columns: repeat(3, 1fr);
609
+ gap: 8px;
610
+ }
611
+ .kh-ip-stat {
612
+ padding: 8px 4px;
613
+ text-align: center;
614
+ background: #fff;
615
+ border: 1px solid #f1f5f9;
616
+ border-radius: 8px;
617
+ }
618
+ .kh-ip-stat-val {
619
+ font-size: 14px;
620
+ font-weight: 600;
621
+ color: #0f172a;
622
+ line-height: 1.2;
623
+ }
624
+ .kh-ip-stat-lbl {
625
+ margin-top: 2px;
626
+ font-size: 10px;
627
+ color: #94a3b8;
628
+ }
629
+
630
+ /* URL row + link pills */
631
+ .kh-ip-url-row {
632
+ display: flex;
633
+ align-items: center;
634
+ gap: 2px;
635
+ padding: 5px 6px 5px 10px;
636
+ background: #fff;
637
+ border: 1px solid #e5e7eb;
638
+ border-radius: 8px;
639
+ margin-bottom: 10px;
640
+ transition: border-color 0.15s ease, box-shadow 0.15s ease;
641
+ }
642
+ .kh-ip-url-row:hover {
643
+ border-color: #c7d2fe;
644
+ box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.06);
645
+ }
646
+ .kh-ip-url {
647
+ flex: 1;
648
+ min-width: 0;
649
+ font-size: 12px;
650
+ color: #0f172a;
651
+ font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
652
+ white-space: nowrap;
653
+ overflow: hidden;
654
+ text-overflow: ellipsis;
655
+ }
656
+ .kh-ip-icon-btn {
657
+ width: 24px;
658
+ height: 24px;
659
+ border: none;
660
+ background: transparent;
661
+ border-radius: 6px;
662
+ color: #94a3b8;
663
+ font-size: 12px;
664
+ cursor: pointer;
665
+ display: inline-flex;
666
+ align-items: center;
667
+ justify-content: center;
668
+ flex-shrink: 0;
669
+ transition: background 0.15s ease, color 0.15s ease;
670
+ }
671
+ .kh-ip-icon-btn:hover {
672
+ background: #eef2ff;
673
+ color: #4338ca;
674
+ }
675
+ .kh-ip-links {
676
+ display: flex;
677
+ flex-wrap: wrap;
678
+ gap: 6px;
679
+ }
680
+ .kh-ip-link {
681
+ padding: 4px 10px;
682
+ border: 1px solid #e5e7eb;
683
+ background: #fff;
684
+ border-radius: 999px;
685
+ color: #475569;
686
+ font-size: 11px;
687
+ font-weight: 500;
688
+ cursor: pointer;
689
+ display: inline-flex;
690
+ align-items: center;
691
+ gap: 4px;
692
+ transition:
693
+ background 0.15s ease,
694
+ border-color 0.15s ease,
695
+ color 0.15s ease;
696
+ }
697
+ .kh-ip-link:hover {
698
+ background: #eef2ff;
699
+ border-color: #c7d2fe;
700
+ color: #4338ca;
701
+ }
702
+
703
+ /* Nav */
704
+ .kh-side-nav {
705
+ padding: 12px 4px 0;
706
+ display: flex;
707
+ flex-direction: column;
708
+ gap: 2px;
709
+ flex: 1;
710
+ min-height: 0;
711
+ overflow-y: auto;
712
+ border-top: 1px solid #f1f5f9;
713
+ }
714
+ .kh-side-nav-item {
715
+ display: flex;
716
+ align-items: center;
717
+ gap: 10px;
718
+ padding: 8px 10px;
719
+ border: none;
720
+ border-radius: 8px;
721
+ background: transparent;
722
+ font-size: 13px;
723
+ color: #475569;
724
+ cursor: pointer;
725
+ text-align: left;
726
+ transition: background 0.15s;
727
+ }
728
+ .kh-side-nav-item:hover {
729
+ background: #f8fafc;
730
+ }
731
+ .kh-side-nav-active {
732
+ background: #eef2ff;
733
+ color: #4338ca;
734
+ font-weight: 600;
735
+ }
736
+ .kh-side-nav-icon {
737
+ font-size: 15px;
738
+ width: 18px;
739
+ text-align: center;
740
+ }
741
+
742
+ /* Footer */
743
+ .kh-side-foot {
744
+ padding-top: 14px;
745
+ border-top: 1px solid #f1f5f9;
746
+ }
747
+ .kh-side-counts {
748
+ display: grid;
749
+ grid-template-columns: 1fr 1fr;
750
+ gap: 8px;
751
+ margin-bottom: 10px;
752
+ }
753
+ .kh-side-count {
754
+ padding: 6px 4px;
755
+ text-align: center;
756
+ }
757
+ .kh-side-count-val {
758
+ font-size: 15px;
759
+ font-weight: 600;
760
+ color: #0f172a;
761
+ }
762
+ .kh-side-count-lbl {
763
+ margin-top: 2px;
764
+ font-size: 10px;
765
+ color: #94a3b8;
766
+ }
767
+ .kh-side-api {
768
+ width: 100%;
769
+ padding: 6px 10px;
770
+ border: 1px solid #e2e8f0;
771
+ border-radius: 6px;
772
+ background: #fff;
773
+ font-size: 12px;
774
+ color: #475569;
775
+ cursor: pointer;
776
+ transition: background 0.15s;
777
+ }
778
+ .kh-side-api:hover {
779
+ background: #f8fafc;
780
+ }
781
+ </style>