@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,297 @@
|
|
|
1
|
+
import type { ProtocolAgentStatus, SessionId } from '@roj-ai/sdk'
|
|
2
|
+
import type { AgentTreeNode } from '@roj-ai/shared'
|
|
3
|
+
import { AgentId } from '@roj-ai/shared'
|
|
4
|
+
import type { RpcOutput } from '@roj-ai/shared/rpc'
|
|
5
|
+
import { useCallback, useEffect, useState } from 'react'
|
|
6
|
+
import { api, unwrap } from '@roj-ai/client'
|
|
7
|
+
import { useAgentTree, useEventStore } from '../../../stores/event-store'
|
|
8
|
+
import { useDebugContext } from '../DebugContext'
|
|
9
|
+
import { DebugLink, useDebugSessionId } from '../DebugNavigation'
|
|
10
|
+
import { AgentDetailPage } from './AgentDetailPage'
|
|
11
|
+
|
|
12
|
+
export function AgentsPage() {
|
|
13
|
+
const sessionId = useDebugSessionId()
|
|
14
|
+
const { params } = useDebugContext()
|
|
15
|
+
|
|
16
|
+
// Get agent tree from event store (already loaded by DebugLayout)
|
|
17
|
+
const agents = useAgentTree()
|
|
18
|
+
const isLoading = useEventStore((s) => s.isLoading)
|
|
19
|
+
const error = useEventStore((s) => s.error)
|
|
20
|
+
|
|
21
|
+
const selectedAgentId = params.agentId ?? null
|
|
22
|
+
|
|
23
|
+
return (
|
|
24
|
+
<div className="h-full flex gap-4">
|
|
25
|
+
{/* Agent Tree - Left Panel */}
|
|
26
|
+
<div className="w-80 shrink-0 bg-white rounded-2xl shadow-card flex flex-col">
|
|
27
|
+
<div className="p-3 border-b border-gray-100">
|
|
28
|
+
<h2 className="font-semibold text-gray-900 text-sm">Agent Tree</h2>
|
|
29
|
+
</div>
|
|
30
|
+
<div className="flex-1 overflow-auto p-3">
|
|
31
|
+
{isLoading && agents.length === 0
|
|
32
|
+
? <div className="text-gray-400 text-sm">Loading...</div>
|
|
33
|
+
: error
|
|
34
|
+
? <div className="text-red-500 text-sm">{error}</div>
|
|
35
|
+
: agents.length === 0
|
|
36
|
+
? <div className="text-gray-400 text-sm">No agents yet</div>
|
|
37
|
+
: (
|
|
38
|
+
<div>
|
|
39
|
+
{agents.map((agent, i) => (
|
|
40
|
+
<AgentNode
|
|
41
|
+
key={agent.id}
|
|
42
|
+
agent={agent}
|
|
43
|
+
selectedId={selectedAgentId}
|
|
44
|
+
isLast={i === agents.length - 1}
|
|
45
|
+
guides={[]}
|
|
46
|
+
/>
|
|
47
|
+
))}
|
|
48
|
+
</div>
|
|
49
|
+
)}
|
|
50
|
+
</div>
|
|
51
|
+
{selectedAgentId && (
|
|
52
|
+
<SpawnAgentSection
|
|
53
|
+
sessionId={sessionId}
|
|
54
|
+
parentId={selectedAgentId}
|
|
55
|
+
parentDefinitionName={findAgentDefinitionName(agents, selectedAgentId)}
|
|
56
|
+
/>
|
|
57
|
+
)}
|
|
58
|
+
</div>
|
|
59
|
+
|
|
60
|
+
{/* Agent Detail - Right Panel */}
|
|
61
|
+
<div className="flex-1 bg-gray-50/50 rounded-2xl flex flex-col min-w-0">
|
|
62
|
+
<div className="p-3 border-b border-gray-100">
|
|
63
|
+
<h2 className="font-semibold text-gray-900 text-sm">Agent Detail</h2>
|
|
64
|
+
</div>
|
|
65
|
+
<div className="flex-1 overflow-auto p-4">
|
|
66
|
+
{selectedAgentId
|
|
67
|
+
? <AgentDetailPage agentId={selectedAgentId} />
|
|
68
|
+
: (
|
|
69
|
+
<div className="text-gray-400 text-sm">
|
|
70
|
+
Select an agent from the tree to view details
|
|
71
|
+
</div>
|
|
72
|
+
)}
|
|
73
|
+
</div>
|
|
74
|
+
</div>
|
|
75
|
+
</div>
|
|
76
|
+
)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Find the definitionName of an agent by its ID in the tree.
|
|
81
|
+
*/
|
|
82
|
+
function findAgentDefinitionName(agents: AgentTreeNode[], agentId: string): string | null {
|
|
83
|
+
for (const agent of agents) {
|
|
84
|
+
if (agent.id === agentId) return agent.definitionName
|
|
85
|
+
const found = findAgentDefinitionName(agent.children, agentId)
|
|
86
|
+
if (found) return found
|
|
87
|
+
}
|
|
88
|
+
return null
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
type PresetAgent = RpcOutput<'presets.getAgents'>['agents'][number]
|
|
92
|
+
|
|
93
|
+
function SpawnAgentSection({
|
|
94
|
+
sessionId,
|
|
95
|
+
parentId,
|
|
96
|
+
parentDefinitionName,
|
|
97
|
+
}: {
|
|
98
|
+
sessionId: SessionId
|
|
99
|
+
parentId: string
|
|
100
|
+
parentDefinitionName: string | null
|
|
101
|
+
}) {
|
|
102
|
+
const [presetAgents, setPresetAgents] = useState<PresetAgent[]>([])
|
|
103
|
+
const [selectedAgent, setSelectedAgent] = useState('')
|
|
104
|
+
const [message, setMessage] = useState('')
|
|
105
|
+
const [spawning, setSpawning] = useState(false)
|
|
106
|
+
const [error, setError] = useState<string | null>(null)
|
|
107
|
+
|
|
108
|
+
useEffect(() => {
|
|
109
|
+
api.call('presets.getAgents', { sessionId }).then((result) => {
|
|
110
|
+
setPresetAgents(unwrap(result).agents)
|
|
111
|
+
}).catch(() => {
|
|
112
|
+
setPresetAgents([])
|
|
113
|
+
})
|
|
114
|
+
}, [sessionId])
|
|
115
|
+
|
|
116
|
+
// Filter agents spawnable by the selected parent
|
|
117
|
+
const spawnableAgents = presetAgents.filter((a) => parentDefinitionName && a.spawnableBy.includes(parentDefinitionName))
|
|
118
|
+
|
|
119
|
+
const handleSpawn = useCallback(async () => {
|
|
120
|
+
if (!selectedAgent) return
|
|
121
|
+
|
|
122
|
+
setSpawning(true)
|
|
123
|
+
setError(null)
|
|
124
|
+
|
|
125
|
+
try {
|
|
126
|
+
unwrap(
|
|
127
|
+
await api.call('agents.spawn', {
|
|
128
|
+
sessionId,
|
|
129
|
+
definitionName: selectedAgent,
|
|
130
|
+
parentId: AgentId(parentId),
|
|
131
|
+
message: message.trim() || undefined,
|
|
132
|
+
}),
|
|
133
|
+
)
|
|
134
|
+
setSelectedAgent('')
|
|
135
|
+
setMessage('')
|
|
136
|
+
} catch (e) {
|
|
137
|
+
setError(e instanceof Error ? e.message : 'Failed to spawn agent')
|
|
138
|
+
} finally {
|
|
139
|
+
setSpawning(false)
|
|
140
|
+
}
|
|
141
|
+
}, [selectedAgent, sessionId, parentId, message])
|
|
142
|
+
|
|
143
|
+
if (spawnableAgents.length === 0) return null
|
|
144
|
+
|
|
145
|
+
return (
|
|
146
|
+
<div className="p-3 border-t border-gray-100">
|
|
147
|
+
<h3 className="text-xs font-medium text-gray-400 mb-2">Spawn Agent</h3>
|
|
148
|
+
<div className="space-y-2">
|
|
149
|
+
<select
|
|
150
|
+
value={selectedAgent}
|
|
151
|
+
onChange={(e) => setSelectedAgent(e.target.value)}
|
|
152
|
+
className="w-full text-xs border border-gray-200 rounded px-2 py-1"
|
|
153
|
+
>
|
|
154
|
+
<option value="">Select agent...</option>
|
|
155
|
+
{spawnableAgents.map((a) => (
|
|
156
|
+
<option key={a.name} value={a.name}>
|
|
157
|
+
{a.name}
|
|
158
|
+
{a.hasInputSchema ? ' (typed)' : ''}
|
|
159
|
+
</option>
|
|
160
|
+
))}
|
|
161
|
+
</select>
|
|
162
|
+
<input
|
|
163
|
+
type="text"
|
|
164
|
+
value={message}
|
|
165
|
+
onChange={(e) => setMessage(e.target.value)}
|
|
166
|
+
placeholder="Initial message (optional)"
|
|
167
|
+
className="w-full text-xs border border-gray-200 rounded px-2 py-1"
|
|
168
|
+
/>
|
|
169
|
+
{error && <div className="text-xs text-red-600">{error}</div>}
|
|
170
|
+
<button
|
|
171
|
+
onClick={handleSpawn}
|
|
172
|
+
disabled={spawning || !selectedAgent}
|
|
173
|
+
className="w-full px-2 py-1 text-xs font-medium text-white bg-green-600 rounded hover:bg-green-700 disabled:opacity-50 disabled:cursor-not-allowed"
|
|
174
|
+
>
|
|
175
|
+
{spawning ? 'Spawning...' : 'Spawn'}
|
|
176
|
+
</button>
|
|
177
|
+
</div>
|
|
178
|
+
</div>
|
|
179
|
+
)
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const statusDotColors: Record<ProtocolAgentStatus, string> = {
|
|
183
|
+
idle: 'bg-gray-400',
|
|
184
|
+
thinking: 'bg-accent-lime animate-pulse',
|
|
185
|
+
responding: 'bg-accent-peri',
|
|
186
|
+
waiting_for_user: 'bg-purple-400',
|
|
187
|
+
error: 'bg-red-400',
|
|
188
|
+
paused: 'bg-amber-400',
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const statusTextColors: Record<ProtocolAgentStatus, string> = {
|
|
192
|
+
idle: 'text-gray-500',
|
|
193
|
+
thinking: 'text-lime-600',
|
|
194
|
+
responding: 'text-indigo-500',
|
|
195
|
+
waiting_for_user: 'text-purple-500',
|
|
196
|
+
error: 'text-red-500',
|
|
197
|
+
paused: 'text-amber-500',
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const statusLabels: Record<ProtocolAgentStatus, string> = {
|
|
201
|
+
idle: 'idle',
|
|
202
|
+
thinking: 'thinking',
|
|
203
|
+
responding: 'responding',
|
|
204
|
+
waiting_for_user: 'waiting',
|
|
205
|
+
error: 'error',
|
|
206
|
+
paused: 'paused',
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function AgentNode({
|
|
210
|
+
agent,
|
|
211
|
+
selectedId,
|
|
212
|
+
isLast,
|
|
213
|
+
guides,
|
|
214
|
+
}: {
|
|
215
|
+
agent: AgentTreeNode
|
|
216
|
+
selectedId: string | null
|
|
217
|
+
isLast: boolean
|
|
218
|
+
guides: boolean[]
|
|
219
|
+
}) {
|
|
220
|
+
const isSelected = selectedId === agent.id
|
|
221
|
+
|
|
222
|
+
return (
|
|
223
|
+
<>
|
|
224
|
+
<div className="flex">
|
|
225
|
+
{/* Ancestor vertical guide lines */}
|
|
226
|
+
{guides.map((active, i) => (
|
|
227
|
+
<div key={i} className="w-5 shrink-0 relative">
|
|
228
|
+
{active && <div className="absolute left-2 top-0 bottom-0 w-px bg-gray-200" />}
|
|
229
|
+
</div>
|
|
230
|
+
))}
|
|
231
|
+
|
|
232
|
+
{/* Branch connector (vertical + horizontal) */}
|
|
233
|
+
<div className="w-5 shrink-0 relative">
|
|
234
|
+
<div className={`absolute left-2 top-0 w-px bg-gray-200 ${isLast ? 'h-5' : 'h-full'}`} />
|
|
235
|
+
<div className="absolute left-2 top-5 h-px w-2.5 bg-gray-200" />
|
|
236
|
+
</div>
|
|
237
|
+
|
|
238
|
+
{/* Node card */}
|
|
239
|
+
<div className="flex-1 min-w-0 py-0.5">
|
|
240
|
+
<DebugLink
|
|
241
|
+
to={`agents/${agent.id}`}
|
|
242
|
+
className={`block w-full text-left px-2.5 py-2 rounded-lg border transition-all ${
|
|
243
|
+
isSelected
|
|
244
|
+
? 'bg-accent-peri/15 border-accent-peri/40 border-l-[3px] border-l-accent-peri text-gray-900 shadow-sm'
|
|
245
|
+
: 'bg-white border-gray-100 text-gray-700 hover:border-gray-200 hover:shadow-sm'
|
|
246
|
+
}`}
|
|
247
|
+
>
|
|
248
|
+
{/* Row 1: status dot + name + right-aligned status/cost */}
|
|
249
|
+
<div className="flex items-center gap-2">
|
|
250
|
+
<span className={`w-2 h-2 rounded-full shrink-0 ${statusDotColors[agent.status]}`} />
|
|
251
|
+
<span className="font-mono text-sm truncate font-medium">{agent.definitionName}</span>
|
|
252
|
+
<span className="ml-auto flex items-center gap-1.5 shrink-0">
|
|
253
|
+
<span className={`text-[10px] font-medium ${statusTextColors[agent.status]}`}>
|
|
254
|
+
{statusLabels[agent.status]}
|
|
255
|
+
</span>
|
|
256
|
+
{agent.cost > 0 && (
|
|
257
|
+
<span className="text-[10px] text-emerald-600 font-medium tabular-nums">${agent.cost.toFixed(4)}</span>
|
|
258
|
+
)}
|
|
259
|
+
</span>
|
|
260
|
+
</div>
|
|
261
|
+
|
|
262
|
+
{/* Row 2: badges + id */}
|
|
263
|
+
<div className="flex items-center gap-1.5 ml-4 mt-1">
|
|
264
|
+
{agent.isExecuting && (
|
|
265
|
+
<span className="text-[10px] bg-amber-100 text-amber-700 px-1.5 py-0.5 rounded-full font-semibold animate-pulse">
|
|
266
|
+
exec
|
|
267
|
+
</span>
|
|
268
|
+
)}
|
|
269
|
+
{agent.mailboxCount > 0 && (
|
|
270
|
+
<span className="text-[10px] bg-orange-100 text-orange-600 px-1.5 py-0.5 rounded-full font-medium">
|
|
271
|
+
{agent.mailboxCount} msgs
|
|
272
|
+
</span>
|
|
273
|
+
)}
|
|
274
|
+
{agent.pendingToolCalls > 0 && (
|
|
275
|
+
<span className="text-[10px] bg-accent-peri/15 text-gray-600 px-1.5 py-0.5 rounded-full font-medium">
|
|
276
|
+
{agent.pendingToolCalls} tools
|
|
277
|
+
</span>
|
|
278
|
+
)}
|
|
279
|
+
<span className="text-[10px] text-gray-400 font-mono ml-auto">{agent.id.slice(0, 8)}</span>
|
|
280
|
+
</div>
|
|
281
|
+
</DebugLink>
|
|
282
|
+
</div>
|
|
283
|
+
</div>
|
|
284
|
+
|
|
285
|
+
{/* Children */}
|
|
286
|
+
{agent.children.map((child, i) => (
|
|
287
|
+
<AgentNode
|
|
288
|
+
key={child.id}
|
|
289
|
+
agent={child}
|
|
290
|
+
selectedId={selectedId}
|
|
291
|
+
isLast={i === agent.children.length - 1}
|
|
292
|
+
guides={[...guides, !isLast]}
|
|
293
|
+
/>
|
|
294
|
+
))}
|
|
295
|
+
</>
|
|
296
|
+
)
|
|
297
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { useAgentTree, useEvents, useEventStore } from '../../../stores/event-store'
|
|
2
|
+
import { CommunicationDiagram } from '../communication/CommunicationDiagram'
|
|
3
|
+
import { useDiagramData } from '../communication/hooks/useDiagramData'
|
|
4
|
+
|
|
5
|
+
export function CommunicationPage() {
|
|
6
|
+
// Get events and agents from event store (already loaded by DebugLayout)
|
|
7
|
+
const events = useEvents()
|
|
8
|
+
const agents = useAgentTree()
|
|
9
|
+
const isLoading = useEventStore((s) => s.isLoading)
|
|
10
|
+
const error = useEventStore((s) => s.error)
|
|
11
|
+
|
|
12
|
+
const diagramData = useDiagramData({ events, agents })
|
|
13
|
+
|
|
14
|
+
// Calculate stats
|
|
15
|
+
const messageCount = diagramData.messages.length
|
|
16
|
+
const llmCount = diagramData.llmBlocks.length
|
|
17
|
+
const toolCount = diagramData.toolBlocks.length
|
|
18
|
+
const participantCount = diagramData.participants.length - 1 // Exclude user
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<div className="h-full flex flex-col gap-3">
|
|
22
|
+
{/* Summary bar */}
|
|
23
|
+
<div className="flex items-center gap-5 text-sm shrink-0">
|
|
24
|
+
<div className="flex items-center gap-1.5">
|
|
25
|
+
<span className="text-slate-400">Agents:</span>
|
|
26
|
+
<span className="font-semibold text-slate-700">{participantCount}</span>
|
|
27
|
+
</div>
|
|
28
|
+
<div className="flex items-center gap-1.5">
|
|
29
|
+
<span className="w-1.5 h-1.5 rounded-full bg-blue-400" />
|
|
30
|
+
<span className="text-slate-400">Messages:</span>
|
|
31
|
+
<span className="font-semibold text-slate-700">{messageCount}</span>
|
|
32
|
+
</div>
|
|
33
|
+
<div className="flex items-center gap-1.5">
|
|
34
|
+
<span className="w-1.5 h-1.5 rounded-full bg-violet-400" />
|
|
35
|
+
<span className="text-slate-400">LLM:</span>
|
|
36
|
+
<span className="font-semibold text-slate-700">{llmCount}</span>
|
|
37
|
+
</div>
|
|
38
|
+
<div className="flex items-center gap-1.5">
|
|
39
|
+
<span className="w-1.5 h-1.5 rounded-full bg-teal-400" />
|
|
40
|
+
<span className="text-slate-400">Tools:</span>
|
|
41
|
+
<span className="font-semibold text-slate-700">{toolCount}</span>
|
|
42
|
+
</div>
|
|
43
|
+
{isLoading && events.length > 0 && <span className="text-slate-300 text-xs ml-auto">Updating...</span>}
|
|
44
|
+
</div>
|
|
45
|
+
|
|
46
|
+
{/* Error */}
|
|
47
|
+
{error && (
|
|
48
|
+
<div className="text-red-600 text-sm shrink-0 bg-red-50 border border-red-100 rounded-md px-3 py-2">
|
|
49
|
+
{error}
|
|
50
|
+
</div>
|
|
51
|
+
)}
|
|
52
|
+
|
|
53
|
+
{/* Loading state */}
|
|
54
|
+
{isLoading && events.length === 0 && (
|
|
55
|
+
<div className="flex-1 flex items-center justify-center">
|
|
56
|
+
<div className="text-center">
|
|
57
|
+
<div className="w-6 h-6 border-2 border-slate-200 border-t-violet-500 rounded-full animate-spin mx-auto mb-2" />
|
|
58
|
+
<div className="text-sm text-slate-400">Loading diagram...</div>
|
|
59
|
+
</div>
|
|
60
|
+
</div>
|
|
61
|
+
)}
|
|
62
|
+
|
|
63
|
+
{/* Empty state */}
|
|
64
|
+
{!isLoading && events.length === 0 && (
|
|
65
|
+
<div className="flex-1 flex items-center justify-center">
|
|
66
|
+
<div className="text-center">
|
|
67
|
+
<svg className="w-10 h-10 text-slate-200 mx-auto mb-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
68
|
+
<path
|
|
69
|
+
strokeLinecap="round"
|
|
70
|
+
strokeLinejoin="round"
|
|
71
|
+
strokeWidth={1.5}
|
|
72
|
+
d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z"
|
|
73
|
+
/>
|
|
74
|
+
</svg>
|
|
75
|
+
<div className="text-sm text-slate-500 font-medium">No activity yet</div>
|
|
76
|
+
<div className="text-xs text-slate-400 mt-0.5">Send a message to start</div>
|
|
77
|
+
</div>
|
|
78
|
+
</div>
|
|
79
|
+
)}
|
|
80
|
+
|
|
81
|
+
{/* Diagram */}
|
|
82
|
+
{events.length > 0 && (
|
|
83
|
+
<div className="flex-1 bg-white rounded-md border border-slate-200 overflow-hidden min-h-0">
|
|
84
|
+
<CommunicationDiagram data={diagramData} />
|
|
85
|
+
</div>
|
|
86
|
+
)}
|
|
87
|
+
</div>
|
|
88
|
+
)
|
|
89
|
+
}
|