@neovate/code 0.0.0-alpha.0 → 0.9.0
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/LICENSE +21 -0
- package/README.md +45 -0
- package/dist/browser/assets/FolderOutlined-BW67ZEQI.js +63 -0
- package/dist/browser/assets/abap-BrgZPUOV.js +6 -0
- package/dist/browser/assets/apex-DyP6w7ZV.js +6 -0
- package/dist/browser/assets/azcli-BaLxmfj-.js +6 -0
- package/dist/browser/assets/bat-CFOPXBzS.js +6 -0
- package/dist/browser/assets/bicep-BfEKNvv3.js +7 -0
- package/dist/browser/assets/cameligo-BFG1Mk7z.js +6 -0
- package/dist/browser/assets/chat-CwwKXi7w.js +605 -0
- package/dist/browser/assets/clojure-DTECt2xU.js +6 -0
- package/dist/browser/assets/codicon-DCmgc-ay.ttf +0 -0
- package/dist/browser/assets/coffee-CDGzqUPQ.js +6 -0
- package/dist/browser/assets/cpp-CLLBncYj.js +6 -0
- package/dist/browser/assets/csharp-dUCx_-0o.js +6 -0
- package/dist/browser/assets/csp-5Rap-vPy.js +6 -0
- package/dist/browser/assets/css-D3h14YRZ.js +8 -0
- package/dist/browser/assets/css.worker-DGFkc2qH.js +84 -0
- package/dist/browser/assets/cssMode-DtLjlSSH.js +9 -0
- package/dist/browser/assets/cypher-DrQuvNYM.js +6 -0
- package/dist/browser/assets/dart-CFKIUWau.js +6 -0
- package/dist/browser/assets/demo-H0guN01E.js +1 -0
- package/dist/browser/assets/dockerfile-Zznr-cwX.js +6 -0
- package/dist/browser/assets/ecl-Ce3n6wWz.js +6 -0
- package/dist/browser/assets/editor.worker-oRlJJsnX.js +12 -0
- package/dist/browser/assets/elixir-deUWdS0T.js +6 -0
- package/dist/browser/assets/flow9-i9-g7ZhI.js +6 -0
- package/dist/browser/assets/freemarker2-FPweaj7f.js +8 -0
- package/dist/browser/assets/fsharp-CzKuDChf.js +6 -0
- package/dist/browser/assets/go-Cphgjts3.js +6 -0
- package/dist/browser/assets/graphql-Cg7bfA9N.js +6 -0
- package/dist/browser/assets/handlebars-DKcBZNpI.js +6 -0
- package/dist/browser/assets/hcl-0cvrggvQ.js +6 -0
- package/dist/browser/assets/html-KBR8pujr.js +6 -0
- package/dist/browser/assets/html.worker-CltiozTZ.js +461 -0
- package/dist/browser/assets/htmlMode-X-5UZY4-.js +9 -0
- package/dist/browser/assets/index-Bo2vWo_9.js +4 -0
- package/dist/browser/assets/index-BxV58nwE.js +1218 -0
- package/dist/browser/assets/index-Clq2xY9m.css +1 -0
- package/dist/browser/assets/ini-Drc7WvVn.js +6 -0
- package/dist/browser/assets/java-B_fMsGYe.js +6 -0
- package/dist/browser/assets/javascript-CKMclO4y.js +6 -0
- package/dist/browser/assets/json.worker-BnUULff4.js +49 -0
- package/dist/browser/assets/jsonMode-DqJOk8sV.js +15 -0
- package/dist/browser/assets/julia-Bqgm2twL.js +6 -0
- package/dist/browser/assets/kmi-ai-aIKAjIWq.png +0 -0
- package/dist/browser/assets/kotlin-BSkB5QuD.js +6 -0
- package/dist/browser/assets/less-BsTHnhdd.js +7 -0
- package/dist/browser/assets/lexon-YWi4-JPR.js +6 -0
- package/dist/browser/assets/liquid-XpnBpahz.js +6 -0
- package/dist/browser/assets/lua-nf6ki56Z.js +6 -0
- package/dist/browser/assets/m3-Cpb6xl2v.js +6 -0
- package/dist/browser/assets/markdown-DSZPf7rp.js +6 -0
- package/dist/browser/assets/mdx-CB_Y1QFp.js +6 -0
- package/dist/browser/assets/mips-B_c3zf-v.js +6 -0
- package/dist/browser/assets/msdax-rUNN04Wq.js +6 -0
- package/dist/browser/assets/mysql-DDwshQtU.js +6 -0
- package/dist/browser/assets/objective-c-B5zXfXm9.js +6 -0
- package/dist/browser/assets/pascal-CXOwvkN_.js +6 -0
- package/dist/browser/assets/pascaligo-Bc-ZgV77.js +6 -0
- package/dist/browser/assets/perl-CwNk8-XU.js +6 -0
- package/dist/browser/assets/pgsql-tGk8EFnU.js +6 -0
- package/dist/browser/assets/php-CpIb_Oan.js +6 -0
- package/dist/browser/assets/pla-B03wrqEc.js +6 -0
- package/dist/browser/assets/postiats-BKlk5iyT.js +6 -0
- package/dist/browser/assets/powerquery-Bhzvs7bI.js +6 -0
- package/dist/browser/assets/powershell-Dd3NCNK9.js +6 -0
- package/dist/browser/assets/protobuf-COyEY5Pt.js +7 -0
- package/dist/browser/assets/pug-BaJupSGV.js +6 -0
- package/dist/browser/assets/python-B1wA04aL.js +6 -0
- package/dist/browser/assets/qsharp-DXyYeYxl.js +6 -0
- package/dist/browser/assets/r-CdQndTaG.js +6 -0
- package/dist/browser/assets/razor-BAG_xOj_.js +6 -0
- package/dist/browser/assets/redis-CVwtpugi.js +6 -0
- package/dist/browser/assets/redshift-25W9uPmb.js +6 -0
- package/dist/browser/assets/restructuredtext-DfzH4Xui.js +6 -0
- package/dist/browser/assets/ruby-Cp1zYvxS.js +6 -0
- package/dist/browser/assets/rust-D5C2fndG.js +6 -0
- package/dist/browser/assets/sb-CDntyWJ8.js +6 -0
- package/dist/browser/assets/scala-BoFRg7Ot.js +6 -0
- package/dist/browser/assets/scheme-Bio4gycK.js +6 -0
- package/dist/browser/assets/scss-4Ik7cdeQ.js +8 -0
- package/dist/browser/assets/settings-B1SQQuDe.js +1 -0
- package/dist/browser/assets/shell-CX-rkNHf.js +6 -0
- package/dist/browser/assets/solidity-Tw7wswEv.js +6 -0
- package/dist/browser/assets/sophia-C5WLch3f.js +6 -0
- package/dist/browser/assets/sparql-DHaeiCBh.js +6 -0
- package/dist/browser/assets/sql-CCSDG5nI.js +6 -0
- package/dist/browser/assets/st-pnP8ivHi.js +6 -0
- package/dist/browser/assets/swift-DwJ7jVG9.js +8 -0
- package/dist/browser/assets/systemverilog-B9Xyijhd.js +6 -0
- package/dist/browser/assets/tcl-DnHyzjbg.js +6 -0
- package/dist/browser/assets/ts.worker-Bd4z-OY3.js +51334 -0
- package/dist/browser/assets/tsMode-C_fR4nSe.js +16 -0
- package/dist/browser/assets/twig-CPajHgWi.js +6 -0
- package/dist/browser/assets/typescript-HaIDwUQt.js +6 -0
- package/dist/browser/assets/typespec-D-MeaMDU.js +6 -0
- package/dist/browser/assets/vb-DgyLZaXg.js +6 -0
- package/dist/browser/assets/wgsl-BIv9DU6q.js +303 -0
- package/dist/browser/assets/xml-cZ22lEu3.js +6 -0
- package/dist/browser/assets/yaml-DtQXZRuf.js +6 -0
- package/dist/browser/index.html +14 -0
- package/dist/cli.mjs +2124 -0
- package/dist/index.d.ts +307 -0
- package/dist/index.mjs +2123 -0
- package/dist/logfiles/index.html +2846 -0
- package/dist/logfiles/live.html +1465 -0
- package/package.json +144 -7
- package/vendor/ripgrep/COPYING +3 -0
- package/vendor/ripgrep/arm64-darwin/rg +0 -0
- package/vendor/ripgrep/arm64-linux/rg +0 -0
- package/vendor/ripgrep/x64-darwin/rg +0 -0
- package/vendor/ripgrep/x64-linux/rg +0 -0
- package/vendor/ripgrep/x64-win32/rg.exe +0 -0
- package/vendor/takumi.vsix +0 -0
|
@@ -0,0 +1,1465 @@
|
|
|
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>Live Activity - 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: 1400px;
|
|
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
|
+
margin-bottom: 1rem;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.nav-links {
|
|
51
|
+
display: flex;
|
|
52
|
+
justify-content: center;
|
|
53
|
+
gap: 0;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.nav-link {
|
|
57
|
+
background: #ffffff;
|
|
58
|
+
color: #0066cc;
|
|
59
|
+
text-decoration: none;
|
|
60
|
+
padding: 0.5rem 1rem;
|
|
61
|
+
border: 1px solid #e0e0e0;
|
|
62
|
+
transition: all 0.2s ease;
|
|
63
|
+
font-weight: normal;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.nav-link:hover {
|
|
67
|
+
text-decoration: underline;
|
|
68
|
+
border-color: #0066cc;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.nav-link.active {
|
|
72
|
+
background: #0066cc;
|
|
73
|
+
color: white;
|
|
74
|
+
font-weight: 600;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.live-container {
|
|
78
|
+
background: #ffffff;
|
|
79
|
+
border: 1px solid #e0e0e0;
|
|
80
|
+
overflow: hidden;
|
|
81
|
+
height: calc(100vh - 200px);
|
|
82
|
+
display: flex;
|
|
83
|
+
flex-direction: column;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
.live-header {
|
|
87
|
+
background: #f5f5f5;
|
|
88
|
+
color: #333333;
|
|
89
|
+
padding: 1rem 1.5rem;
|
|
90
|
+
display: flex;
|
|
91
|
+
justify-content: space-between;
|
|
92
|
+
align-items: center;
|
|
93
|
+
border-bottom: 1px solid #e0e0e0;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
.live-title {
|
|
97
|
+
font-weight: 600;
|
|
98
|
+
font-size: 1.1rem;
|
|
99
|
+
display: flex;
|
|
100
|
+
align-items: center;
|
|
101
|
+
gap: 0.5rem;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
.status-indicator {
|
|
105
|
+
display: flex;
|
|
106
|
+
align-items: center;
|
|
107
|
+
gap: 0.5rem;
|
|
108
|
+
font-size: 0.9rem;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
.status-dot {
|
|
112
|
+
width: 8px;
|
|
113
|
+
height: 8px;
|
|
114
|
+
border-radius: 50%;
|
|
115
|
+
background: #cc0000;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
.status-dot.connected {
|
|
119
|
+
background: #0066cc;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
.activity-stream {
|
|
123
|
+
flex: 1;
|
|
124
|
+
overflow-y: auto;
|
|
125
|
+
padding: 0;
|
|
126
|
+
background: #ffffff;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
.activity-entry {
|
|
130
|
+
border-bottom: 1px solid #e0e0e0;
|
|
131
|
+
padding: 1rem 1.5rem;
|
|
132
|
+
transition: background-color 0.2s ease;
|
|
133
|
+
position: relative;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
.activity-entry:hover {
|
|
137
|
+
background: #f5f5f5;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
.activity-entry.new {
|
|
141
|
+
background: #f5f5f5;
|
|
142
|
+
border-left: 3px solid #0066cc;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
.activity-header {
|
|
146
|
+
display: flex;
|
|
147
|
+
justify-content: space-between;
|
|
148
|
+
align-items: center;
|
|
149
|
+
margin-bottom: 0.5rem;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
.activity-source {
|
|
153
|
+
font-weight: 600;
|
|
154
|
+
color: #000000;
|
|
155
|
+
font-size: 0.9rem;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
.activity-timestamp {
|
|
159
|
+
font-size: 0.8rem;
|
|
160
|
+
color: #666666;
|
|
161
|
+
font-family: 'SF Mono', 'Monaco', 'Inconsolata', 'Fira Code', monospace;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
.activity-content {
|
|
165
|
+
background: #ffffff;
|
|
166
|
+
padding: 1rem;
|
|
167
|
+
border: 1px solid #e0e0e0;
|
|
168
|
+
font-family: 'SF Mono', 'Monaco', 'Inconsolata', 'Fira Code', monospace;
|
|
169
|
+
font-size: 0.85rem;
|
|
170
|
+
line-height: 1.4;
|
|
171
|
+
max-height: 200px;
|
|
172
|
+
overflow-y: auto;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
.activity-type {
|
|
176
|
+
display: inline-block;
|
|
177
|
+
background: #333333;
|
|
178
|
+
color: white;
|
|
179
|
+
padding: 2px 6px;
|
|
180
|
+
font-size: 0.75rem;
|
|
181
|
+
font-weight: 600;
|
|
182
|
+
margin-right: 0.5rem;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
.activity-type.user {
|
|
186
|
+
background: #0066cc;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
.activity-type.assistant {
|
|
190
|
+
background: #333333;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
.activity-type.tool {
|
|
194
|
+
background: #666666;
|
|
195
|
+
color: white;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
.activity-type.summary {
|
|
199
|
+
background: #999999;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/* Tool rendering styles */
|
|
203
|
+
.tool-call-container {
|
|
204
|
+
background: #f5f5f5;
|
|
205
|
+
border: 1px solid #e0e0e0;
|
|
206
|
+
margin: 0.5rem 0;
|
|
207
|
+
font-size: 0.85rem;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
.tool-call-header {
|
|
211
|
+
display: flex;
|
|
212
|
+
align-items: center;
|
|
213
|
+
gap: 8px;
|
|
214
|
+
padding: 8px 12px;
|
|
215
|
+
background: #e0e0e0;
|
|
216
|
+
font-weight: 600;
|
|
217
|
+
color: #333333;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
.tool-call-content {
|
|
221
|
+
background: #ffffff;
|
|
222
|
+
padding: 8px;
|
|
223
|
+
border-left: 2px solid #666666;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
.tool-result-container {
|
|
227
|
+
background: #f5f5f5;
|
|
228
|
+
border: 1px solid #e0e0e0;
|
|
229
|
+
margin: 0.5rem 0;
|
|
230
|
+
font-size: 0.85rem;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
.tool-result-header {
|
|
234
|
+
display: flex;
|
|
235
|
+
align-items: center;
|
|
236
|
+
gap: 8px;
|
|
237
|
+
padding: 8px 12px;
|
|
238
|
+
background: #e0e0e0;
|
|
239
|
+
font-weight: 600;
|
|
240
|
+
color: #333333;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
.tool-result-content {
|
|
244
|
+
background: #ffffff;
|
|
245
|
+
padding: 8px;
|
|
246
|
+
border-left: 2px solid #666666;
|
|
247
|
+
font-family: 'SF Mono', 'Monaco', 'Inconsolata', 'Fira Code', monospace;
|
|
248
|
+
font-size: 0.8rem;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
.controls {
|
|
252
|
+
padding: 1rem 1.5rem;
|
|
253
|
+
background: #f5f5f5;
|
|
254
|
+
border-top: 1px solid #e0e0e0;
|
|
255
|
+
display: flex;
|
|
256
|
+
justify-content: space-between;
|
|
257
|
+
align-items: center;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
.control-group {
|
|
261
|
+
display: flex;
|
|
262
|
+
gap: 0.5rem;
|
|
263
|
+
align-items: center;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
.btn {
|
|
267
|
+
background: #0066cc;
|
|
268
|
+
color: white;
|
|
269
|
+
border: 1px solid #0066cc;
|
|
270
|
+
padding: 0.5rem 1rem;
|
|
271
|
+
cursor: pointer;
|
|
272
|
+
font-size: 0.9rem;
|
|
273
|
+
transition: background-color 0.2s ease;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
.btn:hover {
|
|
277
|
+
background: #0052a3;
|
|
278
|
+
border-color: #0052a3;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
.btn.btn-success {
|
|
282
|
+
background: #0066cc;
|
|
283
|
+
border-color: #0066cc;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
.btn.btn-success:hover {
|
|
287
|
+
background: #0052a3;
|
|
288
|
+
border-color: #0052a3;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
.btn.btn-danger {
|
|
292
|
+
background: #cc0000;
|
|
293
|
+
border-color: #cc0000;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
.btn.btn-danger:hover {
|
|
297
|
+
background: #a60000;
|
|
298
|
+
border-color: #a60000;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
.btn.btn-secondary {
|
|
302
|
+
background: #666666;
|
|
303
|
+
border-color: #666666;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
.btn.btn-secondary:hover {
|
|
307
|
+
background: #4d4d4d;
|
|
308
|
+
border-color: #4d4d4d;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
.activity-stats {
|
|
312
|
+
font-size: 0.85rem;
|
|
313
|
+
color: #666666;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
.empty-state {
|
|
317
|
+
display: flex;
|
|
318
|
+
flex-direction: column;
|
|
319
|
+
align-items: center;
|
|
320
|
+
justify-content: center;
|
|
321
|
+
height: 300px;
|
|
322
|
+
color: #666666;
|
|
323
|
+
text-align: center;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
.empty-state-icon {
|
|
327
|
+
font-size: 2rem;
|
|
328
|
+
margin-bottom: 1rem;
|
|
329
|
+
color: #999999;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
@media (max-width: 768px) {
|
|
333
|
+
.container {
|
|
334
|
+
padding: 1rem;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
h1 {
|
|
338
|
+
font-size: 1.5rem;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
.live-container {
|
|
342
|
+
height: calc(100vh - 150px);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
.controls {
|
|
346
|
+
flex-direction: column;
|
|
347
|
+
gap: 1rem;
|
|
348
|
+
align-items: stretch;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
.control-group {
|
|
352
|
+
justify-content: center;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
</style>
|
|
356
|
+
</head>
|
|
357
|
+
<body>
|
|
358
|
+
<div class="container">
|
|
359
|
+
<header>
|
|
360
|
+
<h1>Live Activity Stream</h1>
|
|
361
|
+
<p class="subtitle">Real-time JSONL message feed from all projects</p>
|
|
362
|
+
|
|
363
|
+
<div class="nav-links">
|
|
364
|
+
<a href="/" class="nav-link">Projects</a>
|
|
365
|
+
<a href="/live" class="nav-link active">Live Activity</a>
|
|
366
|
+
</div>
|
|
367
|
+
</header>
|
|
368
|
+
|
|
369
|
+
<div class="live-container">
|
|
370
|
+
<div class="live-header">
|
|
371
|
+
<div class="live-title">Activity Stream</div>
|
|
372
|
+
<div class="status-indicator">
|
|
373
|
+
<div class="status-dot" id="status-dot"></div>
|
|
374
|
+
<span id="status-text">Disconnected</span>
|
|
375
|
+
</div>
|
|
376
|
+
</div>
|
|
377
|
+
|
|
378
|
+
<div class="activity-stream" id="activity-stream">
|
|
379
|
+
<div class="empty-state" id="empty-state">
|
|
380
|
+
<div class="empty-state-icon">📡</div>
|
|
381
|
+
<h3>Waiting for Activity</h3>
|
|
382
|
+
<p>
|
|
383
|
+
Click "Start Watching" to begin monitoring activity in real-time.
|
|
384
|
+
</p>
|
|
385
|
+
</div>
|
|
386
|
+
</div>
|
|
387
|
+
|
|
388
|
+
<div class="controls">
|
|
389
|
+
<div class="control-group">
|
|
390
|
+
<button
|
|
391
|
+
class="btn btn-success"
|
|
392
|
+
id="start-btn"
|
|
393
|
+
onclick="startWatching()"
|
|
394
|
+
>
|
|
395
|
+
Start Watching
|
|
396
|
+
</button>
|
|
397
|
+
<button
|
|
398
|
+
class="btn btn-danger"
|
|
399
|
+
id="stop-btn"
|
|
400
|
+
onclick="stopWatching()"
|
|
401
|
+
style="display: none"
|
|
402
|
+
>
|
|
403
|
+
Stop Watching
|
|
404
|
+
</button>
|
|
405
|
+
<button class="btn btn-secondary" onclick="clearActivity()">
|
|
406
|
+
Clear
|
|
407
|
+
</button>
|
|
408
|
+
</div>
|
|
409
|
+
<div class="activity-stats">
|
|
410
|
+
<span id="message-count">0 messages</span> •
|
|
411
|
+
<span id="uptime">Not connected</span>
|
|
412
|
+
</div>
|
|
413
|
+
</div>
|
|
414
|
+
</div>
|
|
415
|
+
</div>
|
|
416
|
+
|
|
417
|
+
<script>
|
|
418
|
+
// ABOUTME: Live activity stream WebSocket manager for real-time JSONL message monitoring
|
|
419
|
+
// ABOUTME: Displays all incoming messages from all projects in a live scrolling feed
|
|
420
|
+
|
|
421
|
+
// Base class for all tool handlers
|
|
422
|
+
class ToolHandler {
|
|
423
|
+
constructor(toolName) {
|
|
424
|
+
this.toolName = toolName;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
renderToolCall(toolCall) {
|
|
428
|
+
const toolDiv = document.createElement('div');
|
|
429
|
+
toolDiv.className = 'tool-call-container';
|
|
430
|
+
|
|
431
|
+
const header = this.createHeader(toolCall);
|
|
432
|
+
const content = this.renderInput(toolCall.input);
|
|
433
|
+
|
|
434
|
+
toolDiv.appendChild(header);
|
|
435
|
+
toolDiv.appendChild(content);
|
|
436
|
+
|
|
437
|
+
return toolDiv;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
renderToolResult(toolResult, toolCall) {
|
|
441
|
+
const resultDiv = document.createElement('div');
|
|
442
|
+
resultDiv.className = 'tool-result-container';
|
|
443
|
+
|
|
444
|
+
const header = this.createResultHeader(toolCall);
|
|
445
|
+
const content = this.renderOutput(toolResult, toolCall);
|
|
446
|
+
|
|
447
|
+
resultDiv.appendChild(header);
|
|
448
|
+
resultDiv.appendChild(content);
|
|
449
|
+
|
|
450
|
+
return resultDiv;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
createHeader(toolCall) {
|
|
454
|
+
const header = document.createElement('div');
|
|
455
|
+
header.className = 'tool-call-header';
|
|
456
|
+
header.innerHTML = `<span>${this.getIcon()}</span><span>Tool: ${this.toolName}</span>`;
|
|
457
|
+
return header;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
createResultHeader(toolCall) {
|
|
461
|
+
const header = document.createElement('div');
|
|
462
|
+
header.className = 'tool-result-header';
|
|
463
|
+
header.innerHTML = `<span>📋</span><span>${this.toolName} Result</span>`;
|
|
464
|
+
return header;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
renderInput(input) {
|
|
468
|
+
const content = document.createElement('div');
|
|
469
|
+
content.className = 'tool-call-content';
|
|
470
|
+
content.textContent = JSON.stringify(input, null, 2);
|
|
471
|
+
return content;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
renderOutput(result, toolCall) {
|
|
475
|
+
const content = document.createElement('div');
|
|
476
|
+
content.className = 'tool-result-content';
|
|
477
|
+
content.textContent =
|
|
478
|
+
typeof result === 'string'
|
|
479
|
+
? result
|
|
480
|
+
: JSON.stringify(result, null, 2);
|
|
481
|
+
return content;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
getIcon() {
|
|
485
|
+
return '🔧';
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
// Handler for Bash tool
|
|
490
|
+
class BashHandler extends ToolHandler {
|
|
491
|
+
constructor() {
|
|
492
|
+
super('Bash');
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
renderInput(input) {
|
|
496
|
+
const content = document.createElement('div');
|
|
497
|
+
content.className = 'tool-call-content';
|
|
498
|
+
|
|
499
|
+
const command = document.createElement('div');
|
|
500
|
+
command.style.fontWeight = 'bold';
|
|
501
|
+
command.style.marginBottom = '8px';
|
|
502
|
+
command.textContent = `$ ${input.command}`;
|
|
503
|
+
|
|
504
|
+
if (input.description) {
|
|
505
|
+
const desc = document.createElement('div');
|
|
506
|
+
desc.style.fontStyle = 'italic';
|
|
507
|
+
desc.style.color = '#666';
|
|
508
|
+
desc.style.marginBottom = '8px';
|
|
509
|
+
desc.textContent = input.description;
|
|
510
|
+
content.appendChild(desc);
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
content.appendChild(command);
|
|
514
|
+
return content;
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
renderOutput(result, toolCall) {
|
|
518
|
+
const content = document.createElement('div');
|
|
519
|
+
content.className = 'tool-result-content';
|
|
520
|
+
content.style.fontFamily = 'monospace';
|
|
521
|
+
content.style.whiteSpace = 'pre-wrap';
|
|
522
|
+
content.textContent = result;
|
|
523
|
+
return content;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
getIcon() {
|
|
527
|
+
return '💻';
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
// Handler for Read tool
|
|
532
|
+
class ReadHandler extends ToolHandler {
|
|
533
|
+
constructor() {
|
|
534
|
+
super('Read');
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
renderInput(input) {
|
|
538
|
+
const content = document.createElement('div');
|
|
539
|
+
content.className = 'tool-call-content';
|
|
540
|
+
|
|
541
|
+
const path = document.createElement('div');
|
|
542
|
+
path.style.fontWeight = 'bold';
|
|
543
|
+
path.textContent = `📄 ${input.file_path}`;
|
|
544
|
+
|
|
545
|
+
content.appendChild(path);
|
|
546
|
+
|
|
547
|
+
if (input.offset || input.limit) {
|
|
548
|
+
const params = document.createElement('div');
|
|
549
|
+
params.style.fontSize = '0.9em';
|
|
550
|
+
params.style.color = '#666';
|
|
551
|
+
params.style.marginTop = '4px';
|
|
552
|
+
const offset = input.offset || 0;
|
|
553
|
+
const limit = input.limit || 'end';
|
|
554
|
+
params.textContent = `Lines: ${offset} to ${limit}`;
|
|
555
|
+
content.appendChild(params);
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
return content;
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
renderOutput(result, toolCall) {
|
|
562
|
+
const content = document.createElement('div');
|
|
563
|
+
content.className = 'tool-result-content';
|
|
564
|
+
content.style.fontFamily = 'monospace';
|
|
565
|
+
content.style.fontSize = '0.85em';
|
|
566
|
+
content.style.whiteSpace = 'pre';
|
|
567
|
+
content.style.maxHeight = '400px';
|
|
568
|
+
content.style.overflowY = 'auto';
|
|
569
|
+
content.textContent = result;
|
|
570
|
+
return content;
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
getIcon() {
|
|
574
|
+
return '📖';
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
// Handler for Edit/Write tools
|
|
579
|
+
class EditHandler extends ToolHandler {
|
|
580
|
+
constructor() {
|
|
581
|
+
super('Edit');
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
renderInput(input) {
|
|
585
|
+
const content = document.createElement('div');
|
|
586
|
+
content.className = 'tool-call-content';
|
|
587
|
+
|
|
588
|
+
const path = document.createElement('div');
|
|
589
|
+
path.style.fontWeight = 'bold';
|
|
590
|
+
path.style.marginBottom = '8px';
|
|
591
|
+
path.textContent = `${input.file_path}`;
|
|
592
|
+
|
|
593
|
+
if (input.old_string && input.new_string) {
|
|
594
|
+
const changeDiv = document.createElement('div');
|
|
595
|
+
changeDiv.style.marginTop = '8px';
|
|
596
|
+
changeDiv.style.padding = '8px';
|
|
597
|
+
changeDiv.style.background = '#f8f9fa';
|
|
598
|
+
changeDiv.style.borderRadius = '4px';
|
|
599
|
+
changeDiv.style.fontSize = '0.85em';
|
|
600
|
+
|
|
601
|
+
const oldDiv = document.createElement('div');
|
|
602
|
+
oldDiv.style.marginBottom = '4px';
|
|
603
|
+
oldDiv.innerHTML = `<span style="color: #dc3545; font-weight: bold;">- </span>${this.escapeHtml(input.old_string.substring(0, 100))}${input.old_string.length > 100 ? '...' : ''}`;
|
|
604
|
+
|
|
605
|
+
const newDiv = document.createElement('div');
|
|
606
|
+
newDiv.innerHTML = `<span style="color: #28a745; font-weight: bold;">+ </span>${this.escapeHtml(input.new_string.substring(0, 100))}${input.new_string.length > 100 ? '...' : ''}`;
|
|
607
|
+
|
|
608
|
+
changeDiv.appendChild(oldDiv);
|
|
609
|
+
changeDiv.appendChild(newDiv);
|
|
610
|
+
content.appendChild(changeDiv);
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
content.appendChild(path);
|
|
614
|
+
return content;
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
escapeHtml(text) {
|
|
618
|
+
const div = document.createElement('div');
|
|
619
|
+
div.textContent = text;
|
|
620
|
+
return div.innerHTML;
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
getIcon() {
|
|
624
|
+
return '';
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
// Handler for MultiEdit tool
|
|
629
|
+
class MultiEditHandler extends ToolHandler {
|
|
630
|
+
constructor() {
|
|
631
|
+
super('MultiEdit');
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
renderInput(input) {
|
|
635
|
+
const content = document.createElement('div');
|
|
636
|
+
content.className = 'tool-call-content';
|
|
637
|
+
|
|
638
|
+
const multiEditDiv = document.createElement('div');
|
|
639
|
+
multiEditDiv.style.padding = '12px';
|
|
640
|
+
multiEditDiv.style.background = '#f8f9fa';
|
|
641
|
+
multiEditDiv.style.borderRadius = '8px';
|
|
642
|
+
multiEditDiv.style.border = '1px solid #dee2e6';
|
|
643
|
+
|
|
644
|
+
const header = document.createElement('div');
|
|
645
|
+
header.style.fontWeight = 'bold';
|
|
646
|
+
header.style.marginBottom = '12px';
|
|
647
|
+
header.style.borderBottom = '1px solid #ddd';
|
|
648
|
+
header.style.paddingBottom = '6px';
|
|
649
|
+
header.textContent = `Multi-Edit: ${input.file_path}`;
|
|
650
|
+
|
|
651
|
+
multiEditDiv.appendChild(header);
|
|
652
|
+
|
|
653
|
+
if (input.edits && Array.isArray(input.edits)) {
|
|
654
|
+
input.edits.forEach((edit, index) => {
|
|
655
|
+
const editDiv = document.createElement('div');
|
|
656
|
+
editDiv.style.marginBottom = '12px';
|
|
657
|
+
editDiv.style.padding = '8px';
|
|
658
|
+
editDiv.style.background = '#ffffff';
|
|
659
|
+
editDiv.style.borderRadius = '4px';
|
|
660
|
+
editDiv.style.border = '1px solid #e9ecef';
|
|
661
|
+
|
|
662
|
+
const editHeader = document.createElement('div');
|
|
663
|
+
editHeader.style.fontWeight = 'bold';
|
|
664
|
+
editHeader.style.marginBottom = '6px';
|
|
665
|
+
editHeader.style.fontSize = '0.9em';
|
|
666
|
+
editHeader.textContent = `Edit ${index + 1}:`;
|
|
667
|
+
|
|
668
|
+
const oldDiv = document.createElement('div');
|
|
669
|
+
oldDiv.style.marginBottom = '4px';
|
|
670
|
+
oldDiv.style.fontSize = '0.85em';
|
|
671
|
+
|
|
672
|
+
// Create safe elements to prevent HTML injection
|
|
673
|
+
const oldSymbol = document.createElement('span');
|
|
674
|
+
oldSymbol.style.color = '#dc3545';
|
|
675
|
+
oldSymbol.style.fontWeight = 'bold';
|
|
676
|
+
oldSymbol.textContent = '- ';
|
|
677
|
+
|
|
678
|
+
const oldText = document.createTextNode(
|
|
679
|
+
edit.old_string.substring(0, 80) +
|
|
680
|
+
(edit.old_string.length > 80 ? '...' : ''),
|
|
681
|
+
);
|
|
682
|
+
|
|
683
|
+
oldDiv.appendChild(oldSymbol);
|
|
684
|
+
oldDiv.appendChild(oldText);
|
|
685
|
+
|
|
686
|
+
const newDiv = document.createElement('div');
|
|
687
|
+
newDiv.style.fontSize = '0.85em';
|
|
688
|
+
|
|
689
|
+
// Create safe elements to prevent HTML injection
|
|
690
|
+
const newSymbol = document.createElement('span');
|
|
691
|
+
newSymbol.style.color = '#28a745';
|
|
692
|
+
newSymbol.style.fontWeight = 'bold';
|
|
693
|
+
newSymbol.textContent = '+ ';
|
|
694
|
+
|
|
695
|
+
const newText = document.createTextNode(
|
|
696
|
+
edit.new_string.substring(0, 80) +
|
|
697
|
+
(edit.new_string.length > 80 ? '...' : ''),
|
|
698
|
+
);
|
|
699
|
+
|
|
700
|
+
newDiv.appendChild(newSymbol);
|
|
701
|
+
newDiv.appendChild(newText);
|
|
702
|
+
|
|
703
|
+
editDiv.appendChild(editHeader);
|
|
704
|
+
editDiv.appendChild(oldDiv);
|
|
705
|
+
editDiv.appendChild(newDiv);
|
|
706
|
+
multiEditDiv.appendChild(editDiv);
|
|
707
|
+
});
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
content.appendChild(multiEditDiv);
|
|
711
|
+
return content;
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
getIcon() {
|
|
715
|
+
return '';
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
// Handler for Write tool
|
|
720
|
+
class WriteHandler extends ToolHandler {
|
|
721
|
+
constructor() {
|
|
722
|
+
super('Write');
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
renderInput(input) {
|
|
726
|
+
const content = document.createElement('div');
|
|
727
|
+
content.className = 'tool-call-content';
|
|
728
|
+
|
|
729
|
+
const path = document.createElement('div');
|
|
730
|
+
path.style.fontWeight = 'bold';
|
|
731
|
+
path.style.marginBottom = '8px';
|
|
732
|
+
path.textContent = `${input.file_path}`;
|
|
733
|
+
|
|
734
|
+
const contentInfo = document.createElement('div');
|
|
735
|
+
contentInfo.style.fontSize = '0.9em';
|
|
736
|
+
contentInfo.style.color = '#666';
|
|
737
|
+
const contentLength = input.content ? input.content.length : 0;
|
|
738
|
+
contentInfo.textContent = `Writing ${contentLength} characters`;
|
|
739
|
+
|
|
740
|
+
content.appendChild(path);
|
|
741
|
+
content.appendChild(contentInfo);
|
|
742
|
+
return content;
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
getIcon() {
|
|
746
|
+
return '';
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
// Handler for LS tool
|
|
751
|
+
class LSHandler extends ToolHandler {
|
|
752
|
+
constructor() {
|
|
753
|
+
super('LS');
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
renderInput(input) {
|
|
757
|
+
const content = document.createElement('div');
|
|
758
|
+
content.className = 'tool-call-content';
|
|
759
|
+
|
|
760
|
+
const path = document.createElement('div');
|
|
761
|
+
path.style.fontWeight = 'bold';
|
|
762
|
+
path.textContent = `${input.path}`;
|
|
763
|
+
|
|
764
|
+
content.appendChild(path);
|
|
765
|
+
return content;
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
renderOutput(result, toolCall) {
|
|
769
|
+
const content = document.createElement('div');
|
|
770
|
+
content.className = 'tool-result-content';
|
|
771
|
+
content.style.fontFamily = 'monospace';
|
|
772
|
+
content.style.whiteSpace = 'pre';
|
|
773
|
+
content.textContent = result;
|
|
774
|
+
return content;
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
getIcon() {
|
|
778
|
+
return '';
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
// Handler for Grep tool
|
|
783
|
+
class GrepHandler extends ToolHandler {
|
|
784
|
+
constructor() {
|
|
785
|
+
super('Grep');
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
renderInput(input) {
|
|
789
|
+
const content = document.createElement('div');
|
|
790
|
+
content.className = 'tool-call-content';
|
|
791
|
+
|
|
792
|
+
const pattern = document.createElement('div');
|
|
793
|
+
pattern.style.fontWeight = 'bold';
|
|
794
|
+
pattern.style.marginBottom = '4px';
|
|
795
|
+
pattern.textContent = `"${input.pattern}"`;
|
|
796
|
+
|
|
797
|
+
if (input.path) {
|
|
798
|
+
const path = document.createElement('div');
|
|
799
|
+
path.style.fontSize = '0.9em';
|
|
800
|
+
path.style.color = '#666';
|
|
801
|
+
path.textContent = `in: ${input.path}`;
|
|
802
|
+
content.appendChild(path);
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
content.appendChild(pattern);
|
|
806
|
+
return content;
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
renderOutput(result, toolCall) {
|
|
810
|
+
const content = document.createElement('div');
|
|
811
|
+
content.className = 'tool-result-content';
|
|
812
|
+
content.style.fontFamily = 'monospace';
|
|
813
|
+
content.style.fontSize = '0.85em';
|
|
814
|
+
content.style.whiteSpace = 'pre';
|
|
815
|
+
content.textContent = result;
|
|
816
|
+
return content;
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
getIcon() {
|
|
820
|
+
return '';
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
// Handler for Glob tool
|
|
825
|
+
class GlobHandler extends ToolHandler {
|
|
826
|
+
constructor() {
|
|
827
|
+
super('Glob');
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
renderInput(input) {
|
|
831
|
+
const content = document.createElement('div');
|
|
832
|
+
content.className = 'tool-call-content';
|
|
833
|
+
|
|
834
|
+
const globDiv = document.createElement('div');
|
|
835
|
+
globDiv.style.padding = '12px';
|
|
836
|
+
globDiv.style.background = '#fef3c7';
|
|
837
|
+
globDiv.style.borderRadius = '8px';
|
|
838
|
+
globDiv.style.border = '1px solid #fbbf24';
|
|
839
|
+
|
|
840
|
+
const header = document.createElement('div');
|
|
841
|
+
header.style.fontWeight = 'bold';
|
|
842
|
+
header.style.marginBottom = '8px';
|
|
843
|
+
header.style.color = '#92400e';
|
|
844
|
+
header.textContent = 'File Pattern Search';
|
|
845
|
+
|
|
846
|
+
const pattern = document.createElement('div');
|
|
847
|
+
pattern.style.fontFamily = 'monospace';
|
|
848
|
+
pattern.style.marginBottom = '4px';
|
|
849
|
+
pattern.textContent = `Pattern: ${input.pattern}`;
|
|
850
|
+
|
|
851
|
+
if (input.path) {
|
|
852
|
+
const path = document.createElement('div');
|
|
853
|
+
path.style.fontSize = '0.9em';
|
|
854
|
+
path.style.color = '#666';
|
|
855
|
+
path.textContent = `Path: ${input.path}`;
|
|
856
|
+
globDiv.appendChild(path);
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
globDiv.appendChild(header);
|
|
860
|
+
globDiv.appendChild(pattern);
|
|
861
|
+
content.appendChild(globDiv);
|
|
862
|
+
return content;
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
renderOutput(result, toolCall) {
|
|
866
|
+
const content = document.createElement('div');
|
|
867
|
+
content.className = 'tool-result-content';
|
|
868
|
+
|
|
869
|
+
let resultText = result;
|
|
870
|
+
if (Array.isArray(result) && result[0] && result[0].text) {
|
|
871
|
+
resultText = result[0].text;
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
const resultDiv = document.createElement('div');
|
|
875
|
+
resultDiv.style.fontFamily = 'monospace';
|
|
876
|
+
resultDiv.style.fontSize = '0.9em';
|
|
877
|
+
|
|
878
|
+
if (typeof resultText === 'string' && resultText.includes('\n')) {
|
|
879
|
+
const lines = resultText
|
|
880
|
+
.trim()
|
|
881
|
+
.split('\n')
|
|
882
|
+
.filter((line) => line.trim());
|
|
883
|
+
|
|
884
|
+
if (lines.length > 0) {
|
|
885
|
+
const headerDiv = document.createElement('div');
|
|
886
|
+
headerDiv.style.fontWeight = 'bold';
|
|
887
|
+
headerDiv.style.marginBottom = '12px';
|
|
888
|
+
headerDiv.style.color = '#374151';
|
|
889
|
+
headerDiv.textContent = `Found ${lines.length} matching file${lines.length === 1 ? '' : 's'}:`;
|
|
890
|
+
|
|
891
|
+
const filesDiv = document.createElement('div');
|
|
892
|
+
|
|
893
|
+
lines.forEach((filePath) => {
|
|
894
|
+
const fileDiv = document.createElement('div');
|
|
895
|
+
fileDiv.style.padding = '6px 8px';
|
|
896
|
+
fileDiv.style.marginBottom = '4px';
|
|
897
|
+
fileDiv.style.background = '#ffffff';
|
|
898
|
+
fileDiv.style.borderRadius = '4px';
|
|
899
|
+
fileDiv.style.border = '1px solid #e5e7eb';
|
|
900
|
+
fileDiv.style.fontFamily = 'monospace';
|
|
901
|
+
fileDiv.style.fontSize = '0.85em';
|
|
902
|
+
fileDiv.style.wordBreak = 'break-all';
|
|
903
|
+
|
|
904
|
+
// Add file icon based on extension
|
|
905
|
+
const extension = filePath.split('.').pop()?.toLowerCase();
|
|
906
|
+
let icon = '';
|
|
907
|
+
if (['js', 'ts', 'jsx', 'tsx'].includes(extension)) icon = '';
|
|
908
|
+
else if (['py'].includes(extension)) icon = '';
|
|
909
|
+
else if (['rs'].includes(extension)) icon = '';
|
|
910
|
+
else if (['html', 'htm'].includes(extension)) icon = '';
|
|
911
|
+
else if (['css'].includes(extension)) icon = '';
|
|
912
|
+
else if (['md'].includes(extension)) icon = '';
|
|
913
|
+
else if (['json'].includes(extension)) icon = '';
|
|
914
|
+
|
|
915
|
+
fileDiv.innerHTML = `${filePath}`;
|
|
916
|
+
filesDiv.appendChild(fileDiv);
|
|
917
|
+
});
|
|
918
|
+
|
|
919
|
+
resultDiv.appendChild(headerDiv);
|
|
920
|
+
resultDiv.appendChild(filesDiv);
|
|
921
|
+
} else {
|
|
922
|
+
resultDiv.style.background = '#fef2f2';
|
|
923
|
+
resultDiv.style.border = '1px solid #fecaca';
|
|
924
|
+
resultDiv.style.color = '#991b1b';
|
|
925
|
+
resultDiv.innerHTML = 'No files found matching the pattern';
|
|
926
|
+
}
|
|
927
|
+
} else {
|
|
928
|
+
resultDiv.style.whiteSpace = 'pre-wrap';
|
|
929
|
+
resultDiv.style.fontFamily = 'monospace';
|
|
930
|
+
resultDiv.style.fontSize = '0.9em';
|
|
931
|
+
resultDiv.textContent =
|
|
932
|
+
typeof resultText === 'string' ? resultText : 'No results';
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
content.appendChild(resultDiv);
|
|
936
|
+
return content;
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
getIcon() {
|
|
940
|
+
return '';
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
// Handler for TodoWrite tool
|
|
945
|
+
class TodoWriteHandler extends ToolHandler {
|
|
946
|
+
constructor() {
|
|
947
|
+
super('TodoWrite');
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
renderInput(input) {
|
|
951
|
+
const content = document.createElement('div');
|
|
952
|
+
content.className = 'tool-call-content';
|
|
953
|
+
|
|
954
|
+
if (input.todos && Array.isArray(input.todos)) {
|
|
955
|
+
const todoList = document.createElement('div');
|
|
956
|
+
todoList.style.marginTop = '8px';
|
|
957
|
+
|
|
958
|
+
const header = document.createElement('div');
|
|
959
|
+
header.style.fontWeight = 'bold';
|
|
960
|
+
header.style.marginBottom = '8px';
|
|
961
|
+
header.style.borderBottom = '1px solid #ddd';
|
|
962
|
+
header.style.paddingBottom = '4px';
|
|
963
|
+
header.textContent = `Todo List (${input.todos.length} items)`;
|
|
964
|
+
|
|
965
|
+
todoList.appendChild(header);
|
|
966
|
+
|
|
967
|
+
input.todos.forEach((todo) => {
|
|
968
|
+
const todoItem = document.createElement('div');
|
|
969
|
+
todoItem.style.display = 'flex';
|
|
970
|
+
todoItem.style.alignItems = 'flex-start';
|
|
971
|
+
todoItem.style.marginBottom = '8px';
|
|
972
|
+
todoItem.style.padding = '8px';
|
|
973
|
+
todoItem.style.borderRadius = '4px';
|
|
974
|
+
todoItem.style.fontSize = '0.9em';
|
|
975
|
+
|
|
976
|
+
// Status-based styling
|
|
977
|
+
if (todo.status === 'completed') {
|
|
978
|
+
todoItem.style.background = '#e8f5e8';
|
|
979
|
+
todoItem.style.borderLeft = '3px solid #28a745';
|
|
980
|
+
} else if (todo.status === 'in_progress') {
|
|
981
|
+
todoItem.style.background = '#fff3cd';
|
|
982
|
+
todoItem.style.borderLeft = '3px solid #ffc107';
|
|
983
|
+
} else {
|
|
984
|
+
todoItem.style.background = '#f8f9fa';
|
|
985
|
+
todoItem.style.borderLeft = '3px solid #6c757d';
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
// Status icon
|
|
989
|
+
const statusIcon = document.createElement('span');
|
|
990
|
+
statusIcon.style.marginRight = '8px';
|
|
991
|
+
statusIcon.style.fontSize = '1.1em';
|
|
992
|
+
if (todo.status === 'completed') {
|
|
993
|
+
statusIcon.textContent = '✓';
|
|
994
|
+
} else if (todo.status === 'in_progress') {
|
|
995
|
+
statusIcon.textContent = '○';
|
|
996
|
+
} else {
|
|
997
|
+
statusIcon.textContent = '○';
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
// Content container
|
|
1001
|
+
const contentContainer = document.createElement('div');
|
|
1002
|
+
contentContainer.style.flex = '1';
|
|
1003
|
+
|
|
1004
|
+
// Todo content
|
|
1005
|
+
const todoContent = document.createElement('div');
|
|
1006
|
+
todoContent.style.marginBottom = '4px';
|
|
1007
|
+
if (todo.status === 'completed') {
|
|
1008
|
+
todoContent.style.textDecoration = 'line-through';
|
|
1009
|
+
todoContent.style.color = '#666';
|
|
1010
|
+
}
|
|
1011
|
+
todoContent.textContent = todo.content;
|
|
1012
|
+
|
|
1013
|
+
// Priority and ID
|
|
1014
|
+
const metaInfo = document.createElement('div');
|
|
1015
|
+
metaInfo.style.fontSize = '0.8em';
|
|
1016
|
+
metaInfo.style.color = '#888';
|
|
1017
|
+
|
|
1018
|
+
const priorityColor =
|
|
1019
|
+
todo.priority === 'high'
|
|
1020
|
+
? '#dc3545'
|
|
1021
|
+
: todo.priority === 'medium'
|
|
1022
|
+
? '#fd7e14'
|
|
1023
|
+
: '#28a745';
|
|
1024
|
+
metaInfo.innerHTML = `<span style="color: ${priorityColor}; font-weight: bold;">●</span> ${todo.priority} priority • ID: ${todo.id}`;
|
|
1025
|
+
|
|
1026
|
+
contentContainer.appendChild(todoContent);
|
|
1027
|
+
contentContainer.appendChild(metaInfo);
|
|
1028
|
+
|
|
1029
|
+
todoItem.appendChild(statusIcon);
|
|
1030
|
+
todoItem.appendChild(contentContainer);
|
|
1031
|
+
todoList.appendChild(todoItem);
|
|
1032
|
+
});
|
|
1033
|
+
|
|
1034
|
+
content.appendChild(todoList);
|
|
1035
|
+
} else {
|
|
1036
|
+
content.textContent = JSON.stringify(input, null, 2);
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
return content;
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
getIcon() {
|
|
1043
|
+
return '';
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
// Tool handler registry
|
|
1048
|
+
const toolHandlers = {
|
|
1049
|
+
Bash: new BashHandler(),
|
|
1050
|
+
Read: new ReadHandler(),
|
|
1051
|
+
Edit: new EditHandler(),
|
|
1052
|
+
Write: new WriteHandler(),
|
|
1053
|
+
MultiEdit: new MultiEditHandler(),
|
|
1054
|
+
LS: new LSHandler(),
|
|
1055
|
+
Grep: new GrepHandler(),
|
|
1056
|
+
Glob: new GlobHandler(),
|
|
1057
|
+
TodoWrite: new TodoWriteHandler(),
|
|
1058
|
+
Task: new ToolHandler('Task'),
|
|
1059
|
+
WebFetch: new ToolHandler('WebFetch'),
|
|
1060
|
+
};
|
|
1061
|
+
|
|
1062
|
+
// Get appropriate handler for a tool
|
|
1063
|
+
function getToolHandler(toolName) {
|
|
1064
|
+
return toolHandlers[toolName] || new ToolHandler(toolName);
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
class LiveActivityManager {
|
|
1068
|
+
constructor() {
|
|
1069
|
+
this.ws = null;
|
|
1070
|
+
this.isWatching = false;
|
|
1071
|
+
this.shouldReconnect = false;
|
|
1072
|
+
this.messageCount = 0;
|
|
1073
|
+
this.startTime = null;
|
|
1074
|
+
this.reconnectAttempts = 0;
|
|
1075
|
+
this.maxReconnectAttempts = 5;
|
|
1076
|
+
this.reconnectDelay = 1000;
|
|
1077
|
+
this.autoScroll = true;
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
connect() {
|
|
1081
|
+
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
|
1082
|
+
return;
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
const protocol =
|
|
1086
|
+
window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
1087
|
+
const wsUrl = `${protocol}//${window.location.host}/ws/watch`;
|
|
1088
|
+
|
|
1089
|
+
try {
|
|
1090
|
+
this.ws = new WebSocket(wsUrl);
|
|
1091
|
+
|
|
1092
|
+
this.ws.onopen = () => {
|
|
1093
|
+
console.log('WebSocket connected');
|
|
1094
|
+
this.reconnectAttempts = 0;
|
|
1095
|
+
this.isWatching = true;
|
|
1096
|
+
this.startTime = Date.now();
|
|
1097
|
+
this.updateStatus('connected');
|
|
1098
|
+
this.updateUptime();
|
|
1099
|
+
};
|
|
1100
|
+
|
|
1101
|
+
this.ws.onmessage = (event) => {
|
|
1102
|
+
try {
|
|
1103
|
+
const watchEvent = JSON.parse(event.data);
|
|
1104
|
+
this.handleWatchEvent(watchEvent);
|
|
1105
|
+
} catch (e) {
|
|
1106
|
+
console.error('Failed to parse watch event:', e);
|
|
1107
|
+
}
|
|
1108
|
+
};
|
|
1109
|
+
|
|
1110
|
+
this.ws.onclose = () => {
|
|
1111
|
+
console.log('WebSocket disconnected');
|
|
1112
|
+
this.isWatching = false;
|
|
1113
|
+
this.updateStatus('disconnected');
|
|
1114
|
+
this.scheduleReconnect();
|
|
1115
|
+
};
|
|
1116
|
+
|
|
1117
|
+
this.ws.onerror = (error) => {
|
|
1118
|
+
console.error('WebSocket error:', error);
|
|
1119
|
+
this.updateStatus('error');
|
|
1120
|
+
};
|
|
1121
|
+
} catch (e) {
|
|
1122
|
+
console.error('Failed to create WebSocket connection:', e);
|
|
1123
|
+
this.scheduleReconnect();
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
disconnect() {
|
|
1128
|
+
this.isWatching = false;
|
|
1129
|
+
if (this.ws) {
|
|
1130
|
+
this.ws.close();
|
|
1131
|
+
this.ws = null;
|
|
1132
|
+
}
|
|
1133
|
+
this.updateStatus('disconnected');
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
scheduleReconnect() {
|
|
1137
|
+
if (
|
|
1138
|
+
!this.shouldReconnect ||
|
|
1139
|
+
this.reconnectAttempts >= this.maxReconnectAttempts
|
|
1140
|
+
) {
|
|
1141
|
+
if (this.reconnectAttempts >= this.maxReconnectAttempts) {
|
|
1142
|
+
this.updateStatus('failed');
|
|
1143
|
+
}
|
|
1144
|
+
return;
|
|
1145
|
+
}
|
|
1146
|
+
|
|
1147
|
+
this.reconnectAttempts++;
|
|
1148
|
+
const delay =
|
|
1149
|
+
this.reconnectDelay * Math.pow(2, this.reconnectAttempts - 1);
|
|
1150
|
+
|
|
1151
|
+
setTimeout(() => {
|
|
1152
|
+
if (this.shouldReconnect) {
|
|
1153
|
+
console.log(
|
|
1154
|
+
`Attempting to reconnect (${this.reconnectAttempts}/${this.maxReconnectAttempts})`,
|
|
1155
|
+
);
|
|
1156
|
+
this.connect();
|
|
1157
|
+
}
|
|
1158
|
+
}, delay);
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
handleWatchEvent(watchEvent) {
|
|
1162
|
+
console.log('Received watch event:', watchEvent);
|
|
1163
|
+
|
|
1164
|
+
if (watchEvent.type === 'log_entry' && watchEvent.entry) {
|
|
1165
|
+
this.addActivityEntry(watchEvent);
|
|
1166
|
+
this.messageCount++;
|
|
1167
|
+
this.updateMessageCount();
|
|
1168
|
+
}
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1171
|
+
addActivityEntry(watchEvent) {
|
|
1172
|
+
const stream = document.getElementById('activity-stream');
|
|
1173
|
+
const emptyState = document.getElementById('empty-state');
|
|
1174
|
+
|
|
1175
|
+
// Hide empty state if visible
|
|
1176
|
+
if (emptyState && emptyState.style.display !== 'none') {
|
|
1177
|
+
emptyState.style.display = 'none';
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1180
|
+
const entry = watchEvent.entry;
|
|
1181
|
+
const entryDiv = document.createElement('div');
|
|
1182
|
+
entryDiv.className = 'activity-entry new';
|
|
1183
|
+
|
|
1184
|
+
// Format project path to be more readable
|
|
1185
|
+
let formattedProject = watchEvent.project;
|
|
1186
|
+
if (formattedProject.startsWith('-Users-')) {
|
|
1187
|
+
// Convert -Users-harper-Public-src-2389-cc-log-viewer to /Users/harper/Public/src/2389/cc-log-viewer
|
|
1188
|
+
formattedProject = formattedProject
|
|
1189
|
+
.replace(/^-/, '/')
|
|
1190
|
+
.replace(/-/g, '/');
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1193
|
+
// Add session ID in parentheses if available
|
|
1194
|
+
const sessionInfo = watchEvent.session
|
|
1195
|
+
? ` (${watchEvent.session.substring(0, 8)}...)`
|
|
1196
|
+
: '';
|
|
1197
|
+
const source = `${formattedProject}${sessionInfo}`;
|
|
1198
|
+
|
|
1199
|
+
// Determine entry type and content
|
|
1200
|
+
let entryType = 'unknown';
|
|
1201
|
+
let contentElements = [];
|
|
1202
|
+
|
|
1203
|
+
if (entry.type) {
|
|
1204
|
+
entryType = entry.type;
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1207
|
+
if (entry.message && entry.message.content) {
|
|
1208
|
+
if (typeof entry.message.content === 'string') {
|
|
1209
|
+
contentElements.push({
|
|
1210
|
+
type: 'text',
|
|
1211
|
+
content: entry.message.content,
|
|
1212
|
+
});
|
|
1213
|
+
} else if (Array.isArray(entry.message.content)) {
|
|
1214
|
+
// Handle mixed content including tool use with rich rendering
|
|
1215
|
+
const textContent = entry.message.content
|
|
1216
|
+
.filter((c) => c.type === 'text')
|
|
1217
|
+
.map((c) => c.text)
|
|
1218
|
+
.join('\n');
|
|
1219
|
+
|
|
1220
|
+
const toolUse = entry.message.content.filter(
|
|
1221
|
+
(c) => c.type === 'tool_use',
|
|
1222
|
+
);
|
|
1223
|
+
|
|
1224
|
+
const toolResults = entry.message.content.filter(
|
|
1225
|
+
(c) => c.type === 'tool_result',
|
|
1226
|
+
);
|
|
1227
|
+
|
|
1228
|
+
// Add text content if present
|
|
1229
|
+
if (textContent.trim()) {
|
|
1230
|
+
contentElements.push({ type: 'text', content: textContent });
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1233
|
+
// Add tool calls with rich rendering
|
|
1234
|
+
if (toolUse.length > 0) {
|
|
1235
|
+
entryType = 'tool';
|
|
1236
|
+
toolUse.forEach((tool) => {
|
|
1237
|
+
const handler = getToolHandler(tool.name);
|
|
1238
|
+
contentElements.push({
|
|
1239
|
+
type: 'tool_call',
|
|
1240
|
+
element: handler.renderToolCall(tool),
|
|
1241
|
+
});
|
|
1242
|
+
});
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
// Add tool results with rich rendering
|
|
1246
|
+
if (toolResults.length > 0) {
|
|
1247
|
+
entryType = 'tool';
|
|
1248
|
+
toolResults.forEach((result) => {
|
|
1249
|
+
const toolName = result.tool_use_id
|
|
1250
|
+
? result.tool_use_id.substring(0, 8)
|
|
1251
|
+
: 'unknown';
|
|
1252
|
+
const handler = getToolHandler(toolName);
|
|
1253
|
+
const resultContent =
|
|
1254
|
+
result.content ||
|
|
1255
|
+
result.text ||
|
|
1256
|
+
JSON.stringify(result, null, 2);
|
|
1257
|
+
contentElements.push({
|
|
1258
|
+
type: 'tool_result',
|
|
1259
|
+
element: handler.renderToolResult(resultContent, {
|
|
1260
|
+
name: toolName,
|
|
1261
|
+
}),
|
|
1262
|
+
});
|
|
1263
|
+
});
|
|
1264
|
+
}
|
|
1265
|
+
}
|
|
1266
|
+
} else if (entry.summary) {
|
|
1267
|
+
contentElements.push({ type: 'text', content: entry.summary });
|
|
1268
|
+
entryType = 'summary';
|
|
1269
|
+
} else if (entry.tool_use_result) {
|
|
1270
|
+
// Handle tool results as separate entries
|
|
1271
|
+
entryType = 'tool';
|
|
1272
|
+
const handler = getToolHandler('unknown');
|
|
1273
|
+
const resultContent =
|
|
1274
|
+
typeof entry.tool_use_result === 'string'
|
|
1275
|
+
? entry.tool_use_result
|
|
1276
|
+
: JSON.stringify(entry.tool_use_result, null, 2);
|
|
1277
|
+
contentElements.push({
|
|
1278
|
+
type: 'tool_result',
|
|
1279
|
+
element: handler.renderToolResult(resultContent, {
|
|
1280
|
+
name: 'Tool',
|
|
1281
|
+
}),
|
|
1282
|
+
});
|
|
1283
|
+
}
|
|
1284
|
+
|
|
1285
|
+
const timestamp = new Date(watchEvent.timestamp).toLocaleTimeString();
|
|
1286
|
+
|
|
1287
|
+
// Create header
|
|
1288
|
+
const headerDiv = document.createElement('div');
|
|
1289
|
+
headerDiv.className = 'activity-header';
|
|
1290
|
+
headerDiv.innerHTML = `
|
|
1291
|
+
<div class="activity-source">
|
|
1292
|
+
<span class="activity-type ${entryType}">${entryType.toUpperCase()}</span>
|
|
1293
|
+
${source}
|
|
1294
|
+
</div>
|
|
1295
|
+
<div class="activity-timestamp">${timestamp}</div>
|
|
1296
|
+
`;
|
|
1297
|
+
entryDiv.appendChild(headerDiv);
|
|
1298
|
+
|
|
1299
|
+
// Create content area
|
|
1300
|
+
const contentDiv = document.createElement('div');
|
|
1301
|
+
contentDiv.className = 'activity-content';
|
|
1302
|
+
|
|
1303
|
+
if (contentElements.length === 0) {
|
|
1304
|
+
contentDiv.innerHTML = '<em>No content</em>';
|
|
1305
|
+
} else {
|
|
1306
|
+
contentElements.forEach((elem) => {
|
|
1307
|
+
if (elem.type === 'text') {
|
|
1308
|
+
const textDiv = document.createElement('div');
|
|
1309
|
+
textDiv.style.whiteSpace = 'pre-wrap';
|
|
1310
|
+
textDiv.textContent =
|
|
1311
|
+
elem.content.length > 500
|
|
1312
|
+
? elem.content.substring(0, 500) + '...'
|
|
1313
|
+
: elem.content;
|
|
1314
|
+
contentDiv.appendChild(textDiv);
|
|
1315
|
+
} else if (
|
|
1316
|
+
elem.type === 'tool_call' ||
|
|
1317
|
+
elem.type === 'tool_result'
|
|
1318
|
+
) {
|
|
1319
|
+
contentDiv.appendChild(elem.element);
|
|
1320
|
+
}
|
|
1321
|
+
});
|
|
1322
|
+
}
|
|
1323
|
+
|
|
1324
|
+
entryDiv.appendChild(contentDiv);
|
|
1325
|
+
|
|
1326
|
+
// Add to stream
|
|
1327
|
+
stream.appendChild(entryDiv);
|
|
1328
|
+
|
|
1329
|
+
// Remove 'new' class after animation
|
|
1330
|
+
setTimeout(() => {
|
|
1331
|
+
entryDiv.classList.remove('new');
|
|
1332
|
+
}, 300);
|
|
1333
|
+
|
|
1334
|
+
// Auto-scroll to bottom if enabled
|
|
1335
|
+
if (this.autoScroll) {
|
|
1336
|
+
stream.scrollTop = stream.scrollHeight;
|
|
1337
|
+
}
|
|
1338
|
+
|
|
1339
|
+
// Limit number of entries to prevent memory issues
|
|
1340
|
+
const maxEntries = 1000;
|
|
1341
|
+
const entries = stream.querySelectorAll('.activity-entry');
|
|
1342
|
+
if (entries.length > maxEntries) {
|
|
1343
|
+
entries[0].remove();
|
|
1344
|
+
}
|
|
1345
|
+
}
|
|
1346
|
+
|
|
1347
|
+
escapeHtml(text) {
|
|
1348
|
+
const div = document.createElement('div');
|
|
1349
|
+
div.textContent = text;
|
|
1350
|
+
return div.innerHTML;
|
|
1351
|
+
}
|
|
1352
|
+
|
|
1353
|
+
updateStatus(status) {
|
|
1354
|
+
const statusDot = document.getElementById('status-dot');
|
|
1355
|
+
const statusText = document.getElementById('status-text');
|
|
1356
|
+
const startBtn = document.getElementById('start-btn');
|
|
1357
|
+
const stopBtn = document.getElementById('stop-btn');
|
|
1358
|
+
|
|
1359
|
+
switch (status) {
|
|
1360
|
+
case 'connected':
|
|
1361
|
+
statusDot.className = 'status-dot connected';
|
|
1362
|
+
statusText.textContent = 'Connected';
|
|
1363
|
+
startBtn.style.display = 'none';
|
|
1364
|
+
stopBtn.style.display = 'inline-block';
|
|
1365
|
+
break;
|
|
1366
|
+
case 'disconnected':
|
|
1367
|
+
statusDot.className = 'status-dot';
|
|
1368
|
+
statusText.textContent = 'Disconnected';
|
|
1369
|
+
startBtn.style.display = 'inline-block';
|
|
1370
|
+
stopBtn.style.display = 'none';
|
|
1371
|
+
document.getElementById('uptime').textContent = 'Not connected';
|
|
1372
|
+
break;
|
|
1373
|
+
case 'error':
|
|
1374
|
+
case 'failed':
|
|
1375
|
+
statusDot.className = 'status-dot';
|
|
1376
|
+
statusText.textContent = 'Connection Failed';
|
|
1377
|
+
startBtn.style.display = 'inline-block';
|
|
1378
|
+
stopBtn.style.display = 'none';
|
|
1379
|
+
break;
|
|
1380
|
+
}
|
|
1381
|
+
}
|
|
1382
|
+
|
|
1383
|
+
updateMessageCount() {
|
|
1384
|
+
const countElement = document.getElementById('message-count');
|
|
1385
|
+
countElement.textContent = `${this.messageCount} message${this.messageCount !== 1 ? 's' : ''}`;
|
|
1386
|
+
}
|
|
1387
|
+
|
|
1388
|
+
updateUptime() {
|
|
1389
|
+
if (!this.startTime || !this.isWatching) {
|
|
1390
|
+
return;
|
|
1391
|
+
}
|
|
1392
|
+
|
|
1393
|
+
const uptimeElement = document.getElementById('uptime');
|
|
1394
|
+
const elapsed = Date.now() - this.startTime;
|
|
1395
|
+
const seconds = Math.floor(elapsed / 1000);
|
|
1396
|
+
const minutes = Math.floor(seconds / 60);
|
|
1397
|
+
const hours = Math.floor(minutes / 60);
|
|
1398
|
+
|
|
1399
|
+
let uptimeText = '';
|
|
1400
|
+
if (hours > 0) {
|
|
1401
|
+
uptimeText = `${hours}h ${minutes % 60}m`;
|
|
1402
|
+
} else if (minutes > 0) {
|
|
1403
|
+
uptimeText = `${minutes}m ${seconds % 60}s`;
|
|
1404
|
+
} else {
|
|
1405
|
+
uptimeText = `${seconds}s`;
|
|
1406
|
+
}
|
|
1407
|
+
|
|
1408
|
+
uptimeElement.textContent = `Connected for ${uptimeText}`;
|
|
1409
|
+
|
|
1410
|
+
// Update every second
|
|
1411
|
+
if (this.isWatching) {
|
|
1412
|
+
setTimeout(() => this.updateUptime(), 1000);
|
|
1413
|
+
}
|
|
1414
|
+
}
|
|
1415
|
+
|
|
1416
|
+
startWatching() {
|
|
1417
|
+
this.shouldReconnect = true; // User wants to maintain connection
|
|
1418
|
+
this.connect();
|
|
1419
|
+
this.messageCount = 0;
|
|
1420
|
+
this.updateMessageCount();
|
|
1421
|
+
}
|
|
1422
|
+
|
|
1423
|
+
stopWatching() {
|
|
1424
|
+
this.shouldReconnect = false; // User wants to stop connection
|
|
1425
|
+
this.disconnect();
|
|
1426
|
+
}
|
|
1427
|
+
|
|
1428
|
+
clearActivity() {
|
|
1429
|
+
const stream = document.getElementById('activity-stream');
|
|
1430
|
+
const entries = stream.querySelectorAll('.activity-entry');
|
|
1431
|
+
entries.forEach((entry) => entry.remove());
|
|
1432
|
+
|
|
1433
|
+
const emptyState = document.getElementById('empty-state');
|
|
1434
|
+
if (emptyState) {
|
|
1435
|
+
emptyState.style.display = 'flex';
|
|
1436
|
+
}
|
|
1437
|
+
|
|
1438
|
+
this.messageCount = 0;
|
|
1439
|
+
this.updateMessageCount();
|
|
1440
|
+
}
|
|
1441
|
+
}
|
|
1442
|
+
|
|
1443
|
+
// Global instance
|
|
1444
|
+
const liveActivity = new LiveActivityManager();
|
|
1445
|
+
|
|
1446
|
+
// Global functions for buttons
|
|
1447
|
+
function startWatching() {
|
|
1448
|
+
liveActivity.startWatching();
|
|
1449
|
+
}
|
|
1450
|
+
|
|
1451
|
+
function stopWatching() {
|
|
1452
|
+
liveActivity.stopWatching();
|
|
1453
|
+
}
|
|
1454
|
+
|
|
1455
|
+
function clearActivity() {
|
|
1456
|
+
liveActivity.clearActivity();
|
|
1457
|
+
}
|
|
1458
|
+
|
|
1459
|
+
// Initialize on page load
|
|
1460
|
+
document.addEventListener('DOMContentLoaded', function () {
|
|
1461
|
+
console.log('Live Activity Stream initialized');
|
|
1462
|
+
});
|
|
1463
|
+
</script>
|
|
1464
|
+
</body>
|
|
1465
|
+
</html>
|