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,490 @@
1
+ <script setup lang="ts">
2
+ /**
3
+ * LogAnnotationPanel — content for {@code AppDesignDrawer}'s "日志与标注" tab.
4
+ *
5
+ * Two sub-tabs:
6
+ * • 日志 — conversation list for this app; click a row to see the raw
7
+ * USER/ASSISTANT messages in a right-side drawer.
8
+ * • 标注 — CRUD table of QA pairs (Dify's "标注回复"); add / edit via modal,
9
+ * toggle enabled inline, delete with confirm.
10
+ *
11
+ * All backend I/O goes through {@link AppStudioApi} so a host doesn't have
12
+ * to re-implement the panel — it just supplies the callback bag. Any API
13
+ * method the host omits is treated as "not supported" and the corresponding
14
+ * tab / action degrades to an empty-state placeholder.
15
+ */
16
+ import { computed, onMounted, reactive, ref, watch } from 'vue';
17
+
18
+ import {
19
+ Button,
20
+ Drawer,
21
+ Empty,
22
+ Form,
23
+ FormItem,
24
+ Modal,
25
+ Popconfirm,
26
+ Space,
27
+ Switch,
28
+ Table,
29
+ Tabs,
30
+ TabPane,
31
+ Tag,
32
+ Textarea,
33
+ message,
34
+ } from 'ant-design-vue';
35
+
36
+ import type {
37
+ AppStudioApi,
38
+ StudioAnnotation,
39
+ StudioConversationSummary,
40
+ StudioHistoryMessage,
41
+ } from '../api';
42
+
43
+ interface Props {
44
+ app?: { id: string; name: string } | null;
45
+ api: AppStudioApi;
46
+ }
47
+ const props = defineProps<Props>();
48
+
49
+ const activeTab = ref<'annotations' | 'logs'>('logs');
50
+
51
+ // ---- 日志 tab -----------------------------------------------------------
52
+ const conversations = ref<StudioConversationSummary[]>([]);
53
+ const logsLoading = ref(false);
54
+
55
+ // 后端返回 LocalDateTime.toString()({@code 2025-11-28T14:32:15.123}),
56
+ // 直接给单元格会因为默认 wrap 换行成两行。压成 {@code YYYY-MM-DD HH:mm}
57
+ // 让常用列宽下 nowrap 也不溢出;秒/毫秒精度在"日志"表格里没实际价值。
58
+ function formatUpdatedAt(raw?: null | string): string {
59
+ if (!raw) return '—';
60
+ return raw.slice(0, 16).replace('T', ' ');
61
+ }
62
+
63
+ const logColumns = [
64
+ { title: '会话 ID', dataIndex: 'conversationId', ellipsis: true, width: 200 },
65
+ { title: '用户 ID', dataIndex: 'userId', key: 'userId', ellipsis: true, width: 180 },
66
+ { title: '首条消息', dataIndex: 'firstMessage', ellipsis: true, minWidth: 220 },
67
+ { title: '更新时间', dataIndex: 'updatedAt', key: 'updatedAt', width: 160 },
68
+ { title: '操作', key: 'action', width: 80, fixed: 'right' as const },
69
+ ];
70
+
71
+ async function reloadLogs() {
72
+ if (!props.app?.id || !props.api.listConversations) return;
73
+ logsLoading.value = true;
74
+ try {
75
+ conversations.value = await props.api.listConversations(props.app.id, 100);
76
+ } catch {
77
+ conversations.value = [];
78
+ } finally {
79
+ logsLoading.value = false;
80
+ }
81
+ }
82
+
83
+ // Log drilldown drawer — click a conversation row to see its raw messages.
84
+ const logDrawerOpen = ref(false);
85
+ const logDrawerTitle = ref('');
86
+ const logMessages = ref<StudioHistoryMessage[]>([]);
87
+ const logHistoryLoading = ref(false);
88
+
89
+ async function openLogDetail(row: StudioConversationSummary) {
90
+ if (!props.app?.id || !props.api.fetchHistory) return;
91
+ logDrawerOpen.value = true;
92
+ logDrawerTitle.value = `会话 · ${row.conversationId.slice(0, 8)}…`;
93
+ logHistoryLoading.value = true;
94
+ try {
95
+ logMessages.value = await props.api.fetchHistory(
96
+ props.app.id,
97
+ row.conversationId,
98
+ 500,
99
+ );
100
+ } catch (e: any) {
101
+ message.error(e?.message ?? '加载失败');
102
+ logMessages.value = [];
103
+ } finally {
104
+ logHistoryLoading.value = false;
105
+ }
106
+ }
107
+
108
+ // ---- 标注 tab -----------------------------------------------------------
109
+ const annotations = ref<StudioAnnotation[]>([]);
110
+ const annLoading = ref(false);
111
+
112
+ const annColumns = [
113
+ { title: '提问', dataIndex: 'question', ellipsis: true },
114
+ { title: '回答', dataIndex: 'content', ellipsis: true },
115
+ { title: '命中次数', dataIndex: 'hitCount', width: 100 },
116
+ { title: '启用', key: 'enabled', width: 90 },
117
+ { title: '更新时间', dataIndex: 'updatedAt', width: 180 },
118
+ { title: '操作', key: 'action', width: 140 },
119
+ ];
120
+
121
+ async function reloadAnnotations() {
122
+ if (!props.app?.id || !props.api.listAnnotations) return;
123
+ annLoading.value = true;
124
+ try {
125
+ annotations.value = await props.api.listAnnotations(props.app.id);
126
+ } catch {
127
+ annotations.value = [];
128
+ } finally {
129
+ annLoading.value = false;
130
+ }
131
+ }
132
+
133
+ // Add/edit modal.
134
+ const annModalOpen = ref(false);
135
+ const annModalTitle = ref('');
136
+ const annForm = reactive({
137
+ id: null as string | null,
138
+ question: '',
139
+ content: '',
140
+ enabled: true,
141
+ });
142
+ const annSaving = ref(false);
143
+
144
+ function openAdd() {
145
+ annForm.id = null;
146
+ annForm.question = '';
147
+ annForm.content = '';
148
+ annForm.enabled = true;
149
+ annModalTitle.value = '添加标注';
150
+ annModalOpen.value = true;
151
+ }
152
+
153
+ function openEdit(row: StudioAnnotation) {
154
+ annForm.id = row.id;
155
+ annForm.question = row.question ?? '';
156
+ annForm.content = row.content ?? '';
157
+ annForm.enabled = row.enabled ?? true;
158
+ annModalTitle.value = '编辑标注';
159
+ annModalOpen.value = true;
160
+ }
161
+
162
+ async function submitAnnotation() {
163
+ if (!props.app?.id) return;
164
+ if (!annForm.question.trim() || !annForm.content.trim()) {
165
+ message.warning('请填写提问和回答');
166
+ return;
167
+ }
168
+ annSaving.value = true;
169
+ try {
170
+ const payload = {
171
+ question: annForm.question.trim(),
172
+ content: annForm.content.trim(),
173
+ enabled: annForm.enabled,
174
+ };
175
+ if (annForm.id && props.api.updateAnnotation) {
176
+ await props.api.updateAnnotation(props.app.id, annForm.id, payload);
177
+ message.success('已更新');
178
+ } else if (!annForm.id && props.api.createAnnotation) {
179
+ await props.api.createAnnotation(props.app.id, payload);
180
+ message.success('已添加');
181
+ }
182
+ annModalOpen.value = false;
183
+ await reloadAnnotations();
184
+ } catch (e: any) {
185
+ message.error(e?.message ?? '保存失败');
186
+ } finally {
187
+ annSaving.value = false;
188
+ }
189
+ }
190
+
191
+ async function toggleEnabled(row: StudioAnnotation, next: boolean) {
192
+ if (!props.app?.id || !props.api.updateAnnotation) return;
193
+ try {
194
+ await props.api.updateAnnotation(props.app.id, row.id, {
195
+ question: row.question ?? '',
196
+ content: row.content ?? '',
197
+ enabled: next,
198
+ });
199
+ row.enabled = next;
200
+ message.success(next ? '已启用' : '已停用');
201
+ } catch (e: any) {
202
+ message.error(e?.message ?? '更新失败');
203
+ }
204
+ }
205
+
206
+ async function removeAnnotation(row: StudioAnnotation) {
207
+ if (!props.app?.id || !props.api.deleteAnnotation) return;
208
+ try {
209
+ await props.api.deleteAnnotation(props.app.id, row.id);
210
+ message.success('已删除');
211
+ await reloadAnnotations();
212
+ } catch (e: any) {
213
+ message.error(e?.message ?? '删除失败');
214
+ }
215
+ }
216
+
217
+ // ---- Life cycle ---------------------------------------------------------
218
+ watch(
219
+ () => props.app?.id,
220
+ () => {
221
+ if (activeTab.value === 'logs') reloadLogs();
222
+ else reloadAnnotations();
223
+ },
224
+ );
225
+ watch(activeTab, (v) => {
226
+ if (v === 'logs' && conversations.value.length === 0) reloadLogs();
227
+ if (v === 'annotations' && annotations.value.length === 0) reloadAnnotations();
228
+ });
229
+ onMounted(() => {
230
+ reloadLogs();
231
+ });
232
+
233
+ const disabled = computed(() => !props.app?.id);
234
+ </script>
235
+
236
+ <template>
237
+ <div class="log-annot">
238
+ <Tabs v-model:active-key="activeTab">
239
+ <TabPane key="logs" tab="日志">
240
+ <div class="log-annot-hint">
241
+ 日志记录了应用的运行情况,包括用户的输入和 AI 的回复。
242
+ </div>
243
+ <div class="log-annot-toolbar">
244
+ <span class="log-annot-count">共 {{ conversations.length }} 个会话</span>
245
+ <Button size="small" :disabled="disabled" @click="reloadLogs">
246
+ 🔄 刷新
247
+ </Button>
248
+ </div>
249
+ <Table
250
+ :columns="logColumns"
251
+ :data-source="conversations"
252
+ :loading="logsLoading"
253
+ :pagination="{ pageSize: 20 }"
254
+ :scroll="{ x: 900 }"
255
+ size="small"
256
+ row-key="conversationId"
257
+ :locale="{ emptyText: '暂无对话' }"
258
+ >
259
+ <template #bodyCell="{ column, record }">
260
+ <template v-if="column.key === 'userId'">
261
+ <span
262
+ v-if="record.userId"
263
+ class="log-annot-userid"
264
+ :title="record.userId"
265
+ >
266
+ {{ record.userId }}
267
+ </span>
268
+ <span v-else class="log-annot-muted">—</span>
269
+ </template>
270
+ <template v-else-if="column.key === 'updatedAt'">
271
+ <span class="log-annot-nowrap">{{ formatUpdatedAt(record.updatedAt) }}</span>
272
+ </template>
273
+ <template v-else-if="column.key === 'action'">
274
+ <a
275
+ class="log-annot-link"
276
+ @click="openLogDetail(record as StudioConversationSummary)"
277
+ >
278
+ 查看
279
+ </a>
280
+ </template>
281
+ </template>
282
+ </Table>
283
+ </TabPane>
284
+
285
+ <TabPane key="annotations" tab="标注" force-render>
286
+ <div class="log-annot-hint">
287
+ 标注即用户手工维护的问答对。当用户提问命中一条已启用的标注时,应用会直接返回标注中的回答,跳过 LLM。
288
+ </div>
289
+ <div class="log-annot-toolbar">
290
+ <span class="log-annot-count">共 {{ annotations.length }} 条</span>
291
+ <Space>
292
+ <Button size="small" :disabled="disabled" @click="reloadAnnotations">
293
+ 🔄 刷新
294
+ </Button>
295
+ <Button
296
+ type="primary"
297
+ size="small"
298
+ :disabled="disabled"
299
+ @click="openAdd"
300
+ >
301
+ + 添加标注
302
+ </Button>
303
+ </Space>
304
+ </div>
305
+ <Table
306
+ :columns="annColumns"
307
+ :data-source="annotations"
308
+ :loading="annLoading"
309
+ :pagination="{ pageSize: 20 }"
310
+ size="small"
311
+ row-key="id"
312
+ :locale="{ emptyText: '暂无标注' }"
313
+ >
314
+ <template #bodyCell="{ column, record }">
315
+ <template v-if="column.key === 'enabled'">
316
+ <Switch
317
+ :checked="!!record.enabled"
318
+ size="small"
319
+ @change="(v: any) => toggleEnabled(record as StudioAnnotation, !!v)"
320
+ />
321
+ </template>
322
+ <template v-else-if="column.key === 'action'">
323
+ <Space>
324
+ <a
325
+ class="log-annot-link"
326
+ @click="openEdit(record as StudioAnnotation)"
327
+ >
328
+ 编辑
329
+ </a>
330
+ <Popconfirm
331
+ title="删除后不可恢复"
332
+ ok-text="删除"
333
+ cancel-text="取消"
334
+ @confirm="removeAnnotation(record as StudioAnnotation)"
335
+ >
336
+ <a class="log-annot-link log-annot-link-danger">删除</a>
337
+ </Popconfirm>
338
+ </Space>
339
+ </template>
340
+ </template>
341
+ </Table>
342
+ </TabPane>
343
+ </Tabs>
344
+
345
+ <!-- Log drilldown drawer -->
346
+ <Drawer
347
+ v-model:open="logDrawerOpen"
348
+ :title="logDrawerTitle"
349
+ width="640"
350
+ placement="right"
351
+ >
352
+ <div v-if="logHistoryLoading">加载中…</div>
353
+ <Empty
354
+ v-else-if="logMessages.length === 0"
355
+ description="该会话没有消息"
356
+ />
357
+ <div v-else class="log-annot-messages">
358
+ <div
359
+ v-for="(m, i) in logMessages"
360
+ :key="i"
361
+ class="log-annot-msg"
362
+ :class="`log-annot-msg-${m.role.toLowerCase()}`"
363
+ >
364
+ <Tag
365
+ :color="m.role === 'USER' ? 'blue' : m.role === 'SYSTEM' ? 'default' : 'green'"
366
+ >
367
+ {{ m.role }}
368
+ </Tag>
369
+ <div class="log-annot-msg-body">{{ m.content }}</div>
370
+ </div>
371
+ </div>
372
+ </Drawer>
373
+
374
+ <!-- Add / edit annotation modal -->
375
+ <Modal
376
+ v-model:open="annModalOpen"
377
+ :title="annModalTitle"
378
+ :confirm-loading="annSaving"
379
+ ok-text="保存"
380
+ cancel-text="取消"
381
+ width="640px"
382
+ @ok="submitAnnotation"
383
+ >
384
+ <Form :model="annForm" layout="vertical">
385
+ <FormItem label="提问" required>
386
+ <Textarea
387
+ v-model:value="annForm.question"
388
+ :rows="2"
389
+ placeholder="用户可能输入的问题"
390
+ />
391
+ </FormItem>
392
+ <FormItem label="回答" required>
393
+ <Textarea
394
+ v-model:value="annForm.content"
395
+ :rows="4"
396
+ placeholder="命中该问题时返回的回答"
397
+ />
398
+ </FormItem>
399
+ <FormItem label="启用">
400
+ <Switch v-model:checked="annForm.enabled" />
401
+ <span class="log-annot-inline-hint">
402
+ {{ annForm.enabled ? '已启用 · 命中时立即返回' : '未启用 · 保留但不参与匹配' }}
403
+ </span>
404
+ </FormItem>
405
+ </Form>
406
+ </Modal>
407
+ </div>
408
+ </template>
409
+
410
+ <style scoped>
411
+ .log-annot {
412
+ padding: 12px 20px 20px;
413
+ background: #fff;
414
+ }
415
+ .log-annot-hint {
416
+ padding: 8px 12px;
417
+ margin-bottom: 12px;
418
+ font-size: 12px;
419
+ color: #64748b;
420
+ background: #f8fafc;
421
+ border-left: 3px solid #6366f1;
422
+ border-radius: 4px;
423
+ }
424
+ .log-annot-toolbar {
425
+ display: flex;
426
+ justify-content: space-between;
427
+ align-items: center;
428
+ margin-bottom: 8px;
429
+ }
430
+ .log-annot-count {
431
+ font-size: 12px;
432
+ color: #94a3b8;
433
+ }
434
+ .log-annot-link {
435
+ color: #4338ca;
436
+ cursor: pointer;
437
+ }
438
+ .log-annot-link:hover {
439
+ text-decoration: underline;
440
+ }
441
+ .log-annot-link-danger {
442
+ color: #dc2626;
443
+ }
444
+ .log-annot-nowrap {
445
+ white-space: nowrap;
446
+ font-variant-numeric: tabular-nums;
447
+ color: #475569;
448
+ font-size: 12px;
449
+ }
450
+ .log-annot-userid {
451
+ font-family: 'JetBrains Mono', 'SFMono-Regular', ui-monospace, Menlo, Consolas, monospace;
452
+ font-size: 12px;
453
+ color: #334155;
454
+ }
455
+ .log-annot-muted {
456
+ color: #cbd5e1;
457
+ }
458
+ .log-annot-messages {
459
+ display: flex;
460
+ flex-direction: column;
461
+ gap: 10px;
462
+ }
463
+ .log-annot-msg {
464
+ padding: 8px 12px;
465
+ background: #f8fafc;
466
+ border-radius: 8px;
467
+ display: flex;
468
+ gap: 8px;
469
+ align-items: flex-start;
470
+ }
471
+ .log-annot-msg-user {
472
+ background: #eef4ff;
473
+ }
474
+ .log-annot-msg-assistant {
475
+ background: #f0fdf4;
476
+ }
477
+ .log-annot-msg-body {
478
+ font-size: 13px;
479
+ white-space: pre-wrap;
480
+ word-break: break-word;
481
+ line-height: 1.6;
482
+ color: #0f172a;
483
+ flex: 1;
484
+ }
485
+ .log-annot-inline-hint {
486
+ margin-left: 10px;
487
+ font-size: 12px;
488
+ color: #64748b;
489
+ }
490
+ </style>