openclaw-agent-dashboard 1.0.4

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 (111) hide show
  1. package/.github/workflows/release.yml +56 -0
  2. package/README.md +302 -0
  3. package/docs/CHANGELOG_AGENT_MODIFICATIONS.md +132 -0
  4. package/docs/RELEASE-LATEST.md +189 -0
  5. package/docs/RELEASE-MODEL-CONFIG.md +95 -0
  6. package/docs/release-guide.md +259 -0
  7. package/docs/release-operations-manual.md +167 -0
  8. package/docs/specs/tr3-install-system.md +580 -0
  9. package/docs/windows-collaboration-model-paths-troubleshooting.md +0 -0
  10. package/frontend/index.html +12 -0
  11. package/frontend/package-lock.json +1240 -0
  12. package/frontend/package.json +19 -0
  13. package/frontend/src/App.vue +331 -0
  14. package/frontend/src/components/AgentCard.vue +796 -0
  15. package/frontend/src/components/AgentConfigPanel.vue +539 -0
  16. package/frontend/src/components/AgentDetailPanel.vue +738 -0
  17. package/frontend/src/components/ErrorAnalysisView.vue +546 -0
  18. package/frontend/src/components/ErrorCenterPanel.vue +844 -0
  19. package/frontend/src/components/PerformanceMonitor.vue +515 -0
  20. package/frontend/src/components/SettingsPanel.vue +236 -0
  21. package/frontend/src/components/TokenAnalysisPanel.vue +683 -0
  22. package/frontend/src/components/chain/ChainEdge.vue +85 -0
  23. package/frontend/src/components/chain/ChainNode.vue +166 -0
  24. package/frontend/src/components/chain/TaskChainView.vue +425 -0
  25. package/frontend/src/components/chain/index.ts +3 -0
  26. package/frontend/src/components/chain/types.ts +70 -0
  27. package/frontend/src/components/collaboration/CollaborationFlowSection.vue +1032 -0
  28. package/frontend/src/components/collaboration/CollaborationFlowWrapper.vue +113 -0
  29. package/frontend/src/components/performance/PerformancePanel.vue +119 -0
  30. package/frontend/src/components/performance/PerformanceSection.vue +1137 -0
  31. package/frontend/src/components/tasks/TaskStatusSection.vue +973 -0
  32. package/frontend/src/components/timeline/TimelineConnector.vue +31 -0
  33. package/frontend/src/components/timeline/TimelineRound.vue +135 -0
  34. package/frontend/src/components/timeline/TimelineStep.vue +691 -0
  35. package/frontend/src/components/timeline/TimelineToolLink.vue +109 -0
  36. package/frontend/src/components/timeline/TimelineView.vue +540 -0
  37. package/frontend/src/components/timeline/index.ts +5 -0
  38. package/frontend/src/components/timeline/types.ts +120 -0
  39. package/frontend/src/composables/index.ts +7 -0
  40. package/frontend/src/composables/useDebounce.ts +48 -0
  41. package/frontend/src/composables/useRealtime.ts +52 -0
  42. package/frontend/src/composables/useState.ts +52 -0
  43. package/frontend/src/composables/useThrottle.ts +46 -0
  44. package/frontend/src/composables/useVirtualScroll.ts +106 -0
  45. package/frontend/src/main.ts +4 -0
  46. package/frontend/src/managers/EventDispatcher.ts +127 -0
  47. package/frontend/src/managers/RealtimeDataManager.ts +293 -0
  48. package/frontend/src/managers/StateManager.ts +128 -0
  49. package/frontend/src/managers/index.ts +5 -0
  50. package/frontend/src/types/collaboration.ts +135 -0
  51. package/frontend/src/types/index.ts +20 -0
  52. package/frontend/src/types/performance.ts +105 -0
  53. package/frontend/src/types/task.ts +38 -0
  54. package/frontend/vite.config.ts +18 -0
  55. package/package.json +22 -0
  56. package/plugin/README.md +99 -0
  57. package/plugin/config.json.example +1 -0
  58. package/plugin/index.js +250 -0
  59. package/plugin/openclaw.plugin.json +17 -0
  60. package/plugin/package.json +21 -0
  61. package/scripts/build-plugin.js +67 -0
  62. package/scripts/bundle.sh +62 -0
  63. package/scripts/install-plugin.sh +162 -0
  64. package/scripts/install-python-deps.js +346 -0
  65. package/scripts/install-python-deps.sh +226 -0
  66. package/scripts/install.js +512 -0
  67. package/scripts/install.sh +367 -0
  68. package/scripts/lib/common.js +490 -0
  69. package/scripts/lib/common.sh +137 -0
  70. package/scripts/release-pack.sh +110 -0
  71. package/scripts/start.js +50 -0
  72. package/scripts/test_available_models.py +284 -0
  73. package/scripts/test_websocket_ping.py +44 -0
  74. package/src/backend/agents.py +73 -0
  75. package/src/backend/api/__init__.py +1 -0
  76. package/src/backend/api/agent_config_api.py +90 -0
  77. package/src/backend/api/agents.py +73 -0
  78. package/src/backend/api/agents_config.py +75 -0
  79. package/src/backend/api/chains.py +126 -0
  80. package/src/backend/api/collaboration.py +902 -0
  81. package/src/backend/api/debug_paths.py +39 -0
  82. package/src/backend/api/error_analysis.py +146 -0
  83. package/src/backend/api/errors.py +281 -0
  84. package/src/backend/api/performance.py +784 -0
  85. package/src/backend/api/subagents.py +770 -0
  86. package/src/backend/api/timeline.py +144 -0
  87. package/src/backend/api/websocket.py +251 -0
  88. package/src/backend/collaboration.py +405 -0
  89. package/src/backend/data/__init__.py +1 -0
  90. package/src/backend/data/agent_config_manager.py +270 -0
  91. package/src/backend/data/chain_reader.py +299 -0
  92. package/src/backend/data/config_reader.py +153 -0
  93. package/src/backend/data/error_analyzer.py +430 -0
  94. package/src/backend/data/session_reader.py +445 -0
  95. package/src/backend/data/subagent_reader.py +244 -0
  96. package/src/backend/data/task_history.py +118 -0
  97. package/src/backend/data/timeline_reader.py +981 -0
  98. package/src/backend/errors.py +63 -0
  99. package/src/backend/main.py +89 -0
  100. package/src/backend/mechanism_reader.py +131 -0
  101. package/src/backend/mechanisms.py +32 -0
  102. package/src/backend/performance.py +474 -0
  103. package/src/backend/requirements.txt +5 -0
  104. package/src/backend/session_reader.py +238 -0
  105. package/src/backend/status/__init__.py +1 -0
  106. package/src/backend/status/error_detector.py +122 -0
  107. package/src/backend/status/status_calculator.py +301 -0
  108. package/src/backend/status_calculator.py +121 -0
  109. package/src/backend/subagent_reader.py +229 -0
  110. package/src/backend/watchers/__init__.py +4 -0
  111. package/src/backend/watchers/file_watcher.py +159 -0
@@ -0,0 +1,691 @@
1
+ <template>
2
+ <div
3
+ class="timeline-step"
4
+ :class="[
5
+ `step-${step.type}`,
6
+ `status-${step.status}`,
7
+ { 'is-paired-result': isPairedToolResult, 'highlighted': isHighlighted }
8
+ ]"
9
+ >
10
+ <!-- 步骤头部 -->
11
+ <div class="step-header" @click="handleClick">
12
+ <div class="header-left">
13
+ <span class="step-icon">{{ stepIcon }}</span>
14
+ <span class="step-type">{{ stepLabel }}</span>
15
+ <span class="step-subtitle" v-if="stepSubtitle">{{ stepSubtitle }}</span>
16
+ <span class="collapse-summary" v-if="!isExpanded && collapseSummary">{{ collapseSummary }}</span>
17
+ <span class="step-time">{{ formatTime(step.timestamp) }}</span>
18
+ <span class="step-duration" v-if="step.duration && step.duration > 0">
19
+ +{{ formatDuration(step.duration) }}
20
+ </span>
21
+ <!-- 工具执行时间 -->
22
+ <span class="execution-time" v-if="step.executionTime">
23
+ ⏱ {{ formatDuration(step.executionTime) }}
24
+ </span>
25
+ </div>
26
+ <div class="header-right">
27
+ <span class="step-tokens" v-if="step.tokens">
28
+ <span class="token-label">tokens:</span>
29
+ {{ step.tokens.input + step.tokens.output }}
30
+ </span>
31
+ <span class="pair-indicator" v-if="hasPair" :title="pairTitle">
32
+ 🔗
33
+ </span>
34
+ <span class="expand-icon" v-if="hasExpandableContent">
35
+ {{ isExpanded ? '▼' : '▶' }}
36
+ </span>
37
+ </div>
38
+ </div>
39
+
40
+ <!-- 步骤内容 -->
41
+ <div class="step-content" v-if="hasExpandableContent && isExpanded">
42
+ <!-- 用户消息 -->
43
+ <div class="content-block user-content" v-if="step.type === 'user' && step.content">
44
+ <pre>{{ step.content }}</pre>
45
+ </div>
46
+
47
+ <!-- 思考内容 -->
48
+ <div class="content-block thinking-content" v-if="step.type === 'thinking' && step.thinking">
49
+ <div class="thinking-label">💭 思考过程</div>
50
+ <pre>{{ step.thinking }}</pre>
51
+ </div>
52
+
53
+ <!-- 文本响应 -->
54
+ <div class="content-block text-content" v-if="step.type === 'text' && step.content">
55
+ <pre>{{ step.content }}</pre>
56
+ </div>
57
+
58
+ <!-- 工具调用 -->
59
+ <div class="content-block tool-call-content" v-if="step.type === 'toolCall'">
60
+ <div class="args-section" v-if="step.toolArguments">
61
+ <div class="section-label">参数:</div>
62
+ <pre class="code-block">{{ formatJson(step.toolArguments) }}</pre>
63
+ </div>
64
+ </div>
65
+
66
+ <!-- 工具结果 -->
67
+ <div class="content-block tool-result-content" v-if="step.type === 'toolResult'">
68
+ <div class="result-header">
69
+ <span class="result-status" :class="step.toolResultStatus">
70
+ {{ step.toolResultStatus === 'ok' ? '✅ 成功' : '❌ 工具执行失败' }}
71
+ </span>
72
+ <button class="copy-btn" @click="copyResult" v-if="step.toolResult">
73
+ 复制
74
+ </button>
75
+ </div>
76
+ <!-- 失败时优先展示错误信息与建议 -->
77
+ <template v-if="step.toolResultStatus === 'error'">
78
+ <div class="tool-error-section" v-if="toolResultErrorDisplay">
79
+ <div class="tool-error-message">{{ toolResultErrorDisplay }}</div>
80
+ <div class="tool-error-suggestion" v-if="toolResultErrorSuggestion">
81
+ <span class="suggestion-label">💡 建议:</span>
82
+ <ul>
83
+ <li v-for="(s, i) in toolResultErrorSuggestion" :key="i">{{ s }}</li>
84
+ </ul>
85
+ </div>
86
+ </div>
87
+ <div class="result-body" v-if="step.toolResult">
88
+ <div class="section-label">原始返回:</div>
89
+ <pre :class="{ truncated: !showFullResult }">{{ displayResult }}</pre>
90
+ <button
91
+ v-if="isResultLong"
92
+ class="show-more-btn"
93
+ @click.stop="showFullResult = !showFullResult"
94
+ >
95
+ {{ showFullResult ? '收起' : `展开全部 (${resultLineCount} 行)` }}
96
+ </button>
97
+ </div>
98
+ </template>
99
+ <template v-else>
100
+ <div class="result-body" v-if="step.toolResult">
101
+ <pre :class="{ truncated: !showFullResult }">{{ displayResult }}</pre>
102
+ <button
103
+ v-if="isResultLong"
104
+ class="show-more-btn"
105
+ @click.stop="showFullResult = !showFullResult"
106
+ >
107
+ {{ showFullResult ? '收起' : `展开全部 (${resultLineCount} 行)` }}
108
+ </button>
109
+ </div>
110
+ </template>
111
+ </div>
112
+
113
+ <!-- 错误信息 -->
114
+ <div class="content-block error-content" v-if="step.type === 'error'">
115
+ <div class="error-type">{{ step.errorType || 'unknown' }}</div>
116
+ <div class="error-message">{{ step.errorMessage }}</div>
117
+ <div class="error-suggestion" v-if="errorSuggestion">
118
+ <div class="suggestion-label">💡 建议:</div>
119
+ <ul>
120
+ <li v-for="(s, i) in errorSuggestion" :key="i">{{ s }}</li>
121
+ </ul>
122
+ </div>
123
+ </div>
124
+ </div>
125
+ </div>
126
+ </template>
127
+
128
+ <script setup lang="ts">
129
+ import { ref, computed } from 'vue'
130
+ import type { TimelineStep, StepType } from './types'
131
+ import { stepConfig, getUserStepLabel } from './types'
132
+
133
+ const props = defineProps<{
134
+ step: TimelineStep
135
+ prevStep?: TimelineStep
136
+ highlightedPair?: { callId: string; resultId: string } | null
137
+ }>()
138
+
139
+ const emit = defineEmits<{
140
+ 'toggle-collapse': []
141
+ 'highlight-pair': [pair: { callId: string; resultId: string }]
142
+ }>()
143
+
144
+ const isExpanded = ref(!props.step.collapsed)
145
+ const showFullResult = ref(false)
146
+
147
+ const stepIcon = computed(() => {
148
+ return stepConfig[props.step.type as StepType]?.icon || '📄'
149
+ })
150
+
151
+ const stepLabel = computed(() => {
152
+ // 对于 user 类型,如果有发送者信息,显示发送者名称
153
+ if (props.step.type === 'user') {
154
+ return getUserStepLabel(props.step)
155
+ }
156
+ // toolCall: "Read 调用" 等
157
+ if (props.step.type === 'toolCall') {
158
+ return `${props.step.toolName || '工具'} 调用`
159
+ }
160
+ // toolResult: "Read 结果" 或 "Read 失败"
161
+ if (props.step.type === 'toolResult') {
162
+ const name = props.step.toolName || '工具'
163
+ const suffix = props.step.toolResultStatus === 'ok' ? '结果' : '失败'
164
+ return `${name} ${suffix}`
165
+ }
166
+ return stepConfig[props.step.type as StepType]?.label || props.step.type
167
+ })
168
+
169
+ /** thinking/text 的副标题,标注为 LLM 输出 */
170
+ const stepSubtitle = computed(() => {
171
+ if (props.step.type === 'thinking') return 'LLM 推理'
172
+ if (props.step.type === 'text') return 'LLM 输出'
173
+ return ''
174
+ })
175
+
176
+ /** 是否有配对的工具调用/结果 */
177
+ const hasPair = computed(() => {
178
+ return !!(props.step.pairedToolCallId || props.step.pairedToolResultId)
179
+ })
180
+
181
+ /** 是否是配对的 toolResult(用于缩进显示) */
182
+ const isPairedToolResult = computed(() => {
183
+ return props.step.type === 'toolResult' && props.step.pairedToolCallId
184
+ })
185
+
186
+ /** 是否高亮 */
187
+ const isHighlighted = computed(() => {
188
+ if (!props.highlightedPair || !hasPair.value) return false
189
+ const { callId, resultId } = props.highlightedPair
190
+ return props.step.id === callId || props.step.id === resultId
191
+ })
192
+
193
+ /** 配对提示 */
194
+ const pairTitle = computed(() => {
195
+ if (props.step.type === 'toolCall' && props.step.pairedToolResultId) {
196
+ return '点击高亮对应结果'
197
+ }
198
+ if (props.step.type === 'toolResult' && props.step.pairedToolCallId) {
199
+ return '点击高亮对应调用'
200
+ }
201
+ return ''
202
+ })
203
+
204
+ const hasExpandableContent = computed(() => {
205
+ const { type, content, thinking, toolArguments, toolResult, errorMessage } = props.step
206
+ return content || thinking || toolArguments || toolResult || errorMessage
207
+ })
208
+
209
+ /** 折叠时在头部显示的摘要 */
210
+ const collapseSummary = computed(() => {
211
+ const s = props.step
212
+ if (s.type === 'toolCall' && s.toolArguments) {
213
+ const path = s.toolArguments.path ?? s.toolArguments.file_path ?? s.toolArguments.filePath
214
+ if (typeof path === 'string') return path
215
+ const pattern = s.toolArguments.pattern
216
+ if (typeof pattern === 'string') return pattern
217
+ const cmd = s.toolArguments.command
218
+ if (typeof cmd === 'string') return cmd.length > 40 ? cmd.slice(0, 40) + '…' : cmd
219
+ }
220
+ if (s.type === 'toolResult') {
221
+ if (s.toolResultStatus === 'error') {
222
+ const err = (s.toolResultError || s.toolResult || '').slice(0, 30)
223
+ return err ? `${err}${err.length >= 30 ? '…' : ''}` : '失败'
224
+ }
225
+ if (s.toolResult) {
226
+ const lines = s.toolResult.split('\n').length
227
+ return lines > 1 ? `${lines} 行` : ''
228
+ }
229
+ }
230
+ if (s.type === 'thinking' && s.thinking) {
231
+ const len = s.thinking.length
232
+ return len > 0 ? `约 ${len} 字` : ''
233
+ }
234
+ if (s.type === 'text' && s.content) {
235
+ const len = s.content.length
236
+ return len > 0 ? `约 ${len} 字` : ''
237
+ }
238
+ return ''
239
+ })
240
+
241
+ const isResultLong = computed(() => {
242
+ if (!props.step.toolResult) return false
243
+ return props.step.toolResult.length > 500 || props.step.toolResult.split('\n').length > 10
244
+ })
245
+
246
+ const resultLineCount = computed(() => {
247
+ if (!props.step.toolResult) return 0
248
+ return props.step.toolResult.split('\n').length
249
+ })
250
+
251
+ const displayResult = computed(() => {
252
+ if (!props.step.toolResult) return ''
253
+ if (showFullResult.value || !isResultLong.value) {
254
+ return props.step.toolResult
255
+ }
256
+ const lines = props.step.toolResult.split('\n')
257
+ return lines.slice(0, 10).join('\n') + '\n...'
258
+ })
259
+
260
+ /** 工具失败时的错误信息展示(优先用 toolResultError,否则从 toolResult 提取) */
261
+ const toolResultErrorDisplay = computed(() => {
262
+ if (props.step.type !== 'toolResult' || props.step.toolResultStatus !== 'error') return ''
263
+ if (props.step.toolResultError) return props.step.toolResultError
264
+ return props.step.toolResult || '工具执行失败'
265
+ })
266
+
267
+ /** 工具失败时的建议(根据错误内容推断) */
268
+ const toolResultErrorSuggestion = computed(() => {
269
+ if (props.step.type !== 'toolResult' || props.step.toolResultStatus !== 'error') return null
270
+ const text = (props.step.toolResultError || props.step.toolResult || '').toLowerCase()
271
+ const toolName = (props.step.toolName || '').toLowerCase()
272
+
273
+ const suggestions: string[] = []
274
+ if (text.includes('enoent') || text.includes('no such file') || text.includes('文件不存在')) {
275
+ suggestions.push('检查文件路径是否正确', '确认文件是否存在')
276
+ }
277
+ if (text.includes('eacces') || text.includes('permission') || text.includes('权限')) {
278
+ suggestions.push('检查文件/目录权限', '确认当前用户有访问权限')
279
+ }
280
+ if (text.includes('timeout') || text.includes('超时')) {
281
+ suggestions.push('增加超时时间', '简化任务或检查网络')
282
+ }
283
+ if (toolName === 'read' && suggestions.length === 0) {
284
+ suggestions.push('检查路径是否在 workspace 内', '确认文件编码正确')
285
+ }
286
+ if (toolName.includes('bash') && suggestions.length === 0) {
287
+ suggestions.push('检查命令语法', '确认依赖已安装', '查看退出码')
288
+ }
289
+ if (suggestions.length === 0) {
290
+ suggestions.push('查看原始返回详情', '尝试调整参数后重试')
291
+ }
292
+ return suggestions
293
+ })
294
+
295
+ const errorSuggestion = computed(() => {
296
+ if (props.step.type !== 'error' || !props.step.errorType) return null
297
+
298
+ const suggestions: Record<string, string[]> = {
299
+ 'rate-limit': [
300
+ '降低调用频率',
301
+ '切换到 fallback model',
302
+ '等待配额恢复'
303
+ ],
304
+ 'token-limit': [
305
+ '减少上下文长度',
306
+ '分段处理任务',
307
+ '使用更大 context 的模型'
308
+ ],
309
+ 'timeout': [
310
+ '检查网络连接',
311
+ '简化任务复杂度',
312
+ '增加超时时间'
313
+ ],
314
+ 'quota': [
315
+ '充值账户',
316
+ '切换到其他 provider',
317
+ '等待配额重置'
318
+ ]
319
+ }
320
+
321
+ return suggestions[props.step.errorType] || null
322
+ })
323
+
324
+ function handleClick() {
325
+ // 如果有配对关系,触发高亮事件
326
+ if (hasPair.value) {
327
+ const callId = props.step.pairedToolCallId || props.step.id
328
+ const resultId = props.step.pairedToolResultId || props.step.id
329
+ emit('highlight-pair', { callId, resultId })
330
+ }
331
+ toggleExpand()
332
+ }
333
+
334
+ function toggleExpand() {
335
+ if (hasExpandableContent.value) {
336
+ isExpanded.value = !isExpanded.value
337
+ }
338
+ }
339
+
340
+ function formatTime(ts: number): string {
341
+ if (!ts) return ''
342
+ const d = new Date(ts)
343
+ return d.toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit', second: '2-digit' })
344
+ }
345
+
346
+ function formatDuration(ms: number): string {
347
+ if (!ms) return '0ms'
348
+ if (ms < 1000) return `${ms}ms`
349
+ if (ms < 60000) return `${(ms / 1000).toFixed(1)}s`
350
+ return `${(ms / 60000).toFixed(1)}min`
351
+ }
352
+
353
+ function formatJson(obj: unknown): string {
354
+ try {
355
+ return JSON.stringify(obj, null, 2)
356
+ } catch {
357
+ return String(obj)
358
+ }
359
+ }
360
+
361
+ async function copyResult() {
362
+ if (!props.step.toolResult) return
363
+ try {
364
+ await navigator.clipboard.writeText(props.step.toolResult)
365
+ alert('已复制到剪贴板')
366
+ } catch {
367
+ // fallback
368
+ }
369
+ }
370
+ </script>
371
+
372
+ <style scoped>
373
+ .timeline-step {
374
+ border-radius: 6px;
375
+ overflow: hidden;
376
+ transition: all 0.2s;
377
+ }
378
+
379
+ .step-header {
380
+ display: flex;
381
+ justify-content: space-between;
382
+ align-items: center;
383
+ padding: 10px 12px;
384
+ cursor: pointer;
385
+ user-select: none;
386
+ }
387
+
388
+ .step-header:hover {
389
+ filter: brightness(0.98);
390
+ }
391
+
392
+ .header-left {
393
+ display: flex;
394
+ align-items: center;
395
+ gap: 8px;
396
+ flex: 1;
397
+ min-width: 0;
398
+ }
399
+
400
+ .step-icon {
401
+ font-size: 14px;
402
+ flex-shrink: 0;
403
+ }
404
+
405
+ .step-type {
406
+ font-size: 12px;
407
+ font-weight: 600;
408
+ color: #374151;
409
+ flex-shrink: 0;
410
+ }
411
+
412
+ .step-subtitle {
413
+ font-size: 10px;
414
+ color: #9ca3af;
415
+ background: rgba(0,0,0,0.04);
416
+ padding: 1px 6px;
417
+ border-radius: 3px;
418
+ flex-shrink: 0;
419
+ }
420
+
421
+ .collapse-summary {
422
+ font-size: 11px;
423
+ color: #9ca3af;
424
+ max-width: 180px;
425
+ overflow: hidden;
426
+ text-overflow: ellipsis;
427
+ white-space: nowrap;
428
+ }
429
+
430
+ .step-time {
431
+ font-size: 11px;
432
+ color: #9ca3af;
433
+ flex-shrink: 0;
434
+ }
435
+
436
+ .step-duration {
437
+ font-size: 11px;
438
+ color: #6b7280;
439
+ background: rgba(0,0,0,0.05);
440
+ padding: 2px 6px;
441
+ border-radius: 3px;
442
+ flex-shrink: 0;
443
+ }
444
+
445
+ .header-right {
446
+ display: flex;
447
+ align-items: center;
448
+ gap: 8px;
449
+ flex-shrink: 0;
450
+ }
451
+
452
+ .step-tokens {
453
+ font-size: 11px;
454
+ color: #9ca3af;
455
+ }
456
+
457
+ .token-label {
458
+ margin-right: 4px;
459
+ }
460
+
461
+ .expand-icon {
462
+ font-size: 10px;
463
+ color: #9ca3af;
464
+ }
465
+
466
+ .execution-time {
467
+ font-size: 10px;
468
+ color: #64748b;
469
+ background: #f1f5f9;
470
+ padding: 1px 6px;
471
+ border-radius: 3px;
472
+ }
473
+
474
+ .pair-indicator {
475
+ font-size: 10px;
476
+ cursor: pointer;
477
+ opacity: 0.6;
478
+ transition: opacity 0.2s;
479
+ }
480
+
481
+ .pair-indicator:hover {
482
+ opacity: 1;
483
+ }
484
+
485
+ /* 步骤类型样式 */
486
+ .step-user .step-header { background: #f0f9ff; border-left: 3px solid #3b82f6; }
487
+ .step-thinking .step-header { background: #fef3c7; border-left: 3px solid #f59e0b; }
488
+ .step-text .step-header { background: #f0fdf4; border-left: 3px solid #22c55e; }
489
+ .step-toolCall .step-header { background: #f5f3ff; border-left: 3px solid #8b5cf6; }
490
+ .step-toolResult .step-header { background: #ecfdf5; border-left: 3px solid #10b981; }
491
+ .step-error .step-header { background: #fef2f2; border-left: 3px solid #dc2626; }
492
+
493
+ .step-toolResult.status-error .step-header { border-left-color: #ef4444; }
494
+
495
+ /* 配对 toolResult 缩进 */
496
+ .is-paired-result {
497
+ margin-left: 20px;
498
+ border-left: 2px dashed #d1d5db !important;
499
+ border-radius: 0 6px 6px 0;
500
+ }
501
+
502
+ /* 高亮样式 */
503
+ .timeline-step.highlighted {
504
+ box-shadow: 0 0 0 2px #3b82f6;
505
+ transform: scale(1.01);
506
+ transition: all 0.2s ease;
507
+ }
508
+
509
+ .timeline-step.highlighted .step-header {
510
+ background: #eff6ff !important;
511
+ }
512
+
513
+ /* 内容区域 */
514
+ .step-content {
515
+ padding: 0 12px 12px 12px;
516
+ margin-top: -4px;
517
+ }
518
+
519
+ .content-block {
520
+ margin-top: 8px;
521
+ }
522
+
523
+ .content-block pre {
524
+ margin: 0;
525
+ padding: 10px;
526
+ background: rgba(0,0,0,0.03);
527
+ border-radius: 4px;
528
+ font-size: 12px;
529
+ line-height: 1.5;
530
+ white-space: pre-wrap;
531
+ word-break: break-word;
532
+ max-height: 300px;
533
+ overflow-y: auto;
534
+ }
535
+
536
+ .content-block pre.truncated {
537
+ max-height: 200px;
538
+ }
539
+
540
+ .thinking-label {
541
+ font-size: 11px;
542
+ color: #92400e;
543
+ margin-bottom: 6px;
544
+ }
545
+
546
+ .thinking-content pre {
547
+ background: #fffbeb;
548
+ color: #78350f;
549
+ }
550
+
551
+ .section-label {
552
+ font-size: 11px;
553
+ color: #6b7280;
554
+ margin-bottom: 4px;
555
+ }
556
+
557
+ .code-block {
558
+ font-family: 'Monaco', 'Menlo', monospace;
559
+ font-size: 11px;
560
+ }
561
+
562
+ .result-header {
563
+ display: flex;
564
+ justify-content: space-between;
565
+ align-items: center;
566
+ margin-bottom: 6px;
567
+ }
568
+
569
+ .result-status {
570
+ font-size: 12px;
571
+ font-weight: 500;
572
+ }
573
+
574
+ .result-status.ok { color: #059669; }
575
+ .result-status.error { color: #dc2626; }
576
+
577
+ .tool-error-section {
578
+ margin-bottom: 12px;
579
+ padding: 10px;
580
+ background: #fef2f2;
581
+ border-radius: 4px;
582
+ border-left: 3px solid #ef4444;
583
+ }
584
+
585
+ .tool-error-message {
586
+ font-size: 12px;
587
+ color: #991b1b;
588
+ line-height: 1.5;
589
+ word-break: break-word;
590
+ }
591
+
592
+ .tool-error-suggestion {
593
+ margin-top: 8px;
594
+ padding-top: 8px;
595
+ border-top: 1px solid #fecaca;
596
+ }
597
+
598
+ .tool-error-suggestion .suggestion-label {
599
+ font-size: 11px;
600
+ color: #374151;
601
+ margin-bottom: 4px;
602
+ }
603
+
604
+ .tool-error-suggestion ul {
605
+ margin: 0;
606
+ padding-left: 16px;
607
+ font-size: 12px;
608
+ color: #4b5563;
609
+ }
610
+
611
+ .tool-error-suggestion li {
612
+ margin: 2px 0;
613
+ }
614
+
615
+ .copy-btn {
616
+ font-size: 11px;
617
+ padding: 2px 8px;
618
+ border: 1px solid #e5e7eb;
619
+ border-radius: 3px;
620
+ background: #fff;
621
+ cursor: pointer;
622
+ }
623
+
624
+ .copy-btn:hover {
625
+ background: #f3f4f6;
626
+ }
627
+
628
+ .show-more-btn {
629
+ display: block;
630
+ width: 100%;
631
+ margin-top: 8px;
632
+ padding: 6px;
633
+ font-size: 11px;
634
+ color: #6b7280;
635
+ background: rgba(0,0,0,0.02);
636
+ border: none;
637
+ border-radius: 4px;
638
+ cursor: pointer;
639
+ }
640
+
641
+ .show-more-btn:hover {
642
+ background: rgba(0,0,0,0.05);
643
+ }
644
+
645
+ /* 错误样式 */
646
+ .error-content {
647
+ background: #fef2f2;
648
+ padding: 10px;
649
+ border-radius: 4px;
650
+ }
651
+
652
+ .error-type {
653
+ font-size: 12px;
654
+ font-weight: 600;
655
+ color: #dc2626;
656
+ margin-bottom: 6px;
657
+ }
658
+
659
+ .error-message {
660
+ font-size: 12px;
661
+ color: #7f1d1d;
662
+ padding: 8px;
663
+ background: #fee2e2;
664
+ border-radius: 4px;
665
+ margin-bottom: 8px;
666
+ }
667
+
668
+ .error-suggestion {
669
+ margin-top: 8px;
670
+ padding: 8px;
671
+ background: #fff;
672
+ border-radius: 4px;
673
+ }
674
+
675
+ .suggestion-label {
676
+ font-size: 11px;
677
+ color: #374151;
678
+ margin-bottom: 4px;
679
+ }
680
+
681
+ .error-suggestion ul {
682
+ margin: 0;
683
+ padding-left: 16px;
684
+ font-size: 12px;
685
+ color: #4b5563;
686
+ }
687
+
688
+ .error-suggestion li {
689
+ margin: 4px 0;
690
+ }
691
+ </style>