@roj-ai/debug 0.0.2
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.
- package/dist/components/debug/DebugContext.d.ts +10 -0
- package/dist/components/debug/DebugNavigation.d.ts +29 -0
- package/dist/components/debug/DebugShell.d.ts +18 -0
- package/dist/components/debug/LLMCallDetail.d.ts +7 -0
- package/dist/components/debug/TimelineDetailInspector.d.ts +6 -0
- package/dist/components/debug/communication/CommunicationDiagram.d.ts +9 -0
- package/dist/components/debug/communication/DiagramHeader.d.ts +7 -0
- package/dist/components/debug/communication/ParticipantLane.d.ts +7 -0
- package/dist/components/debug/communication/TimeAxis.d.ts +9 -0
- package/dist/components/debug/communication/elements/IdleGap.d.ts +9 -0
- package/dist/components/debug/communication/elements/LLMBlock.d.ts +9 -0
- package/dist/components/debug/communication/elements/MessageArrow.d.ts +10 -0
- package/dist/components/debug/communication/elements/ToolBlock.d.ts +9 -0
- package/dist/components/debug/communication/hooks/useDiagramData.d.ts +12 -0
- package/dist/components/debug/communication/hooks/useTimeCompression.d.ts +7 -0
- package/dist/components/debug/communication/hooks/useZoomPan.d.ts +11 -0
- package/dist/components/debug/communication/popovers/ElementPopover.d.ts +8 -0
- package/dist/components/debug/communication/types.d.ts +136 -0
- package/dist/components/debug/index.d.ts +11 -0
- package/dist/components/debug/pages/AgentDetailPage.d.ts +3 -0
- package/dist/components/debug/pages/AgentsPage.d.ts +1 -0
- package/dist/components/debug/pages/CommunicationPage.d.ts +1 -0
- package/dist/components/debug/pages/DashboardPage.d.ts +1 -0
- package/dist/components/debug/pages/EventsPage.d.ts +1 -0
- package/dist/components/debug/pages/FilesPage.d.ts +1 -0
- package/dist/components/debug/pages/LLMCallPage.d.ts +1 -0
- package/dist/components/debug/pages/LLMCallsPage.d.ts +1 -0
- package/dist/components/debug/pages/LogsPage.d.ts +1 -0
- package/dist/components/debug/pages/MailboxPage.d.ts +1 -0
- package/dist/components/debug/pages/ServicesPage.d.ts +1 -0
- package/dist/components/debug/pages/TimelinePage.d.ts +1 -0
- package/dist/components/debug/pages/UserChatPage.d.ts +1 -0
- package/dist/components/debug/pages/index.d.ts +13 -0
- package/dist/index.d.ts +9 -0
- package/dist/lib/domain-utils.d.ts +7 -0
- package/dist/providers/EventPollingProvider.d.ts +27 -0
- package/dist/stores/event-store.d.ts +93 -0
- package/dist/utils/format.d.ts +1 -0
- package/package.json +43 -0
- package/src/components/debug/DebugContext.tsx +18 -0
- package/src/components/debug/DebugNavigation.tsx +55 -0
- package/src/components/debug/DebugShell.tsx +321 -0
- package/src/components/debug/LLMCallDetail.tsx +740 -0
- package/src/components/debug/TimelineDetailInspector.tsx +204 -0
- package/src/components/debug/communication/CommunicationDiagram.tsx +260 -0
- package/src/components/debug/communication/DiagramHeader.tsx +113 -0
- package/src/components/debug/communication/ParticipantLane.tsx +60 -0
- package/src/components/debug/communication/TimeAxis.tsx +106 -0
- package/src/components/debug/communication/elements/IdleGap.tsx +90 -0
- package/src/components/debug/communication/elements/LLMBlock.tsx +107 -0
- package/src/components/debug/communication/elements/MessageArrow.tsx +119 -0
- package/src/components/debug/communication/elements/ToolBlock.tsx +99 -0
- package/src/components/debug/communication/hooks/useDiagramData.ts +294 -0
- package/src/components/debug/communication/hooks/useTimeCompression.ts +140 -0
- package/src/components/debug/communication/hooks/useZoomPan.ts +87 -0
- package/src/components/debug/communication/popovers/ElementPopover.tsx +158 -0
- package/src/components/debug/communication/types.ts +180 -0
- package/src/components/debug/index.ts +37 -0
- package/src/components/debug/pages/AgentDetailPage.tsx +1295 -0
- package/src/components/debug/pages/AgentsPage.tsx +297 -0
- package/src/components/debug/pages/CommunicationPage.tsx +89 -0
- package/src/components/debug/pages/DashboardPage.tsx +1504 -0
- package/src/components/debug/pages/EventsPage.tsx +276 -0
- package/src/components/debug/pages/FilesPage.tsx +366 -0
- package/src/components/debug/pages/LLMCallPage.tsx +32 -0
- package/src/components/debug/pages/LLMCallsPage.tsx +473 -0
- package/src/components/debug/pages/LogsPage.tsx +199 -0
- package/src/components/debug/pages/MailboxPage.tsx +232 -0
- package/src/components/debug/pages/ServicesPage.tsx +193 -0
- package/src/components/debug/pages/TimelinePage.tsx +569 -0
- package/src/components/debug/pages/UserChatPage.tsx +250 -0
- package/src/components/debug/pages/index.ts +13 -0
- package/src/index.ts +55 -0
- package/src/lib/domain-utils.ts +12 -0
- package/src/providers/EventPollingProvider.tsx +60 -0
- package/src/stores/event-store.ts +497 -0
- package/src/utils/format.ts +8 -0
|
@@ -0,0 +1,569 @@
|
|
|
1
|
+
import type { TimelineItem } from '@roj-ai/shared'
|
|
2
|
+
import { useState } from 'react'
|
|
3
|
+
import { DebugLink, useDebugSessionId } from '../DebugNavigation'
|
|
4
|
+
import { useEventStore, useTimeline } from '../../../stores/event-store'
|
|
5
|
+
import { LLMCallDetail } from '../LLMCallDetail'
|
|
6
|
+
|
|
7
|
+
export function TimelinePage() {
|
|
8
|
+
const sessionId = useDebugSessionId()
|
|
9
|
+
|
|
10
|
+
// Get timeline from event store (already loaded by DebugLayout)
|
|
11
|
+
const items = useTimeline()
|
|
12
|
+
const isLoading = useEventStore((s) => s.isLoading)
|
|
13
|
+
const error = useEventStore((s) => s.error)
|
|
14
|
+
|
|
15
|
+
const [selectedId, setSelectedId] = useState<string | null>(null)
|
|
16
|
+
|
|
17
|
+
const selectedItem = selectedId ? items.find((i) => i.id === selectedId) : null
|
|
18
|
+
const total = items.length
|
|
19
|
+
|
|
20
|
+
// Calculate stats
|
|
21
|
+
const llmCount = items.filter((i) => i.type === 'llm').length
|
|
22
|
+
const toolCount = items.filter((i) => i.type === 'tool').length
|
|
23
|
+
const compactionCount = items.filter((i) => i.type === 'compaction').length
|
|
24
|
+
const totalCost = items.reduce((sum, i) => sum + (i.cost ?? 0), 0)
|
|
25
|
+
const totalTokens = items.reduce(
|
|
26
|
+
(sum, i) => sum + (i.promptTokens ?? 0) + (i.completionTokens ?? 0),
|
|
27
|
+
0,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
return (
|
|
31
|
+
<div className="h-full flex flex-col gap-4">
|
|
32
|
+
{/* Summary */}
|
|
33
|
+
<div className="flex items-center gap-6 text-sm shrink-0">
|
|
34
|
+
<span className="text-slate-600">
|
|
35
|
+
<span className="font-medium text-slate-900">{total}</span> items
|
|
36
|
+
</span>
|
|
37
|
+
<span className="flex items-center gap-1">
|
|
38
|
+
<span className="w-2 h-2 rounded-full bg-violet-500" />
|
|
39
|
+
<span className="text-slate-600">{llmCount} LLM</span>
|
|
40
|
+
</span>
|
|
41
|
+
<span className="flex items-center gap-1">
|
|
42
|
+
<span className="w-2 h-2 rounded-full bg-emerald-500" />
|
|
43
|
+
<span className="text-slate-600">{toolCount} Tools</span>
|
|
44
|
+
</span>
|
|
45
|
+
<span className="flex items-center gap-1">
|
|
46
|
+
<span className="w-2 h-2 rounded-full bg-amber-500" />
|
|
47
|
+
<span className="text-slate-600">{compactionCount} Compactions</span>
|
|
48
|
+
</span>
|
|
49
|
+
<span className="text-slate-600">
|
|
50
|
+
<span className="font-medium text-slate-900">{totalTokens.toLocaleString()}</span> tokens
|
|
51
|
+
</span>
|
|
52
|
+
{totalCost > 0 && (
|
|
53
|
+
<span className="text-slate-600">
|
|
54
|
+
<span className="font-medium text-green-600">${totalCost.toFixed(4)}</span> cost
|
|
55
|
+
</span>
|
|
56
|
+
)}
|
|
57
|
+
</div>
|
|
58
|
+
|
|
59
|
+
{/* Error */}
|
|
60
|
+
{error && <div className="text-red-500 text-sm shrink-0">{error}</div>}
|
|
61
|
+
|
|
62
|
+
{/* Loading */}
|
|
63
|
+
{isLoading && items.length === 0 && <div className="text-slate-500 text-sm">Loading timeline...</div>}
|
|
64
|
+
|
|
65
|
+
{/* Two-column layout */}
|
|
66
|
+
{items.length > 0 && (
|
|
67
|
+
<div className="flex-1 flex gap-4 min-h-0">
|
|
68
|
+
{/* List */}
|
|
69
|
+
<div className="w-96 shrink-0 bg-white rounded-md border border-slate-200 flex flex-col">
|
|
70
|
+
<div className="p-3 border-b border-slate-200 shrink-0">
|
|
71
|
+
<h3 className="font-medium text-slate-900">Event Timeline</h3>
|
|
72
|
+
</div>
|
|
73
|
+
<div className="flex-1 overflow-auto p-3">
|
|
74
|
+
<TimelineList
|
|
75
|
+
items={items}
|
|
76
|
+
selectedId={selectedId}
|
|
77
|
+
onSelect={setSelectedId}
|
|
78
|
+
/>
|
|
79
|
+
</div>
|
|
80
|
+
</div>
|
|
81
|
+
|
|
82
|
+
{/* Detail */}
|
|
83
|
+
<div className="flex-1 bg-white rounded-md border border-slate-200 flex flex-col min-w-0">
|
|
84
|
+
<div className="p-3 border-b border-slate-200 shrink-0">
|
|
85
|
+
<h3 className="font-medium text-slate-900">Detail Inspector</h3>
|
|
86
|
+
</div>
|
|
87
|
+
<div className="flex-1 overflow-auto p-4">
|
|
88
|
+
{selectedItem ? <TimelineDetail sessionId={sessionId} item={selectedItem} /> : (
|
|
89
|
+
<div className="text-slate-500 text-sm">
|
|
90
|
+
Select an item from the timeline to view details
|
|
91
|
+
</div>
|
|
92
|
+
)}
|
|
93
|
+
</div>
|
|
94
|
+
</div>
|
|
95
|
+
</div>
|
|
96
|
+
)}
|
|
97
|
+
|
|
98
|
+
{/* Empty state */}
|
|
99
|
+
{!isLoading && items.length === 0 && <div className="text-slate-500 text-sm">No timeline items found</div>}
|
|
100
|
+
</div>
|
|
101
|
+
)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function TimelineList({
|
|
105
|
+
items,
|
|
106
|
+
selectedId,
|
|
107
|
+
onSelect,
|
|
108
|
+
}: {
|
|
109
|
+
items: TimelineItem[]
|
|
110
|
+
selectedId: string | null
|
|
111
|
+
onSelect: (id: string) => void
|
|
112
|
+
}) {
|
|
113
|
+
return (
|
|
114
|
+
<div className="relative">
|
|
115
|
+
{/* Vertical line */}
|
|
116
|
+
<div className="absolute left-3 top-0 bottom-0 w-0.5 bg-slate-200" />
|
|
117
|
+
|
|
118
|
+
<div className="space-y-2">
|
|
119
|
+
{items.map((item) => (
|
|
120
|
+
<TimelineItemRow
|
|
121
|
+
key={item.id}
|
|
122
|
+
item={item}
|
|
123
|
+
isSelected={selectedId === item.id}
|
|
124
|
+
onClick={() => onSelect(item.id)}
|
|
125
|
+
/>
|
|
126
|
+
))}
|
|
127
|
+
</div>
|
|
128
|
+
</div>
|
|
129
|
+
)
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function TimelineItemRow({
|
|
133
|
+
item,
|
|
134
|
+
isSelected,
|
|
135
|
+
onClick,
|
|
136
|
+
}: {
|
|
137
|
+
item: TimelineItem
|
|
138
|
+
isSelected: boolean
|
|
139
|
+
onClick: () => void
|
|
140
|
+
}) {
|
|
141
|
+
const typeConfig = getTypeConfig(item.type)
|
|
142
|
+
|
|
143
|
+
return (
|
|
144
|
+
<div
|
|
145
|
+
onClick={onClick}
|
|
146
|
+
className={`relative pl-8 py-2 pr-3 rounded-md cursor-pointer transition-colors ${
|
|
147
|
+
isSelected
|
|
148
|
+
? 'bg-violet-50 border border-violet-200'
|
|
149
|
+
: 'hover:bg-slate-50 border border-transparent'
|
|
150
|
+
}`}
|
|
151
|
+
>
|
|
152
|
+
{/* Icon */}
|
|
153
|
+
<div
|
|
154
|
+
className={`absolute left-1 top-3 w-5 h-5 rounded-full flex items-center justify-center ${
|
|
155
|
+
item.status === 'running'
|
|
156
|
+
? `${typeConfig.bgLight} animate-pulse`
|
|
157
|
+
: item.status === 'error'
|
|
158
|
+
? 'bg-red-100'
|
|
159
|
+
: typeConfig.bgLight
|
|
160
|
+
}`}
|
|
161
|
+
>
|
|
162
|
+
{item.status === 'running'
|
|
163
|
+
? <span className="w-2 h-2 rounded-full bg-blue-500" />
|
|
164
|
+
: item.status === 'error'
|
|
165
|
+
? <span className="text-red-500 text-xs">!</span>
|
|
166
|
+
: <typeConfig.Icon />}
|
|
167
|
+
</div>
|
|
168
|
+
|
|
169
|
+
{/* Content */}
|
|
170
|
+
<div className="min-w-0">
|
|
171
|
+
{/* Header */}
|
|
172
|
+
<div className="flex items-center gap-2">
|
|
173
|
+
<span className="font-mono text-xs text-slate-500">
|
|
174
|
+
{new Date(item.startedAt).toLocaleTimeString()}
|
|
175
|
+
</span>
|
|
176
|
+
<TypeBadge type={item.type} />
|
|
177
|
+
{item.status === 'running' && <span className="text-xs text-violet-600 font-medium">Running...</span>}
|
|
178
|
+
{item.status === 'error' && <span className="text-xs text-red-600 font-medium">Error</span>}
|
|
179
|
+
</div>
|
|
180
|
+
|
|
181
|
+
{/* Name/Model */}
|
|
182
|
+
<div className="mt-1 font-medium text-sm truncate">
|
|
183
|
+
{item.type === 'llm' && (item.model?.split('/').pop() ?? 'LLM Call')}
|
|
184
|
+
{item.type === 'tool' && (item.toolName ?? 'Tool')}
|
|
185
|
+
{item.type === 'compaction' && 'Context Compaction'}
|
|
186
|
+
</div>
|
|
187
|
+
|
|
188
|
+
{/* Agent */}
|
|
189
|
+
<div className="text-xs text-slate-500">
|
|
190
|
+
<DebugLink
|
|
191
|
+
to={`agents/${item.agentId}`}
|
|
192
|
+
className="hover:text-violet-600 hover:underline"
|
|
193
|
+
>
|
|
194
|
+
{item.agentName}
|
|
195
|
+
</DebugLink>
|
|
196
|
+
</div>
|
|
197
|
+
|
|
198
|
+
{/* Metrics */}
|
|
199
|
+
<div className="mt-1 flex items-center gap-3 text-xs text-slate-500">
|
|
200
|
+
{item.durationMs !== undefined && <span>{(item.durationMs / 1000).toFixed(2)}s</span>}
|
|
201
|
+
{item.type === 'llm' && item.promptTokens !== undefined && (
|
|
202
|
+
<span>
|
|
203
|
+
<span className="text-green-600">{item.promptTokens}</span>
|
|
204
|
+
<span className="text-slate-400">/</span>
|
|
205
|
+
<span className="text-violet-600">{item.completionTokens ?? 0}</span>
|
|
206
|
+
</span>
|
|
207
|
+
)}
|
|
208
|
+
{item.type === 'llm' && item.cost !== undefined && <span className="text-green-600">${item.cost.toFixed(5)}</span>}
|
|
209
|
+
</div>
|
|
210
|
+
</div>
|
|
211
|
+
</div>
|
|
212
|
+
)
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function TimelineDetail({
|
|
216
|
+
sessionId,
|
|
217
|
+
item,
|
|
218
|
+
}: {
|
|
219
|
+
sessionId: string
|
|
220
|
+
item: TimelineItem
|
|
221
|
+
}) {
|
|
222
|
+
// For LLM items with llmCallId, embed the full LLMCallDetail
|
|
223
|
+
if (item.type === 'llm' && item.llmCallId) {
|
|
224
|
+
return (
|
|
225
|
+
<div className="space-y-4">
|
|
226
|
+
{/* Header with link */}
|
|
227
|
+
<div className="flex items-center justify-between">
|
|
228
|
+
<div className="flex items-center gap-3">
|
|
229
|
+
<TypeBadge type={item.type} />
|
|
230
|
+
<span className="font-medium">{item.model?.split('/').pop() ?? 'LLM Call'}</span>
|
|
231
|
+
{item.status === 'error' && <span className="text-xs bg-red-100 text-red-700 px-2 py-0.5 rounded-full font-medium">Error</span>}
|
|
232
|
+
{item.status === 'running' && (
|
|
233
|
+
<span className="text-xs bg-blue-100 text-blue-700 px-2 py-0.5 rounded-full font-medium animate-pulse">Running</span>
|
|
234
|
+
)}
|
|
235
|
+
</div>
|
|
236
|
+
<DebugLink
|
|
237
|
+
to={`llm-calls/${item.llmCallId}`}
|
|
238
|
+
className="text-sm text-violet-600 hover:underline"
|
|
239
|
+
>
|
|
240
|
+
Open full page →
|
|
241
|
+
</DebugLink>
|
|
242
|
+
</div>
|
|
243
|
+
|
|
244
|
+
{/* Embedded LLM Call Detail */}
|
|
245
|
+
<LLMCallDetail sessionId={sessionId} callId={item.llmCallId} />
|
|
246
|
+
</div>
|
|
247
|
+
)
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// For LLM items without llmCallId, show what we have from the timeline item
|
|
251
|
+
if (item.type === 'llm') {
|
|
252
|
+
return (
|
|
253
|
+
<div className="space-y-4">
|
|
254
|
+
{/* Header */}
|
|
255
|
+
<div className="flex items-center gap-3">
|
|
256
|
+
<TypeBadge type={item.type} />
|
|
257
|
+
<span className="font-medium">{item.model?.split('/').pop() ?? 'LLM Call'}</span>
|
|
258
|
+
{item.status === 'error' && <span className="text-xs bg-red-100 text-red-700 px-2 py-0.5 rounded-full font-medium">Error</span>}
|
|
259
|
+
{item.status === 'running' && (
|
|
260
|
+
<span className="text-xs bg-blue-100 text-blue-700 px-2 py-0.5 rounded-full font-medium animate-pulse">Running</span>
|
|
261
|
+
)}
|
|
262
|
+
</div>
|
|
263
|
+
|
|
264
|
+
{/* LLM Metrics */}
|
|
265
|
+
<div className="grid grid-cols-2 md:grid-cols-4 gap-3 text-sm">
|
|
266
|
+
<MetricCard label="Agent">
|
|
267
|
+
<DebugLink
|
|
268
|
+
to={`agents/${item.agentId}`}
|
|
269
|
+
className="text-violet-600 hover:underline"
|
|
270
|
+
>
|
|
271
|
+
{item.agentName}
|
|
272
|
+
</DebugLink>
|
|
273
|
+
</MetricCard>
|
|
274
|
+
{item.model && (
|
|
275
|
+
<MetricCard label="Model">
|
|
276
|
+
<span className="font-mono text-xs">{item.model}</span>
|
|
277
|
+
</MetricCard>
|
|
278
|
+
)}
|
|
279
|
+
{item.durationMs !== undefined && <MetricCard label="Duration">{(item.durationMs / 1000).toFixed(2)}s</MetricCard>}
|
|
280
|
+
{item.cost !== undefined && (
|
|
281
|
+
<MetricCard label="Cost">
|
|
282
|
+
<span className="text-green-600">${item.cost.toFixed(6)}</span>
|
|
283
|
+
</MetricCard>
|
|
284
|
+
)}
|
|
285
|
+
</div>
|
|
286
|
+
|
|
287
|
+
{/* Token breakdown */}
|
|
288
|
+
{(item.promptTokens !== undefined || item.completionTokens !== undefined) && (
|
|
289
|
+
<div className="bg-violet-50 border border-violet-200 rounded-md p-4">
|
|
290
|
+
<h3 className="font-medium text-violet-800 mb-3">Token Usage</h3>
|
|
291
|
+
<div className="grid grid-cols-3 gap-4 text-sm">
|
|
292
|
+
<div>
|
|
293
|
+
<div className="text-xs text-violet-600 mb-1">Prompt</div>
|
|
294
|
+
<div className="font-mono font-medium text-green-600">
|
|
295
|
+
{(item.promptTokens ?? 0).toLocaleString()}
|
|
296
|
+
</div>
|
|
297
|
+
</div>
|
|
298
|
+
<div>
|
|
299
|
+
<div className="text-xs text-violet-600 mb-1">Completion</div>
|
|
300
|
+
<div className="font-mono font-medium text-violet-600">
|
|
301
|
+
{(item.completionTokens ?? 0).toLocaleString()}
|
|
302
|
+
</div>
|
|
303
|
+
</div>
|
|
304
|
+
<div>
|
|
305
|
+
<div className="text-xs text-violet-600 mb-1">Total</div>
|
|
306
|
+
<div className="font-mono font-medium">
|
|
307
|
+
{((item.promptTokens ?? 0) + (item.completionTokens ?? 0)).toLocaleString()}
|
|
308
|
+
</div>
|
|
309
|
+
</div>
|
|
310
|
+
</div>
|
|
311
|
+
</div>
|
|
312
|
+
)}
|
|
313
|
+
|
|
314
|
+
{/* Timestamps */}
|
|
315
|
+
<div className="text-xs text-slate-500 space-y-1">
|
|
316
|
+
<div>Started: {new Date(item.startedAt).toLocaleString()}</div>
|
|
317
|
+
{item.completedAt && <div>Completed: {new Date(item.completedAt).toLocaleString()}</div>}
|
|
318
|
+
</div>
|
|
319
|
+
|
|
320
|
+
{/* Error */}
|
|
321
|
+
{item.error && (
|
|
322
|
+
<div className="bg-red-50 border border-red-200 rounded-md p-4">
|
|
323
|
+
<h3 className="font-semibold text-red-800 mb-2">Error</h3>
|
|
324
|
+
<div className="text-red-700 font-mono text-sm whitespace-pre-wrap">{item.error}</div>
|
|
325
|
+
</div>
|
|
326
|
+
)}
|
|
327
|
+
|
|
328
|
+
{/* Note about missing detailed log */}
|
|
329
|
+
<div className="text-xs text-slate-400 italic">
|
|
330
|
+
Detailed request/response log not available for this call.
|
|
331
|
+
</div>
|
|
332
|
+
</div>
|
|
333
|
+
)
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// Tool detail
|
|
337
|
+
if (item.type === 'tool') {
|
|
338
|
+
return (
|
|
339
|
+
<div className="space-y-4">
|
|
340
|
+
{/* Header */}
|
|
341
|
+
<div className="flex items-center gap-3">
|
|
342
|
+
<TypeBadge type={item.type} />
|
|
343
|
+
<span className="font-medium font-mono">{item.toolName ?? 'Tool'}</span>
|
|
344
|
+
{item.status === 'error' && <span className="text-xs bg-red-100 text-red-700 px-2 py-0.5 rounded-full font-medium">Error</span>}
|
|
345
|
+
{item.status === 'running' && (
|
|
346
|
+
<span className="text-xs bg-blue-100 text-blue-700 px-2 py-0.5 rounded-full font-medium animate-pulse">Running</span>
|
|
347
|
+
)}
|
|
348
|
+
</div>
|
|
349
|
+
|
|
350
|
+
{/* Metrics */}
|
|
351
|
+
<div className="grid grid-cols-2 md:grid-cols-4 gap-3 text-sm">
|
|
352
|
+
<MetricCard label="Agent">
|
|
353
|
+
<DebugLink
|
|
354
|
+
to={`agents/${item.agentId}`}
|
|
355
|
+
className="text-violet-600 hover:underline"
|
|
356
|
+
>
|
|
357
|
+
{item.agentName}
|
|
358
|
+
</DebugLink>
|
|
359
|
+
</MetricCard>
|
|
360
|
+
{item.durationMs !== undefined && <MetricCard label="Duration">{(item.durationMs / 1000).toFixed(2)}s</MetricCard>}
|
|
361
|
+
<MetricCard label="Started">
|
|
362
|
+
{new Date(item.startedAt).toLocaleTimeString()}
|
|
363
|
+
</MetricCard>
|
|
364
|
+
{item.completedAt && (
|
|
365
|
+
<MetricCard label="Completed">
|
|
366
|
+
{new Date(item.completedAt).toLocaleTimeString()}
|
|
367
|
+
</MetricCard>
|
|
368
|
+
)}
|
|
369
|
+
</div>
|
|
370
|
+
|
|
371
|
+
{/* Input */}
|
|
372
|
+
{item.toolInput !== undefined && (
|
|
373
|
+
<div className="border border-slate-200 rounded-md overflow-hidden">
|
|
374
|
+
<div className="px-4 py-2 bg-slate-50 border-b border-slate-200">
|
|
375
|
+
<h3 className="text-sm font-medium text-slate-700">Input</h3>
|
|
376
|
+
</div>
|
|
377
|
+
<div className="p-4 max-h-64 overflow-auto">
|
|
378
|
+
<pre className="font-mono text-xs whitespace-pre-wrap break-words">
|
|
379
|
+
{JSON.stringify(item.toolInput, null, 2)}
|
|
380
|
+
</pre>
|
|
381
|
+
</div>
|
|
382
|
+
</div>
|
|
383
|
+
)}
|
|
384
|
+
|
|
385
|
+
{/* Result */}
|
|
386
|
+
{item.toolResult !== undefined && (
|
|
387
|
+
<div className="border border-emerald-200 rounded-md overflow-hidden">
|
|
388
|
+
<div className="px-4 py-2 bg-emerald-50 border-b border-emerald-200">
|
|
389
|
+
<h3 className="text-sm font-medium text-emerald-700">Result</h3>
|
|
390
|
+
</div>
|
|
391
|
+
<div className="p-4 max-h-64 overflow-auto">
|
|
392
|
+
<pre className="font-mono text-xs whitespace-pre-wrap break-words">
|
|
393
|
+
{JSON.stringify(item.toolResult, null, 2)}
|
|
394
|
+
</pre>
|
|
395
|
+
</div>
|
|
396
|
+
</div>
|
|
397
|
+
)}
|
|
398
|
+
|
|
399
|
+
{/* Error */}
|
|
400
|
+
{item.error && (
|
|
401
|
+
<div className="bg-red-50 border border-red-200 rounded-md p-4">
|
|
402
|
+
<h3 className="font-semibold text-red-800 mb-2">Error</h3>
|
|
403
|
+
<div className="text-red-700 font-mono text-sm whitespace-pre-wrap">{item.error}</div>
|
|
404
|
+
</div>
|
|
405
|
+
)}
|
|
406
|
+
</div>
|
|
407
|
+
)
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
// Compaction detail
|
|
411
|
+
if (item.type === 'compaction') {
|
|
412
|
+
return (
|
|
413
|
+
<div className="space-y-4">
|
|
414
|
+
{/* Header */}
|
|
415
|
+
<div className="flex items-center gap-3">
|
|
416
|
+
<TypeBadge type={item.type} />
|
|
417
|
+
<span className="font-medium">Context Compaction</span>
|
|
418
|
+
</div>
|
|
419
|
+
|
|
420
|
+
{/* Metrics */}
|
|
421
|
+
<div className="grid grid-cols-2 md:grid-cols-4 gap-3 text-sm">
|
|
422
|
+
<MetricCard label="Agent">
|
|
423
|
+
<DebugLink
|
|
424
|
+
to={`agents/${item.agentId}`}
|
|
425
|
+
className="text-violet-600 hover:underline"
|
|
426
|
+
>
|
|
427
|
+
{item.agentName}
|
|
428
|
+
</DebugLink>
|
|
429
|
+
</MetricCard>
|
|
430
|
+
{item.originalTokens !== undefined && (
|
|
431
|
+
<MetricCard label="Original Tokens">
|
|
432
|
+
{item.originalTokens.toLocaleString()}
|
|
433
|
+
</MetricCard>
|
|
434
|
+
)}
|
|
435
|
+
{item.compactedTokens !== undefined && (
|
|
436
|
+
<MetricCard label="Compacted Tokens">
|
|
437
|
+
{item.compactedTokens.toLocaleString()}
|
|
438
|
+
</MetricCard>
|
|
439
|
+
)}
|
|
440
|
+
{item.messagesRemoved !== undefined && (
|
|
441
|
+
<MetricCard label="Messages Removed">
|
|
442
|
+
{item.messagesRemoved}
|
|
443
|
+
</MetricCard>
|
|
444
|
+
)}
|
|
445
|
+
</div>
|
|
446
|
+
|
|
447
|
+
{/* Reduction visualization */}
|
|
448
|
+
{item.originalTokens !== undefined && item.compactedTokens !== undefined && (
|
|
449
|
+
<div className="bg-amber-50 border border-amber-200 rounded-md p-4">
|
|
450
|
+
<h3 className="font-medium text-amber-800 mb-3">Compression</h3>
|
|
451
|
+
<div className="flex items-center gap-4">
|
|
452
|
+
<div className="text-2xl font-bold text-amber-600">
|
|
453
|
+
{Math.round((1 - item.compactedTokens / item.originalTokens) * 100)}%
|
|
454
|
+
</div>
|
|
455
|
+
<div className="text-sm text-amber-700">
|
|
456
|
+
reduced ({item.originalTokens.toLocaleString()} → {item.compactedTokens.toLocaleString()} tokens)
|
|
457
|
+
</div>
|
|
458
|
+
</div>
|
|
459
|
+
</div>
|
|
460
|
+
)}
|
|
461
|
+
|
|
462
|
+
{/* Timestamp */}
|
|
463
|
+
<div className="text-xs text-slate-500">
|
|
464
|
+
Compacted at: {new Date(item.startedAt).toLocaleString()}
|
|
465
|
+
</div>
|
|
466
|
+
</div>
|
|
467
|
+
)
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
// Fallback (shouldn't happen)
|
|
471
|
+
return <div className="text-slate-500 text-sm">Unknown item type</div>
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
function MetricCard({
|
|
475
|
+
label,
|
|
476
|
+
children,
|
|
477
|
+
}: {
|
|
478
|
+
label: string
|
|
479
|
+
children: React.ReactNode
|
|
480
|
+
}) {
|
|
481
|
+
return (
|
|
482
|
+
<div className="bg-slate-50 rounded-md p-3">
|
|
483
|
+
<div className="text-xs text-slate-500 mb-1">{label}</div>
|
|
484
|
+
<div className="font-medium text-slate-900">{children}</div>
|
|
485
|
+
</div>
|
|
486
|
+
)
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
function TypeBadge({ type }: { type: TimelineItem['type'] }) {
|
|
490
|
+
const config = getTypeConfig(type)
|
|
491
|
+
return (
|
|
492
|
+
<span className={`text-xs px-1.5 py-0.5 rounded-full font-medium ${config.badge}`}>
|
|
493
|
+
{config.label}
|
|
494
|
+
</span>
|
|
495
|
+
)
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
interface TypeConfig {
|
|
499
|
+
label: string
|
|
500
|
+
badge: string
|
|
501
|
+
bgLight: string
|
|
502
|
+
Icon: () => React.ReactNode
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
function getTypeConfig(type: TimelineItem['type']): TypeConfig {
|
|
506
|
+
switch (type) {
|
|
507
|
+
case 'llm':
|
|
508
|
+
return {
|
|
509
|
+
label: 'LLM',
|
|
510
|
+
badge: 'bg-violet-100 text-violet-700',
|
|
511
|
+
bgLight: 'bg-violet-100',
|
|
512
|
+
Icon: LLMIcon,
|
|
513
|
+
}
|
|
514
|
+
case 'tool':
|
|
515
|
+
return {
|
|
516
|
+
label: 'Tool',
|
|
517
|
+
badge: 'bg-emerald-100 text-emerald-700',
|
|
518
|
+
bgLight: 'bg-emerald-100',
|
|
519
|
+
Icon: ToolIcon,
|
|
520
|
+
}
|
|
521
|
+
case 'compaction':
|
|
522
|
+
return {
|
|
523
|
+
label: 'Compact',
|
|
524
|
+
badge: 'bg-amber-100 text-amber-700',
|
|
525
|
+
bgLight: 'bg-amber-100',
|
|
526
|
+
Icon: CompactionIcon,
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
function LLMIcon() {
|
|
532
|
+
return (
|
|
533
|
+
<svg className="w-3 h-3 text-violet-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
534
|
+
<path
|
|
535
|
+
strokeLinecap="round"
|
|
536
|
+
strokeLinejoin="round"
|
|
537
|
+
strokeWidth={2}
|
|
538
|
+
d="M9.75 17L9 20l-1 1h8l-1-1-.75-3M3 13h18M5 17h14a2 2 0 002-2V5a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"
|
|
539
|
+
/>
|
|
540
|
+
</svg>
|
|
541
|
+
)
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
function ToolIcon() {
|
|
545
|
+
return (
|
|
546
|
+
<svg className="w-3 h-3 text-emerald-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
547
|
+
<path
|
|
548
|
+
strokeLinecap="round"
|
|
549
|
+
strokeLinejoin="round"
|
|
550
|
+
strokeWidth={2}
|
|
551
|
+
d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"
|
|
552
|
+
/>
|
|
553
|
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
|
|
554
|
+
</svg>
|
|
555
|
+
)
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
function CompactionIcon() {
|
|
559
|
+
return (
|
|
560
|
+
<svg className="w-3 h-3 text-amber-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
561
|
+
<path
|
|
562
|
+
strokeLinecap="round"
|
|
563
|
+
strokeLinejoin="round"
|
|
564
|
+
strokeWidth={2}
|
|
565
|
+
d="M4 7v10c0 2.21 3.582 4 8 4s8-1.79 8-4V7M4 7c0 2.21 3.582 4 8 4s8-1.79 8-4M4 7c0-2.21 3.582-4 8-4s8 1.79 8 4m0 5c0 2.21-3.582 4-8 4s-8-1.79-8-4"
|
|
566
|
+
/>
|
|
567
|
+
</svg>
|
|
568
|
+
)
|
|
569
|
+
}
|