@timmeck/brain-core 2.17.1 → 2.19.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/README.md +142 -185
- package/codegen-dashboard.html +987 -0
- package/dist/attention/attention-engine.d.ts +108 -0
- package/dist/attention/attention-engine.js +411 -0
- package/dist/attention/attention-engine.js.map +1 -0
- package/dist/attention/index.d.ts +2 -0
- package/dist/attention/index.js +2 -0
- package/dist/attention/index.js.map +1 -0
- package/dist/codegen/codegen-server.d.ts +26 -0
- package/dist/codegen/codegen-server.js +211 -0
- package/dist/codegen/codegen-server.js.map +1 -0
- package/dist/codegen/index.d.ts +2 -0
- package/dist/codegen/index.js +1 -0
- package/dist/codegen/index.js.map +1 -1
- package/dist/consciousness/types.d.ts +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/research/research-orchestrator.d.ts +4 -0
- package/dist/research/research-orchestrator.js +22 -0
- package/dist/research/research-orchestrator.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,987 @@
|
|
|
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>CodeGen Dashboard</title>
|
|
7
|
+
<style>
|
|
8
|
+
:root {
|
|
9
|
+
--bg-primary: #0a0a1a;
|
|
10
|
+
--bg-secondary: #0f1128;
|
|
11
|
+
--bg-card: rgba(15, 17, 40, 0.85);
|
|
12
|
+
--border: rgba(100, 120, 255, 0.15);
|
|
13
|
+
--text-primary: #e0e4ff;
|
|
14
|
+
--text-secondary: #8890b5;
|
|
15
|
+
--text-muted: #555a7a;
|
|
16
|
+
--cyan: #00e5ff;
|
|
17
|
+
--magenta: #ff0090;
|
|
18
|
+
--gold: #ffd700;
|
|
19
|
+
--green: #00ff88;
|
|
20
|
+
--purple: #b388ff;
|
|
21
|
+
--orange: #ff9100;
|
|
22
|
+
--blue: #448aff;
|
|
23
|
+
--red: #ff5252;
|
|
24
|
+
--glow-cyan: 0 0 20px rgba(0, 229, 255, 0.4);
|
|
25
|
+
--glow-green: 0 0 20px rgba(0, 255, 136, 0.4);
|
|
26
|
+
--glow-red: 0 0 15px rgba(255, 82, 82, 0.3);
|
|
27
|
+
--glow-gold: 0 0 20px rgba(255, 215, 0, 0.4);
|
|
28
|
+
}
|
|
29
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
30
|
+
body {
|
|
31
|
+
font-family: 'SF Mono', 'Cascadia Code', 'JetBrains Mono', 'Fira Code', monospace;
|
|
32
|
+
background: var(--bg-primary);
|
|
33
|
+
color: var(--text-primary);
|
|
34
|
+
height: 100vh;
|
|
35
|
+
overflow-y: auto;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/* ── Layout ─────────────────────────────────── */
|
|
39
|
+
.dashboard {
|
|
40
|
+
max-width: 1400px;
|
|
41
|
+
margin: 0 auto;
|
|
42
|
+
padding: 16px 20px;
|
|
43
|
+
display: flex;
|
|
44
|
+
flex-direction: column;
|
|
45
|
+
gap: 16px;
|
|
46
|
+
min-height: 100vh;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/* ── Header ─────────────────────────────────── */
|
|
50
|
+
.header {
|
|
51
|
+
display: flex;
|
|
52
|
+
align-items: center;
|
|
53
|
+
justify-content: space-between;
|
|
54
|
+
padding: 12px 20px;
|
|
55
|
+
background: var(--bg-secondary);
|
|
56
|
+
border: 1px solid var(--border);
|
|
57
|
+
border-radius: 10px;
|
|
58
|
+
}
|
|
59
|
+
.header-left {
|
|
60
|
+
display: flex;
|
|
61
|
+
align-items: center;
|
|
62
|
+
gap: 12px;
|
|
63
|
+
}
|
|
64
|
+
.header h1 {
|
|
65
|
+
font-size: 16px;
|
|
66
|
+
font-weight: 600;
|
|
67
|
+
letter-spacing: 1px;
|
|
68
|
+
background: linear-gradient(135deg, var(--cyan), var(--green));
|
|
69
|
+
-webkit-background-clip: text;
|
|
70
|
+
-webkit-text-fill-color: transparent;
|
|
71
|
+
}
|
|
72
|
+
.header .logo {
|
|
73
|
+
font-size: 22px;
|
|
74
|
+
}
|
|
75
|
+
.connection-status {
|
|
76
|
+
display: flex;
|
|
77
|
+
align-items: center;
|
|
78
|
+
gap: 6px;
|
|
79
|
+
font-size: 11px;
|
|
80
|
+
color: var(--text-muted);
|
|
81
|
+
}
|
|
82
|
+
.connection-dot {
|
|
83
|
+
width: 8px; height: 8px; border-radius: 50%;
|
|
84
|
+
background: var(--red);
|
|
85
|
+
transition: all 0.3s;
|
|
86
|
+
}
|
|
87
|
+
.connection-dot.connected {
|
|
88
|
+
background: var(--green);
|
|
89
|
+
box-shadow: var(--glow-green);
|
|
90
|
+
animation: pulse-dot 2s ease-in-out infinite;
|
|
91
|
+
}
|
|
92
|
+
@keyframes pulse-dot {
|
|
93
|
+
0%, 100% { opacity: 1; }
|
|
94
|
+
50% { opacity: 0.5; }
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/* ── Status Cards ───────────────────────────── */
|
|
98
|
+
.status-cards {
|
|
99
|
+
display: grid;
|
|
100
|
+
grid-template-columns: repeat(4, 1fr);
|
|
101
|
+
gap: 12px;
|
|
102
|
+
}
|
|
103
|
+
.stat-card {
|
|
104
|
+
background: var(--bg-secondary);
|
|
105
|
+
border: 1px solid var(--border);
|
|
106
|
+
border-radius: 10px;
|
|
107
|
+
padding: 16px 20px;
|
|
108
|
+
text-align: center;
|
|
109
|
+
}
|
|
110
|
+
.stat-card .value {
|
|
111
|
+
font-size: 28px;
|
|
112
|
+
font-weight: 700;
|
|
113
|
+
margin-bottom: 4px;
|
|
114
|
+
}
|
|
115
|
+
.stat-card .label {
|
|
116
|
+
font-size: 10px;
|
|
117
|
+
text-transform: uppercase;
|
|
118
|
+
letter-spacing: 1.5px;
|
|
119
|
+
color: var(--text-secondary);
|
|
120
|
+
}
|
|
121
|
+
.stat-card:nth-child(1) .value { color: var(--cyan); }
|
|
122
|
+
.stat-card:nth-child(2) .value { color: var(--green); }
|
|
123
|
+
.stat-card:nth-child(3) .value { color: var(--gold); }
|
|
124
|
+
.stat-card:nth-child(4) .value { color: var(--purple); }
|
|
125
|
+
|
|
126
|
+
/* ── Generate Panel ─────────────────────────── */
|
|
127
|
+
.generate-panel {
|
|
128
|
+
background: var(--bg-secondary);
|
|
129
|
+
border: 1px solid var(--border);
|
|
130
|
+
border-radius: 10px;
|
|
131
|
+
padding: 16px 20px;
|
|
132
|
+
}
|
|
133
|
+
.generate-panel .panel-title {
|
|
134
|
+
font-size: 11px;
|
|
135
|
+
text-transform: uppercase;
|
|
136
|
+
letter-spacing: 2px;
|
|
137
|
+
color: var(--text-secondary);
|
|
138
|
+
margin-bottom: 12px;
|
|
139
|
+
display: flex;
|
|
140
|
+
align-items: center;
|
|
141
|
+
gap: 8px;
|
|
142
|
+
}
|
|
143
|
+
.generate-form {
|
|
144
|
+
display: flex;
|
|
145
|
+
gap: 10px;
|
|
146
|
+
align-items: flex-end;
|
|
147
|
+
}
|
|
148
|
+
.form-group {
|
|
149
|
+
display: flex;
|
|
150
|
+
flex-direction: column;
|
|
151
|
+
gap: 4px;
|
|
152
|
+
}
|
|
153
|
+
.form-group.task-group { flex: 1; }
|
|
154
|
+
.form-group label {
|
|
155
|
+
font-size: 10px;
|
|
156
|
+
color: var(--text-muted);
|
|
157
|
+
text-transform: uppercase;
|
|
158
|
+
letter-spacing: 1px;
|
|
159
|
+
}
|
|
160
|
+
.generate-form textarea {
|
|
161
|
+
background: var(--bg-primary);
|
|
162
|
+
border: 1px solid var(--border);
|
|
163
|
+
border-radius: 6px;
|
|
164
|
+
color: var(--text-primary);
|
|
165
|
+
font-family: inherit;
|
|
166
|
+
font-size: 12px;
|
|
167
|
+
padding: 10px 12px;
|
|
168
|
+
resize: none;
|
|
169
|
+
height: 60px;
|
|
170
|
+
width: 100%;
|
|
171
|
+
outline: none;
|
|
172
|
+
transition: border-color 0.2s;
|
|
173
|
+
}
|
|
174
|
+
.generate-form textarea:focus {
|
|
175
|
+
border-color: var(--cyan);
|
|
176
|
+
}
|
|
177
|
+
.generate-form select {
|
|
178
|
+
background: var(--bg-primary);
|
|
179
|
+
border: 1px solid var(--border);
|
|
180
|
+
border-radius: 6px;
|
|
181
|
+
color: var(--text-primary);
|
|
182
|
+
font-family: inherit;
|
|
183
|
+
font-size: 12px;
|
|
184
|
+
padding: 10px 12px;
|
|
185
|
+
outline: none;
|
|
186
|
+
cursor: pointer;
|
|
187
|
+
min-width: 130px;
|
|
188
|
+
}
|
|
189
|
+
.btn-generate {
|
|
190
|
+
background: linear-gradient(135deg, var(--cyan), var(--blue));
|
|
191
|
+
border: none;
|
|
192
|
+
border-radius: 6px;
|
|
193
|
+
color: #0a0a1a;
|
|
194
|
+
font-family: inherit;
|
|
195
|
+
font-size: 12px;
|
|
196
|
+
font-weight: 700;
|
|
197
|
+
padding: 10px 24px;
|
|
198
|
+
cursor: pointer;
|
|
199
|
+
text-transform: uppercase;
|
|
200
|
+
letter-spacing: 1px;
|
|
201
|
+
transition: all 0.2s;
|
|
202
|
+
white-space: nowrap;
|
|
203
|
+
height: 40px;
|
|
204
|
+
}
|
|
205
|
+
.btn-generate:hover { opacity: 0.9; transform: translateY(-1px); }
|
|
206
|
+
.btn-generate:disabled { opacity: 0.4; cursor: not-allowed; transform: none; }
|
|
207
|
+
|
|
208
|
+
/* ── Main Content Grid ──────────────────────── */
|
|
209
|
+
.main-grid {
|
|
210
|
+
display: grid;
|
|
211
|
+
grid-template-columns: 1fr 1fr;
|
|
212
|
+
gap: 16px;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/* ── Pending Review ─────────────────────────── */
|
|
216
|
+
.pending-panel {
|
|
217
|
+
background: var(--bg-secondary);
|
|
218
|
+
border: 1px solid var(--border);
|
|
219
|
+
border-radius: 10px;
|
|
220
|
+
padding: 16px 20px;
|
|
221
|
+
max-height: 600px;
|
|
222
|
+
overflow-y: auto;
|
|
223
|
+
grid-column: 1 / -1;
|
|
224
|
+
}
|
|
225
|
+
.pending-panel .panel-title {
|
|
226
|
+
font-size: 11px;
|
|
227
|
+
text-transform: uppercase;
|
|
228
|
+
letter-spacing: 2px;
|
|
229
|
+
color: var(--text-secondary);
|
|
230
|
+
margin-bottom: 12px;
|
|
231
|
+
display: flex;
|
|
232
|
+
align-items: center;
|
|
233
|
+
gap: 8px;
|
|
234
|
+
}
|
|
235
|
+
.pending-panel .panel-title .count {
|
|
236
|
+
background: var(--gold);
|
|
237
|
+
color: #0a0a1a;
|
|
238
|
+
font-size: 10px;
|
|
239
|
+
font-weight: 700;
|
|
240
|
+
padding: 2px 8px;
|
|
241
|
+
border-radius: 10px;
|
|
242
|
+
}
|
|
243
|
+
.pending-panel::-webkit-scrollbar { width: 4px; }
|
|
244
|
+
.pending-panel::-webkit-scrollbar-track { background: transparent; }
|
|
245
|
+
.pending-panel::-webkit-scrollbar-thumb { background: var(--border); border-radius: 2px; }
|
|
246
|
+
|
|
247
|
+
.review-card {
|
|
248
|
+
background: var(--bg-primary);
|
|
249
|
+
border: 1px solid var(--border);
|
|
250
|
+
border-radius: 8px;
|
|
251
|
+
padding: 16px;
|
|
252
|
+
margin-bottom: 12px;
|
|
253
|
+
transition: border-color 0.2s;
|
|
254
|
+
}
|
|
255
|
+
.review-card:hover { border-color: var(--cyan); }
|
|
256
|
+
.review-card .task-title {
|
|
257
|
+
font-size: 13px;
|
|
258
|
+
font-weight: 600;
|
|
259
|
+
color: var(--text-primary);
|
|
260
|
+
margin-bottom: 8px;
|
|
261
|
+
display: flex;
|
|
262
|
+
justify-content: space-between;
|
|
263
|
+
align-items: center;
|
|
264
|
+
}
|
|
265
|
+
.review-card .task-title .gen-id {
|
|
266
|
+
font-size: 10px;
|
|
267
|
+
color: var(--text-muted);
|
|
268
|
+
font-weight: 400;
|
|
269
|
+
}
|
|
270
|
+
.review-card .meta-row {
|
|
271
|
+
display: flex;
|
|
272
|
+
gap: 12px;
|
|
273
|
+
margin-bottom: 10px;
|
|
274
|
+
flex-wrap: wrap;
|
|
275
|
+
}
|
|
276
|
+
.meta-tag {
|
|
277
|
+
font-size: 10px;
|
|
278
|
+
padding: 2px 8px;
|
|
279
|
+
border-radius: 4px;
|
|
280
|
+
background: rgba(100, 120, 255, 0.1);
|
|
281
|
+
color: var(--text-secondary);
|
|
282
|
+
}
|
|
283
|
+
.code-block {
|
|
284
|
+
background: #060612;
|
|
285
|
+
border: 1px solid rgba(100, 120, 255, 0.1);
|
|
286
|
+
border-radius: 6px;
|
|
287
|
+
padding: 14px;
|
|
288
|
+
margin: 10px 0;
|
|
289
|
+
overflow-x: auto;
|
|
290
|
+
max-height: 300px;
|
|
291
|
+
overflow-y: auto;
|
|
292
|
+
font-size: 12px;
|
|
293
|
+
line-height: 1.6;
|
|
294
|
+
position: relative;
|
|
295
|
+
}
|
|
296
|
+
.code-block pre {
|
|
297
|
+
margin: 0;
|
|
298
|
+
white-space: pre;
|
|
299
|
+
font-family: inherit;
|
|
300
|
+
}
|
|
301
|
+
.code-block::-webkit-scrollbar { width: 4px; height: 4px; }
|
|
302
|
+
.code-block::-webkit-scrollbar-track { background: transparent; }
|
|
303
|
+
.code-block::-webkit-scrollbar-thumb { background: var(--border); border-radius: 2px; }
|
|
304
|
+
/* Syntax highlighting */
|
|
305
|
+
.syn-keyword { color: var(--magenta); }
|
|
306
|
+
.syn-string { color: var(--green); }
|
|
307
|
+
.syn-comment { color: var(--text-muted); font-style: italic; }
|
|
308
|
+
.syn-number { color: var(--gold); }
|
|
309
|
+
.syn-type { color: var(--cyan); }
|
|
310
|
+
.syn-fn { color: var(--blue); }
|
|
311
|
+
|
|
312
|
+
.explanation {
|
|
313
|
+
font-size: 12px;
|
|
314
|
+
color: var(--text-secondary);
|
|
315
|
+
line-height: 1.6;
|
|
316
|
+
margin: 8px 0;
|
|
317
|
+
padding: 10px 12px;
|
|
318
|
+
background: rgba(100, 120, 255, 0.05);
|
|
319
|
+
border-radius: 6px;
|
|
320
|
+
border-left: 3px solid var(--purple);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
.review-actions {
|
|
324
|
+
display: flex;
|
|
325
|
+
gap: 8px;
|
|
326
|
+
margin-top: 12px;
|
|
327
|
+
align-items: center;
|
|
328
|
+
}
|
|
329
|
+
.review-actions input {
|
|
330
|
+
flex: 1;
|
|
331
|
+
background: var(--bg-primary);
|
|
332
|
+
border: 1px solid var(--border);
|
|
333
|
+
border-radius: 6px;
|
|
334
|
+
color: var(--text-primary);
|
|
335
|
+
font-family: inherit;
|
|
336
|
+
font-size: 11px;
|
|
337
|
+
padding: 8px 12px;
|
|
338
|
+
outline: none;
|
|
339
|
+
}
|
|
340
|
+
.review-actions input:focus { border-color: var(--cyan); }
|
|
341
|
+
.btn-approve, .btn-reject {
|
|
342
|
+
border: none;
|
|
343
|
+
border-radius: 6px;
|
|
344
|
+
font-family: inherit;
|
|
345
|
+
font-size: 11px;
|
|
346
|
+
font-weight: 700;
|
|
347
|
+
padding: 8px 18px;
|
|
348
|
+
cursor: pointer;
|
|
349
|
+
text-transform: uppercase;
|
|
350
|
+
letter-spacing: 1px;
|
|
351
|
+
transition: all 0.2s;
|
|
352
|
+
}
|
|
353
|
+
.btn-approve {
|
|
354
|
+
background: var(--green);
|
|
355
|
+
color: #0a0a1a;
|
|
356
|
+
}
|
|
357
|
+
.btn-approve:hover { box-shadow: var(--glow-green); }
|
|
358
|
+
.btn-reject {
|
|
359
|
+
background: var(--red);
|
|
360
|
+
color: #fff;
|
|
361
|
+
}
|
|
362
|
+
.btn-reject:hover { box-shadow: var(--glow-red); }
|
|
363
|
+
|
|
364
|
+
.no-pending {
|
|
365
|
+
text-align: center;
|
|
366
|
+
padding: 30px;
|
|
367
|
+
color: var(--text-muted);
|
|
368
|
+
font-size: 12px;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/* ── History Table ──────────────────────────── */
|
|
372
|
+
.history-panel {
|
|
373
|
+
background: var(--bg-secondary);
|
|
374
|
+
border: 1px solid var(--border);
|
|
375
|
+
border-radius: 10px;
|
|
376
|
+
padding: 16px 20px;
|
|
377
|
+
max-height: 400px;
|
|
378
|
+
overflow-y: auto;
|
|
379
|
+
}
|
|
380
|
+
.history-panel .panel-title {
|
|
381
|
+
font-size: 11px;
|
|
382
|
+
text-transform: uppercase;
|
|
383
|
+
letter-spacing: 2px;
|
|
384
|
+
color: var(--text-secondary);
|
|
385
|
+
margin-bottom: 12px;
|
|
386
|
+
}
|
|
387
|
+
.history-panel::-webkit-scrollbar { width: 4px; }
|
|
388
|
+
.history-panel::-webkit-scrollbar-track { background: transparent; }
|
|
389
|
+
.history-panel::-webkit-scrollbar-thumb { background: var(--border); border-radius: 2px; }
|
|
390
|
+
|
|
391
|
+
.history-table {
|
|
392
|
+
width: 100%;
|
|
393
|
+
border-collapse: collapse;
|
|
394
|
+
font-size: 11px;
|
|
395
|
+
}
|
|
396
|
+
.history-table th {
|
|
397
|
+
text-align: left;
|
|
398
|
+
padding: 8px 10px;
|
|
399
|
+
color: var(--text-muted);
|
|
400
|
+
font-weight: 500;
|
|
401
|
+
text-transform: uppercase;
|
|
402
|
+
letter-spacing: 1px;
|
|
403
|
+
font-size: 10px;
|
|
404
|
+
border-bottom: 1px solid var(--border);
|
|
405
|
+
}
|
|
406
|
+
.history-table td {
|
|
407
|
+
padding: 8px 10px;
|
|
408
|
+
border-bottom: 1px solid rgba(100, 120, 255, 0.05);
|
|
409
|
+
color: var(--text-secondary);
|
|
410
|
+
}
|
|
411
|
+
.history-table tr:hover td { background: rgba(100, 120, 255, 0.03); }
|
|
412
|
+
.history-table .task-cell {
|
|
413
|
+
max-width: 250px;
|
|
414
|
+
overflow: hidden;
|
|
415
|
+
text-overflow: ellipsis;
|
|
416
|
+
white-space: nowrap;
|
|
417
|
+
color: var(--text-primary);
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
/* Status badges */
|
|
421
|
+
.badge {
|
|
422
|
+
display: inline-block;
|
|
423
|
+
padding: 2px 8px;
|
|
424
|
+
border-radius: 4px;
|
|
425
|
+
font-size: 10px;
|
|
426
|
+
font-weight: 600;
|
|
427
|
+
text-transform: uppercase;
|
|
428
|
+
letter-spacing: 0.5px;
|
|
429
|
+
}
|
|
430
|
+
.badge-generating { background: rgba(68, 138, 255, 0.2); color: var(--blue); }
|
|
431
|
+
.badge-generated { background: rgba(255, 215, 0, 0.2); color: var(--gold); }
|
|
432
|
+
.badge-approved { background: rgba(0, 255, 136, 0.2); color: var(--green); }
|
|
433
|
+
.badge-rejected { background: rgba(255, 82, 82, 0.2); color: var(--red); }
|
|
434
|
+
.badge-failed { background: rgba(255, 82, 82, 0.15); color: var(--red); }
|
|
435
|
+
.badge-pending_review { background: rgba(255, 145, 0, 0.2); color: var(--orange); }
|
|
436
|
+
|
|
437
|
+
/* ── Patterns Panel ─────────────────────────── */
|
|
438
|
+
.patterns-panel {
|
|
439
|
+
background: var(--bg-secondary);
|
|
440
|
+
border: 1px solid var(--border);
|
|
441
|
+
border-radius: 10px;
|
|
442
|
+
padding: 16px 20px;
|
|
443
|
+
max-height: 400px;
|
|
444
|
+
overflow-y: auto;
|
|
445
|
+
}
|
|
446
|
+
.patterns-panel .panel-title {
|
|
447
|
+
font-size: 11px;
|
|
448
|
+
text-transform: uppercase;
|
|
449
|
+
letter-spacing: 2px;
|
|
450
|
+
color: var(--text-secondary);
|
|
451
|
+
margin-bottom: 12px;
|
|
452
|
+
}
|
|
453
|
+
.patterns-panel::-webkit-scrollbar { width: 4px; }
|
|
454
|
+
.patterns-panel::-webkit-scrollbar-track { background: transparent; }
|
|
455
|
+
.patterns-panel::-webkit-scrollbar-thumb { background: var(--border); border-radius: 2px; }
|
|
456
|
+
|
|
457
|
+
.pattern-tabs {
|
|
458
|
+
display: flex;
|
|
459
|
+
gap: 4px;
|
|
460
|
+
margin-bottom: 12px;
|
|
461
|
+
}
|
|
462
|
+
.pattern-tab {
|
|
463
|
+
background: transparent;
|
|
464
|
+
border: 1px solid var(--border);
|
|
465
|
+
border-radius: 6px;
|
|
466
|
+
color: var(--text-muted);
|
|
467
|
+
font-family: inherit;
|
|
468
|
+
font-size: 10px;
|
|
469
|
+
padding: 6px 12px;
|
|
470
|
+
cursor: pointer;
|
|
471
|
+
transition: all 0.2s;
|
|
472
|
+
text-transform: uppercase;
|
|
473
|
+
letter-spacing: 0.5px;
|
|
474
|
+
}
|
|
475
|
+
.pattern-tab.active {
|
|
476
|
+
background: rgba(0, 229, 255, 0.1);
|
|
477
|
+
border-color: var(--cyan);
|
|
478
|
+
color: var(--cyan);
|
|
479
|
+
}
|
|
480
|
+
.pattern-tab:hover:not(.active) {
|
|
481
|
+
border-color: var(--text-muted);
|
|
482
|
+
color: var(--text-secondary);
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
.pattern-list {
|
|
486
|
+
display: flex;
|
|
487
|
+
flex-direction: column;
|
|
488
|
+
gap: 4px;
|
|
489
|
+
}
|
|
490
|
+
.pattern-item {
|
|
491
|
+
display: flex;
|
|
492
|
+
justify-content: space-between;
|
|
493
|
+
align-items: center;
|
|
494
|
+
padding: 6px 10px;
|
|
495
|
+
background: rgba(255, 255, 255, 0.02);
|
|
496
|
+
border-radius: 4px;
|
|
497
|
+
font-size: 11px;
|
|
498
|
+
}
|
|
499
|
+
.pattern-item:hover { background: rgba(255, 255, 255, 0.04); }
|
|
500
|
+
.pattern-item .name { color: var(--text-primary); flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
501
|
+
.pattern-item .freq {
|
|
502
|
+
color: var(--cyan);
|
|
503
|
+
font-weight: 600;
|
|
504
|
+
margin-left: 8px;
|
|
505
|
+
white-space: nowrap;
|
|
506
|
+
}
|
|
507
|
+
.pattern-bar {
|
|
508
|
+
height: 3px;
|
|
509
|
+
background: var(--cyan);
|
|
510
|
+
border-radius: 2px;
|
|
511
|
+
opacity: 0.4;
|
|
512
|
+
margin-top: 3px;
|
|
513
|
+
transition: width 0.3s;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
/* ── Footer ─────────────────────────────────── */
|
|
517
|
+
.footer {
|
|
518
|
+
display: flex;
|
|
519
|
+
align-items: center;
|
|
520
|
+
justify-content: space-between;
|
|
521
|
+
padding: 10px 20px;
|
|
522
|
+
background: var(--bg-secondary);
|
|
523
|
+
border: 1px solid var(--border);
|
|
524
|
+
border-radius: 10px;
|
|
525
|
+
font-size: 10px;
|
|
526
|
+
color: var(--text-muted);
|
|
527
|
+
}
|
|
528
|
+
.footer a { color: var(--cyan); text-decoration: none; }
|
|
529
|
+
.footer a:hover { text-decoration: underline; }
|
|
530
|
+
|
|
531
|
+
/* ── Animations ─────────────────────────────── */
|
|
532
|
+
@keyframes flash-green {
|
|
533
|
+
0% { background: rgba(0, 255, 136, 0.15); }
|
|
534
|
+
100% { background: transparent; }
|
|
535
|
+
}
|
|
536
|
+
@keyframes flash-red {
|
|
537
|
+
0% { background: rgba(255, 82, 82, 0.15); }
|
|
538
|
+
100% { background: transparent; }
|
|
539
|
+
}
|
|
540
|
+
.flash-approved { animation: flash-green 1.5s ease-out; }
|
|
541
|
+
.flash-rejected { animation: flash-red 1.5s ease-out; }
|
|
542
|
+
|
|
543
|
+
/* ── Responsive ─────────────────────────────── */
|
|
544
|
+
@media (max-width: 900px) {
|
|
545
|
+
.status-cards { grid-template-columns: repeat(2, 1fr); }
|
|
546
|
+
.main-grid { grid-template-columns: 1fr; }
|
|
547
|
+
.generate-form { flex-direction: column; }
|
|
548
|
+
}
|
|
549
|
+
</style>
|
|
550
|
+
</head>
|
|
551
|
+
<body>
|
|
552
|
+
<div class="dashboard">
|
|
553
|
+
|
|
554
|
+
<!-- Header -->
|
|
555
|
+
<div class="header">
|
|
556
|
+
<div class="header-left">
|
|
557
|
+
<span class="logo"></></span>
|
|
558
|
+
<h1>CodeGen Dashboard</h1>
|
|
559
|
+
</div>
|
|
560
|
+
<div class="connection-status">
|
|
561
|
+
<div class="connection-dot" id="connDot"></div>
|
|
562
|
+
<span id="connText">Disconnected</span>
|
|
563
|
+
</div>
|
|
564
|
+
</div>
|
|
565
|
+
|
|
566
|
+
<!-- Status Cards -->
|
|
567
|
+
<div class="status-cards">
|
|
568
|
+
<div class="stat-card">
|
|
569
|
+
<div class="value" id="statTotal">0</div>
|
|
570
|
+
<div class="label">Total Generations</div>
|
|
571
|
+
</div>
|
|
572
|
+
<div class="stat-card">
|
|
573
|
+
<div class="value" id="statApproval">0%</div>
|
|
574
|
+
<div class="label">Approval Rate</div>
|
|
575
|
+
</div>
|
|
576
|
+
<div class="stat-card">
|
|
577
|
+
<div class="value" id="statTokens">0</div>
|
|
578
|
+
<div class="label">Tokens Used</div>
|
|
579
|
+
</div>
|
|
580
|
+
<div class="stat-card">
|
|
581
|
+
<div class="value" id="statRepos">0</div>
|
|
582
|
+
<div class="label">Repos Mined</div>
|
|
583
|
+
</div>
|
|
584
|
+
</div>
|
|
585
|
+
|
|
586
|
+
<!-- Generate Panel -->
|
|
587
|
+
<div class="generate-panel">
|
|
588
|
+
<div class="panel-title">
|
|
589
|
+
<span>⚡</span> Generate Code
|
|
590
|
+
</div>
|
|
591
|
+
<div class="generate-form">
|
|
592
|
+
<div class="form-group task-group">
|
|
593
|
+
<label>Task Description</label>
|
|
594
|
+
<textarea id="taskInput" placeholder="Describe what code to generate..."></textarea>
|
|
595
|
+
</div>
|
|
596
|
+
<div class="form-group">
|
|
597
|
+
<label>Language</label>
|
|
598
|
+
<select id="langSelect">
|
|
599
|
+
<option value="typescript">TypeScript</option>
|
|
600
|
+
<option value="javascript">JavaScript</option>
|
|
601
|
+
<option value="python">Python</option>
|
|
602
|
+
<option value="rust">Rust</option>
|
|
603
|
+
<option value="go">Go</option>
|
|
604
|
+
</select>
|
|
605
|
+
</div>
|
|
606
|
+
<button class="btn-generate" id="btnGenerate" onclick="triggerGenerate()">Generate</button>
|
|
607
|
+
</div>
|
|
608
|
+
</div>
|
|
609
|
+
|
|
610
|
+
<!-- Pending Review -->
|
|
611
|
+
<div class="pending-panel" id="pendingPanel">
|
|
612
|
+
<div class="panel-title">
|
|
613
|
+
<span>🔍</span> Pending Review <span class="count" id="pendingCount">0</span>
|
|
614
|
+
</div>
|
|
615
|
+
<div id="pendingList">
|
|
616
|
+
<div class="no-pending">No pending generations to review</div>
|
|
617
|
+
</div>
|
|
618
|
+
</div>
|
|
619
|
+
|
|
620
|
+
<!-- History + Patterns -->
|
|
621
|
+
<div class="main-grid">
|
|
622
|
+
<div class="history-panel">
|
|
623
|
+
<div class="panel-title">📋 Generation History</div>
|
|
624
|
+
<table class="history-table">
|
|
625
|
+
<thead>
|
|
626
|
+
<tr>
|
|
627
|
+
<th>#</th>
|
|
628
|
+
<th>Task</th>
|
|
629
|
+
<th>Status</th>
|
|
630
|
+
<th>Trigger</th>
|
|
631
|
+
<th>Tokens</th>
|
|
632
|
+
<th>Time</th>
|
|
633
|
+
<th>Date</th>
|
|
634
|
+
</tr>
|
|
635
|
+
</thead>
|
|
636
|
+
<tbody id="historyBody"></tbody>
|
|
637
|
+
</table>
|
|
638
|
+
</div>
|
|
639
|
+
<div class="patterns-panel">
|
|
640
|
+
<div class="panel-title">🧰 Mined Patterns</div>
|
|
641
|
+
<div class="pattern-tabs" id="patternTabs">
|
|
642
|
+
<button class="pattern-tab active" data-tab="dependencies" onclick="switchTab('dependencies')">Dependencies</button>
|
|
643
|
+
<button class="pattern-tab" data-tab="tech_stacks" onclick="switchTab('tech_stacks')">Tech Stacks</button>
|
|
644
|
+
<button class="pattern-tab" data-tab="structures" onclick="switchTab('structures')">Structures</button>
|
|
645
|
+
<button class="pattern-tab" data-tab="readme" onclick="switchTab('readme')">README</button>
|
|
646
|
+
</div>
|
|
647
|
+
<div class="pattern-list" id="patternList">
|
|
648
|
+
<div class="no-pending">No patterns extracted yet</div>
|
|
649
|
+
</div>
|
|
650
|
+
</div>
|
|
651
|
+
</div>
|
|
652
|
+
|
|
653
|
+
<!-- Footer -->
|
|
654
|
+
<div class="footer">
|
|
655
|
+
<span>CodeGen Dashboard — Brain Ecosystem</span>
|
|
656
|
+
<span id="footerStatus">Uptime: --</span>
|
|
657
|
+
</div>
|
|
658
|
+
|
|
659
|
+
</div>
|
|
660
|
+
|
|
661
|
+
<script>
|
|
662
|
+
// ── State ──────────────────────────────────────
|
|
663
|
+
let state = { summary: null, generations: [], pending: [], minerSummary: null, patterns: null };
|
|
664
|
+
let connected = false;
|
|
665
|
+
let startTime = Date.now();
|
|
666
|
+
let currentTab = 'dependencies';
|
|
667
|
+
let eventSource = null;
|
|
668
|
+
|
|
669
|
+
// ── SSE Connection ─────────────────────────────
|
|
670
|
+
function connectSSE() {
|
|
671
|
+
if (eventSource) { try { eventSource.close(); } catch {} }
|
|
672
|
+
eventSource = new EventSource('/events');
|
|
673
|
+
|
|
674
|
+
eventSource.addEventListener('connected', () => {
|
|
675
|
+
connected = true;
|
|
676
|
+
updateConnectionUI();
|
|
677
|
+
loadState();
|
|
678
|
+
});
|
|
679
|
+
|
|
680
|
+
eventSource.addEventListener('codegen:generated', (e) => {
|
|
681
|
+
const gen = JSON.parse(e.data);
|
|
682
|
+
loadState(); // Refresh full state
|
|
683
|
+
});
|
|
684
|
+
|
|
685
|
+
eventSource.addEventListener('codegen:approved', (e) => {
|
|
686
|
+
loadState();
|
|
687
|
+
});
|
|
688
|
+
|
|
689
|
+
eventSource.addEventListener('codegen:rejected', (e) => {
|
|
690
|
+
loadState();
|
|
691
|
+
});
|
|
692
|
+
|
|
693
|
+
eventSource.addEventListener('codegen:error', (e) => {
|
|
694
|
+
const err = JSON.parse(e.data);
|
|
695
|
+
showNotification('Generation error: ' + err.error, 'error');
|
|
696
|
+
loadState();
|
|
697
|
+
});
|
|
698
|
+
|
|
699
|
+
eventSource.addEventListener('heartbeat', () => {
|
|
700
|
+
// keep-alive
|
|
701
|
+
});
|
|
702
|
+
|
|
703
|
+
eventSource.onerror = () => {
|
|
704
|
+
connected = false;
|
|
705
|
+
updateConnectionUI();
|
|
706
|
+
setTimeout(connectSSE, 3000);
|
|
707
|
+
};
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
// ── Data Loading ───────────────────────────────
|
|
711
|
+
async function loadState() {
|
|
712
|
+
try {
|
|
713
|
+
const res = await fetch('/api/state');
|
|
714
|
+
if (!res.ok) return;
|
|
715
|
+
state = await res.json();
|
|
716
|
+
render();
|
|
717
|
+
} catch {}
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
// ── Rendering ──────────────────────────────────
|
|
721
|
+
function render() {
|
|
722
|
+
renderStats();
|
|
723
|
+
renderPending();
|
|
724
|
+
renderHistory();
|
|
725
|
+
renderPatterns();
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
function renderStats() {
|
|
729
|
+
const s = state.summary;
|
|
730
|
+
if (!s) return;
|
|
731
|
+
document.getElementById('statTotal').textContent = s.total_generations;
|
|
732
|
+
document.getElementById('statApproval').textContent = Math.round(s.approval_rate * 100) + '%';
|
|
733
|
+
document.getElementById('statTokens').textContent = formatNumber(s.total_tokens_used);
|
|
734
|
+
document.getElementById('statRepos').textContent = state.minerSummary ? state.minerSummary.total_repos_mined : '0';
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
function renderPending() {
|
|
738
|
+
const pending = state.pending || [];
|
|
739
|
+
document.getElementById('pendingCount').textContent = pending.length;
|
|
740
|
+
const container = document.getElementById('pendingList');
|
|
741
|
+
|
|
742
|
+
if (pending.length === 0) {
|
|
743
|
+
container.innerHTML = '<div class="no-pending">No pending generations to review</div>';
|
|
744
|
+
return;
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
container.innerHTML = pending.map(gen => `
|
|
748
|
+
<div class="review-card" id="review-${gen.id}">
|
|
749
|
+
<div class="task-title">
|
|
750
|
+
<span>${escapeHtml(gen.task)}</span>
|
|
751
|
+
<span class="gen-id">#${gen.id}</span>
|
|
752
|
+
</div>
|
|
753
|
+
<div class="meta-row">
|
|
754
|
+
<span class="meta-tag">${gen.language || 'typescript'}</span>
|
|
755
|
+
<span class="meta-tag">${gen.trigger}</span>
|
|
756
|
+
<span class="meta-tag">${gen.tokens_used} tokens</span>
|
|
757
|
+
<span class="meta-tag">${gen.generation_time_ms}ms</span>
|
|
758
|
+
${gen.context_summary ? `<span class="meta-tag">${escapeHtml(gen.context_summary)}</span>` : ''}
|
|
759
|
+
</div>
|
|
760
|
+
${gen.generated_code ? `<div class="code-block"><pre>${highlightSyntax(gen.generated_code, gen.language)}</pre></div>` : ''}
|
|
761
|
+
${gen.generated_explanation ? `<div class="explanation">${escapeHtml(gen.generated_explanation)}</div>` : ''}
|
|
762
|
+
<div class="review-actions">
|
|
763
|
+
<input type="text" id="notes-${gen.id}" placeholder="Review notes (optional)..." />
|
|
764
|
+
<button class="btn-approve" onclick="reviewGeneration(${gen.id}, 'approve')">Approve</button>
|
|
765
|
+
<button class="btn-reject" onclick="reviewGeneration(${gen.id}, 'reject')">Reject</button>
|
|
766
|
+
</div>
|
|
767
|
+
</div>
|
|
768
|
+
`).join('');
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
function renderHistory() {
|
|
772
|
+
const gens = state.generations || [];
|
|
773
|
+
const tbody = document.getElementById('historyBody');
|
|
774
|
+
|
|
775
|
+
tbody.innerHTML = gens.map(gen => `
|
|
776
|
+
<tr>
|
|
777
|
+
<td style="color:var(--text-muted)">#${gen.id}</td>
|
|
778
|
+
<td class="task-cell" title="${escapeAttr(gen.task)}">${escapeHtml(gen.task.substring(0, 60))}</td>
|
|
779
|
+
<td><span class="badge badge-${gen.status}">${gen.status}</span></td>
|
|
780
|
+
<td>${gen.trigger}</td>
|
|
781
|
+
<td>${formatNumber(gen.tokens_used)}</td>
|
|
782
|
+
<td>${gen.generation_time_ms ? (gen.generation_time_ms / 1000).toFixed(1) + 's' : '-'}</td>
|
|
783
|
+
<td style="color:var(--text-muted)">${formatDate(gen.created_at)}</td>
|
|
784
|
+
</tr>
|
|
785
|
+
`).join('');
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
function renderPatterns() {
|
|
789
|
+
const patterns = state.patterns;
|
|
790
|
+
const container = document.getElementById('patternList');
|
|
791
|
+
|
|
792
|
+
if (!patterns) {
|
|
793
|
+
container.innerHTML = '<div class="no-pending">No patterns extracted yet</div>';
|
|
794
|
+
return;
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
const data = patterns[currentTab] || [];
|
|
798
|
+
if (data.length === 0) {
|
|
799
|
+
container.innerHTML = '<div class="no-pending">No patterns in this category</div>';
|
|
800
|
+
return;
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
const maxFreq = Math.max(...data.map(p => p.frequency || 1));
|
|
804
|
+
|
|
805
|
+
container.innerHTML = data.map(p => {
|
|
806
|
+
let parsed = {};
|
|
807
|
+
try { parsed = JSON.parse(p.pattern_data); } catch {}
|
|
808
|
+
const name = parsed.name || parsed.stack || parsed.path || parsed.section || p.pattern_key;
|
|
809
|
+
const freq = p.frequency;
|
|
810
|
+
const pct = Math.round((freq / maxFreq) * 100);
|
|
811
|
+
|
|
812
|
+
return `
|
|
813
|
+
<div class="pattern-item">
|
|
814
|
+
<span class="name" title="${escapeAttr(name)}">${escapeHtml(name)}</span>
|
|
815
|
+
<span class="freq">${freq}x</span>
|
|
816
|
+
</div>
|
|
817
|
+
<div class="pattern-bar" style="width: ${pct}%"></div>
|
|
818
|
+
`;
|
|
819
|
+
}).join('');
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
// ── Actions ────────────────────────────────────
|
|
823
|
+
async function triggerGenerate() {
|
|
824
|
+
const taskInput = document.getElementById('taskInput');
|
|
825
|
+
const langSelect = document.getElementById('langSelect');
|
|
826
|
+
const btn = document.getElementById('btnGenerate');
|
|
827
|
+
const task = taskInput.value.trim();
|
|
828
|
+
|
|
829
|
+
if (!task) { taskInput.focus(); return; }
|
|
830
|
+
|
|
831
|
+
btn.disabled = true;
|
|
832
|
+
btn.textContent = 'Generating...';
|
|
833
|
+
|
|
834
|
+
try {
|
|
835
|
+
const res = await fetch('/api/generate', {
|
|
836
|
+
method: 'POST',
|
|
837
|
+
headers: { 'Content-Type': 'application/json' },
|
|
838
|
+
body: JSON.stringify({ task, language: langSelect.value }),
|
|
839
|
+
});
|
|
840
|
+
const data = await res.json();
|
|
841
|
+
if (data.accepted) {
|
|
842
|
+
taskInput.value = '';
|
|
843
|
+
showNotification('Generation started: ' + task.substring(0, 50), 'info');
|
|
844
|
+
} else {
|
|
845
|
+
showNotification('Error: ' + (data.error || 'Unknown error'), 'error');
|
|
846
|
+
}
|
|
847
|
+
} catch (err) {
|
|
848
|
+
showNotification('Network error', 'error');
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
btn.disabled = false;
|
|
852
|
+
btn.textContent = 'Generate';
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
async function reviewGeneration(id, action) {
|
|
856
|
+
const notesInput = document.getElementById('notes-' + id);
|
|
857
|
+
const notes = notesInput ? notesInput.value.trim() : '';
|
|
858
|
+
const card = document.getElementById('review-' + id);
|
|
859
|
+
|
|
860
|
+
try {
|
|
861
|
+
const res = await fetch(`/api/${action}/${id}`, {
|
|
862
|
+
method: 'POST',
|
|
863
|
+
headers: { 'Content-Type': 'application/json' },
|
|
864
|
+
body: JSON.stringify({ notes: notes || undefined }),
|
|
865
|
+
});
|
|
866
|
+
|
|
867
|
+
if (res.ok) {
|
|
868
|
+
if (card) {
|
|
869
|
+
card.classList.add(action === 'approve' ? 'flash-approved' : 'flash-rejected');
|
|
870
|
+
setTimeout(() => loadState(), 800);
|
|
871
|
+
}
|
|
872
|
+
} else {
|
|
873
|
+
const data = await res.json();
|
|
874
|
+
showNotification('Error: ' + data.error, 'error');
|
|
875
|
+
}
|
|
876
|
+
} catch {
|
|
877
|
+
showNotification('Network error', 'error');
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
function switchTab(tab) {
|
|
882
|
+
currentTab = tab;
|
|
883
|
+
document.querySelectorAll('.pattern-tab').forEach(el => {
|
|
884
|
+
el.classList.toggle('active', el.dataset.tab === tab);
|
|
885
|
+
});
|
|
886
|
+
renderPatterns();
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
// ── Syntax Highlighting (regex-based) ──────────
|
|
890
|
+
function highlightSyntax(code, language) {
|
|
891
|
+
let html = escapeHtml(code);
|
|
892
|
+
|
|
893
|
+
// Comments: // ... and /* ... */
|
|
894
|
+
html = html.replace(/(\/\/[^\n]*)/g, '<span class="syn-comment">$1</span>');
|
|
895
|
+
html = html.replace(/(\/\*[\s\S]*?\*\/)/g, '<span class="syn-comment">$1</span>');
|
|
896
|
+
|
|
897
|
+
// Strings: "..." and '...' and `...`
|
|
898
|
+
html = html.replace(/("(?:[^&]|&(?!quot;))*?")/g, '<span class="syn-string">$1</span>');
|
|
899
|
+
html = html.replace(/('(?:[^&]|&(?!#x27;))*?')/g, '<span class="syn-string">$1</span>');
|
|
900
|
+
|
|
901
|
+
// Numbers
|
|
902
|
+
html = html.replace(/\b(\d+\.?\d*)\b/g, '<span class="syn-number">$1</span>');
|
|
903
|
+
|
|
904
|
+
// Keywords
|
|
905
|
+
const keywords = ['const', 'let', 'var', 'function', 'class', 'interface', 'type', 'import', 'export',
|
|
906
|
+
'from', 'return', 'if', 'else', 'for', 'while', 'async', 'await', 'new', 'throw', 'try', 'catch',
|
|
907
|
+
'finally', 'extends', 'implements', 'default', 'switch', 'case', 'break', 'continue', 'typeof',
|
|
908
|
+
'instanceof', 'void', 'null', 'undefined', 'true', 'false', 'this', 'super', 'static', 'readonly',
|
|
909
|
+
'public', 'private', 'protected', 'abstract', 'enum', 'namespace', 'declare', 'module',
|
|
910
|
+
'def', 'self', 'None', 'True', 'False', 'fn', 'impl', 'struct', 'trait', 'pub', 'mod', 'use',
|
|
911
|
+
'func', 'package', 'defer', 'go', 'chan', 'select', 'range', 'map', 'make'];
|
|
912
|
+
const kwRegex = new RegExp('\\b(' + keywords.join('|') + ')\\b', 'g');
|
|
913
|
+
html = html.replace(kwRegex, '<span class="syn-keyword">$1</span>');
|
|
914
|
+
|
|
915
|
+
// Types (capitalized words)
|
|
916
|
+
html = html.replace(/\b([A-Z][a-zA-Z0-9]+)\b/g, '<span class="syn-type">$1</span>');
|
|
917
|
+
|
|
918
|
+
return html;
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
// ── Utilities ──────────────────────────────────
|
|
922
|
+
function escapeHtml(str) {
|
|
923
|
+
if (!str) return '';
|
|
924
|
+
return str.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')
|
|
925
|
+
.replace(/"/g, '"').replace(/'/g, ''');
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
function escapeAttr(str) {
|
|
929
|
+
if (!str) return '';
|
|
930
|
+
return str.replace(/&/g, '&').replace(/"/g, '"').replace(/</g, '<').replace(/>/g, '>');
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
function formatNumber(n) {
|
|
934
|
+
if (n === undefined || n === null) return '0';
|
|
935
|
+
if (n >= 1_000_000) return (n / 1_000_000).toFixed(1) + 'M';
|
|
936
|
+
if (n >= 1_000) return (n / 1_000).toFixed(1) + 'k';
|
|
937
|
+
return String(n);
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
function formatDate(dateStr) {
|
|
941
|
+
if (!dateStr) return '-';
|
|
942
|
+
const d = new Date(dateStr + 'Z');
|
|
943
|
+
const now = new Date();
|
|
944
|
+
const diffMs = now - d;
|
|
945
|
+
if (diffMs < 60_000) return 'just now';
|
|
946
|
+
if (diffMs < 3_600_000) return Math.floor(diffMs / 60_000) + 'm ago';
|
|
947
|
+
if (diffMs < 86_400_000) return Math.floor(diffMs / 3_600_000) + 'h ago';
|
|
948
|
+
return d.toLocaleDateString();
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
function showNotification(msg, type) {
|
|
952
|
+
// Simple in-place notification via console + brief visual feedback
|
|
953
|
+
console.log(`[${type}] ${msg}`);
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
function updateConnectionUI() {
|
|
957
|
+
const dot = document.getElementById('connDot');
|
|
958
|
+
const text = document.getElementById('connText');
|
|
959
|
+
dot.classList.toggle('connected', connected);
|
|
960
|
+
text.textContent = connected ? 'Connected' : 'Disconnected';
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
function updateUptime() {
|
|
964
|
+
const secs = Math.floor((Date.now() - startTime) / 1000);
|
|
965
|
+
const mins = Math.floor(secs / 60);
|
|
966
|
+
const hrs = Math.floor(mins / 60);
|
|
967
|
+
let str = '';
|
|
968
|
+
if (hrs > 0) str += hrs + 'h ';
|
|
969
|
+
str += (mins % 60) + 'm ' + (secs % 60) + 's';
|
|
970
|
+
document.getElementById('footerStatus').textContent = 'Session: ' + str + (connected ? ' | Connected' : ' | Disconnected');
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
// ── Keyboard ───────────────────────────────────
|
|
974
|
+
document.addEventListener('keydown', (e) => {
|
|
975
|
+
if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') {
|
|
976
|
+
triggerGenerate();
|
|
977
|
+
}
|
|
978
|
+
});
|
|
979
|
+
|
|
980
|
+
// ── Init ───────────────────────────────────────
|
|
981
|
+
connectSSE();
|
|
982
|
+
loadState();
|
|
983
|
+
setInterval(updateUptime, 1000);
|
|
984
|
+
setInterval(loadState, 15000); // Poll every 15s as fallback
|
|
985
|
+
</script>
|
|
986
|
+
</body>
|
|
987
|
+
</html>
|