bareagent-cli 0.1.0__py3-none-any.whl
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.
- bareagent/__init__.py +10 -0
- bareagent/concurrency/__init__.py +6 -0
- bareagent/concurrency/background.py +97 -0
- bareagent/concurrency/notification.py +61 -0
- bareagent/concurrency/scheduler.py +136 -0
- bareagent/config.toml +299 -0
- bareagent/core/__init__.py +1 -0
- bareagent/core/config_paths.py +49 -0
- bareagent/core/context.py +127 -0
- bareagent/core/fileutil.py +103 -0
- bareagent/core/goal.py +214 -0
- bareagent/core/handlers/__init__.py +1 -0
- bareagent/core/handlers/bash.py +79 -0
- bareagent/core/handlers/file_edit.py +47 -0
- bareagent/core/handlers/file_read.py +270 -0
- bareagent/core/handlers/file_write.py +34 -0
- bareagent/core/handlers/glob_search.py +30 -0
- bareagent/core/handlers/goal.py +60 -0
- bareagent/core/handlers/grep_search.py +52 -0
- bareagent/core/handlers/memory.py +71 -0
- bareagent/core/handlers/plan.py +106 -0
- bareagent/core/handlers/search_utils.py +77 -0
- bareagent/core/handlers/skill.py +87 -0
- bareagent/core/handlers/subagent_send.py +70 -0
- bareagent/core/handlers/web_fetch.py +126 -0
- bareagent/core/handlers/web_search.py +165 -0
- bareagent/core/handlers/workflow.py +190 -0
- bareagent/core/loop.py +535 -0
- bareagent/core/retry.py +131 -0
- bareagent/core/sandbox.py +27 -0
- bareagent/core/schema.py +21 -0
- bareagent/core/tools.py +779 -0
- bareagent/core/workflow.py +517 -0
- bareagent/core/workflow_registry.py +219 -0
- bareagent/debug/__init__.py +0 -0
- bareagent/debug/interaction_log.py +263 -0
- bareagent/debug/viewer.html +1750 -0
- bareagent/debug/web_viewer.py +157 -0
- bareagent/hooks/__init__.py +32 -0
- bareagent/hooks/config.py +118 -0
- bareagent/hooks/engine.py +197 -0
- bareagent/hooks/errors.py +14 -0
- bareagent/hooks/events.py +22 -0
- bareagent/lsp/__init__.py +63 -0
- bareagent/lsp/config.py +134 -0
- bareagent/lsp/coord.py +118 -0
- bareagent/lsp/diagnostics.py +240 -0
- bareagent/lsp/errors.py +24 -0
- bareagent/lsp/manager.py +866 -0
- bareagent/lsp/tools.py +629 -0
- bareagent/lsp/workspace_edit.py +305 -0
- bareagent/main.py +4205 -0
- bareagent/mcp/__init__.py +69 -0
- bareagent/mcp/_sse.py +69 -0
- bareagent/mcp/client.py +341 -0
- bareagent/mcp/config.py +169 -0
- bareagent/mcp/errors.py +32 -0
- bareagent/mcp/manager.py +318 -0
- bareagent/mcp/protocol.py +187 -0
- bareagent/mcp/registry.py +557 -0
- bareagent/mcp/transport/__init__.py +15 -0
- bareagent/mcp/transport/base.py +149 -0
- bareagent/mcp/transport/http_legacy.py +192 -0
- bareagent/mcp/transport/http_streamable.py +217 -0
- bareagent/mcp/transport/stdio.py +202 -0
- bareagent/memory/__init__.py +1 -0
- bareagent/memory/compact.py +203 -0
- bareagent/memory/conversation_io.py +226 -0
- bareagent/memory/embedding.py +194 -0
- bareagent/memory/persistent.py +515 -0
- bareagent/memory/token_counter.py +67 -0
- bareagent/memory/token_tracker.py +262 -0
- bareagent/memory/transcript.py +100 -0
- bareagent/permission/__init__.py +1 -0
- bareagent/permission/guard.py +329 -0
- bareagent/permission/rules.py +19 -0
- bareagent/planning/__init__.py +19 -0
- bareagent/planning/agent_types.py +169 -0
- bareagent/planning/skill_gen.py +141 -0
- bareagent/planning/skill_store.py +173 -0
- bareagent/planning/skills.py +146 -0
- bareagent/planning/subagent.py +355 -0
- bareagent/planning/subagent_registry.py +77 -0
- bareagent/planning/tasks.py +348 -0
- bareagent/planning/todo.py +153 -0
- bareagent/planning/worktree.py +122 -0
- bareagent/provider/__init__.py +1 -0
- bareagent/provider/anthropic.py +348 -0
- bareagent/provider/base.py +136 -0
- bareagent/provider/factory.py +130 -0
- bareagent/provider/openai.py +881 -0
- bareagent/provider/presets.py +72 -0
- bareagent/provider/setup.py +356 -0
- bareagent/skills/.gitkeep +1 -0
- bareagent/skills/code-review/SKILL.md +68 -0
- bareagent/skills/git/SKILL.md +68 -0
- bareagent/skills/test/SKILL.md +70 -0
- bareagent/team/__init__.py +17 -0
- bareagent/team/autonomous.py +193 -0
- bareagent/team/mailbox.py +239 -0
- bareagent/team/manager.py +155 -0
- bareagent/team/protocols.py +129 -0
- bareagent/tracing/__init__.py +12 -0
- bareagent/tracing/_api.py +92 -0
- bareagent/tracing/_proxy.py +60 -0
- bareagent/tracing/composite.py +115 -0
- bareagent/tracing/json_file.py +115 -0
- bareagent/tracing/langfuse.py +139 -0
- bareagent/tracing/otel.py +107 -0
- bareagent/tracing/setup.py +85 -0
- bareagent/ui/__init__.py +24 -0
- bareagent/ui/console.py +167 -0
- bareagent/ui/prompt.py +78 -0
- bareagent/ui/protocol.py +24 -0
- bareagent/ui/stream.py +66 -0
- bareagent/ui/theme.py +240 -0
- bareagent_cli-0.1.0.dist-info/METADATA +331 -0
- bareagent_cli-0.1.0.dist-info/RECORD +121 -0
- bareagent_cli-0.1.0.dist-info/WHEEL +4 -0
- bareagent_cli-0.1.0.dist-info/entry_points.txt +2 -0
- bareagent_cli-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,1750 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>BareAgent Debug Viewer</title>
|
|
7
|
+
<style>
|
|
8
|
+
:root {
|
|
9
|
+
--bg: #1e1e2e;
|
|
10
|
+
--bg-elevated: #181825;
|
|
11
|
+
--bg-panel: rgba(30, 30, 46, 0.92);
|
|
12
|
+
--bg-soft: #313244;
|
|
13
|
+
--bg-code: #11111b;
|
|
14
|
+
--text: #cdd6f4;
|
|
15
|
+
--text-dim: #a6adc8;
|
|
16
|
+
--text-muted: #9399b2;
|
|
17
|
+
--accent: #89b4fa;
|
|
18
|
+
--accent-strong: #74c7ec;
|
|
19
|
+
--system: #cba6f7;
|
|
20
|
+
--user: #89b4fa;
|
|
21
|
+
--assistant: #a6e3a1;
|
|
22
|
+
--tool: #89dceb;
|
|
23
|
+
--warning: #f9e2af;
|
|
24
|
+
--shadow: 0 18px 45px rgba(17, 17, 27, 0.38);
|
|
25
|
+
--radius-lg: 22px;
|
|
26
|
+
--radius-md: 16px;
|
|
27
|
+
--radius-sm: 12px;
|
|
28
|
+
--border: rgba(205, 214, 244, 0.1);
|
|
29
|
+
--font-display: "Iowan Old Style", "Palatino Linotype", "Book Antiqua", Georgia, serif;
|
|
30
|
+
--font-body: "Segoe UI", "Helvetica Neue", Arial, sans-serif;
|
|
31
|
+
--font-mono: "Cascadia Code", "SFMono-Regular", Consolas, "Liberation Mono", monospace;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
* {
|
|
35
|
+
box-sizing: border-box;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
html,
|
|
39
|
+
body {
|
|
40
|
+
min-height: 100%;
|
|
41
|
+
margin: 0;
|
|
42
|
+
background:
|
|
43
|
+
radial-gradient(circle at top left, rgba(203, 166, 247, 0.18), transparent 28%),
|
|
44
|
+
radial-gradient(circle at top right, rgba(137, 220, 235, 0.12), transparent 24%),
|
|
45
|
+
linear-gradient(180deg, #181825 0%, #1e1e2e 55%, #11111b 100%);
|
|
46
|
+
color: var(--text);
|
|
47
|
+
font-family: var(--font-body);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
body {
|
|
51
|
+
min-width: 800px;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.shell {
|
|
55
|
+
position: relative;
|
|
56
|
+
min-height: 100vh;
|
|
57
|
+
padding: 28px;
|
|
58
|
+
overflow: hidden;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.shell::before {
|
|
62
|
+
content: "";
|
|
63
|
+
position: absolute;
|
|
64
|
+
inset: 18px;
|
|
65
|
+
border: 1px solid rgba(205, 214, 244, 0.06);
|
|
66
|
+
border-radius: 28px;
|
|
67
|
+
pointer-events: none;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.header {
|
|
71
|
+
position: relative;
|
|
72
|
+
z-index: 1;
|
|
73
|
+
display: flex;
|
|
74
|
+
align-items: end;
|
|
75
|
+
justify-content: space-between;
|
|
76
|
+
gap: 20px;
|
|
77
|
+
margin-bottom: 22px;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
.header-copy h1 {
|
|
81
|
+
margin: 0;
|
|
82
|
+
font-family: var(--font-display);
|
|
83
|
+
font-size: clamp(2rem, 3vw, 3rem);
|
|
84
|
+
font-weight: 600;
|
|
85
|
+
letter-spacing: 0.02em;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.header-copy p {
|
|
89
|
+
max-width: 46rem;
|
|
90
|
+
margin: 8px 0 0;
|
|
91
|
+
color: var(--text-dim);
|
|
92
|
+
line-height: 1.6;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.header-actions {
|
|
96
|
+
display: flex;
|
|
97
|
+
align-items: center;
|
|
98
|
+
gap: 12px;
|
|
99
|
+
flex-wrap: wrap;
|
|
100
|
+
justify-content: end;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
.locale-switch {
|
|
104
|
+
display: inline-flex;
|
|
105
|
+
align-items: center;
|
|
106
|
+
gap: 6px;
|
|
107
|
+
padding: 6px;
|
|
108
|
+
border: 1px solid rgba(137, 180, 250, 0.18);
|
|
109
|
+
border-radius: 999px;
|
|
110
|
+
background: rgba(24, 24, 37, 0.78);
|
|
111
|
+
box-shadow: var(--shadow);
|
|
112
|
+
backdrop-filter: blur(12px);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
.locale-button {
|
|
116
|
+
padding: 9px 12px;
|
|
117
|
+
border: 0;
|
|
118
|
+
border-radius: 999px;
|
|
119
|
+
background: transparent;
|
|
120
|
+
color: var(--text-muted);
|
|
121
|
+
font-family: var(--font-body);
|
|
122
|
+
font-size: 0.82rem;
|
|
123
|
+
font-weight: 600;
|
|
124
|
+
letter-spacing: 0.04em;
|
|
125
|
+
cursor: pointer;
|
|
126
|
+
transition: background 160ms ease, color 160ms ease, transform 160ms ease;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
.locale-button:hover,
|
|
130
|
+
.locale-button:focus-visible {
|
|
131
|
+
background: rgba(49, 50, 68, 0.72);
|
|
132
|
+
color: var(--text);
|
|
133
|
+
transform: translateY(-1px);
|
|
134
|
+
outline: none;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
.locale-button.active {
|
|
138
|
+
background: linear-gradient(180deg, rgba(137, 180, 250, 0.2), rgba(17, 17, 27, 0.82));
|
|
139
|
+
color: var(--accent-strong);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
.status-pill {
|
|
143
|
+
display: inline-flex;
|
|
144
|
+
align-items: center;
|
|
145
|
+
gap: 10px;
|
|
146
|
+
padding: 12px 16px;
|
|
147
|
+
border: 1px solid rgba(137, 180, 250, 0.24);
|
|
148
|
+
border-radius: 999px;
|
|
149
|
+
background: rgba(24, 24, 37, 0.82);
|
|
150
|
+
color: var(--text-dim);
|
|
151
|
+
box-shadow: var(--shadow);
|
|
152
|
+
backdrop-filter: blur(12px);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
.status-dot {
|
|
156
|
+
width: 10px;
|
|
157
|
+
height: 10px;
|
|
158
|
+
border-radius: 50%;
|
|
159
|
+
background: var(--warning);
|
|
160
|
+
box-shadow: 0 0 16px rgba(249, 226, 175, 0.45);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
.layout {
|
|
164
|
+
position: relative;
|
|
165
|
+
z-index: 1;
|
|
166
|
+
display: grid;
|
|
167
|
+
grid-template-columns: 320px minmax(0, 1fr);
|
|
168
|
+
gap: 20px;
|
|
169
|
+
align-items: start;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
.panel {
|
|
173
|
+
position: relative;
|
|
174
|
+
border: 1px solid var(--border);
|
|
175
|
+
border-radius: var(--radius-lg);
|
|
176
|
+
background: linear-gradient(180deg, rgba(49, 50, 68, 0.58), rgba(24, 24, 37, 0.92));
|
|
177
|
+
box-shadow: var(--shadow);
|
|
178
|
+
backdrop-filter: blur(14px);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
.panel-header {
|
|
182
|
+
display: flex;
|
|
183
|
+
align-items: baseline;
|
|
184
|
+
justify-content: space-between;
|
|
185
|
+
gap: 12px;
|
|
186
|
+
padding: 22px 24px 16px;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
.panel-header h2 {
|
|
190
|
+
margin: 0;
|
|
191
|
+
font-family: var(--font-display);
|
|
192
|
+
font-size: 1.45rem;
|
|
193
|
+
font-weight: 600;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
.panel-header p,
|
|
197
|
+
.panel-header span {
|
|
198
|
+
margin: 0;
|
|
199
|
+
color: var(--text-muted);
|
|
200
|
+
font-size: 0.92rem;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
.session-list {
|
|
204
|
+
display: grid;
|
|
205
|
+
gap: 12px;
|
|
206
|
+
padding: 0 16px 18px;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
.session-button {
|
|
210
|
+
width: 100%;
|
|
211
|
+
padding: 16px 16px 15px;
|
|
212
|
+
border: 1px solid transparent;
|
|
213
|
+
border-radius: var(--radius-md);
|
|
214
|
+
background: rgba(17, 17, 27, 0.58);
|
|
215
|
+
color: var(--text);
|
|
216
|
+
text-align: left;
|
|
217
|
+
cursor: pointer;
|
|
218
|
+
transition: transform 160ms ease, border-color 160ms ease, background 160ms ease;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
.session-button:hover,
|
|
222
|
+
.session-button:focus-visible {
|
|
223
|
+
border-color: rgba(137, 180, 250, 0.35);
|
|
224
|
+
background: rgba(49, 50, 68, 0.78);
|
|
225
|
+
transform: translateY(-1px);
|
|
226
|
+
outline: none;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
.session-button.active {
|
|
230
|
+
border-color: rgba(137, 180, 250, 0.48);
|
|
231
|
+
background: linear-gradient(180deg, rgba(137, 180, 250, 0.18), rgba(30, 30, 46, 0.88));
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
.session-name {
|
|
235
|
+
display: block;
|
|
236
|
+
font-family: var(--font-mono);
|
|
237
|
+
font-size: 0.94rem;
|
|
238
|
+
word-break: break-word;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
.session-meta {
|
|
242
|
+
display: flex;
|
|
243
|
+
justify-content: space-between;
|
|
244
|
+
gap: 12px;
|
|
245
|
+
margin-top: 10px;
|
|
246
|
+
color: var(--text-muted);
|
|
247
|
+
font-size: 0.82rem;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
.timeline-shell {
|
|
251
|
+
padding-bottom: 18px;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
.timeline-meta {
|
|
255
|
+
display: flex;
|
|
256
|
+
flex-wrap: wrap;
|
|
257
|
+
gap: 10px;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
.meta-chip {
|
|
261
|
+
display: inline-flex;
|
|
262
|
+
align-items: center;
|
|
263
|
+
gap: 8px;
|
|
264
|
+
padding: 7px 11px;
|
|
265
|
+
border-radius: 999px;
|
|
266
|
+
background: rgba(17, 17, 27, 0.56);
|
|
267
|
+
color: var(--text-muted);
|
|
268
|
+
font-size: 0.82rem;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
.timeline {
|
|
272
|
+
display: grid;
|
|
273
|
+
gap: 14px;
|
|
274
|
+
padding: 0 18px;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
.empty-state {
|
|
278
|
+
margin: 0 18px;
|
|
279
|
+
padding: 38px 22px;
|
|
280
|
+
border: 1px dashed rgba(205, 214, 244, 0.15);
|
|
281
|
+
border-radius: var(--radius-md);
|
|
282
|
+
background: rgba(17, 17, 27, 0.34);
|
|
283
|
+
color: var(--text-muted);
|
|
284
|
+
text-align: center;
|
|
285
|
+
line-height: 1.7;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
.interaction-card {
|
|
289
|
+
border: 1px solid rgba(205, 214, 244, 0.08);
|
|
290
|
+
border-radius: var(--radius-lg);
|
|
291
|
+
background: linear-gradient(180deg, rgba(17, 17, 27, 0.88), rgba(30, 30, 46, 0.92));
|
|
292
|
+
box-shadow: 0 12px 28px rgba(17, 17, 27, 0.34);
|
|
293
|
+
overflow: hidden;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
.card-toggle {
|
|
297
|
+
width: 100%;
|
|
298
|
+
padding: 20px 20px 18px;
|
|
299
|
+
border: 0;
|
|
300
|
+
background: transparent;
|
|
301
|
+
color: inherit;
|
|
302
|
+
cursor: pointer;
|
|
303
|
+
text-align: left;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
.card-toggle:hover,
|
|
307
|
+
.card-toggle:focus-visible {
|
|
308
|
+
background: rgba(49, 50, 68, 0.22);
|
|
309
|
+
outline: none;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
.card-header {
|
|
313
|
+
display: flex;
|
|
314
|
+
justify-content: space-between;
|
|
315
|
+
gap: 18px;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
.card-title {
|
|
319
|
+
display: flex;
|
|
320
|
+
align-items: baseline;
|
|
321
|
+
gap: 12px;
|
|
322
|
+
flex-wrap: wrap;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
.card-seq {
|
|
326
|
+
font-family: var(--font-display);
|
|
327
|
+
font-size: 1.25rem;
|
|
328
|
+
color: var(--accent-strong);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
.card-time {
|
|
332
|
+
color: var(--text-muted);
|
|
333
|
+
font-family: var(--font-mono);
|
|
334
|
+
font-size: 0.84rem;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
.card-summary {
|
|
338
|
+
display: grid;
|
|
339
|
+
grid-template-columns: repeat(4, minmax(0, 1fr));
|
|
340
|
+
gap: 10px;
|
|
341
|
+
margin-top: 18px;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
.summary-item {
|
|
345
|
+
padding: 12px 12px 11px;
|
|
346
|
+
border-radius: var(--radius-sm);
|
|
347
|
+
background: rgba(49, 50, 68, 0.34);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
.summary-label {
|
|
351
|
+
display: block;
|
|
352
|
+
margin-bottom: 8px;
|
|
353
|
+
color: var(--text-muted);
|
|
354
|
+
font-size: 0.75rem;
|
|
355
|
+
letter-spacing: 0.08em;
|
|
356
|
+
text-transform: uppercase;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
.summary-value {
|
|
360
|
+
display: block;
|
|
361
|
+
font-family: var(--font-mono);
|
|
362
|
+
font-size: 0.95rem;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
.card-detail {
|
|
366
|
+
display: none;
|
|
367
|
+
border-top: 1px solid rgba(205, 214, 244, 0.08);
|
|
368
|
+
background: rgba(17, 17, 27, 0.46);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
.interaction-card.expanded .card-detail {
|
|
372
|
+
display: block;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
.tab-strip {
|
|
376
|
+
display: flex;
|
|
377
|
+
gap: 10px;
|
|
378
|
+
flex-wrap: wrap;
|
|
379
|
+
padding: 18px 20px 0;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
.tab-button {
|
|
383
|
+
padding: 10px 14px;
|
|
384
|
+
border: 1px solid rgba(205, 214, 244, 0.1);
|
|
385
|
+
border-radius: 999px;
|
|
386
|
+
background: rgba(30, 30, 46, 0.84);
|
|
387
|
+
color: var(--text-dim);
|
|
388
|
+
cursor: pointer;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
.tab-button.active {
|
|
392
|
+
border-color: rgba(137, 180, 250, 0.34);
|
|
393
|
+
background: rgba(137, 180, 250, 0.16);
|
|
394
|
+
color: var(--text);
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
.tab-panel {
|
|
398
|
+
display: none;
|
|
399
|
+
padding: 20px;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
.tab-panel.active {
|
|
403
|
+
display: block;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
.detail-grid {
|
|
407
|
+
display: grid;
|
|
408
|
+
gap: 16px;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
.detail-block {
|
|
412
|
+
padding: 16px;
|
|
413
|
+
border: 1px solid rgba(205, 214, 244, 0.08);
|
|
414
|
+
border-radius: var(--radius-md);
|
|
415
|
+
background: rgba(30, 30, 46, 0.72);
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
.detail-block h3,
|
|
419
|
+
.detail-block h4 {
|
|
420
|
+
margin: 0 0 12px;
|
|
421
|
+
font-size: 0.96rem;
|
|
422
|
+
letter-spacing: 0.03em;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
.role-badge {
|
|
426
|
+
display: inline-flex;
|
|
427
|
+
align-items: center;
|
|
428
|
+
padding: 5px 10px;
|
|
429
|
+
border-radius: 999px;
|
|
430
|
+
font-size: 0.74rem;
|
|
431
|
+
font-weight: 700;
|
|
432
|
+
letter-spacing: 0.06em;
|
|
433
|
+
text-transform: uppercase;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
.role-system {
|
|
437
|
+
color: var(--system);
|
|
438
|
+
background: rgba(203, 166, 247, 0.1);
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
.role-user {
|
|
442
|
+
color: var(--user);
|
|
443
|
+
background: rgba(137, 180, 250, 0.1);
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
.role-assistant {
|
|
447
|
+
color: var(--assistant);
|
|
448
|
+
background: rgba(166, 227, 161, 0.1);
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
.role-tool,
|
|
452
|
+
.role-tool_result {
|
|
453
|
+
color: var(--tool);
|
|
454
|
+
background: rgba(137, 220, 235, 0.12);
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
.message-list,
|
|
458
|
+
.tool-call-list {
|
|
459
|
+
display: grid;
|
|
460
|
+
gap: 14px;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
.message-item,
|
|
464
|
+
.tool-call-item {
|
|
465
|
+
padding: 14px;
|
|
466
|
+
border-radius: var(--radius-md);
|
|
467
|
+
background: rgba(17, 17, 27, 0.56);
|
|
468
|
+
border: 1px solid rgba(205, 214, 244, 0.08);
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
.message-head,
|
|
472
|
+
.tool-call-head {
|
|
473
|
+
display: flex;
|
|
474
|
+
align-items: center;
|
|
475
|
+
justify-content: space-between;
|
|
476
|
+
gap: 12px;
|
|
477
|
+
margin-bottom: 12px;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
.message-body {
|
|
481
|
+
color: var(--text-dim);
|
|
482
|
+
line-height: 1.65;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
.message-body > *:first-child,
|
|
486
|
+
.detail-block > *:first-child {
|
|
487
|
+
margin-top: 0;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
.message-body > *:last-child,
|
|
491
|
+
.detail-block > *:last-child {
|
|
492
|
+
margin-bottom: 0;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
.message-body pre,
|
|
496
|
+
.json-block,
|
|
497
|
+
.code-block {
|
|
498
|
+
margin: 0;
|
|
499
|
+
padding: 14px 15px;
|
|
500
|
+
border-radius: var(--radius-sm);
|
|
501
|
+
background: var(--bg-code);
|
|
502
|
+
color: var(--text);
|
|
503
|
+
font-family: var(--font-mono);
|
|
504
|
+
font-size: 0.9rem;
|
|
505
|
+
line-height: 1.55;
|
|
506
|
+
white-space: pre-wrap;
|
|
507
|
+
word-break: break-word;
|
|
508
|
+
overflow-x: auto;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
.thinking-block {
|
|
512
|
+
color: var(--text-muted);
|
|
513
|
+
font-style: italic;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
.tools-box details {
|
|
517
|
+
border-radius: var(--radius-sm);
|
|
518
|
+
background: rgba(17, 17, 27, 0.56);
|
|
519
|
+
border: 1px solid rgba(205, 214, 244, 0.08);
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
.tools-box summary {
|
|
523
|
+
padding: 14px 16px;
|
|
524
|
+
cursor: pointer;
|
|
525
|
+
color: var(--text-dim);
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
.tools-box summary::marker {
|
|
529
|
+
color: var(--accent);
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
.tools-box .json-block {
|
|
533
|
+
border-top: 1px solid rgba(205, 214, 244, 0.08);
|
|
534
|
+
border-radius: 0 0 var(--radius-sm) var(--radius-sm);
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
.loading-note,
|
|
538
|
+
.error-note {
|
|
539
|
+
padding: 16px;
|
|
540
|
+
border-radius: var(--radius-md);
|
|
541
|
+
font-size: 0.92rem;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
.loading-note {
|
|
545
|
+
background: rgba(137, 180, 250, 0.08);
|
|
546
|
+
color: var(--text-dim);
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
.error-note {
|
|
550
|
+
background: rgba(243, 139, 168, 0.1);
|
|
551
|
+
color: #f38ba8;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
.footer-note {
|
|
555
|
+
padding: 16px 24px 22px;
|
|
556
|
+
color: var(--text-muted);
|
|
557
|
+
font-size: 0.85rem;
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
@media (max-width: 1080px) {
|
|
561
|
+
body {
|
|
562
|
+
min-width: 0;
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
.shell {
|
|
566
|
+
padding: 16px;
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
.layout {
|
|
570
|
+
grid-template-columns: 1fr;
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
.card-summary {
|
|
574
|
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
@media (max-width: 720px) {
|
|
579
|
+
.header {
|
|
580
|
+
align-items: start;
|
|
581
|
+
flex-direction: column;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
.header-actions {
|
|
585
|
+
width: 100%;
|
|
586
|
+
justify-content: space-between;
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
.card-header {
|
|
590
|
+
flex-direction: column;
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
.card-summary {
|
|
594
|
+
grid-template-columns: 1fr;
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
</style>
|
|
598
|
+
</head>
|
|
599
|
+
<body>
|
|
600
|
+
<div class="shell">
|
|
601
|
+
<header class="header">
|
|
602
|
+
<div class="header-copy">
|
|
603
|
+
<h1 id="page-title">BareAgent Debug Viewer</h1>
|
|
604
|
+
<p id="page-subtitle">
|
|
605
|
+
Inspect saved request and response payloads, expand a single interaction, and
|
|
606
|
+
watch new activity stream in over Server-Sent Events.
|
|
607
|
+
</p>
|
|
608
|
+
</div>
|
|
609
|
+
<div class="header-actions">
|
|
610
|
+
<div class="locale-switch" id="locale-switch" role="group" aria-label="Switch language">
|
|
611
|
+
<button class="locale-button active" id="locale-en" type="button" data-locale="en">EN</button>
|
|
612
|
+
<button class="locale-button" id="locale-zh" type="button" data-locale="zh">中文</button>
|
|
613
|
+
</div>
|
|
614
|
+
<div class="status-pill" id="connection-status">
|
|
615
|
+
<span class="status-dot" id="status-dot"></span>
|
|
616
|
+
<span id="status-text">Connecting to event stream...</span>
|
|
617
|
+
</div>
|
|
618
|
+
</div>
|
|
619
|
+
</header>
|
|
620
|
+
|
|
621
|
+
<main class="layout">
|
|
622
|
+
<aside class="panel">
|
|
623
|
+
<div class="panel-header">
|
|
624
|
+
<div>
|
|
625
|
+
<h2 id="sessions-title">Sessions</h2>
|
|
626
|
+
<p id="sessions-subtitle">Recorded debug timelines</p>
|
|
627
|
+
</div>
|
|
628
|
+
<span id="session-count">0 loaded</span>
|
|
629
|
+
</div>
|
|
630
|
+
<div class="session-list" id="session-list">
|
|
631
|
+
<div class="empty-state">Loading session index...</div>
|
|
632
|
+
</div>
|
|
633
|
+
<div class="footer-note" id="sessions-footer-note">
|
|
634
|
+
Session selection refreshes the interaction timeline on the right.
|
|
635
|
+
</div>
|
|
636
|
+
</aside>
|
|
637
|
+
|
|
638
|
+
<section class="panel timeline-shell">
|
|
639
|
+
<div class="panel-header">
|
|
640
|
+
<div>
|
|
641
|
+
<h2 id="timeline-title">Interactions</h2>
|
|
642
|
+
<p id="timeline-subtitle">Choose a session to inspect its timeline.</p>
|
|
643
|
+
</div>
|
|
644
|
+
<div class="timeline-meta">
|
|
645
|
+
<span class="meta-chip" id="timeline-session-chip">No session selected</span>
|
|
646
|
+
<span class="meta-chip" id="timeline-count-chip">0 interactions</span>
|
|
647
|
+
</div>
|
|
648
|
+
</div>
|
|
649
|
+
<div class="timeline" id="timeline">
|
|
650
|
+
<div class="empty-state">
|
|
651
|
+
The timeline will appear here after a session is selected.
|
|
652
|
+
</div>
|
|
653
|
+
</div>
|
|
654
|
+
</section>
|
|
655
|
+
</main>
|
|
656
|
+
</div>
|
|
657
|
+
|
|
658
|
+
<template id="session-template">
|
|
659
|
+
<button class="session-button" type="button">
|
|
660
|
+
<span class="session-name"></span>
|
|
661
|
+
<span class="session-meta">
|
|
662
|
+
<span class="session-count"></span>
|
|
663
|
+
<span class="session-latest"></span>
|
|
664
|
+
</span>
|
|
665
|
+
</button>
|
|
666
|
+
</template>
|
|
667
|
+
|
|
668
|
+
<template id="interaction-template">
|
|
669
|
+
<article class="interaction-card">
|
|
670
|
+
<button class="card-toggle" type="button">
|
|
671
|
+
<div class="card-header">
|
|
672
|
+
<div class="card-title">
|
|
673
|
+
<span class="card-seq"></span>
|
|
674
|
+
<span class="card-time"></span>
|
|
675
|
+
</div>
|
|
676
|
+
<span class="meta-chip card-state"></span>
|
|
677
|
+
</div>
|
|
678
|
+
<div class="card-summary">
|
|
679
|
+
<div class="summary-item">
|
|
680
|
+
<span class="summary-label">Tokens</span>
|
|
681
|
+
<span class="summary-value summary-tokens"></span>
|
|
682
|
+
</div>
|
|
683
|
+
<div class="summary-item">
|
|
684
|
+
<span class="summary-label">Duration</span>
|
|
685
|
+
<span class="summary-value summary-duration"></span>
|
|
686
|
+
</div>
|
|
687
|
+
<div class="summary-item">
|
|
688
|
+
<span class="summary-label">Tool Calls</span>
|
|
689
|
+
<span class="summary-value summary-tool-calls"></span>
|
|
690
|
+
</div>
|
|
691
|
+
<div class="summary-item">
|
|
692
|
+
<span class="summary-label">Messages</span>
|
|
693
|
+
<span class="summary-value summary-messages"></span>
|
|
694
|
+
</div>
|
|
695
|
+
</div>
|
|
696
|
+
</button>
|
|
697
|
+
|
|
698
|
+
<div class="card-detail">
|
|
699
|
+
<div class="tab-strip">
|
|
700
|
+
<button class="tab-button active" type="button" data-tab="request">Request</button>
|
|
701
|
+
<button class="tab-button" type="button" data-tab="response">Response</button>
|
|
702
|
+
<button class="tab-button" type="button" data-tab="raw">Raw JSON</button>
|
|
703
|
+
</div>
|
|
704
|
+
|
|
705
|
+
<section class="tab-panel active" data-panel="request">
|
|
706
|
+
<div class="detail-grid request-panel"></div>
|
|
707
|
+
</section>
|
|
708
|
+
|
|
709
|
+
<section class="tab-panel" data-panel="response">
|
|
710
|
+
<div class="detail-grid response-panel"></div>
|
|
711
|
+
</section>
|
|
712
|
+
|
|
713
|
+
<section class="tab-panel" data-panel="raw">
|
|
714
|
+
<pre class="json-block raw-panel"></pre>
|
|
715
|
+
</section>
|
|
716
|
+
</div>
|
|
717
|
+
</article>
|
|
718
|
+
</template>
|
|
719
|
+
|
|
720
|
+
<script>
|
|
721
|
+
const LOCALE_STORAGE_KEY = "bareagent-debug-viewer-locale";
|
|
722
|
+
const translations = {
|
|
723
|
+
en: {
|
|
724
|
+
documentTitle: "BareAgent Debug Viewer",
|
|
725
|
+
pageTitle: "BareAgent Debug Viewer",
|
|
726
|
+
pageSubtitle:
|
|
727
|
+
"Inspect saved request and response payloads, expand a single interaction, and watch new activity stream in over Server-Sent Events.",
|
|
728
|
+
localeSwitcherLabel: "Switch language",
|
|
729
|
+
sessionsTitle: "Sessions",
|
|
730
|
+
sessionsSubtitle: "Recorded debug timelines",
|
|
731
|
+
sessionsFooterNote: "Session selection refreshes the interaction timeline on the right.",
|
|
732
|
+
sessionIndexLoading: "Loading session index...",
|
|
733
|
+
sessionsEmpty: "No debug sessions have been recorded yet.",
|
|
734
|
+
clickToLoad: "Click to load",
|
|
735
|
+
noCachedTimeline: "No cached timeline",
|
|
736
|
+
latestSeq: "Latest #{seq}",
|
|
737
|
+
timelineTitle: "Interactions",
|
|
738
|
+
timelineTitleForSession: "Interactions for {sessionId}",
|
|
739
|
+
timelineSubtitleIdle: "Choose a session to inspect its timeline.",
|
|
740
|
+
timelineSubtitleActive: "Click a card to expand request, response, and raw JSON payloads.",
|
|
741
|
+
timelineSubtitleLoadError: "This session timeline could not be loaded from the debug viewer API.",
|
|
742
|
+
timelineEmpty: "The timeline will appear here after a session is selected.",
|
|
743
|
+
noSessionSelected: "No session selected",
|
|
744
|
+
loadingInteractions: "Loading interactions...",
|
|
745
|
+
noInteractions: "No interactions found for this session.",
|
|
746
|
+
sessionLoadError: "Failed to load session timeline: {message}",
|
|
747
|
+
summaryTokens: "Tokens",
|
|
748
|
+
summaryDuration: "Duration",
|
|
749
|
+
summaryToolCalls: "Tool Calls",
|
|
750
|
+
summaryMessages: "Messages",
|
|
751
|
+
tabRequest: "Request",
|
|
752
|
+
tabResponse: "Response",
|
|
753
|
+
tabRaw: "Raw JSON",
|
|
754
|
+
statusConnecting: "Connecting to event stream...",
|
|
755
|
+
statusLive: "Live updates connected",
|
|
756
|
+
statusRetrying: "Event stream reconnecting...",
|
|
757
|
+
unknownTime: "Unknown time",
|
|
758
|
+
noContent: "No content",
|
|
759
|
+
noMessages: "No messages recorded.",
|
|
760
|
+
noToolSchemas: "No tool schemas were sent with this request.",
|
|
761
|
+
toolSchemas: "Tool Schemas ({count})",
|
|
762
|
+
noToolCalls: "No tool calls in this response.",
|
|
763
|
+
systemPrompt: "System Prompt",
|
|
764
|
+
noSystemPrompt: "No explicit system prompt found.",
|
|
765
|
+
messages: "Messages",
|
|
766
|
+
tools: "Tools",
|
|
767
|
+
requestUnavailable: "Request payload is not available.",
|
|
768
|
+
responseUnavailable: "Response has not been logged yet.",
|
|
769
|
+
responseText: "Text",
|
|
770
|
+
noAssistantText: "No assistant text in this response.",
|
|
771
|
+
responseThinking: "Thinking",
|
|
772
|
+
noThinking: "No thinking trace recorded.",
|
|
773
|
+
responseToolCalls: "Tool Calls",
|
|
774
|
+
responseMetrics: "Response Metrics",
|
|
775
|
+
inputTokens: "Input Tokens",
|
|
776
|
+
outputTokens: "Output Tokens",
|
|
777
|
+
responseError: "Error: {message}",
|
|
778
|
+
loadingRequestPayload: "Loading request payload...",
|
|
779
|
+
loadingResponsePayload: "Loading response payload...",
|
|
780
|
+
loadingRawJson: "Loading raw JSON...",
|
|
781
|
+
bootSessionsError: "Failed to load sessions from the debug viewer API.",
|
|
782
|
+
bootTimelineError: "The timeline could not be loaded.",
|
|
783
|
+
interactionLoadError: "Failed to load interaction: {message}",
|
|
784
|
+
stateError: "error",
|
|
785
|
+
statePending: "pending",
|
|
786
|
+
stateComplete: "complete",
|
|
787
|
+
roleSystem: "system",
|
|
788
|
+
roleUser: "user",
|
|
789
|
+
roleAssistant: "assistant",
|
|
790
|
+
roleTool: "tool",
|
|
791
|
+
roleUnknown: "unknown",
|
|
792
|
+
},
|
|
793
|
+
zh: {
|
|
794
|
+
documentTitle: "BareAgent 调试查看器",
|
|
795
|
+
pageTitle: "BareAgent 调试查看器",
|
|
796
|
+
pageSubtitle: "查看已保存的请求与响应载荷,展开单次交互详情,并通过 Server-Sent Events 观察实时更新。",
|
|
797
|
+
localeSwitcherLabel: "切换语言",
|
|
798
|
+
sessionsTitle: "会话",
|
|
799
|
+
sessionsSubtitle: "已记录的调试时间线",
|
|
800
|
+
sessionsFooterNote: "切换左侧会话后,右侧时间线会同步刷新。",
|
|
801
|
+
sessionIndexLoading: "正在加载会话索引...",
|
|
802
|
+
sessionsEmpty: "当前还没有记录任何调试会话。",
|
|
803
|
+
clickToLoad: "点击加载",
|
|
804
|
+
noCachedTimeline: "暂无缓存时间线",
|
|
805
|
+
latestSeq: "最新 #{seq}",
|
|
806
|
+
timelineTitle: "交互",
|
|
807
|
+
timelineTitleForSession: "{sessionId} 的交互",
|
|
808
|
+
timelineSubtitleIdle: "选择一个会话以查看它的时间线。",
|
|
809
|
+
timelineSubtitleActive: "点击卡片可展开查看请求、响应和原始 JSON 载荷。",
|
|
810
|
+
timelineSubtitleLoadError: "无法从调试查看器 API 加载该会话的时间线。",
|
|
811
|
+
timelineEmpty: "选择会话后即可在这里查看时间线。",
|
|
812
|
+
noSessionSelected: "尚未选择会话",
|
|
813
|
+
loadingInteractions: "正在加载交互...",
|
|
814
|
+
noInteractions: "该会话下没有交互记录。",
|
|
815
|
+
sessionLoadError: "加载会话时间线失败:{message}",
|
|
816
|
+
summaryTokens: "Token",
|
|
817
|
+
summaryDuration: "耗时",
|
|
818
|
+
summaryToolCalls: "工具调用",
|
|
819
|
+
summaryMessages: "消息",
|
|
820
|
+
tabRequest: "请求",
|
|
821
|
+
tabResponse: "响应",
|
|
822
|
+
tabRaw: "原始 JSON",
|
|
823
|
+
statusConnecting: "正在连接事件流...",
|
|
824
|
+
statusLive: "实时更新已连接",
|
|
825
|
+
statusRetrying: "事件流正在重连...",
|
|
826
|
+
unknownTime: "未知时间",
|
|
827
|
+
noContent: "没有内容",
|
|
828
|
+
noMessages: "没有记录任何消息。",
|
|
829
|
+
noToolSchemas: "这次请求没有携带任何工具 Schema。",
|
|
830
|
+
toolSchemas: "工具 Schema({count})",
|
|
831
|
+
noToolCalls: "这次响应没有工具调用。",
|
|
832
|
+
systemPrompt: "系统提示词",
|
|
833
|
+
noSystemPrompt: "未找到显式系统提示词。",
|
|
834
|
+
messages: "消息",
|
|
835
|
+
tools: "工具",
|
|
836
|
+
requestUnavailable: "请求载荷不可用。",
|
|
837
|
+
responseUnavailable: "响应尚未记录。",
|
|
838
|
+
responseText: "文本",
|
|
839
|
+
noAssistantText: "这次响应没有助手文本。",
|
|
840
|
+
responseThinking: "思考",
|
|
841
|
+
noThinking: "没有记录思考轨迹。",
|
|
842
|
+
responseToolCalls: "工具调用",
|
|
843
|
+
responseMetrics: "响应指标",
|
|
844
|
+
inputTokens: "输入 Token",
|
|
845
|
+
outputTokens: "输出 Token",
|
|
846
|
+
responseError: "错误:{message}",
|
|
847
|
+
loadingRequestPayload: "正在加载请求载荷...",
|
|
848
|
+
loadingResponsePayload: "正在加载响应载荷...",
|
|
849
|
+
loadingRawJson: "正在加载原始 JSON...",
|
|
850
|
+
bootSessionsError: "无法从调试查看器 API 加载会话。",
|
|
851
|
+
bootTimelineError: "时间线加载失败。",
|
|
852
|
+
interactionLoadError: "加载交互失败:{message}",
|
|
853
|
+
stateError: "错误",
|
|
854
|
+
statePending: "等待中",
|
|
855
|
+
stateComplete: "完成",
|
|
856
|
+
roleSystem: "系统",
|
|
857
|
+
roleUser: "用户",
|
|
858
|
+
roleAssistant: "助手",
|
|
859
|
+
roleTool: "工具",
|
|
860
|
+
roleUnknown: "未知",
|
|
861
|
+
},
|
|
862
|
+
};
|
|
863
|
+
|
|
864
|
+
function normalizeLocale(locale) {
|
|
865
|
+
if (typeof locale !== "string") {
|
|
866
|
+
return null;
|
|
867
|
+
}
|
|
868
|
+
const normalized = locale.toLowerCase();
|
|
869
|
+
if (normalized.startsWith("zh")) {
|
|
870
|
+
return "zh";
|
|
871
|
+
}
|
|
872
|
+
if (normalized.startsWith("en")) {
|
|
873
|
+
return "en";
|
|
874
|
+
}
|
|
875
|
+
return null;
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
function resolveInitialLocale() {
|
|
879
|
+
try {
|
|
880
|
+
const stored = localStorage.getItem(LOCALE_STORAGE_KEY);
|
|
881
|
+
if (stored) {
|
|
882
|
+
const resolved = normalizeLocale(stored);
|
|
883
|
+
if (resolved) {
|
|
884
|
+
return resolved;
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
} catch (_error) {
|
|
888
|
+
// Ignore localStorage access failures and fall back to browser preferences.
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
const browserLocales = Array.isArray(navigator.languages) && navigator.languages.length > 0
|
|
892
|
+
? navigator.languages
|
|
893
|
+
: [navigator.language];
|
|
894
|
+
for (const locale of browserLocales) {
|
|
895
|
+
const resolved = normalizeLocale(locale);
|
|
896
|
+
if (resolved) {
|
|
897
|
+
return resolved;
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
return "en";
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
const state = {
|
|
904
|
+
locale: resolveInitialLocale(),
|
|
905
|
+
sessions: [],
|
|
906
|
+
activeSessionId: null,
|
|
907
|
+
interactionsBySession: new Map(),
|
|
908
|
+
detailCache: new Map(),
|
|
909
|
+
expandedKey: null,
|
|
910
|
+
activeTabs: new Map(),
|
|
911
|
+
loadingSession: false,
|
|
912
|
+
sessionsRequestId: 0,
|
|
913
|
+
sessionRequestIds: new Map(),
|
|
914
|
+
sessionErrors: new Map(),
|
|
915
|
+
detailRequestIds: new Map(),
|
|
916
|
+
connectionStatusKind: "connecting",
|
|
917
|
+
bootError: false,
|
|
918
|
+
booting: true,
|
|
919
|
+
};
|
|
920
|
+
|
|
921
|
+
const elements = {
|
|
922
|
+
pageTitle: document.getElementById("page-title"),
|
|
923
|
+
pageSubtitle: document.getElementById("page-subtitle"),
|
|
924
|
+
sessionsTitle: document.getElementById("sessions-title"),
|
|
925
|
+
sessionsSubtitle: document.getElementById("sessions-subtitle"),
|
|
926
|
+
sessionsFooterNote: document.getElementById("sessions-footer-note"),
|
|
927
|
+
sessionList: document.getElementById("session-list"),
|
|
928
|
+
sessionCount: document.getElementById("session-count"),
|
|
929
|
+
timeline: document.getElementById("timeline"),
|
|
930
|
+
timelineTitle: document.getElementById("timeline-title"),
|
|
931
|
+
timelineSubtitle: document.getElementById("timeline-subtitle"),
|
|
932
|
+
timelineSessionChip: document.getElementById("timeline-session-chip"),
|
|
933
|
+
timelineCountChip: document.getElementById("timeline-count-chip"),
|
|
934
|
+
localeSwitch: document.getElementById("locale-switch"),
|
|
935
|
+
localeButtons: Array.from(document.querySelectorAll(".locale-button")),
|
|
936
|
+
statusText: document.getElementById("status-text"),
|
|
937
|
+
statusDot: document.getElementById("status-dot"),
|
|
938
|
+
sessionTemplate: document.getElementById("session-template"),
|
|
939
|
+
interactionTemplate: document.getElementById("interaction-template"),
|
|
940
|
+
};
|
|
941
|
+
|
|
942
|
+
function t(key, params = {}) {
|
|
943
|
+
const active = translations[state.locale] || translations.en;
|
|
944
|
+
const fallback = translations.en;
|
|
945
|
+
const template = active[key] || fallback[key] || key;
|
|
946
|
+
return template.replace(/\{(\w+)\}/g, (_match, name) => String(params[name] ?? ""));
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
function currentLanguageTag() {
|
|
950
|
+
return state.locale === "zh" ? "zh-CN" : "en";
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
function persistLocale(locale) {
|
|
954
|
+
try {
|
|
955
|
+
localStorage.setItem(LOCALE_STORAGE_KEY, locale);
|
|
956
|
+
} catch (_error) {
|
|
957
|
+
// Ignore localStorage access failures.
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
function formatLoadedCount(count) {
|
|
962
|
+
return state.locale === "zh" ? `已加载 ${count} 个` : `${count} loaded`;
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
function formatInteractionCount(count) {
|
|
966
|
+
return state.locale === "zh"
|
|
967
|
+
? `${count} 次交互`
|
|
968
|
+
: `${count} interaction${count === 1 ? "" : "s"}`;
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
function formatLatestSeq(seq) {
|
|
972
|
+
return t("latestSeq", { seq });
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
function formatMessageToolSummary(messageCount, toolCount) {
|
|
976
|
+
return state.locale === "zh"
|
|
977
|
+
? `${messageCount} 条消息 / ${toolCount} 个工具`
|
|
978
|
+
: `${messageCount} msg / ${toolCount} tools`;
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
function formatRoleLabel(role) {
|
|
982
|
+
const normalized = String(role || "unknown").toLowerCase();
|
|
983
|
+
const key = `role${normalized.charAt(0).toUpperCase()}${normalized.slice(1)}`;
|
|
984
|
+
return translations[state.locale][key] || translations.en[key] || translations[state.locale].roleUnknown;
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
function formatMessageOrdinal(index) {
|
|
988
|
+
return state.locale === "zh" ? `消息 ${index}` : `Message ${index}`;
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
function formatToolCallLabel(toolName, index) {
|
|
992
|
+
return state.locale === "zh"
|
|
993
|
+
? `${toolName}(#${index})`
|
|
994
|
+
: `${toolName} (#${index})`;
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
function applyLocale() {
|
|
998
|
+
document.documentElement.lang = currentLanguageTag();
|
|
999
|
+
document.title = t("documentTitle");
|
|
1000
|
+
elements.pageTitle.textContent = t("pageTitle");
|
|
1001
|
+
elements.pageSubtitle.textContent = t("pageSubtitle");
|
|
1002
|
+
elements.sessionsTitle.textContent = t("sessionsTitle");
|
|
1003
|
+
elements.sessionsSubtitle.textContent = t("sessionsSubtitle");
|
|
1004
|
+
elements.sessionsFooterNote.textContent = t("sessionsFooterNote");
|
|
1005
|
+
elements.localeSwitch.setAttribute("aria-label", t("localeSwitcherLabel"));
|
|
1006
|
+
elements.localeButtons.forEach((button) => {
|
|
1007
|
+
button.classList.toggle("active", button.dataset.locale === state.locale);
|
|
1008
|
+
});
|
|
1009
|
+
setConnectionStatus(state.connectionStatusKind);
|
|
1010
|
+
renderSessions();
|
|
1011
|
+
renderTimeline();
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
function setLocale(locale) {
|
|
1015
|
+
const normalized = normalizeLocale(locale) || "en";
|
|
1016
|
+
if (normalized === state.locale) {
|
|
1017
|
+
return;
|
|
1018
|
+
}
|
|
1019
|
+
state.locale = normalized;
|
|
1020
|
+
persistLocale(normalized);
|
|
1021
|
+
applyLocale();
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
function detailKey(sessionId, seq) {
|
|
1025
|
+
return `${sessionId}:${seq}`;
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
async function fetchJson(path) {
|
|
1029
|
+
const response = await fetch(path, {
|
|
1030
|
+
headers: { Accept: "application/json" },
|
|
1031
|
+
});
|
|
1032
|
+
if (!response.ok) {
|
|
1033
|
+
throw new Error(`${response.status} ${response.statusText}`);
|
|
1034
|
+
}
|
|
1035
|
+
return response.json();
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
function formatTimestamp(value) {
|
|
1039
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
1040
|
+
return new Date(value * 1000).toLocaleString(currentLanguageTag());
|
|
1041
|
+
}
|
|
1042
|
+
if (typeof value === "string" && value.trim()) {
|
|
1043
|
+
const parsed = Date.parse(value);
|
|
1044
|
+
if (!Number.isNaN(parsed)) {
|
|
1045
|
+
return new Date(parsed).toLocaleString(currentLanguageTag());
|
|
1046
|
+
}
|
|
1047
|
+
return value;
|
|
1048
|
+
}
|
|
1049
|
+
return t("unknownTime");
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
function formatDuration(value) {
|
|
1053
|
+
const numeric = Number(value);
|
|
1054
|
+
if (!Number.isFinite(numeric) || numeric < 0) {
|
|
1055
|
+
return "--";
|
|
1056
|
+
}
|
|
1057
|
+
if (numeric >= 1000) {
|
|
1058
|
+
return state.locale === "zh"
|
|
1059
|
+
? `${(numeric / 1000).toFixed(2)} 秒`
|
|
1060
|
+
: `${(numeric / 1000).toFixed(2)} s`;
|
|
1061
|
+
}
|
|
1062
|
+
return state.locale === "zh"
|
|
1063
|
+
? `${numeric.toFixed(2)} 毫秒`
|
|
1064
|
+
: `${numeric.toFixed(2)} ms`;
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
function formatTokens(inputTokens, outputTokens) {
|
|
1068
|
+
const input = Number(inputTokens);
|
|
1069
|
+
const output = Number(outputTokens);
|
|
1070
|
+
if (!Number.isFinite(input) && !Number.isFinite(output)) {
|
|
1071
|
+
return "--";
|
|
1072
|
+
}
|
|
1073
|
+
return state.locale === "zh"
|
|
1074
|
+
? `${Number.isFinite(input) ? input : 0} 输入 / ${Number.isFinite(output) ? output : 0} 输出`
|
|
1075
|
+
: `${Number.isFinite(input) ? input : 0} in / ${Number.isFinite(output) ? output : 0} out`;
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
function normalizeSummary(entry) {
|
|
1079
|
+
return {
|
|
1080
|
+
seq: Number(entry?.seq ?? 0),
|
|
1081
|
+
timestamp: entry?.timestamp ?? null,
|
|
1082
|
+
message_count: Number(entry?.message_count ?? 0),
|
|
1083
|
+
tool_count: Number(entry?.tool_count ?? 0),
|
|
1084
|
+
input_tokens: Number(entry?.input_tokens ?? 0),
|
|
1085
|
+
output_tokens: Number(entry?.output_tokens ?? 0),
|
|
1086
|
+
duration_ms: Number(entry?.duration_ms ?? 0),
|
|
1087
|
+
tool_call_count: Number(entry?.tool_call_count ?? 0),
|
|
1088
|
+
has_error: Boolean(entry?.has_error),
|
|
1089
|
+
has_response: Object.prototype.hasOwnProperty.call(entry || {}, "input_tokens"),
|
|
1090
|
+
};
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
function getSessionSummaries(sessionId) {
|
|
1094
|
+
return state.interactionsBySession.get(sessionId) || [];
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
function getSessionMeta(sessionId) {
|
|
1098
|
+
const summaries = getSessionSummaries(sessionId);
|
|
1099
|
+
if (summaries.length === 0) {
|
|
1100
|
+
return {
|
|
1101
|
+
countText: t("clickToLoad"),
|
|
1102
|
+
latestText: t("noCachedTimeline"),
|
|
1103
|
+
};
|
|
1104
|
+
}
|
|
1105
|
+
const latest = summaries[summaries.length - 1];
|
|
1106
|
+
return {
|
|
1107
|
+
countText: formatInteractionCount(summaries.length),
|
|
1108
|
+
latestText: formatLatestSeq(latest.seq),
|
|
1109
|
+
};
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1112
|
+
function setConnectionStatus(kind) {
|
|
1113
|
+
state.connectionStatusKind = kind;
|
|
1114
|
+
const labels = {
|
|
1115
|
+
connecting: t("statusConnecting"),
|
|
1116
|
+
live: t("statusLive"),
|
|
1117
|
+
retrying: t("statusRetrying"),
|
|
1118
|
+
};
|
|
1119
|
+
elements.statusText.textContent = labels[kind] || labels.connecting;
|
|
1120
|
+
const colors = {
|
|
1121
|
+
connecting: "#f9e2af",
|
|
1122
|
+
live: "#a6e3a1",
|
|
1123
|
+
retrying: "#f38ba8",
|
|
1124
|
+
};
|
|
1125
|
+
const color = colors[kind] || colors.connecting;
|
|
1126
|
+
elements.statusDot.style.background = color;
|
|
1127
|
+
elements.statusDot.style.boxShadow = `0 0 16px ${color}55`;
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1130
|
+
function clearElement(node) {
|
|
1131
|
+
while (node.firstChild) {
|
|
1132
|
+
node.removeChild(node.firstChild);
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
function createTextBlock(text, className = "code-block") {
|
|
1137
|
+
const pre = document.createElement("pre");
|
|
1138
|
+
pre.className = className;
|
|
1139
|
+
pre.textContent = String(text ?? "");
|
|
1140
|
+
return pre;
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
function createJsonBlock(value) {
|
|
1144
|
+
return createTextBlock(JSON.stringify(value, null, 2), "json-block");
|
|
1145
|
+
}
|
|
1146
|
+
|
|
1147
|
+
function createNote(text, className) {
|
|
1148
|
+
const div = document.createElement("div");
|
|
1149
|
+
div.className = className;
|
|
1150
|
+
div.textContent = text;
|
|
1151
|
+
return div;
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
function createDetailBlock(title, bodyNode) {
|
|
1155
|
+
const section = document.createElement("section");
|
|
1156
|
+
section.className = "detail-block";
|
|
1157
|
+
const heading = document.createElement("h3");
|
|
1158
|
+
heading.textContent = title;
|
|
1159
|
+
section.appendChild(heading);
|
|
1160
|
+
section.appendChild(bodyNode);
|
|
1161
|
+
return section;
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1164
|
+
function createRoleBadge(role) {
|
|
1165
|
+
const badge = document.createElement("span");
|
|
1166
|
+
const normalized = String(role || "unknown").replace(/[^a-z0-9_]+/gi, "_");
|
|
1167
|
+
badge.className = `role-badge role-${normalized}`;
|
|
1168
|
+
badge.textContent = formatRoleLabel(role);
|
|
1169
|
+
return badge;
|
|
1170
|
+
}
|
|
1171
|
+
|
|
1172
|
+
function appendContentValue(container, value) {
|
|
1173
|
+
if (value === null || value === undefined || value === "") {
|
|
1174
|
+
container.appendChild(createNote(t("noContent"), "loading-note"));
|
|
1175
|
+
return;
|
|
1176
|
+
}
|
|
1177
|
+
|
|
1178
|
+
if (typeof value === "string") {
|
|
1179
|
+
container.appendChild(createTextBlock(value));
|
|
1180
|
+
return;
|
|
1181
|
+
}
|
|
1182
|
+
|
|
1183
|
+
if (Array.isArray(value)) {
|
|
1184
|
+
if (value.length === 0) {
|
|
1185
|
+
container.appendChild(createNote(t("noContent"), "loading-note"));
|
|
1186
|
+
return;
|
|
1187
|
+
}
|
|
1188
|
+
value.forEach((item) => {
|
|
1189
|
+
if (item && typeof item === "object" && item.type === "text") {
|
|
1190
|
+
container.appendChild(createTextBlock(item.text || ""));
|
|
1191
|
+
return;
|
|
1192
|
+
}
|
|
1193
|
+
container.appendChild(createJsonBlock(item));
|
|
1194
|
+
});
|
|
1195
|
+
return;
|
|
1196
|
+
}
|
|
1197
|
+
|
|
1198
|
+
container.appendChild(createJsonBlock(value));
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1201
|
+
function flattenContentToText(content) {
|
|
1202
|
+
if (content === null || content === undefined) {
|
|
1203
|
+
return "";
|
|
1204
|
+
}
|
|
1205
|
+
if (typeof content === "string") {
|
|
1206
|
+
return content;
|
|
1207
|
+
}
|
|
1208
|
+
if (Array.isArray(content)) {
|
|
1209
|
+
return content
|
|
1210
|
+
.map((item) => {
|
|
1211
|
+
if (item && typeof item === "object" && item.type === "text") {
|
|
1212
|
+
return String(item.text || "");
|
|
1213
|
+
}
|
|
1214
|
+
return JSON.stringify(item, null, 2);
|
|
1215
|
+
})
|
|
1216
|
+
.filter(Boolean)
|
|
1217
|
+
.join("\n\n");
|
|
1218
|
+
}
|
|
1219
|
+
return JSON.stringify(content, null, 2);
|
|
1220
|
+
}
|
|
1221
|
+
|
|
1222
|
+
function collectSystemPrompt(messages) {
|
|
1223
|
+
const systemTexts = (Array.isArray(messages) ? messages : [])
|
|
1224
|
+
.filter((message) => message && message.role === "system")
|
|
1225
|
+
.map((message) => flattenContentToText(message.content))
|
|
1226
|
+
.filter(Boolean);
|
|
1227
|
+
return systemTexts.join("\n\n");
|
|
1228
|
+
}
|
|
1229
|
+
|
|
1230
|
+
function renderMessages(messages) {
|
|
1231
|
+
const list = document.createElement("div");
|
|
1232
|
+
list.className = "message-list";
|
|
1233
|
+
|
|
1234
|
+
if (!Array.isArray(messages) || messages.length === 0) {
|
|
1235
|
+
list.appendChild(createNote(t("noMessages"), "loading-note"));
|
|
1236
|
+
return list;
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1239
|
+
messages.forEach((message, index) => {
|
|
1240
|
+
const item = document.createElement("article");
|
|
1241
|
+
item.className = "message-item";
|
|
1242
|
+
|
|
1243
|
+
const head = document.createElement("div");
|
|
1244
|
+
head.className = "message-head";
|
|
1245
|
+
head.appendChild(createRoleBadge(message?.role || "unknown"));
|
|
1246
|
+
|
|
1247
|
+
const ordinal = document.createElement("span");
|
|
1248
|
+
ordinal.className = "card-time";
|
|
1249
|
+
ordinal.textContent = formatMessageOrdinal(index + 1);
|
|
1250
|
+
head.appendChild(ordinal);
|
|
1251
|
+
|
|
1252
|
+
const body = document.createElement("div");
|
|
1253
|
+
body.className = "message-body";
|
|
1254
|
+
appendContentValue(body, message?.content);
|
|
1255
|
+
|
|
1256
|
+
item.appendChild(head);
|
|
1257
|
+
item.appendChild(body);
|
|
1258
|
+
list.appendChild(item);
|
|
1259
|
+
});
|
|
1260
|
+
|
|
1261
|
+
return list;
|
|
1262
|
+
}
|
|
1263
|
+
|
|
1264
|
+
function renderTools(tools) {
|
|
1265
|
+
const wrapper = document.createElement("div");
|
|
1266
|
+
wrapper.className = "tools-box";
|
|
1267
|
+
|
|
1268
|
+
if (!Array.isArray(tools) || tools.length === 0) {
|
|
1269
|
+
wrapper.appendChild(createNote(t("noToolSchemas"), "loading-note"));
|
|
1270
|
+
return wrapper;
|
|
1271
|
+
}
|
|
1272
|
+
|
|
1273
|
+
const details = document.createElement("details");
|
|
1274
|
+
const summary = document.createElement("summary");
|
|
1275
|
+
summary.textContent = t("toolSchemas", { count: tools.length });
|
|
1276
|
+
details.appendChild(summary);
|
|
1277
|
+
details.appendChild(createJsonBlock(tools));
|
|
1278
|
+
wrapper.appendChild(details);
|
|
1279
|
+
return wrapper;
|
|
1280
|
+
}
|
|
1281
|
+
|
|
1282
|
+
function renderToolCalls(toolCalls) {
|
|
1283
|
+
const list = document.createElement("div");
|
|
1284
|
+
list.className = "tool-call-list";
|
|
1285
|
+
|
|
1286
|
+
if (!Array.isArray(toolCalls) || toolCalls.length === 0) {
|
|
1287
|
+
list.appendChild(createNote(t("noToolCalls"), "loading-note"));
|
|
1288
|
+
return list;
|
|
1289
|
+
}
|
|
1290
|
+
|
|
1291
|
+
toolCalls.forEach((toolCall, index) => {
|
|
1292
|
+
const item = document.createElement("article");
|
|
1293
|
+
item.className = "tool-call-item";
|
|
1294
|
+
|
|
1295
|
+
const head = document.createElement("div");
|
|
1296
|
+
head.className = "tool-call-head";
|
|
1297
|
+
head.appendChild(createRoleBadge("tool"));
|
|
1298
|
+
|
|
1299
|
+
const label = document.createElement("span");
|
|
1300
|
+
label.className = "card-time";
|
|
1301
|
+
const toolName = toolCall?.name || `tool_${index + 1}`;
|
|
1302
|
+
label.textContent = formatToolCallLabel(toolName, index + 1);
|
|
1303
|
+
head.appendChild(label);
|
|
1304
|
+
|
|
1305
|
+
item.appendChild(head);
|
|
1306
|
+
item.appendChild(createJsonBlock(toolCall));
|
|
1307
|
+
list.appendChild(item);
|
|
1308
|
+
});
|
|
1309
|
+
|
|
1310
|
+
return list;
|
|
1311
|
+
}
|
|
1312
|
+
|
|
1313
|
+
function renderRequestPanel(panel, interaction) {
|
|
1314
|
+
clearElement(panel);
|
|
1315
|
+
const request = interaction?.request;
|
|
1316
|
+
|
|
1317
|
+
if (!request) {
|
|
1318
|
+
panel.appendChild(createNote(t("requestUnavailable"), "error-note"));
|
|
1319
|
+
return;
|
|
1320
|
+
}
|
|
1321
|
+
|
|
1322
|
+
const systemPrompt = collectSystemPrompt(request.messages);
|
|
1323
|
+
panel.appendChild(
|
|
1324
|
+
createDetailBlock(
|
|
1325
|
+
t("systemPrompt"),
|
|
1326
|
+
systemPrompt ? createTextBlock(systemPrompt) : createNote(t("noSystemPrompt"), "loading-note")
|
|
1327
|
+
)
|
|
1328
|
+
);
|
|
1329
|
+
panel.appendChild(createDetailBlock(t("messages"), renderMessages(request.messages)));
|
|
1330
|
+
panel.appendChild(createDetailBlock(t("tools"), renderTools(request.tools)));
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1333
|
+
function renderResponsePanel(panel, interaction, summary) {
|
|
1334
|
+
clearElement(panel);
|
|
1335
|
+
const response = interaction?.response;
|
|
1336
|
+
|
|
1337
|
+
if (interaction?.viewer_error_message) {
|
|
1338
|
+
panel.appendChild(
|
|
1339
|
+
createNote(
|
|
1340
|
+
t("interactionLoadError", { message: interaction.viewer_error_message }),
|
|
1341
|
+
"error-note"
|
|
1342
|
+
)
|
|
1343
|
+
);
|
|
1344
|
+
return;
|
|
1345
|
+
}
|
|
1346
|
+
|
|
1347
|
+
if (!response) {
|
|
1348
|
+
panel.appendChild(createNote(t("responseUnavailable"), "loading-note"));
|
|
1349
|
+
return;
|
|
1350
|
+
}
|
|
1351
|
+
|
|
1352
|
+
panel.appendChild(
|
|
1353
|
+
createDetailBlock(
|
|
1354
|
+
t("responseText"),
|
|
1355
|
+
response.text ? createTextBlock(response.text) : createNote(t("noAssistantText"), "loading-note")
|
|
1356
|
+
)
|
|
1357
|
+
);
|
|
1358
|
+
|
|
1359
|
+
panel.appendChild(
|
|
1360
|
+
createDetailBlock(
|
|
1361
|
+
t("responseThinking"),
|
|
1362
|
+
response.thinking
|
|
1363
|
+
? createTextBlock(response.thinking, "code-block thinking-block")
|
|
1364
|
+
: createNote(t("noThinking"), "loading-note")
|
|
1365
|
+
)
|
|
1366
|
+
);
|
|
1367
|
+
|
|
1368
|
+
panel.appendChild(createDetailBlock(t("responseToolCalls"), renderToolCalls(response.tool_calls)));
|
|
1369
|
+
|
|
1370
|
+
const stats = document.createElement("div");
|
|
1371
|
+
stats.className = "message-list";
|
|
1372
|
+
const metrics = [
|
|
1373
|
+
[t("inputTokens"), summary.input_tokens],
|
|
1374
|
+
[t("outputTokens"), summary.output_tokens],
|
|
1375
|
+
[t("summaryDuration"), formatDuration(summary.duration_ms)],
|
|
1376
|
+
];
|
|
1377
|
+
metrics.forEach(([label, value]) => {
|
|
1378
|
+
const row = document.createElement("article");
|
|
1379
|
+
row.className = "message-item";
|
|
1380
|
+
const head = document.createElement("div");
|
|
1381
|
+
head.className = "message-head";
|
|
1382
|
+
const heading = document.createElement("strong");
|
|
1383
|
+
heading.textContent = label;
|
|
1384
|
+
head.appendChild(heading);
|
|
1385
|
+
row.appendChild(head);
|
|
1386
|
+
row.appendChild(createTextBlock(String(value)));
|
|
1387
|
+
stats.appendChild(row);
|
|
1388
|
+
});
|
|
1389
|
+
panel.appendChild(createDetailBlock(t("responseMetrics"), stats));
|
|
1390
|
+
|
|
1391
|
+
if (response.error) {
|
|
1392
|
+
panel.appendChild(createNote(t("responseError", { message: response.error }), "error-note"));
|
|
1393
|
+
}
|
|
1394
|
+
}
|
|
1395
|
+
|
|
1396
|
+
function renderRawPanel(panel, interaction) {
|
|
1397
|
+
panel.textContent = JSON.stringify(interaction, null, 2);
|
|
1398
|
+
}
|
|
1399
|
+
|
|
1400
|
+
function setActiveTab(key, tabName) {
|
|
1401
|
+
state.activeTabs.set(key, tabName);
|
|
1402
|
+
renderTimeline();
|
|
1403
|
+
}
|
|
1404
|
+
|
|
1405
|
+
function getCardState(summary) {
|
|
1406
|
+
if (summary.has_error) {
|
|
1407
|
+
return t("stateError");
|
|
1408
|
+
}
|
|
1409
|
+
if (!summary.has_response) {
|
|
1410
|
+
return t("statePending");
|
|
1411
|
+
}
|
|
1412
|
+
return t("stateComplete");
|
|
1413
|
+
}
|
|
1414
|
+
|
|
1415
|
+
function updateTimelineHeader() {
|
|
1416
|
+
if (!state.activeSessionId) {
|
|
1417
|
+
elements.timelineTitle.textContent = t("timelineTitle");
|
|
1418
|
+
elements.timelineSubtitle.textContent = t("timelineSubtitleIdle");
|
|
1419
|
+
elements.timelineSessionChip.textContent = t("noSessionSelected");
|
|
1420
|
+
elements.timelineCountChip.textContent = formatInteractionCount(0);
|
|
1421
|
+
return;
|
|
1422
|
+
}
|
|
1423
|
+
|
|
1424
|
+
const summaries = getSessionSummaries(state.activeSessionId);
|
|
1425
|
+
const sessionError = state.sessionErrors.get(state.activeSessionId);
|
|
1426
|
+
elements.timelineTitle.textContent = t("timelineTitleForSession", { sessionId: state.activeSessionId });
|
|
1427
|
+
elements.timelineSubtitle.textContent = sessionError
|
|
1428
|
+
? t("timelineSubtitleLoadError")
|
|
1429
|
+
: t("timelineSubtitleActive");
|
|
1430
|
+
elements.timelineSessionChip.textContent = state.activeSessionId;
|
|
1431
|
+
elements.timelineCountChip.textContent = formatInteractionCount(summaries.length);
|
|
1432
|
+
}
|
|
1433
|
+
|
|
1434
|
+
function renderSessions() {
|
|
1435
|
+
clearElement(elements.sessionList);
|
|
1436
|
+
elements.sessionCount.textContent = formatLoadedCount(state.sessions.length);
|
|
1437
|
+
|
|
1438
|
+
if (state.bootError && state.sessions.length === 0) {
|
|
1439
|
+
elements.sessionList.appendChild(createNote(t("bootSessionsError"), "error-note"));
|
|
1440
|
+
return;
|
|
1441
|
+
}
|
|
1442
|
+
|
|
1443
|
+
if (state.booting && state.sessions.length === 0) {
|
|
1444
|
+
elements.sessionList.appendChild(createNote(t("sessionIndexLoading"), "empty-state"));
|
|
1445
|
+
return;
|
|
1446
|
+
}
|
|
1447
|
+
|
|
1448
|
+
if (state.sessions.length === 0) {
|
|
1449
|
+
elements.sessionList.appendChild(createNote(t("sessionsEmpty"), "empty-state"));
|
|
1450
|
+
return;
|
|
1451
|
+
}
|
|
1452
|
+
|
|
1453
|
+
state.sessions.forEach((sessionId) => {
|
|
1454
|
+
const fragment = elements.sessionTemplate.content.cloneNode(true);
|
|
1455
|
+
const button = fragment.querySelector(".session-button");
|
|
1456
|
+
const name = fragment.querySelector(".session-name");
|
|
1457
|
+
const count = fragment.querySelector(".session-count");
|
|
1458
|
+
const latest = fragment.querySelector(".session-latest");
|
|
1459
|
+
const meta = getSessionMeta(sessionId);
|
|
1460
|
+
|
|
1461
|
+
name.textContent = sessionId;
|
|
1462
|
+
count.textContent = meta.countText;
|
|
1463
|
+
latest.textContent = meta.latestText;
|
|
1464
|
+
|
|
1465
|
+
if (sessionId === state.activeSessionId) {
|
|
1466
|
+
button.classList.add("active");
|
|
1467
|
+
}
|
|
1468
|
+
|
|
1469
|
+
button.addEventListener("click", async () => {
|
|
1470
|
+
if (state.activeSessionId === sessionId) {
|
|
1471
|
+
return;
|
|
1472
|
+
}
|
|
1473
|
+
state.activeSessionId = sessionId;
|
|
1474
|
+
state.expandedKey = null;
|
|
1475
|
+
renderSessions();
|
|
1476
|
+
renderTimeline();
|
|
1477
|
+
await loadSession(sessionId);
|
|
1478
|
+
});
|
|
1479
|
+
|
|
1480
|
+
elements.sessionList.appendChild(fragment);
|
|
1481
|
+
});
|
|
1482
|
+
}
|
|
1483
|
+
|
|
1484
|
+
function renderTimeline() {
|
|
1485
|
+
updateTimelineHeader();
|
|
1486
|
+
clearElement(elements.timeline);
|
|
1487
|
+
|
|
1488
|
+
if (state.bootError && !state.activeSessionId) {
|
|
1489
|
+
elements.timeline.appendChild(createNote(t("bootTimelineError"), "error-note"));
|
|
1490
|
+
return;
|
|
1491
|
+
}
|
|
1492
|
+
|
|
1493
|
+
if (!state.activeSessionId) {
|
|
1494
|
+
elements.timeline.appendChild(createNote(t("timelineEmpty"), "empty-state"));
|
|
1495
|
+
return;
|
|
1496
|
+
}
|
|
1497
|
+
|
|
1498
|
+
const summaries = getSessionSummaries(state.activeSessionId);
|
|
1499
|
+
const sessionError = state.sessionErrors.get(state.activeSessionId);
|
|
1500
|
+
if (state.loadingSession && summaries.length === 0) {
|
|
1501
|
+
elements.timeline.appendChild(createNote(t("loadingInteractions"), "empty-state"));
|
|
1502
|
+
return;
|
|
1503
|
+
}
|
|
1504
|
+
|
|
1505
|
+
if (sessionError) {
|
|
1506
|
+
elements.timeline.appendChild(
|
|
1507
|
+
createNote(t("sessionLoadError", { message: sessionError }), "error-note")
|
|
1508
|
+
);
|
|
1509
|
+
return;
|
|
1510
|
+
}
|
|
1511
|
+
|
|
1512
|
+
if (summaries.length === 0) {
|
|
1513
|
+
elements.timeline.appendChild(createNote(t("noInteractions"), "empty-state"));
|
|
1514
|
+
return;
|
|
1515
|
+
}
|
|
1516
|
+
|
|
1517
|
+
summaries.forEach((summary) => {
|
|
1518
|
+
const fragment = elements.interactionTemplate.content.cloneNode(true);
|
|
1519
|
+
const card = fragment.querySelector(".interaction-card");
|
|
1520
|
+
const toggle = fragment.querySelector(".card-toggle");
|
|
1521
|
+
const stateChip = fragment.querySelector(".card-state");
|
|
1522
|
+
const seqText = `#${summary.seq}`;
|
|
1523
|
+
const key = detailKey(state.activeSessionId, summary.seq);
|
|
1524
|
+
|
|
1525
|
+
fragment.querySelector(".card-seq").textContent = seqText;
|
|
1526
|
+
fragment.querySelector(".card-time").textContent = formatTimestamp(summary.timestamp);
|
|
1527
|
+
fragment.querySelector(".summary-tokens").textContent = formatTokens(summary.input_tokens, summary.output_tokens);
|
|
1528
|
+
fragment.querySelector(".summary-duration").textContent = formatDuration(summary.duration_ms);
|
|
1529
|
+
fragment.querySelector(".summary-tool-calls").textContent = String(summary.tool_call_count);
|
|
1530
|
+
fragment.querySelector(".summary-messages").textContent = formatMessageToolSummary(
|
|
1531
|
+
summary.message_count,
|
|
1532
|
+
summary.tool_count
|
|
1533
|
+
);
|
|
1534
|
+
fragment.querySelectorAll(".summary-label")[0].textContent = t("summaryTokens");
|
|
1535
|
+
fragment.querySelectorAll(".summary-label")[1].textContent = t("summaryDuration");
|
|
1536
|
+
fragment.querySelectorAll(".summary-label")[2].textContent = t("summaryToolCalls");
|
|
1537
|
+
fragment.querySelectorAll(".summary-label")[3].textContent = t("summaryMessages");
|
|
1538
|
+
fragment.querySelector('[data-tab="request"]').textContent = t("tabRequest");
|
|
1539
|
+
fragment.querySelector('[data-tab="response"]').textContent = t("tabResponse");
|
|
1540
|
+
fragment.querySelector('[data-tab="raw"]').textContent = t("tabRaw");
|
|
1541
|
+
|
|
1542
|
+
stateChip.textContent = getCardState(summary);
|
|
1543
|
+
|
|
1544
|
+
if (key === state.expandedKey) {
|
|
1545
|
+
card.classList.add("expanded");
|
|
1546
|
+
}
|
|
1547
|
+
|
|
1548
|
+
toggle.addEventListener("click", async () => {
|
|
1549
|
+
if (state.expandedKey === key) {
|
|
1550
|
+
state.expandedKey = null;
|
|
1551
|
+
renderTimeline();
|
|
1552
|
+
return;
|
|
1553
|
+
}
|
|
1554
|
+
state.expandedKey = key;
|
|
1555
|
+
if (!state.activeTabs.has(key)) {
|
|
1556
|
+
state.activeTabs.set(key, "request");
|
|
1557
|
+
}
|
|
1558
|
+
renderTimeline();
|
|
1559
|
+
await loadInteractionDetail(state.activeSessionId, summary.seq);
|
|
1560
|
+
});
|
|
1561
|
+
|
|
1562
|
+
const tabButtons = fragment.querySelectorAll(".tab-button");
|
|
1563
|
+
const tabPanels = fragment.querySelectorAll(".tab-panel");
|
|
1564
|
+
const activeTab = state.activeTabs.get(key) || "request";
|
|
1565
|
+
tabButtons.forEach((button) => {
|
|
1566
|
+
const isActive = button.dataset.tab === activeTab;
|
|
1567
|
+
button.classList.toggle("active", isActive);
|
|
1568
|
+
button.addEventListener("click", () => setActiveTab(key, button.dataset.tab));
|
|
1569
|
+
});
|
|
1570
|
+
tabPanels.forEach((panel) => {
|
|
1571
|
+
panel.classList.toggle("active", panel.dataset.panel === activeTab);
|
|
1572
|
+
});
|
|
1573
|
+
|
|
1574
|
+
if (key === state.expandedKey) {
|
|
1575
|
+
const requestPanel = fragment.querySelector(".request-panel");
|
|
1576
|
+
const responsePanel = fragment.querySelector(".response-panel");
|
|
1577
|
+
const rawPanel = fragment.querySelector(".raw-panel");
|
|
1578
|
+
const interaction = state.detailCache.get(key);
|
|
1579
|
+
|
|
1580
|
+
if (!interaction) {
|
|
1581
|
+
requestPanel.appendChild(createNote(t("loadingRequestPayload"), "loading-note"));
|
|
1582
|
+
responsePanel.appendChild(createNote(t("loadingResponsePayload"), "loading-note"));
|
|
1583
|
+
rawPanel.textContent = t("loadingRawJson");
|
|
1584
|
+
} else {
|
|
1585
|
+
renderRequestPanel(requestPanel, interaction);
|
|
1586
|
+
renderResponsePanel(responsePanel, interaction, summary);
|
|
1587
|
+
renderRawPanel(rawPanel, interaction);
|
|
1588
|
+
}
|
|
1589
|
+
}
|
|
1590
|
+
|
|
1591
|
+
elements.timeline.appendChild(fragment);
|
|
1592
|
+
});
|
|
1593
|
+
}
|
|
1594
|
+
|
|
1595
|
+
async function refreshSessions() {
|
|
1596
|
+
const requestId = ++state.sessionsRequestId;
|
|
1597
|
+
const sessions = await fetchJson("/api/sessions");
|
|
1598
|
+
if (requestId !== state.sessionsRequestId) {
|
|
1599
|
+
return;
|
|
1600
|
+
}
|
|
1601
|
+
|
|
1602
|
+
state.bootError = false;
|
|
1603
|
+
state.sessions = Array.isArray(sessions) ? sessions : [];
|
|
1604
|
+
|
|
1605
|
+
if (!state.activeSessionId || !state.sessions.includes(state.activeSessionId)) {
|
|
1606
|
+
state.activeSessionId = state.sessions[state.sessions.length - 1] || null;
|
|
1607
|
+
state.expandedKey = null;
|
|
1608
|
+
}
|
|
1609
|
+
|
|
1610
|
+
renderSessions();
|
|
1611
|
+
}
|
|
1612
|
+
|
|
1613
|
+
async function loadSession(sessionId) {
|
|
1614
|
+
state.loadingSession = state.activeSessionId === sessionId;
|
|
1615
|
+
renderTimeline();
|
|
1616
|
+
|
|
1617
|
+
const requestId = (state.sessionRequestIds.get(sessionId) || 0) + 1;
|
|
1618
|
+
state.sessionRequestIds.set(sessionId, requestId);
|
|
1619
|
+
|
|
1620
|
+
try {
|
|
1621
|
+
const summaries = await fetchJson(`/api/sessions/${encodeURIComponent(sessionId)}`);
|
|
1622
|
+
if (state.sessionRequestIds.get(sessionId) !== requestId) {
|
|
1623
|
+
return;
|
|
1624
|
+
}
|
|
1625
|
+
|
|
1626
|
+
const normalized = Array.isArray(summaries) ? summaries.map(normalizeSummary) : [];
|
|
1627
|
+
state.interactionsBySession.set(sessionId, normalized);
|
|
1628
|
+
state.sessionErrors.delete(sessionId);
|
|
1629
|
+
|
|
1630
|
+
if (state.expandedKey) {
|
|
1631
|
+
const [expandedSessionId, expandedSeqText] = state.expandedKey.split(":");
|
|
1632
|
+
if (expandedSessionId === sessionId) {
|
|
1633
|
+
const expandedSeq = Number(expandedSeqText);
|
|
1634
|
+
const stillExists = normalized.some((item) => item.seq === expandedSeq);
|
|
1635
|
+
if (!stillExists) {
|
|
1636
|
+
state.expandedKey = null;
|
|
1637
|
+
}
|
|
1638
|
+
}
|
|
1639
|
+
}
|
|
1640
|
+
} catch (error) {
|
|
1641
|
+
if (state.sessionRequestIds.get(sessionId) !== requestId) {
|
|
1642
|
+
return;
|
|
1643
|
+
}
|
|
1644
|
+
state.sessionErrors.set(
|
|
1645
|
+
sessionId,
|
|
1646
|
+
error instanceof Error ? error.message : "Unknown session load error"
|
|
1647
|
+
);
|
|
1648
|
+
} finally {
|
|
1649
|
+
if (state.activeSessionId === sessionId) {
|
|
1650
|
+
state.loadingSession = false;
|
|
1651
|
+
}
|
|
1652
|
+
renderSessions();
|
|
1653
|
+
renderTimeline();
|
|
1654
|
+
}
|
|
1655
|
+
}
|
|
1656
|
+
|
|
1657
|
+
async function loadInteractionDetail(sessionId, seq) {
|
|
1658
|
+
const key = detailKey(sessionId, seq);
|
|
1659
|
+
const requestId = (state.detailRequestIds.get(key) || 0) + 1;
|
|
1660
|
+
state.detailRequestIds.set(key, requestId);
|
|
1661
|
+
|
|
1662
|
+
try {
|
|
1663
|
+
const detail = await fetchJson(`/api/interactions/${encodeURIComponent(sessionId)}/${seq}`);
|
|
1664
|
+
if (state.detailRequestIds.get(key) !== requestId) {
|
|
1665
|
+
return;
|
|
1666
|
+
}
|
|
1667
|
+
state.detailCache.set(key, detail);
|
|
1668
|
+
} catch (error) {
|
|
1669
|
+
state.detailCache.set(key, {
|
|
1670
|
+
request: null,
|
|
1671
|
+
response: null,
|
|
1672
|
+
viewer_error_message: error instanceof Error ? error.message : "",
|
|
1673
|
+
});
|
|
1674
|
+
}
|
|
1675
|
+
|
|
1676
|
+
if (state.expandedKey === key) {
|
|
1677
|
+
renderTimeline();
|
|
1678
|
+
}
|
|
1679
|
+
}
|
|
1680
|
+
|
|
1681
|
+
async function handleIncomingEvent(payload) {
|
|
1682
|
+
const sessionId = payload && payload.session_id ? String(payload.session_id) : "";
|
|
1683
|
+
const seq = Number(payload?.seq);
|
|
1684
|
+
|
|
1685
|
+
try {
|
|
1686
|
+
await refreshSessions();
|
|
1687
|
+
|
|
1688
|
+
if (sessionId && (!state.activeSessionId || state.activeSessionId === sessionId)) {
|
|
1689
|
+
if (!state.activeSessionId) {
|
|
1690
|
+
state.activeSessionId = sessionId;
|
|
1691
|
+
}
|
|
1692
|
+
await loadSession(sessionId);
|
|
1693
|
+
|
|
1694
|
+
if (state.expandedKey === detailKey(sessionId, seq)) {
|
|
1695
|
+
await loadInteractionDetail(sessionId, seq);
|
|
1696
|
+
}
|
|
1697
|
+
}
|
|
1698
|
+
} catch (error) {
|
|
1699
|
+
console.error("Failed to refresh after SSE event", error);
|
|
1700
|
+
}
|
|
1701
|
+
}
|
|
1702
|
+
|
|
1703
|
+
function connectEvents() {
|
|
1704
|
+
setConnectionStatus("connecting");
|
|
1705
|
+
const source = new EventSource("/api/events");
|
|
1706
|
+
|
|
1707
|
+
source.onopen = () => {
|
|
1708
|
+
setConnectionStatus("live");
|
|
1709
|
+
};
|
|
1710
|
+
|
|
1711
|
+
source.onmessage = async (event) => {
|
|
1712
|
+
try {
|
|
1713
|
+
const payload = JSON.parse(event.data);
|
|
1714
|
+
await handleIncomingEvent(payload);
|
|
1715
|
+
} catch (error) {
|
|
1716
|
+
console.error("Failed to process SSE event", error);
|
|
1717
|
+
}
|
|
1718
|
+
};
|
|
1719
|
+
|
|
1720
|
+
source.onerror = () => {
|
|
1721
|
+
setConnectionStatus("retrying");
|
|
1722
|
+
};
|
|
1723
|
+
}
|
|
1724
|
+
|
|
1725
|
+
async function boot() {
|
|
1726
|
+
applyLocale();
|
|
1727
|
+
try {
|
|
1728
|
+
await refreshSessions();
|
|
1729
|
+
if (state.activeSessionId) {
|
|
1730
|
+
await loadSession(state.activeSessionId);
|
|
1731
|
+
}
|
|
1732
|
+
} catch (error) {
|
|
1733
|
+
console.error(error);
|
|
1734
|
+
state.bootError = true;
|
|
1735
|
+
} finally {
|
|
1736
|
+
state.booting = false;
|
|
1737
|
+
}
|
|
1738
|
+
|
|
1739
|
+
applyLocale();
|
|
1740
|
+
connectEvents();
|
|
1741
|
+
}
|
|
1742
|
+
|
|
1743
|
+
elements.localeButtons.forEach((button) => {
|
|
1744
|
+
button.addEventListener("click", () => setLocale(button.dataset.locale || "en"));
|
|
1745
|
+
});
|
|
1746
|
+
|
|
1747
|
+
boot();
|
|
1748
|
+
</script>
|
|
1749
|
+
</body>
|
|
1750
|
+
</html>
|