@neovate/code 0.12.1 → 0.12.3
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/README.md +31 -19
- package/dist/cli.mjs +567 -556
- package/dist/index.d.ts +1 -0
- package/dist/index.mjs +568 -557
- package/package.json +14 -6
- package/dist/logfiles/index.html +0 -2846
- package/dist/logfiles/live.html +0 -1465
package/dist/logfiles/index.html
DELETED
|
@@ -1,2846 +0,0 @@
|
|
|
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>Log Viewer</title>
|
|
7
|
-
<style>
|
|
8
|
-
* {
|
|
9
|
-
margin: 0;
|
|
10
|
-
padding: 0;
|
|
11
|
-
box-sizing: border-box;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
body {
|
|
15
|
-
font-family:
|
|
16
|
-
-apple-system, BlinkMacSystemFont, 'Segoe UI', 'Microsoft YaHei',
|
|
17
|
-
'SimHei', sans-serif;
|
|
18
|
-
background: #ffffff;
|
|
19
|
-
min-height: 100vh;
|
|
20
|
-
color: #333333;
|
|
21
|
-
line-height: 1.4;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
.container {
|
|
25
|
-
max-width: 1200px;
|
|
26
|
-
margin: 0 auto;
|
|
27
|
-
padding: 1rem 2rem 2rem 2rem;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
header {
|
|
31
|
-
text-align: center;
|
|
32
|
-
margin-bottom: 2rem;
|
|
33
|
-
padding-bottom: 1rem;
|
|
34
|
-
border-bottom: 1px solid #e0e0e0;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
h1 {
|
|
38
|
-
color: #000000;
|
|
39
|
-
font-size: 1.8rem;
|
|
40
|
-
margin-bottom: 0.5rem;
|
|
41
|
-
font-weight: 600;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
.subtitle {
|
|
45
|
-
color: #666666;
|
|
46
|
-
font-size: 1rem;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
.card {
|
|
50
|
-
background: #ffffff;
|
|
51
|
-
border: 1px solid #e0e0e0;
|
|
52
|
-
margin-bottom: 2rem;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
.card-header {
|
|
56
|
-
background: #f5f5f5;
|
|
57
|
-
color: #333333;
|
|
58
|
-
padding: 1rem 1.5rem;
|
|
59
|
-
font-weight: 600;
|
|
60
|
-
font-size: 1.1rem;
|
|
61
|
-
border-bottom: 1px solid #e0e0e0;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
.card-body {
|
|
65
|
-
padding: 1.5rem;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
.projects-grid {
|
|
69
|
-
display: grid;
|
|
70
|
-
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
|
71
|
-
gap: 1rem;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
.project-card {
|
|
75
|
-
background: #ffffff;
|
|
76
|
-
border: 1px solid #e0e0e0;
|
|
77
|
-
padding: 1.5rem;
|
|
78
|
-
cursor: pointer;
|
|
79
|
-
transition: border-color 0.2s ease;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
.project-card:hover {
|
|
83
|
-
border-color: #0066cc;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
.project-name {
|
|
87
|
-
font-weight: 600;
|
|
88
|
-
font-size: 1.1rem;
|
|
89
|
-
margin-bottom: 0.5rem;
|
|
90
|
-
color: #000000;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
.project-stats {
|
|
94
|
-
display: flex;
|
|
95
|
-
justify-content: space-between;
|
|
96
|
-
font-size: 0.9rem;
|
|
97
|
-
color: #666666;
|
|
98
|
-
margin-bottom: 0.5rem;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
.project-activity {
|
|
102
|
-
font-size: 0.8rem;
|
|
103
|
-
color: #999999;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
.sessions-list {
|
|
107
|
-
display: none;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
.sessions-list.active {
|
|
111
|
-
display: block;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
.session-item {
|
|
115
|
-
border-bottom: 1px solid #e0e0e0;
|
|
116
|
-
padding: 1rem;
|
|
117
|
-
cursor: pointer;
|
|
118
|
-
transition: background-color 0.2s ease;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
.session-item:hover {
|
|
122
|
-
background-color: #f5f5f5;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
.session-item:last-child {
|
|
126
|
-
border-bottom: none;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
.session-title {
|
|
130
|
-
font-weight: 600;
|
|
131
|
-
margin-bottom: 0.5rem;
|
|
132
|
-
color: #000000;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
.session-meta {
|
|
136
|
-
display: flex;
|
|
137
|
-
justify-content: space-between;
|
|
138
|
-
font-size: 0.85rem;
|
|
139
|
-
color: #666666;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
.log-viewer {
|
|
143
|
-
display: none;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
.log-viewer.active {
|
|
147
|
-
display: block;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
.conversation-container {
|
|
151
|
-
max-width: 800px;
|
|
152
|
-
margin: 0 auto;
|
|
153
|
-
padding: 0;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
.message {
|
|
157
|
-
display: flex;
|
|
158
|
-
margin-bottom: 1.5rem;
|
|
159
|
-
gap: 12px;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
.message.user {
|
|
163
|
-
flex-direction: row-reverse;
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
.message.assistant {
|
|
167
|
-
flex-direction: row;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
.message.tool {
|
|
171
|
-
justify-content: center;
|
|
172
|
-
margin: 1rem 0;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
.message.summary {
|
|
176
|
-
justify-content: center;
|
|
177
|
-
margin: 2rem 0;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
.avatar {
|
|
181
|
-
width: 32px;
|
|
182
|
-
height: 32px;
|
|
183
|
-
border: 1px solid #e0e0e0;
|
|
184
|
-
flex-shrink: 0;
|
|
185
|
-
display: flex;
|
|
186
|
-
align-items: center;
|
|
187
|
-
justify-content: center;
|
|
188
|
-
font-size: 14px;
|
|
189
|
-
font-weight: 600;
|
|
190
|
-
background: #f5f5f5;
|
|
191
|
-
color: #333333;
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
.avatar.user {
|
|
195
|
-
background: #ffffff;
|
|
196
|
-
border-color: #0066cc;
|
|
197
|
-
color: #0066cc;
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
.avatar.assistant {
|
|
201
|
-
background: #ffffff;
|
|
202
|
-
border-color: #333333;
|
|
203
|
-
color: #333333;
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
.message-content {
|
|
207
|
-
flex: 1;
|
|
208
|
-
max-width: calc(100% - 44px);
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
.message.user .message-content {
|
|
212
|
-
background: #ffffff;
|
|
213
|
-
color: #333333;
|
|
214
|
-
padding: 12px 16px;
|
|
215
|
-
border: 1px solid #e0e0e0;
|
|
216
|
-
border-left: 3px solid #0066cc;
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
.message.assistant .message-content {
|
|
220
|
-
background: #f5f5f5;
|
|
221
|
-
color: #333333;
|
|
222
|
-
padding: 12px 16px;
|
|
223
|
-
border: 1px solid #e0e0e0;
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
.message-text {
|
|
227
|
-
line-height: 1.5;
|
|
228
|
-
white-space: pre-wrap;
|
|
229
|
-
word-wrap: break-word;
|
|
230
|
-
margin: 0;
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
.message-meta {
|
|
234
|
-
font-size: 0.75rem;
|
|
235
|
-
color: #999999;
|
|
236
|
-
margin-top: 4px;
|
|
237
|
-
text-align: right;
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
.message.assistant .message-meta {
|
|
241
|
-
text-align: left;
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
.tool-call-container {
|
|
245
|
-
background: #f5f5f5;
|
|
246
|
-
border: 1px solid #e0e0e0;
|
|
247
|
-
padding: 12px;
|
|
248
|
-
margin: 1rem auto;
|
|
249
|
-
max-width: 600px;
|
|
250
|
-
font-family: 'SF Mono', 'Monaco', 'Inconsolata', 'Fira Code', monospace;
|
|
251
|
-
font-size: 0.85rem;
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
.tool-call-header {
|
|
255
|
-
display: flex;
|
|
256
|
-
align-items: center;
|
|
257
|
-
gap: 8px;
|
|
258
|
-
margin-bottom: 8px;
|
|
259
|
-
font-weight: 600;
|
|
260
|
-
color: #333333;
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
.tool-call-content {
|
|
264
|
-
background: #ffffff;
|
|
265
|
-
padding: 8px;
|
|
266
|
-
border-left: 2px solid #666666;
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
.tool-result-container {
|
|
270
|
-
background: #f5f5f5;
|
|
271
|
-
border: 1px solid #e0e0e0;
|
|
272
|
-
padding: 12px;
|
|
273
|
-
margin: 1rem auto;
|
|
274
|
-
max-width: 600px;
|
|
275
|
-
font-family: 'SF Mono', 'Monaco', 'Inconsolata', 'Fira Code', monospace;
|
|
276
|
-
font-size: 0.85rem;
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
.tool-result-header {
|
|
280
|
-
display: flex;
|
|
281
|
-
align-items: center;
|
|
282
|
-
gap: 8px;
|
|
283
|
-
margin-bottom: 8px;
|
|
284
|
-
font-weight: 600;
|
|
285
|
-
color: #333333;
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
.tool-result-content {
|
|
289
|
-
background: #ffffff;
|
|
290
|
-
padding: 8px;
|
|
291
|
-
border-left: 2px solid #666666;
|
|
292
|
-
max-height: 300px;
|
|
293
|
-
overflow-y: auto;
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
.session-summary {
|
|
297
|
-
background: #f5f5f5;
|
|
298
|
-
color: #333333;
|
|
299
|
-
padding: 20px;
|
|
300
|
-
border: 1px solid #e0e0e0;
|
|
301
|
-
text-align: center;
|
|
302
|
-
margin: 2rem auto;
|
|
303
|
-
max-width: 600px;
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
.session-summary h3 {
|
|
307
|
-
margin: 0 0 8px 0;
|
|
308
|
-
font-size: 1.2rem;
|
|
309
|
-
color: #000000;
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
.session-summary p {
|
|
313
|
-
margin: 0;
|
|
314
|
-
color: #666666;
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
.back-btn {
|
|
318
|
-
background: #ffffff;
|
|
319
|
-
color: #333333;
|
|
320
|
-
border: 1px solid #e0e0e0;
|
|
321
|
-
padding: 0.5rem 1rem;
|
|
322
|
-
cursor: pointer;
|
|
323
|
-
font-size: 0.9rem;
|
|
324
|
-
margin-bottom: 1rem;
|
|
325
|
-
transition: border-color 0.2s ease;
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
.back-btn:hover {
|
|
329
|
-
border-color: #0066cc;
|
|
330
|
-
color: #0066cc;
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
.loading {
|
|
334
|
-
text-align: center;
|
|
335
|
-
padding: 2rem;
|
|
336
|
-
color: #666666;
|
|
337
|
-
font-style: italic;
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
.error {
|
|
341
|
-
background: #ffffff;
|
|
342
|
-
border: 1px solid #cc0000;
|
|
343
|
-
color: #cc0000;
|
|
344
|
-
padding: 1rem;
|
|
345
|
-
margin: 1rem 0;
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
.timestamp {
|
|
349
|
-
font-family: 'SF Mono', 'Monaco', 'Inconsolata', 'Fira Code', monospace;
|
|
350
|
-
font-size: 0.8rem;
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
.tool-call {
|
|
354
|
-
background: #f5f5f5;
|
|
355
|
-
border: 1px solid #e0e0e0;
|
|
356
|
-
padding: 0.75rem;
|
|
357
|
-
margin: 0.5rem 0;
|
|
358
|
-
font-family: 'SF Mono', 'Monaco', 'Inconsolata', 'Fira Code', monospace;
|
|
359
|
-
font-size: 0.85rem;
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
.tool-result {
|
|
363
|
-
background: #ffffff;
|
|
364
|
-
border: 1px solid #e0e0e0;
|
|
365
|
-
padding: 0.75rem;
|
|
366
|
-
margin: 0.5rem 0;
|
|
367
|
-
font-family: 'SF Mono', 'Monaco', 'Inconsolata', 'Fira Code', monospace;
|
|
368
|
-
font-size: 0.85rem;
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
.watch-btn {
|
|
372
|
-
background: #0066cc;
|
|
373
|
-
color: white;
|
|
374
|
-
border: 1px solid #0066cc;
|
|
375
|
-
padding: 0.75rem 1.5rem;
|
|
376
|
-
cursor: pointer;
|
|
377
|
-
font-size: 0.9rem;
|
|
378
|
-
font-weight: 600;
|
|
379
|
-
transition: background-color 0.2s ease;
|
|
380
|
-
display: inline-flex;
|
|
381
|
-
align-items: center;
|
|
382
|
-
gap: 0.5rem;
|
|
383
|
-
margin-left: 1rem;
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
.watch-btn:hover {
|
|
387
|
-
background: #0052a3;
|
|
388
|
-
border-color: #0052a3;
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
.activity-badge {
|
|
392
|
-
position: absolute;
|
|
393
|
-
top: 10px;
|
|
394
|
-
right: 10px;
|
|
395
|
-
width: 8px;
|
|
396
|
-
height: 8px;
|
|
397
|
-
background: #0066cc;
|
|
398
|
-
border-radius: 50%;
|
|
399
|
-
z-index: 10;
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
.nav-links {
|
|
403
|
-
display: flex;
|
|
404
|
-
justify-content: center;
|
|
405
|
-
gap: 0;
|
|
406
|
-
margin-top: 1rem;
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
.nav-link {
|
|
410
|
-
background: #ffffff;
|
|
411
|
-
color: #0066cc;
|
|
412
|
-
text-decoration: none;
|
|
413
|
-
padding: 0.5rem 1rem;
|
|
414
|
-
border: 1px solid #e0e0e0;
|
|
415
|
-
transition: all 0.2s ease;
|
|
416
|
-
font-weight: normal;
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
.nav-link:hover {
|
|
420
|
-
text-decoration: underline;
|
|
421
|
-
border-color: #0066cc;
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
.nav-link.active {
|
|
425
|
-
background: #0066cc;
|
|
426
|
-
color: white;
|
|
427
|
-
font-weight: 600;
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
@media (max-width: 768px) {
|
|
431
|
-
.container {
|
|
432
|
-
padding: 1rem;
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
h1 {
|
|
436
|
-
font-size: 1.5rem;
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
.projects-grid {
|
|
440
|
-
grid-template-columns: 1fr;
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
.project-stats {
|
|
444
|
-
flex-direction: column;
|
|
445
|
-
gap: 0.25rem;
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
.watch-btn {
|
|
449
|
-
margin-left: 0;
|
|
450
|
-
margin-top: 0.5rem;
|
|
451
|
-
}
|
|
452
|
-
}
|
|
453
|
-
</style>
|
|
454
|
-
</head>
|
|
455
|
-
<body>
|
|
456
|
-
<div class="container">
|
|
457
|
-
<header>
|
|
458
|
-
<h1>Log Viewer</h1>
|
|
459
|
-
<p class="subtitle">Explore and audit your conversation logs</p>
|
|
460
|
-
|
|
461
|
-
<div class="nav-links">
|
|
462
|
-
<a href="/" class="nav-link active">Projects</a>
|
|
463
|
-
<a href="/live" class="nav-link">Live Activity</a>
|
|
464
|
-
</div>
|
|
465
|
-
</header>
|
|
466
|
-
|
|
467
|
-
<div id="projects-view" class="card">
|
|
468
|
-
<div class="card-header">Projects</div>
|
|
469
|
-
<div class="card-body">
|
|
470
|
-
<div id="projects-loading" class="loading">Loading projects...</div>
|
|
471
|
-
<div id="projects-list" class="projects-grid"></div>
|
|
472
|
-
</div>
|
|
473
|
-
</div>
|
|
474
|
-
|
|
475
|
-
<div id="sessions-view" class="card sessions-list">
|
|
476
|
-
<div class="card-header">
|
|
477
|
-
<button class="back-btn" onclick="showProjects()">
|
|
478
|
-
← Back to Projects
|
|
479
|
-
</button>
|
|
480
|
-
<span id="project-title">Sessions</span>
|
|
481
|
-
</div>
|
|
482
|
-
<div class="card-body">
|
|
483
|
-
<div id="sessions-loading" class="loading">Loading sessions...</div>
|
|
484
|
-
<div id="sessions-list"></div>
|
|
485
|
-
</div>
|
|
486
|
-
</div>
|
|
487
|
-
|
|
488
|
-
<div id="log-view" class="card log-viewer">
|
|
489
|
-
<div class="card-header">
|
|
490
|
-
<button class="back-btn" onclick="showSessions()">
|
|
491
|
-
← Back to Sessions
|
|
492
|
-
</button>
|
|
493
|
-
<span id="session-title">Conversation Log</span>
|
|
494
|
-
</div>
|
|
495
|
-
<div class="card-body">
|
|
496
|
-
<div id="log-loading" class="loading">Loading conversation...</div>
|
|
497
|
-
<div id="log-entries" class="conversation-container"></div>
|
|
498
|
-
</div>
|
|
499
|
-
</div>
|
|
500
|
-
</div>
|
|
501
|
-
|
|
502
|
-
<script>
|
|
503
|
-
// ABOUTME: Tool handler classes for rendering different tool types with specific formatting
|
|
504
|
-
// ABOUTME: Each tool type has its own renderer for inputs and outputs based on the tool's schema
|
|
505
|
-
|
|
506
|
-
// Base class for all tool handlers
|
|
507
|
-
class ToolHandler {
|
|
508
|
-
constructor(toolName) {
|
|
509
|
-
this.toolName = toolName;
|
|
510
|
-
}
|
|
511
|
-
|
|
512
|
-
renderToolCall(toolCall) {
|
|
513
|
-
const toolDiv = document.createElement('div');
|
|
514
|
-
toolDiv.className = 'tool-call-container';
|
|
515
|
-
|
|
516
|
-
const header = this.createHeader(toolCall);
|
|
517
|
-
const content = this.renderInput(toolCall.input);
|
|
518
|
-
|
|
519
|
-
toolDiv.appendChild(header);
|
|
520
|
-
toolDiv.appendChild(content);
|
|
521
|
-
return toolDiv;
|
|
522
|
-
}
|
|
523
|
-
|
|
524
|
-
renderToolResult(toolResult, toolCall) {
|
|
525
|
-
const resultDiv = document.createElement('div');
|
|
526
|
-
resultDiv.className = 'tool-result-container';
|
|
527
|
-
|
|
528
|
-
const header = this.createResultHeader(toolCall);
|
|
529
|
-
const content = this.renderOutput(toolResult, toolCall);
|
|
530
|
-
|
|
531
|
-
resultDiv.appendChild(header);
|
|
532
|
-
resultDiv.appendChild(content);
|
|
533
|
-
return resultDiv;
|
|
534
|
-
}
|
|
535
|
-
|
|
536
|
-
createHeader(toolCall) {
|
|
537
|
-
const header = document.createElement('div');
|
|
538
|
-
header.className = 'tool-call-header';
|
|
539
|
-
header.innerHTML = `<span>${this.getIcon()}</span><span>Tool: ${this.toolName}</span>`;
|
|
540
|
-
return header;
|
|
541
|
-
}
|
|
542
|
-
|
|
543
|
-
createResultHeader(toolCall) {
|
|
544
|
-
const header = document.createElement('div');
|
|
545
|
-
header.className = 'tool-result-header';
|
|
546
|
-
header.innerHTML = `<span>📋</span><span>${this.toolName} Result</span>`;
|
|
547
|
-
return header;
|
|
548
|
-
}
|
|
549
|
-
|
|
550
|
-
renderInput(input) {
|
|
551
|
-
const content = document.createElement('div');
|
|
552
|
-
content.className = 'tool-call-content';
|
|
553
|
-
content.textContent = JSON.stringify(input, null, 2);
|
|
554
|
-
return content;
|
|
555
|
-
}
|
|
556
|
-
|
|
557
|
-
renderOutput(result, toolCall) {
|
|
558
|
-
const content = document.createElement('div');
|
|
559
|
-
content.className = 'tool-result-content';
|
|
560
|
-
content.textContent =
|
|
561
|
-
typeof result === 'string'
|
|
562
|
-
? result
|
|
563
|
-
: JSON.stringify(result, null, 2);
|
|
564
|
-
return content;
|
|
565
|
-
}
|
|
566
|
-
|
|
567
|
-
getIcon() {
|
|
568
|
-
return '🔧';
|
|
569
|
-
}
|
|
570
|
-
}
|
|
571
|
-
|
|
572
|
-
// Handler for Bash tool
|
|
573
|
-
class BashHandler extends ToolHandler {
|
|
574
|
-
constructor() {
|
|
575
|
-
super('Bash');
|
|
576
|
-
}
|
|
577
|
-
|
|
578
|
-
renderInput(input) {
|
|
579
|
-
const content = document.createElement('div');
|
|
580
|
-
content.className = 'tool-call-content';
|
|
581
|
-
|
|
582
|
-
const command = document.createElement('div');
|
|
583
|
-
command.style.fontWeight = 'bold';
|
|
584
|
-
command.style.marginBottom = '8px';
|
|
585
|
-
command.textContent = `$ ${input.command}`;
|
|
586
|
-
|
|
587
|
-
if (input.description) {
|
|
588
|
-
const desc = document.createElement('div');
|
|
589
|
-
desc.style.fontStyle = 'italic';
|
|
590
|
-
desc.style.color = '#666';
|
|
591
|
-
desc.textContent = input.description;
|
|
592
|
-
content.appendChild(desc);
|
|
593
|
-
}
|
|
594
|
-
|
|
595
|
-
content.appendChild(command);
|
|
596
|
-
return content;
|
|
597
|
-
}
|
|
598
|
-
|
|
599
|
-
renderOutput(result, toolCall) {
|
|
600
|
-
const content = document.createElement('div');
|
|
601
|
-
content.className = 'tool-result-content';
|
|
602
|
-
content.style.fontFamily = 'monospace';
|
|
603
|
-
content.style.whiteSpace = 'pre-wrap';
|
|
604
|
-
content.textContent = result;
|
|
605
|
-
return content;
|
|
606
|
-
}
|
|
607
|
-
|
|
608
|
-
getIcon() {
|
|
609
|
-
return '💻';
|
|
610
|
-
}
|
|
611
|
-
}
|
|
612
|
-
|
|
613
|
-
// Handler for Read tool
|
|
614
|
-
class ReadHandler extends ToolHandler {
|
|
615
|
-
constructor() {
|
|
616
|
-
super('Read');
|
|
617
|
-
}
|
|
618
|
-
|
|
619
|
-
renderInput(input) {
|
|
620
|
-
const content = document.createElement('div');
|
|
621
|
-
content.className = 'tool-call-content';
|
|
622
|
-
|
|
623
|
-
const path = document.createElement('div');
|
|
624
|
-
path.style.fontWeight = 'bold';
|
|
625
|
-
path.textContent = `📄 ${input.file_path}`;
|
|
626
|
-
|
|
627
|
-
content.appendChild(path);
|
|
628
|
-
|
|
629
|
-
if (input.offset || input.limit) {
|
|
630
|
-
const range = document.createElement('div');
|
|
631
|
-
range.style.fontSize = '0.9em';
|
|
632
|
-
range.style.color = '#666';
|
|
633
|
-
range.textContent = `Lines: ${input.offset || 1} - ${(input.offset || 1) + (input.limit || 'end')}`;
|
|
634
|
-
content.appendChild(range);
|
|
635
|
-
}
|
|
636
|
-
|
|
637
|
-
return content;
|
|
638
|
-
}
|
|
639
|
-
|
|
640
|
-
renderOutput(result, toolCall) {
|
|
641
|
-
const content = document.createElement('div');
|
|
642
|
-
content.className = 'tool-result-content';
|
|
643
|
-
content.style.fontFamily = 'monospace';
|
|
644
|
-
content.style.fontSize = '0.85em';
|
|
645
|
-
content.style.whiteSpace = 'pre';
|
|
646
|
-
content.style.maxHeight = '400px';
|
|
647
|
-
content.style.overflowY = 'auto';
|
|
648
|
-
content.textContent = result;
|
|
649
|
-
return content;
|
|
650
|
-
}
|
|
651
|
-
|
|
652
|
-
getIcon() {
|
|
653
|
-
return '📖';
|
|
654
|
-
}
|
|
655
|
-
}
|
|
656
|
-
|
|
657
|
-
// Handler for Edit/Write tools
|
|
658
|
-
class EditHandler extends ToolHandler {
|
|
659
|
-
constructor() {
|
|
660
|
-
super('Edit');
|
|
661
|
-
}
|
|
662
|
-
|
|
663
|
-
renderInput(input) {
|
|
664
|
-
const content = document.createElement('div');
|
|
665
|
-
content.className = 'tool-call-content';
|
|
666
|
-
|
|
667
|
-
const path = document.createElement('div');
|
|
668
|
-
path.style.fontWeight = 'bold';
|
|
669
|
-
path.style.marginBottom = '8px';
|
|
670
|
-
path.textContent = `✏️ ${input.file_path}`;
|
|
671
|
-
|
|
672
|
-
if (input.old_string && input.new_string) {
|
|
673
|
-
const changeDiv = document.createElement('div');
|
|
674
|
-
changeDiv.style.fontSize = '0.9em';
|
|
675
|
-
|
|
676
|
-
const oldDiv = document.createElement('div');
|
|
677
|
-
oldDiv.style.background = '#ffe6e6';
|
|
678
|
-
oldDiv.style.padding = '4px';
|
|
679
|
-
oldDiv.style.marginBottom = '4px';
|
|
680
|
-
oldDiv.style.whiteSpace = 'pre-wrap';
|
|
681
|
-
oldDiv.textContent = `- ${input.old_string}`;
|
|
682
|
-
|
|
683
|
-
const newDiv = document.createElement('div');
|
|
684
|
-
newDiv.style.background = '#e6ffe6';
|
|
685
|
-
newDiv.style.padding = '4px';
|
|
686
|
-
newDiv.style.whiteSpace = 'pre-wrap';
|
|
687
|
-
newDiv.textContent = `+ ${input.new_string}`;
|
|
688
|
-
|
|
689
|
-
changeDiv.appendChild(oldDiv);
|
|
690
|
-
changeDiv.appendChild(newDiv);
|
|
691
|
-
content.appendChild(path);
|
|
692
|
-
content.appendChild(changeDiv);
|
|
693
|
-
} else {
|
|
694
|
-
content.appendChild(path);
|
|
695
|
-
}
|
|
696
|
-
|
|
697
|
-
return content;
|
|
698
|
-
}
|
|
699
|
-
|
|
700
|
-
getIcon() {
|
|
701
|
-
return '✏️';
|
|
702
|
-
}
|
|
703
|
-
}
|
|
704
|
-
|
|
705
|
-
// Handler for MultiEdit tool
|
|
706
|
-
class MultiEditHandler extends ToolHandler {
|
|
707
|
-
constructor() {
|
|
708
|
-
super('MultiEdit');
|
|
709
|
-
}
|
|
710
|
-
|
|
711
|
-
renderInput(input) {
|
|
712
|
-
const content = document.createElement('div');
|
|
713
|
-
content.className = 'tool-call-content';
|
|
714
|
-
|
|
715
|
-
const multiEditDiv = document.createElement('div');
|
|
716
|
-
multiEditDiv.style.padding = '12px';
|
|
717
|
-
multiEditDiv.style.background = '#f8f9fa';
|
|
718
|
-
multiEditDiv.style.borderRadius = '8px';
|
|
719
|
-
multiEditDiv.style.border = '1px solid #dee2e6';
|
|
720
|
-
|
|
721
|
-
const header = document.createElement('div');
|
|
722
|
-
header.style.fontWeight = 'bold';
|
|
723
|
-
header.style.marginBottom = '12px';
|
|
724
|
-
header.style.color = '#2c3e50';
|
|
725
|
-
header.style.borderBottom = '1px solid #ddd';
|
|
726
|
-
header.style.paddingBottom = '6px';
|
|
727
|
-
|
|
728
|
-
const editCount = input.edits ? input.edits.length : 0;
|
|
729
|
-
header.textContent = `🔄 Multiple Edits to ${input.file_path} (${editCount} changes)`;
|
|
730
|
-
|
|
731
|
-
multiEditDiv.appendChild(header);
|
|
732
|
-
|
|
733
|
-
if (input.edits && Array.isArray(input.edits)) {
|
|
734
|
-
input.edits.forEach((edit, index) => {
|
|
735
|
-
const editDiv = document.createElement('div');
|
|
736
|
-
editDiv.style.marginBottom = '16px';
|
|
737
|
-
editDiv.style.padding = '12px';
|
|
738
|
-
editDiv.style.background = '#ffffff';
|
|
739
|
-
editDiv.style.borderRadius = '6px';
|
|
740
|
-
editDiv.style.border = '1px solid #e5e7eb';
|
|
741
|
-
|
|
742
|
-
const editHeader = document.createElement('div');
|
|
743
|
-
editHeader.style.fontWeight = 'bold';
|
|
744
|
-
editHeader.style.marginBottom = '8px';
|
|
745
|
-
editHeader.style.color = '#4b5563';
|
|
746
|
-
editHeader.style.fontSize = '0.9em';
|
|
747
|
-
editHeader.innerHTML = `Edit ${index + 1}${edit.replace_all ? ' <span style="color: #dc3545;">(replace all)</span>' : ''}`;
|
|
748
|
-
|
|
749
|
-
const changeDiv = document.createElement('div');
|
|
750
|
-
changeDiv.style.fontSize = '0.85em';
|
|
751
|
-
|
|
752
|
-
if (edit.old_string && edit.new_string) {
|
|
753
|
-
const oldDiv = document.createElement('div');
|
|
754
|
-
oldDiv.style.background = '#ffe6e6';
|
|
755
|
-
oldDiv.style.padding = '8px';
|
|
756
|
-
oldDiv.style.marginBottom = '4px';
|
|
757
|
-
oldDiv.style.whiteSpace = 'pre-wrap';
|
|
758
|
-
oldDiv.style.borderRadius = '4px';
|
|
759
|
-
oldDiv.style.fontFamily = 'monospace';
|
|
760
|
-
oldDiv.style.fontSize = '0.8em';
|
|
761
|
-
oldDiv.style.maxHeight = '150px';
|
|
762
|
-
oldDiv.style.overflowY = 'auto';
|
|
763
|
-
oldDiv.textContent = `- ${edit.old_string}`;
|
|
764
|
-
|
|
765
|
-
const newDiv = document.createElement('div');
|
|
766
|
-
newDiv.style.background = '#e6ffe6';
|
|
767
|
-
newDiv.style.padding = '8px';
|
|
768
|
-
newDiv.style.whiteSpace = 'pre-wrap';
|
|
769
|
-
newDiv.style.borderRadius = '4px';
|
|
770
|
-
newDiv.style.fontFamily = 'monospace';
|
|
771
|
-
newDiv.style.fontSize = '0.8em';
|
|
772
|
-
newDiv.style.maxHeight = '150px';
|
|
773
|
-
newDiv.style.overflowY = 'auto';
|
|
774
|
-
newDiv.textContent = `+ ${edit.new_string}`;
|
|
775
|
-
|
|
776
|
-
changeDiv.appendChild(oldDiv);
|
|
777
|
-
changeDiv.appendChild(newDiv);
|
|
778
|
-
} else {
|
|
779
|
-
changeDiv.textContent = JSON.stringify(edit, null, 2);
|
|
780
|
-
}
|
|
781
|
-
|
|
782
|
-
editDiv.appendChild(editHeader);
|
|
783
|
-
editDiv.appendChild(changeDiv);
|
|
784
|
-
multiEditDiv.appendChild(editDiv);
|
|
785
|
-
});
|
|
786
|
-
} else {
|
|
787
|
-
const noEditsDiv = document.createElement('div');
|
|
788
|
-
noEditsDiv.style.color = '#6b7280';
|
|
789
|
-
noEditsDiv.style.fontStyle = 'italic';
|
|
790
|
-
noEditsDiv.textContent = 'No edits provided';
|
|
791
|
-
multiEditDiv.appendChild(noEditsDiv);
|
|
792
|
-
}
|
|
793
|
-
|
|
794
|
-
content.appendChild(multiEditDiv);
|
|
795
|
-
return content;
|
|
796
|
-
}
|
|
797
|
-
|
|
798
|
-
renderOutput(result, toolCall) {
|
|
799
|
-
const content = document.createElement('div');
|
|
800
|
-
content.className = 'tool-result-content';
|
|
801
|
-
|
|
802
|
-
// Try to extract text from array format first
|
|
803
|
-
let resultText = result;
|
|
804
|
-
if (Array.isArray(result) && result[0] && result[0].text) {
|
|
805
|
-
resultText = result[0].text;
|
|
806
|
-
}
|
|
807
|
-
|
|
808
|
-
const resultDiv = document.createElement('div');
|
|
809
|
-
resultDiv.style.padding = '12px';
|
|
810
|
-
resultDiv.style.borderRadius = '6px';
|
|
811
|
-
|
|
812
|
-
// Check if it's a success message
|
|
813
|
-
if (
|
|
814
|
-
typeof resultText === 'string' &&
|
|
815
|
-
(resultText.includes('successfully') ||
|
|
816
|
-
resultText.includes('updated') ||
|
|
817
|
-
resultText.includes('file has been updated'))
|
|
818
|
-
) {
|
|
819
|
-
resultDiv.style.background = '#d4edda';
|
|
820
|
-
resultDiv.style.border = '1px solid #c3e6cb';
|
|
821
|
-
resultDiv.style.color = '#155724';
|
|
822
|
-
|
|
823
|
-
const successIcon = document.createElement('span');
|
|
824
|
-
successIcon.textContent = '✅ ';
|
|
825
|
-
successIcon.style.fontWeight = 'bold';
|
|
826
|
-
|
|
827
|
-
const message = document.createElement('span');
|
|
828
|
-
message.textContent = resultText;
|
|
829
|
-
|
|
830
|
-
resultDiv.appendChild(successIcon);
|
|
831
|
-
resultDiv.appendChild(message);
|
|
832
|
-
} else {
|
|
833
|
-
// Default rendering
|
|
834
|
-
resultDiv.style.whiteSpace = 'pre-wrap';
|
|
835
|
-
resultDiv.style.fontFamily = 'monospace';
|
|
836
|
-
resultDiv.style.fontSize = '0.9em';
|
|
837
|
-
resultDiv.textContent = resultText;
|
|
838
|
-
}
|
|
839
|
-
|
|
840
|
-
content.appendChild(resultDiv);
|
|
841
|
-
return content;
|
|
842
|
-
}
|
|
843
|
-
|
|
844
|
-
getIcon() {
|
|
845
|
-
return '🔄';
|
|
846
|
-
}
|
|
847
|
-
}
|
|
848
|
-
|
|
849
|
-
// Handler for Write tool
|
|
850
|
-
class WriteHandler extends ToolHandler {
|
|
851
|
-
constructor() {
|
|
852
|
-
super('Write');
|
|
853
|
-
}
|
|
854
|
-
|
|
855
|
-
renderInput(input) {
|
|
856
|
-
const content = document.createElement('div');
|
|
857
|
-
content.className = 'tool-call-content';
|
|
858
|
-
|
|
859
|
-
const path = document.createElement('div');
|
|
860
|
-
path.style.fontWeight = 'bold';
|
|
861
|
-
path.style.marginBottom = '8px';
|
|
862
|
-
path.textContent = `📝 ${input.file_path}`;
|
|
863
|
-
|
|
864
|
-
const contentInfo = document.createElement('div');
|
|
865
|
-
contentInfo.style.fontSize = '0.9em';
|
|
866
|
-
contentInfo.style.color = '#666';
|
|
867
|
-
contentInfo.textContent = `${input.content ? input.content.length : 0} characters`;
|
|
868
|
-
|
|
869
|
-
content.appendChild(path);
|
|
870
|
-
content.appendChild(contentInfo);
|
|
871
|
-
return content;
|
|
872
|
-
}
|
|
873
|
-
|
|
874
|
-
getIcon() {
|
|
875
|
-
return '📝';
|
|
876
|
-
}
|
|
877
|
-
}
|
|
878
|
-
|
|
879
|
-
// Handler for LS tool
|
|
880
|
-
class LSHandler extends ToolHandler {
|
|
881
|
-
constructor() {
|
|
882
|
-
super('LS');
|
|
883
|
-
}
|
|
884
|
-
|
|
885
|
-
renderInput(input) {
|
|
886
|
-
const content = document.createElement('div');
|
|
887
|
-
content.className = 'tool-call-content';
|
|
888
|
-
|
|
889
|
-
const path = document.createElement('div');
|
|
890
|
-
path.style.fontWeight = 'bold';
|
|
891
|
-
path.textContent = `📁 ${input.path}`;
|
|
892
|
-
|
|
893
|
-
content.appendChild(path);
|
|
894
|
-
return content;
|
|
895
|
-
}
|
|
896
|
-
|
|
897
|
-
renderOutput(result, toolCall) {
|
|
898
|
-
const content = document.createElement('div');
|
|
899
|
-
content.className = 'tool-result-content';
|
|
900
|
-
content.style.fontFamily = 'monospace';
|
|
901
|
-
content.style.whiteSpace = 'pre';
|
|
902
|
-
content.textContent = result;
|
|
903
|
-
return content;
|
|
904
|
-
}
|
|
905
|
-
|
|
906
|
-
getIcon() {
|
|
907
|
-
return '📁';
|
|
908
|
-
}
|
|
909
|
-
}
|
|
910
|
-
|
|
911
|
-
// Handler for Grep tool
|
|
912
|
-
class GrepHandler extends ToolHandler {
|
|
913
|
-
constructor() {
|
|
914
|
-
super('Grep');
|
|
915
|
-
}
|
|
916
|
-
|
|
917
|
-
renderInput(input) {
|
|
918
|
-
const content = document.createElement('div');
|
|
919
|
-
content.className = 'tool-call-content';
|
|
920
|
-
|
|
921
|
-
const pattern = document.createElement('div');
|
|
922
|
-
pattern.style.fontWeight = 'bold';
|
|
923
|
-
pattern.style.marginBottom = '4px';
|
|
924
|
-
pattern.textContent = `🔍 "${input.pattern}"`;
|
|
925
|
-
|
|
926
|
-
if (input.path) {
|
|
927
|
-
const path = document.createElement('div');
|
|
928
|
-
path.style.fontSize = '0.9em';
|
|
929
|
-
path.style.color = '#666';
|
|
930
|
-
path.textContent = `in ${input.path}`;
|
|
931
|
-
content.appendChild(pattern);
|
|
932
|
-
content.appendChild(path);
|
|
933
|
-
} else {
|
|
934
|
-
content.appendChild(pattern);
|
|
935
|
-
}
|
|
936
|
-
|
|
937
|
-
return content;
|
|
938
|
-
}
|
|
939
|
-
|
|
940
|
-
renderOutput(result, toolCall) {
|
|
941
|
-
const content = document.createElement('div');
|
|
942
|
-
content.className = 'tool-result-content';
|
|
943
|
-
content.style.fontFamily = 'monospace';
|
|
944
|
-
content.style.fontSize = '0.85em';
|
|
945
|
-
content.style.whiteSpace = 'pre';
|
|
946
|
-
content.textContent = result;
|
|
947
|
-
return content;
|
|
948
|
-
}
|
|
949
|
-
|
|
950
|
-
getIcon() {
|
|
951
|
-
return '🔍';
|
|
952
|
-
}
|
|
953
|
-
}
|
|
954
|
-
|
|
955
|
-
// Handler for Glob tool
|
|
956
|
-
class GlobHandler extends ToolHandler {
|
|
957
|
-
constructor() {
|
|
958
|
-
super('Glob');
|
|
959
|
-
}
|
|
960
|
-
|
|
961
|
-
renderInput(input) {
|
|
962
|
-
const content = document.createElement('div');
|
|
963
|
-
content.className = 'tool-call-content';
|
|
964
|
-
|
|
965
|
-
const globDiv = document.createElement('div');
|
|
966
|
-
globDiv.style.padding = '12px';
|
|
967
|
-
globDiv.style.background = '#fef3c7';
|
|
968
|
-
globDiv.style.borderRadius = '8px';
|
|
969
|
-
globDiv.style.border = '1px solid #fbbf24';
|
|
970
|
-
|
|
971
|
-
const header = document.createElement('div');
|
|
972
|
-
header.style.fontWeight = 'bold';
|
|
973
|
-
header.style.marginBottom = '12px';
|
|
974
|
-
header.style.color = '#92400e';
|
|
975
|
-
header.textContent = '🗂️ File Pattern Search';
|
|
976
|
-
|
|
977
|
-
const patternDiv = document.createElement('div');
|
|
978
|
-
patternDiv.style.marginBottom = '8px';
|
|
979
|
-
|
|
980
|
-
const patternLabel = document.createElement('div');
|
|
981
|
-
patternLabel.style.fontWeight = 'bold';
|
|
982
|
-
patternLabel.style.marginBottom = '4px';
|
|
983
|
-
patternLabel.style.color = '#2c3e50';
|
|
984
|
-
patternLabel.textContent = 'Pattern:';
|
|
985
|
-
|
|
986
|
-
const patternContent = document.createElement('div');
|
|
987
|
-
patternContent.style.padding = '8px';
|
|
988
|
-
patternContent.style.background = '#ffffff';
|
|
989
|
-
patternContent.style.borderRadius = '4px';
|
|
990
|
-
patternContent.style.border = '1px solid #e5e7eb';
|
|
991
|
-
patternContent.style.fontFamily = 'monospace';
|
|
992
|
-
patternContent.style.fontSize = '0.9em';
|
|
993
|
-
patternContent.textContent = input.pattern || '';
|
|
994
|
-
|
|
995
|
-
patternDiv.appendChild(patternLabel);
|
|
996
|
-
patternDiv.appendChild(patternContent);
|
|
997
|
-
globDiv.appendChild(header);
|
|
998
|
-
globDiv.appendChild(patternDiv);
|
|
999
|
-
|
|
1000
|
-
if (input.path) {
|
|
1001
|
-
const pathDiv = document.createElement('div');
|
|
1002
|
-
pathDiv.style.marginBottom = '8px';
|
|
1003
|
-
|
|
1004
|
-
const pathLabel = document.createElement('div');
|
|
1005
|
-
pathLabel.style.fontWeight = 'bold';
|
|
1006
|
-
pathLabel.style.marginBottom = '4px';
|
|
1007
|
-
pathLabel.style.color = '#2c3e50';
|
|
1008
|
-
pathLabel.textContent = 'Search Path:';
|
|
1009
|
-
|
|
1010
|
-
const pathContent = document.createElement('div');
|
|
1011
|
-
pathContent.style.padding = '8px';
|
|
1012
|
-
pathContent.style.background = '#ffffff';
|
|
1013
|
-
pathContent.style.borderRadius = '4px';
|
|
1014
|
-
pathContent.style.border = '1px solid #e5e7eb';
|
|
1015
|
-
pathContent.style.fontFamily = 'monospace';
|
|
1016
|
-
pathContent.style.fontSize = '0.9em';
|
|
1017
|
-
pathContent.textContent = input.path;
|
|
1018
|
-
|
|
1019
|
-
pathDiv.appendChild(pathLabel);
|
|
1020
|
-
pathDiv.appendChild(pathContent);
|
|
1021
|
-
globDiv.appendChild(pathDiv);
|
|
1022
|
-
}
|
|
1023
|
-
|
|
1024
|
-
content.appendChild(globDiv);
|
|
1025
|
-
return content;
|
|
1026
|
-
}
|
|
1027
|
-
|
|
1028
|
-
renderOutput(result, toolCall) {
|
|
1029
|
-
const content = document.createElement('div');
|
|
1030
|
-
content.className = 'tool-result-content';
|
|
1031
|
-
|
|
1032
|
-
// Try to extract text from array format first
|
|
1033
|
-
let resultText = result;
|
|
1034
|
-
if (Array.isArray(result) && result[0] && result[0].text) {
|
|
1035
|
-
resultText = result[0].text;
|
|
1036
|
-
}
|
|
1037
|
-
|
|
1038
|
-
const resultDiv = document.createElement('div');
|
|
1039
|
-
resultDiv.style.padding = '12px';
|
|
1040
|
-
resultDiv.style.borderRadius = '6px';
|
|
1041
|
-
resultDiv.style.background = '#f9fafb';
|
|
1042
|
-
resultDiv.style.border = '1px solid #d1d5db';
|
|
1043
|
-
|
|
1044
|
-
// Check if we have file paths (likely line-separated)
|
|
1045
|
-
if (typeof resultText === 'string' && resultText.trim()) {
|
|
1046
|
-
const lines = resultText
|
|
1047
|
-
.trim()
|
|
1048
|
-
.split('\n')
|
|
1049
|
-
.filter((line) => line.trim());
|
|
1050
|
-
|
|
1051
|
-
if (lines.length > 0) {
|
|
1052
|
-
const headerDiv = document.createElement('div');
|
|
1053
|
-
headerDiv.style.fontWeight = 'bold';
|
|
1054
|
-
headerDiv.style.marginBottom = '12px';
|
|
1055
|
-
headerDiv.style.color = '#374151';
|
|
1056
|
-
headerDiv.textContent = `Found ${lines.length} matching file${lines.length === 1 ? '' : 's'}:`;
|
|
1057
|
-
|
|
1058
|
-
const filesDiv = document.createElement('div');
|
|
1059
|
-
|
|
1060
|
-
lines.forEach((filePath) => {
|
|
1061
|
-
const fileDiv = document.createElement('div');
|
|
1062
|
-
fileDiv.style.padding = '6px 8px';
|
|
1063
|
-
fileDiv.style.marginBottom = '4px';
|
|
1064
|
-
fileDiv.style.background = '#ffffff';
|
|
1065
|
-
fileDiv.style.borderRadius = '4px';
|
|
1066
|
-
fileDiv.style.border = '1px solid #e5e7eb';
|
|
1067
|
-
fileDiv.style.fontFamily = 'monospace';
|
|
1068
|
-
fileDiv.style.fontSize = '0.85em';
|
|
1069
|
-
fileDiv.style.wordBreak = 'break-all';
|
|
1070
|
-
|
|
1071
|
-
// Add file icon based on extension
|
|
1072
|
-
const extension = filePath.split('.').pop()?.toLowerCase();
|
|
1073
|
-
let icon = '📄';
|
|
1074
|
-
if (['js', 'ts', 'jsx', 'tsx'].includes(extension)) icon = '📜';
|
|
1075
|
-
else if (['py'].includes(extension)) icon = '🐍';
|
|
1076
|
-
else if (['rs'].includes(extension)) icon = '🦀';
|
|
1077
|
-
else if (['html', 'htm'].includes(extension)) icon = '🌐';
|
|
1078
|
-
else if (['css'].includes(extension)) icon = '🎨';
|
|
1079
|
-
else if (['md'].includes(extension)) icon = '📝';
|
|
1080
|
-
else if (['json'].includes(extension)) icon = '📋';
|
|
1081
|
-
|
|
1082
|
-
fileDiv.innerHTML = `${icon} ${filePath}`;
|
|
1083
|
-
filesDiv.appendChild(fileDiv);
|
|
1084
|
-
});
|
|
1085
|
-
|
|
1086
|
-
resultDiv.appendChild(headerDiv);
|
|
1087
|
-
resultDiv.appendChild(filesDiv);
|
|
1088
|
-
} else {
|
|
1089
|
-
resultDiv.style.background = '#fef2f2';
|
|
1090
|
-
resultDiv.style.border = '1px solid #fecaca';
|
|
1091
|
-
resultDiv.style.color = '#991b1b';
|
|
1092
|
-
resultDiv.innerHTML = '❌ No files found matching the pattern';
|
|
1093
|
-
}
|
|
1094
|
-
} else {
|
|
1095
|
-
resultDiv.style.whiteSpace = 'pre-wrap';
|
|
1096
|
-
resultDiv.style.fontFamily = 'monospace';
|
|
1097
|
-
resultDiv.style.fontSize = '0.9em';
|
|
1098
|
-
resultDiv.textContent =
|
|
1099
|
-
typeof resultText === 'string' ? resultText : 'No results';
|
|
1100
|
-
}
|
|
1101
|
-
|
|
1102
|
-
content.appendChild(resultDiv);
|
|
1103
|
-
return content;
|
|
1104
|
-
}
|
|
1105
|
-
|
|
1106
|
-
getIcon() {
|
|
1107
|
-
return '🗂️';
|
|
1108
|
-
}
|
|
1109
|
-
}
|
|
1110
|
-
|
|
1111
|
-
// Handler for TodoWrite tool
|
|
1112
|
-
class TodoWriteHandler extends ToolHandler {
|
|
1113
|
-
constructor() {
|
|
1114
|
-
super('TodoWrite');
|
|
1115
|
-
}
|
|
1116
|
-
|
|
1117
|
-
renderInput(input) {
|
|
1118
|
-
const content = document.createElement('div');
|
|
1119
|
-
content.className = 'tool-call-content';
|
|
1120
|
-
|
|
1121
|
-
if (input.todos && Array.isArray(input.todos)) {
|
|
1122
|
-
const todoList = document.createElement('div');
|
|
1123
|
-
todoList.style.marginTop = '8px';
|
|
1124
|
-
|
|
1125
|
-
const header = document.createElement('div');
|
|
1126
|
-
header.style.fontWeight = 'bold';
|
|
1127
|
-
header.style.marginBottom = '8px';
|
|
1128
|
-
header.style.borderBottom = '1px solid #ddd';
|
|
1129
|
-
header.style.paddingBottom = '4px';
|
|
1130
|
-
header.textContent = `📝 Todo List (${input.todos.length} items)`;
|
|
1131
|
-
|
|
1132
|
-
todoList.appendChild(header);
|
|
1133
|
-
|
|
1134
|
-
input.todos.forEach((todo) => {
|
|
1135
|
-
const todoItem = document.createElement('div');
|
|
1136
|
-
todoItem.style.display = 'flex';
|
|
1137
|
-
todoItem.style.alignItems = 'flex-start';
|
|
1138
|
-
todoItem.style.marginBottom = '8px';
|
|
1139
|
-
todoItem.style.padding = '8px';
|
|
1140
|
-
todoItem.style.borderRadius = '4px';
|
|
1141
|
-
todoItem.style.fontSize = '0.9em';
|
|
1142
|
-
|
|
1143
|
-
// Status-based styling
|
|
1144
|
-
if (todo.status === 'completed') {
|
|
1145
|
-
todoItem.style.background = '#e8f5e8';
|
|
1146
|
-
todoItem.style.borderLeft = '3px solid #28a745';
|
|
1147
|
-
} else if (todo.status === 'in_progress') {
|
|
1148
|
-
todoItem.style.background = '#fff3cd';
|
|
1149
|
-
todoItem.style.borderLeft = '3px solid #ffc107';
|
|
1150
|
-
} else {
|
|
1151
|
-
todoItem.style.background = '#f8f9fa';
|
|
1152
|
-
todoItem.style.borderLeft = '3px solid #6c757d';
|
|
1153
|
-
}
|
|
1154
|
-
|
|
1155
|
-
// Status icon
|
|
1156
|
-
const statusIcon = document.createElement('span');
|
|
1157
|
-
statusIcon.style.marginRight = '8px';
|
|
1158
|
-
statusIcon.style.fontSize = '1.1em';
|
|
1159
|
-
if (todo.status === 'completed') {
|
|
1160
|
-
statusIcon.textContent = '✅';
|
|
1161
|
-
} else if (todo.status === 'in_progress') {
|
|
1162
|
-
statusIcon.textContent = '🔄';
|
|
1163
|
-
} else {
|
|
1164
|
-
statusIcon.textContent = '⭕';
|
|
1165
|
-
}
|
|
1166
|
-
|
|
1167
|
-
// Content container
|
|
1168
|
-
const contentContainer = document.createElement('div');
|
|
1169
|
-
contentContainer.style.flex = '1';
|
|
1170
|
-
|
|
1171
|
-
// Todo content
|
|
1172
|
-
const todoContent = document.createElement('div');
|
|
1173
|
-
todoContent.style.marginBottom = '4px';
|
|
1174
|
-
if (todo.status === 'completed') {
|
|
1175
|
-
todoContent.style.textDecoration = 'line-through';
|
|
1176
|
-
todoContent.style.color = '#666';
|
|
1177
|
-
}
|
|
1178
|
-
todoContent.textContent = todo.content;
|
|
1179
|
-
|
|
1180
|
-
// Priority and ID
|
|
1181
|
-
const metaInfo = document.createElement('div');
|
|
1182
|
-
metaInfo.style.fontSize = '0.8em';
|
|
1183
|
-
metaInfo.style.color = '#888';
|
|
1184
|
-
|
|
1185
|
-
const priorityColor =
|
|
1186
|
-
todo.priority === 'high'
|
|
1187
|
-
? '#dc3545'
|
|
1188
|
-
: todo.priority === 'medium'
|
|
1189
|
-
? '#fd7e14'
|
|
1190
|
-
: '#28a745';
|
|
1191
|
-
metaInfo.innerHTML = `<span style="color: ${priorityColor}; font-weight: bold;">●</span> ${todo.priority} priority • ID: ${todo.id}`;
|
|
1192
|
-
|
|
1193
|
-
contentContainer.appendChild(todoContent);
|
|
1194
|
-
contentContainer.appendChild(metaInfo);
|
|
1195
|
-
|
|
1196
|
-
todoItem.appendChild(statusIcon);
|
|
1197
|
-
todoItem.appendChild(contentContainer);
|
|
1198
|
-
todoList.appendChild(todoItem);
|
|
1199
|
-
});
|
|
1200
|
-
|
|
1201
|
-
content.appendChild(todoList);
|
|
1202
|
-
} else {
|
|
1203
|
-
content.textContent = JSON.stringify(input, null, 2);
|
|
1204
|
-
}
|
|
1205
|
-
|
|
1206
|
-
return content;
|
|
1207
|
-
}
|
|
1208
|
-
|
|
1209
|
-
getIcon() {
|
|
1210
|
-
return '📝';
|
|
1211
|
-
}
|
|
1212
|
-
}
|
|
1213
|
-
|
|
1214
|
-
// Handler for mcp__private-journal__process_thoughts tool
|
|
1215
|
-
class PrivateJournalHandler extends ToolHandler {
|
|
1216
|
-
constructor() {
|
|
1217
|
-
super('mcp__private-journal__process_thoughts');
|
|
1218
|
-
}
|
|
1219
|
-
|
|
1220
|
-
renderInput(input) {
|
|
1221
|
-
const content = document.createElement('div');
|
|
1222
|
-
content.className = 'tool-call-content';
|
|
1223
|
-
|
|
1224
|
-
const journalEntry = document.createElement('div');
|
|
1225
|
-
journalEntry.style.marginTop = '8px';
|
|
1226
|
-
|
|
1227
|
-
const header = document.createElement('div');
|
|
1228
|
-
header.style.fontWeight = 'bold';
|
|
1229
|
-
header.style.marginBottom = '12px';
|
|
1230
|
-
header.style.borderBottom = '2px solid #ddd';
|
|
1231
|
-
header.style.paddingBottom = '6px';
|
|
1232
|
-
header.style.color = '#2c3e50';
|
|
1233
|
-
header.textContent = '🧠 Private Journal Entry';
|
|
1234
|
-
|
|
1235
|
-
journalEntry.appendChild(header);
|
|
1236
|
-
|
|
1237
|
-
// Define journal sections with their icons and colors
|
|
1238
|
-
const sections = [
|
|
1239
|
-
{
|
|
1240
|
-
key: 'feelings',
|
|
1241
|
-
label: 'Feelings',
|
|
1242
|
-
icon: '💭',
|
|
1243
|
-
color: '#e74c3c',
|
|
1244
|
-
},
|
|
1245
|
-
{
|
|
1246
|
-
key: 'user_context',
|
|
1247
|
-
label: 'User Context',
|
|
1248
|
-
icon: '👤',
|
|
1249
|
-
color: '#3498db',
|
|
1250
|
-
},
|
|
1251
|
-
{
|
|
1252
|
-
key: 'technical_insights',
|
|
1253
|
-
label: 'Technical Insights',
|
|
1254
|
-
icon: '🔧',
|
|
1255
|
-
color: '#2ecc71',
|
|
1256
|
-
},
|
|
1257
|
-
{
|
|
1258
|
-
key: 'project_notes',
|
|
1259
|
-
label: 'Project Notes',
|
|
1260
|
-
icon: '📋',
|
|
1261
|
-
color: '#f39c12',
|
|
1262
|
-
},
|
|
1263
|
-
{
|
|
1264
|
-
key: 'world_knowledge',
|
|
1265
|
-
label: 'World Knowledge',
|
|
1266
|
-
icon: '🌍',
|
|
1267
|
-
color: '#9b59b6',
|
|
1268
|
-
},
|
|
1269
|
-
];
|
|
1270
|
-
|
|
1271
|
-
sections.forEach((section) => {
|
|
1272
|
-
if (input[section.key]) {
|
|
1273
|
-
const sectionDiv = document.createElement('div');
|
|
1274
|
-
sectionDiv.style.marginBottom = '16px';
|
|
1275
|
-
sectionDiv.style.padding = '12px';
|
|
1276
|
-
sectionDiv.style.borderRadius = '8px';
|
|
1277
|
-
sectionDiv.style.border = `1px solid ${section.color}30`;
|
|
1278
|
-
sectionDiv.style.background = `${section.color}08`;
|
|
1279
|
-
|
|
1280
|
-
const sectionHeader = document.createElement('div');
|
|
1281
|
-
sectionHeader.style.fontWeight = 'bold';
|
|
1282
|
-
sectionHeader.style.marginBottom = '8px';
|
|
1283
|
-
sectionHeader.style.color = section.color;
|
|
1284
|
-
sectionHeader.style.fontSize = '0.95em';
|
|
1285
|
-
sectionHeader.innerHTML = `${section.icon} ${section.label}`;
|
|
1286
|
-
|
|
1287
|
-
const sectionContent = document.createElement('div');
|
|
1288
|
-
sectionContent.style.lineHeight = '1.5';
|
|
1289
|
-
sectionContent.style.color = '#333';
|
|
1290
|
-
sectionContent.style.fontSize = '0.9em';
|
|
1291
|
-
sectionContent.style.whiteSpace = 'pre-wrap';
|
|
1292
|
-
sectionContent.textContent = input[section.key];
|
|
1293
|
-
|
|
1294
|
-
sectionDiv.appendChild(sectionHeader);
|
|
1295
|
-
sectionDiv.appendChild(sectionContent);
|
|
1296
|
-
journalEntry.appendChild(sectionDiv);
|
|
1297
|
-
}
|
|
1298
|
-
});
|
|
1299
|
-
|
|
1300
|
-
// If no recognized sections found, fall back to JSON
|
|
1301
|
-
if (!sections.some((section) => input[section.key])) {
|
|
1302
|
-
content.textContent = JSON.stringify(input, null, 2);
|
|
1303
|
-
return content;
|
|
1304
|
-
}
|
|
1305
|
-
|
|
1306
|
-
content.appendChild(journalEntry);
|
|
1307
|
-
return content;
|
|
1308
|
-
}
|
|
1309
|
-
|
|
1310
|
-
renderOutput(result, toolCall) {
|
|
1311
|
-
const content = document.createElement('div');
|
|
1312
|
-
content.className = 'tool-result-content';
|
|
1313
|
-
|
|
1314
|
-
// Try to extract text from array format first
|
|
1315
|
-
let resultText = result;
|
|
1316
|
-
if (Array.isArray(result) && result[0] && result[0].text) {
|
|
1317
|
-
resultText = result[0].text;
|
|
1318
|
-
}
|
|
1319
|
-
|
|
1320
|
-
const resultDiv = document.createElement('div');
|
|
1321
|
-
resultDiv.style.padding = '12px';
|
|
1322
|
-
resultDiv.style.borderRadius = '6px';
|
|
1323
|
-
resultDiv.style.background = '#d4edda';
|
|
1324
|
-
resultDiv.style.border = '1px solid #c3e6cb';
|
|
1325
|
-
resultDiv.style.color = '#155724';
|
|
1326
|
-
|
|
1327
|
-
const successIcon = document.createElement('span');
|
|
1328
|
-
successIcon.textContent = '✅ ';
|
|
1329
|
-
successIcon.style.fontWeight = 'bold';
|
|
1330
|
-
|
|
1331
|
-
const message = document.createElement('span');
|
|
1332
|
-
message.textContent =
|
|
1333
|
-
typeof resultText === 'string'
|
|
1334
|
-
? resultText
|
|
1335
|
-
: 'Thoughts recorded successfully.';
|
|
1336
|
-
|
|
1337
|
-
resultDiv.appendChild(successIcon);
|
|
1338
|
-
resultDiv.appendChild(message);
|
|
1339
|
-
content.appendChild(resultDiv);
|
|
1340
|
-
return content;
|
|
1341
|
-
}
|
|
1342
|
-
|
|
1343
|
-
getIcon() {
|
|
1344
|
-
return '🧠';
|
|
1345
|
-
}
|
|
1346
|
-
}
|
|
1347
|
-
|
|
1348
|
-
// Handler for MCP Social Media Login tool
|
|
1349
|
-
class SocialMediaLoginHandler extends ToolHandler {
|
|
1350
|
-
constructor() {
|
|
1351
|
-
super('mcp__socialmedia__login');
|
|
1352
|
-
}
|
|
1353
|
-
|
|
1354
|
-
renderInput(input) {
|
|
1355
|
-
const content = document.createElement('div');
|
|
1356
|
-
content.className = 'tool-call-content';
|
|
1357
|
-
|
|
1358
|
-
const loginDiv = document.createElement('div');
|
|
1359
|
-
loginDiv.style.padding = '12px';
|
|
1360
|
-
loginDiv.style.background = '#e8f4fd';
|
|
1361
|
-
loginDiv.style.borderRadius = '8px';
|
|
1362
|
-
loginDiv.style.border = '1px solid #b8daff';
|
|
1363
|
-
|
|
1364
|
-
const header = document.createElement('div');
|
|
1365
|
-
header.style.fontWeight = 'bold';
|
|
1366
|
-
header.style.marginBottom = '8px';
|
|
1367
|
-
header.style.color = '#2c3e50';
|
|
1368
|
-
header.textContent = '🔐 Social Media Login';
|
|
1369
|
-
|
|
1370
|
-
const agentName = document.createElement('div');
|
|
1371
|
-
agentName.style.fontSize = '0.9em';
|
|
1372
|
-
agentName.innerHTML = `<strong>Agent Name:</strong> ${input.agent_name || 'Unknown'}`;
|
|
1373
|
-
|
|
1374
|
-
loginDiv.appendChild(header);
|
|
1375
|
-
loginDiv.appendChild(agentName);
|
|
1376
|
-
content.appendChild(loginDiv);
|
|
1377
|
-
return content;
|
|
1378
|
-
}
|
|
1379
|
-
|
|
1380
|
-
renderOutput(result, toolCall) {
|
|
1381
|
-
const content = document.createElement('div');
|
|
1382
|
-
content.className = 'tool-result-content';
|
|
1383
|
-
|
|
1384
|
-
try {
|
|
1385
|
-
// Try to parse JSON from the text result
|
|
1386
|
-
let jsonData;
|
|
1387
|
-
if (Array.isArray(result) && result[0] && result[0].text) {
|
|
1388
|
-
jsonData = JSON.parse(result[0].text);
|
|
1389
|
-
} else if (typeof result === 'string') {
|
|
1390
|
-
jsonData = JSON.parse(result);
|
|
1391
|
-
} else {
|
|
1392
|
-
jsonData = result;
|
|
1393
|
-
}
|
|
1394
|
-
|
|
1395
|
-
const resultDiv = document.createElement('div');
|
|
1396
|
-
resultDiv.style.padding = '12px';
|
|
1397
|
-
resultDiv.style.borderRadius = '6px';
|
|
1398
|
-
|
|
1399
|
-
if (jsonData.success) {
|
|
1400
|
-
resultDiv.style.background = '#d4edda';
|
|
1401
|
-
resultDiv.style.border = '1px solid #c3e6cb';
|
|
1402
|
-
resultDiv.style.color = '#155724';
|
|
1403
|
-
|
|
1404
|
-
const successIcon = document.createElement('span');
|
|
1405
|
-
successIcon.textContent = '✅ ';
|
|
1406
|
-
successIcon.style.fontWeight = 'bold';
|
|
1407
|
-
|
|
1408
|
-
const message = document.createElement('div');
|
|
1409
|
-
message.appendChild(successIcon);
|
|
1410
|
-
message.appendChild(document.createTextNode('Login Successful!'));
|
|
1411
|
-
|
|
1412
|
-
if (jsonData.message) {
|
|
1413
|
-
const welcomeMsg = document.createElement('div');
|
|
1414
|
-
welcomeMsg.style.marginTop = '8px';
|
|
1415
|
-
welcomeMsg.style.fontStyle = 'italic';
|
|
1416
|
-
welcomeMsg.textContent = jsonData.message;
|
|
1417
|
-
message.appendChild(welcomeMsg);
|
|
1418
|
-
}
|
|
1419
|
-
|
|
1420
|
-
resultDiv.appendChild(message);
|
|
1421
|
-
} else {
|
|
1422
|
-
resultDiv.style.background = '#f8d7da';
|
|
1423
|
-
resultDiv.style.border = '1px solid #f5c6cb';
|
|
1424
|
-
resultDiv.style.color = '#721c24';
|
|
1425
|
-
resultDiv.textContent = '❌ Login Failed';
|
|
1426
|
-
}
|
|
1427
|
-
|
|
1428
|
-
content.appendChild(resultDiv);
|
|
1429
|
-
} catch (e) {
|
|
1430
|
-
// Fallback to default text rendering
|
|
1431
|
-
content.style.whiteSpace = 'pre-wrap';
|
|
1432
|
-
content.textContent =
|
|
1433
|
-
typeof result === 'string'
|
|
1434
|
-
? result
|
|
1435
|
-
: JSON.stringify(result, null, 2);
|
|
1436
|
-
}
|
|
1437
|
-
|
|
1438
|
-
return content;
|
|
1439
|
-
}
|
|
1440
|
-
|
|
1441
|
-
getIcon() {
|
|
1442
|
-
return '🔐';
|
|
1443
|
-
}
|
|
1444
|
-
}
|
|
1445
|
-
|
|
1446
|
-
// Handler for MCP Social Media Create Post tool
|
|
1447
|
-
class SocialMediaCreatePostHandler extends ToolHandler {
|
|
1448
|
-
constructor() {
|
|
1449
|
-
super('mcp__socialmedia__create_post');
|
|
1450
|
-
}
|
|
1451
|
-
|
|
1452
|
-
renderInput(input) {
|
|
1453
|
-
const content = document.createElement('div');
|
|
1454
|
-
content.className = 'tool-call-content';
|
|
1455
|
-
|
|
1456
|
-
const postDiv = document.createElement('div');
|
|
1457
|
-
postDiv.style.padding = '12px';
|
|
1458
|
-
postDiv.style.background = '#f0f9ff';
|
|
1459
|
-
postDiv.style.borderRadius = '8px';
|
|
1460
|
-
postDiv.style.border = '1px solid #bae6fd';
|
|
1461
|
-
|
|
1462
|
-
const header = document.createElement('div');
|
|
1463
|
-
header.style.fontWeight = 'bold';
|
|
1464
|
-
header.style.marginBottom = '12px';
|
|
1465
|
-
header.style.color = '#2c3e50';
|
|
1466
|
-
header.textContent = '📱 Creating Social Media Post';
|
|
1467
|
-
|
|
1468
|
-
const postContent = document.createElement('div');
|
|
1469
|
-
postContent.style.padding = '12px';
|
|
1470
|
-
postContent.style.background = '#ffffff';
|
|
1471
|
-
postContent.style.borderRadius = '6px';
|
|
1472
|
-
postContent.style.border = '1px solid #e5e7eb';
|
|
1473
|
-
postContent.style.marginBottom = '8px';
|
|
1474
|
-
postContent.style.lineHeight = '1.5';
|
|
1475
|
-
postContent.textContent = input.content || '';
|
|
1476
|
-
|
|
1477
|
-
postDiv.appendChild(header);
|
|
1478
|
-
postDiv.appendChild(postContent);
|
|
1479
|
-
|
|
1480
|
-
if (
|
|
1481
|
-
input.tags &&
|
|
1482
|
-
Array.isArray(input.tags) &&
|
|
1483
|
-
input.tags.length > 0
|
|
1484
|
-
) {
|
|
1485
|
-
const tagsDiv = document.createElement('div');
|
|
1486
|
-
tagsDiv.style.marginTop = '8px';
|
|
1487
|
-
|
|
1488
|
-
const tagsLabel = document.createElement('span');
|
|
1489
|
-
tagsLabel.style.fontSize = '0.85em';
|
|
1490
|
-
tagsLabel.style.color = '#666';
|
|
1491
|
-
tagsLabel.textContent = 'Tags: ';
|
|
1492
|
-
tagsDiv.appendChild(tagsLabel);
|
|
1493
|
-
|
|
1494
|
-
input.tags.forEach((tag) => {
|
|
1495
|
-
const tagSpan = document.createElement('span');
|
|
1496
|
-
tagSpan.style.display = 'inline-block';
|
|
1497
|
-
tagSpan.style.background = '#3b82f6';
|
|
1498
|
-
tagSpan.style.color = 'white';
|
|
1499
|
-
tagSpan.style.padding = '2px 6px';
|
|
1500
|
-
tagSpan.style.borderRadius = '12px';
|
|
1501
|
-
tagSpan.style.fontSize = '0.8em';
|
|
1502
|
-
tagSpan.style.marginRight = '4px';
|
|
1503
|
-
tagSpan.textContent = `#${tag}`;
|
|
1504
|
-
tagsDiv.appendChild(tagSpan);
|
|
1505
|
-
});
|
|
1506
|
-
|
|
1507
|
-
postDiv.appendChild(tagsDiv);
|
|
1508
|
-
}
|
|
1509
|
-
|
|
1510
|
-
content.appendChild(postDiv);
|
|
1511
|
-
return content;
|
|
1512
|
-
}
|
|
1513
|
-
|
|
1514
|
-
renderOutput(result, toolCall) {
|
|
1515
|
-
const content = document.createElement('div');
|
|
1516
|
-
content.className = 'tool-result-content';
|
|
1517
|
-
|
|
1518
|
-
try {
|
|
1519
|
-
// Try to parse JSON from the text result
|
|
1520
|
-
let jsonData;
|
|
1521
|
-
if (Array.isArray(result) && result[0] && result[0].text) {
|
|
1522
|
-
jsonData = JSON.parse(result[0].text);
|
|
1523
|
-
} else if (typeof result === 'string') {
|
|
1524
|
-
jsonData = JSON.parse(result);
|
|
1525
|
-
} else {
|
|
1526
|
-
jsonData = result;
|
|
1527
|
-
}
|
|
1528
|
-
|
|
1529
|
-
const resultDiv = document.createElement('div');
|
|
1530
|
-
resultDiv.style.padding = '12px';
|
|
1531
|
-
resultDiv.style.borderRadius = '6px';
|
|
1532
|
-
|
|
1533
|
-
if (jsonData.success && jsonData.post) {
|
|
1534
|
-
resultDiv.style.background = '#d4edda';
|
|
1535
|
-
resultDiv.style.border = '1px solid #c3e6cb';
|
|
1536
|
-
resultDiv.style.color = '#155724';
|
|
1537
|
-
|
|
1538
|
-
const successMsg = document.createElement('div');
|
|
1539
|
-
successMsg.innerHTML =
|
|
1540
|
-
'✅ <strong>Post Created Successfully!</strong>';
|
|
1541
|
-
successMsg.style.marginBottom = '8px';
|
|
1542
|
-
|
|
1543
|
-
const postInfo = document.createElement('div');
|
|
1544
|
-
postInfo.style.fontSize = '0.9em';
|
|
1545
|
-
postInfo.innerHTML = `
|
|
1546
|
-
<div><strong>Post ID:</strong> ${jsonData.post.id}</div>
|
|
1547
|
-
<div><strong>Author:</strong> ${jsonData.post.author_name}</div>
|
|
1548
|
-
<div><strong>Timestamp:</strong> ${new Date(jsonData.post.timestamp).toLocaleString()}</div>
|
|
1549
|
-
`;
|
|
1550
|
-
|
|
1551
|
-
resultDiv.appendChild(successMsg);
|
|
1552
|
-
resultDiv.appendChild(postInfo);
|
|
1553
|
-
} else {
|
|
1554
|
-
resultDiv.style.background = '#f8d7da';
|
|
1555
|
-
resultDiv.style.border = '1px solid #f5c6cb';
|
|
1556
|
-
resultDiv.style.color = '#721c24';
|
|
1557
|
-
resultDiv.textContent = '❌ Failed to create post';
|
|
1558
|
-
}
|
|
1559
|
-
|
|
1560
|
-
content.appendChild(resultDiv);
|
|
1561
|
-
} catch (e) {
|
|
1562
|
-
// Fallback to default text rendering
|
|
1563
|
-
content.style.whiteSpace = 'pre-wrap';
|
|
1564
|
-
content.textContent =
|
|
1565
|
-
typeof result === 'string'
|
|
1566
|
-
? result
|
|
1567
|
-
: JSON.stringify(result, null, 2);
|
|
1568
|
-
}
|
|
1569
|
-
|
|
1570
|
-
return content;
|
|
1571
|
-
}
|
|
1572
|
-
|
|
1573
|
-
getIcon() {
|
|
1574
|
-
return '📱';
|
|
1575
|
-
}
|
|
1576
|
-
}
|
|
1577
|
-
|
|
1578
|
-
// Handler for Task tool
|
|
1579
|
-
class TaskHandler extends ToolHandler {
|
|
1580
|
-
constructor() {
|
|
1581
|
-
super('Task');
|
|
1582
|
-
}
|
|
1583
|
-
|
|
1584
|
-
renderInput(input) {
|
|
1585
|
-
const content = document.createElement('div');
|
|
1586
|
-
content.className = 'tool-call-content';
|
|
1587
|
-
|
|
1588
|
-
const taskDiv = document.createElement('div');
|
|
1589
|
-
taskDiv.style.padding = '12px';
|
|
1590
|
-
taskDiv.style.background = '#fff7ed';
|
|
1591
|
-
taskDiv.style.borderRadius = '8px';
|
|
1592
|
-
taskDiv.style.border = '1px solid #fed7aa';
|
|
1593
|
-
|
|
1594
|
-
const header = document.createElement('div');
|
|
1595
|
-
header.style.fontWeight = 'bold';
|
|
1596
|
-
header.style.marginBottom = '12px';
|
|
1597
|
-
header.style.color = '#ea580c';
|
|
1598
|
-
header.textContent = `🎯 Task: ${input.description || 'Unnamed Task'}`;
|
|
1599
|
-
|
|
1600
|
-
if (input.prompt) {
|
|
1601
|
-
const promptDiv = document.createElement('div');
|
|
1602
|
-
promptDiv.style.background = '#ffffff';
|
|
1603
|
-
promptDiv.style.padding = '12px';
|
|
1604
|
-
promptDiv.style.borderRadius = '6px';
|
|
1605
|
-
promptDiv.style.border = '1px solid #e5e7eb';
|
|
1606
|
-
promptDiv.style.lineHeight = '1.5';
|
|
1607
|
-
promptDiv.style.whiteSpace = 'pre-wrap';
|
|
1608
|
-
promptDiv.textContent = input.prompt;
|
|
1609
|
-
|
|
1610
|
-
const promptLabel = document.createElement('div');
|
|
1611
|
-
promptLabel.style.fontWeight = 'bold';
|
|
1612
|
-
promptLabel.style.marginBottom = '8px';
|
|
1613
|
-
promptLabel.style.color = '#2c3e50';
|
|
1614
|
-
promptLabel.textContent = '📋 Task Instructions';
|
|
1615
|
-
|
|
1616
|
-
taskDiv.appendChild(header);
|
|
1617
|
-
taskDiv.appendChild(promptLabel);
|
|
1618
|
-
taskDiv.appendChild(promptDiv);
|
|
1619
|
-
} else {
|
|
1620
|
-
taskDiv.appendChild(header);
|
|
1621
|
-
}
|
|
1622
|
-
|
|
1623
|
-
content.appendChild(taskDiv);
|
|
1624
|
-
return content;
|
|
1625
|
-
}
|
|
1626
|
-
|
|
1627
|
-
renderOutput(result, toolCall) {
|
|
1628
|
-
const content = document.createElement('div');
|
|
1629
|
-
content.className = 'tool-result-content';
|
|
1630
|
-
content.style.maxHeight = '400px';
|
|
1631
|
-
content.style.overflowY = 'auto';
|
|
1632
|
-
content.style.lineHeight = '1.5';
|
|
1633
|
-
content.style.whiteSpace = 'pre-wrap';
|
|
1634
|
-
|
|
1635
|
-
// Try to extract text from array format
|
|
1636
|
-
if (Array.isArray(result) && result[0] && result[0].text) {
|
|
1637
|
-
content.textContent = result[0].text;
|
|
1638
|
-
} else {
|
|
1639
|
-
content.textContent =
|
|
1640
|
-
typeof result === 'string'
|
|
1641
|
-
? result
|
|
1642
|
-
: JSON.stringify(result, null, 2);
|
|
1643
|
-
}
|
|
1644
|
-
|
|
1645
|
-
return content;
|
|
1646
|
-
}
|
|
1647
|
-
|
|
1648
|
-
getIcon() {
|
|
1649
|
-
return '🎯';
|
|
1650
|
-
}
|
|
1651
|
-
}
|
|
1652
|
-
|
|
1653
|
-
// Handler for WebFetch tool
|
|
1654
|
-
class WebFetchHandler extends ToolHandler {
|
|
1655
|
-
constructor() {
|
|
1656
|
-
super('WebFetch');
|
|
1657
|
-
}
|
|
1658
|
-
|
|
1659
|
-
renderInput(input) {
|
|
1660
|
-
const content = document.createElement('div');
|
|
1661
|
-
content.className = 'tool-call-content';
|
|
1662
|
-
|
|
1663
|
-
const webFetch = document.createElement('div');
|
|
1664
|
-
webFetch.style.marginTop = '8px';
|
|
1665
|
-
|
|
1666
|
-
// URL section
|
|
1667
|
-
const urlDiv = document.createElement('div');
|
|
1668
|
-
urlDiv.style.marginBottom = '12px';
|
|
1669
|
-
|
|
1670
|
-
const urlLabel = document.createElement('div');
|
|
1671
|
-
urlLabel.style.fontWeight = 'bold';
|
|
1672
|
-
urlLabel.style.marginBottom = '4px';
|
|
1673
|
-
urlLabel.style.color = '#2c3e50';
|
|
1674
|
-
urlLabel.textContent = '🌐 URL';
|
|
1675
|
-
|
|
1676
|
-
const urlContent = document.createElement('div');
|
|
1677
|
-
urlContent.style.padding = '8px';
|
|
1678
|
-
urlContent.style.background = '#f8f9fa';
|
|
1679
|
-
urlContent.style.borderRadius = '4px';
|
|
1680
|
-
urlContent.style.border = '1px solid #dee2e6';
|
|
1681
|
-
urlContent.style.fontFamily = 'monospace';
|
|
1682
|
-
urlContent.style.fontSize = '0.9em';
|
|
1683
|
-
urlContent.style.wordBreak = 'break-all';
|
|
1684
|
-
|
|
1685
|
-
// Make URL clickable
|
|
1686
|
-
const urlLink = document.createElement('a');
|
|
1687
|
-
urlLink.href = input.url;
|
|
1688
|
-
urlLink.target = '_blank';
|
|
1689
|
-
urlLink.rel = 'noopener noreferrer';
|
|
1690
|
-
urlLink.style.color = '#007bff';
|
|
1691
|
-
urlLink.style.textDecoration = 'none';
|
|
1692
|
-
urlLink.textContent = input.url;
|
|
1693
|
-
urlLink.onmouseover = () =>
|
|
1694
|
-
(urlLink.style.textDecoration = 'underline');
|
|
1695
|
-
urlLink.onmouseout = () => (urlLink.style.textDecoration = 'none');
|
|
1696
|
-
|
|
1697
|
-
urlContent.appendChild(urlLink);
|
|
1698
|
-
urlDiv.appendChild(urlLabel);
|
|
1699
|
-
urlDiv.appendChild(urlContent);
|
|
1700
|
-
|
|
1701
|
-
// Prompt section
|
|
1702
|
-
if (input.prompt) {
|
|
1703
|
-
const promptDiv = document.createElement('div');
|
|
1704
|
-
promptDiv.style.marginBottom = '12px';
|
|
1705
|
-
|
|
1706
|
-
const promptLabel = document.createElement('div');
|
|
1707
|
-
promptLabel.style.fontWeight = 'bold';
|
|
1708
|
-
promptLabel.style.marginBottom = '4px';
|
|
1709
|
-
promptLabel.style.color = '#2c3e50';
|
|
1710
|
-
promptLabel.textContent = '📝 Analysis Prompt';
|
|
1711
|
-
|
|
1712
|
-
const promptContent = document.createElement('div');
|
|
1713
|
-
promptContent.style.padding = '12px';
|
|
1714
|
-
promptContent.style.background = '#e8f4fd';
|
|
1715
|
-
promptContent.style.borderRadius = '4px';
|
|
1716
|
-
promptContent.style.border = '1px solid #b8daff';
|
|
1717
|
-
promptContent.style.lineHeight = '1.5';
|
|
1718
|
-
promptContent.style.fontSize = '0.9em';
|
|
1719
|
-
promptContent.style.whiteSpace = 'pre-wrap';
|
|
1720
|
-
promptContent.textContent = input.prompt;
|
|
1721
|
-
|
|
1722
|
-
promptDiv.appendChild(promptLabel);
|
|
1723
|
-
promptDiv.appendChild(promptContent);
|
|
1724
|
-
webFetch.appendChild(promptDiv);
|
|
1725
|
-
}
|
|
1726
|
-
|
|
1727
|
-
webFetch.appendChild(urlDiv);
|
|
1728
|
-
content.appendChild(webFetch);
|
|
1729
|
-
return content;
|
|
1730
|
-
}
|
|
1731
|
-
|
|
1732
|
-
renderOutput(result, toolCall) {
|
|
1733
|
-
const content = document.createElement('div');
|
|
1734
|
-
content.className = 'tool-result-content';
|
|
1735
|
-
content.style.maxHeight = '400px';
|
|
1736
|
-
content.style.overflowY = 'auto';
|
|
1737
|
-
content.style.lineHeight = '1.5';
|
|
1738
|
-
content.style.whiteSpace = 'pre-wrap';
|
|
1739
|
-
content.textContent = result;
|
|
1740
|
-
return content;
|
|
1741
|
-
}
|
|
1742
|
-
|
|
1743
|
-
getIcon() {
|
|
1744
|
-
return '🌐';
|
|
1745
|
-
}
|
|
1746
|
-
}
|
|
1747
|
-
|
|
1748
|
-
// Tool handler registry
|
|
1749
|
-
const toolHandlers = {
|
|
1750
|
-
Bash: new BashHandler(),
|
|
1751
|
-
Read: new ReadHandler(),
|
|
1752
|
-
Edit: new EditHandler(),
|
|
1753
|
-
MultiEdit: new MultiEditHandler(),
|
|
1754
|
-
Write: new WriteHandler(),
|
|
1755
|
-
LS: new LSHandler(),
|
|
1756
|
-
Grep: new GrepHandler(),
|
|
1757
|
-
Glob: new GlobHandler(),
|
|
1758
|
-
TodoWrite: new TodoWriteHandler(),
|
|
1759
|
-
'mcp__private-journal__process_thoughts': new PrivateJournalHandler(),
|
|
1760
|
-
WebFetch: new WebFetchHandler(),
|
|
1761
|
-
mcp__socialmedia__login: new SocialMediaLoginHandler(),
|
|
1762
|
-
mcp__socialmedia__create_post: new SocialMediaCreatePostHandler(),
|
|
1763
|
-
Task: new TaskHandler(),
|
|
1764
|
-
};
|
|
1765
|
-
|
|
1766
|
-
// Get appropriate handler for a tool
|
|
1767
|
-
function getToolHandler(toolName) {
|
|
1768
|
-
return toolHandlers[toolName] || new ToolHandler(toolName);
|
|
1769
|
-
}
|
|
1770
|
-
|
|
1771
|
-
// WebSocket Watch Manager for real-time updates
|
|
1772
|
-
class WatchManager {
|
|
1773
|
-
constructor() {
|
|
1774
|
-
this.ws = null;
|
|
1775
|
-
this.subscribedProjects = new Set();
|
|
1776
|
-
this.subscribedSessions = new Set();
|
|
1777
|
-
this.activityIndicators = new Map();
|
|
1778
|
-
this.reconnectAttempts = 0;
|
|
1779
|
-
this.maxReconnectAttempts = 5;
|
|
1780
|
-
this.reconnectDelay = 1000;
|
|
1781
|
-
this.isWatching = false;
|
|
1782
|
-
this.shouldReconnect = false;
|
|
1783
|
-
this.cleanupTimer = null;
|
|
1784
|
-
}
|
|
1785
|
-
|
|
1786
|
-
connect() {
|
|
1787
|
-
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
|
1788
|
-
return;
|
|
1789
|
-
}
|
|
1790
|
-
|
|
1791
|
-
const protocol =
|
|
1792
|
-
window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
1793
|
-
const wsUrl = `${protocol}//${window.location.host}/ws/watch`;
|
|
1794
|
-
|
|
1795
|
-
try {
|
|
1796
|
-
this.ws = new WebSocket(wsUrl);
|
|
1797
|
-
|
|
1798
|
-
this.ws.onopen = () => {
|
|
1799
|
-
console.log('WebSocket connected');
|
|
1800
|
-
this.reconnectAttempts = 0;
|
|
1801
|
-
this.isWatching = true;
|
|
1802
|
-
this.updateWatchStatus('connected');
|
|
1803
|
-
};
|
|
1804
|
-
|
|
1805
|
-
this.ws.onmessage = (event) => {
|
|
1806
|
-
try {
|
|
1807
|
-
const watchEvent = JSON.parse(event.data);
|
|
1808
|
-
this.handleWatchEvent(watchEvent);
|
|
1809
|
-
} catch (e) {
|
|
1810
|
-
console.error('Failed to parse watch event:', e);
|
|
1811
|
-
}
|
|
1812
|
-
};
|
|
1813
|
-
|
|
1814
|
-
this.ws.onclose = () => {
|
|
1815
|
-
console.log('WebSocket disconnected');
|
|
1816
|
-
this.isWatching = false;
|
|
1817
|
-
this.updateWatchStatus('disconnected');
|
|
1818
|
-
if (this.shouldReconnect) {
|
|
1819
|
-
this.scheduleReconnect();
|
|
1820
|
-
}
|
|
1821
|
-
};
|
|
1822
|
-
|
|
1823
|
-
this.ws.onerror = (error) => {
|
|
1824
|
-
console.error('WebSocket error:', error);
|
|
1825
|
-
this.updateWatchStatus('error');
|
|
1826
|
-
};
|
|
1827
|
-
} catch (e) {
|
|
1828
|
-
console.error('Failed to create WebSocket connection:', e);
|
|
1829
|
-
this.scheduleReconnect();
|
|
1830
|
-
}
|
|
1831
|
-
}
|
|
1832
|
-
|
|
1833
|
-
disconnect() {
|
|
1834
|
-
this.isWatching = false;
|
|
1835
|
-
this.shouldReconnect = false;
|
|
1836
|
-
this.reconnectAttempts = 0;
|
|
1837
|
-
this.stopCleanupTimer(); // Stop periodic cleanup
|
|
1838
|
-
if (this.ws) {
|
|
1839
|
-
this.ws.close();
|
|
1840
|
-
this.ws = null;
|
|
1841
|
-
}
|
|
1842
|
-
this.updateWatchStatus('disconnected');
|
|
1843
|
-
}
|
|
1844
|
-
|
|
1845
|
-
scheduleReconnect() {
|
|
1846
|
-
if (
|
|
1847
|
-
!this.shouldReconnect ||
|
|
1848
|
-
this.reconnectAttempts >= this.maxReconnectAttempts
|
|
1849
|
-
) {
|
|
1850
|
-
if (this.reconnectAttempts >= this.maxReconnectAttempts) {
|
|
1851
|
-
console.error('Max reconnection attempts reached');
|
|
1852
|
-
this.updateWatchStatus('failed');
|
|
1853
|
-
}
|
|
1854
|
-
return;
|
|
1855
|
-
}
|
|
1856
|
-
|
|
1857
|
-
this.reconnectAttempts++;
|
|
1858
|
-
const delay =
|
|
1859
|
-
this.reconnectDelay * Math.pow(2, this.reconnectAttempts - 1);
|
|
1860
|
-
|
|
1861
|
-
setTimeout(() => {
|
|
1862
|
-
if (this.shouldReconnect && !this.isWatching) {
|
|
1863
|
-
console.log(
|
|
1864
|
-
`Attempting to reconnect (${this.reconnectAttempts}/${this.maxReconnectAttempts})`,
|
|
1865
|
-
);
|
|
1866
|
-
this.connect();
|
|
1867
|
-
}
|
|
1868
|
-
}, delay);
|
|
1869
|
-
}
|
|
1870
|
-
|
|
1871
|
-
handleWatchEvent(watchEvent) {
|
|
1872
|
-
console.log('Received watch event:', watchEvent);
|
|
1873
|
-
|
|
1874
|
-
switch (watchEvent.type) {
|
|
1875
|
-
case 'log_entry':
|
|
1876
|
-
this.handleNewLogEntry(watchEvent);
|
|
1877
|
-
break;
|
|
1878
|
-
case 'session_created':
|
|
1879
|
-
this.handleSessionCreated(watchEvent);
|
|
1880
|
-
break;
|
|
1881
|
-
case 'project_activity':
|
|
1882
|
-
this.handleProjectActivity(watchEvent);
|
|
1883
|
-
break;
|
|
1884
|
-
default:
|
|
1885
|
-
console.log('Unknown watch event type:', watchEvent.type);
|
|
1886
|
-
}
|
|
1887
|
-
}
|
|
1888
|
-
|
|
1889
|
-
handleNewLogEntry(watchEvent) {
|
|
1890
|
-
// Only handle if we're currently viewing this session
|
|
1891
|
-
if (
|
|
1892
|
-
currentProject === watchEvent.project &&
|
|
1893
|
-
currentSession === watchEvent.session
|
|
1894
|
-
) {
|
|
1895
|
-
this.appendLogEntry(watchEvent.entry);
|
|
1896
|
-
}
|
|
1897
|
-
|
|
1898
|
-
// Show activity indicator
|
|
1899
|
-
this.showActivityIndicator(watchEvent.project, watchEvent.session);
|
|
1900
|
-
}
|
|
1901
|
-
|
|
1902
|
-
handleSessionCreated(watchEvent) {
|
|
1903
|
-
// If we're viewing the project's sessions, refresh the list
|
|
1904
|
-
if (currentProject === watchEvent.project && !currentSession) {
|
|
1905
|
-
// Add a small delay to ensure the file is fully written
|
|
1906
|
-
setTimeout(() => {
|
|
1907
|
-
loadSessions(currentProject, false);
|
|
1908
|
-
}, 500);
|
|
1909
|
-
}
|
|
1910
|
-
}
|
|
1911
|
-
|
|
1912
|
-
handleProjectActivity(watchEvent) {
|
|
1913
|
-
this.showActivityIndicator(watchEvent.project, null);
|
|
1914
|
-
}
|
|
1915
|
-
|
|
1916
|
-
appendLogEntry(entry) {
|
|
1917
|
-
if (!entry) return;
|
|
1918
|
-
|
|
1919
|
-
const logEntries = document.getElementById('log-entries');
|
|
1920
|
-
if (!logEntries) return;
|
|
1921
|
-
|
|
1922
|
-
// Defensive checks for entry structure
|
|
1923
|
-
try {
|
|
1924
|
-
// Validate entry has required type property
|
|
1925
|
-
if (typeof entry.type !== 'string' || !entry.type) {
|
|
1926
|
-
console.warn(
|
|
1927
|
-
'appendLogEntry: Invalid or missing entry.type',
|
|
1928
|
-
entry,
|
|
1929
|
-
);
|
|
1930
|
-
return;
|
|
1931
|
-
}
|
|
1932
|
-
|
|
1933
|
-
// Validate entry has message object
|
|
1934
|
-
if (!entry.message || typeof entry.message !== 'object') {
|
|
1935
|
-
console.warn(
|
|
1936
|
-
'appendLogEntry: Invalid or missing entry.message',
|
|
1937
|
-
entry,
|
|
1938
|
-
);
|
|
1939
|
-
return;
|
|
1940
|
-
}
|
|
1941
|
-
|
|
1942
|
-
// Validate message has content property
|
|
1943
|
-
if (!entry.message.hasOwnProperty('content')) {
|
|
1944
|
-
console.warn(
|
|
1945
|
-
'appendLogEntry: Missing entry.message.content',
|
|
1946
|
-
entry,
|
|
1947
|
-
);
|
|
1948
|
-
return;
|
|
1949
|
-
}
|
|
1950
|
-
|
|
1951
|
-
// Create a new message element
|
|
1952
|
-
const messageDiv = document.createElement('div');
|
|
1953
|
-
messageDiv.className = 'message assistant';
|
|
1954
|
-
messageDiv.style.opacity = '0';
|
|
1955
|
-
messageDiv.style.transition = 'opacity 0.3s ease';
|
|
1956
|
-
|
|
1957
|
-
// Determine the content based on entry type
|
|
1958
|
-
let content = '';
|
|
1959
|
-
|
|
1960
|
-
if (entry.type === 'assistant') {
|
|
1961
|
-
messageDiv.className = 'message assistant';
|
|
1962
|
-
content = this.extractTextContent(entry.message.content);
|
|
1963
|
-
} else if (entry.type === 'user') {
|
|
1964
|
-
messageDiv.className = 'message user';
|
|
1965
|
-
content = this.extractTextContent(entry.message.content);
|
|
1966
|
-
} else {
|
|
1967
|
-
// Unknown entry type, skip processing
|
|
1968
|
-
console.warn('appendLogEntry: Unknown entry type:', entry.type);
|
|
1969
|
-
return;
|
|
1970
|
-
}
|
|
1971
|
-
} catch (error) {
|
|
1972
|
-
console.error(
|
|
1973
|
-
'appendLogEntry: Error processing entry:',
|
|
1974
|
-
error,
|
|
1975
|
-
entry,
|
|
1976
|
-
);
|
|
1977
|
-
return;
|
|
1978
|
-
}
|
|
1979
|
-
|
|
1980
|
-
if (content && content.trim()) {
|
|
1981
|
-
const avatar = document.createElement('div');
|
|
1982
|
-
avatar.className =
|
|
1983
|
-
entry.type === 'user' ? 'avatar user' : 'avatar assistant';
|
|
1984
|
-
avatar.textContent = entry.type === 'user' ? 'U' : 'AI';
|
|
1985
|
-
|
|
1986
|
-
const messageContent = document.createElement('div');
|
|
1987
|
-
messageContent.className = 'message-content';
|
|
1988
|
-
|
|
1989
|
-
const messageText = document.createElement('div');
|
|
1990
|
-
messageText.className = 'message-text';
|
|
1991
|
-
messageText.textContent = content;
|
|
1992
|
-
|
|
1993
|
-
const messageMeta = document.createElement('div');
|
|
1994
|
-
messageMeta.className = 'message-meta';
|
|
1995
|
-
try {
|
|
1996
|
-
const timestamp = entry.timestamp
|
|
1997
|
-
? new Date(entry.timestamp).toLocaleString()
|
|
1998
|
-
: '';
|
|
1999
|
-
messageMeta.textContent = timestamp;
|
|
2000
|
-
} catch (error) {
|
|
2001
|
-
console.warn(
|
|
2002
|
-
'appendLogEntry: Invalid timestamp:',
|
|
2003
|
-
entry.timestamp,
|
|
2004
|
-
);
|
|
2005
|
-
messageMeta.textContent = '';
|
|
2006
|
-
}
|
|
2007
|
-
|
|
2008
|
-
messageContent.appendChild(messageText);
|
|
2009
|
-
messageContent.appendChild(messageMeta);
|
|
2010
|
-
messageDiv.appendChild(avatar);
|
|
2011
|
-
messageDiv.appendChild(messageContent);
|
|
2012
|
-
|
|
2013
|
-
// Add highlight border for new entries
|
|
2014
|
-
messageDiv.style.borderLeft = '4px solid #28a745';
|
|
2015
|
-
messageDiv.style.paddingLeft = '1rem';
|
|
2016
|
-
|
|
2017
|
-
logEntries.appendChild(messageDiv);
|
|
2018
|
-
|
|
2019
|
-
// Animate the entry in
|
|
2020
|
-
setTimeout(() => {
|
|
2021
|
-
messageDiv.style.opacity = '1';
|
|
2022
|
-
}, 10);
|
|
2023
|
-
|
|
2024
|
-
// Remove highlight after a few seconds
|
|
2025
|
-
setTimeout(() => {
|
|
2026
|
-
messageDiv.style.borderLeft = 'none';
|
|
2027
|
-
messageDiv.style.paddingLeft = '0';
|
|
2028
|
-
}, 3000);
|
|
2029
|
-
|
|
2030
|
-
// Auto-scroll to the new entry unless user has scrolled up
|
|
2031
|
-
this.autoScrollToBottom();
|
|
2032
|
-
}
|
|
2033
|
-
}
|
|
2034
|
-
|
|
2035
|
-
// Helper method to safely extract text content from various content formats
|
|
2036
|
-
extractTextContent(content) {
|
|
2037
|
-
try {
|
|
2038
|
-
if (!content) return '';
|
|
2039
|
-
|
|
2040
|
-
if (typeof content === 'string') {
|
|
2041
|
-
return content;
|
|
2042
|
-
}
|
|
2043
|
-
|
|
2044
|
-
if (Array.isArray(content)) {
|
|
2045
|
-
return content
|
|
2046
|
-
.filter(
|
|
2047
|
-
(c) =>
|
|
2048
|
-
c &&
|
|
2049
|
-
typeof c === 'object' &&
|
|
2050
|
-
c.type === 'text' &&
|
|
2051
|
-
typeof c.text === 'string',
|
|
2052
|
-
)
|
|
2053
|
-
.map((c) => c.text)
|
|
2054
|
-
.join('\n');
|
|
2055
|
-
}
|
|
2056
|
-
|
|
2057
|
-
// If content is an object but not an array, try to extract meaningful text
|
|
2058
|
-
if (typeof content === 'object') {
|
|
2059
|
-
if (content.text && typeof content.text === 'string') {
|
|
2060
|
-
return content.text;
|
|
2061
|
-
}
|
|
2062
|
-
if (content.content && typeof content.content === 'string') {
|
|
2063
|
-
return content.content;
|
|
2064
|
-
}
|
|
2065
|
-
}
|
|
2066
|
-
|
|
2067
|
-
return '';
|
|
2068
|
-
} catch (error) {
|
|
2069
|
-
console.warn(
|
|
2070
|
-
'extractTextContent: Error extracting text:',
|
|
2071
|
-
error,
|
|
2072
|
-
content,
|
|
2073
|
-
);
|
|
2074
|
-
return '';
|
|
2075
|
-
}
|
|
2076
|
-
}
|
|
2077
|
-
|
|
2078
|
-
showActivityIndicator(project, session) {
|
|
2079
|
-
// Create activity indicator for the project or session
|
|
2080
|
-
const key = session ? `${project}:${session}` : project;
|
|
2081
|
-
|
|
2082
|
-
// Store activity timestamp
|
|
2083
|
-
this.activityIndicators.set(key, Date.now());
|
|
2084
|
-
|
|
2085
|
-
// Add visual indicator if element exists
|
|
2086
|
-
this.addActivityBadge(project, session);
|
|
2087
|
-
|
|
2088
|
-
// Remove indicator after a delay
|
|
2089
|
-
setTimeout(() => {
|
|
2090
|
-
this.removeActivityBadge(project, session);
|
|
2091
|
-
}, 5000);
|
|
2092
|
-
}
|
|
2093
|
-
|
|
2094
|
-
addActivityBadge(project, session) {
|
|
2095
|
-
// Find the relevant DOM element and add activity indicator
|
|
2096
|
-
let targetElement = null;
|
|
2097
|
-
|
|
2098
|
-
if (session && currentProject === project) {
|
|
2099
|
-
// Find session item
|
|
2100
|
-
const sessionItems = document.querySelectorAll('.session-item');
|
|
2101
|
-
sessionItems.forEach((item) => {
|
|
2102
|
-
if (item.textContent.includes(session)) {
|
|
2103
|
-
targetElement = item;
|
|
2104
|
-
}
|
|
2105
|
-
});
|
|
2106
|
-
} else {
|
|
2107
|
-
// Find project card
|
|
2108
|
-
const projectCards = document.querySelectorAll('.project-card');
|
|
2109
|
-
projectCards.forEach((card) => {
|
|
2110
|
-
if (card.textContent.includes(project)) {
|
|
2111
|
-
targetElement = card;
|
|
2112
|
-
}
|
|
2113
|
-
});
|
|
2114
|
-
}
|
|
2115
|
-
|
|
2116
|
-
if (
|
|
2117
|
-
targetElement &&
|
|
2118
|
-
!targetElement.querySelector('.activity-badge')
|
|
2119
|
-
) {
|
|
2120
|
-
const badge = document.createElement('div');
|
|
2121
|
-
badge.className = 'activity-badge';
|
|
2122
|
-
badge.style.cssText = `
|
|
2123
|
-
position: absolute;
|
|
2124
|
-
top: 10px;
|
|
2125
|
-
right: 10px;
|
|
2126
|
-
width: 12px;
|
|
2127
|
-
height: 12px;
|
|
2128
|
-
background: #28a745;
|
|
2129
|
-
border-radius: 50%;
|
|
2130
|
-
animation: pulse 1.5s infinite;
|
|
2131
|
-
`;
|
|
2132
|
-
|
|
2133
|
-
targetElement.style.position = 'relative';
|
|
2134
|
-
targetElement.appendChild(badge);
|
|
2135
|
-
}
|
|
2136
|
-
}
|
|
2137
|
-
|
|
2138
|
-
removeActivityBadge(project, session) {
|
|
2139
|
-
// Remove activity badge
|
|
2140
|
-
let targetElement = null;
|
|
2141
|
-
|
|
2142
|
-
if (session && currentProject === project) {
|
|
2143
|
-
const sessionItems = document.querySelectorAll('.session-item');
|
|
2144
|
-
sessionItems.forEach((item) => {
|
|
2145
|
-
if (item.textContent.includes(session)) {
|
|
2146
|
-
targetElement = item;
|
|
2147
|
-
}
|
|
2148
|
-
});
|
|
2149
|
-
} else {
|
|
2150
|
-
const projectCards = document.querySelectorAll('.project-card');
|
|
2151
|
-
projectCards.forEach((card) => {
|
|
2152
|
-
if (card.textContent.includes(project)) {
|
|
2153
|
-
targetElement = card;
|
|
2154
|
-
}
|
|
2155
|
-
});
|
|
2156
|
-
}
|
|
2157
|
-
|
|
2158
|
-
if (targetElement) {
|
|
2159
|
-
const badge = targetElement.querySelector('.activity-badge');
|
|
2160
|
-
if (badge) {
|
|
2161
|
-
badge.remove();
|
|
2162
|
-
}
|
|
2163
|
-
}
|
|
2164
|
-
}
|
|
2165
|
-
|
|
2166
|
-
cleanupStaleActivityBadges() {
|
|
2167
|
-
const STALE_THRESHOLD = 30000; // 30 seconds
|
|
2168
|
-
const now = Date.now();
|
|
2169
|
-
const staleBadges = [];
|
|
2170
|
-
|
|
2171
|
-
// Find stale entries in the activityIndicators map
|
|
2172
|
-
for (const [key, timestamp] of this.activityIndicators.entries()) {
|
|
2173
|
-
if (now - timestamp > STALE_THRESHOLD) {
|
|
2174
|
-
staleBadges.push(key);
|
|
2175
|
-
}
|
|
2176
|
-
}
|
|
2177
|
-
|
|
2178
|
-
// Clean up stale entries and their DOM badges
|
|
2179
|
-
staleBadges.forEach((key) => {
|
|
2180
|
-
const [project, session] = key.includes(':')
|
|
2181
|
-
? key.split(':')
|
|
2182
|
-
: [key, null];
|
|
2183
|
-
|
|
2184
|
-
// Remove from map
|
|
2185
|
-
this.activityIndicators.delete(key);
|
|
2186
|
-
|
|
2187
|
-
// Remove DOM badge if it still exists
|
|
2188
|
-
this.removeActivityBadge(project, session);
|
|
2189
|
-
});
|
|
2190
|
-
|
|
2191
|
-
// Also clean up any orphaned DOM badges that might exist
|
|
2192
|
-
const allBadges = document.querySelectorAll('.activity-badge');
|
|
2193
|
-
allBadges.forEach((badge) => {
|
|
2194
|
-
const parent = badge.parentElement;
|
|
2195
|
-
if (!parent || !parent.isConnected) {
|
|
2196
|
-
// Badge's parent is no longer in the DOM, remove badge
|
|
2197
|
-
badge.remove();
|
|
2198
|
-
}
|
|
2199
|
-
});
|
|
2200
|
-
|
|
2201
|
-
if (staleBadges.length > 0) {
|
|
2202
|
-
console.debug(
|
|
2203
|
-
`Cleaned up ${staleBadges.length} stale activity badges`,
|
|
2204
|
-
);
|
|
2205
|
-
}
|
|
2206
|
-
}
|
|
2207
|
-
|
|
2208
|
-
autoScrollToBottom() {
|
|
2209
|
-
const logEntries = document.getElementById('log-entries');
|
|
2210
|
-
if (!logEntries) return;
|
|
2211
|
-
|
|
2212
|
-
const container = logEntries.closest('.card-body');
|
|
2213
|
-
if (!container) return;
|
|
2214
|
-
|
|
2215
|
-
// Check if user is near the bottom (within 100px)
|
|
2216
|
-
const isNearBottom =
|
|
2217
|
-
container.scrollTop + container.clientHeight >=
|
|
2218
|
-
container.scrollHeight - 100;
|
|
2219
|
-
|
|
2220
|
-
if (isNearBottom) {
|
|
2221
|
-
container.scrollTop = container.scrollHeight;
|
|
2222
|
-
}
|
|
2223
|
-
}
|
|
2224
|
-
|
|
2225
|
-
updateWatchStatus(status) {
|
|
2226
|
-
// Update UI to show connection status
|
|
2227
|
-
let statusElement = document.getElementById('watch-status');
|
|
2228
|
-
if (!statusElement) {
|
|
2229
|
-
statusElement = document.createElement('div');
|
|
2230
|
-
statusElement.id = 'watch-status';
|
|
2231
|
-
statusElement.style.cssText = `
|
|
2232
|
-
position: fixed;
|
|
2233
|
-
top: 20px;
|
|
2234
|
-
right: 20px;
|
|
2235
|
-
padding: 8px 12px;
|
|
2236
|
-
border-radius: 6px;
|
|
2237
|
-
font-size: 0.85em;
|
|
2238
|
-
font-weight: 600;
|
|
2239
|
-
z-index: 1000;
|
|
2240
|
-
transition: all 0.3s ease;
|
|
2241
|
-
`;
|
|
2242
|
-
document.body.appendChild(statusElement);
|
|
2243
|
-
}
|
|
2244
|
-
|
|
2245
|
-
switch (status) {
|
|
2246
|
-
case 'connected':
|
|
2247
|
-
statusElement.textContent = '🟢 LIVE';
|
|
2248
|
-
statusElement.style.background = '#d4edda';
|
|
2249
|
-
statusElement.style.color = '#155724';
|
|
2250
|
-
statusElement.style.border = '1px solid #c3e6cb';
|
|
2251
|
-
break;
|
|
2252
|
-
case 'disconnected':
|
|
2253
|
-
statusElement.textContent = '🟡 Reconnecting...';
|
|
2254
|
-
statusElement.style.background = '#fff3cd';
|
|
2255
|
-
statusElement.style.color = '#856404';
|
|
2256
|
-
statusElement.style.border = '1px solid #ffeaa7';
|
|
2257
|
-
break;
|
|
2258
|
-
case 'error':
|
|
2259
|
-
case 'failed':
|
|
2260
|
-
statusElement.textContent = '🔴 Offline';
|
|
2261
|
-
statusElement.style.background = '#f8d7da';
|
|
2262
|
-
statusElement.style.color = '#721c24';
|
|
2263
|
-
statusElement.style.border = '1px solid #f5c6cb';
|
|
2264
|
-
break;
|
|
2265
|
-
}
|
|
2266
|
-
}
|
|
2267
|
-
|
|
2268
|
-
startCleanupTimer() {
|
|
2269
|
-
// Start periodic cleanup every 60 seconds
|
|
2270
|
-
if (!this.cleanupTimer) {
|
|
2271
|
-
this.cleanupTimer = setInterval(() => {
|
|
2272
|
-
this.cleanupStaleActivityBadges();
|
|
2273
|
-
}, 60000); // 60 seconds
|
|
2274
|
-
}
|
|
2275
|
-
}
|
|
2276
|
-
|
|
2277
|
-
stopCleanupTimer() {
|
|
2278
|
-
if (this.cleanupTimer) {
|
|
2279
|
-
clearInterval(this.cleanupTimer);
|
|
2280
|
-
this.cleanupTimer = null;
|
|
2281
|
-
}
|
|
2282
|
-
}
|
|
2283
|
-
|
|
2284
|
-
startWatching() {
|
|
2285
|
-
this.shouldReconnect = true; // User wants to maintain connection
|
|
2286
|
-
if (!this.isWatching) {
|
|
2287
|
-
this.connect();
|
|
2288
|
-
}
|
|
2289
|
-
this.startCleanupTimer(); // Start periodic cleanup
|
|
2290
|
-
this.showWatchControls();
|
|
2291
|
-
}
|
|
2292
|
-
|
|
2293
|
-
stopWatching() {
|
|
2294
|
-
this.stopCleanupTimer(); // Stop periodic cleanup
|
|
2295
|
-
this.disconnect();
|
|
2296
|
-
this.hideWatchControls();
|
|
2297
|
-
}
|
|
2298
|
-
|
|
2299
|
-
showWatchControls() {
|
|
2300
|
-
// Add watch control buttons to the UI
|
|
2301
|
-
let controlsContainer = document.getElementById('watch-controls');
|
|
2302
|
-
if (!controlsContainer) {
|
|
2303
|
-
controlsContainer = document.createElement('div');
|
|
2304
|
-
controlsContainer.id = 'watch-controls';
|
|
2305
|
-
controlsContainer.style.cssText = `
|
|
2306
|
-
position: fixed;
|
|
2307
|
-
bottom: 20px;
|
|
2308
|
-
right: 20px;
|
|
2309
|
-
display: flex;
|
|
2310
|
-
gap: 10px;
|
|
2311
|
-
z-index: 1000;
|
|
2312
|
-
`;
|
|
2313
|
-
|
|
2314
|
-
const stopBtn = document.createElement('button');
|
|
2315
|
-
stopBtn.textContent = '⏹️ Stop Watching';
|
|
2316
|
-
stopBtn.style.cssText = `
|
|
2317
|
-
background: #dc3545;
|
|
2318
|
-
color: white;
|
|
2319
|
-
border: none;
|
|
2320
|
-
padding: 10px 15px;
|
|
2321
|
-
border-radius: 6px;
|
|
2322
|
-
cursor: pointer;
|
|
2323
|
-
font-size: 0.9em;
|
|
2324
|
-
transition: background-color 0.2s ease;
|
|
2325
|
-
`;
|
|
2326
|
-
stopBtn.onmouseover = () => (stopBtn.style.background = '#c82333');
|
|
2327
|
-
stopBtn.onmouseout = () => (stopBtn.style.background = '#dc3545');
|
|
2328
|
-
stopBtn.onclick = () => this.stopWatching();
|
|
2329
|
-
|
|
2330
|
-
controlsContainer.appendChild(stopBtn);
|
|
2331
|
-
document.body.appendChild(controlsContainer);
|
|
2332
|
-
}
|
|
2333
|
-
}
|
|
2334
|
-
|
|
2335
|
-
hideWatchControls() {
|
|
2336
|
-
const controlsContainer = document.getElementById('watch-controls');
|
|
2337
|
-
if (controlsContainer) {
|
|
2338
|
-
controlsContainer.remove();
|
|
2339
|
-
}
|
|
2340
|
-
}
|
|
2341
|
-
}
|
|
2342
|
-
|
|
2343
|
-
// Global watch manager instance
|
|
2344
|
-
let watchManager = null;
|
|
2345
|
-
|
|
2346
|
-
// Function to start watching
|
|
2347
|
-
function startWatching() {
|
|
2348
|
-
if (!watchManager) {
|
|
2349
|
-
watchManager = new WatchManager();
|
|
2350
|
-
}
|
|
2351
|
-
watchManager.startWatching();
|
|
2352
|
-
}
|
|
2353
|
-
|
|
2354
|
-
// Function to stop watching
|
|
2355
|
-
function stopWatching() {
|
|
2356
|
-
if (watchManager) {
|
|
2357
|
-
watchManager.stopWatching();
|
|
2358
|
-
}
|
|
2359
|
-
}
|
|
2360
|
-
|
|
2361
|
-
// Function to clean up watch manager and prevent connection leaks
|
|
2362
|
-
function cleanupWatchManager() {
|
|
2363
|
-
if (watchManager) {
|
|
2364
|
-
// Clean up stale activity badges before disconnecting
|
|
2365
|
-
watchManager.cleanupStaleActivityBadges();
|
|
2366
|
-
watchManager.disconnect();
|
|
2367
|
-
watchManager = null;
|
|
2368
|
-
}
|
|
2369
|
-
}
|
|
2370
|
-
|
|
2371
|
-
let currentProject = null;
|
|
2372
|
-
let currentSession = null;
|
|
2373
|
-
|
|
2374
|
-
// URL routing functions
|
|
2375
|
-
function updateURL(view, project = null, session = null) {
|
|
2376
|
-
let url = '/';
|
|
2377
|
-
if (view === 'sessions' && project) {
|
|
2378
|
-
url = `/project/${encodeURIComponent(project)}`;
|
|
2379
|
-
} else if (view === 'conversation' && project && session) {
|
|
2380
|
-
url = `/project/${encodeURIComponent(project)}/session/${encodeURIComponent(session)}`;
|
|
2381
|
-
}
|
|
2382
|
-
history.pushState({ view, project, session }, '', url);
|
|
2383
|
-
}
|
|
2384
|
-
|
|
2385
|
-
function parseURL() {
|
|
2386
|
-
const path = window.location.pathname;
|
|
2387
|
-
const matches = path.match(
|
|
2388
|
-
/^\/project\/([^\/]+)(?:\/session\/([^\/]+))?$/,
|
|
2389
|
-
);
|
|
2390
|
-
|
|
2391
|
-
if (matches) {
|
|
2392
|
-
const project = decodeURIComponent(matches[1]);
|
|
2393
|
-
const session = matches[2] ? decodeURIComponent(matches[2]) : null;
|
|
2394
|
-
|
|
2395
|
-
if (session) {
|
|
2396
|
-
loadSession(project, session, false);
|
|
2397
|
-
} else {
|
|
2398
|
-
loadSessions(project, false);
|
|
2399
|
-
}
|
|
2400
|
-
} else {
|
|
2401
|
-
showProjects(false);
|
|
2402
|
-
}
|
|
2403
|
-
}
|
|
2404
|
-
|
|
2405
|
-
// Clean up WebSocket connections on page unload to prevent leaks
|
|
2406
|
-
window.addEventListener('beforeunload', function (event) {
|
|
2407
|
-
cleanupWatchManager();
|
|
2408
|
-
});
|
|
2409
|
-
|
|
2410
|
-
// Handle browser back/forward buttons
|
|
2411
|
-
window.addEventListener('popstate', function (event) {
|
|
2412
|
-
if (event.state) {
|
|
2413
|
-
const { view, project, session } = event.state;
|
|
2414
|
-
if (view === 'conversation' && project && session) {
|
|
2415
|
-
loadSession(project, session, false);
|
|
2416
|
-
} else if (view === 'sessions' && project) {
|
|
2417
|
-
loadSessions(project, false);
|
|
2418
|
-
} else {
|
|
2419
|
-
showProjects(false);
|
|
2420
|
-
}
|
|
2421
|
-
} else {
|
|
2422
|
-
showProjects(false);
|
|
2423
|
-
}
|
|
2424
|
-
});
|
|
2425
|
-
|
|
2426
|
-
async function loadProjects() {
|
|
2427
|
-
document.getElementById('projects-loading').style.display = 'block';
|
|
2428
|
-
document.getElementById('projects-list').innerHTML = '';
|
|
2429
|
-
|
|
2430
|
-
try {
|
|
2431
|
-
const response = await fetch('/api/projects');
|
|
2432
|
-
const projects = await response.json();
|
|
2433
|
-
|
|
2434
|
-
const projectsList = document.getElementById('projects-list');
|
|
2435
|
-
|
|
2436
|
-
if (projects.length === 0) {
|
|
2437
|
-
projectsList.innerHTML = '<p class="loading">No projects found</p>';
|
|
2438
|
-
return;
|
|
2439
|
-
}
|
|
2440
|
-
|
|
2441
|
-
projects.forEach((project) => {
|
|
2442
|
-
const projectCard = document.createElement('div');
|
|
2443
|
-
projectCard.className = 'project-card';
|
|
2444
|
-
projectCard.onclick = () => loadSessions(project.name, true);
|
|
2445
|
-
|
|
2446
|
-
const lastActivity = project.latestActivity
|
|
2447
|
-
? new Date(project.latestActivity).toLocaleDateString()
|
|
2448
|
-
: 'No activity';
|
|
2449
|
-
|
|
2450
|
-
projectCard.innerHTML = `
|
|
2451
|
-
<div class="project-name">${project.name}</div>
|
|
2452
|
-
<div class="project-stats">
|
|
2453
|
-
<span>${project.sessionCount} sessions</span>
|
|
2454
|
-
</div>
|
|
2455
|
-
<div class="project-activity">Last activity: ${lastActivity}</div>
|
|
2456
|
-
`;
|
|
2457
|
-
|
|
2458
|
-
projectsList.appendChild(projectCard);
|
|
2459
|
-
});
|
|
2460
|
-
} catch (error) {
|
|
2461
|
-
document.getElementById('projects-list').innerHTML =
|
|
2462
|
-
`<div class="error">Failed to load projects: ${error.message}</div>`;
|
|
2463
|
-
} finally {
|
|
2464
|
-
document.getElementById('projects-loading').style.display = 'none';
|
|
2465
|
-
}
|
|
2466
|
-
}
|
|
2467
|
-
|
|
2468
|
-
async function loadSessions(projectName, updateUrl = true) {
|
|
2469
|
-
cleanupWatchManager(); // Clean up any active WebSocket connections
|
|
2470
|
-
currentProject = projectName;
|
|
2471
|
-
document.getElementById('project-title').textContent =
|
|
2472
|
-
`Sessions - ${projectName}`;
|
|
2473
|
-
document.getElementById('projects-view').style.display = 'none';
|
|
2474
|
-
document.getElementById('sessions-view').style.display = 'block';
|
|
2475
|
-
document.getElementById('sessions-loading').style.display = 'block';
|
|
2476
|
-
document.getElementById('sessions-list').innerHTML = '';
|
|
2477
|
-
|
|
2478
|
-
if (updateUrl) {
|
|
2479
|
-
updateURL('sessions', projectName);
|
|
2480
|
-
}
|
|
2481
|
-
|
|
2482
|
-
try {
|
|
2483
|
-
const response = await fetch(
|
|
2484
|
-
`/api/projects/${encodeURIComponent(projectName)}/sessions`,
|
|
2485
|
-
);
|
|
2486
|
-
const sessions = await response.json();
|
|
2487
|
-
|
|
2488
|
-
const sessionsList = document.getElementById('sessions-list');
|
|
2489
|
-
|
|
2490
|
-
if (sessions.length === 0) {
|
|
2491
|
-
sessionsList.innerHTML = '<p class="loading">No sessions found</p>';
|
|
2492
|
-
return;
|
|
2493
|
-
}
|
|
2494
|
-
|
|
2495
|
-
sessions.forEach((session) => {
|
|
2496
|
-
const sessionItem = document.createElement('div');
|
|
2497
|
-
sessionItem.className = 'session-item';
|
|
2498
|
-
sessionItem.onclick = () =>
|
|
2499
|
-
loadSession(projectName, session.id, true);
|
|
2500
|
-
|
|
2501
|
-
sessionItem.innerHTML = `
|
|
2502
|
-
<div class="session-title">${session.summary}</div>
|
|
2503
|
-
<div class="session-meta">
|
|
2504
|
-
<span class="timestamp">${new Date(session.timestamp).toLocaleString()}</span>
|
|
2505
|
-
<span>${session.messageCount} entries</span>
|
|
2506
|
-
</div>
|
|
2507
|
-
`;
|
|
2508
|
-
|
|
2509
|
-
sessionsList.appendChild(sessionItem);
|
|
2510
|
-
});
|
|
2511
|
-
} catch (error) {
|
|
2512
|
-
document.getElementById('sessions-list').innerHTML =
|
|
2513
|
-
`<div class="error">Failed to load sessions: ${error.message}</div>`;
|
|
2514
|
-
} finally {
|
|
2515
|
-
document.getElementById('sessions-loading').style.display = 'none';
|
|
2516
|
-
}
|
|
2517
|
-
}
|
|
2518
|
-
|
|
2519
|
-
async function loadSession(projectName, sessionId, updateUrl = true) {
|
|
2520
|
-
cleanupWatchManager(); // Clean up any active WebSocket connections
|
|
2521
|
-
currentSession = sessionId;
|
|
2522
|
-
document.getElementById('session-title').textContent =
|
|
2523
|
-
`Conversation - ${sessionId}`;
|
|
2524
|
-
document.getElementById('sessions-view').style.display = 'none';
|
|
2525
|
-
document.getElementById('log-view').style.display = 'block';
|
|
2526
|
-
document.getElementById('log-loading').style.display = 'block';
|
|
2527
|
-
document.getElementById('log-entries').innerHTML = '';
|
|
2528
|
-
|
|
2529
|
-
if (updateUrl) {
|
|
2530
|
-
updateURL('conversation', projectName, sessionId);
|
|
2531
|
-
}
|
|
2532
|
-
|
|
2533
|
-
try {
|
|
2534
|
-
const response = await fetch(
|
|
2535
|
-
`/api/projects/${encodeURIComponent(projectName)}/sessions/${encodeURIComponent(sessionId)}`,
|
|
2536
|
-
);
|
|
2537
|
-
const entries = await response.json();
|
|
2538
|
-
|
|
2539
|
-
const logEntries = document.getElementById('log-entries');
|
|
2540
|
-
|
|
2541
|
-
if (entries.length === 0) {
|
|
2542
|
-
logEntries.innerHTML =
|
|
2543
|
-
'<p class="loading">No log entries found</p>';
|
|
2544
|
-
return;
|
|
2545
|
-
}
|
|
2546
|
-
|
|
2547
|
-
// First pass: collect tool calls and their results for proper grouping
|
|
2548
|
-
const toolCallMap = new Map(); // tool_use_id -> {call, result}
|
|
2549
|
-
const processedEntries = [];
|
|
2550
|
-
|
|
2551
|
-
entries.forEach((entry) => {
|
|
2552
|
-
if (
|
|
2553
|
-
entry.type === 'user' &&
|
|
2554
|
-
entry.message &&
|
|
2555
|
-
entry.message.content
|
|
2556
|
-
) {
|
|
2557
|
-
if (Array.isArray(entry.message.content)) {
|
|
2558
|
-
// Check if this user message contains only tool results
|
|
2559
|
-
const toolResults = entry.message.content.filter(
|
|
2560
|
-
(c) => c.type === 'tool_result',
|
|
2561
|
-
);
|
|
2562
|
-
const otherContent = entry.message.content.filter(
|
|
2563
|
-
(c) => c.type !== 'tool_result',
|
|
2564
|
-
);
|
|
2565
|
-
|
|
2566
|
-
// Store tool results in the map
|
|
2567
|
-
toolResults.forEach((result) => {
|
|
2568
|
-
if (result.tool_use_id) {
|
|
2569
|
-
if (!toolCallMap.has(result.tool_use_id)) {
|
|
2570
|
-
toolCallMap.set(result.tool_use_id, {
|
|
2571
|
-
call: null,
|
|
2572
|
-
result: null,
|
|
2573
|
-
});
|
|
2574
|
-
}
|
|
2575
|
-
toolCallMap.get(result.tool_use_id).result = result;
|
|
2576
|
-
}
|
|
2577
|
-
});
|
|
2578
|
-
|
|
2579
|
-
// Only include user message if it has non-tool-result content
|
|
2580
|
-
if (otherContent.length > 0) {
|
|
2581
|
-
const modifiedEntry = { ...entry };
|
|
2582
|
-
modifiedEntry.message = { ...entry.message };
|
|
2583
|
-
modifiedEntry.message.content = otherContent;
|
|
2584
|
-
processedEntries.push(modifiedEntry);
|
|
2585
|
-
}
|
|
2586
|
-
} else {
|
|
2587
|
-
// Regular user message
|
|
2588
|
-
processedEntries.push(entry);
|
|
2589
|
-
}
|
|
2590
|
-
} else if (
|
|
2591
|
-
entry.type === 'assistant' &&
|
|
2592
|
-
entry.message &&
|
|
2593
|
-
entry.message.content
|
|
2594
|
-
) {
|
|
2595
|
-
// Store tool calls in the map
|
|
2596
|
-
if (Array.isArray(entry.message.content)) {
|
|
2597
|
-
entry.message.content.forEach((c) => {
|
|
2598
|
-
if (c.type === 'tool_use') {
|
|
2599
|
-
if (!toolCallMap.has(c.id)) {
|
|
2600
|
-
toolCallMap.set(c.id, { call: null, result: null });
|
|
2601
|
-
}
|
|
2602
|
-
toolCallMap.get(c.id).call = c;
|
|
2603
|
-
}
|
|
2604
|
-
});
|
|
2605
|
-
}
|
|
2606
|
-
processedEntries.push(entry);
|
|
2607
|
-
} else {
|
|
2608
|
-
processedEntries.push(entry);
|
|
2609
|
-
}
|
|
2610
|
-
});
|
|
2611
|
-
|
|
2612
|
-
// Second pass: render entries with proper tool call/result grouping
|
|
2613
|
-
processedEntries.forEach((entry) => {
|
|
2614
|
-
if (entry.type === 'summary') {
|
|
2615
|
-
const summaryDiv = document.createElement('div');
|
|
2616
|
-
summaryDiv.className = 'session-summary';
|
|
2617
|
-
summaryDiv.innerHTML = `
|
|
2618
|
-
<h3>📋 Session Summary</h3>
|
|
2619
|
-
<p>${entry.summary || 'No summary available'}</p>
|
|
2620
|
-
`;
|
|
2621
|
-
logEntries.appendChild(summaryDiv);
|
|
2622
|
-
} else if (entry.type === 'user') {
|
|
2623
|
-
const messageDiv = document.createElement('div');
|
|
2624
|
-
messageDiv.className = 'message user';
|
|
2625
|
-
|
|
2626
|
-
let content = '';
|
|
2627
|
-
if (entry.message && entry.message.content) {
|
|
2628
|
-
if (typeof entry.message.content === 'string') {
|
|
2629
|
-
content = entry.message.content;
|
|
2630
|
-
} else if (Array.isArray(entry.message.content)) {
|
|
2631
|
-
// Handle mixed content including images
|
|
2632
|
-
const contentElements = [];
|
|
2633
|
-
entry.message.content.forEach((c) => {
|
|
2634
|
-
if (typeof c === 'string') {
|
|
2635
|
-
contentElements.push({ type: 'text', content: c });
|
|
2636
|
-
} else if (c.type === 'text') {
|
|
2637
|
-
contentElements.push({ type: 'text', content: c.text });
|
|
2638
|
-
} else if (c.type === 'image' && c.source) {
|
|
2639
|
-
contentElements.push({ type: 'image', content: c });
|
|
2640
|
-
} else if (c.type === 'tool_result') {
|
|
2641
|
-
contentElements.push({
|
|
2642
|
-
type: 'text',
|
|
2643
|
-
content: `Tool Result:\n${JSON.stringify(c.content, null, 2)}`,
|
|
2644
|
-
});
|
|
2645
|
-
} else {
|
|
2646
|
-
contentElements.push({
|
|
2647
|
-
type: 'text',
|
|
2648
|
-
content: JSON.stringify(c, null, 2),
|
|
2649
|
-
});
|
|
2650
|
-
}
|
|
2651
|
-
});
|
|
2652
|
-
|
|
2653
|
-
// Store elements for later rendering
|
|
2654
|
-
entry._parsedContent = contentElements;
|
|
2655
|
-
content = contentElements
|
|
2656
|
-
.filter((e) => e.type === 'text')
|
|
2657
|
-
.map((e) => e.content)
|
|
2658
|
-
.join('\n');
|
|
2659
|
-
}
|
|
2660
|
-
}
|
|
2661
|
-
|
|
2662
|
-
const avatar = document.createElement('div');
|
|
2663
|
-
avatar.className = 'avatar user';
|
|
2664
|
-
avatar.textContent = 'U';
|
|
2665
|
-
|
|
2666
|
-
const messageContent = document.createElement('div');
|
|
2667
|
-
messageContent.className = 'message-content';
|
|
2668
|
-
|
|
2669
|
-
const messageText = document.createElement('div');
|
|
2670
|
-
messageText.className = 'message-text';
|
|
2671
|
-
|
|
2672
|
-
// Render mixed content including images
|
|
2673
|
-
if (entry._parsedContent) {
|
|
2674
|
-
entry._parsedContent.forEach((element) => {
|
|
2675
|
-
if (element.type === 'text' && element.content.trim()) {
|
|
2676
|
-
const textDiv = document.createElement('div');
|
|
2677
|
-
textDiv.style.marginBottom = '8px';
|
|
2678
|
-
textDiv.textContent = element.content;
|
|
2679
|
-
messageText.appendChild(textDiv);
|
|
2680
|
-
} else if (element.type === 'image') {
|
|
2681
|
-
const imageContainer = document.createElement('div');
|
|
2682
|
-
imageContainer.style.marginBottom = '12px';
|
|
2683
|
-
imageContainer.style.padding = '8px';
|
|
2684
|
-
imageContainer.style.border = '1px solid #ddd';
|
|
2685
|
-
imageContainer.style.borderRadius = '6px';
|
|
2686
|
-
imageContainer.style.background = '#f9f9f9';
|
|
2687
|
-
|
|
2688
|
-
const imageLabel = document.createElement('div');
|
|
2689
|
-
imageLabel.style.fontSize = '0.85em';
|
|
2690
|
-
imageLabel.style.color = '#666';
|
|
2691
|
-
imageLabel.style.marginBottom = '8px';
|
|
2692
|
-
imageLabel.textContent = '🖼️ Image';
|
|
2693
|
-
|
|
2694
|
-
const img = document.createElement('img');
|
|
2695
|
-
img.style.maxWidth = '100%';
|
|
2696
|
-
img.style.height = 'auto';
|
|
2697
|
-
img.style.borderRadius = '4px';
|
|
2698
|
-
img.style.cursor = 'pointer';
|
|
2699
|
-
|
|
2700
|
-
if (element.content.source && element.content.source.data) {
|
|
2701
|
-
const mediaType =
|
|
2702
|
-
element.content.source.media_type || 'image/png';
|
|
2703
|
-
img.src = `data:${mediaType};base64,${element.content.source.data}`;
|
|
2704
|
-
|
|
2705
|
-
// Click to open in new tab
|
|
2706
|
-
img.onclick = () => {
|
|
2707
|
-
const newWindow = window.open();
|
|
2708
|
-
newWindow.document.write(
|
|
2709
|
-
`<img src="${img.src}" style="max-width: 100%; height: auto;" />`,
|
|
2710
|
-
);
|
|
2711
|
-
};
|
|
2712
|
-
}
|
|
2713
|
-
|
|
2714
|
-
imageContainer.appendChild(imageLabel);
|
|
2715
|
-
imageContainer.appendChild(img);
|
|
2716
|
-
messageText.appendChild(imageContainer);
|
|
2717
|
-
}
|
|
2718
|
-
});
|
|
2719
|
-
} else {
|
|
2720
|
-
messageText.textContent = content;
|
|
2721
|
-
}
|
|
2722
|
-
|
|
2723
|
-
const messageMeta = document.createElement('div');
|
|
2724
|
-
messageMeta.className = 'message-meta';
|
|
2725
|
-
messageMeta.textContent = entry.timestamp
|
|
2726
|
-
? new Date(entry.timestamp).toLocaleString()
|
|
2727
|
-
: '';
|
|
2728
|
-
|
|
2729
|
-
messageContent.appendChild(messageText);
|
|
2730
|
-
messageContent.appendChild(messageMeta);
|
|
2731
|
-
messageDiv.appendChild(avatar);
|
|
2732
|
-
messageDiv.appendChild(messageContent);
|
|
2733
|
-
|
|
2734
|
-
logEntries.appendChild(messageDiv);
|
|
2735
|
-
} else if (entry.type === 'assistant') {
|
|
2736
|
-
const messageDiv = document.createElement('div');
|
|
2737
|
-
messageDiv.className = 'message assistant';
|
|
2738
|
-
|
|
2739
|
-
let content = '';
|
|
2740
|
-
let toolCalls = [];
|
|
2741
|
-
|
|
2742
|
-
if (entry.message && entry.message.content) {
|
|
2743
|
-
if (Array.isArray(entry.message.content)) {
|
|
2744
|
-
entry.message.content.forEach((c) => {
|
|
2745
|
-
if (c.type === 'text') {
|
|
2746
|
-
content += c.text;
|
|
2747
|
-
} else if (c.type === 'tool_use') {
|
|
2748
|
-
toolCalls.push(c);
|
|
2749
|
-
}
|
|
2750
|
-
});
|
|
2751
|
-
} else if (typeof entry.message.content === 'string') {
|
|
2752
|
-
content = entry.message.content;
|
|
2753
|
-
}
|
|
2754
|
-
}
|
|
2755
|
-
|
|
2756
|
-
// Add the assistant message
|
|
2757
|
-
if (content.trim()) {
|
|
2758
|
-
const avatar = document.createElement('div');
|
|
2759
|
-
avatar.className = 'avatar assistant';
|
|
2760
|
-
avatar.textContent = 'AI';
|
|
2761
|
-
|
|
2762
|
-
const messageContent = document.createElement('div');
|
|
2763
|
-
messageContent.className = 'message-content';
|
|
2764
|
-
|
|
2765
|
-
const messageText = document.createElement('div');
|
|
2766
|
-
messageText.className = 'message-text';
|
|
2767
|
-
messageText.textContent = content;
|
|
2768
|
-
|
|
2769
|
-
const messageMeta = document.createElement('div');
|
|
2770
|
-
messageMeta.className = 'message-meta';
|
|
2771
|
-
messageMeta.textContent = entry.timestamp
|
|
2772
|
-
? new Date(entry.timestamp).toLocaleString()
|
|
2773
|
-
: '';
|
|
2774
|
-
|
|
2775
|
-
messageContent.appendChild(messageText);
|
|
2776
|
-
messageContent.appendChild(messageMeta);
|
|
2777
|
-
messageDiv.appendChild(avatar);
|
|
2778
|
-
messageDiv.appendChild(messageContent);
|
|
2779
|
-
|
|
2780
|
-
logEntries.appendChild(messageDiv);
|
|
2781
|
-
}
|
|
2782
|
-
|
|
2783
|
-
// Add tool calls as separate items using tool handlers, with their results
|
|
2784
|
-
toolCalls.forEach((toolCall) => {
|
|
2785
|
-
const handler = getToolHandler(toolCall.name);
|
|
2786
|
-
const toolDiv = handler.renderToolCall(toolCall);
|
|
2787
|
-
logEntries.appendChild(toolDiv);
|
|
2788
|
-
|
|
2789
|
-
// Add corresponding tool result if it exists
|
|
2790
|
-
if (
|
|
2791
|
-
toolCallMap.has(toolCall.id) &&
|
|
2792
|
-
toolCallMap.get(toolCall.id).result
|
|
2793
|
-
) {
|
|
2794
|
-
const toolResult = toolCallMap.get(toolCall.id).result;
|
|
2795
|
-
const resultContent =
|
|
2796
|
-
toolResult.content ||
|
|
2797
|
-
toolResult.text ||
|
|
2798
|
-
JSON.stringify(toolResult, null, 2);
|
|
2799
|
-
const toolResultDiv = handler.renderToolResult(
|
|
2800
|
-
resultContent,
|
|
2801
|
-
toolCall,
|
|
2802
|
-
);
|
|
2803
|
-
logEntries.appendChild(toolResultDiv);
|
|
2804
|
-
}
|
|
2805
|
-
});
|
|
2806
|
-
}
|
|
2807
|
-
});
|
|
2808
|
-
} catch (error) {
|
|
2809
|
-
document.getElementById('log-entries').innerHTML =
|
|
2810
|
-
`<div class="error">Failed to load session logs: ${error.message}</div>`;
|
|
2811
|
-
} finally {
|
|
2812
|
-
document.getElementById('log-loading').style.display = 'none';
|
|
2813
|
-
}
|
|
2814
|
-
}
|
|
2815
|
-
|
|
2816
|
-
function showProjects(updateUrl = true) {
|
|
2817
|
-
cleanupWatchManager(); // Clean up any active WebSocket connections
|
|
2818
|
-
document.getElementById('sessions-view').style.display = 'none';
|
|
2819
|
-
document.getElementById('log-view').style.display = 'none';
|
|
2820
|
-
document.getElementById('projects-view').style.display = 'block';
|
|
2821
|
-
currentProject = null;
|
|
2822
|
-
currentSession = null;
|
|
2823
|
-
|
|
2824
|
-
if (updateUrl) {
|
|
2825
|
-
updateURL('projects');
|
|
2826
|
-
}
|
|
2827
|
-
}
|
|
2828
|
-
|
|
2829
|
-
function showSessions(updateUrl = true) {
|
|
2830
|
-
document.getElementById('log-view').style.display = 'none';
|
|
2831
|
-
document.getElementById('sessions-view').style.display = 'block';
|
|
2832
|
-
currentSession = null;
|
|
2833
|
-
|
|
2834
|
-
if (updateUrl && currentProject) {
|
|
2835
|
-
updateURL('sessions', currentProject);
|
|
2836
|
-
}
|
|
2837
|
-
}
|
|
2838
|
-
|
|
2839
|
-
// Initialize on page load
|
|
2840
|
-
loadProjects().then(() => {
|
|
2841
|
-
// Parse URL and show appropriate view
|
|
2842
|
-
parseURL();
|
|
2843
|
-
});
|
|
2844
|
-
</script>
|
|
2845
|
-
</body>
|
|
2846
|
-
</html>
|