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,683 +0,0 @@
1
- <template>
2
- <section class="token-analysis">
3
- <div class="section-header">
4
- <h2>Token 分析</h2>
5
- <div class="time-range-selector">
6
- <button
7
- v-for="range in timeRanges"
8
- :key="range.value"
9
- :class="['range-btn', { active: selectedRange === range.value }]"
10
- @click="selectedRange = range.value"
11
- >
12
- {{ range.label }}
13
- </button>
14
- </div>
15
- </div>
16
-
17
- <div v-if="loading" class="loading-state">
18
- <div class="spinner"></div>
19
- <span>加载 Token 数据...</span>
20
- </div>
21
-
22
- <div v-else-if="error" class="error-state">
23
- <span class="error-icon">⚠️</span>
24
- <span>{{ error }}</span>
25
- <button @click="load" class="retry-btn">重试</button>
26
- </div>
27
-
28
- <template v-else>
29
- <!-- Summary Cards -->
30
- <div class="summary-cards">
31
- <div class="summary-card">
32
- <div class="card-icon">📥</div>
33
- <div class="card-content">
34
- <div class="card-label">Input</div>
35
- <div class="card-value">{{ formatNum(data.summary?.input) }}</div>
36
- </div>
37
- </div>
38
- <div class="summary-card">
39
- <div class="card-icon">📤</div>
40
- <div class="card-content">
41
- <div class="card-label">Output</div>
42
- <div class="card-value">{{ formatNum(data.summary?.output) }}</div>
43
- </div>
44
- </div>
45
- <div class="summary-card highlight">
46
- <div class="card-icon">💾</div>
47
- <div class="card-content">
48
- <div class="card-label">Cache Read</div>
49
- <div class="card-value">{{ formatNum(data.summary?.cacheRead) }}</div>
50
- <div class="card-sub">命中率 {{ formatPercent(data.summary?.cacheHitRate) }}</div>
51
- </div>
52
- </div>
53
- <div class="summary-card">
54
- <div class="card-icon">📝</div>
55
- <div class="card-content">
56
- <div class="card-label">Cache Write</div>
57
- <div class="card-value">{{ formatNum(data.summary?.cacheWrite) }}</div>
58
- </div>
59
- </div>
60
- <div class="summary-card cost">
61
- <div class="card-icon">💰</div>
62
- <div class="card-content">
63
- <div class="card-label">估算成本</div>
64
- <div class="card-value">${{ formatCost(data.cost?.total) }}</div>
65
- <div v-if="data.cost?.saved > 0" class="card-sub saved">节省 ${{ formatCost(data.cost?.saved) }}</div>
66
- </div>
67
- </div>
68
- </div>
69
-
70
- <!-- Trend Chart (for 20m/1h/24h range) -->
71
- <div v-if="data.trend && showTrend" class="trend-section">
72
- <h3>Token 消耗趋势</h3>
73
- <div class="trend-chart">
74
- <div class="trend-bars">
75
- <div
76
- v-for="(timestamp, i) in data.trend.timestamps"
77
- :key="i"
78
- class="trend-bar-group"
79
- >
80
- <div class="trend-bar input" :style="{ height: getBarHeight(data.trend.input[i], maxTrendValue) + '%' }">
81
- <span v-if="data.trend.input[i] > 0" class="bar-tooltip">In: {{ formatNum(data.trend.input[i]) }}</span>
82
- </div>
83
- <div class="trend-bar output" :style="{ height: getBarHeight(data.trend.output[i], maxTrendValue) + '%' }">
84
- <span v-if="data.trend.output[i] > 0" class="bar-tooltip">Out: {{ formatNum(data.trend.output[i]) }}</span>
85
- </div>
86
- <span class="trend-time">{{ formatTrendTime(timestamp) }}</span>
87
- </div>
88
- </div>
89
- <div class="trend-legend">
90
- <span class="legend-item"><span class="legend-color input"></span> Input</span>
91
- <span class="legend-item"><span class="legend-color output"></span> Output</span>
92
- </div>
93
- </div>
94
- </div>
95
-
96
- <!-- View Toggle -->
97
- <div class="view-toggle">
98
- <button :class="{ active: viewMode === 'table' }" @click="viewMode = 'table'">📊 表格</button>
99
- <button :class="{ active: viewMode === 'chart' }" @click="viewMode = 'chart'">📈 图表</button>
100
- </div>
101
-
102
- <!-- Table View -->
103
- <div v-if="viewMode === 'table'" class="by-agent-table">
104
- <table>
105
- <thead>
106
- <tr>
107
- <th>Agent</th>
108
- <th>Input</th>
109
- <th>Output</th>
110
- <th>Cache</th>
111
- <th>总计</th>
112
- <th>占比</th>
113
- </tr>
114
- </thead>
115
- <tbody>
116
- <tr v-for="agent in sortedAgents" :key="agent.agent">
117
- <td class="agent-name">{{ agent.agent }}</td>
118
- <td>{{ formatNum(agent.input) }}</td>
119
- <td>{{ formatNum(agent.output) }}</td>
120
- <td>{{ formatNum(agent.cacheRead + agent.cacheWrite) }}</td>
121
- <td class="total-col">{{ formatNum(agent.total) }}</td>
122
- <td>
123
- <div class="percent-bar">
124
- <div class="percent-fill" :style="{ width: (agent.percent * 100) + '%' }"></div>
125
- <span class="percent-text">{{ formatPercent(agent.percent) }}</span>
126
- </div>
127
- </td>
128
- </tr>
129
- </tbody>
130
- <tfoot>
131
- <tr>
132
- <td><strong>合计</strong></td>
133
- <td>{{ formatNum(data.summary?.input) }}</td>
134
- <td>{{ formatNum(data.summary?.output) }}</td>
135
- <td>{{ formatNum((data.summary?.cacheRead || 0) + (data.summary?.cacheWrite || 0)) }}</td>
136
- <td class="total-col"><strong>{{ formatNum(data.summary?.total) }}</strong></td>
137
- <td>100%</td>
138
- </tr>
139
- </tfoot>
140
- </table>
141
- </div>
142
-
143
- <!-- Chart View -->
144
- <div v-else class="by-agent-chart">
145
- <h3>Token 消耗分布</h3>
146
- <div class="bar-chart">
147
- <div v-for="agent in sortedAgents" :key="agent.agent" class="bar-row">
148
- <div class="bar-label">{{ agent.agent }}</div>
149
- <div class="bar-container">
150
- <div class="bar" :style="{ width: (agent.percent * 100) + '%' }">
151
- <span class="bar-value">{{ formatNum(agent.total) }}</span>
152
- </div>
153
- </div>
154
- <div class="bar-percent">{{ formatPercent(agent.percent) }}</div>
155
- </div>
156
- </div>
157
- </div>
158
- </template>
159
- </section>
160
- </template>
161
-
162
- <script setup lang="ts">
163
- import { ref, computed, onMounted, watch } from 'vue'
164
- import type { TokenAnalysisData, AgentTokenData } from '../types/performance'
165
-
166
- const data = ref<TokenAnalysisData>({
167
- summary: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0, cacheHitRate: 0 },
168
- cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0, saved: 0, savedPercent: 0 },
169
- byAgent: [],
170
- trend: null
171
- })
172
- const loading = ref(false)
173
- const error = ref('')
174
- const viewMode = ref<'table' | 'chart'>('table')
175
- const selectedRange = ref<string>('all')
176
-
177
- const timeRanges = [
178
- { value: 'all', label: '全部' },
179
- { value: '24h', label: '24小时' },
180
- { value: '1h', label: '1小时' },
181
- { value: '20m', label: '20分钟' }
182
- ]
183
-
184
- const showTrend = computed(() => selectedRange.value !== 'all' && data.value.trend)
185
-
186
- const maxTrendValue = computed(() => {
187
- if (!data.value.trend) return 1
188
- const allValues = [...data.value.trend.input, ...data.value.trend.output]
189
- return Math.max(...allValues, 1)
190
- })
191
-
192
- const sortedAgents = computed(() => {
193
- return [...(data.value.byAgent || [])].sort((a, b) => b.total - a.total)
194
- })
195
-
196
- function getBarHeight(value: number, max: number): number {
197
- return Math.max((value / max) * 100, 2)
198
- }
199
-
200
- function formatTrendTime(timestamp: number): string {
201
- const date = new Date(timestamp)
202
- if (selectedRange.value === '24h') {
203
- return date.toLocaleString('zh-CN', { hour: '2-digit', hour12: false }) + ':00'
204
- }
205
- return date.toLocaleString('zh-CN', { hour: '2-digit', minute: '2-digit', hour12: false })
206
- }
207
-
208
- function formatNum(n: number | undefined) {
209
- if (n == null) return '0'
210
- if (n >= 1000000) return (n / 1000000).toFixed(2) + 'M'
211
- if (n >= 1000) return (n / 1000).toFixed(1) + 'K'
212
- return n.toLocaleString()
213
- }
214
-
215
- function formatPercent(n: number | undefined) {
216
- if (n == null) return '0%'
217
- return (n * 100).toFixed(1) + '%'
218
- }
219
-
220
- function formatCost(n: number | undefined) {
221
- if (n == null) return '0.00'
222
- if (n >= 1) return n.toFixed(2)
223
- return n.toFixed(4)
224
- }
225
-
226
- async function load() {
227
- loading.value = true
228
- error.value = ''
229
- try {
230
- const res = await fetch(`/api/tokens/analysis?range=${selectedRange.value}`)
231
- if (res.ok) {
232
- data.value = await res.json()
233
- } else {
234
- error.value = '加载失败'
235
- }
236
- } catch (e) {
237
- error.value = String(e)
238
- } finally {
239
- loading.value = false
240
- }
241
- }
242
-
243
- onMounted(load)
244
-
245
- watch(selectedRange, load)
246
- </script>
247
-
248
- <style scoped>
249
- .token-analysis {
250
- background: white;
251
- border-radius: 8px;
252
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
253
- padding: 1.5rem;
254
- }
255
-
256
- .section-header {
257
- display: flex;
258
- justify-content: space-between;
259
- align-items: center;
260
- margin-bottom: 1.5rem;
261
- flex-wrap: wrap;
262
- gap: 1rem;
263
- }
264
-
265
- .section-header h2 {
266
- margin: 0;
267
- font-size: 1.3rem;
268
- color: #333;
269
- }
270
-
271
- .time-range-selector {
272
- display: flex;
273
- gap: 0.5rem;
274
- }
275
-
276
- .range-btn {
277
- padding: 0.5rem 1rem;
278
- border: 1px solid #e5e7eb;
279
- border-radius: 6px;
280
- background: white;
281
- font-size: 0.85rem;
282
- cursor: pointer;
283
- transition: all 0.2s;
284
- }
285
-
286
- .range-btn:hover {
287
- border-color: #4a9eff;
288
- }
289
-
290
- .range-btn.active {
291
- background: #4a9eff;
292
- color: white;
293
- border-color: #4a9eff;
294
- }
295
-
296
- .loading-state,
297
- .error-state {
298
- display: flex;
299
- flex-direction: column;
300
- align-items: center;
301
- justify-content: center;
302
- height: 200px;
303
- gap: 1rem;
304
- color: #6b7280;
305
- }
306
-
307
- .spinner {
308
- width: 32px;
309
- height: 32px;
310
- border: 3px solid #e5e7eb;
311
- border-top-color: #4a9eff;
312
- border-radius: 50%;
313
- animation: spin 1s linear infinite;
314
- }
315
-
316
- @keyframes spin {
317
- to { transform: rotate(360deg); }
318
- }
319
-
320
- .retry-btn {
321
- padding: 0.5rem 1rem;
322
- background: #4a9eff;
323
- color: white;
324
- border: none;
325
- border-radius: 4px;
326
- cursor: pointer;
327
- }
328
-
329
- .error-icon {
330
- font-size: 2rem;
331
- }
332
-
333
- /* Summary Cards */
334
- .summary-cards {
335
- display: grid;
336
- grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
337
- gap: 1rem;
338
- margin-bottom: 1.5rem;
339
- }
340
-
341
- .summary-card {
342
- display: flex;
343
- align-items: center;
344
- gap: 0.75rem;
345
- padding: 1rem;
346
- background: #f9fafb;
347
- border-radius: 8px;
348
- border: 1px solid #e5e7eb;
349
- }
350
-
351
- .summary-card.highlight {
352
- background: #f0fdf4;
353
- border-color: #86efac;
354
- }
355
-
356
- .summary-card.cost {
357
- background: linear-gradient(135deg, #fefce8 0%, #fef9c3 100%);
358
- border-color: #fde047;
359
- }
360
-
361
- .card-icon {
362
- font-size: 1.5rem;
363
- }
364
-
365
- .card-content {
366
- flex: 1;
367
- }
368
-
369
- .card-label {
370
- font-size: 0.8rem;
371
- color: #6b7280;
372
- margin-bottom: 0.25rem;
373
- }
374
-
375
- .card-value {
376
- font-size: 1.25rem;
377
- font-weight: 600;
378
- color: #333;
379
- }
380
-
381
- .card-sub {
382
- font-size: 0.75rem;
383
- color: #6b7280;
384
- margin-top: 0.25rem;
385
- }
386
-
387
- .card-sub.saved {
388
- color: #16a34a;
389
- font-weight: 500;
390
- }
391
-
392
- /* Trend Section */
393
- .trend-section {
394
- margin-bottom: 1.5rem;
395
- padding: 1rem;
396
- background: #f9fafb;
397
- border-radius: 8px;
398
- border: 1px solid #e5e7eb;
399
- }
400
-
401
- .trend-section h3 {
402
- margin: 0 0 1rem 0;
403
- font-size: 1rem;
404
- color: #666;
405
- }
406
-
407
- .trend-chart {
408
- position: relative;
409
- }
410
-
411
- .trend-bars {
412
- display: flex;
413
- align-items: flex-end;
414
- gap: 2px;
415
- height: 120px;
416
- padding-bottom: 30px;
417
- }
418
-
419
- .trend-bar-group {
420
- flex: 1;
421
- display: flex;
422
- align-items: flex-end;
423
- gap: 1px;
424
- min-width: 20px;
425
- position: relative;
426
- }
427
-
428
- .trend-bar {
429
- flex: 1;
430
- min-width: 8px;
431
- border-radius: 2px 2px 0 0;
432
- min-height: 2px;
433
- position: relative;
434
- transition: height 0.3s ease;
435
- }
436
-
437
- .trend-bar.input {
438
- background: linear-gradient(to top, #4a9eff, #6bb9ff);
439
- }
440
-
441
- .trend-bar.output {
442
- background: linear-gradient(to top, #f59e0b, #fbbf24);
443
- }
444
-
445
- .bar-tooltip {
446
- position: absolute;
447
- bottom: 100%;
448
- left: 50%;
449
- transform: translateX(-50%);
450
- background: #333;
451
- color: white;
452
- padding: 2px 6px;
453
- border-radius: 4px;
454
- font-size: 0.7rem;
455
- white-space: nowrap;
456
- opacity: 0;
457
- transition: opacity 0.2s;
458
- pointer-events: none;
459
- }
460
-
461
- .trend-bar:hover .bar-tooltip {
462
- opacity: 1;
463
- }
464
-
465
- .trend-time {
466
- position: absolute;
467
- bottom: -25px;
468
- left: 50%;
469
- transform: translateX(-50%);
470
- font-size: 0.65rem;
471
- color: #6b7280;
472
- white-space: nowrap;
473
- }
474
-
475
- .trend-legend {
476
- display: flex;
477
- justify-content: center;
478
- gap: 1.5rem;
479
- margin-top: 0.5rem;
480
- }
481
-
482
- .legend-item {
483
- display: flex;
484
- align-items: center;
485
- gap: 0.5rem;
486
- font-size: 0.8rem;
487
- color: #6b7280;
488
- }
489
-
490
- .legend-color {
491
- width: 12px;
492
- height: 12px;
493
- border-radius: 2px;
494
- }
495
-
496
- .legend-color.input {
497
- background: #4a9eff;
498
- }
499
-
500
- .legend-color.output {
501
- background: #f59e0b;
502
- }
503
-
504
- /* View Toggle */
505
- .view-toggle {
506
- display: flex;
507
- gap: 0.5rem;
508
- margin-bottom: 1rem;
509
- }
510
-
511
- .view-toggle button {
512
- padding: 0.5rem 1rem;
513
- border: 1px solid #e5e7eb;
514
- border-radius: 6px;
515
- background: white;
516
- font-size: 0.85rem;
517
- cursor: pointer;
518
- transition: all 0.2s;
519
- }
520
-
521
- .view-toggle button:hover {
522
- border-color: #4a9eff;
523
- }
524
-
525
- .view-toggle button.active {
526
- background: #4a9eff;
527
- color: white;
528
- border-color: #4a9eff;
529
- }
530
-
531
- /* Table View */
532
- .by-agent-table {
533
- overflow-x: auto;
534
- }
535
-
536
- .by-agent-table table {
537
- width: 100%;
538
- border-collapse: collapse;
539
- font-size: 0.9rem;
540
- }
541
-
542
- .by-agent-table th,
543
- .by-agent-table td {
544
- padding: 0.75rem;
545
- text-align: left;
546
- border-bottom: 1px solid #e5e7eb;
547
- }
548
-
549
- .by-agent-table th {
550
- background: #f9fafb;
551
- font-weight: 600;
552
- color: #6b7280;
553
- font-size: 0.8rem;
554
- text-transform: uppercase;
555
- }
556
-
557
- .by-agent-table tfoot td {
558
- background: #f9fafb;
559
- font-weight: 500;
560
- }
561
-
562
- .agent-name {
563
- font-weight: 500;
564
- color: #333;
565
- }
566
-
567
- .total-col {
568
- font-weight: 600;
569
- color: #4a9eff;
570
- }
571
-
572
- .percent-bar {
573
- display: flex;
574
- align-items: center;
575
- gap: 0.5rem;
576
- }
577
-
578
- .percent-fill {
579
- height: 8px;
580
- background: #4a9eff;
581
- border-radius: 4px;
582
- min-width: 4px;
583
- }
584
-
585
- .percent-text {
586
- font-size: 0.8rem;
587
- color: #6b7280;
588
- white-space: nowrap;
589
- }
590
-
591
- /* Chart View */
592
- .by-agent-chart h3 {
593
- margin: 0 0 1rem 0;
594
- font-size: 1rem;
595
- color: #666;
596
- }
597
-
598
- .bar-chart {
599
- display: flex;
600
- flex-direction: column;
601
- gap: 0.75rem;
602
- }
603
-
604
- .bar-row {
605
- display: flex;
606
- align-items: center;
607
- gap: 1rem;
608
- }
609
-
610
- .bar-label {
611
- width: 120px;
612
- font-size: 0.85rem;
613
- font-weight: 500;
614
- color: #333;
615
- flex-shrink: 0;
616
- }
617
-
618
- .bar-container {
619
- flex: 1;
620
- height: 24px;
621
- background: #f3f4f6;
622
- border-radius: 4px;
623
- overflow: hidden;
624
- }
625
-
626
- .bar {
627
- height: 100%;
628
- background: linear-gradient(90deg, #4a9eff, #6bb9ff);
629
- border-radius: 4px;
630
- display: flex;
631
- align-items: center;
632
- justify-content: flex-end;
633
- padding-right: 0.5rem;
634
- min-width: fit-content;
635
- }
636
-
637
- .bar-value {
638
- font-size: 0.75rem;
639
- color: white;
640
- font-weight: 500;
641
- white-space: nowrap;
642
- }
643
-
644
- .bar-percent {
645
- width: 50px;
646
- font-size: 0.8rem;
647
- color: #6b7280;
648
- text-align: right;
649
- }
650
-
651
- /* Responsive */
652
- @media (max-width: 640px) {
653
- .section-header {
654
- flex-direction: column;
655
- align-items: flex-start;
656
- }
657
-
658
- .time-range-selector {
659
- width: 100%;
660
- justify-content: space-between;
661
- }
662
-
663
- .range-btn {
664
- flex: 1;
665
- text-align: center;
666
- padding: 0.5rem;
667
- font-size: 0.75rem;
668
- }
669
-
670
- .summary-cards {
671
- grid-template-columns: 1fr 1fr;
672
- }
673
-
674
- .bar-label {
675
- width: 80px;
676
- font-size: 0.75rem;
677
- }
678
-
679
- .bar-percent {
680
- width: 40px;
681
- }
682
- }
683
- </style>