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,504 @@
1
+ <script setup lang="ts">
2
+ /**
3
+ * KnowledgeHubApp — the drop-in top-level component for the knowledge-base
4
+ * feature. Consumers only need to implement the `KnowledgeHubApi` interface
5
+ * (a bag of async REST calls) and drop this component in — the whole Dify-
6
+ * parity flow is contained inside:
7
+ *
8
+ * ┌────────────────────────────────────────────────────────┐
9
+ * │ <KnowledgeHubApp :api="apiImpl" /> │
10
+ * └────────────────────────────────────────────────────────┘
11
+ *
12
+ * renders the card grid.
13
+ * +新建 click → left-side wizard drawer (self-contained)
14
+ * dataset card click → right-side detail drawer with 4 tabs +
15
+ * inline chunks browser (no route change)
16
+ *
17
+ * The parent app doesn't need routes, watchers or dialog plumbing for the
18
+ * knowledge feature. Emits `changed` when list-affecting mutations happen so
19
+ * consumers can refresh their own counters if needed.
20
+ *
21
+ * Expose `openCreate()` / `openDetail(id)` via defineExpose so the host can
22
+ * also trigger the drawers programmatically (per the demo request).
23
+ */
24
+ import { computed, onMounted, ref, watch } from 'vue';
25
+
26
+ import { type KhLocale, provideKhI18n, useKhI18n } from '../i18n';
27
+ import type {
28
+ DatasetCardItem,
29
+ DatasetDetailHub,
30
+ DatasetTab,
31
+ EmbeddingModelOption,
32
+ KnowledgeHubApi,
33
+ ProcessRule,
34
+ RetrievalConfig,
35
+ } from '../types';
36
+ import CreateDatasetWizard from './CreateDatasetWizard.vue';
37
+ import DatasetCardGrid from './DatasetCardGrid.vue';
38
+ import DatasetDetailDrawer from './DatasetDetailDrawer.vue';
39
+ import KhDialog from './internal/KhDialog.vue';
40
+
41
+ interface Props {
42
+ api: KnowledgeHubApi;
43
+ /**
44
+ * Optional locale — either a language code ('zh-CN' | 'en-US') or a partial
45
+ * message catalog for host-owned overrides. Default: zh-CN.
46
+ */
47
+ locale?: KhLocale;
48
+ /** Optional page title override — falls back to the locale catalog. */
49
+ title?: string;
50
+ /** Optional description override — falls back to the locale catalog. */
51
+ description?: string;
52
+ }
53
+
54
+ const props = defineProps<Props>();
55
+ provideKhI18n(() => props.locale);
56
+ const { t } = useKhI18n();
57
+
58
+ const displayTitle = computed(() => props.title ?? t('app.title'));
59
+ const displayDescription = computed(
60
+ () => props.description ?? t('app.description'),
61
+ );
62
+
63
+ const emit = defineEmits<{
64
+ (e: 'changed'): void;
65
+ }>();
66
+
67
+ // -------- state
68
+ const datasets = ref<DatasetCardItem[]>([]);
69
+ const loading = ref(false);
70
+ const keyword = ref('');
71
+
72
+ const wizardOpen = ref(false);
73
+ const detailOpen = ref(false);
74
+ const detailId = ref<null | string>(null);
75
+ const detailInitialTab = ref<DatasetTab | undefined>(undefined);
76
+
77
+ const embeddingModels = ref<EmbeddingModelOption[]>([]);
78
+ const rerankModels = ref<Array<{ id: string; label: string }>>([]);
79
+
80
+ // -------- helpers
81
+ function notifySuccess(msg: string) {
82
+ props.api.onSuccess?.(msg);
83
+ }
84
+ function notifyError(msg: string) {
85
+ props.api.onError?.(msg);
86
+ }
87
+
88
+ // -------- load
89
+ async function refresh() {
90
+ loading.value = true;
91
+ try {
92
+ datasets.value = await props.api.listDatasets();
93
+ } catch (e: any) {
94
+ notifyError(`${t('common.loadFailed')}: ${e?.message ?? e}`);
95
+ datasets.value = [];
96
+ } finally {
97
+ loading.value = false;
98
+ }
99
+ }
100
+
101
+ async function loadEmbeddingModels() {
102
+ try {
103
+ embeddingModels.value = await props.api.listEmbeddingModels();
104
+ } catch {
105
+ embeddingModels.value = [];
106
+ }
107
+ }
108
+
109
+ async function loadRerankModels() {
110
+ if (!props.api.listRerankModels) {
111
+ rerankModels.value = [];
112
+ return;
113
+ }
114
+ try {
115
+ rerankModels.value = await props.api.listRerankModels();
116
+ } catch {
117
+ rerankModels.value = [];
118
+ }
119
+ }
120
+
121
+ onMounted(async () => {
122
+ await Promise.all([refresh(), loadEmbeddingModels(), loadRerankModels()]);
123
+ });
124
+
125
+ // -------- create
126
+ function openCreate() {
127
+ // Re-load embedding + rerank models each time so newly-registered ones show up.
128
+ loadEmbeddingModels();
129
+ loadRerankModels();
130
+ wizardOpen.value = true;
131
+ }
132
+
133
+ async function onWizardCreate(payload: {
134
+ description: string;
135
+ embeddingModelId: string;
136
+ files: File[];
137
+ indexingTechnique: 'ECONOMY' | 'HIGH_QUALITY';
138
+ name: string;
139
+ processRule: ProcessRule;
140
+ retrievalConfig: RetrievalConfig;
141
+ }) {
142
+ try {
143
+ const created = await props.api.createDataset({
144
+ name: payload.name,
145
+ description: payload.description,
146
+ embeddingModelId: payload.embeddingModelId || undefined,
147
+ indexingTechnique: payload.indexingTechnique,
148
+ processRule: payload.processRule,
149
+ retrievalConfig: payload.retrievalConfig,
150
+ });
151
+ // Serial uploads keep progress easy to reason about.
152
+ for (const file of payload.files) {
153
+ await props.api.uploadDocument(created.id, file);
154
+ }
155
+ notifySuccess(t('common.createdSuccess'));
156
+ wizardOpen.value = false;
157
+ await refresh();
158
+ emit('changed');
159
+ // Auto-open the detail drawer on the freshly-created dataset — matches Dify.
160
+ openDetail(created.id);
161
+ } catch (e: any) {
162
+ notifyError(`${t('common.createFailed')}: ${e?.message ?? e}`);
163
+ }
164
+ }
165
+
166
+ // -------- create empty (KhDialog prompt, replaces window.prompt)
167
+ const emptyPromptOpen = ref(false);
168
+ function onWizardSkipEmpty() {
169
+ // Ask the user via our own dialog — no native browser prompts.
170
+ wizardOpen.value = false;
171
+ emptyPromptOpen.value = true;
172
+ }
173
+ async function onEmptyPromptConfirm(name: string | undefined) {
174
+ const trimmed = (name ?? '').trim();
175
+ if (!trimmed) return;
176
+ try {
177
+ const created = await props.api.createDataset({
178
+ name: trimmed,
179
+ indexingTechnique: 'ECONOMY',
180
+ });
181
+ notifySuccess(t('common.createdSuccess'));
182
+ await refresh();
183
+ emit('changed');
184
+ openDetail(created.id);
185
+ } catch (e: any) {
186
+ notifyError(`${t('common.createFailed')}: ${e?.message ?? e}`);
187
+ }
188
+ }
189
+
190
+ // -------- open detail
191
+ function openDetail(id: string, tab?: DatasetTab) {
192
+ detailId.value = id;
193
+ detailInitialTab.value = tab;
194
+ detailOpen.value = true;
195
+ }
196
+ function openSettings(d: DatasetCardItem) {
197
+ openDetail(d.id, 'settings');
198
+ }
199
+
200
+ // -------- delete from card (KhDialog confirm, replaces window.confirm)
201
+ const deleteConfirmOpen = ref(false);
202
+ const deleteTarget = ref<DatasetCardItem | null>(null);
203
+
204
+ function onDeleteCard(d: DatasetCardItem) {
205
+ deleteTarget.value = d;
206
+ deleteConfirmOpen.value = true;
207
+ }
208
+ async function onDeleteConfirm() {
209
+ const d = deleteTarget.value;
210
+ if (!d) return;
211
+ try {
212
+ await props.api.deleteDataset(d.id);
213
+ notifySuccess(t('common.deleteSuccess'));
214
+ await refresh();
215
+ emit('changed');
216
+ } catch (e: any) {
217
+ notifyError(`${e?.message ?? e}`);
218
+ }
219
+ }
220
+
221
+ // -------- detail drawer hub adapter (satisfies DatasetDetailHub interface)
222
+ const detailHub = computed<DatasetDetailHub>(() => ({
223
+ loadDataset: (id) => props.api.getDataset(id),
224
+ updateDataset: (id, patch) => props.api.updateDataset(id, patch),
225
+ deleteDataset: async (id) => {
226
+ await props.api.deleteDataset(id);
227
+ await refresh();
228
+ emit('changed');
229
+ },
230
+ listDocuments: (id) => props.api.listDocuments(id),
231
+ uploadDocuments: async (id, files) => {
232
+ for (const f of files) await props.api.uploadDocument(id, f);
233
+ },
234
+ deleteDocument: (id, docId) => props.api.deleteDocument(id, docId),
235
+ setDocumentEnabled: props.api.setDocumentEnabled,
236
+ listSegments: (id, docId, page, pageSize) =>
237
+ props.api.listSegments(id, docId, page, pageSize),
238
+ loadDocumentMetadata: (id, docId) => props.api.loadDocumentMetadata(id, docId),
239
+ updateSegment: (id, docId, segId, content) =>
240
+ props.api.updateSegment(id, docId, segId, content),
241
+ deleteSegment: (id, docId, segId) => props.api.deleteSegment(id, docId, segId),
242
+ setSegmentEnabled: (id, docId, segId, enabled) =>
243
+ props.api.setSegmentEnabled(id, docId, segId, enabled),
244
+ appendSegment: props.api.appendSegment,
245
+ retrieve: (id, req) => props.api.retrieve(id, req),
246
+ listRecallHistory: (id, limit) => props.api.listRecallHistory(id, limit),
247
+ listEmbeddingModels: () => props.api.listEmbeddingModels(),
248
+ previewChunks: props.api.previewChunks,
249
+ onCopyApi: props.api.onCopyApi,
250
+ }));
251
+
252
+ function onDetailChanged() {
253
+ refresh();
254
+ emit('changed');
255
+ }
256
+
257
+ watch(detailOpen, (v) => {
258
+ if (!v) {
259
+ // Refresh on close in case anything mutated inside the drawer.
260
+ refresh();
261
+ }
262
+ });
263
+
264
+ // -------- public API for host programmatic control
265
+ defineExpose({
266
+ openCreate,
267
+ openDetail,
268
+ refresh,
269
+ });
270
+ </script>
271
+
272
+ <template>
273
+ <div class="kh-app">
274
+ <!--
275
+ header — replace the whole thing with #header, or extend with #header-actions
276
+ to keep the title/search but add extra buttons on the right.
277
+ -->
278
+ <slot
279
+ name="header"
280
+ :title="displayTitle"
281
+ :description="displayDescription"
282
+ :search-value="keyword"
283
+ :on-search="(v: string) => (keyword = v)"
284
+ :open-create="openCreate"
285
+ >
286
+ <div class="kh-app-head">
287
+ <div>
288
+ <div class="kh-app-title">{{ displayTitle }}</div>
289
+ <div class="kh-app-desc">{{ displayDescription }}</div>
290
+ </div>
291
+ <div class="kh-app-toolbar">
292
+ <input
293
+ v-model="keyword"
294
+ class="kh-app-search"
295
+ :placeholder="t('app.searchPlaceholder')"
296
+ />
297
+ <slot name="header-actions" :open-create="openCreate" />
298
+ </div>
299
+ </div>
300
+ </slot>
301
+
302
+ <!--
303
+ First-time-use nudge: no embedding model registered yet. Only shows once
304
+ the initial fetch has resolved with an empty list — avoids a flash while
305
+ loading. Consumers wire {@code api.onGoToEmbeddingSetup} to their model
306
+ route so the "去配置模型" button jumps there in-app. When the callback
307
+ is omitted the card still renders (useful hint) but without the button.
308
+ -->
309
+ <div
310
+ v-if="!loading && embeddingModels.length === 0"
311
+ class="kh-app-nudge"
312
+ >
313
+ <div class="kh-app-nudge-icon">🎯</div>
314
+ <div class="kh-app-nudge-body">
315
+ <div class="kh-app-nudge-title">{{ t('app.nudgeEmbeddingTitle') }}</div>
316
+ <div class="kh-app-nudge-desc">{{ t('app.nudgeEmbeddingDesc') }}</div>
317
+ <button
318
+ v-if="api.onGoToEmbeddingSetup"
319
+ class="kh-app-nudge-btn"
320
+ type="button"
321
+ @click="api.onGoToEmbeddingSetup?.()"
322
+ >
323
+ {{ t('app.nudgeEmbeddingCta') }}
324
+ </button>
325
+ </div>
326
+ </div>
327
+
328
+ <!-- card grid — replace with #cards to render your own layout, or use #empty
329
+ to override just the empty state. -->
330
+ <div class="kh-app-body">
331
+ <div v-if="loading" class="kh-app-loading">
332
+ <slot name="loading">{{ t('app.loading') }}</slot>
333
+ </div>
334
+ <template v-else-if="datasets.length === 0">
335
+ <slot name="empty" :open-create="openCreate">
336
+ <DatasetCardGrid
337
+ :datasets="datasets"
338
+ :keyword="keyword"
339
+ @create="openCreate"
340
+ @open="(d: DatasetCardItem) => openDetail(d.id)"
341
+ @settings="openSettings"
342
+ @delete="onDeleteCard"
343
+ />
344
+ </slot>
345
+ </template>
346
+ <slot
347
+ v-else
348
+ name="cards"
349
+ :datasets="datasets"
350
+ :keyword="keyword"
351
+ :open-create="openCreate"
352
+ :open-detail="openDetail"
353
+ :open-settings="openSettings"
354
+ :on-delete="onDeleteCard"
355
+ >
356
+ <DatasetCardGrid
357
+ :datasets="datasets"
358
+ :keyword="keyword"
359
+ @create="openCreate"
360
+ @open="(d: DatasetCardItem) => openDetail(d.id)"
361
+ @settings="openSettings"
362
+ @delete="onDeleteCard"
363
+ />
364
+ </slot>
365
+ </div>
366
+
367
+ <!-- create wizard (left-side drawer). previewChunks runs through the api
368
+ adapter so the step-2 preview matches real ingestion output. -->
369
+ <CreateDatasetWizard
370
+ :open="wizardOpen"
371
+ :embedding-models="embeddingModels"
372
+ :rerank-models="rerankModels"
373
+ :preview-chunks="api.previewChunks"
374
+ @update:open="wizardOpen = $event"
375
+ @create="onWizardCreate"
376
+ @skip-to-empty="onWizardSkipEmpty"
377
+ />
378
+
379
+ <!-- detail drawer (right-side) -->
380
+ <DatasetDetailDrawer
381
+ :open="detailOpen"
382
+ :dataset-id="detailId"
383
+ :initial-tab="detailInitialTab"
384
+ :hub="detailHub"
385
+ @update:open="detailOpen = $event"
386
+ @refresh-list="onDetailChanged"
387
+ />
388
+
389
+ <!-- inline prompt for "create empty knowledge base" — replaces window.prompt -->
390
+ <KhDialog
391
+ :open="emptyPromptOpen"
392
+ :title="t('wizard.createEmptyPromptTitle')"
393
+ :prompt-default="t('wizard.createEmptyPromptDefault')"
394
+ @update:open="emptyPromptOpen = $event"
395
+ @confirm="onEmptyPromptConfirm"
396
+ />
397
+
398
+ <!-- inline confirm for dataset delete — replaces window.confirm -->
399
+ <KhDialog
400
+ :open="deleteConfirmOpen"
401
+ :title="t('card.delete')"
402
+ :content="deleteTarget
403
+ ? t('card.confirmDelete', { name: deleteTarget.name })
404
+ : ''"
405
+ :ok-text="t('common.delete')"
406
+ danger
407
+ @update:open="deleteConfirmOpen = $event"
408
+ @confirm="onDeleteConfirm"
409
+ />
410
+ </div>
411
+ </template>
412
+
413
+ <style scoped>
414
+ .kh-app {
415
+ display: flex;
416
+ flex-direction: column;
417
+ gap: var(--kh-space-4);
418
+ }
419
+ .kh-app-head {
420
+ display: flex;
421
+ justify-content: space-between;
422
+ align-items: flex-end;
423
+ gap: var(--kh-space-3);
424
+ }
425
+ .kh-app-title {
426
+ font-size: 20px;
427
+ font-weight: 600;
428
+ color: var(--kh-color-text-primary);
429
+ }
430
+ .kh-app-desc {
431
+ margin-top: 2px;
432
+ font-size: var(--kh-fs-md);
433
+ color: var(--kh-color-text-muted);
434
+ }
435
+ /* First-time-use nudge — "先注册 Embedding 模型" empty-state card. */
436
+ .kh-app-nudge {
437
+ display: flex;
438
+ align-items: flex-start;
439
+ gap: var(--kh-space-3);
440
+ padding: var(--kh-space-3) var(--kh-space-4);
441
+ background: #fff;
442
+ border: 1px solid var(--kh-color-border);
443
+ border-radius: 8px;
444
+ }
445
+ .kh-app-nudge-icon {
446
+ font-size: 22px;
447
+ line-height: 1;
448
+ }
449
+ .kh-app-nudge-body {
450
+ flex: 1;
451
+ }
452
+ .kh-app-nudge-title {
453
+ font-size: var(--kh-fs-md);
454
+ font-weight: 500;
455
+ color: var(--kh-color-text-primary);
456
+ }
457
+ .kh-app-nudge-desc {
458
+ margin-top: 4px;
459
+ font-size: var(--kh-fs-sm, 13px);
460
+ color: var(--kh-color-text-muted);
461
+ line-height: 1.55;
462
+ }
463
+ .kh-app-nudge-btn {
464
+ margin-top: 8px;
465
+ padding: 4px 12px;
466
+ background: #4f46e5;
467
+ color: #fff;
468
+ border: none;
469
+ border-radius: 6px;
470
+ font-size: 13px;
471
+ cursor: pointer;
472
+ transition: background 0.15s ease;
473
+ }
474
+ .kh-app-nudge-btn:hover {
475
+ background: #4338ca;
476
+ }
477
+ .kh-app-toolbar {
478
+ display: flex;
479
+ gap: var(--kh-space-2);
480
+ }
481
+ .kh-app-search {
482
+ padding: 6px var(--kh-space-3);
483
+ border: 1px solid var(--kh-color-border);
484
+ border-radius: var(--kh-input-radius);
485
+ outline: none;
486
+ font-size: var(--kh-fs-lg);
487
+ min-width: 240px;
488
+ background: var(--kh-input-bg);
489
+ color: var(--kh-color-text-primary);
490
+ }
491
+ .kh-app-search:focus {
492
+ border-color: var(--kh-color-primary);
493
+ box-shadow: var(--kh-focus-ring);
494
+ }
495
+ .kh-app-body {
496
+ min-height: 200px;
497
+ }
498
+ .kh-app-loading {
499
+ padding: 60px 20px;
500
+ text-align: center;
501
+ color: var(--kh-color-text-muted);
502
+ font-size: var(--kh-fs-md);
503
+ }
504
+ </style>