openclaw-agent-dashboard 1.0.21 → 1.0.23

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 (135) hide show
  1. package/README.md +55 -321
  2. package/frontend-dist/assets/index-B7XqKAxm.css +1 -0
  3. package/frontend-dist/assets/index-CxJaSYyo.js +24 -0
  4. package/{frontend → frontend-dist}/index.html +2 -1
  5. package/{plugin/openclaw.plugin.json → openclaw.plugin.json} +2 -2
  6. package/package.json +21 -13
  7. package/.github/workflows/release.yml +0 -56
  8. package/VERSION_DISPLAY_delivery.md +0 -242
  9. package/VERSION_DISPLAY_implementation_summary.md +0 -315
  10. package/design_manifest.md +0 -100
  11. package/docs/CHANGELOG_AGENT_MODIFICATIONS.md +0 -132
  12. package/docs/MAINTAINER_RELEASE_WORKFLOW.md +0 -211
  13. package/docs/Openclaw-Agent-Dashboard/345/217/221/345/270/203/344/270/216/346/233/264/346/226/260.md +0 -147
  14. package/docs/RELEASE-LATEST.md +0 -189
  15. package/docs/RELEASE-MODEL-CONFIG.md +0 -95
  16. package/docs/WINDOWS_INSTALL_TROUBLESHOOTING.md +0 -171
  17. package/docs/design/.gitkeep +0 -0
  18. package/docs/design/VERSION_DISPLAY_design.md +0 -1236
  19. package/docs/release-guide.md +0 -259
  20. package/docs/release-operations-manual.md +0 -167
  21. package/docs/reviews/.gitkeep +0 -0
  22. package/docs/reviews/approval_history.json +0 -14
  23. package/docs/reviews/cr_VERSION_DISPLAY.md +0 -397
  24. package/docs/reviews/traceability_manifest.json +0 -279
  25. package/docs/specs/VERSION_DISPLAY_spec.md +0 -371
  26. package/docs/specs/tr3-install-system.md +0 -580
  27. package/docs/windows-collaboration-model-paths-troubleshooting.md +0 -0
  28. package/frontend/package-lock.json +0 -1240
  29. package/frontend/package.json +0 -19
  30. package/frontend/src/App.vue +0 -355
  31. package/frontend/src/components/AgentCard.vue +0 -796
  32. package/frontend/src/components/AgentConfigPanel.vue +0 -539
  33. package/frontend/src/components/AgentDetailPanel.vue +0 -738
  34. package/frontend/src/components/ErrorAnalysisView.vue +0 -546
  35. package/frontend/src/components/ErrorCenterPanel.vue +0 -844
  36. package/frontend/src/components/PerformanceMonitor.vue +0 -515
  37. package/frontend/src/components/SettingsPanel.vue +0 -236
  38. package/frontend/src/components/TokenAnalysisPanel.vue +0 -683
  39. package/frontend/src/components/chain/ChainEdge.vue +0 -85
  40. package/frontend/src/components/chain/ChainNode.vue +0 -166
  41. package/frontend/src/components/chain/TaskChainView.vue +0 -425
  42. package/frontend/src/components/chain/index.ts +0 -3
  43. package/frontend/src/components/chain/types.ts +0 -70
  44. package/frontend/src/components/collaboration/CollaborationFlowSection.vue +0 -1032
  45. package/frontend/src/components/collaboration/CollaborationFlowWrapper.vue +0 -113
  46. package/frontend/src/components/common/VersionDisplay.vue +0 -187
  47. package/frontend/src/components/performance/PerformancePanel.vue +0 -119
  48. package/frontend/src/components/performance/PerformanceSection.vue +0 -1137
  49. package/frontend/src/components/tasks/TaskStatusSection.vue +0 -973
  50. package/frontend/src/components/timeline/TimelineConnector.vue +0 -31
  51. package/frontend/src/components/timeline/TimelineRound.vue +0 -135
  52. package/frontend/src/components/timeline/TimelineStep.vue +0 -691
  53. package/frontend/src/components/timeline/TimelineToolLink.vue +0 -109
  54. package/frontend/src/components/timeline/TimelineView.vue +0 -540
  55. package/frontend/src/components/timeline/index.ts +0 -5
  56. package/frontend/src/components/timeline/types.ts +0 -120
  57. package/frontend/src/composables/index.ts +0 -7
  58. package/frontend/src/composables/useDebounce.ts +0 -48
  59. package/frontend/src/composables/useRealtime.ts +0 -52
  60. package/frontend/src/composables/useState.ts +0 -52
  61. package/frontend/src/composables/useThrottle.ts +0 -46
  62. package/frontend/src/composables/useVirtualScroll.ts +0 -106
  63. package/frontend/src/main.ts +0 -4
  64. package/frontend/src/managers/EventDispatcher.ts +0 -127
  65. package/frontend/src/managers/RealtimeDataManager.ts +0 -302
  66. package/frontend/src/managers/StateManager.ts +0 -128
  67. package/frontend/src/managers/index.ts +0 -5
  68. package/frontend/src/types/collaboration.ts +0 -135
  69. package/frontend/src/types/index.ts +0 -20
  70. package/frontend/src/types/performance.ts +0 -105
  71. package/frontend/src/types/task.ts +0 -38
  72. package/frontend/vite.config.ts +0 -18
  73. package/legacy_code_anatomy.md +0 -518
  74. package/plugin/README.md +0 -99
  75. package/plugin/config.json.example +0 -1
  76. package/plugin/package.json +0 -26
  77. package/scripts/build-plugin.js +0 -81
  78. package/scripts/bundle.sh +0 -62
  79. package/scripts/install-plugin.sh +0 -162
  80. package/scripts/install-python-deps.sh +0 -226
  81. package/scripts/install.js +0 -684
  82. package/scripts/install.sh +0 -367
  83. package/scripts/lib/common.sh +0 -137
  84. package/scripts/release-pack.sh +0 -110
  85. package/scripts/start.js +0 -50
  86. package/scripts/test_available_models.py +0 -284
  87. package/scripts/test_version_display.sh +0 -128
  88. package/scripts/test_websocket_ping.py +0 -44
  89. package/session_registry.json +0 -58
  90. package/tests/.gitkeep +0 -0
  91. package/tests/qa_regression_report.md +0 -359
  92. package/tests/qa_version_display_report.md +0 -598
  93. /package/{src/backend → dashboard}/agents.py +0 -0
  94. /package/{src/backend → dashboard}/api/__init__.py +0 -0
  95. /package/{src/backend → dashboard}/api/agent_config_api.py +0 -0
  96. /package/{src/backend → dashboard}/api/agents.py +0 -0
  97. /package/{src/backend → dashboard}/api/agents_config.py +0 -0
  98. /package/{src/backend → dashboard}/api/chains.py +0 -0
  99. /package/{src/backend → dashboard}/api/collaboration.py +0 -0
  100. /package/{src/backend → dashboard}/api/debug_paths.py +0 -0
  101. /package/{src/backend → dashboard}/api/error_analysis.py +0 -0
  102. /package/{src/backend → dashboard}/api/errors.py +0 -0
  103. /package/{src/backend → dashboard}/api/performance.py +0 -0
  104. /package/{src/backend → dashboard}/api/subagents.py +0 -0
  105. /package/{src/backend → dashboard}/api/timeline.py +0 -0
  106. /package/{src/backend → dashboard}/api/version.py +0 -0
  107. /package/{src/backend → dashboard}/api/websocket.py +0 -0
  108. /package/{src/backend → dashboard}/collaboration.py +0 -0
  109. /package/{src/backend → dashboard}/data/__init__.py +0 -0
  110. /package/{src/backend → dashboard}/data/agent_config_manager.py +0 -0
  111. /package/{src/backend → dashboard}/data/chain_reader.py +0 -0
  112. /package/{src/backend → dashboard}/data/config_reader.py +0 -0
  113. /package/{src/backend → dashboard}/data/error_analyzer.py +0 -0
  114. /package/{src/backend → dashboard}/data/session_reader.py +0 -0
  115. /package/{src/backend → dashboard}/data/subagent_reader.py +0 -0
  116. /package/{src/backend → dashboard}/data/task_history.py +0 -0
  117. /package/{src/backend → dashboard}/data/timeline_reader.py +0 -0
  118. /package/{src/backend → dashboard}/data/version_info_reader.py +0 -0
  119. /package/{src/backend → dashboard}/errors.py +0 -0
  120. /package/{src/backend → dashboard}/main.py +0 -0
  121. /package/{src/backend → dashboard}/mechanism_reader.py +0 -0
  122. /package/{src/backend → dashboard}/mechanisms.py +0 -0
  123. /package/{src/backend → dashboard}/performance.py +0 -0
  124. /package/{src/backend → dashboard}/requirements.txt +0 -0
  125. /package/{src/backend → dashboard}/session_reader.py +0 -0
  126. /package/{src/backend → dashboard}/status/__init__.py +0 -0
  127. /package/{src/backend → dashboard}/status/change_tracker.py +0 -0
  128. /package/{src/backend → dashboard}/status/error_detector.py +0 -0
  129. /package/{src/backend → dashboard}/status/status_cache.py +0 -0
  130. /package/{src/backend → dashboard}/status/status_calculator.py +0 -0
  131. /package/{src/backend → dashboard}/status_calculator.py +0 -0
  132. /package/{src/backend → dashboard}/subagent_reader.py +0 -0
  133. /package/{src/backend → dashboard}/watchers/__init__.py +0 -0
  134. /package/{src/backend → dashboard}/watchers/file_watcher.py +0 -0
  135. /package/{plugin/index.js → index.js} +0 -0
@@ -1,844 +0,0 @@
1
- <template>
2
- <section class="error-center">
3
- <div class="header">
4
- <h2>错误中心</h2>
5
- <div class="header-actions">
6
- <select v-model="selectedAgent" @change="loadData" class="filter-select">
7
- <option value="">全部 Agent</option>
8
- <option v-for="agent in agents" :key="agent.id" :value="agent.id">
9
- {{ agent.name }}
10
- </option>
11
- </select>
12
- <select v-model="selectedType" @change="loadData" class="filter-select">
13
- <option value="">全部类型</option>
14
- <option v-for="(info, type) in errorTypes" :key="type" :value="type">
15
- {{ info.label }}
16
- </option>
17
- </select>
18
- <button @click="loadData" class="refresh-btn">刷新</button>
19
- </div>
20
- </div>
21
-
22
- <!-- 统计卡片 -->
23
- <div class="stats-row">
24
- <div class="stat-card total">
25
- <div class="stat-value">{{ stats.totalCount }}</div>
26
- <div class="stat-label">总错误数</div>
27
- </div>
28
- <div class="stat-card session">
29
- <div class="stat-value">{{ stats.sessionErrorCount }}</div>
30
- <div class="stat-label">Session 错误</div>
31
- </div>
32
- <div class="stat-card model">
33
- <div class="stat-value">{{ stats.modelFailureCount }}</div>
34
- <div class="stat-label">Model 错误</div>
35
- </div>
36
- <div class="stat-card" :class="healthyApiCount === apiStatus.length ? 'healthy' : 'warning'">
37
- <div class="stat-value">{{ healthyApiCount }}/{{ apiStatus.length }}</div>
38
- <div class="stat-label">API 健康</div>
39
- </div>
40
- </div>
41
-
42
- <!-- 趋势图 -->
43
- <div class="trend-section" v-if="stats.hourlyTrend?.length">
44
- <h3>24 小时错误趋势</h3>
45
- <div class="trend-chart">
46
- <div
47
- v-for="(item, i) in stats.hourlyTrend"
48
- :key="i"
49
- class="trend-bar"
50
- :style="{ height: getBarHeight(item.count) + '%' }"
51
- :title="`${item.hour}: ${item.count} 个错误`"
52
- >
53
- <span v-if="item.count > 0" class="bar-label">{{ item.count }}</span>
54
- </div>
55
- </div>
56
- <div class="trend-labels">
57
- <span v-for="(item, i) in stats.hourlyTrend" :key="i" class="trend-time">
58
- {{ i % 4 === 0 ? formatHour(item.hour) : '' }}
59
- </span>
60
- </div>
61
- </div>
62
-
63
- <!-- 错误类型分布 -->
64
- <div class="type-distribution" v-if="Object.keys(stats.byType || {}).length">
65
- <h3>错误类型分布</h3>
66
- <div class="type-bars">
67
- <div
68
- v-for="(info, type) in stats.byType"
69
- :key="type"
70
- class="type-bar-item"
71
- >
72
- <div class="type-bar-label">
73
- <span class="type-dot" :style="{ background: info.color }"></span>
74
- {{ info.label }}
75
- </div>
76
- <div class="type-bar-track">
77
- <div
78
- class="type-bar-fill"
79
- :style="{
80
- width: getTypePercent(info.count) + '%',
81
- background: info.color
82
- }"
83
- ></div>
84
- </div>
85
- <span class="type-count">{{ info.count }}</span>
86
- </div>
87
- </div>
88
- </div>
89
-
90
- <!-- API 状态 -->
91
- <div class="api-status-section" v-if="apiStatus.length">
92
- <h3>API 状态</h3>
93
- <div class="api-status-grid">
94
- <div
95
- v-for="api in apiStatus"
96
- :key="api.model"
97
- class="api-status-card"
98
- :class="`status-${api.status}`"
99
- >
100
- <div class="api-header">
101
- <span class="api-model">{{ api.model }}</span>
102
- <span class="api-provider">{{ api.provider }}</span>
103
- </div>
104
- <div class="api-status">
105
- <span class="status-dot" :class="`status-${api.status}`"></span>
106
- <span class="status-text">{{ getStatusText(api.status) }}</span>
107
- </div>
108
- <div v-if="api.lastError" class="api-last-error">
109
- <span class="error-type">{{ api.lastError.type }}</span>
110
- <span class="error-time">{{ formatTime(api.lastError.timestamp) }}</span>
111
- </div>
112
- <div v-if="api.errorCount > 0" class="api-error-count">
113
- 错误: {{ api.errorCount }} 次
114
- </div>
115
- </div>
116
- </div>
117
- </div>
118
-
119
- <!-- 错误列表 -->
120
- <div class="error-lists">
121
- <!-- Session 错误 -->
122
- <div class="error-group">
123
- <h3>Session 错误 <span class="count-badge">{{ sessionErrors.length }}</span></h3>
124
- <div v-if="!sessionErrors.length" class="empty">暂无错误</div>
125
- <div v-else class="error-list">
126
- <div
127
- v-for="e in sessionErrors"
128
- :key="e.id"
129
- class="error-item"
130
- :class="`severity-${e.severity}`"
131
- @click="toggleDetail(e.id)"
132
- >
133
- <div class="error-main">
134
- <span class="error-agent">{{ getAgentName(e.agentId) }}</span>
135
- <span class="error-type" :style="{ color: getTypeColor(e.type) }">
136
- {{ e.typeLabel }}
137
- </span>
138
- <span class="error-msg">{{ truncate(e.message, 80) }}</span>
139
- <span class="error-time">{{ formatTime(e.timestamp) }}</span>
140
- <span class="expand-icon">{{ expandedId === e.id ? '▼' : '▶' }}</span>
141
- </div>
142
- <div v-if="expandedId === e.id" class="error-detail">
143
- <div class="detail-row">
144
- <span class="detail-label">时间:</span>
145
- <span>{{ e.datetime }}</span>
146
- </div>
147
- <div class="detail-row">
148
- <span class="detail-label">完整信息:</span>
149
- <span class="detail-message">{{ e.fullMessage || e.message }}</span>
150
- </div>
151
- </div>
152
- </div>
153
- </div>
154
- </div>
155
-
156
- <!-- Model Failures -->
157
- <div class="error-group">
158
- <h3>Model Failures <span class="count-badge">{{ modelFailures.length }}</span></h3>
159
- <div v-if="!modelFailures.length" class="empty">暂无错误</div>
160
- <div v-else class="error-list">
161
- <div
162
- v-for="f in modelFailures"
163
- :key="f.id"
164
- class="error-item"
165
- :class="`severity-${f.severity}`"
166
- @click="toggleDetail(f.id)"
167
- >
168
- <div class="error-main">
169
- <span class="error-agent">{{ f.model }}</span>
170
- <span class="error-type" :style="{ color: getTypeColor(f.type) }">
171
- {{ f.typeLabel }}
172
- </span>
173
- <span class="error-msg">{{ truncate(f.message, 80) }}</span>
174
- <span class="error-time">{{ formatTime(f.timestamp) }}</span>
175
- <span class="expand-icon">{{ expandedId === f.id ? '▼' : '▶' }}</span>
176
- </div>
177
- <div v-if="expandedId === f.id" class="error-detail">
178
- <div class="detail-row">
179
- <span class="detail-label">时间:</span>
180
- <span>{{ f.datetime }}</span>
181
- </div>
182
- <div class="detail-row">
183
- <span class="detail-label">完整信息:</span>
184
- <span class="detail-message">{{ f.fullMessage || f.message }}</span>
185
- </div>
186
- </div>
187
- </div>
188
- </div>
189
- </div>
190
- </div>
191
- </section>
192
- </template>
193
-
194
- <script setup lang="ts">
195
- import { ref, computed, onMounted, onUnmounted } from 'vue'
196
-
197
- interface SessionError {
198
- id: string
199
- source: string
200
- agentId: string
201
- type: string
202
- typeLabel: string
203
- severity: string
204
- message: string
205
- fullMessage: string
206
- timestamp: number
207
- datetime: string
208
- }
209
-
210
- interface ModelFailure {
211
- id: string
212
- source: string
213
- model: string
214
- type: string
215
- typeLabel: string
216
- severity: string
217
- message: string
218
- fullMessage: string
219
- timestamp: number
220
- datetime: string
221
- }
222
-
223
- interface ApiStatus {
224
- model: string
225
- provider: string
226
- status: string
227
- errorCount: number
228
- lastError?: { type: string; message: string; timestamp: number }
229
- }
230
-
231
- interface Stats {
232
- totalCount: number
233
- sessionErrorCount: number
234
- modelFailureCount: number
235
- byType: Record<string, { count: number; label: string; color: string }>
236
- byAgent: Record<string, { count: number; agentId: string }>
237
- hourlyTrend: Array<{ hour: string; count: number }>
238
- }
239
-
240
- const sessionErrors = ref<SessionError[]>([])
241
- const modelFailures = ref<ModelFailure[]>([])
242
- const apiStatus = ref<ApiStatus[]>([])
243
- const stats = ref<Stats>({
244
- totalCount: 0,
245
- sessionErrorCount: 0,
246
- modelFailureCount: 0,
247
- byType: {},
248
- byAgent: {},
249
- hourlyTrend: []
250
- })
251
-
252
- const loading = ref(false)
253
- const error = ref('')
254
- const expandedId = ref<string | null>(null)
255
- const selectedAgent = ref('')
256
- const selectedType = ref('')
257
- const agents = ref<Array<{ id: string; name: string }>>([])
258
-
259
- const errorTypes: Record<string, { label: string; color: string }> = {
260
- 'rate-limit': { label: 'Rate Limit', color: '#f59e0b' },
261
- 'token-limit': { label: 'Token 超限', color: '#8b5cf6' },
262
- 'timeout': { label: '超时', color: '#ef4444' },
263
- 'auth': { label: '认证失败', color: '#dc2626' },
264
- 'unknown': { label: '未知错误', color: '#6b7280' },
265
- }
266
-
267
- const healthyApiCount = computed(() => {
268
- return apiStatus.value.filter(a => a.status === 'healthy').length
269
- })
270
-
271
- const maxHourlyCount = computed(() => {
272
- return Math.max(...(stats.value.hourlyTrend?.map(h => h.count) || [1]), 1)
273
- })
274
-
275
- function getBarHeight(count: number): number {
276
- return (count / maxHourlyCount.value) * 100
277
- }
278
-
279
- function getTypePercent(count: number): number {
280
- const total = stats.value.totalCount || 1
281
- return (count / total) * 100
282
- }
283
-
284
- function getTypeColor(type: string): string {
285
- return errorTypes[type]?.color || '#6b7280'
286
- }
287
-
288
- function formatHour(hourStr: string): string {
289
- const match = hourStr.match(/(\d{2}):00/)
290
- return match ? match[1] + 'h' : ''
291
- }
292
-
293
- function formatTime(ts: number): string {
294
- if (!ts) return '-'
295
- const now = Date.now()
296
- const diff = now - ts
297
- const minutes = Math.floor(diff / 60000)
298
-
299
- if (minutes < 1) return '刚刚'
300
- if (minutes < 60) return `${minutes}分钟前`
301
- const hours = Math.floor(minutes / 60)
302
- if (hours < 24) return `${hours}小时前`
303
- return `${Math.floor(hours / 24)}天前`
304
- }
305
-
306
- function truncate(str: string, len: number): string {
307
- if (!str) return '-'
308
- return str.length > len ? str.slice(0, len) + '...' : str
309
- }
310
-
311
- function getStatusText(status: string): string {
312
- const map: Record<string, string> = {
313
- 'healthy': '正常',
314
- 'degraded': '降级',
315
- 'down': '异常'
316
- }
317
- return map[status] || '未知'
318
- }
319
-
320
- function getAgentName(agentId: string): string {
321
- const agent = agents.value.find(a => a.id === agentId)
322
- return agent ? agent.name : agentId
323
- }
324
-
325
- function toggleDetail(id: string): void {
326
- expandedId.value = expandedId.value === id ? null : id
327
- }
328
-
329
- async function loadAgents(): Promise<void> {
330
- try {
331
- const res = await fetch('/api/agents')
332
- if (res.ok) {
333
- const data = await res.json()
334
- agents.value = (Array.isArray(data) ? data : []).map((a: any) => ({
335
- id: a.id,
336
- name: a.name || a.id
337
- }))
338
- }
339
- } catch (e) {
340
- console.error('Failed to load agents:', e)
341
- }
342
- }
343
-
344
- async function loadData(): Promise<void> {
345
- loading.value = true
346
- error.value = ''
347
- try {
348
- const params = new URLSearchParams()
349
- if (selectedAgent.value) params.set('agent', selectedAgent.value)
350
- if (selectedType.value) params.set('type', selectedType.value)
351
-
352
- // 并行请求
353
- const [summaryRes, statsRes] = await Promise.all([
354
- fetch(`/api/errors/summary?${params.toString()}`),
355
- fetch('/api/errors/stats')
356
- ])
357
-
358
- if (summaryRes.ok) {
359
- const data = await summaryRes.json()
360
- sessionErrors.value = data.sessionErrors || []
361
- modelFailures.value = data.modelFailures || []
362
- apiStatus.value = data.apiStatus || []
363
- }
364
-
365
- if (statsRes.ok) {
366
- stats.value = await statsRes.json()
367
- }
368
- } catch (e) {
369
- error.value = String(e)
370
- } finally {
371
- loading.value = false
372
- }
373
- }
374
-
375
- onMounted(() => {
376
- loadAgents()
377
- loadData()
378
- })
379
- </script>
380
-
381
- <style scoped>
382
- .error-center {
383
- background: white;
384
- border-radius: 8px;
385
- padding: 1.5rem;
386
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
387
- }
388
-
389
- .header {
390
- display: flex;
391
- justify-content: space-between;
392
- align-items: center;
393
- margin-bottom: 1.5rem;
394
- flex-wrap: wrap;
395
- gap: 1rem;
396
- }
397
-
398
- .header h2 {
399
- margin: 0;
400
- font-size: 1.3rem;
401
- color: #333;
402
- }
403
-
404
- .header-actions {
405
- display: flex;
406
- gap: 0.75rem;
407
- align-items: center;
408
- }
409
-
410
- .filter-select {
411
- padding: 0.5rem 0.75rem;
412
- border: 1px solid #e5e7eb;
413
- border-radius: 6px;
414
- background: white;
415
- font-size: 0.9rem;
416
- min-width: 120px;
417
- }
418
-
419
- .refresh-btn {
420
- padding: 0.5rem 1rem;
421
- background: #4a9eff;
422
- color: white;
423
- border: none;
424
- border-radius: 6px;
425
- cursor: pointer;
426
- font-size: 0.9rem;
427
- }
428
-
429
- .refresh-btn:hover {
430
- background: #3a8eef;
431
- }
432
-
433
- /* 统计卡片 */
434
- .stats-row {
435
- display: grid;
436
- grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
437
- gap: 1rem;
438
- margin-bottom: 1.5rem;
439
- }
440
-
441
- .stat-card {
442
- background: #f8fafc;
443
- border-radius: 8px;
444
- padding: 1rem;
445
- text-align: center;
446
- border: 1px solid #e2e8f0;
447
- }
448
-
449
- .stat-card.total {
450
- background: #fef2f2;
451
- border-color: #fecaca;
452
- }
453
-
454
- .stat-card.session {
455
- background: #fef3c7;
456
- border-color: #fde68a;
457
- }
458
-
459
- .stat-card.model {
460
- background: #ede9fe;
461
- border-color: #ddd6fe;
462
- }
463
-
464
- .stat-card.healthy {
465
- background: #d1fae5;
466
- border-color: #a7f3d0;
467
- }
468
-
469
- .stat-card.warning {
470
- background: #fef3c7;
471
- border-color: #fde68a;
472
- }
473
-
474
- .stat-value {
475
- font-size: 1.8rem;
476
- font-weight: 700;
477
- color: #1e293b;
478
- }
479
-
480
- .stat-label {
481
- font-size: 0.85rem;
482
- color: #64748b;
483
- margin-top: 0.25rem;
484
- }
485
-
486
- /* 趋势图 */
487
- .trend-section {
488
- margin-bottom: 1.5rem;
489
- }
490
-
491
- .trend-section h3 {
492
- margin: 0 0 0.75rem 0;
493
- font-size: 1rem;
494
- color: #475569;
495
- }
496
-
497
- .trend-chart {
498
- display: flex;
499
- align-items: flex-end;
500
- gap: 2px;
501
- height: 80px;
502
- background: #f8fafc;
503
- border-radius: 6px;
504
- padding: 0.5rem;
505
- }
506
-
507
- .trend-bar {
508
- flex: 1;
509
- background: #cbd5e1;
510
- border-radius: 2px 2px 0 0;
511
- min-height: 4px;
512
- position: relative;
513
- transition: background 0.2s;
514
- }
515
-
516
- .trend-bar:hover {
517
- background: #ef4444;
518
- }
519
-
520
- .bar-label {
521
- position: absolute;
522
- top: -16px;
523
- left: 50%;
524
- transform: translateX(-50%);
525
- font-size: 0.7rem;
526
- color: #64748b;
527
- }
528
-
529
- .trend-labels {
530
- display: flex;
531
- gap: 2px;
532
- margin-top: 0.25rem;
533
- }
534
-
535
- .trend-time {
536
- flex: 1;
537
- text-align: center;
538
- font-size: 0.7rem;
539
- color: #94a3b8;
540
- }
541
-
542
- /* 错误类型分布 */
543
- .type-distribution {
544
- margin-bottom: 1.5rem;
545
- }
546
-
547
- .type-distribution h3 {
548
- margin: 0 0 0.75rem 0;
549
- font-size: 1rem;
550
- color: #475569;
551
- }
552
-
553
- .type-bars {
554
- display: flex;
555
- flex-direction: column;
556
- gap: 0.5rem;
557
- }
558
-
559
- .type-bar-item {
560
- display: flex;
561
- align-items: center;
562
- gap: 0.75rem;
563
- }
564
-
565
- .type-bar-label {
566
- display: flex;
567
- align-items: center;
568
- gap: 0.5rem;
569
- min-width: 100px;
570
- font-size: 0.85rem;
571
- color: #475569;
572
- }
573
-
574
- .type-dot {
575
- width: 8px;
576
- height: 8px;
577
- border-radius: 50%;
578
- }
579
-
580
- .type-bar-track {
581
- flex: 1;
582
- height: 8px;
583
- background: #e2e8f0;
584
- border-radius: 4px;
585
- overflow: hidden;
586
- }
587
-
588
- .type-bar-fill {
589
- height: 100%;
590
- border-radius: 4px;
591
- transition: width 0.3s;
592
- }
593
-
594
- .type-count {
595
- min-width: 40px;
596
- text-align: right;
597
- font-size: 0.85rem;
598
- color: #64748b;
599
- font-weight: 500;
600
- }
601
-
602
- /* API 状态 */
603
- .api-status-section {
604
- margin-bottom: 1.5rem;
605
- }
606
-
607
- .api-status-section h3 {
608
- margin: 0 0 0.75rem 0;
609
- font-size: 1rem;
610
- color: #475569;
611
- }
612
-
613
- .api-status-grid {
614
- display: grid;
615
- grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
616
- gap: 0.75rem;
617
- }
618
-
619
- .api-status-card {
620
- padding: 0.75rem;
621
- background: #f8fafc;
622
- border-radius: 6px;
623
- border: 1px solid #e2e8f0;
624
- }
625
-
626
- .api-status-card.status-healthy {
627
- background: #f0fdf4;
628
- border-color: #bbf7d0;
629
- }
630
-
631
- .api-status-card.status-degraded {
632
- background: #fffbeb;
633
- border-color: #fde68a;
634
- }
635
-
636
- .api-status-card.status-down {
637
- background: #fef2f2;
638
- border-color: #fecaca;
639
- }
640
-
641
- .api-header {
642
- display: flex;
643
- justify-content: space-between;
644
- margin-bottom: 0.5rem;
645
- }
646
-
647
- .api-model {
648
- font-weight: 600;
649
- font-size: 0.9rem;
650
- color: #1e293b;
651
- }
652
-
653
- .api-provider {
654
- font-size: 0.8rem;
655
- color: #94a3b8;
656
- }
657
-
658
- .api-status {
659
- display: flex;
660
- align-items: center;
661
- gap: 0.5rem;
662
- }
663
-
664
- .status-dot {
665
- width: 8px;
666
- height: 8px;
667
- border-radius: 50%;
668
- }
669
-
670
- .status-dot.status-healthy {
671
- background: #22c55e;
672
- }
673
-
674
- .status-dot.status-degraded {
675
- background: #f59e0b;
676
- }
677
-
678
- .status-dot.status-down {
679
- background: #ef4444;
680
- }
681
-
682
- .status-text {
683
- font-size: 0.85rem;
684
- color: #64748b;
685
- }
686
-
687
- .api-last-error {
688
- margin-top: 0.5rem;
689
- font-size: 0.8rem;
690
- color: #dc2626;
691
- display: flex;
692
- justify-content: space-between;
693
- }
694
-
695
- .api-error-count {
696
- margin-top: 0.25rem;
697
- font-size: 0.8rem;
698
- color: #ef4444;
699
- }
700
-
701
- /* 错误列表 */
702
- .error-lists {
703
- display: grid;
704
- grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
705
- gap: 1.5rem;
706
- }
707
-
708
- .error-group h3 {
709
- margin: 0 0 0.75rem 0;
710
- font-size: 1rem;
711
- color: #475569;
712
- display: flex;
713
- align-items: center;
714
- gap: 0.5rem;
715
- }
716
-
717
- .count-badge {
718
- background: #e2e8f0;
719
- color: #64748b;
720
- font-size: 0.75rem;
721
- padding: 0.125rem 0.5rem;
722
- border-radius: 10px;
723
- font-weight: 500;
724
- }
725
-
726
- .empty {
727
- color: #94a3b8;
728
- font-size: 0.9rem;
729
- padding: 1rem;
730
- text-align: center;
731
- background: #f8fafc;
732
- border-radius: 6px;
733
- }
734
-
735
- .error-list {
736
- display: flex;
737
- flex-direction: column;
738
- gap: 0.5rem;
739
- }
740
-
741
- .error-item {
742
- background: #fef2f2;
743
- border-radius: 6px;
744
- border-left: 4px solid #ef4444;
745
- cursor: pointer;
746
- transition: box-shadow 0.2s;
747
- }
748
-
749
- .error-item:hover {
750
- box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
751
- }
752
-
753
- .error-item.severity-warning {
754
- border-left-color: #f59e0b;
755
- background: #fffbeb;
756
- }
757
-
758
- .error-item.severity-critical {
759
- border-left-color: #dc2626;
760
- background: #fef2f2;
761
- }
762
-
763
- .error-main {
764
- display: grid;
765
- grid-template-columns: 80px 80px 1fr 70px 24px;
766
- gap: 0.5rem;
767
- align-items: center;
768
- padding: 0.75rem;
769
- font-size: 0.85rem;
770
- }
771
-
772
- .error-agent {
773
- font-weight: 500;
774
- color: #991b1b;
775
- }
776
-
777
- .error-type {
778
- font-weight: 500;
779
- }
780
-
781
- .error-msg {
782
- color: #7f1d1d;
783
- overflow: hidden;
784
- text-overflow: ellipsis;
785
- white-space: nowrap;
786
- }
787
-
788
- .error-time {
789
- color: #b91c1c;
790
- font-size: 0.8rem;
791
- text-align: right;
792
- }
793
-
794
- .expand-icon {
795
- color: #94a3b8;
796
- font-size: 0.7rem;
797
- text-align: center;
798
- }
799
-
800
- .error-detail {
801
- padding: 0.75rem;
802
- padding-top: 0;
803
- border-top: 1px solid #fecaca;
804
- margin: 0 0.75rem 0.75rem 0.75rem;
805
- }
806
-
807
- .detail-row {
808
- display: flex;
809
- gap: 0.5rem;
810
- margin-bottom: 0.5rem;
811
- font-size: 0.85rem;
812
- }
813
-
814
- .detail-label {
815
- color: #64748b;
816
- min-width: 70px;
817
- }
818
-
819
- .detail-message {
820
- color: #1e293b;
821
- word-break: break-word;
822
- }
823
-
824
- /* 响应式 */
825
- @media (max-width: 768px) {
826
- .header {
827
- flex-direction: column;
828
- align-items: flex-start;
829
- }
830
-
831
- .error-main {
832
- grid-template-columns: 1fr;
833
- gap: 0.25rem;
834
- }
835
-
836
- .error-lists {
837
- grid-template-columns: 1fr;
838
- }
839
-
840
- .api-status-grid {
841
- grid-template-columns: 1fr;
842
- }
843
- }
844
- </style>