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,535 @@
1
+ <script setup lang="ts">
2
+ /**
3
+ * MonitorPanel — content for {@code AppDesignDrawer}'s "监测" tab.
4
+ *
5
+ * All backend I/O goes through {@link AppStudioApi} — the host supplies
6
+ * {@code fetchAppMetrics}, {@code fetchLlmUsage}, {@code fetchRecentLlmCalls}
7
+ * and the panel wires them into the tile grid + recent-calls table.
8
+ *
9
+ * Layout mirrors the Dify workspace/monitor stub:
10
+ * ┌───── 应用级指标 ──────────────┐
11
+ * │ 会话数 消息数 平均互动 最近活跃 │
12
+ * └───────────────────────────────┘
13
+ * ┌───── 全局 LLM 用量 ────────────┐
14
+ * │ 调用数 Tokens 费用 平均延迟 │
15
+ * └───────────────────────────────┘
16
+ * ┌───── 最近 10 条 LLM 调用 ──────┐
17
+ * │ Table: model · tokens · cost │
18
+ * └───────────────────────────────┘
19
+ */
20
+ import { computed, markRaw, onMounted, ref, watch } from 'vue';
21
+
22
+ import {
23
+ ClockCircleOutlined,
24
+ CommentOutlined,
25
+ DollarOutlined,
26
+ ExperimentOutlined,
27
+ FieldTimeOutlined,
28
+ MessageOutlined,
29
+ ReloadOutlined,
30
+ SwapOutlined,
31
+ ThunderboltOutlined,
32
+ } from '@ant-design/icons-vue';
33
+ import { Button, Empty, Skeleton, Table, Tag, message } from 'ant-design-vue';
34
+
35
+ import type {
36
+ AppStudioApi,
37
+ StudioAppMetrics,
38
+ StudioLlmCallRecord,
39
+ StudioLlmUsageStats,
40
+ } from '../api';
41
+
42
+ interface Props {
43
+ app?: { id: string; name: string } | null;
44
+ api: AppStudioApi;
45
+ }
46
+ const props = defineProps<Props>();
47
+
48
+ const appMetrics = ref<StudioAppMetrics | null>(null);
49
+ const totalStats = ref<StudioLlmUsageStats | null>(null);
50
+ const recentCalls = ref<StudioLlmCallRecord[]>([]);
51
+ const loading = ref(false);
52
+
53
+ const callColumns = [
54
+ { title: '模型', key: 'model', dataIndex: 'model', ellipsis: true },
55
+ {
56
+ title: '提供商',
57
+ key: 'provider',
58
+ dataIndex: 'provider',
59
+ width: 110,
60
+ ellipsis: true,
61
+ },
62
+ {
63
+ title: 'Tokens',
64
+ key: 'totalTokens',
65
+ dataIndex: 'totalTokens',
66
+ width: 90,
67
+ align: 'right' as const,
68
+ },
69
+ { title: '费用', key: 'costMicros', width: 90, align: 'right' as const },
70
+ { title: '延迟', key: 'latencyMs', width: 80, align: 'right' as const },
71
+ { title: '状态', key: 'success', width: 70, align: 'center' as const },
72
+ { title: '时间', key: 'createdAt', width: 150, align: 'right' as const },
73
+ ];
74
+
75
+ async function reload() {
76
+ if (!props.app?.id) return;
77
+ loading.value = true;
78
+ const jobs: Promise<unknown>[] = [];
79
+ if (props.api.fetchAppMetrics) {
80
+ jobs.push(
81
+ props.api
82
+ .fetchAppMetrics(props.app.id)
83
+ .then((r) => (appMetrics.value = r))
84
+ .catch(() => (appMetrics.value = null)),
85
+ );
86
+ }
87
+ if (props.api.fetchLlmUsage) {
88
+ jobs.push(
89
+ props.api
90
+ .fetchLlmUsage()
91
+ .then((r) => (totalStats.value = r))
92
+ .catch(() => (totalStats.value = null)),
93
+ );
94
+ }
95
+ if (props.api.fetchRecentLlmCalls) {
96
+ jobs.push(
97
+ props.api
98
+ .fetchRecentLlmCalls(10)
99
+ .then((r) => (recentCalls.value = r))
100
+ .catch(() => (recentCalls.value = [])),
101
+ );
102
+ }
103
+ try {
104
+ await Promise.all(jobs);
105
+ } catch (e: any) {
106
+ message.error(e?.message ?? '加载失败');
107
+ } finally {
108
+ loading.value = false;
109
+ }
110
+ }
111
+
112
+ watch(() => props.app?.id, reload);
113
+ onMounted(reload);
114
+
115
+ // ---- Formatters ---------------------------------------------------------
116
+ function fromNow(iso?: string): string {
117
+ if (!iso) return '暂无数据';
118
+ const t = new Date(iso).getTime();
119
+ if (Number.isNaN(t)) return iso;
120
+ const diff = (Date.now() - t) / 1000;
121
+ if (diff < 60) return '刚刚';
122
+ if (diff < 3600) return `${Math.floor(diff / 60)} 分钟前`;
123
+ if (diff < 86_400) return `${Math.floor(diff / 3600)} 小时前`;
124
+ if (diff < 86_400 * 30) return `${Math.floor(diff / 86_400)} 天前`;
125
+ return new Date(iso).toLocaleDateString();
126
+ }
127
+ function fmtNumber(n?: number): string {
128
+ if (n == null) return '0';
129
+ if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(1)}M`;
130
+ if (n >= 1000) return `${(n / 1000).toFixed(1)}k`;
131
+ return String(n);
132
+ }
133
+ function fmtCost(micros?: number): string {
134
+ if (!micros) return '¥0.00';
135
+ const v = micros / 1_000_000;
136
+ return `¥${v.toFixed(v < 1 ? 4 : 2)}`;
137
+ }
138
+ function fmtLatency(ms?: number): string {
139
+ if (ms == null) return '—';
140
+ if (ms >= 1000) return `${(ms / 1000).toFixed(2)}s`;
141
+ return `${Math.round(ms)}ms`;
142
+ }
143
+ /** Compact table timestamp — same-day shows HH:mm:ss, older shows MM-DD HH:mm. */
144
+ function fmtCallTime(iso?: string): string {
145
+ if (!iso) return '—';
146
+ const d = new Date(iso);
147
+ if (Number.isNaN(d.getTime())) return iso;
148
+ const pad = (n: number) => String(n).padStart(2, '0');
149
+ const now = new Date();
150
+ const sameDay =
151
+ d.getFullYear() === now.getFullYear() &&
152
+ d.getMonth() === now.getMonth() &&
153
+ d.getDate() === now.getDate();
154
+ if (sameDay) {
155
+ return `${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`;
156
+ }
157
+ return `${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}`;
158
+ }
159
+
160
+ /**
161
+ * Tile palette — each metric gets a distinct hue via CSS variables so the
162
+ * icon badge (bg + fg) and the top accent line stay in sync. Colors are
163
+ * picked from the Tailwind 500/50 pairs so the tint stays legible on white.
164
+ */
165
+ type TilePalette = { fg: string; bg: string };
166
+ const PALETTE = {
167
+ indigo: { fg: '#4f46e5', bg: '#eef2ff' },
168
+ green: { fg: '#16a34a', bg: '#f0fdf4' },
169
+ orange: { fg: '#ea580c', bg: '#fff7ed' },
170
+ sky: { fg: '#0284c7', bg: '#f0f9ff' },
171
+ violet: { fg: '#7c3aed', bg: '#f5f3ff' },
172
+ fuchsia: { fg: '#c026d3', bg: '#fdf4ff' },
173
+ rose: { fg: '#e11d48', bg: '#fff1f2' },
174
+ cyan: { fg: '#0891b2', bg: '#ecfeff' },
175
+ } satisfies Record<string, TilePalette>;
176
+
177
+ const appTiles = computed(() => {
178
+ const m = appMetrics.value;
179
+ return [
180
+ {
181
+ label: '全部会话数',
182
+ value: fmtNumber(m?.totalConversations),
183
+ icon: markRaw(MessageOutlined),
184
+ palette: PALETTE.indigo,
185
+ },
186
+ {
187
+ label: '全部消息数',
188
+ value: fmtNumber(m?.totalMessages),
189
+ icon: markRaw(CommentOutlined),
190
+ palette: PALETTE.green,
191
+ },
192
+ {
193
+ label: '平均会话互动数',
194
+ value: (m?.avgInteractionsPerConversation ?? 0).toFixed(2),
195
+ icon: markRaw(SwapOutlined),
196
+ palette: PALETTE.orange,
197
+ },
198
+ {
199
+ label: '最近活跃',
200
+ value: fromNow(m?.lastActivityAt),
201
+ icon: markRaw(ClockCircleOutlined),
202
+ palette: PALETTE.sky,
203
+ },
204
+ ];
205
+ });
206
+
207
+ const globalTiles = computed(() => {
208
+ const t = totalStats.value;
209
+ return [
210
+ {
211
+ label: 'LLM 调用数',
212
+ value: fmtNumber(t?.calls),
213
+ sub: t ? `失败 ${t.errors}` : '',
214
+ icon: markRaw(ThunderboltOutlined),
215
+ palette: PALETTE.violet,
216
+ },
217
+ {
218
+ label: '总 Tokens',
219
+ value: fmtNumber(t?.totalTokens),
220
+ sub: t
221
+ ? `Prompt ${fmtNumber(t.promptTokens)} · Completion ${fmtNumber(t.completionTokens)}`
222
+ : '',
223
+ icon: markRaw(ExperimentOutlined),
224
+ palette: PALETTE.fuchsia,
225
+ },
226
+ {
227
+ label: '费用消耗',
228
+ value: fmtCost(t?.costMicros),
229
+ sub: '按配置的模型定价累计',
230
+ icon: markRaw(DollarOutlined),
231
+ palette: PALETTE.rose,
232
+ },
233
+ {
234
+ label: '平均延迟',
235
+ value: fmtLatency(t?.avgLatencyMs),
236
+ sub: '所有 LLM 调用',
237
+ icon: markRaw(FieldTimeOutlined),
238
+ palette: PALETTE.cyan,
239
+ },
240
+ ];
241
+ });
242
+ </script>
243
+
244
+ <template>
245
+ <div class="monitor">
246
+ <div class="monitor-head">
247
+ <div>
248
+ <div class="monitor-title">监测 · 追踪应用性能</div>
249
+ <div class="monitor-sub">
250
+ 应用级指标 + 全局 LLM 用量。数据源由宿主通过 <code>AppStudioApi</code> 注入。
251
+ </div>
252
+ </div>
253
+ <Button size="small" @click="reload">
254
+ <template #icon>
255
+ <ReloadOutlined />
256
+ </template>
257
+ 刷新
258
+ </Button>
259
+ </div>
260
+
261
+ <!-- Per-app tiles -->
262
+ <section class="monitor-section">
263
+ <div class="monitor-section-title">
264
+ <span class="monitor-section-dot" />
265
+ {{ app?.name || '应用' }} · 应用级指标
266
+ </div>
267
+ <div class="monitor-tiles">
268
+ <div
269
+ v-for="t in appTiles"
270
+ :key="t.label"
271
+ class="monitor-tile"
272
+ :style="{
273
+ '--tile-fg': t.palette.fg,
274
+ '--tile-bg': t.palette.bg,
275
+ }"
276
+ >
277
+ <div class="monitor-tile-icon">
278
+ <component :is="t.icon" />
279
+ </div>
280
+ <div class="monitor-tile-body">
281
+ <div class="monitor-tile-label">{{ t.label }}</div>
282
+ <div class="monitor-tile-value">
283
+ <Skeleton
284
+ v-if="loading && !appMetrics"
285
+ active
286
+ :paragraph="false"
287
+ :title="{ width: '60%' }"
288
+ />
289
+ <template v-else>{{ t.value }}</template>
290
+ </div>
291
+ </div>
292
+ </div>
293
+ </div>
294
+ </section>
295
+
296
+ <!-- Global LLM tiles -->
297
+ <section class="monitor-section">
298
+ <div class="monitor-section-title">
299
+ <span class="monitor-section-dot" />
300
+ 全局 LLM 用量
301
+ </div>
302
+ <div class="monitor-tiles">
303
+ <div
304
+ v-for="t in globalTiles"
305
+ :key="t.label"
306
+ class="monitor-tile"
307
+ :style="{
308
+ '--tile-fg': t.palette.fg,
309
+ '--tile-bg': t.palette.bg,
310
+ }"
311
+ >
312
+ <div class="monitor-tile-icon">
313
+ <component :is="t.icon" />
314
+ </div>
315
+ <div class="monitor-tile-body">
316
+ <div class="monitor-tile-label">{{ t.label }}</div>
317
+ <div class="monitor-tile-value">
318
+ <Skeleton
319
+ v-if="loading && !totalStats"
320
+ active
321
+ :paragraph="false"
322
+ :title="{ width: '60%' }"
323
+ />
324
+ <template v-else>{{ t.value }}</template>
325
+ </div>
326
+ <div v-if="t.sub" class="monitor-tile-sub">{{ t.sub }}</div>
327
+ </div>
328
+ </div>
329
+ </div>
330
+ </section>
331
+
332
+ <!-- Recent calls -->
333
+ <section class="monitor-section">
334
+ <div class="monitor-section-title">
335
+ <span class="monitor-section-dot" />
336
+ 最近 10 条 LLM 调用
337
+ </div>
338
+ <Table
339
+ class="monitor-table"
340
+ :columns="callColumns"
341
+ :data-source="recentCalls"
342
+ :loading="loading"
343
+ :pagination="false"
344
+ row-key="id"
345
+ size="small"
346
+ :scroll="{ x: 720 }"
347
+ :locale="{ emptyText: '尚无 LLM 调用记录' }"
348
+ >
349
+ <template #bodyCell="{ column, record }">
350
+ <template v-if="column.key === 'costMicros'">
351
+ {{ fmtCost(record.costMicros) }}
352
+ </template>
353
+ <template v-else-if="column.key === 'latencyMs'">
354
+ {{ fmtLatency(record.latencyMs) }}
355
+ </template>
356
+ <template v-else-if="column.key === 'success'">
357
+ <Tag :color="record.success ? 'green' : 'red'" class="mono-tag">
358
+ {{ record.success ? '✓' : '✕' }}
359
+ </Tag>
360
+ </template>
361
+ <template v-else-if="column.key === 'createdAt'">
362
+ <span
363
+ class="monitor-time"
364
+ :title="record.createdAt"
365
+ >{{ fmtCallTime(record.createdAt) }}</span>
366
+ </template>
367
+ </template>
368
+ </Table>
369
+ <Empty
370
+ v-if="!loading && recentCalls.length === 0"
371
+ description="尚无 LLM 调用记录 —— 让应用跑一次对话就会出现"
372
+ class="monitor-empty"
373
+ />
374
+ </section>
375
+ </div>
376
+ </template>
377
+
378
+ <style scoped>
379
+ .monitor {
380
+ padding: 16px 24px 24px;
381
+ background: #f8fafc;
382
+ min-height: 100%;
383
+ }
384
+ .monitor-head {
385
+ display: flex;
386
+ justify-content: space-between;
387
+ align-items: flex-start;
388
+ margin-bottom: 16px;
389
+ }
390
+ .monitor-title {
391
+ font-size: 15px;
392
+ font-weight: 600;
393
+ color: #0f172a;
394
+ }
395
+ .monitor-sub {
396
+ margin-top: 4px;
397
+ font-size: 12px;
398
+ color: #64748b;
399
+ }
400
+ .monitor-sub code {
401
+ padding: 1px 4px;
402
+ background: #eef2ff;
403
+ color: #4338ca;
404
+ border-radius: 3px;
405
+ font-size: 11px;
406
+ }
407
+ .monitor-section {
408
+ margin-top: 20px;
409
+ }
410
+ .monitor-section-title {
411
+ display: flex;
412
+ align-items: center;
413
+ gap: 6px;
414
+ font-size: 13px;
415
+ font-weight: 600;
416
+ color: #334155;
417
+ margin-bottom: 10px;
418
+ }
419
+ .monitor-section-dot {
420
+ width: 4px;
421
+ height: 14px;
422
+ background: #6366f1;
423
+ border-radius: 2px;
424
+ }
425
+ .monitor-tiles {
426
+ display: grid;
427
+ grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
428
+ gap: 12px;
429
+ }
430
+ .monitor-tile {
431
+ position: relative;
432
+ padding: 16px;
433
+ background: #fff;
434
+ border-radius: 12px;
435
+ border: 1px solid #eef1f5;
436
+ min-height: 96px;
437
+ display: flex;
438
+ align-items: flex-start;
439
+ gap: 12px;
440
+ overflow: hidden;
441
+ transition:
442
+ box-shadow 0.15s ease,
443
+ transform 0.15s ease,
444
+ border-color 0.15s ease;
445
+ }
446
+ .monitor-tile::before {
447
+ /* Left accent bar — colored per tile, keeps the grid visually rhythmic. */
448
+ content: '';
449
+ position: absolute;
450
+ top: 14px;
451
+ bottom: 14px;
452
+ left: 0;
453
+ width: 3px;
454
+ border-radius: 0 3px 3px 0;
455
+ background: var(--tile-fg, #6366f1);
456
+ }
457
+ .monitor-tile:hover {
458
+ box-shadow: 0 6px 18px rgba(15, 23, 42, 0.08);
459
+ transform: translateY(-1px);
460
+ border-color: #e2e8f0;
461
+ }
462
+ .monitor-tile-icon {
463
+ width: 38px;
464
+ height: 38px;
465
+ border-radius: 10px;
466
+ background: var(--tile-bg, #eef2ff);
467
+ color: var(--tile-fg, #4f46e5);
468
+ display: inline-flex;
469
+ align-items: center;
470
+ justify-content: center;
471
+ font-size: 18px;
472
+ flex-shrink: 0;
473
+ }
474
+ .monitor-tile-body {
475
+ flex: 1;
476
+ min-width: 0;
477
+ display: flex;
478
+ flex-direction: column;
479
+ gap: 4px;
480
+ }
481
+ .monitor-tile-label {
482
+ font-size: 12px;
483
+ font-weight: 500;
484
+ color: #64748b;
485
+ letter-spacing: 0.02em;
486
+ }
487
+ .monitor-tile-value {
488
+ font-size: 22px;
489
+ font-weight: 700;
490
+ color: #0f172a;
491
+ line-height: 1.2;
492
+ min-height: 28px;
493
+ font-variant-numeric: tabular-nums;
494
+ word-break: keep-all;
495
+ }
496
+ .monitor-tile-sub {
497
+ font-size: 11px;
498
+ color: #94a3b8;
499
+ line-height: 1.5;
500
+ white-space: nowrap;
501
+ overflow: hidden;
502
+ text-overflow: ellipsis;
503
+ }
504
+ .monitor-empty {
505
+ padding: 24px 0;
506
+ }
507
+
508
+ /* ── Recent calls table ─────────────────────────────────────────────── */
509
+ .monitor-table :deep(.ant-table-thead > tr > th) {
510
+ background: #f8fafc;
511
+ color: #475569;
512
+ font-weight: 600;
513
+ white-space: nowrap;
514
+ }
515
+ .monitor-table :deep(.ant-table-tbody > tr > td) {
516
+ white-space: nowrap;
517
+ font-variant-numeric: tabular-nums;
518
+ }
519
+ .monitor-table :deep(.ant-table-tbody > tr > td.ant-table-cell-ellipsis) {
520
+ white-space: nowrap;
521
+ overflow: hidden;
522
+ text-overflow: ellipsis;
523
+ }
524
+ .monitor-time {
525
+ color: #475569;
526
+ font-size: 12px;
527
+ white-space: nowrap;
528
+ font-variant-numeric: tabular-nums;
529
+ }
530
+ .mono-tag {
531
+ margin: 0;
532
+ min-width: 28px;
533
+ text-align: center;
534
+ }
535
+ </style>