neohive 6.0.2 → 6.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +7 -0
- package/cli.js +113 -6
- package/conversation-templates/autonomous-feature.json +54 -4
- package/conversation-templates/code-review.json +41 -3
- package/conversation-templates/debug-squad.json +41 -3
- package/conversation-templates/feature-build.json +41 -3
- package/conversation-templates/research-write.json +41 -3
- package/dashboard.html +2000 -690
- package/dashboard.js +717 -65
- package/lib/compact.js +5 -2
- package/lib/config.js +4 -3
- package/lib/file-io.js +3 -3
- package/lib/resolve-server-data-dir.js +96 -0
- package/package.json +2 -2
- package/server.js +871 -147
- package/templates/debate.json +24 -5
- package/templates/managed.json +48 -9
- package/templates/pair.json +22 -3
- package/templates/review.json +26 -5
- package/templates/team.json +38 -8
package/dashboard.html
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
<meta charset="UTF-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
6
|
<title>Neohive</title>
|
|
7
|
-
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><rect rx='20' width='100' height='100' fill='%230d1117'/><path d='M20 30 Q20 20 30 20 H70 Q80 20 80 30 V55 Q80 65 70 65 H55 L40 80 V65 H30 Q20 65 20 55Z' fill='%
|
|
7
|
+
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><rect rx='20' width='100' height='100' fill='%230d1117'/><path d='M20 30 Q20 20 30 20 H70 Q80 20 80 30 V55 Q80 65 70 65 H55 L40 80 V65 H30 Q20 65 20 55Z' fill='%23f59e0b'/><circle cx='38' cy='42' r='5' fill='%230d1117'/><circle cx='55' cy='42' r='5' fill='%230d1117'/></svg>">
|
|
8
8
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
9
9
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
10
10
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet">
|
|
@@ -12,64 +12,69 @@
|
|
|
12
12
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
13
13
|
|
|
14
14
|
:root {
|
|
15
|
-
--bg: #
|
|
16
|
-
--surface: #
|
|
17
|
-
--surface-2: #
|
|
18
|
-
--surface-3: #
|
|
19
|
-
--border: rgba(255, 255, 255, 0.
|
|
20
|
-
--border-light: rgba(255, 255, 255, 0.
|
|
21
|
-
--text: #
|
|
22
|
-
--text-dim: #
|
|
23
|
-
--text-muted: #
|
|
24
|
-
--accent: #
|
|
25
|
-
--accent-dim: rgba(
|
|
26
|
-
--accent-glow: rgba(
|
|
27
|
-
--green: #
|
|
28
|
-
--green-dim: rgba(
|
|
29
|
-
--red: #
|
|
30
|
-
--red-dim: rgba(
|
|
31
|
-
--orange: #
|
|
32
|
-
--orange-dim: rgba(
|
|
33
|
-
--purple: #
|
|
34
|
-
--purple-dim: rgba(
|
|
35
|
-
--yellow: #
|
|
36
|
-
--
|
|
37
|
-
--
|
|
38
|
-
--
|
|
39
|
-
--
|
|
40
|
-
--
|
|
41
|
-
--
|
|
42
|
-
--
|
|
43
|
-
--
|
|
15
|
+
--bg: #09090b;
|
|
16
|
+
--surface: #111113;
|
|
17
|
+
--surface-2: #1a1a1f;
|
|
18
|
+
--surface-3: #232329;
|
|
19
|
+
--border: rgba(255, 255, 255, 0.07);
|
|
20
|
+
--border-light: rgba(255, 255, 255, 0.12);
|
|
21
|
+
--text: #ececf1;
|
|
22
|
+
--text-dim: #8e8ea0;
|
|
23
|
+
--text-muted: #56566a;
|
|
24
|
+
--accent: #f59e0b;
|
|
25
|
+
--accent-dim: rgba(245, 158, 11, 0.12);
|
|
26
|
+
--accent-glow: rgba(245, 158, 11, 0.2);
|
|
27
|
+
--green: #22c55e;
|
|
28
|
+
--green-dim: rgba(34, 197, 94, 0.12);
|
|
29
|
+
--red: #ef4444;
|
|
30
|
+
--red-dim: rgba(239, 68, 68, 0.12);
|
|
31
|
+
--orange: #f97316;
|
|
32
|
+
--orange-dim: rgba(249, 115, 22, 0.12);
|
|
33
|
+
--purple: #a78bfa;
|
|
34
|
+
--purple-dim: rgba(167, 139, 250, 0.12);
|
|
35
|
+
--yellow: #eab308;
|
|
36
|
+
--text-xs: 12px;
|
|
37
|
+
--text-sm: 13px;
|
|
38
|
+
--text-base: 14px;
|
|
39
|
+
--text-lg: 16px;
|
|
40
|
+
--text-xl: 24px;
|
|
41
|
+
--sidebar-w: 220px;
|
|
42
|
+
--header-h: 48px;
|
|
43
|
+
--glow: 0 0 24px rgba(245, 158, 11, 0.06);
|
|
44
|
+
--shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.4);
|
|
45
|
+
--shadow-md: 0 4px 12px rgba(0, 0, 0, 0.5);
|
|
46
|
+
--shadow-lg: 0 8px 32px rgba(0, 0, 0, 0.6);
|
|
47
|
+
--gradient-accent: linear-gradient(135deg, #f59e0b, #f97316);
|
|
48
|
+
--gradient-surface: linear-gradient(180deg, rgba(255,255,255,0.02) 0%, transparent 100%);
|
|
44
49
|
}
|
|
45
50
|
|
|
46
51
|
[data-theme="light"] {
|
|
47
|
-
--bg: #
|
|
52
|
+
--bg: #fafaf9;
|
|
48
53
|
--surface: #ffffff;
|
|
49
|
-
--surface-2: #
|
|
50
|
-
--surface-3: #
|
|
54
|
+
--surface-2: #f5f5f4;
|
|
55
|
+
--surface-3: #e7e5e4;
|
|
51
56
|
--border: rgba(0, 0, 0, 0.08);
|
|
52
57
|
--border-light: rgba(0, 0, 0, 0.12);
|
|
53
|
-
--text: #
|
|
54
|
-
--text-dim: #
|
|
55
|
-
--text-muted: #
|
|
56
|
-
--accent: #
|
|
57
|
-
--accent-dim: rgba(
|
|
58
|
-
--accent-glow: rgba(
|
|
59
|
-
--green: #
|
|
60
|
-
--green-dim: rgba(
|
|
58
|
+
--text: #0c0a09;
|
|
59
|
+
--text-dim: #57534e;
|
|
60
|
+
--text-muted: #a8a29e;
|
|
61
|
+
--accent: #d97706;
|
|
62
|
+
--accent-dim: rgba(217, 119, 6, 0.08);
|
|
63
|
+
--accent-glow: rgba(217, 119, 6, 0.12);
|
|
64
|
+
--green: #16a34a;
|
|
65
|
+
--green-dim: rgba(22, 163, 74, 0.08);
|
|
61
66
|
--red: #dc2626;
|
|
62
67
|
--red-dim: rgba(220, 38, 38, 0.08);
|
|
63
|
-
--orange: #
|
|
64
|
-
--orange-dim: rgba(
|
|
68
|
+
--orange: #ea580c;
|
|
69
|
+
--orange-dim: rgba(234, 88, 12, 0.08);
|
|
65
70
|
--purple: #7c3aed;
|
|
66
71
|
--purple-dim: rgba(124, 58, 237, 0.08);
|
|
67
|
-
--yellow: #
|
|
68
|
-
--glow: 0 0 20px rgba(
|
|
69
|
-
--shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.
|
|
70
|
-
--shadow-md: 0 4px 12px rgba(0, 0, 0, 0.
|
|
71
|
-
--shadow-lg: 0 8px 32px rgba(0, 0, 0, 0.
|
|
72
|
-
--gradient-accent: linear-gradient(135deg, #
|
|
72
|
+
--yellow: #ca8a04;
|
|
73
|
+
--glow: 0 0 20px rgba(217, 119, 6, 0.05);
|
|
74
|
+
--shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.06);
|
|
75
|
+
--shadow-md: 0 4px 12px rgba(0, 0, 0, 0.08);
|
|
76
|
+
--shadow-lg: 0 8px 32px rgba(0, 0, 0, 0.1);
|
|
77
|
+
--gradient-accent: linear-gradient(135deg, #d97706, #ea580c);
|
|
73
78
|
--gradient-surface: linear-gradient(180deg, rgba(0,0,0,0.01) 0%, transparent 100%);
|
|
74
79
|
}
|
|
75
80
|
|
|
@@ -112,12 +117,10 @@
|
|
|
112
117
|
|
|
113
118
|
/* ===== HEADER ===== */
|
|
114
119
|
.header {
|
|
115
|
-
background:
|
|
116
|
-
backdrop-filter: blur(16px) saturate(180%);
|
|
117
|
-
-webkit-backdrop-filter: blur(16px) saturate(180%);
|
|
120
|
+
background: var(--bg);
|
|
118
121
|
border-bottom: 1px solid var(--border);
|
|
119
|
-
padding: 0
|
|
120
|
-
height:
|
|
122
|
+
padding: 0 16px;
|
|
123
|
+
height: 48px;
|
|
121
124
|
display: flex;
|
|
122
125
|
align-items: center;
|
|
123
126
|
justify-content: space-between;
|
|
@@ -129,64 +132,53 @@
|
|
|
129
132
|
}
|
|
130
133
|
|
|
131
134
|
[data-theme="light"] .header {
|
|
132
|
-
background:
|
|
135
|
+
background: var(--bg);
|
|
133
136
|
}
|
|
134
137
|
|
|
135
138
|
.header-left {
|
|
136
139
|
display: flex;
|
|
137
140
|
align-items: center;
|
|
138
|
-
gap:
|
|
141
|
+
gap: 12px;
|
|
139
142
|
}
|
|
140
143
|
|
|
141
144
|
.logo {
|
|
142
|
-
font-size:
|
|
143
|
-
font-weight:
|
|
144
|
-
|
|
145
|
-
-webkit-background-clip: text;
|
|
146
|
-
-webkit-text-fill-color: transparent;
|
|
147
|
-
background-clip: text;
|
|
145
|
+
font-size: 15px;
|
|
146
|
+
font-weight: 700;
|
|
147
|
+
color: var(--text);
|
|
148
148
|
white-space: nowrap;
|
|
149
149
|
letter-spacing: -0.3px;
|
|
150
150
|
}
|
|
151
151
|
|
|
152
152
|
.header-stats {
|
|
153
|
-
display:
|
|
154
|
-
gap: 16px;
|
|
155
|
-
align-items: center;
|
|
153
|
+
display: none;
|
|
156
154
|
}
|
|
157
155
|
|
|
158
156
|
.h-stat {
|
|
159
|
-
display:
|
|
160
|
-
align-items: center;
|
|
161
|
-
gap: 5px;
|
|
162
|
-
font-size: 12px;
|
|
163
|
-
color: var(--text-dim);
|
|
157
|
+
display: none;
|
|
164
158
|
}
|
|
165
159
|
|
|
166
160
|
.h-stat-val {
|
|
167
|
-
|
|
168
|
-
color: var(--accent);
|
|
169
|
-
font-size: 13px;
|
|
161
|
+
display: none;
|
|
170
162
|
}
|
|
171
163
|
|
|
172
164
|
.header-actions {
|
|
173
165
|
display: flex;
|
|
174
166
|
align-items: center;
|
|
175
|
-
gap:
|
|
167
|
+
gap: 4px;
|
|
176
168
|
}
|
|
177
169
|
|
|
178
170
|
.connection {
|
|
179
171
|
display: flex;
|
|
180
172
|
align-items: center;
|
|
181
173
|
gap: 5px;
|
|
182
|
-
font-size:
|
|
174
|
+
font-size: 12px;
|
|
183
175
|
color: var(--text-dim);
|
|
184
|
-
margin-right:
|
|
176
|
+
margin-right: 4px;
|
|
185
177
|
}
|
|
186
178
|
|
|
187
179
|
.conn-dot {
|
|
188
|
-
width:
|
|
189
|
-
height:
|
|
180
|
+
width: 7px;
|
|
181
|
+
height: 7px;
|
|
190
182
|
border-radius: 50%;
|
|
191
183
|
background: var(--green);
|
|
192
184
|
box-shadow: 0 0 6px var(--green);
|
|
@@ -205,7 +197,7 @@
|
|
|
205
197
|
padding: 6px 12px;
|
|
206
198
|
border-radius: 8px;
|
|
207
199
|
cursor: pointer;
|
|
208
|
-
font-size:
|
|
200
|
+
font-size: 13px;
|
|
209
201
|
font-weight: 500;
|
|
210
202
|
transition: all 0.2s ease;
|
|
211
203
|
white-space: nowrap;
|
|
@@ -246,7 +238,13 @@
|
|
|
246
238
|
display: flex;
|
|
247
239
|
flex-direction: column;
|
|
248
240
|
overflow: hidden;
|
|
249
|
-
transition:
|
|
241
|
+
transition: width 0.25s ease, min-width 0.25s ease, margin-left 0.25s ease;
|
|
242
|
+
}
|
|
243
|
+
.sidebar.collapsed {
|
|
244
|
+
width: 0;
|
|
245
|
+
min-width: 0;
|
|
246
|
+
border-right: none;
|
|
247
|
+
overflow: hidden;
|
|
250
248
|
}
|
|
251
249
|
|
|
252
250
|
.sidebar-scroll {
|
|
@@ -260,7 +258,7 @@
|
|
|
260
258
|
}
|
|
261
259
|
|
|
262
260
|
.sidebar-title {
|
|
263
|
-
font-size:
|
|
261
|
+
font-size: 11px;
|
|
264
262
|
font-weight: 700;
|
|
265
263
|
color: var(--text-muted);
|
|
266
264
|
text-transform: uppercase;
|
|
@@ -296,7 +294,7 @@
|
|
|
296
294
|
border: 1px solid var(--border);
|
|
297
295
|
border-radius: 6px;
|
|
298
296
|
padding: 4px 8px;
|
|
299
|
-
font-size:
|
|
297
|
+
font-size: 12px;
|
|
300
298
|
color: var(--text);
|
|
301
299
|
outline: none;
|
|
302
300
|
min-width: 0;
|
|
@@ -307,7 +305,7 @@
|
|
|
307
305
|
border: 1px solid var(--border);
|
|
308
306
|
border-radius: 6px;
|
|
309
307
|
padding: 4px 4px;
|
|
310
|
-
font-size:
|
|
308
|
+
font-size: 11px;
|
|
311
309
|
color: var(--text);
|
|
312
310
|
outline: none;
|
|
313
311
|
cursor: pointer;
|
|
@@ -325,7 +323,7 @@
|
|
|
325
323
|
padding: 4px 6px;
|
|
326
324
|
cursor: pointer;
|
|
327
325
|
border-radius: 6px;
|
|
328
|
-
font-size:
|
|
326
|
+
font-size: 11px;
|
|
329
327
|
font-weight: 700;
|
|
330
328
|
text-transform: uppercase;
|
|
331
329
|
letter-spacing: 0.5px;
|
|
@@ -335,14 +333,14 @@
|
|
|
335
333
|
.role-group-header:hover { background: var(--surface-2); }
|
|
336
334
|
.role-group-header .role-group-arrow {
|
|
337
335
|
transition: transform 0.2s;
|
|
338
|
-
font-size:
|
|
336
|
+
font-size: 9px;
|
|
339
337
|
}
|
|
340
338
|
.role-group-header .role-group-arrow.collapsed { transform: rotate(-90deg); }
|
|
341
339
|
.role-group-header .role-group-count {
|
|
342
340
|
background: var(--surface-2);
|
|
343
341
|
padding: 1px 6px;
|
|
344
342
|
border-radius: 8px;
|
|
345
|
-
font-size:
|
|
343
|
+
font-size: 10px;
|
|
346
344
|
color: var(--text-dim);
|
|
347
345
|
}
|
|
348
346
|
.role-group-body.collapsed { display: none; }
|
|
@@ -352,20 +350,20 @@
|
|
|
352
350
|
background: var(--surface-2);
|
|
353
351
|
background-image: var(--gradient-surface);
|
|
354
352
|
border: 1px solid var(--border);
|
|
355
|
-
border-radius:
|
|
356
|
-
padding: 10px
|
|
357
|
-
margin-bottom:
|
|
353
|
+
border-radius: 8px;
|
|
354
|
+
padding: 6px 10px;
|
|
355
|
+
margin-bottom: 4px;
|
|
358
356
|
transition: all 0.25s ease;
|
|
357
|
+
cursor: pointer;
|
|
359
358
|
}
|
|
360
359
|
|
|
361
360
|
.agent-card:hover { border-color: var(--border-light); box-shadow: var(--shadow-sm); }
|
|
362
361
|
|
|
363
|
-
.agent-card.sleeping {
|
|
364
|
-
border-color: var(--orange);
|
|
362
|
+
.agent-card.sleeping, .agent-card.idle {
|
|
365
363
|
border-left: 3px solid var(--orange);
|
|
366
364
|
}
|
|
367
365
|
|
|
368
|
-
.agent-card.dead {
|
|
366
|
+
.agent-card.dead, .agent-card.offline {
|
|
369
367
|
opacity: 0.5;
|
|
370
368
|
}
|
|
371
369
|
|
|
@@ -373,142 +371,50 @@
|
|
|
373
371
|
display: flex;
|
|
374
372
|
align-items: center;
|
|
375
373
|
gap: 8px;
|
|
376
|
-
margin-bottom: 6px;
|
|
377
374
|
}
|
|
378
375
|
|
|
379
376
|
.agent-avatar {
|
|
380
|
-
width:
|
|
381
|
-
height:
|
|
377
|
+
width: 26px;
|
|
378
|
+
height: 26px;
|
|
382
379
|
border-radius: 50%;
|
|
383
380
|
display: flex;
|
|
384
381
|
align-items: center;
|
|
385
382
|
justify-content: center;
|
|
386
383
|
font-weight: 700;
|
|
387
|
-
font-size:
|
|
384
|
+
font-size: 11px;
|
|
388
385
|
color: #fff;
|
|
389
386
|
flex-shrink: 0;
|
|
390
387
|
}
|
|
391
388
|
|
|
392
389
|
.agent-info { flex: 1; min-width: 0; }
|
|
393
390
|
|
|
394
|
-
.agent-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
gap: 6px;
|
|
391
|
+
.agent-status-dot {
|
|
392
|
+
width: 8px;
|
|
393
|
+
height: 8px;
|
|
394
|
+
border-radius: 50%;
|
|
395
|
+
flex-shrink: 0;
|
|
400
396
|
}
|
|
397
|
+
.agent-status-dot.active, .agent-status-dot.working { background: #22c55e; box-shadow: 0 0 6px #22c55e; }
|
|
398
|
+
.agent-status-dot.listening { background: #3b82f6; box-shadow: 0 0 6px #3b82f6; animation: pulse 2s infinite; }
|
|
399
|
+
.agent-status-dot.sleeping, .agent-status-dot.idle { background: #f59e0b; animation: pulse 2s infinite; }
|
|
400
|
+
.agent-status-dot.dead, .agent-status-dot.offline { background: #6b7280; }
|
|
401
401
|
|
|
402
|
-
.agent-
|
|
403
|
-
font-size: 9px;
|
|
404
|
-
padding: 1px 5px;
|
|
405
|
-
border-radius: 6px;
|
|
402
|
+
.agent-name {
|
|
406
403
|
font-weight: 600;
|
|
407
|
-
|
|
408
|
-
letter-spacing: 0.3px;
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
.agent-badge.active { background: var(--green-dim); color: var(--green); }
|
|
412
|
-
.agent-badge.sleeping { background: var(--orange-dim); color: var(--orange); }
|
|
413
|
-
.agent-badge.dead { background: var(--red-dim); color: var(--red); }
|
|
414
|
-
|
|
415
|
-
.agent-status-intent {
|
|
416
|
-
font-size: 11px;
|
|
417
|
-
color: var(--text-dim);
|
|
418
|
-
padding: 3px 0;
|
|
404
|
+
font-size: 13px;
|
|
419
405
|
white-space: nowrap;
|
|
420
406
|
overflow: hidden;
|
|
421
407
|
text-overflow: ellipsis;
|
|
422
|
-
font-style: italic;
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
.agent-status-intent::before {
|
|
426
|
-
content: '\1F4AD';
|
|
427
|
-
margin-right: 4px;
|
|
428
|
-
font-style: normal;
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
.listen-badge {
|
|
432
|
-
font-size: 9px;
|
|
433
|
-
padding: 2px 6px;
|
|
434
|
-
border-radius: 6px;
|
|
435
|
-
font-weight: 700;
|
|
436
|
-
text-transform: uppercase;
|
|
437
|
-
letter-spacing: 0.3px;
|
|
438
|
-
display: inline-flex;
|
|
439
|
-
align-items: center;
|
|
440
|
-
gap: 4px;
|
|
441
|
-
margin-top: 5px;
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
.listen-badge.listening {
|
|
445
|
-
background: var(--green-dim);
|
|
446
|
-
color: var(--green);
|
|
447
|
-
border: 1px solid rgba(63, 185, 80, 0.3);
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
.listen-badge.busy {
|
|
451
|
-
background: var(--orange-dim);
|
|
452
|
-
color: var(--yellow);
|
|
453
|
-
border: 1px solid rgba(227, 179, 65, 0.3);
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
.listen-badge.not-listening {
|
|
457
|
-
background: var(--red-dim);
|
|
458
|
-
color: var(--red);
|
|
459
|
-
border: 1px solid rgba(248, 81, 73, 0.3);
|
|
460
|
-
animation: pulseAlert 1.5s infinite;
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
.listen-badge.offline {
|
|
464
|
-
background: var(--surface-3);
|
|
465
|
-
color: var(--text-muted);
|
|
466
|
-
border: 1px solid var(--border);
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
.listen-dot {
|
|
470
|
-
width: 5px;
|
|
471
|
-
height: 5px;
|
|
472
|
-
border-radius: 50%;
|
|
473
|
-
display: inline-block;
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
.listen-dot.on { background: var(--green); box-shadow: 0 0 8px var(--green); }
|
|
477
|
-
.listen-dot.off { background: var(--red); }
|
|
478
|
-
|
|
479
|
-
@keyframes pulseAlert {
|
|
480
|
-
0%, 100% { opacity: 1; }
|
|
481
|
-
50% { opacity: 0.6; }
|
|
482
408
|
}
|
|
483
409
|
|
|
484
|
-
.agent-
|
|
485
|
-
font-size:
|
|
410
|
+
.agent-subtitle {
|
|
411
|
+
font-size: 12px;
|
|
486
412
|
color: var(--text-muted);
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
.agent-activity {
|
|
492
|
-
font-size: 10px;
|
|
493
|
-
color: var(--text-dim);
|
|
494
|
-
margin-top: 4px;
|
|
495
|
-
display: flex;
|
|
496
|
-
align-items: center;
|
|
497
|
-
gap: 4px;
|
|
498
|
-
}
|
|
499
|
-
|
|
500
|
-
.agent-activity-icon {
|
|
501
|
-
display: inline-block;
|
|
502
|
-
width: 6px;
|
|
503
|
-
height: 6px;
|
|
504
|
-
border-radius: 50%;
|
|
505
|
-
flex-shrink: 0;
|
|
413
|
+
white-space: nowrap;
|
|
414
|
+
overflow: hidden;
|
|
415
|
+
text-overflow: ellipsis;
|
|
506
416
|
}
|
|
507
417
|
|
|
508
|
-
.agent-activity-icon.active { background: var(--green); box-shadow: 0 0 4px var(--green); }
|
|
509
|
-
.agent-activity-icon.sleeping { background: var(--orange); animation: pulse 2s infinite; }
|
|
510
|
-
.agent-activity-icon.dead { background: var(--red); }
|
|
511
|
-
|
|
512
418
|
.nudge-btn {
|
|
513
419
|
background: var(--orange-dim);
|
|
514
420
|
color: var(--orange);
|
|
@@ -908,15 +814,15 @@
|
|
|
908
814
|
flex-wrap: wrap;
|
|
909
815
|
}
|
|
910
816
|
|
|
911
|
-
.msg-from { font-weight: 600; font-size:
|
|
912
|
-
.msg-arrow { color: var(--text-muted); font-size:
|
|
913
|
-
.msg-to { font-size:
|
|
914
|
-
.msg-time { font-size:
|
|
817
|
+
.msg-from { font-weight: 600; font-size: 14px; }
|
|
818
|
+
.msg-arrow { color: var(--text-muted); font-size: 12px; }
|
|
819
|
+
.msg-to { font-size: 13px; color: var(--text-dim); }
|
|
820
|
+
.msg-time { font-size: 11px; color: var(--text-muted); }
|
|
915
821
|
|
|
916
822
|
.badge-channel {
|
|
917
823
|
background: var(--purple-dim);
|
|
918
824
|
color: var(--purple);
|
|
919
|
-
font-size:
|
|
825
|
+
font-size: 10px;
|
|
920
826
|
padding: 1px 5px;
|
|
921
827
|
border-radius: 6px;
|
|
922
828
|
font-weight: 600;
|
|
@@ -937,7 +843,7 @@
|
|
|
937
843
|
.channel-filter-bar::-webkit-scrollbar { display: none; }
|
|
938
844
|
|
|
939
845
|
.channel-tab {
|
|
940
|
-
font-size:
|
|
846
|
+
font-size: 12px;
|
|
941
847
|
padding: 3px 10px;
|
|
942
848
|
border-radius: 12px;
|
|
943
849
|
border: 1px solid var(--border);
|
|
@@ -963,7 +869,7 @@
|
|
|
963
869
|
}
|
|
964
870
|
|
|
965
871
|
.badge {
|
|
966
|
-
font-size:
|
|
872
|
+
font-size: 10px;
|
|
967
873
|
padding: 2px 6px;
|
|
968
874
|
border-radius: 6px;
|
|
969
875
|
font-weight: 600;
|
|
@@ -975,16 +881,16 @@
|
|
|
975
881
|
|
|
976
882
|
/* ===== MARKDOWN CONTENT ===== */
|
|
977
883
|
.msg-content {
|
|
978
|
-
font-size:
|
|
884
|
+
font-size: 14px;
|
|
979
885
|
line-height: 1.6;
|
|
980
886
|
word-break: break-word;
|
|
981
887
|
color: var(--text);
|
|
982
888
|
}
|
|
983
889
|
|
|
984
|
-
.msg-content h1 { font-size:
|
|
985
|
-
.msg-content h2 { font-size:
|
|
986
|
-
.msg-content h3 { font-size:
|
|
987
|
-
.msg-content h4 { font-size:
|
|
890
|
+
.msg-content h1 { font-size: 22px; font-weight: 700; margin: 12px 0 6px; padding-bottom: 4px; border-bottom: 1px solid var(--border); }
|
|
891
|
+
.msg-content h2 { font-size: 18px; font-weight: 700; margin: 10px 0 5px; padding-bottom: 3px; border-bottom: 1px solid var(--border); }
|
|
892
|
+
.msg-content h3 { font-size: 16px; font-weight: 600; margin: 8px 0 4px; }
|
|
893
|
+
.msg-content h4 { font-size: 15px; font-weight: 600; margin: 6px 0 3px; }
|
|
988
894
|
|
|
989
895
|
.msg-content p { margin: 4px 0; }
|
|
990
896
|
|
|
@@ -1200,8 +1106,8 @@
|
|
|
1200
1106
|
}
|
|
1201
1107
|
|
|
1202
1108
|
.empty-icon { font-size: 40px; opacity: 0.2; }
|
|
1203
|
-
.empty-text { font-size:
|
|
1204
|
-
.empty-sub { font-size:
|
|
1109
|
+
.empty-text { font-size: 16px; }
|
|
1110
|
+
.empty-sub { font-size: 13px; color: var(--text-muted); }
|
|
1205
1111
|
|
|
1206
1112
|
/* ===== MESSAGE FLASH ===== */
|
|
1207
1113
|
.message-new { animation: flashIn 0.8s ease-out; }
|
|
@@ -1489,28 +1395,272 @@
|
|
|
1489
1395
|
30% { opacity: 1; transform: translateY(-3px); }
|
|
1490
1396
|
}
|
|
1491
1397
|
|
|
1492
|
-
/* =====
|
|
1493
|
-
.
|
|
1398
|
+
/* ===== UNIFIED NAV SIDEBAR ===== */
|
|
1399
|
+
.nav-sidebar {
|
|
1400
|
+
width: 220px;
|
|
1401
|
+
min-width: 220px;
|
|
1402
|
+
background: var(--surface);
|
|
1403
|
+
border-right: 1px solid var(--border);
|
|
1404
|
+
display: flex;
|
|
1405
|
+
flex-direction: column;
|
|
1406
|
+
overflow-y: auto;
|
|
1407
|
+
overflow-x: hidden;
|
|
1408
|
+
flex-shrink: 0;
|
|
1409
|
+
transition: width 0.25s ease, min-width 0.25s ease;
|
|
1410
|
+
}
|
|
1411
|
+
.nav-sidebar.collapsed {
|
|
1412
|
+
width: 56px;
|
|
1413
|
+
min-width: 56px;
|
|
1414
|
+
}
|
|
1415
|
+
.nav-sidebar-header {
|
|
1416
|
+
display: flex;
|
|
1417
|
+
align-items: center;
|
|
1418
|
+
justify-content: space-between;
|
|
1419
|
+
padding: 16px 16px 12px;
|
|
1420
|
+
flex-shrink: 0;
|
|
1421
|
+
}
|
|
1422
|
+
.nav-sidebar-logo {
|
|
1423
|
+
font-size: 13px;
|
|
1424
|
+
font-weight: 700;
|
|
1425
|
+
color: var(--text-dim);
|
|
1426
|
+
white-space: nowrap;
|
|
1427
|
+
overflow: hidden;
|
|
1428
|
+
letter-spacing: 0.02em;
|
|
1429
|
+
}
|
|
1430
|
+
.nav-sidebar.collapsed .nav-sidebar-logo { display: none; }
|
|
1431
|
+
.nav-sidebar-toggle {
|
|
1432
|
+
background: none;
|
|
1433
|
+
border: none;
|
|
1434
|
+
color: var(--text-muted);
|
|
1435
|
+
font-size: 14px;
|
|
1436
|
+
cursor: pointer;
|
|
1437
|
+
padding: 6px 8px;
|
|
1438
|
+
border-radius: 6px;
|
|
1439
|
+
transition: all 0.15s;
|
|
1440
|
+
font-family: inherit;
|
|
1441
|
+
flex-shrink: 0;
|
|
1442
|
+
}
|
|
1443
|
+
.nav-sidebar-toggle:hover { background: rgba(255,255,255,0.06); color: var(--text); }
|
|
1444
|
+
.nav-sidebar.collapsed .nav-sidebar-header { justify-content: center; padding: 14px 8px 10px; }
|
|
1445
|
+
.nav-sidebar.collapsed .nav-sidebar-toggle { margin: 0; }
|
|
1446
|
+
.nav-sidebar-section { padding: 4px 6px; }
|
|
1447
|
+
.nav-section-main { display: flex; flex-direction: column; gap: 1px; }
|
|
1448
|
+
.nav-sidebar-divider { height: 1px; background: var(--border); margin: 10px 14px; flex-shrink: 0; }
|
|
1449
|
+
.nav-sidebar-label {
|
|
1450
|
+
font-size: 10px;
|
|
1451
|
+
font-weight: 600;
|
|
1452
|
+
color: var(--text-muted);
|
|
1453
|
+
letter-spacing: 0.08em;
|
|
1454
|
+
padding: 8px 14px 6px;
|
|
1455
|
+
white-space: nowrap;
|
|
1456
|
+
overflow: hidden;
|
|
1457
|
+
text-transform: uppercase;
|
|
1458
|
+
}
|
|
1459
|
+
.nav-sidebar.collapsed .nav-sidebar-label { display: none; }
|
|
1460
|
+
.nav-sidebar.collapsed .nav-sidebar-project { display: none; }
|
|
1461
|
+
.nav-sidebar.collapsed .nav-sidebar-agents #agents-list { display: none; }
|
|
1462
|
+
.nav-sidebar-spacer { flex: 1; }
|
|
1463
|
+
.nav-sidebar-agents { overflow-y: auto; min-height: 0; }
|
|
1464
|
+
|
|
1465
|
+
/* Nav items */
|
|
1466
|
+
.nav-item {
|
|
1467
|
+
display: flex;
|
|
1468
|
+
align-items: center;
|
|
1469
|
+
gap: 10px;
|
|
1470
|
+
padding: 8px 12px;
|
|
1471
|
+
cursor: pointer;
|
|
1472
|
+
border-radius: 8px;
|
|
1473
|
+
transition: all 0.15s;
|
|
1474
|
+
white-space: nowrap;
|
|
1475
|
+
overflow: hidden;
|
|
1476
|
+
margin: 1px 6px;
|
|
1477
|
+
position: relative;
|
|
1478
|
+
}
|
|
1479
|
+
.nav-item:hover { background: rgba(255,255,255,0.04); }
|
|
1480
|
+
.nav-item.active { background: var(--accent-dim); }
|
|
1481
|
+
.nav-item .nav-icon { line-height: 0; flex-shrink: 0; width: 20px; min-width: 20px; display: flex; align-items: center; justify-content: center; color: var(--text-muted); }
|
|
1482
|
+
.nav-item .nav-icon svg { flex-shrink: 0; width: 16px; height: 16px; }
|
|
1483
|
+
.nav-item.active .nav-icon { color: var(--accent); }
|
|
1484
|
+
.nav-item:hover .nav-icon { color: var(--text); }
|
|
1485
|
+
.nav-item .nav-text { font-size: 13px; font-weight: 500; color: var(--text-dim); }
|
|
1486
|
+
.nav-item.active .nav-text { color: var(--accent); font-weight: 600; }
|
|
1487
|
+
.nav-item:hover .nav-text { color: var(--text); }
|
|
1488
|
+
.nav-sidebar.collapsed .nav-item { justify-content: center; padding: 10px 4px; margin: 0; }
|
|
1489
|
+
.nav-sidebar.collapsed .nav-item .nav-text { display: none; }
|
|
1490
|
+
|
|
1491
|
+
/* ===== AGENT STATUS BAR ===== */
|
|
1492
|
+
.agent-bar {
|
|
1494
1493
|
display: flex;
|
|
1494
|
+
align-items: center;
|
|
1495
|
+
gap: 6px;
|
|
1496
|
+
padding: 6px 12px;
|
|
1495
1497
|
background: var(--surface);
|
|
1496
1498
|
border-bottom: 1px solid var(--border);
|
|
1499
|
+
overflow-x: auto;
|
|
1500
|
+
flex-shrink: 0;
|
|
1501
|
+
min-height: 36px;
|
|
1502
|
+
}
|
|
1503
|
+
.agent-bar::-webkit-scrollbar { height: 0; }
|
|
1504
|
+
.agent-pill {
|
|
1505
|
+
display: flex;
|
|
1506
|
+
align-items: center;
|
|
1507
|
+
gap: 5px;
|
|
1508
|
+
padding: 3px 10px 3px 6px;
|
|
1509
|
+
border-radius: 20px;
|
|
1510
|
+
background: var(--surface-2);
|
|
1511
|
+
border: 1px solid var(--border);
|
|
1512
|
+
font-size: 13px;
|
|
1513
|
+
white-space: nowrap;
|
|
1514
|
+
cursor: pointer;
|
|
1515
|
+
transition: all 0.15s;
|
|
1516
|
+
flex-shrink: 0;
|
|
1497
1517
|
}
|
|
1518
|
+
.agent-pill:hover { border-color: var(--accent); }
|
|
1519
|
+
.agent-pill .status-dot {
|
|
1520
|
+
width: 7px; height: 7px; border-radius: 50%; flex-shrink: 0;
|
|
1521
|
+
}
|
|
1522
|
+
.agent-pill.online .status-dot, .agent-pill.working .status-dot { background: #22c55e; }
|
|
1523
|
+
.agent-pill.listening .status-dot { background: #3b82f6; animation: pulse 2s infinite; }
|
|
1524
|
+
.agent-pill.idle .status-dot { background: #f59e0b; }
|
|
1525
|
+
.agent-pill.offline .status-dot { background: #6b7280; opacity: 0.6; }
|
|
1526
|
+
.agent-pill .agent-name { font-weight: 600; color: var(--text); }
|
|
1527
|
+
.agent-pill .agent-activity { color: var(--text-muted); font-size: 12px; }
|
|
1528
|
+
.agent-pill.offline .agent-name { color: var(--text-muted); opacity: 0.6; }
|
|
1498
1529
|
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1530
|
+
/* ===== OVERVIEW PAGE ===== */
|
|
1531
|
+
.overview-area { display: none; flex-direction: column; gap: 24px; padding: 28px 32px; overflow-y: auto; }
|
|
1532
|
+
.overview-area.visible { display: flex; }
|
|
1533
|
+
|
|
1534
|
+
.overview-welcome {
|
|
1535
|
+
margin-bottom: 4px;
|
|
1536
|
+
}
|
|
1537
|
+
.overview-welcome h2 {
|
|
1538
|
+
font-size: 22px;
|
|
1539
|
+
font-weight: 700;
|
|
1540
|
+
color: var(--text);
|
|
1541
|
+
margin-bottom: 4px;
|
|
1542
|
+
letter-spacing: -0.3px;
|
|
1543
|
+
}
|
|
1544
|
+
.overview-welcome p {
|
|
1545
|
+
font-size: 13px;
|
|
1505
1546
|
color: var(--text-muted);
|
|
1506
|
-
cursor: pointer;
|
|
1507
|
-
border-bottom: 2px solid transparent;
|
|
1508
|
-
transition: all 0.2s ease;
|
|
1509
|
-
letter-spacing: 0.02em;
|
|
1510
1547
|
}
|
|
1511
1548
|
|
|
1512
|
-
.
|
|
1513
|
-
|
|
1549
|
+
.overview-metrics {
|
|
1550
|
+
display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 16px;
|
|
1551
|
+
}
|
|
1552
|
+
.metric-card {
|
|
1553
|
+
background: var(--surface); border: 1px solid var(--border); border-radius: 12px;
|
|
1554
|
+
padding: 20px 22px; display: flex; flex-direction: column; gap: 6px;
|
|
1555
|
+
position: relative; overflow: hidden;
|
|
1556
|
+
transition: border-color 0.2s, box-shadow 0.2s;
|
|
1557
|
+
}
|
|
1558
|
+
.metric-card:hover { border-color: var(--border-light); box-shadow: var(--shadow-sm); }
|
|
1559
|
+
.metric-card .metric-icon {
|
|
1560
|
+
width: 36px; height: 36px; border-radius: 10px;
|
|
1561
|
+
display: flex; align-items: center; justify-content: center;
|
|
1562
|
+
margin-bottom: 4px; flex-shrink: 0;
|
|
1563
|
+
}
|
|
1564
|
+
.metric-card .metric-label { font-size: 12px; color: var(--text-muted); font-weight: 500; letter-spacing: 0.02em; }
|
|
1565
|
+
.metric-card .metric-value { font-size: 32px; font-weight: 800; color: var(--text); line-height: 1.1; letter-spacing: -0.5px; }
|
|
1566
|
+
.metric-card .metric-sub { font-size: 12px; color: var(--text-muted); margin-top: 2px; }
|
|
1567
|
+
|
|
1568
|
+
.overview-grid {
|
|
1569
|
+
display: grid; grid-template-columns: 1fr 1fr; gap: 16px;
|
|
1570
|
+
}
|
|
1571
|
+
.overview-panel {
|
|
1572
|
+
background: var(--surface); border: 1px solid var(--border); border-radius: 12px; padding: 20px;
|
|
1573
|
+
transition: border-color 0.2s;
|
|
1574
|
+
}
|
|
1575
|
+
.overview-panel:hover { border-color: var(--border-light); }
|
|
1576
|
+
.overview-panel h3 {
|
|
1577
|
+
font-size: 14px; font-weight: 600; color: var(--text); margin-bottom: 14px;
|
|
1578
|
+
display: flex; align-items: center; justify-content: space-between;
|
|
1579
|
+
}
|
|
1580
|
+
.overview-panel h3 a { font-size: 12px; font-weight: 500; color: var(--text-muted); cursor: pointer; text-decoration: none; transition: color 0.15s; }
|
|
1581
|
+
.overview-panel h3 a:hover { color: var(--accent); }
|
|
1582
|
+
.overview-agent-row {
|
|
1583
|
+
display: flex; align-items: center; gap: 10px; padding: 8px 0;
|
|
1584
|
+
border-bottom: 1px solid var(--border);
|
|
1585
|
+
}
|
|
1586
|
+
.overview-agent-row:last-child { border-bottom: none; }
|
|
1587
|
+
.overview-agent-dot { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; }
|
|
1588
|
+
.overview-agent-name { font-size: 13px; font-weight: 600; color: var(--text); flex: 1; }
|
|
1589
|
+
.overview-agent-status { font-size: 12px; color: var(--text-muted); }
|
|
1590
|
+
.overview-msg-row {
|
|
1591
|
+
display: flex; gap: 10px; padding: 8px 0; border-bottom: 1px solid var(--border); font-size: 13px; align-items: baseline;
|
|
1592
|
+
}
|
|
1593
|
+
.overview-msg-row:last-child { border-bottom: none; }
|
|
1594
|
+
.overview-msg-from { font-weight: 600; color: var(--text); min-width: 80px; font-size: 13px; }
|
|
1595
|
+
.overview-msg-text { color: var(--text-muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex: 1; font-size: 13px; }
|
|
1596
|
+
.overview-msg-time { color: var(--text-muted); font-size: 11px; flex-shrink: 0; }
|
|
1597
|
+
.overview-full-width { grid-column: 1 / -1; }
|
|
1598
|
+
.overview-task-summary { display: flex; gap: 10px; flex-wrap: wrap; }
|
|
1599
|
+
.overview-task-badge {
|
|
1600
|
+
padding: 6px 14px; border-radius: 8px; font-size: 13px; font-weight: 600;
|
|
1601
|
+
}
|
|
1602
|
+
.overview-task-badge.pending { background: rgba(234, 179, 8, 0.1); color: var(--yellow); }
|
|
1603
|
+
.overview-task-badge.active { background: rgba(245, 158, 11, 0.1); color: var(--accent); }
|
|
1604
|
+
.overview-task-badge.done { background: rgba(63, 185, 80, 0.1); color: var(--green); }
|
|
1605
|
+
.overview-launch-btn {
|
|
1606
|
+
display: inline-flex; align-items: center; gap: 8px; padding: 10px 20px;
|
|
1607
|
+
background: var(--gradient-accent); color: #fff; border: none; border-radius: 10px;
|
|
1608
|
+
font-size: 13px; font-weight: 600; cursor: pointer; font-family: inherit; margin-top: 12px;
|
|
1609
|
+
transition: all 0.2s; box-shadow: 0 2px 8px var(--accent-glow);
|
|
1610
|
+
}
|
|
1611
|
+
.overview-launch-btn:hover { transform: translateY(-1px); box-shadow: 0 4px 16px var(--accent-glow); }
|
|
1612
|
+
|
|
1613
|
+
/* ===== TOAST NOTIFICATIONS ===== */
|
|
1614
|
+
.toast-container {
|
|
1615
|
+
position: fixed; top: 16px; left: 50%; transform: translateX(-50%); z-index: 9999;
|
|
1616
|
+
display: flex; flex-direction: column; gap: 8px; pointer-events: none; align-items: center;
|
|
1617
|
+
}
|
|
1618
|
+
.toast {
|
|
1619
|
+
background: var(--surface); border: 1px solid var(--border); border-radius: 10px;
|
|
1620
|
+
padding: 12px 20px; font-size: 13px; color: var(--text); box-shadow: var(--shadow-lg);
|
|
1621
|
+
pointer-events: auto; display: flex; align-items: center; gap: 10px;
|
|
1622
|
+
animation: toast-in 0.3s ease-out;
|
|
1623
|
+
max-width: 420px;
|
|
1624
|
+
}
|
|
1625
|
+
.toast.toast-error { background: var(--red-dim); border-color: var(--red); color: var(--red); }
|
|
1626
|
+
.toast.toast-success { background: var(--green-dim); border-color: var(--green); }
|
|
1627
|
+
.toast.leaving { animation: toast-out 0.3s ease-in forwards; }
|
|
1628
|
+
.toast-icon { font-size: 16px; flex-shrink: 0; }
|
|
1629
|
+
.toast-text { flex: 1; }
|
|
1630
|
+
@keyframes toast-in { from { opacity: 0; transform: translateY(-12px) scale(0.95); } to { opacity: 1; transform: translateY(0) scale(1); } }
|
|
1631
|
+
@keyframes toast-out { from { opacity: 1; transform: translateY(0); } to { opacity: 0; transform: translateY(12px); } }
|
|
1632
|
+
|
|
1633
|
+
/* ===== USER MESSAGES ===== */
|
|
1634
|
+
.message.user-msg { background: rgba(245, 158, 11, 0.04); border-left: 2px solid var(--accent); }
|
|
1635
|
+
|
|
1636
|
+
/* Hide old view-tabs (replaced by nav sidebar) */
|
|
1637
|
+
.view-tabs { display: none !important; }
|
|
1638
|
+
|
|
1639
|
+
/* ===== SETTINGS MENU ===== */
|
|
1640
|
+
.header-settings-btn {
|
|
1641
|
+
background: none; border: none; color: var(--text-muted); cursor: pointer;
|
|
1642
|
+
padding: 6px; border-radius: 6px; display: flex; align-items: center;
|
|
1643
|
+
transition: all 0.15s;
|
|
1644
|
+
}
|
|
1645
|
+
.header-settings-btn:hover { background: rgba(255,255,255,0.06); color: var(--text); }
|
|
1646
|
+
.settings-item {
|
|
1647
|
+
padding: 8px 14px; font-size: 12px; color: var(--text-dim); cursor: pointer;
|
|
1648
|
+
display: flex; align-items: center; gap: 8px; transition: background 0.1s;
|
|
1649
|
+
}
|
|
1650
|
+
.settings-item:hover { background: rgba(255,255,255,0.04); color: var(--text); }
|
|
1651
|
+
.settings-item.settings-danger { color: var(--red); }
|
|
1652
|
+
.settings-item.settings-danger:hover { background: var(--red-dim); }
|
|
1653
|
+
|
|
1654
|
+
/* (metric-card styles consolidated in overview section above) */
|
|
1655
|
+
|
|
1656
|
+
/* ===== BUTTON SYSTEM ===== */
|
|
1657
|
+
.btn-primary { background: var(--gradient-accent); color: #fff; border: none; }
|
|
1658
|
+
.btn-secondary { background: transparent; color: var(--text-dim); border: 1px solid var(--border); }
|
|
1659
|
+
.btn-secondary:hover { border-color: var(--accent); color: var(--text); }
|
|
1660
|
+
.btn-ghost { background: none; border: none; color: var(--text-muted); }
|
|
1661
|
+
.btn-ghost:hover { color: var(--text); }
|
|
1662
|
+
.btn-danger { background: transparent; color: var(--red); border: 1px solid var(--red-dim); }
|
|
1663
|
+
.btn-danger:hover { background: var(--red-dim); }
|
|
1514
1664
|
|
|
1515
1665
|
/* ===== LAN BADGE ===== */
|
|
1516
1666
|
.lan-badge {
|
|
@@ -2093,7 +2243,7 @@
|
|
|
2093
2243
|
@media (max-width: 768px) {
|
|
2094
2244
|
:root {
|
|
2095
2245
|
--sidebar-w: 280px;
|
|
2096
|
-
--header-h:
|
|
2246
|
+
--header-h: 48px;
|
|
2097
2247
|
}
|
|
2098
2248
|
|
|
2099
2249
|
.mobile-toggle { display: block; }
|
|
@@ -2201,12 +2351,23 @@
|
|
|
2201
2351
|
|
|
2202
2352
|
/* Scroll-to-bottom button: bigger touch target */
|
|
2203
2353
|
.scroll-bottom { width: 44px; height: 44px; font-size: 18px; bottom: 80px; }
|
|
2354
|
+
|
|
2355
|
+
/* Icon rail: hide on mobile */
|
|
2356
|
+
.nav-sidebar { display: none; }
|
|
2357
|
+
|
|
2358
|
+
/* Agent bar: smaller on mobile */
|
|
2359
|
+
.agent-bar { padding: 4px 8px; min-height: 30px; }
|
|
2360
|
+
.agent-pill { font-size: 10px; padding: 2px 8px 2px 5px; }
|
|
2361
|
+
|
|
2362
|
+
/* Overview: single column on mobile */
|
|
2363
|
+
.overview-grid { grid-template-columns: 1fr; }
|
|
2364
|
+
.overview-metrics { grid-template-columns: 1fr; }
|
|
2204
2365
|
}
|
|
2205
2366
|
|
|
2206
2367
|
/* ===== MOBILE: PHONE (480px) ===== */
|
|
2207
2368
|
@media (max-width: 480px) {
|
|
2208
2369
|
:root {
|
|
2209
|
-
--header-h:
|
|
2370
|
+
--header-h: 44px;
|
|
2210
2371
|
}
|
|
2211
2372
|
|
|
2212
2373
|
.header { padding: 0 8px; }
|
|
@@ -2581,11 +2742,47 @@
|
|
|
2581
2742
|
.launch-area {
|
|
2582
2743
|
flex: 1;
|
|
2583
2744
|
overflow-y: auto;
|
|
2584
|
-
padding:
|
|
2745
|
+
padding: 28px 32px;
|
|
2585
2746
|
display: none;
|
|
2586
2747
|
}
|
|
2587
|
-
|
|
2588
2748
|
.launch-area.visible { display: block; }
|
|
2749
|
+
.launch-section {
|
|
2750
|
+
background: var(--surface); border: 1px solid var(--border); border-radius: 12px;
|
|
2751
|
+
padding: 20px; margin-bottom: 20px;
|
|
2752
|
+
}
|
|
2753
|
+
.launch-section h3 { font-size: 16px; font-weight: 700; margin-bottom: 16px; color: var(--text); }
|
|
2754
|
+
.launch-form-row { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin-bottom: 12px; }
|
|
2755
|
+
.launch-form-field { display: flex; flex-direction: column; gap: 4px; }
|
|
2756
|
+
.launch-form-field label { font-size: 12px; font-weight: 500; color: var(--text-muted); }
|
|
2757
|
+
.launch-form-field input, .launch-form-field select {
|
|
2758
|
+
background: var(--surface-2); border: 1px solid var(--border); border-radius: 8px;
|
|
2759
|
+
padding: 8px 12px; font-size: 13px; color: var(--text); outline: none; transition: border-color 0.15s;
|
|
2760
|
+
}
|
|
2761
|
+
.launch-form-field input:focus, .launch-form-field select:focus { border-color: var(--accent); }
|
|
2762
|
+
.launch-form-field input.invalid { border-color: var(--red); }
|
|
2763
|
+
.launch-form-field .field-hint { font-size: 11px; color: var(--text-muted); min-height: 16px; }
|
|
2764
|
+
.launch-form-field .field-hint.error { color: var(--red); }
|
|
2765
|
+
.launch-form-field .field-hint.success { color: var(--green); }
|
|
2766
|
+
.launch-prompt-output {
|
|
2767
|
+
background: var(--bg); border: 1px solid var(--border); border-radius: 8px;
|
|
2768
|
+
padding: 12px; font-size: 12px; font-family: 'SFMono-Regular', Consolas, monospace;
|
|
2769
|
+
color: var(--text); line-height: 1.5; max-height: 200px; overflow-y: auto;
|
|
2770
|
+
white-space: pre-wrap; cursor: pointer; position: relative; transition: border-color 0.15s;
|
|
2771
|
+
}
|
|
2772
|
+
.launch-prompt-output:hover { border-color: var(--accent); }
|
|
2773
|
+
.template-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 12px; }
|
|
2774
|
+
.template-card {
|
|
2775
|
+
background: var(--surface-2); border: 1px solid var(--border); border-radius: 10px;
|
|
2776
|
+
padding: 16px; cursor: pointer; transition: all 0.15s;
|
|
2777
|
+
}
|
|
2778
|
+
.template-card:hover { border-color: var(--accent); transform: translateY(-1px); box-shadow: var(--shadow-sm); }
|
|
2779
|
+
.template-card-name { font-size: 14px; font-weight: 600; color: var(--text); margin-bottom: 4px; }
|
|
2780
|
+
.template-card-desc { font-size: 12px; color: var(--text-muted); margin-bottom: 10px; line-height: 1.4; }
|
|
2781
|
+
.template-card-agents { display: flex; flex-wrap: wrap; gap: 4px; }
|
|
2782
|
+
.template-agent-chip {
|
|
2783
|
+
font-size: 11px; padding: 2px 8px; border-radius: 6px;
|
|
2784
|
+
background: var(--accent-dim); color: var(--accent); font-weight: 500;
|
|
2785
|
+
}
|
|
2589
2786
|
|
|
2590
2787
|
/* ===== RULES VIEW ===== */
|
|
2591
2788
|
.rules-area {
|
|
@@ -2697,7 +2894,7 @@
|
|
|
2697
2894
|
.docs-badge { display: inline-block; padding: 2px 8px; border-radius: 10px; font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.5px; }
|
|
2698
2895
|
.docs-badge-beta { background: rgba(210, 153, 34, 0.15); color: #d29922; }
|
|
2699
2896
|
.docs-badge-new { background: rgba(63, 185, 80, 0.15); color: #3fb950; }
|
|
2700
|
-
.docs-badge-stable { background: rgba(
|
|
2897
|
+
.docs-badge-stable { background: rgba(245, 158, 11, 0.15); color: var(--accent); }
|
|
2701
2898
|
.docs-phase-diagram { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; margin: 10px 0; }
|
|
2702
2899
|
.docs-phase-box { background: var(--surface-2); border: 1px solid var(--border); border-radius: 8px; padding: 6px 14px; font-size: 12px; font-weight: 600; color: var(--text); }
|
|
2703
2900
|
.docs-phase-arrow { color: var(--text-muted); font-size: 14px; }
|
|
@@ -3125,10 +3322,7 @@
|
|
|
3125
3322
|
color: var(--text-muted);
|
|
3126
3323
|
}
|
|
3127
3324
|
|
|
3128
|
-
/* =====
|
|
3129
|
-
/* Plugins removed in v3.4.3 */
|
|
3130
|
-
|
|
3131
|
-
/* ===== v3.0: AVATAR PICKER ===== */
|
|
3325
|
+
/* ===== AVATAR PICKER ===== */
|
|
3132
3326
|
.avatar-option {
|
|
3133
3327
|
width: 44px;
|
|
3134
3328
|
height: 44px;
|
|
@@ -3329,49 +3523,36 @@
|
|
|
3329
3523
|
<div class="header">
|
|
3330
3524
|
<div class="header-left">
|
|
3331
3525
|
<button class="mobile-toggle" onclick="toggleSidebar()" aria-label="Menu">☰</button>
|
|
3332
|
-
<
|
|
3526
|
+
<svg width="18" height="18" viewBox="0 0 32 32" style="flex-shrink:0"><defs><linearGradient id="hg" x1="0%" y1="0%" x2="100%" y2="100%"><stop offset="0%" stop-color="#f59e0b"/><stop offset="100%" stop-color="#f97316"/></linearGradient></defs><path d="M16 2L27.6 9v14L16 30 4.4 23V9z" fill="url(#hg)" opacity="0.9"/><path d="M16 7L23 11v8l-7 4-7-4v-8z" fill="#09090b" opacity="0.6"/><circle cx="16" cy="15.5" r="3" fill="#fff" opacity="0.9"/></svg>
|
|
3333
3527
|
<div class="logo">Neohive</div>
|
|
3334
|
-
|
|
3335
|
-
<
|
|
3336
|
-
|
|
3337
|
-
</
|
|
3338
|
-
<
|
|
3339
|
-
|
|
3340
|
-
|
|
3341
|
-
<div class="h-stat"><span class="h-stat-val" id="stat-sleeping" style="color:var(--orange)">0</span> sleeping</div>
|
|
3342
|
-
<div class="h-stat"><span class="h-stat-val" id="stat-threads">0</span> threads</div>
|
|
3343
|
-
<div class="h-stat"><span class="h-stat-val" id="stat-clis">0</span> CLI types</div>
|
|
3344
|
-
<div class="h-stat" id="managed-badge" style="display:none;background:rgba(88,166,255,0.15);border-radius:8px;padding:2px 8px">
|
|
3345
|
-
<span class="h-stat-val" style="color:#58a6ff;font-size:11px" id="managed-phase">MANAGED</span>
|
|
3346
|
-
<span style="color:#8b949e;font-size:10px" id="managed-floor-info"></span>
|
|
3347
|
-
</div>
|
|
3348
|
-
</div>
|
|
3528
|
+
<!-- Hidden stat elements for JS compatibility -->
|
|
3529
|
+
<span id="stat-messages" style="display:none">0</span>
|
|
3530
|
+
<span id="stat-agents" style="display:none">0</span>
|
|
3531
|
+
<span id="stat-sleeping" style="display:none">0</span>
|
|
3532
|
+
<span id="stat-threads" style="display:none">0</span>
|
|
3533
|
+
<span id="stat-clis" style="display:none">0</span>
|
|
3534
|
+
<div id="managed-badge" style="display:none"><span id="managed-phase"></span><span id="managed-floor-info"></span></div>
|
|
3349
3535
|
</div>
|
|
3350
3536
|
<div class="header-actions">
|
|
3537
|
+
<span class="conn-dot" title="Connected"></span>
|
|
3538
|
+
<!-- Settings dropdown -->
|
|
3351
3539
|
<div style="position:relative;display:inline-block">
|
|
3352
|
-
<button class="
|
|
3353
|
-
|
|
3354
|
-
|
|
3355
|
-
|
|
3540
|
+
<button class="header-settings-btn" onclick="toggleSettingsMenu()" title="Settings">
|
|
3541
|
+
<svg viewBox="0 0 16 16" width="15" height="15" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="8" cy="8" r="2.5"/><path d="M8 1v2M8 13v2M1 8h2M13 8h2M2.9 2.9l1.4 1.4M11.7 11.7l1.4 1.4M13.1 2.9l-1.4 1.4M4.3 11.7l-1.4 1.4"/></svg>
|
|
3542
|
+
</button>
|
|
3543
|
+
<div id="settings-menu" style="display:none;position:absolute;right:0;top:100%;margin-top:6px;background:var(--surface);border:1px solid var(--border);border-radius:10px;overflow:hidden;z-index:300;min-width:180px;box-shadow:var(--shadow-lg)">
|
|
3544
|
+
<div class="settings-item" onclick="toggleTheme();toggleSettingsMenu()"><svg viewBox="0 0 16 16" width="14" height="14" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="8" cy="8" r="4"/><path d="M8 1v2M8 13v2M1 8h2M13 8h2"/></svg> Theme</div>
|
|
3545
|
+
<div class="settings-item" id="settings-notif-item" onclick="toggleCombinedNotifications();"><svg viewBox="0 0 16 16" width="14" height="14" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M4 6a4 4 0 018 0v3l2 2H2l2-2z"/><path d="M6 13a2 2 0 004 0"/></svg> <span id="settings-notif-label">Notifications</span></div>
|
|
3546
|
+
<div style="height:1px;background:var(--border);margin:4px 8px"></div>
|
|
3547
|
+
<div class="settings-item" onclick="exportShareableHTML();toggleSettingsMenu()">Export HTML</div>
|
|
3548
|
+
<div class="settings-item" onclick="exportJSON();toggleSettingsMenu()">Export JSON</div>
|
|
3549
|
+
<div class="settings-item" onclick="enterReplay();toggleSettingsMenu()">Replay</div>
|
|
3550
|
+
<div class="settings-item" onclick="openPhoneAccess();toggleSettingsMenu()">Phone Access</div>
|
|
3551
|
+
<div class="settings-item" onclick="switchView('docs');toggleSettingsMenu()">Help & Docs</div>
|
|
3552
|
+
<div style="height:1px;background:var(--border);margin:4px 8px"></div>
|
|
3553
|
+
<div class="settings-item settings-danger" onclick="doReset();toggleSettingsMenu()">Reset Data</div>
|
|
3356
3554
|
</div>
|
|
3357
3555
|
</div>
|
|
3358
|
-
<button class="notif-toggle" id="notif-toggle" onclick="toggleNotifications()" title="Browser notifications">🔔</button>
|
|
3359
|
-
<button class="theme-toggle" id="theme-toggle" onclick="toggleTheme()" title="Toggle dark/light theme">🌙</button>
|
|
3360
|
-
<span onclick="toggleShortcutsOverlay()" style="font-size:10px;color:var(--text-muted);cursor:pointer;padding:0 4px" title="Keyboard shortcuts">? keys</span>
|
|
3361
|
-
<button class="sound-toggle" id="sound-toggle" onclick="toggleSound()" title="Toggle notification sound">🔈</button>
|
|
3362
|
-
<button class="phone-btn" id="phone-btn" onclick="openPhoneAccess()" title="Phone access (LAN)">📱</button>
|
|
3363
|
-
<div class="connection"><span class="conn-dot"></span><span id="conn-label">Live</span><span class="conn-detail" id="conn-detail"></span></div>
|
|
3364
|
-
<button class="btn" onclick="enterReplay()" id="replay-header-btn" title="Replay conversation">Replay</button>
|
|
3365
|
-
<div style="position:relative;display:inline-block">
|
|
3366
|
-
<button class="btn btn-primary" onclick="toggleExportMenu()">Export ▾</button>
|
|
3367
|
-
<div id="export-menu" style="display:none;position:absolute;right:0;top:100%;margin-top:4px;background:var(--surface-2);border:1px solid var(--border);border-radius:6px;overflow:hidden;z-index:200;min-width:160px">
|
|
3368
|
-
<div style="padding:7px 12px;font-size:12px;cursor:pointer;transition:background 0.1s" onmouseover="this.style.background='var(--surface-3)'" onmouseout="this.style.background=''" onclick="exportShareableHTML();toggleExportMenu()">HTML (shareable)</div>
|
|
3369
|
-
<div style="padding:7px 12px;font-size:12px;cursor:pointer;transition:background 0.1s" onmouseover="this.style.background='var(--surface-3)'" onmouseout="this.style.background=''" onclick="exportConversation();toggleExportMenu()">Markdown (.md)</div>
|
|
3370
|
-
<div style="padding:7px 12px;font-size:12px;cursor:pointer;transition:background 0.1s" onmouseover="this.style.background='var(--surface-3)'" onmouseout="this.style.background=''" onclick="exportJSON();toggleExportMenu()">JSON (.json)</div>
|
|
3371
|
-
<div style="padding:7px 12px;font-size:12px;cursor:pointer;transition:background 0.1s;border-top:1px solid var(--border)" onmouseover="this.style.background='var(--surface-3)'" onmouseout="this.style.background=''" onclick="exportReplay();toggleExportMenu()">Animated Replay (.html)</div>
|
|
3372
|
-
</div>
|
|
3373
|
-
</div>
|
|
3374
|
-
<button class="btn btn-danger" onclick="doReset()">Reset</button>
|
|
3375
3556
|
</div>
|
|
3376
3557
|
</div>
|
|
3377
3558
|
|
|
@@ -3406,85 +3587,76 @@
|
|
|
3406
3587
|
|
|
3407
3588
|
<!-- APP LAYOUT -->
|
|
3408
3589
|
<div class="app">
|
|
3409
|
-
|
|
3410
|
-
|
|
3411
|
-
|
|
3412
|
-
|
|
3413
|
-
|
|
3414
|
-
<div class="project-switcher">
|
|
3415
|
-
<select class="project-select" id="project-select" onchange="switchProject()">
|
|
3416
|
-
<option value="">Default (local)</option>
|
|
3417
|
-
</select>
|
|
3418
|
-
<input class="project-input" id="project-path-input" placeholder="Enter project folder path..."
|
|
3419
|
-
onkeydown="if(event.key==='Enter'){addProject();}">
|
|
3420
|
-
<div class="project-actions">
|
|
3421
|
-
<button class="btn btn-primary" onclick="showAddProject()">+ Add</button>
|
|
3422
|
-
<button class="btn" onclick="discoverProjects()">Discover</button>
|
|
3423
|
-
<button class="btn btn-danger" onclick="removeProject()" id="remove-project-btn" style="display:none">Remove</button>
|
|
3424
|
-
</div>
|
|
3425
|
-
<div id="discover-results" style="display:none"></div>
|
|
3590
|
+
<!-- UNIFIED NAV SIDEBAR -->
|
|
3591
|
+
<nav class="nav-sidebar expanded" id="nav-sidebar">
|
|
3592
|
+
<div class="nav-sidebar-header">
|
|
3593
|
+
<span class="nav-sidebar-logo"><svg viewBox="0 0 16 16" width="14" height="14" fill="none" stroke="var(--accent)" stroke-width="1.5" style="vertical-align:-2px;margin-right:6px"><path d="M8 1.5L14 5v6l-6 3.5L2 11V5z"/></svg>Neohive</span>
|
|
3594
|
+
<button class="nav-sidebar-toggle" onclick="toggleNavSidebar()" title="Collapse sidebar">◀</button>
|
|
3426
3595
|
</div>
|
|
3427
|
-
|
|
3428
|
-
|
|
3429
|
-
<div class="
|
|
3430
|
-
<
|
|
3431
|
-
<span>Agents</span>
|
|
3432
|
-
<span class="alert-count" id="alert-count" style="display:none">0</span>
|
|
3433
|
-
</div>
|
|
3434
|
-
<div class="agent-filter-bar" id="agent-filter-bar">
|
|
3435
|
-
<input type="text" id="agent-search" placeholder="Search..." oninput="filterAgents()">
|
|
3436
|
-
<select id="agent-role-filter" onchange="filterAgents()">
|
|
3437
|
-
<option value="">Role</option>
|
|
3438
|
-
<option value="lead">Lead</option>
|
|
3439
|
-
<option value="backend">Backend</option>
|
|
3440
|
-
<option value="frontend">Frontend</option>
|
|
3441
|
-
<option value="quality">Quality</option>
|
|
3442
|
-
<option value="monitor">Monitor</option>
|
|
3443
|
-
<option value="advisor">Advisor</option>
|
|
3444
|
-
</select>
|
|
3445
|
-
<select id="agent-status-filter" onchange="filterAgents()">
|
|
3446
|
-
<option value="">Status</option>
|
|
3447
|
-
<option value="active">Online</option>
|
|
3448
|
-
<option value="dead">Offline</option>
|
|
3449
|
-
<option value="sleeping">Idle</option>
|
|
3450
|
-
</select>
|
|
3451
|
-
</div>
|
|
3452
|
-
<div id="agents-list"></div>
|
|
3596
|
+
<!-- Navigation -->
|
|
3597
|
+
<div class="nav-sidebar-section nav-section-main">
|
|
3598
|
+
<div class="nav-item active" data-view="overview" onclick="switchView('overview')">
|
|
3599
|
+
<span class="nav-icon"><svg viewBox="0 0 16 16" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M8 1.5L14 5v6l-6 3.5L2 11V5z"/></svg></span><span class="nav-text">Overview</span>
|
|
3453
3600
|
</div>
|
|
3454
|
-
|
|
3455
|
-
|
|
3456
|
-
<div class="sidebar-section">
|
|
3457
|
-
<div class="sidebar-title"><span>Stats</span></div>
|
|
3458
|
-
<div id="agent-stats"></div>
|
|
3601
|
+
<div class="nav-item" data-view="messages" onclick="switchView('messages')">
|
|
3602
|
+
<span class="nav-icon"><svg viewBox="0 0 16 16" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M2 3h12v8H6l-3 2.5V11H2z" stroke-linejoin="round"/></svg></span><span class="nav-text">Messages</span>
|
|
3459
3603
|
</div>
|
|
3460
|
-
|
|
3461
|
-
|
|
3462
|
-
<div class="sidebar-section">
|
|
3463
|
-
<div class="sidebar-title"><span>Activity</span></div>
|
|
3464
|
-
<div id="activity-heatmap"></div>
|
|
3604
|
+
<div class="nav-item" data-view="tasks" onclick="switchView('tasks')">
|
|
3605
|
+
<span class="nav-icon"><svg viewBox="0 0 16 16" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="2" y="2" width="12" height="12" rx="2"/><path d="M5 8l2 2 4-4"/></svg></span><span class="nav-text">Tasks</span>
|
|
3465
3606
|
</div>
|
|
3466
|
-
|
|
3467
|
-
|
|
3468
|
-
<div class="sidebar-section">
|
|
3469
|
-
<div class="sidebar-title">
|
|
3470
|
-
<span>Bookmarks</span>
|
|
3471
|
-
<span class="alert-count" id="bookmark-count" style="display:none">0</span>
|
|
3472
|
-
</div>
|
|
3473
|
-
<div id="bookmarks-list"></div>
|
|
3474
|
-
<div class="filter-clear" id="bookmark-clear" onclick="clearBookmarkFilter()" style="display:none">✕ Clear filter</div>
|
|
3607
|
+
<div class="nav-item" data-view="workflows" onclick="switchView('workflows')">
|
|
3608
|
+
<span class="nav-icon"><svg viewBox="0 0 16 16" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M2 8a6 6 0 0111 0M14 8a6 6 0 01-11 0"/><path d="M13 5v3h-3M3 11v-3h3"/></svg></span><span class="nav-text">Workflows</span>
|
|
3475
3609
|
</div>
|
|
3476
|
-
|
|
3477
|
-
|
|
3478
|
-
|
|
3479
|
-
|
|
3480
|
-
<
|
|
3481
|
-
<div class="filter-clear" id="filter-clear" onclick="clearThreadFilter()">✕ Clear filter</div>
|
|
3610
|
+
<div class="nav-item" data-view="stats" onclick="switchView('stats')">
|
|
3611
|
+
<span class="nav-icon"><svg viewBox="0 0 16 16" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M3 14V8M8 14V3M13 14V6"/></svg></span><span class="nav-text">Analytics</span>
|
|
3612
|
+
</div>
|
|
3613
|
+
<div class="nav-item" data-view="launch" onclick="switchView('launch')">
|
|
3614
|
+
<span class="nav-icon"><svg viewBox="0 0 16 16" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M8 2v12M2 8h12"/></svg></span><span class="nav-text">Launch</span>
|
|
3482
3615
|
</div>
|
|
3483
3616
|
</div>
|
|
3484
|
-
|
|
3617
|
+
<div class="nav-sidebar-divider"></div>
|
|
3618
|
+
<!-- Agents -->
|
|
3619
|
+
<div class="nav-sidebar-section nav-sidebar-agents">
|
|
3620
|
+
<div class="nav-sidebar-label">AGENTS</div>
|
|
3621
|
+
<div id="agents-list"></div>
|
|
3622
|
+
</div>
|
|
3623
|
+
<div class="nav-sidebar-divider"></div>
|
|
3624
|
+
<!-- Project -->
|
|
3625
|
+
<div class="nav-sidebar-section nav-sidebar-project">
|
|
3626
|
+
<div class="nav-sidebar-label" style="display:flex;align-items:center;justify-content:space-between;gap:6px">
|
|
3627
|
+
<span>PROJECT</span>
|
|
3628
|
+
<span style="display:flex;align-items:center;gap:2px">
|
|
3629
|
+
<button type="button" id="remove-project-btn" onclick="removeProject()" title="Remove selected project from this list (does not delete .neohive data on disk)" aria-label="Remove project"
|
|
3630
|
+
style="display:none;background:none;border:none;color:var(--text-muted);cursor:pointer;padding:2px 4px;line-height:1;border-radius:4px;opacity:0.85"
|
|
3631
|
+
onmouseenter="this.style.color='#f87171'" onmouseleave="this.style.color='var(--text-muted)'">
|
|
3632
|
+
<svg viewBox="0 0 16 16" width="15" height="15" fill="none" stroke="currentColor" stroke-width="1.25" aria-hidden="true"><path d="M2 4h12"/><path d="M5 4V2.5h6V4"/><path d="M6 7v5M10 7v5"/><path d="M3 4v9a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V4"/></svg>
|
|
3633
|
+
</button>
|
|
3634
|
+
<button type="button" onclick="showAddProject()" style="background:none;border:none;color:var(--text-muted);cursor:pointer;font-size:14px;padding:0 2px;line-height:1" title="Add project">+</button>
|
|
3635
|
+
</span>
|
|
3636
|
+
</div>
|
|
3637
|
+
<select class="project-select" id="project-select" onchange="switchProject()" style="width:100%;margin-top:4px;background:var(--surface-2);color:var(--text);border:1px solid var(--border);border-radius:6px;padding:4px 8px;font-size:12px">
|
|
3638
|
+
<option value="">Default (local)</option>
|
|
3639
|
+
</select>
|
|
3640
|
+
<button type="button" id="remove-project-text-btn" class="btn" onclick="removeProject()" style="display:none;width:100%;margin-top:6px;padding:5px 8px;font-size:11px;background:var(--surface-2);border:1px solid var(--border);color:var(--text-muted)" title="Removes this folder from the dashboard only">Remove project from list</button>
|
|
3641
|
+
<input class="project-input" id="project-path-input" placeholder="Repo root (not …/.neohive)" title="Use the folder you open in Cursor — not the .neohive subfolder" onkeydown="if(event.key==='Enter')addProject();if(event.key==='Escape')this.classList.remove('visible');" style="margin-top:4px">
|
|
3642
|
+
</div>
|
|
3643
|
+
<div class="nav-sidebar-spacer"></div>
|
|
3644
|
+
<!-- Bottom nav -->
|
|
3645
|
+
<div class="nav-sidebar-section">
|
|
3646
|
+
<div class="nav-item" data-view="plan" onclick="switchView('plan')">
|
|
3647
|
+
<span class="nav-icon"><svg viewBox="0 0 16 16" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M4 2l10 6-10 6z"/></svg></span><span class="nav-text">Plan</span>
|
|
3648
|
+
</div>
|
|
3649
|
+
<div class="nav-item" data-view="rules" onclick="switchView('rules')">
|
|
3650
|
+
<span class="nav-icon"><svg viewBox="0 0 16 16" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M8 1L2 4v4c0 3.5 2.5 6.5 6 7.5 3.5-1 6-4 6-7.5V4z"/></svg></span><span class="nav-text">Rules</span>
|
|
3651
|
+
</div>
|
|
3652
|
+
</div>
|
|
3653
|
+
</nav>
|
|
3485
3654
|
|
|
3486
3655
|
<!-- MAIN -->
|
|
3487
3656
|
<div class="main" style="position:relative">
|
|
3657
|
+
<!-- Agent Status Bar -->
|
|
3658
|
+
<div class="agent-bar" id="agent-bar"></div>
|
|
3659
|
+
|
|
3488
3660
|
<div class="replay-bar" id="replay-bar">
|
|
3489
3661
|
<div class="replay-controls">
|
|
3490
3662
|
<button class="replay-btn" onclick="replayPlayPause()" id="replay-play">▶</button>
|
|
@@ -3525,6 +3697,7 @@
|
|
|
3525
3697
|
<div id="pinned-list"></div>
|
|
3526
3698
|
</div>
|
|
3527
3699
|
<div class="messages-area" id="messages"></div>
|
|
3700
|
+
<div class="overview-area visible" id="overview-area"></div>
|
|
3528
3701
|
<div class="tasks-area" id="tasks-area"></div>
|
|
3529
3702
|
<div class="workspaces-area" id="workspaces-area"></div>
|
|
3530
3703
|
<div class="workflows-area" id="workflows-area"></div>
|
|
@@ -3569,6 +3742,7 @@
|
|
|
3569
3742
|
<div class="profile-popup-bio" id="pp-bio"></div>
|
|
3570
3743
|
<div class="profile-popup-stats" id="pp-stats"></div>
|
|
3571
3744
|
<button class="btn btn-primary" style="width:100%;margin-top:10px" id="pp-edit-btn" onclick="openProfileEditor()">Edit Profile</button>
|
|
3745
|
+
<button class="btn btn-danger" style="width:100%;margin-top:6px" id="pp-delete-btn" onclick="confirmDeleteAgent()">Delete Agent</button>
|
|
3572
3746
|
</div>
|
|
3573
3747
|
|
|
3574
3748
|
<!-- Character Designer Panel -->
|
|
@@ -3579,8 +3753,6 @@
|
|
|
3579
3753
|
<button class="cd-close" onclick="closeProfileEditor()">×</button>
|
|
3580
3754
|
</div>
|
|
3581
3755
|
<div class="cd-body">
|
|
3582
|
-
<!-- 3D Preview -->
|
|
3583
|
-
<div class="cd-preview" id="cd-preview-container"></div>
|
|
3584
3756
|
|
|
3585
3757
|
<!-- Info Section: Avatar + Name/Role/Bio -->
|
|
3586
3758
|
<div class="cd-info-section">
|
|
@@ -3831,8 +4003,8 @@
|
|
|
3831
4003
|
</div>
|
|
3832
4004
|
<div class="cd-color-row">
|
|
3833
4005
|
<label>Shirt color</label>
|
|
3834
|
-
<input type="color" id="pe-shirt-color" value="#
|
|
3835
|
-
<span class="cd-color-hex" id="cd-shirt-hex">#
|
|
4006
|
+
<input type="color" id="pe-shirt-color" value="#f59e0b" onchange="cdOnChange()">
|
|
4007
|
+
<span class="cd-color-hex" id="cd-shirt-hex">#f59e0b</span>
|
|
3836
4008
|
</div>
|
|
3837
4009
|
<div class="cd-color-row">
|
|
3838
4010
|
<label>Pants color</label>
|
|
@@ -3967,6 +4139,9 @@ function lttFetch(url, opts) {
|
|
|
3967
4139
|
|
|
3968
4140
|
var activeThread = null;
|
|
3969
4141
|
var activeChannel = null; // null = all channels
|
|
4142
|
+
// Safe element getter — returns dummy div for removed elements to prevent null crashes
|
|
4143
|
+
function $id(id) { return document.getElementById(id) || document.createElement('div'); }
|
|
4144
|
+
|
|
3970
4145
|
var activeProject = ''; // empty = default/local
|
|
3971
4146
|
var cachedHistory = [];
|
|
3972
4147
|
var cachedAgents = {};
|
|
@@ -3980,8 +4155,8 @@ window.addEventListener('resize', function() { isMobile = window.innerWidth <= 7
|
|
|
3980
4155
|
|
|
3981
4156
|
// Agent color palette
|
|
3982
4157
|
var COLORS = [
|
|
3983
|
-
'#
|
|
3984
|
-
'#f778ba','#
|
|
4158
|
+
'#f59e0b','#f97316','#3fb950','#d29922','#f85149',
|
|
4159
|
+
'#f778ba','#7ee787','#e3b341','#ffa198','#14b8a6'
|
|
3985
4160
|
];
|
|
3986
4161
|
var COLORS_LIGHT = [
|
|
3987
4162
|
'#0969da','#1a7f37','#9a6700','#cf222e','#8250df',
|
|
@@ -4197,7 +4372,7 @@ function renderTable(rows) {
|
|
|
4197
4372
|
|
|
4198
4373
|
// ==================== AGENT MONITORING ====================
|
|
4199
4374
|
|
|
4200
|
-
var PROVIDER_COLORS = { claude: '#d97706', anthropic: '#d97706', openai: '#10b981', gemini: '#3b82f6', google: '#3b82f6' };
|
|
4375
|
+
var PROVIDER_COLORS = { claude: '#d97706', anthropic: '#d97706', openai: '#10b981', gemini: '#3b82f6', google: '#3b82f6', cursor: '#6366f1' };
|
|
4201
4376
|
|
|
4202
4377
|
function getProviderBadge(provider) {
|
|
4203
4378
|
if (!provider) return '';
|
|
@@ -4212,6 +4387,7 @@ var agentFilterStatus = '';
|
|
|
4212
4387
|
var collapsedRoleGroups = {};
|
|
4213
4388
|
|
|
4214
4389
|
function filterAgents() {
|
|
4390
|
+
if (!document.getElementById('agent-search')) return;
|
|
4215
4391
|
agentFilterSearch = (document.getElementById('agent-search').value || '').toLowerCase();
|
|
4216
4392
|
agentFilterRole = document.getElementById('agent-role-filter').value;
|
|
4217
4393
|
agentFilterStatus = document.getElementById('agent-status-filter').value;
|
|
@@ -4223,11 +4399,13 @@ function toggleRoleGroup(role) {
|
|
|
4223
4399
|
renderAgents(cachedAgents);
|
|
4224
4400
|
}
|
|
4225
4401
|
|
|
4402
|
+
var _lastAgentsHash = '';
|
|
4226
4403
|
function renderAgents(agents) {
|
|
4404
|
+
updateAgentBar(agents);
|
|
4227
4405
|
var el = document.getElementById('agents-list');
|
|
4228
4406
|
var keys = Object.keys(agents);
|
|
4229
4407
|
if (!keys.length) {
|
|
4230
|
-
el.innerHTML = '<div style="color:var(--text-muted);font-size:12px;padding:4px;">No agents registered</div>';
|
|
4408
|
+
if (_lastAgentsHash !== 'empty') { el.innerHTML = '<div style="color:var(--text-muted);font-size:12px;padding:4px;">No agents registered</div>'; _lastAgentsHash = 'empty'; }
|
|
4231
4409
|
return;
|
|
4232
4410
|
}
|
|
4233
4411
|
|
|
@@ -4236,7 +4414,7 @@ function renderAgents(agents) {
|
|
|
4236
4414
|
for (var f = 0; f < keys.length; f++) {
|
|
4237
4415
|
var fname = keys[f];
|
|
4238
4416
|
var finfo = agents[fname];
|
|
4239
|
-
var fstate = finfo.status || (finfo.alive ? '
|
|
4417
|
+
var fstate = finfo.status || (finfo.alive ? 'working' : 'offline');
|
|
4240
4418
|
if (agentFilterSearch && fname.toLowerCase().indexOf(agentFilterSearch) === -1 &&
|
|
4241
4419
|
(!finfo.display_name || finfo.display_name.toLowerCase().indexOf(agentFilterSearch) === -1)) continue;
|
|
4242
4420
|
if (agentFilterRole && finfo.role !== agentFilterRole) continue;
|
|
@@ -4244,8 +4422,17 @@ function renderAgents(agents) {
|
|
|
4244
4422
|
filtered.push(fname);
|
|
4245
4423
|
}
|
|
4246
4424
|
|
|
4425
|
+
var collapsedSig = Object.keys(collapsedRoleGroups).filter(function(k) { return collapsedRoleGroups[k]; }).sort().join(',');
|
|
4426
|
+
var structuralPart = keys.map(function(k) { var a = agents[k]; return k + ':' + (a.alive ? '1' : '0') + ':' + (a.is_listening ? 'L' : 'W') + ':' + (a.role || '') + ':' + (a.status || ''); }).join('|');
|
|
4427
|
+
var useGrouping = filtered.length > 6;
|
|
4428
|
+
var layoutHash = structuralPart + '|F:' + agentFilterSearch + ':' + agentFilterRole + ':' + agentFilterStatus + '|C:' + collapsedSig + '|G:' + (useGrouping ? '1' : '0');
|
|
4429
|
+
|
|
4247
4430
|
if (!filtered.length) {
|
|
4248
4431
|
el.innerHTML = '<div style="color:var(--text-muted);font-size:12px;padding:4px;">No matching agents</div>';
|
|
4432
|
+
_lastAgentsHash = layoutHash;
|
|
4433
|
+
var alertEl0 = document.getElementById('alert-count');
|
|
4434
|
+
if (alertEl0) alertEl0.style.display = 'none';
|
|
4435
|
+
updateInjectTargets(keys);
|
|
4249
4436
|
return;
|
|
4250
4437
|
}
|
|
4251
4438
|
|
|
@@ -4261,9 +4448,6 @@ function renderAgents(agents) {
|
|
|
4261
4448
|
roleGroups[grole].push(gname);
|
|
4262
4449
|
}
|
|
4263
4450
|
|
|
4264
|
-
// Only use role grouping if more than 6 agents (otherwise flat list)
|
|
4265
|
-
var useGrouping = filtered.length > 6;
|
|
4266
|
-
|
|
4267
4451
|
var sleepCount = 0;
|
|
4268
4452
|
var html = '';
|
|
4269
4453
|
|
|
@@ -4272,90 +4456,44 @@ function renderAgents(agents) {
|
|
|
4272
4456
|
for (var i = 0; i < agentNames.length; i++) {
|
|
4273
4457
|
var name = agentNames[i];
|
|
4274
4458
|
var info = agents[name];
|
|
4275
|
-
|
|
4276
|
-
|
|
4277
|
-
|
|
4278
|
-
|
|
4279
|
-
|
|
4280
|
-
|
|
4281
|
-
|
|
4282
|
-
|
|
4283
|
-
|
|
4284
|
-
|
|
4285
|
-
|
|
4286
|
-
|
|
4287
|
-
|
|
4288
|
-
|
|
4289
|
-
} else {
|
|
4290
|
-
activityText = 'Registered ' + timeAgo(info.registered_at);
|
|
4291
|
-
}
|
|
4292
|
-
|
|
4293
|
-
var stateLabel = state.charAt(0).toUpperCase() + state.slice(1);
|
|
4294
|
-
var cardClass = 'agent-card' + (state !== 'active' ? ' ' + state : '');
|
|
4295
|
-
|
|
4296
|
-
var nudgeHtml = '';
|
|
4297
|
-
if (state === 'sleeping' || (state === 'active' && !info.is_listening && info.idle_seconds > 30)) {
|
|
4298
|
-
nudgeHtml = '<button class="nudge-btn" onclick="sendNudge(\'' + escapeHtml(name) + '\')">Send Nudge</button>';
|
|
4299
|
-
}
|
|
4300
|
-
var removeHtml = '';
|
|
4301
|
-
var respawnHtml = '';
|
|
4302
|
-
if (state === 'dead') {
|
|
4303
|
-
removeHtml = '<button class="remove-agent-btn" onclick="event.stopPropagation();removeAgent(\'' + escapeHtml(name) + '\')" title="Remove this agent">Remove</button>';
|
|
4304
|
-
respawnHtml = '<button class="respawn-btn" onclick="event.stopPropagation();respawnAgent(\'' + escapeHtml(name) + '\')" title="Generate resume prompt for this agent">🔄 Respawn</button>';
|
|
4305
|
-
}
|
|
4306
|
-
|
|
4307
|
-
// Listening status — simplified: skip for dead (already shown via badge)
|
|
4308
|
-
var listenHtml = '';
|
|
4309
|
-
if (state !== 'dead') {
|
|
4310
|
-
if (info.is_listening) {
|
|
4311
|
-
var sinceTxt = info.listening_since ? ' since ' + formatTime(info.listening_since) : '';
|
|
4312
|
-
listenHtml = '<div class="listen-badge listening" title="Agent is waiting for messages — can receive dashboard injections"><span class="listen-dot on"></span>Listening' + sinceTxt + '</div>';
|
|
4313
|
-
} else if (state === 'active') {
|
|
4314
|
-
listenHtml = '<div class="listen-badge busy" title="Agent is working, not currently listening for messages"><span class="listen-dot off"></span>Busy</div>';
|
|
4315
|
-
} else {
|
|
4316
|
-
listenHtml = '<div class="listen-badge not-listening" title="Agent is not listening. Send a nudge or type \'listen\' in the terminal."><span class="listen-dot off"></span>Not Listening</div>';
|
|
4459
|
+
var color = getColor(name);
|
|
4460
|
+
var state = !info.alive ? 'offline' : info.is_listening ? 'listening' : (info.status === 'idle' || info.status === 'sleeping') ? 'idle' : 'working';
|
|
4461
|
+
if (state === 'idle') sleepCount++;
|
|
4462
|
+
|
|
4463
|
+
// Compact activity text
|
|
4464
|
+
var activityText = '';
|
|
4465
|
+
if (state === 'offline' || state === 'dead') {
|
|
4466
|
+
activityText = 'offline';
|
|
4467
|
+
} else if (info.idle_seconds != null) {
|
|
4468
|
+
if (info.idle_seconds < 5) activityText = 'just now';
|
|
4469
|
+
else if (info.idle_seconds < 60) activityText = info.idle_seconds + 's ago';
|
|
4470
|
+
else activityText = Math.floor(info.idle_seconds / 60) + 'm ago';
|
|
4471
|
+
} else if (info.last_activity) {
|
|
4472
|
+
activityText = timeAgo(info.last_activity);
|
|
4317
4473
|
}
|
|
4318
|
-
}
|
|
4319
4474
|
|
|
4320
|
-
|
|
4321
|
-
|
|
4322
|
-
|
|
4323
|
-
|
|
4324
|
-
|
|
4325
|
-
|
|
4326
|
-
|
|
4327
|
-
|
|
4328
|
-
|
|
4329
|
-
|
|
4330
|
-
|
|
4331
|
-
|
|
4332
|
-
|
|
4333
|
-
|
|
4334
|
-
|
|
4335
|
-
|
|
4336
|
-
|
|
4337
|
-
|
|
4338
|
-
'<div class="agent-name" style="color:' + color + '">' +
|
|
4339
|
-
escapeHtml(displayName) +
|
|
4340
|
-
roleHtml +
|
|
4341
|
-
' <span class="agent-badge ' + state + '">' + stateLabel + '</span>' +
|
|
4342
|
-
branchHtml +
|
|
4343
|
-
'</div>' +
|
|
4344
|
-
'<div class="agent-meta"><span>' + getProviderBadge(info.provider) + 'PID ' + info.pid + '</span>' +
|
|
4345
|
-
(info.last_activity ? '<span style="margin-left:8px" title="Last heartbeat: ' + new Date(info.last_activity).toLocaleTimeString() + '">💓 ' + timeAgo(info.last_activity) + '</span>' : '') +
|
|
4475
|
+
var cardClass = 'agent-card' + (state !== 'working' ? ' ' + state : '');
|
|
4476
|
+
var avatarHtml = info.avatar
|
|
4477
|
+
? '<img class="agent-avatar-img" src="' + escapeHtml(info.avatar) + '" alt="' + escapeHtml(name) + '" onerror="this.style.display=\'none\'">'
|
|
4478
|
+
: '<div class="agent-avatar" style="background:' + color + '">' + initial(name) + '</div>';
|
|
4479
|
+
var displayName = info.display_name || name;
|
|
4480
|
+
|
|
4481
|
+
// Subtitle: role + activity
|
|
4482
|
+
var subtitleParts = [];
|
|
4483
|
+
if (info.role) subtitleParts.push(info.role);
|
|
4484
|
+
if (activityText) subtitleParts.push(activityText);
|
|
4485
|
+
var subtitleText = subtitleParts.join(' \u00B7 ');
|
|
4486
|
+
|
|
4487
|
+
cardHtml += '<div class="' + cardClass + '" data-agent="' + escapeHtml(name) + '" onclick="showProfilePopup(\'' + escapeHtml(name) + '\', event)">' +
|
|
4488
|
+
'<div class="agent-top">' +
|
|
4489
|
+
avatarHtml +
|
|
4490
|
+
'<div class="agent-info">' +
|
|
4491
|
+
'<div class="agent-name" style="color:' + color + '">' + escapeHtml(displayName) + '</div>' +
|
|
4492
|
+
'<div class="agent-subtitle">' + escapeHtml(subtitleText) + '</div>' +
|
|
4346
4493
|
'</div>' +
|
|
4494
|
+
'<span class="agent-status-dot ' + state + '" title="' + state + '"></span>' +
|
|
4347
4495
|
'</div>' +
|
|
4348
|
-
'</div>'
|
|
4349
|
-
'<div class="agent-activity">' +
|
|
4350
|
-
'<span class="agent-activity-icon ' + state + '"></span>' +
|
|
4351
|
-
activityText +
|
|
4352
|
-
'</div>' +
|
|
4353
|
-
(info.current_status ? '<div class="agent-status-intent" title="' + escapeHtml(info.current_status) + '">' + escapeHtml(info.current_status) + '</div>' : '') +
|
|
4354
|
-
listenHtml +
|
|
4355
|
-
nudgeHtml +
|
|
4356
|
-
respawnHtml +
|
|
4357
|
-
removeHtml +
|
|
4358
|
-
'</div>';
|
|
4496
|
+
'</div>';
|
|
4359
4497
|
}
|
|
4360
4498
|
return cardHtml;
|
|
4361
4499
|
}
|
|
@@ -4382,10 +4520,47 @@ function renderAgents(agents) {
|
|
|
4382
4520
|
html += renderAgentCards(filtered);
|
|
4383
4521
|
}
|
|
4384
4522
|
|
|
4385
|
-
|
|
4523
|
+
// DOM patching: when layout is unchanged, update cards in place (activity text, dots); else full rebuild
|
|
4524
|
+
var existingCards = el.querySelectorAll('[data-agent]');
|
|
4525
|
+
var canPatch = !useGrouping && existingCards.length === filtered.length && existingCards.length > 0;
|
|
4526
|
+
var didPatch = false;
|
|
4527
|
+
if (canPatch && layoutHash === _lastAgentsHash) {
|
|
4528
|
+
didPatch = true;
|
|
4529
|
+
for (var pi = 0; pi < filtered.length; pi++) {
|
|
4530
|
+
var pName = filtered[pi];
|
|
4531
|
+
var pCard = el.querySelector('[data-agent="' + pName + '"]');
|
|
4532
|
+
if (!pCard) { didPatch = false; break; }
|
|
4533
|
+
var pInfo = agents[pName];
|
|
4534
|
+
var pState = !pInfo.alive ? 'offline' : pInfo.is_listening ? 'listening' : (pInfo.status === 'idle' || pInfo.status === 'sleeping') ? 'idle' : 'working';
|
|
4535
|
+
var dot = pCard.querySelector('.agent-status-dot');
|
|
4536
|
+
if (dot) { dot.className = 'agent-status-dot ' + pState; dot.title = pState; }
|
|
4537
|
+
var sub = pCard.querySelector('.agent-subtitle');
|
|
4538
|
+
if (sub) {
|
|
4539
|
+
var parts = [];
|
|
4540
|
+
if (pInfo.role) parts.push(pInfo.role);
|
|
4541
|
+
if (pState === 'offline') parts.push('offline');
|
|
4542
|
+
else if (pInfo.idle_seconds != null) {
|
|
4543
|
+
if (pInfo.idle_seconds < 5) parts.push('just now');
|
|
4544
|
+
else if (pInfo.idle_seconds < 60) parts.push(pInfo.idle_seconds + 's ago');
|
|
4545
|
+
else parts.push(Math.floor(pInfo.idle_seconds / 60) + 'm ago');
|
|
4546
|
+
} else if (pInfo.last_activity) {
|
|
4547
|
+
parts.push(timeAgo(pInfo.last_activity));
|
|
4548
|
+
}
|
|
4549
|
+
var newSub = parts.join(' \u00B7 ');
|
|
4550
|
+
if (sub.textContent !== newSub) sub.textContent = newSub;
|
|
4551
|
+
}
|
|
4552
|
+
var newCardClass = 'agent-card' + (pState !== 'working' ? ' ' + pState : '');
|
|
4553
|
+
if (pCard.className !== newCardClass) pCard.className = newCardClass;
|
|
4554
|
+
}
|
|
4555
|
+
}
|
|
4556
|
+
if (!didPatch) {
|
|
4557
|
+
el.innerHTML = html;
|
|
4558
|
+
_lastAgentsHash = layoutHash;
|
|
4559
|
+
}
|
|
4386
4560
|
|
|
4387
4561
|
// Update alert badge
|
|
4388
4562
|
var alertEl = document.getElementById('alert-count');
|
|
4563
|
+
if (!alertEl) return;
|
|
4389
4564
|
if (sleepCount > 0) {
|
|
4390
4565
|
alertEl.textContent = sleepCount;
|
|
4391
4566
|
alertEl.style.display = '';
|
|
@@ -4464,12 +4639,12 @@ function showRespawnModal(agentName, prompt) {
|
|
|
4464
4639
|
|
|
4465
4640
|
overlay.innerHTML =
|
|
4466
4641
|
'<div class="respawn-modal">' +
|
|
4467
|
-
'<h3
|
|
4642
|
+
'<h3>Respawn ' + escapeHtml(agentName) + '</h3>' +
|
|
4468
4643
|
'<p style="color:var(--text-secondary);font-size:12px;margin:0 0 8px">Copy this prompt and paste it into a fresh CLI terminal to respawn the agent:</p>' +
|
|
4469
4644
|
'<div class="respawn-modal-prompt" id="respawn-prompt-text">' + escapeHtml(prompt) + '</div>' +
|
|
4470
4645
|
'<div class="respawn-modal-actions">' +
|
|
4471
4646
|
'<button class="respawn-close-btn" onclick="dismissRespawnModal()">Close</button>' +
|
|
4472
|
-
'<button class="respawn-copy-btn" onclick="copyRespawnPrompt()"
|
|
4647
|
+
'<button class="respawn-copy-btn" onclick="copyRespawnPrompt()">Copy to Clipboard</button>' +
|
|
4473
4648
|
'</div>' +
|
|
4474
4649
|
'</div>';
|
|
4475
4650
|
|
|
@@ -4489,7 +4664,7 @@ function copyRespawnPrompt() {
|
|
|
4489
4664
|
var btn = document.querySelector('.respawn-copy-btn');
|
|
4490
4665
|
if (btn) {
|
|
4491
4666
|
btn.textContent = '\u2705 Copied!';
|
|
4492
|
-
setTimeout(function() { btn.innerHTML = '
|
|
4667
|
+
setTimeout(function() { btn.innerHTML = 'Copy to Clipboard'; }, 2000);
|
|
4493
4668
|
}
|
|
4494
4669
|
}).catch(function() {
|
|
4495
4670
|
// Fallback: select all text
|
|
@@ -4568,6 +4743,7 @@ function doInject() {
|
|
|
4568
4743
|
// ==================== THREADS ====================
|
|
4569
4744
|
|
|
4570
4745
|
function renderThreads(messages) {
|
|
4746
|
+
if (!document.getElementById('threads-list')) return;
|
|
4571
4747
|
var threads = {};
|
|
4572
4748
|
for (var i = 0; i < messages.length; i++) {
|
|
4573
4749
|
var m = messages[i];
|
|
@@ -4616,17 +4792,17 @@ function renderMessages(messages) {
|
|
|
4616
4792
|
? '<div class="empty-sub">Select a project above to see agent conversations</div>'
|
|
4617
4793
|
: '<div class="empty-sub">Waiting for messages in: ' + activeProject.split(/[/\\]/).pop() + '</div>';
|
|
4618
4794
|
el.innerHTML = '<div class="empty-state">' +
|
|
4619
|
-
'<div class="empty-icon"
|
|
4795
|
+
'<div class="empty-icon" style="font-size:40px;opacity:0.2">--</div>' +
|
|
4620
4796
|
'<div class="empty-text">No messages yet</div>' +
|
|
4621
4797
|
mobileHint +
|
|
4622
4798
|
'</div>';
|
|
4623
4799
|
} else {
|
|
4624
4800
|
el.innerHTML = '<div class="empty-state">' +
|
|
4625
|
-
'<div class="empty-icon"
|
|
4626
|
-
'<div class="empty-text">Neohive
|
|
4801
|
+
'<div class="empty-icon" style="font-size:40px;opacity:0.2">--</div>' +
|
|
4802
|
+
'<div class="empty-text">Neohive v6.0</div>' +
|
|
4627
4803
|
'<div class="empty-sub">Autonomous AI agent teams — one command, zero babysitting</div>' +
|
|
4628
4804
|
'<div class="onboard-steps">' +
|
|
4629
|
-
'<div class="onboard-step" style="margin-bottom:12px"><span class="onboard-num" style="background:var(--accent)"
|
|
4805
|
+
'<div class="onboard-step" style="margin-bottom:12px"><span class="onboard-num" style="background:var(--accent)"></span><span style="font-weight:600">Quickest start — one command:</span></div>' +
|
|
4630
4806
|
'<div class="copy-block" onclick="copyText(this)" data-text="npx neohive run "build a REST API with auth" --agents 4"><span class="copy-hint">click to copy</span>npx neohive run "build a REST API with auth" --agents 4</div>' +
|
|
4631
4807
|
'<div style="font-size:11px;color:var(--text-muted);margin:8px 0 16px;text-align:center">Spawns 4 agents, auto-assigns roles, creates autonomous workflow. Walk away, come back to finished work.</div>' +
|
|
4632
4808
|
'<div style="border-top:1px solid var(--border);padding-top:12px;margin-top:4px"></div>' +
|
|
@@ -4710,10 +4886,12 @@ function renderMessages(messages) {
|
|
|
4710
4886
|
|
|
4711
4887
|
for (var i = windowStart; i < filtered.length; i++) {
|
|
4712
4888
|
var m = filtered[i];
|
|
4713
|
-
var color = getColor(m.from);
|
|
4714
|
-
var fromDisplay = (cachedAgents[m.from] && cachedAgents[m.from].display_name) || m.from;
|
|
4715
|
-
var toDisplay = (cachedAgents[m.to] && cachedAgents[m.to].display_name) || m.to;
|
|
4889
|
+
var color = m.from === '__user__' ? 'var(--accent)' : getColor(m.from);
|
|
4890
|
+
var fromDisplay = m.from === '__user__' ? 'You' : (cachedAgents[m.from] && cachedAgents[m.from].display_name) || m.from;
|
|
4891
|
+
var toDisplay = m.to === '__user__' ? 'You' : (cachedAgents[m.to] && cachedAgents[m.to].display_name) || m.to;
|
|
4716
4892
|
var newClass = (isNew && i >= lastMessageCount) ? ' message-new' : '';
|
|
4893
|
+
var isUserMsg = m.from === '__user__';
|
|
4894
|
+
var isReplyToUser = m.to === '__user__';
|
|
4717
4895
|
var isSystem = m.system === true;
|
|
4718
4896
|
|
|
4719
4897
|
// Date separator
|
|
@@ -4758,7 +4936,7 @@ function renderMessages(messages) {
|
|
|
4758
4936
|
buildMsgActions(m.id) +
|
|
4759
4937
|
msgAvatarHtml +
|
|
4760
4938
|
'<div class="msg-body">' +
|
|
4761
|
-
'<div class="handoff-banner"
|
|
4939
|
+
'<div class="handoff-banner">Handoff: ' + escapeHtml(fromDisplay) + ' → ' + escapeHtml(toDisplay) + '</div>' +
|
|
4762
4940
|
'<div class="msg-header">' +
|
|
4763
4941
|
'<span class="msg-from" style="color:' + color + '" title="@' + escapeHtml(m.from) + '">' + escapeHtml(fromDisplay) + '</span>' +
|
|
4764
4942
|
'<span class="msg-time" title="' + formatTime(m.timestamp) + '">' + relativeTime(m.timestamp) + '</span>' +
|
|
@@ -4785,7 +4963,7 @@ function renderMessages(messages) {
|
|
|
4785
4963
|
'<span class="msg-time" title="' + formatTime(m.timestamp) + '">' + relativeTime(m.timestamp) + '</span>' +
|
|
4786
4964
|
'<div class="msg-badges">' + badges + '</div>' +
|
|
4787
4965
|
'</div>' +
|
|
4788
|
-
'<div class="file-meta"><span class="file-icon"
|
|
4966
|
+
'<div class="file-meta"><span class="file-icon" style="opacity:0.5">[ ]</span>' + escapeHtml(fileName) + '<span class="file-size">' + fileSize + '</span></div>' +
|
|
4789
4967
|
'<div class="msg-content">' + renderMarkdown(m.content) + '</div>' +
|
|
4790
4968
|
buildReactionsHtml(m.id) +
|
|
4791
4969
|
buildReadReceipts(m.id) +
|
|
@@ -4793,7 +4971,9 @@ function renderMessages(messages) {
|
|
|
4793
4971
|
lastFrom = m.from;
|
|
4794
4972
|
lastTo = m.to;
|
|
4795
4973
|
} else {
|
|
4796
|
-
|
|
4974
|
+
var userMsgClass = isUserMsg ? ' user-msg' : '';
|
|
4975
|
+
var replyBadge = isReplyToUser ? '<span class="badge" style="background:var(--accent-dim);color:var(--accent)">Reply to you</span>' : '';
|
|
4976
|
+
html += '<div class="message' + newClass + groupClass + userMsgClass + '">' +
|
|
4797
4977
|
buildMsgActions(m.id) +
|
|
4798
4978
|
msgAvatarHtml +
|
|
4799
4979
|
'<div class="msg-body">' +
|
|
@@ -4802,7 +4982,7 @@ function renderMessages(messages) {
|
|
|
4802
4982
|
'<span class="msg-arrow">→</span>' +
|
|
4803
4983
|
'<span class="msg-to">' + escapeHtml(toDisplay) + '</span>' +
|
|
4804
4984
|
'<span class="msg-time" title="' + formatTime(m.timestamp) + '">' + relativeTime(m.timestamp) + '</span>' +
|
|
4805
|
-
'<div class="msg-badges">' + badges + '</div>' +
|
|
4985
|
+
'<div class="msg-badges">' + replyBadge + badges + '</div>' +
|
|
4806
4986
|
'</div>' +
|
|
4807
4987
|
'<div class="msg-content">' + renderMarkdown(m.content) + '</div>' +
|
|
4808
4988
|
buildReactionsHtml(m.id) +
|
|
@@ -4972,13 +5152,17 @@ function renderChannelBar(messages) {
|
|
|
4972
5152
|
}
|
|
4973
5153
|
|
|
4974
5154
|
function toggleSidebar() {
|
|
4975
|
-
document.getElementById('sidebar').
|
|
4976
|
-
|
|
5155
|
+
var sb = document.getElementById('sidebar') || document.getElementById('nav-sidebar');
|
|
5156
|
+
if (sb) sb.classList.toggle('open');
|
|
5157
|
+
var ov = document.getElementById('sidebar-overlay');
|
|
5158
|
+
if (ov) ov.classList.toggle('open');
|
|
4977
5159
|
}
|
|
4978
5160
|
|
|
4979
5161
|
function closeSidebar() {
|
|
4980
|
-
document.getElementById('sidebar').
|
|
4981
|
-
|
|
5162
|
+
var sb = document.getElementById('sidebar') || document.getElementById('nav-sidebar');
|
|
5163
|
+
if (sb) sb.classList.remove('open');
|
|
5164
|
+
var ov = document.getElementById('sidebar-overlay');
|
|
5165
|
+
if (ov) ov.classList.remove('open');
|
|
4982
5166
|
}
|
|
4983
5167
|
|
|
4984
5168
|
var newWhileScrolled = 0;
|
|
@@ -5136,7 +5320,7 @@ function deepSearch() {
|
|
|
5136
5320
|
lastMessageCount = 0;
|
|
5137
5321
|
var el = document.getElementById('messages');
|
|
5138
5322
|
if (!messages.length) {
|
|
5139
|
-
el.innerHTML = '<div class="empty-state"><div class="empty-icon"
|
|
5323
|
+
el.innerHTML = '<div class="empty-state"><div class="empty-icon" style="font-size:40px;opacity:0.2">--</div><div class="empty-text">No results for "' + escapeHtml(query) + '"</div><div class="empty-sub">Searched full history including channels</div></div>';
|
|
5140
5324
|
return;
|
|
5141
5325
|
}
|
|
5142
5326
|
// Render search results directly (don't overwrite cachedHistory)
|
|
@@ -5261,14 +5445,14 @@ function buildMsgActions(msgId) {
|
|
|
5261
5445
|
|
|
5262
5446
|
var msg = cachedHistory.find(function(m) { return m.id === msgId; });
|
|
5263
5447
|
var isDeletable = msg && (msg.from === 'dashboard' || msg.from === 'Dashboard' || msg.from === 'system' || msg.from === '__system__');
|
|
5264
|
-
var deleteBtn = isDeletable ? '<button class="msg-action-btn" onclick="deleteMessage(\'' + msgId + '\')" title="Delete" style="color:var(--red,#e74c3c)"
|
|
5448
|
+
var deleteBtn = isDeletable ? '<button class="msg-action-btn" onclick="deleteMessage(\'' + msgId + '\')" title="Delete" style="color:var(--red,#e74c3c)">Del</button>' : '';
|
|
5265
5449
|
|
|
5266
5450
|
return '<div class="msg-actions">' +
|
|
5267
|
-
'<button class="msg-action-btn" onclick="toggleReactPicker(\'' + msgId + '\')" title="React"
|
|
5451
|
+
'<button class="msg-action-btn" onclick="toggleReactPicker(\'' + msgId + '\')" title="React">+</button>' +
|
|
5268
5452
|
'<button class="msg-action-btn' + pinClass + '" onclick="togglePin(\'' + msgId + '\')" title="Pin">\ud83d\udccc</button>' +
|
|
5269
5453
|
'<button class="msg-action-btn' + starClass + '" onclick="toggleBookmark(\'' + msgId + '\')" title="Bookmark">' + starChar + '</button>' +
|
|
5270
|
-
'<button class="msg-action-btn" onclick="copyMessage(\'' + msgId + '\', this)" title="Copy"
|
|
5271
|
-
'<button class="msg-action-btn" onclick="openEditMessage(\'' + msgId + '\')" title="Edit"
|
|
5454
|
+
'<button class="msg-action-btn" onclick="copyMessage(\'' + msgId + '\', this)" title="Copy">Copy</button>' +
|
|
5455
|
+
'<button class="msg-action-btn" onclick="openEditMessage(\'' + msgId + '\')" title="Edit">Edit</button>' +
|
|
5272
5456
|
deleteBtn +
|
|
5273
5457
|
'<div class="react-picker" id="react-' + msgId + '">' +
|
|
5274
5458
|
REACTION_EMOJIS.map(function(e) { return '<button class="react-emoji" onclick="addReaction(\'' + msgId + '\',\'' + e + '\')">' + e + '</button>'; }).join('') +
|
|
@@ -5281,7 +5465,7 @@ function copyMessage(msgId, btn) {
|
|
|
5281
5465
|
if (!msg) return;
|
|
5282
5466
|
navigator.clipboard.writeText(msg.content).then(function() {
|
|
5283
5467
|
var orig = btn.innerHTML;
|
|
5284
|
-
btn.innerHTML = '
|
|
5468
|
+
btn.innerHTML = '<svg viewBox="0 0 16 16" width="12" height="12" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 8l3 3 7-7"/></svg>';
|
|
5285
5469
|
btn.title = 'Copied!';
|
|
5286
5470
|
setTimeout(function() { btn.innerHTML = orig; btn.title = 'Copy'; }, 1500);
|
|
5287
5471
|
});
|
|
@@ -5381,11 +5565,11 @@ function applyTheme(theme) {
|
|
|
5381
5565
|
currentTheme = theme;
|
|
5382
5566
|
if (theme === 'light') {
|
|
5383
5567
|
document.documentElement.setAttribute('data-theme', 'light');
|
|
5384
|
-
document.getElementById('theme-toggle').innerHTML = '☀️';
|
|
5385
5568
|
} else {
|
|
5386
5569
|
document.documentElement.removeAttribute('data-theme');
|
|
5387
|
-
document.getElementById('theme-toggle').innerHTML = '🌙';
|
|
5388
5570
|
}
|
|
5571
|
+
var themeBtn = document.getElementById('theme-toggle');
|
|
5572
|
+
if (themeBtn) themeBtn.innerHTML = theme === 'light' ? 'Light' : 'Dark';
|
|
5389
5573
|
localStorage.setItem('ltt-theme', theme);
|
|
5390
5574
|
}
|
|
5391
5575
|
|
|
@@ -5504,6 +5688,7 @@ function copyText(el) {
|
|
|
5504
5688
|
// ==================== AGENT STATS ====================
|
|
5505
5689
|
|
|
5506
5690
|
function renderAgentStats() {
|
|
5691
|
+
if (!document.getElementById('agent-stats')) return;
|
|
5507
5692
|
var el = document.getElementById('agent-stats');
|
|
5508
5693
|
if (!cachedHistory.length) {
|
|
5509
5694
|
el.innerHTML = '<div style="color:var(--text-muted);font-size:12px;padding:4px;">No data yet</div>';
|
|
@@ -5575,6 +5760,7 @@ function fetchActivity() {
|
|
|
5575
5760
|
|
|
5576
5761
|
function renderActivityHeatmap(data) {
|
|
5577
5762
|
var el = document.getElementById('activity-heatmap');
|
|
5763
|
+
if (!el) return;
|
|
5578
5764
|
if (!data.agents || !Object.keys(data.agents).length) {
|
|
5579
5765
|
el.innerHTML = '<div style="color:var(--text-muted);font-size:12px;padding:4px;">No activity data</div>';
|
|
5580
5766
|
return;
|
|
@@ -5593,7 +5779,7 @@ function renderActivityHeatmap(data) {
|
|
|
5593
5779
|
html += '<div class="activity-agent">' +
|
|
5594
5780
|
'<div class="activity-label">' +
|
|
5595
5781
|
'<span class="activity-name" style="color:' + color + '">' + escapeHtml(name) + '</span>' +
|
|
5596
|
-
'<span class="activity-pct">' + pct + '
|
|
5782
|
+
'<span class="activity-pct">' + pct + '<span style="font-size:0.75em;margin-left:1px">%</span></span>' +
|
|
5597
5783
|
'</div>' +
|
|
5598
5784
|
'<div class="activity-bar">' +
|
|
5599
5785
|
'<div class="activity-fill" style="width:' + pct + '%;background:' + color + '"></div>' +
|
|
@@ -5632,13 +5818,14 @@ function toggleBookmark(msgId) {
|
|
|
5632
5818
|
}
|
|
5633
5819
|
|
|
5634
5820
|
function renderBookmarksSidebar() {
|
|
5821
|
+
if (!document.getElementById('bookmarks-list')) return;
|
|
5635
5822
|
var el = document.getElementById('bookmarks-list');
|
|
5636
5823
|
var countEl = document.getElementById('bookmark-count');
|
|
5637
5824
|
var clearEl = document.getElementById('bookmark-clear');
|
|
5638
5825
|
var ids = Object.keys(bookmarks);
|
|
5639
5826
|
|
|
5640
5827
|
if (!ids.length) {
|
|
5641
|
-
el.innerHTML = '<div style="color:var(--text-muted);font-size:12px;padding:4px;">No bookmarks yet<br><span style="font-size:10px;opacity:0.7">Hover a message and click
|
|
5828
|
+
el.innerHTML = '<div style="color:var(--text-muted);font-size:12px;padding:4px;">No bookmarks yet<br><span style="font-size:10px;opacity:0.7">Hover a message and click star to bookmark</span></div>';
|
|
5642
5829
|
countEl.style.display = 'none';
|
|
5643
5830
|
clearEl.style.display = 'none';
|
|
5644
5831
|
return;
|
|
@@ -5710,19 +5897,28 @@ function exportJSON() {
|
|
|
5710
5897
|
|
|
5711
5898
|
// ==================== VIEW SWITCHING ====================
|
|
5712
5899
|
|
|
5713
|
-
var activeView = '
|
|
5900
|
+
var activeView = localStorage.getItem('neohive_activeView') || 'overview';
|
|
5714
5901
|
|
|
5715
5902
|
function switchView(view) {
|
|
5716
5903
|
activeView = view;
|
|
5717
|
-
|
|
5718
|
-
|
|
5719
|
-
|
|
5720
|
-
|
|
5721
|
-
|
|
5722
|
-
|
|
5723
|
-
document.
|
|
5724
|
-
|
|
5725
|
-
|
|
5904
|
+
try { localStorage.setItem('neohive_activeView', view); } catch {}
|
|
5905
|
+
// Reset render hashes so switching tabs forces a fresh render
|
|
5906
|
+
_lastTasksHash = '';
|
|
5907
|
+
_lastAgentsHash = '';
|
|
5908
|
+
if (renderOverview._lastHash) renderOverview._lastHash = '';
|
|
5909
|
+
// Update nav sidebar active state
|
|
5910
|
+
var navItems = document.querySelectorAll('.nav-item');
|
|
5911
|
+
for (var i = 0; i < navItems.length; i++) {
|
|
5912
|
+
navItems[i].classList.toggle('active', navItems[i].getAttribute('data-view') === view);
|
|
5913
|
+
}
|
|
5914
|
+
// Update old tab active states (kept for compatibility)
|
|
5915
|
+
var tabIds = ['messages','tasks','workspaces','workflows','plan','launch','rules','stats','docs'];
|
|
5916
|
+
for (var t = 0; t < tabIds.length; t++) {
|
|
5917
|
+
var el = document.getElementById('tab-' + tabIds[t]);
|
|
5918
|
+
if (el) el.classList.toggle('active', view === tabIds[t]);
|
|
5919
|
+
}
|
|
5920
|
+
// Toggle view areas
|
|
5921
|
+
document.getElementById('overview-area').classList.toggle('visible', view === 'overview');
|
|
5726
5922
|
document.getElementById('messages').style.display = view === 'messages' ? 'flex' : 'none';
|
|
5727
5923
|
document.getElementById('tasks-area').classList.toggle('visible', view === 'tasks');
|
|
5728
5924
|
document.getElementById('workspaces-area').classList.toggle('visible', view === 'workspaces');
|
|
@@ -5735,6 +5931,9 @@ function switchView(view) {
|
|
|
5735
5931
|
document.getElementById('docs-area').classList.toggle('visible', view === 'docs');
|
|
5736
5932
|
document.getElementById('search-bar').style.display = view === 'messages' ? 'flex' : 'none';
|
|
5737
5933
|
document.getElementById('channel-filter-bar').style.display = view === 'messages' && activeChannel !== undefined ? '' : 'none';
|
|
5934
|
+
var composeBar = document.querySelector('.msg-input-bar');
|
|
5935
|
+
if (composeBar) composeBar.style.display = (view === 'messages') ? 'flex' : 'none';
|
|
5936
|
+
if (view === 'overview') renderOverview();
|
|
5738
5937
|
if (view === 'messages') renderChannelBar(cachedHistory);
|
|
5739
5938
|
if (view === 'tasks') fetchTasks();
|
|
5740
5939
|
if (view === 'workspaces') fetchWorkspaces();
|
|
@@ -5744,7 +5943,6 @@ function switchView(view) {
|
|
|
5744
5943
|
if (view === 'launch') renderLaunchPanel();
|
|
5745
5944
|
if (view === 'rules') fetchRules();
|
|
5746
5945
|
if (view === 'stats') fetchStats();
|
|
5747
|
-
// Auto-close sidebar on mobile after view switch
|
|
5748
5946
|
if (isMobile) closeSidebar();
|
|
5749
5947
|
}
|
|
5750
5948
|
|
|
@@ -5754,7 +5952,10 @@ var cachedTasks = [];
|
|
|
5754
5952
|
|
|
5755
5953
|
function fetchTasks() {
|
|
5756
5954
|
var pq = projectParam();
|
|
5757
|
-
document.getElementById('tasks-area')
|
|
5955
|
+
var tasksArea = document.getElementById('tasks-area');
|
|
5956
|
+
if (tasksArea && !tasksArea.querySelector('.kanban') && !tasksArea.querySelector('.tasks-empty')) {
|
|
5957
|
+
tasksArea.innerHTML = '<div class="loading-spinner">Loading tasks...</div>';
|
|
5958
|
+
}
|
|
5758
5959
|
lttFetch('/api/tasks' + pq).then(function(r) { return r.json(); }).then(function(tasks) {
|
|
5759
5960
|
cachedTasks = Array.isArray(tasks) ? tasks : [];
|
|
5760
5961
|
renderTasks();
|
|
@@ -5764,9 +5965,13 @@ function fetchTasks() {
|
|
|
5764
5965
|
});
|
|
5765
5966
|
}
|
|
5766
5967
|
|
|
5968
|
+
var _lastTasksHash = '';
|
|
5767
5969
|
function renderTasks() {
|
|
5768
5970
|
var el = document.getElementById('tasks-area');
|
|
5971
|
+
var savedScroll = el.scrollTop;
|
|
5769
5972
|
if (!cachedTasks.length) {
|
|
5973
|
+
_lastTasksHash = '';
|
|
5974
|
+
|
|
5770
5975
|
el.innerHTML = '<div class="kanban">' +
|
|
5771
5976
|
'<div class="kanban-col">' +
|
|
5772
5977
|
'<div class="kanban-title pending">Pending<span class="kanban-count">0</span></div>' +
|
|
@@ -5789,6 +5994,11 @@ function renderTasks() {
|
|
|
5789
5994
|
return;
|
|
5790
5995
|
}
|
|
5791
5996
|
|
|
5997
|
+
// Data hash to skip unnecessary DOM rebuilds
|
|
5998
|
+
var tasksHash = cachedTasks.map(function(t) { return t.id + ':' + t.status + ':' + (t.assignee || ''); }).join('|');
|
|
5999
|
+
if (tasksHash === _lastTasksHash) return;
|
|
6000
|
+
_lastTasksHash = tasksHash;
|
|
6001
|
+
|
|
5792
6002
|
var groups = { pending: [], in_progress: [], in_review: [], done: [], blocked: [] };
|
|
5793
6003
|
for (var i = 0; i < cachedTasks.length; i++) {
|
|
5794
6004
|
var t = cachedTasks[i];
|
|
@@ -5817,7 +6027,10 @@ function renderTasks() {
|
|
|
5817
6027
|
html += '</div>';
|
|
5818
6028
|
}
|
|
5819
6029
|
html += '</div>';
|
|
5820
|
-
el.innerHTML
|
|
6030
|
+
if (el.innerHTML !== html) {
|
|
6031
|
+
el.innerHTML = html;
|
|
6032
|
+
el.scrollTop = savedScroll;
|
|
6033
|
+
}
|
|
5821
6034
|
}
|
|
5822
6035
|
|
|
5823
6036
|
function buildTaskCard(t) {
|
|
@@ -5846,7 +6059,7 @@ function buildTaskCard(t) {
|
|
|
5846
6059
|
notesHtml = '<div style="font-size:10px;color:var(--text-muted);margin-top:4px;font-style:italic">' + escapeHtml(lastNote.by) + ': ' + escapeHtml((lastNote.text || '').substring(0, 80)) + '</div>';
|
|
5847
6060
|
}
|
|
5848
6061
|
|
|
5849
|
-
return '<div class="task-card" draggable="true" data-task-id="' + t.id + '" ondragstart="onTaskDragStart(event)" ondragend="onTaskDragEnd(event)">' +
|
|
6062
|
+
return '<div class="task-card" draggable="true" data-task-id="' + t.id + '" ondragstart="onTaskDragStart(event)" ondragend="onTaskDragEnd(event)" onclick="showTaskDetail(\'' + t.id + '\')">' +
|
|
5850
6063
|
'<div class="task-title">' + escapeHtml(t.title || 'Untitled') + (badgesHtml ? ' ' + badgesHtml : '') + '</div>' +
|
|
5851
6064
|
(t.description ? '<div class="task-desc">' + escapeHtml(t.description) + '</div>' : '') +
|
|
5852
6065
|
notesHtml +
|
|
@@ -5868,6 +6081,152 @@ function updateTaskStatus(taskId, newStatus) {
|
|
|
5868
6081
|
}).catch(function(e) { console.error('Task update failed:', e); });
|
|
5869
6082
|
}
|
|
5870
6083
|
|
|
6084
|
+
// ==================== TASK DETAIL VIEW ====================
|
|
6085
|
+
|
|
6086
|
+
function showTaskDetail(taskId) {
|
|
6087
|
+
var task = cachedTasks.find(function(t) { return t.id === taskId; });
|
|
6088
|
+
if (!task) return;
|
|
6089
|
+
var el = document.getElementById('task-detail-content');
|
|
6090
|
+
if (!el) return;
|
|
6091
|
+
var statusColors = { pending: 'var(--yellow)', in_progress: 'var(--accent)', in_review: 'var(--purple)', done: 'var(--green)', blocked: 'var(--red)' };
|
|
6092
|
+
var statusColor = statusColors[task.status] || 'var(--text-muted)';
|
|
6093
|
+
|
|
6094
|
+
var html = '';
|
|
6095
|
+
html += '<div style="margin-bottom:16px"><div style="font-size:18px;font-weight:700;color:var(--text);margin-bottom:6px">' + escapeHtml(task.title || 'Untitled') + '</div>';
|
|
6096
|
+
html += '<span style="font-size:11px;padding:3px 8px;border-radius:6px;font-weight:600;background:' + statusColor + '20;color:' + statusColor + '">' + escapeHtml(task.status || 'pending') + '</span></div>';
|
|
6097
|
+
|
|
6098
|
+
if (task.description) {
|
|
6099
|
+
html += '<div style="font-size:13px;color:var(--text-dim);line-height:1.6;margin-bottom:16px;padding:12px;background:var(--surface-2);border-radius:8px">' + escapeHtml(task.description) + '</div>';
|
|
6100
|
+
}
|
|
6101
|
+
|
|
6102
|
+
html += '<div style="display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-bottom:16px;font-size:12px">';
|
|
6103
|
+
html += '<div style="color:var(--text-muted)">Assignee <div style="color:var(--text);font-weight:600;margin-top:2px">' + escapeHtml(task.assignee || 'Unassigned') + '</div></div>';
|
|
6104
|
+
html += '<div style="color:var(--text-muted)">Created by <div style="color:var(--text);font-weight:600;margin-top:2px">' + escapeHtml(task.created_by || '?') + '</div></div>';
|
|
6105
|
+
html += '<div style="color:var(--text-muted)">Created <div style="color:var(--text);margin-top:2px">' + (task.created_at ? new Date(task.created_at).toLocaleString() : '?') + '</div></div>';
|
|
6106
|
+
html += '<div style="color:var(--text-muted)">Updated <div style="color:var(--text);margin-top:2px">' + (task.updated_at ? new Date(task.updated_at).toLocaleString() : '?') + '</div></div>';
|
|
6107
|
+
html += '</div>';
|
|
6108
|
+
|
|
6109
|
+
// Notes
|
|
6110
|
+
if (task.notes && task.notes.length) {
|
|
6111
|
+
html += '<div style="margin-bottom:16px"><div style="font-size:12px;font-weight:600;color:var(--text-muted);margin-bottom:6px">Notes (' + task.notes.length + ')</div>';
|
|
6112
|
+
for (var n = 0; n < task.notes.length; n++) {
|
|
6113
|
+
var note = task.notes[n];
|
|
6114
|
+
html += '<div style="font-size:12px;padding:6px 8px;background:var(--surface-2);border-radius:6px;margin-bottom:4px"><span style="font-weight:600;color:var(--text)">' + escapeHtml(note.by || '?') + '</span> <span style="color:var(--text-muted);font-size:10px">' + (note.at ? timeAgo(note.at) : '') + '</span><div style="color:var(--text-dim);margin-top:2px">' + escapeHtml(note.text || '') + '</div></div>';
|
|
6115
|
+
}
|
|
6116
|
+
html += '</div>';
|
|
6117
|
+
}
|
|
6118
|
+
|
|
6119
|
+
// Action buttons
|
|
6120
|
+
var canEdit = task.status === 'pending';
|
|
6121
|
+
var canDelete = task.status !== 'in_progress';
|
|
6122
|
+
html += '<div style="border-top:1px solid var(--border);padding-top:12px;display:flex;justify-content:flex-end;gap:8px">';
|
|
6123
|
+
if (canEdit) {
|
|
6124
|
+
html += '<button class="btn btn-primary" onclick="editTask(\'' + task.id + '\')" style="font-size:12px">Edit</button>';
|
|
6125
|
+
} else {
|
|
6126
|
+
html += '<button class="btn" disabled style="font-size:12px;opacity:0.3;cursor:not-allowed" title="Only pending tasks can be edited">Edit</button>';
|
|
6127
|
+
}
|
|
6128
|
+
if (canDelete) {
|
|
6129
|
+
html += '<button class="btn btn-danger" id="task-delete-btn" onclick="confirmDeleteTask(\'' + task.id + '\')" style="font-size:12px">Delete Task</button>';
|
|
6130
|
+
} else {
|
|
6131
|
+
html += '<button class="btn btn-danger" disabled style="font-size:12px;opacity:0.4;cursor:not-allowed" title="Cannot delete active task">Delete Task</button>';
|
|
6132
|
+
}
|
|
6133
|
+
html += '</div>';
|
|
6134
|
+
|
|
6135
|
+
el.innerHTML = html;
|
|
6136
|
+
var taskOverlay = document.getElementById('task-detail-overlay');
|
|
6137
|
+
if (taskOverlay) taskOverlay.classList.add('open');
|
|
6138
|
+
}
|
|
6139
|
+
|
|
6140
|
+
function closeTaskDetail() {
|
|
6141
|
+
var taskOv = document.getElementById('task-detail-overlay');
|
|
6142
|
+
if (taskOv) taskOv.classList.remove('open');
|
|
6143
|
+
var btn = document.getElementById('task-delete-btn');
|
|
6144
|
+
if (btn) { btn.textContent = 'Delete Task'; btn._confirming = false; }
|
|
6145
|
+
}
|
|
6146
|
+
|
|
6147
|
+
function editTask(taskId) {
|
|
6148
|
+
var task = cachedTasks.find(function(t) { return t.id === taskId; });
|
|
6149
|
+
if (!task) return;
|
|
6150
|
+
var el = document.getElementById('task-detail-content');
|
|
6151
|
+
if (!el) return;
|
|
6152
|
+
var html = '';
|
|
6153
|
+
html += '<div style="font-size:16px;font-weight:700;color:var(--text);margin-bottom:16px">Edit Task</div>';
|
|
6154
|
+
html += '<div style="margin-bottom:12px"><label style="font-size:12px;color:var(--text-muted);display:block;margin-bottom:4px">Title</label>';
|
|
6155
|
+
html += '<input type="text" id="task-edit-title" value="' + escapeHtml(task.title || '') + '" style="width:100%;background:var(--surface-2);border:1px solid var(--border);border-radius:8px;padding:8px 12px;font-size:13px;color:var(--text);outline:none"></div>';
|
|
6156
|
+
html += '<div style="margin-bottom:12px"><label style="font-size:12px;color:var(--text-muted);display:block;margin-bottom:4px">Description</label>';
|
|
6157
|
+
html += '<textarea id="task-edit-desc" style="width:100%;min-height:80px;background:var(--surface-2);border:1px solid var(--border);border-radius:8px;padding:8px 12px;font-size:13px;color:var(--text);outline:none;resize:vertical;font-family:inherit">' + escapeHtml(task.description || '') + '</textarea></div>';
|
|
6158
|
+
html += '<div style="margin-bottom:16px"><label style="font-size:12px;color:var(--text-muted);display:block;margin-bottom:4px">Assignee</label>';
|
|
6159
|
+
html += '<input type="text" id="task-edit-assignee" value="' + escapeHtml(task.assignee || '') + '" placeholder="Agent name" style="width:100%;background:var(--surface-2);border:1px solid var(--border);border-radius:8px;padding:8px 12px;font-size:13px;color:var(--text);outline:none"></div>';
|
|
6160
|
+
html += '<div style="display:flex;gap:8px;justify-content:flex-end">';
|
|
6161
|
+
html += '<button class="btn" onclick="showTaskDetail(\'' + taskId + '\')" style="font-size:12px">Cancel</button>';
|
|
6162
|
+
html += '<button class="btn btn-primary" onclick="saveTaskEdit(\'' + taskId + '\')" style="font-size:12px">Save Changes</button>';
|
|
6163
|
+
html += '</div>';
|
|
6164
|
+
el.innerHTML = html;
|
|
6165
|
+
}
|
|
6166
|
+
|
|
6167
|
+
function saveTaskEdit(taskId) {
|
|
6168
|
+
var title = document.getElementById('task-edit-title').value.trim();
|
|
6169
|
+
var desc = document.getElementById('task-edit-desc').value.trim();
|
|
6170
|
+
var assignee = document.getElementById('task-edit-assignee').value.trim();
|
|
6171
|
+
if (!title) { showToast('!', 'Title is required'); return; }
|
|
6172
|
+
lttFetch('/api/tasks' + projectParam(), {
|
|
6173
|
+
method: 'PUT',
|
|
6174
|
+
headers: { 'Content-Type': 'application/json' },
|
|
6175
|
+
body: JSON.stringify({ task_id: taskId, title: title, description: desc, assignee: assignee || null })
|
|
6176
|
+
}).then(function(r) { return r.json(); }).then(function(data) {
|
|
6177
|
+
if (data.error) {
|
|
6178
|
+
showToast('!', data.error);
|
|
6179
|
+
} else {
|
|
6180
|
+
showToast('✓', 'Task updated');
|
|
6181
|
+
closeTaskDetail();
|
|
6182
|
+
fetchTasks();
|
|
6183
|
+
}
|
|
6184
|
+
}).catch(function() {
|
|
6185
|
+
showToast('!', 'Save failed');
|
|
6186
|
+
});
|
|
6187
|
+
}
|
|
6188
|
+
|
|
6189
|
+
var _taskDeleteTimer = null;
|
|
6190
|
+
function confirmDeleteTask(taskId) {
|
|
6191
|
+
var btn = document.getElementById('task-delete-btn');
|
|
6192
|
+
if (!btn) return;
|
|
6193
|
+
if (!btn._confirming) {
|
|
6194
|
+
btn._confirming = true;
|
|
6195
|
+
btn.textContent = 'Confirm Delete';
|
|
6196
|
+
btn.style.background = 'var(--red-dim)';
|
|
6197
|
+
_taskDeleteTimer = setTimeout(function() {
|
|
6198
|
+
btn.textContent = 'Delete Task';
|
|
6199
|
+
btn.style.background = '';
|
|
6200
|
+
btn._confirming = false;
|
|
6201
|
+
}, 3000);
|
|
6202
|
+
} else {
|
|
6203
|
+
if (_taskDeleteTimer) clearTimeout(_taskDeleteTimer);
|
|
6204
|
+
btn.textContent = 'Deleting...';
|
|
6205
|
+
btn.disabled = true;
|
|
6206
|
+
lttFetch('/api/tasks' + projectParam(), {
|
|
6207
|
+
method: 'DELETE',
|
|
6208
|
+
headers: { 'Content-Type': 'application/json' },
|
|
6209
|
+
body: JSON.stringify({ task_id: taskId })
|
|
6210
|
+
}).then(function(r) { return r.json(); }).then(function(data) {
|
|
6211
|
+
if (data.error) {
|
|
6212
|
+
showToast('!', 'Delete failed: ' + data.error);
|
|
6213
|
+
btn.textContent = 'Delete Task';
|
|
6214
|
+
btn.disabled = false;
|
|
6215
|
+
btn._confirming = false;
|
|
6216
|
+
} else {
|
|
6217
|
+
showToast('✓', 'Task deleted');
|
|
6218
|
+
closeTaskDetail();
|
|
6219
|
+
fetchTasks();
|
|
6220
|
+
}
|
|
6221
|
+
}).catch(function() {
|
|
6222
|
+
showToast('!', 'Delete failed');
|
|
6223
|
+
btn.textContent = 'Delete Task';
|
|
6224
|
+
btn.disabled = false;
|
|
6225
|
+
btn._confirming = false;
|
|
6226
|
+
});
|
|
6227
|
+
}
|
|
6228
|
+
}
|
|
6229
|
+
|
|
5871
6230
|
// ==================== KANBAN DRAG-AND-DROP ====================
|
|
5872
6231
|
|
|
5873
6232
|
var draggedTaskId = null;
|
|
@@ -5912,7 +6271,7 @@ function onKanbanDrop(e) {
|
|
|
5912
6271
|
|
|
5913
6272
|
// ==================== STATS VIEW ====================
|
|
5914
6273
|
|
|
5915
|
-
var AGENT_COLORS = ['#
|
|
6274
|
+
var AGENT_COLORS = ['#f59e0b', '#f97316', '#7ee787', '#d29922', '#ffa657', '#14b8a6', '#f778ba', '#56d364'];
|
|
5916
6275
|
|
|
5917
6276
|
function fetchStats() {
|
|
5918
6277
|
var pq = activeProject ? '?project=' + encodeURIComponent(activeProject) : '';
|
|
@@ -6203,13 +6562,13 @@ function deleteRule(ruleId) {
|
|
|
6203
6562
|
// ==================== EXPORT MENU ====================
|
|
6204
6563
|
|
|
6205
6564
|
function toggleExportMenu() {
|
|
6206
|
-
var menu = document.getElementById('export-menu');
|
|
6565
|
+
var menu = document.getElementById('export-menu') || document.createElement('div');
|
|
6207
6566
|
menu.style.display = menu.style.display === 'none' ? 'block' : 'none';
|
|
6208
6567
|
}
|
|
6209
6568
|
|
|
6210
6569
|
// Close menu on outside click
|
|
6211
6570
|
document.addEventListener('click', function(e) {
|
|
6212
|
-
var menu = document.getElementById('export-menu');
|
|
6571
|
+
var menu = document.getElementById('export-menu') || document.createElement('div');
|
|
6213
6572
|
if (menu && menu.style.display !== 'none' && !e.target.closest('#export-menu') && !e.target.closest('.btn-primary')) {
|
|
6214
6573
|
menu.style.display = 'none';
|
|
6215
6574
|
}
|
|
@@ -6281,23 +6640,75 @@ function showProfilePopup(agentName, event) {
|
|
|
6281
6640
|
|
|
6282
6641
|
document.addEventListener('click', function() {
|
|
6283
6642
|
document.getElementById('profile-popup').classList.remove('open');
|
|
6643
|
+
// Reset delete button if popup closes
|
|
6644
|
+
var delBtn = document.getElementById('pp-delete-btn');
|
|
6645
|
+
if (delBtn) { delBtn.textContent = 'Delete Agent'; delBtn._confirming = false; }
|
|
6284
6646
|
});
|
|
6285
6647
|
|
|
6648
|
+
var _deleteConfirmTimer = null;
|
|
6649
|
+
function confirmDeleteAgent() {
|
|
6650
|
+
var btn = document.getElementById('pp-delete-btn');
|
|
6651
|
+
if (!editingAgent) return;
|
|
6652
|
+
if (!btn._confirming) {
|
|
6653
|
+
// First click: change to confirm state
|
|
6654
|
+
btn._confirming = true;
|
|
6655
|
+
btn.textContent = 'Confirm Delete';
|
|
6656
|
+
btn.style.background = 'var(--red-dim)';
|
|
6657
|
+
// Reset after 3s if not confirmed
|
|
6658
|
+
_deleteConfirmTimer = setTimeout(function() {
|
|
6659
|
+
btn.textContent = 'Delete Agent';
|
|
6660
|
+
btn.style.background = '';
|
|
6661
|
+
btn._confirming = false;
|
|
6662
|
+
}, 3000);
|
|
6663
|
+
} else {
|
|
6664
|
+
// Second click: actually delete
|
|
6665
|
+
if (_deleteConfirmTimer) clearTimeout(_deleteConfirmTimer);
|
|
6666
|
+
btn.textContent = 'Deleting...';
|
|
6667
|
+
btn.disabled = true;
|
|
6668
|
+
var agentName = editingAgent;
|
|
6669
|
+
var pq = projectParam();
|
|
6670
|
+
lttFetch('/api/agents' + pq, {
|
|
6671
|
+
method: 'DELETE',
|
|
6672
|
+
headers: { 'Content-Type': 'application/json' },
|
|
6673
|
+
body: JSON.stringify({ name: agentName })
|
|
6674
|
+
}).then(function(r) { return r.json(); }).then(function(data) {
|
|
6675
|
+
if (data.error) {
|
|
6676
|
+
showToast('!', 'Delete failed: ' + data.error);
|
|
6677
|
+
btn.textContent = 'Delete Agent';
|
|
6678
|
+
btn.disabled = false;
|
|
6679
|
+
btn._confirming = false;
|
|
6680
|
+
} else {
|
|
6681
|
+
showToast('✓', agentName + ' removed');
|
|
6682
|
+
document.getElementById('profile-popup').classList.remove('open');
|
|
6683
|
+
btn.textContent = 'Delete Agent';
|
|
6684
|
+
btn.disabled = false;
|
|
6685
|
+
btn._confirming = false;
|
|
6686
|
+
poll(); // Refresh agents
|
|
6687
|
+
}
|
|
6688
|
+
}).catch(function() {
|
|
6689
|
+
showToast('!', 'Delete failed');
|
|
6690
|
+
btn.textContent = 'Delete Agent';
|
|
6691
|
+
btn.disabled = false;
|
|
6692
|
+
btn._confirming = false;
|
|
6693
|
+
});
|
|
6694
|
+
}
|
|
6695
|
+
}
|
|
6696
|
+
|
|
6286
6697
|
// ==================== v3.0: PROFILE EDITOR ====================
|
|
6287
6698
|
|
|
6288
6699
|
var BUILT_IN_AVATARS = [
|
|
6289
|
-
"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64'%3E%3Crect width='64' height='64' rx='32' fill='%
|
|
6700
|
+
"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64'%3E%3Crect width='64' height='64' rx='32' fill='%23f59e0b'/%3E%3Ccircle cx='22' cy='26' r='4' fill='%23fff'/%3E%3Ccircle cx='42' cy='26' r='4' fill='%23fff'/%3E%3Crect x='20' y='38' width='24' height='4' rx='2' fill='%23fff'/%3E%3Crect x='14' y='12' width='6' height='10' rx='3' fill='%23f59e0b' stroke='%23fff' stroke-width='1.5'/%3E%3Crect x='44' y='12' width='6' height='10' rx='3' fill='%23f59e0b' stroke='%23fff' stroke-width='1.5'/%3E%3C/svg%3E",
|
|
6290
6701
|
"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64'%3E%3Crect width='64' height='64' rx='32' fill='%233fb950'/%3E%3Ccircle cx='22' cy='26' r='5' fill='%23fff'/%3E%3Ccircle cx='42' cy='26' r='5' fill='%23fff'/%3E%3Ccircle cx='22' cy='26' r='2' fill='%23333'/%3E%3Ccircle cx='42' cy='26' r='2' fill='%23333'/%3E%3Cpath d='M20 38 Q32 46 44 38' stroke='%23fff' fill='none' stroke-width='2.5' stroke-linecap='round'/%3E%3C/svg%3E",
|
|
6291
6702
|
"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64'%3E%3Crect width='64' height='64' rx='32' fill='%23d29922'/%3E%3Crect x='16' y='22' width='12' height='8' rx='2' fill='%23fff'/%3E%3Crect x='36' y='22' width='12' height='8' rx='2' fill='%23fff'/%3E%3Ccircle cx='22' cy='26' r='2' fill='%23333'/%3E%3Ccircle cx='42' cy='26' r='2' fill='%23333'/%3E%3Cpath d='M24 40 H40' stroke='%23fff' stroke-width='2.5' stroke-linecap='round'/%3E%3C/svg%3E",
|
|
6292
6703
|
"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64'%3E%3Crect width='64' height='64' rx='32' fill='%23f85149'/%3E%3Ccircle cx='22' cy='26' r='4' fill='%23fff'/%3E%3Ccircle cx='42' cy='26' r='4' fill='%23fff'/%3E%3Ccircle cx='22' cy='26' r='2' fill='%23333'/%3E%3Ccircle cx='42' cy='26' r='2' fill='%23333'/%3E%3Cpath d='M22 40 Q32 34 42 40' stroke='%23fff' fill='none' stroke-width='2.5' stroke-linecap='round'/%3E%3C/svg%3E",
|
|
6293
|
-
"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64'%3E%3Crect width='64' height='64' rx='32' fill='%
|
|
6704
|
+
"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64'%3E%3Crect width='64' height='64' rx='32' fill='%23fb923c'/%3E%3Ccircle cx='22' cy='28' r='4' fill='%23fff'/%3E%3Ccircle cx='42' cy='28' r='4' fill='%23fff'/%3E%3Cpath d='M16 18 L22 24' stroke='%23fff' stroke-width='2' stroke-linecap='round'/%3E%3Cpath d='M48 18 L42 24' stroke='%23fff' stroke-width='2' stroke-linecap='round'/%3E%3Cellipse cx='32' cy='42' rx='8' ry='4' fill='%23fff'/%3E%3C/svg%3E",
|
|
6294
6705
|
"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64'%3E%3Crect width='64' height='64' rx='32' fill='%23f778ba'/%3E%3Ccircle cx='24' cy='26' r='6' fill='%23fff'/%3E%3Ccircle cx='40' cy='26' r='6' fill='%23fff'/%3E%3Ccircle cx='24' cy='26' r='3' fill='%23333'/%3E%3Ccircle cx='40' cy='26' r='3' fill='%23333'/%3E%3Cpath d='M26 40 Q32 46 38 40' stroke='%23fff' fill='none' stroke-width='2' stroke-linecap='round'/%3E%3C/svg%3E",
|
|
6295
|
-
"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64'%3E%3Crect width='64' height='64' rx='32' fill='%
|
|
6706
|
+
"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64'%3E%3Crect width='64' height='64' rx='32' fill='%23fbbf24'/%3E%3Crect x='17' y='23' width='10' height='6' rx='3' fill='%23fff'/%3E%3Crect x='37' y='23' width='10' height='6' rx='3' fill='%23fff'/%3E%3Cpath d='M22 38 L32 44 L42 38' stroke='%23fff' fill='none' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E",
|
|
6296
6707
|
"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64'%3E%3Crect width='64' height='64' rx='32' fill='%237ee787'/%3E%3Ccircle cx='22' cy='26' r='4' fill='%23fff'/%3E%3Ccircle cx='42' cy='26' r='4' fill='%23fff'/%3E%3Ccircle cx='23' cy='25' r='2' fill='%23333'/%3E%3Ccircle cx='43' cy='25' r='2' fill='%23333'/%3E%3Cpath d='M20 38 Q32 48 44 38' stroke='%23fff' fill='none' stroke-width='2.5' stroke-linecap='round'/%3E%3C/svg%3E",
|
|
6297
6708
|
"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64'%3E%3Crect width='64' height='64' rx='32' fill='%23e3b341'/%3E%3Cpath d='M18 22 L26 30 L18 30Z' fill='%23fff'/%3E%3Cpath d='M46 22 L38 30 L46 30Z' fill='%23fff'/%3E%3Crect x='24' y='38' width='16' height='6' rx='3' fill='%23fff'/%3E%3C/svg%3E",
|
|
6298
6709
|
"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64'%3E%3Crect width='64' height='64' rx='32' fill='%23ffa198'/%3E%3Ccircle cx='22' cy='26' r='5' fill='%23fff'/%3E%3Ccircle cx='42' cy='26' r='5' fill='%23fff'/%3E%3Ccircle cx='22' cy='27' r='2.5' fill='%23333'/%3E%3Ccircle cx='42' cy='27' r='2.5' fill='%23333'/%3E%3Cellipse cx='32' cy='42' rx='6' ry='3' fill='%23fff'/%3E%3C/svg%3E",
|
|
6299
|
-
"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64'%3E%3Crect width='64' height='64' rx='32' fill='%
|
|
6300
|
-
"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64'%3E%3Crect width='64' height='64' rx='32' fill='%
|
|
6710
|
+
"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64'%3E%3Crect width='64' height='64' rx='32' fill='%23d97706'/%3E%3Crect x='16' y='20' width='14' height='10' rx='2' fill='%23fff'/%3E%3Crect x='34' y='20' width='14' height='10' rx='2' fill='%23fff'/%3E%3Ccircle cx='23' cy='25' r='2' fill='%23d97706'/%3E%3Ccircle cx='41' cy='25' r='2' fill='%23d97706'/%3E%3Crect x='26' y='38' width='12' height='4' rx='2' fill='%23fff'/%3E%3C/svg%3E",
|
|
6711
|
+
"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64'%3E%3Crect width='64' height='64' rx='32' fill='%23b45309'/%3E%3Ccircle cx='24' cy='24' r='5' fill='%23fff'/%3E%3Ccircle cx='40' cy='24' r='5' fill='%23fff'/%3E%3Ccircle cx='24' cy='24' r='2' fill='%23b45309'/%3E%3Ccircle cx='40' cy='24' r='2' fill='%23b45309'/%3E%3Cpath d='M20 38 Q32 50 44 38' stroke='%23fff' fill='none' stroke-width='3' stroke-linecap='round'/%3E%3Ccircle cx='32' cy='10' r='4' fill='%23fff'/%3E%3C/svg%3E",
|
|
6301
6712
|
];
|
|
6302
6713
|
|
|
6303
6714
|
var editingAgent = '';
|
|
@@ -6412,7 +6823,7 @@ function cdPickBodyType(el) {
|
|
|
6412
6823
|
function cdRandomize() {
|
|
6413
6824
|
var skinColors = ['#FFD5B8', '#F5D0A9', '#D4A574', '#C68642', '#8D5524', '#6B3E26', '#FFDFC4', '#F0C8A0', '#E0AC69', '#503335'];
|
|
6414
6825
|
var hairColors = ['#4A3728', '#1a1a1a', '#8B4513', '#D4A574', '#333', '#8B0000', '#F5DEB3', '#FFD700', '#A52A2A', '#2C1608'];
|
|
6415
|
-
var shirtColors = ['#
|
|
6826
|
+
var shirtColors = ['#f59e0b', '#f97316', '#a855f7', '#22c55e', '#ef4444', '#eab308', '#06b6d4', '#ec4899', '#6366f1', '#14b8a6'];
|
|
6416
6827
|
var pantsColors = ['#2d3748', '#3d2b1f', '#1e1b2e', '#1a2e1a', '#2e1a1a', '#2e2a1a', '#1a2e2e', '#2e1a28', '#1a1a3e', '#1e2e2e'];
|
|
6417
6828
|
var shoeColors = ['#1a1a2e', '#2a1a0a', '#0a0a0a', '#3a2a1a', '#1a0a0a'];
|
|
6418
6829
|
var hairStyles = ['none', 'short', 'spiky', 'long', 'ponytail', 'bob', 'curly', 'afro', 'bun', 'braids', 'mohawk', 'wavy'];
|
|
@@ -6482,7 +6893,7 @@ function openProfileEditor() {
|
|
|
6482
6893
|
|
|
6483
6894
|
// Set color pickers
|
|
6484
6895
|
document.getElementById('pe-head-color').value = defApp.head_color || '#FFD5B8';
|
|
6485
|
-
document.getElementById('pe-shirt-color').value = defApp.shirt_color || '#
|
|
6896
|
+
document.getElementById('pe-shirt-color').value = defApp.shirt_color || '#f59e0b';
|
|
6486
6897
|
document.getElementById('pe-hair-color').value = defApp.hair_color || '#4A3728';
|
|
6487
6898
|
document.getElementById('pe-pants-color').value = defApp.pants_color || '#2d3748';
|
|
6488
6899
|
document.getElementById('pe-shoe-color').value = defApp.shoe_color || '#1a1a2e';
|
|
@@ -6658,74 +7069,159 @@ function renderWorkspaces(data) {
|
|
|
6658
7069
|
|
|
6659
7070
|
function fetchWorkflows() {
|
|
6660
7071
|
var pq = projectParam();
|
|
6661
|
-
document.getElementById('workflows-area')
|
|
7072
|
+
var wfArea = document.getElementById('workflows-area');
|
|
7073
|
+
// Only show loading spinner on first load (no existing workflow elements)
|
|
7074
|
+
if (wfArea && !wfArea.querySelector('[data-wf-id]') && !wfArea.querySelector('.tasks-empty')) {
|
|
7075
|
+
wfArea.innerHTML = '<div class="loading-spinner">Loading workflows...</div>';
|
|
7076
|
+
}
|
|
6662
7077
|
lttFetch('/api/workflows' + pq).then(function(r) { return r.json(); }).then(function(data) {
|
|
6663
7078
|
renderWorkflows(Array.isArray(data) ? data : []);
|
|
6664
7079
|
}).catch(function() {});
|
|
6665
7080
|
}
|
|
6666
7081
|
|
|
7082
|
+
function buildWorkflowHTML(wf) {
|
|
7083
|
+
var doneCount = wf.steps.filter(function(s) { return s.status === 'done'; }).length;
|
|
7084
|
+
var pct = Math.round((doneCount / wf.steps.length) * 100);
|
|
7085
|
+
var statusColor = wf.status === 'completed' ? 'var(--green)' : 'var(--accent)';
|
|
7086
|
+
var barColor = statusColor;
|
|
7087
|
+
|
|
7088
|
+
var stepsHtml = '';
|
|
7089
|
+
for (var j = 0; j < wf.steps.length; j++) {
|
|
7090
|
+
var s = wf.steps[j];
|
|
7091
|
+
var stepColor = s.status === 'done' ? 'var(--green)' : s.status === 'in_progress' ? 'var(--accent)' : 'var(--text-muted)';
|
|
7092
|
+
if (j > 0) stepsHtml += '<span class="step-arrow">→</span>';
|
|
7093
|
+
var stepTimeHtml = '';
|
|
7094
|
+
if (s.status === 'done' && s.started_at && s.completed_at) {
|
|
7095
|
+
var dur = Math.round((new Date(s.completed_at) - new Date(s.started_at)) / 60000);
|
|
7096
|
+
stepTimeHtml = '<div class="step-time" style="font-size:9px;color:var(--text-muted);margin-top:2px">' + (dur > 0 ? dur + 'm' : '<1m') + '</div>';
|
|
7097
|
+
} else if (s.status === 'in_progress' && s.started_at) {
|
|
7098
|
+
var elapsed = Math.round((Date.now() - new Date(s.started_at)) / 60000);
|
|
7099
|
+
stepTimeHtml = '<div class="step-time" style="font-size:9px;color:var(--accent);margin-top:2px">' + elapsed + 'm elapsed</div>';
|
|
7100
|
+
}
|
|
7101
|
+
stepsHtml += '<div class="step-card ' + s.status + '" data-step-id="' + s.id + '" title="' + escapeHtml(s.notes || '') + '">' +
|
|
7102
|
+
'<span class="step-num">' + s.id + '</span>' +
|
|
7103
|
+
'<span class="step-status" style="font-size:10px;color:' + stepColor + ';font-weight:600;text-transform:uppercase">' + s.status.replace('_', ' ') + '</span>' +
|
|
7104
|
+
'<div class="step-desc">' + escapeHtml(s.description) + '</div>' +
|
|
7105
|
+
(s.assignee ? '<div class="step-assignee">' + escapeHtml(s.assignee) + '</div>' : '') +
|
|
7106
|
+
stepTimeHtml +
|
|
7107
|
+
'</div>';
|
|
7108
|
+
}
|
|
7109
|
+
|
|
7110
|
+
var controlsHtml = wf.status === 'active' ? '<div class="wf-advance-controls" style="margin-top:8px;display:flex;gap:6px"><button class="btn btn-primary" onclick="dashAdvanceWorkflow(\'' + wf.id + '\')">Complete Step</button></div>' : '';
|
|
7111
|
+
|
|
7112
|
+
return '<div class="pipeline" data-wf-id="' + wf.id + '" style="cursor:pointer" onclick="showWorkflowDetail(\'' + wf.id + '\')">' +
|
|
7113
|
+
'<div class="pipeline-header">' +
|
|
7114
|
+
'<div class="pipeline-name">' + escapeHtml(wf.name) + ' <span class="wf-status-label" style="font-size:11px;color:' + statusColor + ';font-weight:600">' + escapeHtml(wf.status) + '</span></div>' +
|
|
7115
|
+
'<div class="pipeline-progress">' + doneCount + '/' + wf.steps.length + ' (' + pct + '%)</div>' +
|
|
7116
|
+
'</div>' +
|
|
7117
|
+
'<div class="pipeline-bar"><div class="pipeline-bar-fill" style="width:' + pct + '%;background:' + barColor + '"></div></div>' +
|
|
7118
|
+
'<div class="pipeline-steps">' + stepsHtml + '</div>' +
|
|
7119
|
+
controlsHtml +
|
|
7120
|
+
'</div>';
|
|
7121
|
+
}
|
|
7122
|
+
|
|
6667
7123
|
function renderWorkflows(workflows) {
|
|
6668
7124
|
var el = document.getElementById('workflows-area');
|
|
6669
7125
|
if (!workflows.length) {
|
|
6670
|
-
el.
|
|
7126
|
+
if (!el.querySelector('.tasks-empty')) {
|
|
7127
|
+
el.innerHTML = '<div class="tasks-empty">No workflows yet. Agents create workflows with <code style="background:var(--surface-2);padding:2px 6px;border-radius:4px;font-size:12px;color:var(--orange)">create_workflow(name, steps)</code></div>';
|
|
7128
|
+
}
|
|
6671
7129
|
return;
|
|
6672
7130
|
}
|
|
6673
7131
|
|
|
6674
|
-
|
|
7132
|
+
// Remove empty-state if present
|
|
7133
|
+
var emptyEl = el.querySelector('.tasks-empty');
|
|
7134
|
+
if (emptyEl) emptyEl.remove();
|
|
7135
|
+
// Remove loading spinner if present
|
|
7136
|
+
var spinner = el.querySelector('.loading-spinner');
|
|
7137
|
+
if (spinner) spinner.remove();
|
|
7138
|
+
|
|
7139
|
+
// Build map of existing workflow DOM elements
|
|
7140
|
+
var existingEls = {};
|
|
7141
|
+
el.querySelectorAll('[data-wf-id]').forEach(function(node) {
|
|
7142
|
+
existingEls[node.getAttribute('data-wf-id')] = node;
|
|
7143
|
+
});
|
|
7144
|
+
|
|
7145
|
+
// Track which IDs are in the new data
|
|
7146
|
+
var activeIds = {};
|
|
7147
|
+
|
|
6675
7148
|
for (var i = 0; i < workflows.length; i++) {
|
|
6676
7149
|
var wf = workflows[i];
|
|
6677
|
-
|
|
6678
|
-
var
|
|
6679
|
-
|
|
6680
|
-
|
|
6681
|
-
|
|
6682
|
-
|
|
6683
|
-
|
|
6684
|
-
|
|
6685
|
-
|
|
6686
|
-
|
|
6687
|
-
|
|
6688
|
-
|
|
6689
|
-
|
|
6690
|
-
|
|
6691
|
-
|
|
6692
|
-
|
|
6693
|
-
|
|
6694
|
-
|
|
6695
|
-
|
|
6696
|
-
|
|
6697
|
-
for (var j = 0; j < wf.steps.length; j++) {
|
|
6698
|
-
var s = wf.steps[j];
|
|
6699
|
-
var stepColor = s.status === 'done' ? 'var(--green)' : s.status === 'in_progress' ? 'var(--accent)' : 'var(--text-muted)';
|
|
6700
|
-
if (j > 0) html += '<span class="step-arrow">→</span>';
|
|
6701
|
-
var stepTimeHtml = '';
|
|
6702
|
-
if (s.status === 'done' && s.started_at && s.completed_at) {
|
|
6703
|
-
var dur = Math.round((new Date(s.completed_at) - new Date(s.started_at)) / 60000);
|
|
6704
|
-
stepTimeHtml = '<div style="font-size:9px;color:var(--text-muted);margin-top:2px">' + (dur > 0 ? dur + 'm' : '<1m') + '</div>';
|
|
6705
|
-
} else if (s.status === 'in_progress' && s.started_at) {
|
|
6706
|
-
var elapsed = Math.round((Date.now() - new Date(s.started_at)) / 60000);
|
|
6707
|
-
stepTimeHtml = '<div style="font-size:9px;color:var(--accent);margin-top:2px">' + elapsed + 'm elapsed</div>';
|
|
7150
|
+
activeIds[wf.id] = true;
|
|
7151
|
+
var existing = existingEls[wf.id];
|
|
7152
|
+
|
|
7153
|
+
if (existing) {
|
|
7154
|
+
// PATCH existing workflow element in place
|
|
7155
|
+
var doneCount = wf.steps.filter(function(s) { return s.status === 'done'; }).length;
|
|
7156
|
+
var pct = Math.round((doneCount / wf.steps.length) * 100);
|
|
7157
|
+
|
|
7158
|
+
// Update progress text
|
|
7159
|
+
var progEl = existing.querySelector('.pipeline-progress');
|
|
7160
|
+
var newProg = doneCount + '/' + wf.steps.length + ' (' + pct + '%)';
|
|
7161
|
+
if (progEl && progEl.textContent !== newProg) progEl.textContent = newProg;
|
|
7162
|
+
|
|
7163
|
+
// Update progress bar
|
|
7164
|
+
var barFill = existing.querySelector('.pipeline-bar-fill');
|
|
7165
|
+
if (barFill) {
|
|
7166
|
+
var newWidth = pct + '%';
|
|
7167
|
+
if (barFill.style.width !== newWidth) barFill.style.width = newWidth;
|
|
7168
|
+
var barColor = wf.status === 'completed' ? 'var(--green)' : 'var(--accent)';
|
|
7169
|
+
if (barFill.style.background !== barColor) barFill.style.background = barColor;
|
|
6708
7170
|
}
|
|
6709
7171
|
|
|
6710
|
-
|
|
6711
|
-
|
|
6712
|
-
|
|
6713
|
-
|
|
6714
|
-
|
|
6715
|
-
|
|
6716
|
-
'</div>';
|
|
6717
|
-
}
|
|
6718
|
-
html += '</div>';
|
|
7172
|
+
// Update status label
|
|
7173
|
+
var statusLabel = existing.querySelector('.wf-status-label');
|
|
7174
|
+
if (statusLabel && statusLabel.textContent !== wf.status) {
|
|
7175
|
+
statusLabel.textContent = wf.status;
|
|
7176
|
+
statusLabel.style.color = wf.status === 'completed' ? 'var(--green)' : 'var(--accent)';
|
|
7177
|
+
}
|
|
6719
7178
|
|
|
6720
|
-
|
|
6721
|
-
|
|
6722
|
-
|
|
6723
|
-
|
|
6724
|
-
|
|
7179
|
+
// Update each step
|
|
7180
|
+
for (var j = 0; j < wf.steps.length; j++) {
|
|
7181
|
+
var s = wf.steps[j];
|
|
7182
|
+
var stepEl = existing.querySelector('[data-step-id="' + s.id + '"]');
|
|
7183
|
+
if (stepEl) {
|
|
7184
|
+
var newClass = 'step-card ' + s.status;
|
|
7185
|
+
if (stepEl.className !== newClass) stepEl.className = newClass;
|
|
7186
|
+
// Update step status text
|
|
7187
|
+
var stepStatusEl = stepEl.querySelector('.step-status');
|
|
7188
|
+
var newStatusText = s.status.replace('_', ' ');
|
|
7189
|
+
if (stepStatusEl && stepStatusEl.textContent !== newStatusText) {
|
|
7190
|
+
var stepColor = s.status === 'done' ? 'var(--green)' : s.status === 'in_progress' ? 'var(--accent)' : 'var(--text-muted)';
|
|
7191
|
+
stepStatusEl.textContent = newStatusText;
|
|
7192
|
+
stepStatusEl.style.color = stepColor;
|
|
7193
|
+
}
|
|
7194
|
+
// Update elapsed time
|
|
7195
|
+
var timeEl = stepEl.querySelector('.step-time');
|
|
7196
|
+
if (s.status === 'in_progress' && s.started_at) {
|
|
7197
|
+
var elapsed = Math.round((Date.now() - new Date(s.started_at)) / 60000);
|
|
7198
|
+
var newTime = elapsed + 'm elapsed';
|
|
7199
|
+
if (timeEl) { if (timeEl.textContent !== newTime) timeEl.textContent = newTime; }
|
|
7200
|
+
else { stepEl.insertAdjacentHTML('beforeend', '<div class="step-time" style="font-size:9px;color:var(--accent);margin-top:2px">' + newTime + '</div>'); }
|
|
7201
|
+
} else if (timeEl && s.status !== 'done') {
|
|
7202
|
+
timeEl.remove();
|
|
7203
|
+
}
|
|
7204
|
+
}
|
|
7205
|
+
}
|
|
7206
|
+
|
|
7207
|
+
var advControls = existing.querySelector('.wf-advance-controls');
|
|
7208
|
+
if (wf.status === 'active') {
|
|
7209
|
+
if (!advControls) {
|
|
7210
|
+
existing.insertAdjacentHTML('beforeend', '<div class="wf-advance-controls" style="margin-top:8px;display:flex;gap:6px"><button class="btn btn-primary" onclick="dashAdvanceWorkflow(\'' + wf.id + '\')">Complete Step</button></div>');
|
|
7211
|
+
}
|
|
7212
|
+
} else if (advControls) {
|
|
7213
|
+
advControls.remove();
|
|
7214
|
+
}
|
|
7215
|
+
} else {
|
|
7216
|
+
// NEW workflow — append
|
|
7217
|
+
el.insertAdjacentHTML('beforeend', buildWorkflowHTML(wf));
|
|
6725
7218
|
}
|
|
6726
|
-
html += '</div>';
|
|
6727
7219
|
}
|
|
6728
|
-
|
|
7220
|
+
|
|
7221
|
+
// Remove workflows that no longer exist
|
|
7222
|
+
Object.keys(existingEls).forEach(function(id) {
|
|
7223
|
+
if (!activeIds[id]) existingEls[id].remove();
|
|
7224
|
+
});
|
|
6729
7225
|
}
|
|
6730
7226
|
|
|
6731
7227
|
function dashAdvanceWorkflow(wfId) {
|
|
@@ -6736,6 +7232,150 @@ function dashAdvanceWorkflow(wfId) {
|
|
|
6736
7232
|
}).then(function() { fetchWorkflows(); }).catch(function() {});
|
|
6737
7233
|
}
|
|
6738
7234
|
|
|
7235
|
+
// ==================== WORKFLOW DETAIL VIEW ====================
|
|
7236
|
+
|
|
7237
|
+
var _cachedWorkflows = [];
|
|
7238
|
+
|
|
7239
|
+
// Override fetchWorkflows to cache
|
|
7240
|
+
var _origFetchWorkflows = fetchWorkflows;
|
|
7241
|
+
fetchWorkflows = function() {
|
|
7242
|
+
var pq = projectParam();
|
|
7243
|
+
var wfArea = document.getElementById('workflows-area');
|
|
7244
|
+
if (wfArea && !wfArea.querySelector('[data-wf-id]') && !wfArea.querySelector('.tasks-empty')) {
|
|
7245
|
+
wfArea.innerHTML = '<div class="loading-spinner">Loading workflows...</div>';
|
|
7246
|
+
}
|
|
7247
|
+
lttFetch('/api/workflows' + pq).then(function(r) { return r.json(); }).then(function(wfs) {
|
|
7248
|
+
_cachedWorkflows = Array.isArray(wfs) ? wfs : [];
|
|
7249
|
+
renderWorkflows(_cachedWorkflows);
|
|
7250
|
+
}).catch(function() { _cachedWorkflows = []; renderWorkflows([]); });
|
|
7251
|
+
};
|
|
7252
|
+
|
|
7253
|
+
function showWorkflowDetail(wfId) {
|
|
7254
|
+
var wf = _cachedWorkflows.find(function(w) { return w.id === wfId; });
|
|
7255
|
+
if (!wf) return;
|
|
7256
|
+
var el = document.getElementById('wf-detail-content');
|
|
7257
|
+
if (!el) return;
|
|
7258
|
+
var steps = wf.steps || [];
|
|
7259
|
+
var doneCount = steps.filter(function(s) { return s.status === 'done'; }).length;
|
|
7260
|
+
var pct = steps.length > 0 ? Math.round((doneCount / steps.length) * 100) : 0;
|
|
7261
|
+
var statusColors = { active: 'var(--accent)', completed: 'var(--green)', paused: 'var(--orange)' };
|
|
7262
|
+
var statusColor = statusColors[wf.status] || 'var(--text-muted)';
|
|
7263
|
+
|
|
7264
|
+
var html = '';
|
|
7265
|
+
// Header
|
|
7266
|
+
html += '<div style="margin-bottom:16px">';
|
|
7267
|
+
html += '<div style="font-size:18px;font-weight:700;color:var(--text);margin-bottom:6px">' + escapeHtml(wf.name || 'Workflow') + '</div>';
|
|
7268
|
+
html += '<span style="font-size:11px;padding:3px 8px;border-radius:6px;font-weight:600;background:' + statusColor + '20;color:' + statusColor + '">' + escapeHtml(wf.status) + '</span>';
|
|
7269
|
+
if (wf.autonomous) html += ' <span style="font-size:10px;padding:2px 6px;border-radius:4px;background:var(--accent-dim);color:var(--accent);font-weight:600">AUTO</span>';
|
|
7270
|
+
if (wf.parallel) html += ' <span style="font-size:10px;padding:2px 6px;border-radius:4px;background:var(--purple-dim);color:var(--purple);font-weight:600">PARALLEL</span>';
|
|
7271
|
+
html += '</div>';
|
|
7272
|
+
|
|
7273
|
+
// Info grid
|
|
7274
|
+
html += '<div style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:8px;margin-bottom:16px;font-size:12px">';
|
|
7275
|
+
html += '<div style="color:var(--text-muted)">Progress <div style="color:var(--text);font-weight:700;font-size:16px;margin-top:2px">' + pct + '<span style="font-size:12px;margin-left:1px">%</span></div></div>';
|
|
7276
|
+
html += '<div style="color:var(--text-muted)">Created by <div style="color:var(--text);font-weight:600;margin-top:2px">' + escapeHtml(wf.created_by || '?') + '</div></div>';
|
|
7277
|
+
html += '<div style="color:var(--text-muted)">Created <div style="color:var(--text);margin-top:2px">' + (wf.created_at ? timeAgo(wf.created_at) : '?') + '</div></div>';
|
|
7278
|
+
html += '</div>';
|
|
7279
|
+
|
|
7280
|
+
// Progress bar
|
|
7281
|
+
html += '<div style="height:6px;background:var(--surface-2);border-radius:3px;overflow:hidden;margin-bottom:16px"><div style="height:100%;width:' + pct + '%;background:' + statusColor + ';border-radius:3px;transition:width 0.3s"></div></div>';
|
|
7282
|
+
|
|
7283
|
+
// Steps
|
|
7284
|
+
html += '<div style="font-size:13px;font-weight:600;color:var(--text);margin-bottom:8px">Steps (' + doneCount + '/' + steps.length + ')</div>';
|
|
7285
|
+
for (var i = 0; i < steps.length; i++) {
|
|
7286
|
+
var s = steps[i];
|
|
7287
|
+
var sColor = s.status === 'done' ? 'var(--green)' : s.status === 'in_progress' ? 'var(--accent)' : s.status === 'awaiting_approval' ? 'var(--orange)' : 'var(--text-muted)';
|
|
7288
|
+
html += '<div style="background:var(--surface-2);border:1px solid var(--border);border-radius:8px;padding:10px 12px;margin-bottom:6px;border-left:3px solid ' + sColor + '">';
|
|
7289
|
+
html += '<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:4px">';
|
|
7290
|
+
html += '<span style="font-size:13px;font-weight:600;color:var(--text)">Step ' + (s.id || i+1) + ': ' + escapeHtml((s.description || '').substring(0, 80)) + '</span>';
|
|
7291
|
+
html += '<span style="font-size:10px;padding:2px 6px;border-radius:4px;background:' + sColor + '20;color:' + sColor + ';font-weight:600">' + escapeHtml(s.status || 'pending').replace('_', ' ') + '</span>';
|
|
7292
|
+
html += '</div>';
|
|
7293
|
+
html += '<div style="font-size:11px;color:var(--text-muted)">';
|
|
7294
|
+
if (s.assignee) html += 'Assignee: <span style="color:var(--text)">' + escapeHtml(s.assignee) + '</span>';
|
|
7295
|
+
if (s.started_at) html += ' | Started: ' + timeAgo(s.started_at);
|
|
7296
|
+
if (s.completed_at) html += ' | Done: ' + timeAgo(s.completed_at);
|
|
7297
|
+
html += '</div>';
|
|
7298
|
+
if (s.notes) html += '<div style="font-size:11px;color:var(--text-dim);margin-top:4px;font-style:italic">' + escapeHtml(s.notes.substring(0, 200)) + '</div>';
|
|
7299
|
+
html += '</div>';
|
|
7300
|
+
}
|
|
7301
|
+
|
|
7302
|
+
// Checkpoints
|
|
7303
|
+
if (wf.checkpoints && wf.checkpoints.length > 0) {
|
|
7304
|
+
html += '<div style="font-size:13px;font-weight:600;color:var(--text);margin:12px 0 8px">Checkpoints (' + wf.checkpoints.length + ')</div>';
|
|
7305
|
+
for (var c = 0; c < wf.checkpoints.length; c++) {
|
|
7306
|
+
var cp = wf.checkpoints[c];
|
|
7307
|
+
html += '<div style="font-size:12px;padding:6px 8px;background:var(--surface-2);border-radius:6px;margin-bottom:4px;display:flex;justify-content:space-between;align-items:center">';
|
|
7308
|
+
html += '<span style="color:var(--text-dim)">Step ' + cp.step_id + ' — ' + escapeHtml((cp.step_description || '').substring(0, 50)) + ' <span style="color:var(--text-muted)">' + (cp.completed_at ? timeAgo(cp.completed_at) : '') + '</span></span>';
|
|
7309
|
+
html += '</div>';
|
|
7310
|
+
}
|
|
7311
|
+
}
|
|
7312
|
+
|
|
7313
|
+
// Actions
|
|
7314
|
+
var canDelete = wf.status !== 'active';
|
|
7315
|
+
html += '<div style="border-top:1px solid var(--border);padding-top:12px;margin-top:12px;display:flex;justify-content:flex-end;gap:8px">';
|
|
7316
|
+
if (wf.status === 'active') {
|
|
7317
|
+
html += '<button class="btn btn-primary" onclick="event.stopPropagation();dashAdvanceWorkflow(\'' + wf.id + '\');closeWfDetail()" style="font-size:12px">Complete Step</button>';
|
|
7318
|
+
}
|
|
7319
|
+
if (canDelete) {
|
|
7320
|
+
html += '<button class="btn btn-danger" id="wf-delete-btn" onclick="event.stopPropagation();confirmDeleteWorkflow(\'' + wf.id + '\')" style="font-size:12px">Delete</button>';
|
|
7321
|
+
} else {
|
|
7322
|
+
html += '<button class="btn btn-danger" disabled style="font-size:12px;opacity:0.4;cursor:not-allowed" title="Cannot delete active workflow">Delete</button>';
|
|
7323
|
+
}
|
|
7324
|
+
html += '</div>';
|
|
7325
|
+
|
|
7326
|
+
el.innerHTML = html;
|
|
7327
|
+
var wfOverlay = document.getElementById('wf-detail-overlay');
|
|
7328
|
+
if (wfOverlay) wfOverlay.classList.add('open');
|
|
7329
|
+
}
|
|
7330
|
+
|
|
7331
|
+
function closeWfDetail() {
|
|
7332
|
+
var wfOv = document.getElementById('wf-detail-overlay');
|
|
7333
|
+
if (wfOv) wfOv.classList.remove('open');
|
|
7334
|
+
var btn = document.getElementById('wf-delete-btn');
|
|
7335
|
+
if (btn) { btn.textContent = 'Delete'; btn._confirming = false; }
|
|
7336
|
+
}
|
|
7337
|
+
|
|
7338
|
+
var _wfDeleteTimer = null;
|
|
7339
|
+
function confirmDeleteWorkflow(wfId) {
|
|
7340
|
+
var btn = document.getElementById('wf-delete-btn');
|
|
7341
|
+
if (!btn) return;
|
|
7342
|
+
if (!btn._confirming) {
|
|
7343
|
+
btn._confirming = true;
|
|
7344
|
+
btn.textContent = 'Confirm Delete';
|
|
7345
|
+
btn.style.background = 'var(--red-dim)';
|
|
7346
|
+
_wfDeleteTimer = setTimeout(function() {
|
|
7347
|
+
btn.textContent = 'Delete';
|
|
7348
|
+
btn.style.background = '';
|
|
7349
|
+
btn._confirming = false;
|
|
7350
|
+
}, 3000);
|
|
7351
|
+
} else {
|
|
7352
|
+
if (_wfDeleteTimer) clearTimeout(_wfDeleteTimer);
|
|
7353
|
+
btn.textContent = 'Deleting...';
|
|
7354
|
+
btn.disabled = true;
|
|
7355
|
+
lttFetch('/api/workflows' + projectParam(), {
|
|
7356
|
+
method: 'DELETE',
|
|
7357
|
+
headers: { 'Content-Type': 'application/json' },
|
|
7358
|
+
body: JSON.stringify({ workflow_id: wfId })
|
|
7359
|
+
}).then(function(r) { return r.json(); }).then(function(data) {
|
|
7360
|
+
if (data.error) {
|
|
7361
|
+
showToast('!', 'Delete failed: ' + data.error);
|
|
7362
|
+
btn.textContent = 'Delete';
|
|
7363
|
+
btn.disabled = false;
|
|
7364
|
+
btn._confirming = false;
|
|
7365
|
+
} else {
|
|
7366
|
+
showToast('✓', 'Workflow deleted');
|
|
7367
|
+
closeWfDetail();
|
|
7368
|
+
fetchWorkflows();
|
|
7369
|
+
}
|
|
7370
|
+
}).catch(function() {
|
|
7371
|
+
showToast('!', 'Delete failed');
|
|
7372
|
+
btn.textContent = 'Delete';
|
|
7373
|
+
btn.disabled = false;
|
|
7374
|
+
btn._confirming = false;
|
|
7375
|
+
});
|
|
7376
|
+
}
|
|
7377
|
+
}
|
|
7378
|
+
|
|
6739
7379
|
// ==================== v5.0: PLAN EXECUTION VIEW ====================
|
|
6740
7380
|
|
|
6741
7381
|
var planRefreshInterval = null;
|
|
@@ -6823,7 +7463,7 @@ function renderPlanView(data) {
|
|
|
6823
7463
|
// Step cards
|
|
6824
7464
|
for (var j = 0; j < steps.length; j++) {
|
|
6825
7465
|
var s = steps[j];
|
|
6826
|
-
var icon = s.status === 'done' ? '
|
|
7466
|
+
var icon = s.status === 'done' ? '<svg viewBox="0 0 16 16" width="12" height="12" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 8l3 3 7-7"/></svg>' : s.status === 'in_progress' ? '<svg viewBox="0 0 16 16" width="12" height="12" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M2 8a6 6 0 0111 0M14 8a6 6 0 01-11 0"/></svg>' : '...';
|
|
6827
7467
|
var bgColor = s.status === 'done' ? 'rgba(34,197,94,0.08)' : s.status === 'in_progress' ? 'rgba(59,130,246,0.08)' : 'var(--surface-2)';
|
|
6828
7468
|
var borderColor = s.status === 'done' ? 'var(--green)' : s.status === 'in_progress' ? 'var(--accent)' : 'var(--border)';
|
|
6829
7469
|
|
|
@@ -6852,15 +7492,15 @@ function renderPlanView(data) {
|
|
|
6852
7492
|
confidence +
|
|
6853
7493
|
'</div>' +
|
|
6854
7494
|
'</div>' +
|
|
6855
|
-
(s.flagged ? '<div style="font-size:10px;color:var(--orange);margin-top:2px"
|
|
6856
|
-
(s.verification && s.verification.learnings ? '<div style="font-size:10px;color:var(--purple,#a855f7);margin-top:2px"
|
|
7495
|
+
(s.flagged ? '<div style="font-size:10px;color:var(--orange);margin-top:2px">' + escapeHtml(s.flag_reason || 'Flagged for review') + '</div>' : '') +
|
|
7496
|
+
(s.verification && s.verification.learnings ? '<div style="font-size:10px;color:var(--purple,#a855f7);margin-top:2px">Learned: ' + escapeHtml(s.verification.learnings.substring(0, 100)) + '</div>' : '') +
|
|
6857
7497
|
'</div>';
|
|
6858
7498
|
|
|
6859
7499
|
// Step control buttons (skip/reassign) for in_progress/pending steps
|
|
6860
7500
|
if (wf.status === 'active' && (s.status === 'in_progress' || s.status === 'pending')) {
|
|
6861
7501
|
html += '<div style="display:flex;gap:4px">' +
|
|
6862
7502
|
'<button onclick="planAction(\'skip\',' + s.id + ',\'' + wf.id + '\')" style="font-size:10px;padding:3px 8px;background:var(--surface-2);border:1px solid var(--border);border-radius:4px;cursor:pointer;color:var(--text-muted)" title="Skip this step">Skip</button>' +
|
|
6863
|
-
'<button onclick="planReassign(' + s.id + ',\'' + wf.id + '\')" style="font-size:10px;padding:3px 8px;background:var(--surface-2);border:1px solid var(--border);border-radius:4px;cursor:pointer;color:var(--text-muted)" title="Reassign"
|
|
7503
|
+
'<button onclick="planReassign(' + s.id + ',\'' + wf.id + '\')" style="font-size:10px;padding:3px 8px;background:var(--surface-2);border:1px solid var(--border);border-radius:4px;cursor:pointer;color:var(--text-muted)" title="Reassign">Re</button>' +
|
|
6864
7504
|
'</div>';
|
|
6865
7505
|
}
|
|
6866
7506
|
html += '</div>';
|
|
@@ -6868,9 +7508,9 @@ function renderPlanView(data) {
|
|
|
6868
7508
|
|
|
6869
7509
|
// Stats footer
|
|
6870
7510
|
html += '<div style="display:flex;gap:16px;margin-top:12px;padding-top:10px;border-top:1px solid var(--border);font-size:11px;color:var(--text-muted)">';
|
|
6871
|
-
if (retryCount > 0) html += '<span
|
|
6872
|
-
if (flaggedSteps.length > 0) html += '<span
|
|
6873
|
-
html += '<span
|
|
7511
|
+
if (retryCount > 0) html += '<span>Retries: ' + retryCount + '</span>';
|
|
7512
|
+
if (flaggedSteps.length > 0) html += '<span>Flagged: ' + flaggedSteps.length + '</span>';
|
|
7513
|
+
html += '<span>Done: ' + doneCount + '/' + steps.length + '</span>';
|
|
6874
7514
|
html += '</div>';
|
|
6875
7515
|
|
|
6876
7516
|
// Control buttons
|
|
@@ -7007,7 +7647,7 @@ function renderMonitorPanel(data) {
|
|
|
7007
7647
|
for (var i = 0; i < agentNames.length; i++) {
|
|
7008
7648
|
var name = agentNames[i];
|
|
7009
7649
|
var a = agents[name];
|
|
7010
|
-
var color = a.status === '
|
|
7650
|
+
var color = a.status === 'working' ? 'var(--green)' : a.status === 'listening' ? '#3b82f6' : a.status === 'idle' ? 'var(--orange)' : 'var(--red,#ef4444)';
|
|
7011
7651
|
var role = a.role ? ' (' + a.role + ')' : '';
|
|
7012
7652
|
html += '<div style="padding:4px 8px;background:var(--surface-2);border-left:3px solid ' + color + ';border-radius:4px;font-size:11px">' +
|
|
7013
7653
|
'<span style="font-weight:600">' + escapeHtml(name) + '</span>' +
|
|
@@ -7084,6 +7724,7 @@ var notifData = [];
|
|
|
7084
7724
|
var notifSeen = 0;
|
|
7085
7725
|
|
|
7086
7726
|
function toggleNotifPanel() {
|
|
7727
|
+
if (!document.getElementById('notif-panel')) return;
|
|
7087
7728
|
var panel = document.getElementById('notif-panel');
|
|
7088
7729
|
var isOpen = panel.style.display !== 'none';
|
|
7089
7730
|
panel.style.display = isOpen ? 'none' : 'block';
|
|
@@ -7109,7 +7750,7 @@ function renderNotifList() {
|
|
|
7109
7750
|
var html = '';
|
|
7110
7751
|
for (var i = notifData.length - 1; i >= 0; i--) {
|
|
7111
7752
|
var n = notifData[i];
|
|
7112
|
-
var icon = n.type === 'online' ? '
|
|
7753
|
+
var icon = n.type === 'online' ? '+' : n.type === 'offline' ? '-' : n.type === 'listening' ? '~' : 'x';
|
|
7113
7754
|
var time = new Date(n.timestamp).toLocaleTimeString();
|
|
7114
7755
|
html += '<div style="padding:8px 16px;border-bottom:1px solid var(--border);font-size:12px;display:flex;gap:8px;align-items:center">' +
|
|
7115
7756
|
'<span>' + icon + '</span>' +
|
|
@@ -7120,6 +7761,7 @@ function renderNotifList() {
|
|
|
7120
7761
|
}
|
|
7121
7762
|
|
|
7122
7763
|
function updateNotifBadge() {
|
|
7764
|
+
if (!document.getElementById('notif-badge')) return;
|
|
7123
7765
|
var badge = document.getElementById('notif-badge');
|
|
7124
7766
|
var unseen = notifData.length - notifSeen;
|
|
7125
7767
|
if (unseen > 0) { badge.textContent = unseen; badge.style.display = 'block'; }
|
|
@@ -7127,6 +7769,7 @@ function updateNotifBadge() {
|
|
|
7127
7769
|
}
|
|
7128
7770
|
|
|
7129
7771
|
function clearNotifications() {
|
|
7772
|
+
if (!document.getElementById('notif-list')) return;
|
|
7130
7773
|
notifData = [];
|
|
7131
7774
|
notifSeen = 0;
|
|
7132
7775
|
renderNotifList();
|
|
@@ -7136,7 +7779,7 @@ function clearNotifications() {
|
|
|
7136
7779
|
// Close notif panel on outside click
|
|
7137
7780
|
document.addEventListener('click', function(e) {
|
|
7138
7781
|
var panel = document.getElementById('notif-panel');
|
|
7139
|
-
var bell = document.getElementById('notif-bell');
|
|
7782
|
+
var bell = document.getElementById('notif-bell') || document.createElement('div');
|
|
7140
7783
|
if (panel && panel.style.display !== 'none' && !panel.contains(e.target) && e.target !== bell && !bell.contains(e.target)) {
|
|
7141
7784
|
panel.style.display = 'none';
|
|
7142
7785
|
}
|
|
@@ -7226,7 +7869,7 @@ function renderScores(data, repData) {
|
|
|
7226
7869
|
var n = names[i];
|
|
7227
7870
|
var a = agents[n];
|
|
7228
7871
|
var agentRep = rep ? rep[n] : null;
|
|
7229
|
-
var medal = i === 0 ? '
|
|
7872
|
+
var medal = i === 0 ? '#1' : i === 1 ? '#2' : i === 2 ? '#3' : '#' + (i + 1);
|
|
7230
7873
|
var scoreColor = a.score >= 80 ? 'var(--green)' : a.score >= 60 ? 'var(--accent)' : a.score >= 40 ? 'var(--orange)' : 'var(--red)';
|
|
7231
7874
|
html += '<div style="background:var(--surface-2);border:1px solid var(--border);border-radius:10px;padding:12px 16px;display:flex;align-items:center;gap:12px">' +
|
|
7232
7875
|
'<div style="font-size:18px;width:28px;text-align:center">' + medal + '</div>' +
|
|
@@ -7271,13 +7914,13 @@ function poll() {
|
|
|
7271
7914
|
sendBrowserNotification(cachedHistory[cachedHistory.length - 1]);
|
|
7272
7915
|
}
|
|
7273
7916
|
|
|
7274
|
-
|
|
7275
|
-
|
|
7276
|
-
|
|
7277
|
-
|
|
7917
|
+
$id('stat-messages').textContent = status.messageCount;
|
|
7918
|
+
$id('stat-agents').textContent = status.agentCount;
|
|
7919
|
+
$id('stat-sleeping').textContent = status.sleepingCount || 0;
|
|
7920
|
+
$id('stat-threads').textContent = status.threadCount;
|
|
7278
7921
|
|
|
7279
7922
|
// Managed mode badge
|
|
7280
|
-
var managedBadge =
|
|
7923
|
+
var managedBadge = $id('managed-badge');
|
|
7281
7924
|
if (status.conversation_mode === 'managed' && status.managed) {
|
|
7282
7925
|
managedBadge.style.display = '';
|
|
7283
7926
|
var phaseLabel = String(status.managed.phase || 'discussion').toUpperCase();
|
|
@@ -7285,8 +7928,8 @@ function poll() {
|
|
|
7285
7928
|
status.managed.floor === 'directed' ? (status.managed.turn_current || '?') + ' speaking' :
|
|
7286
7929
|
status.managed.floor === 'open' ? 'round-robin' :
|
|
7287
7930
|
status.managed.floor === 'execution' ? 'working' : '';
|
|
7288
|
-
|
|
7289
|
-
|
|
7931
|
+
$id('managed-phase').textContent = phaseLabel;
|
|
7932
|
+
$id('managed-floor-info').textContent = floorInfo ? ' | ' + floorInfo : '';
|
|
7290
7933
|
} else {
|
|
7291
7934
|
managedBadge.style.display = 'none';
|
|
7292
7935
|
}
|
|
@@ -7298,7 +7941,7 @@ function poll() {
|
|
|
7298
7941
|
var ag = cachedAgents[agentKeys[p]];
|
|
7299
7942
|
if (ag.alive && ag.provider && ag.provider !== 'unknown') providers[ag.provider] = true;
|
|
7300
7943
|
}
|
|
7301
|
-
|
|
7944
|
+
$id('stat-clis').textContent = Object.keys(providers).length;
|
|
7302
7945
|
|
|
7303
7946
|
// Update tab title with message count
|
|
7304
7947
|
document.title = status.messageCount > 0
|
|
@@ -7325,7 +7968,7 @@ function poll() {
|
|
|
7325
7968
|
checkPlanCompletion();
|
|
7326
7969
|
}).catch(function(e) {
|
|
7327
7970
|
console.error('Poll failed:', e);
|
|
7328
|
-
document.getElementById('conn-detail').textContent = ' ERR: ' + e.message;
|
|
7971
|
+
var _cd = document.getElementById('conn-detail'); if(_cd) _cd.textContent = ' ERR: ' + e.message;
|
|
7329
7972
|
});
|
|
7330
7973
|
}
|
|
7331
7974
|
|
|
@@ -7342,6 +7985,14 @@ function doReset() {
|
|
|
7342
7985
|
|
|
7343
7986
|
// ==================== PROJECT MANAGEMENT ====================
|
|
7344
7987
|
|
|
7988
|
+
function setProjectRemoveButtonsVisible(visible) {
|
|
7989
|
+
var disp = visible ? '' : 'none';
|
|
7990
|
+
var icon = document.getElementById('remove-project-btn');
|
|
7991
|
+
var text = document.getElementById('remove-project-text-btn');
|
|
7992
|
+
if (icon) icon.style.display = disp;
|
|
7993
|
+
if (text) text.style.display = disp;
|
|
7994
|
+
}
|
|
7995
|
+
|
|
7345
7996
|
function loadProjects() {
|
|
7346
7997
|
return lttFetch('/api/projects').then(function(r) { return r.json(); }).then(function(projects) {
|
|
7347
7998
|
console.log('[LTT] loadProjects:', projects.length, 'projects, activeProject:', activeProject);
|
|
@@ -7355,10 +8006,12 @@ function loadProjects() {
|
|
|
7355
8006
|
sel.appendChild(opt);
|
|
7356
8007
|
}
|
|
7357
8008
|
|
|
7358
|
-
//
|
|
7359
|
-
if (
|
|
7360
|
-
activeProject =
|
|
7361
|
-
|
|
8009
|
+
// If selection pointed at a project the server removed (e.g. redundant with Default), fall back to Default
|
|
8010
|
+
if (activeProject && !projects.some(function(p) { return p.path === activeProject; })) {
|
|
8011
|
+
activeProject = '';
|
|
8012
|
+
sel.value = '';
|
|
8013
|
+
setProjectRemoveButtonsVisible(false);
|
|
8014
|
+
poll();
|
|
7362
8015
|
}
|
|
7363
8016
|
|
|
7364
8017
|
// If project came from URL param, try fuzzy match (handles path separator differences)
|
|
@@ -7374,7 +8027,7 @@ function loadProjects() {
|
|
|
7374
8027
|
|
|
7375
8028
|
if (activeProject) {
|
|
7376
8029
|
sel.value = activeProject;
|
|
7377
|
-
|
|
8030
|
+
setProjectRemoveButtonsVisible(true);
|
|
7378
8031
|
// Update header project indicator for mobile
|
|
7379
8032
|
var indicator = document.getElementById('mobile-project-name');
|
|
7380
8033
|
if (indicator) {
|
|
@@ -7384,8 +8037,8 @@ function loadProjects() {
|
|
|
7384
8037
|
}
|
|
7385
8038
|
}
|
|
7386
8039
|
|
|
7387
|
-
// Show/hide remove
|
|
7388
|
-
|
|
8040
|
+
// Show/hide remove buttons (not shown for Default / local)
|
|
8041
|
+
setProjectRemoveButtonsVisible(!!activeProject);
|
|
7389
8042
|
// Sync mobile header project select
|
|
7390
8043
|
syncMobileProjectSelect();
|
|
7391
8044
|
}).catch(function(e) {
|
|
@@ -7396,7 +8049,7 @@ function loadProjects() {
|
|
|
7396
8049
|
function switchProject() {
|
|
7397
8050
|
activeProject = document.getElementById('project-select').value;
|
|
7398
8051
|
lastMessageCount = 0;
|
|
7399
|
-
|
|
8052
|
+
setProjectRemoveButtonsVisible(!!activeProject);
|
|
7400
8053
|
syncMobileProjectSelect();
|
|
7401
8054
|
loadConversationList();
|
|
7402
8055
|
poll();
|
|
@@ -7408,7 +8061,7 @@ function mobileSelectProject(val) {
|
|
|
7408
8061
|
lastMessageCount = 0;
|
|
7409
8062
|
// Sync sidebar select
|
|
7410
8063
|
document.getElementById('project-select').value = val;
|
|
7411
|
-
|
|
8064
|
+
setProjectRemoveButtonsVisible(!!val);
|
|
7412
8065
|
poll();
|
|
7413
8066
|
}
|
|
7414
8067
|
|
|
@@ -7429,6 +8082,7 @@ function syncMobileProjectSelect() {
|
|
|
7429
8082
|
|
|
7430
8083
|
function showAddProject() {
|
|
7431
8084
|
var input = document.getElementById('project-path-input');
|
|
8085
|
+
if (!input) return;
|
|
7432
8086
|
if (input.classList.contains('visible')) {
|
|
7433
8087
|
input.classList.remove('visible');
|
|
7434
8088
|
} else {
|
|
@@ -7439,8 +8093,11 @@ function showAddProject() {
|
|
|
7439
8093
|
|
|
7440
8094
|
function addProject() {
|
|
7441
8095
|
var input = document.getElementById('project-path-input');
|
|
8096
|
+
if (!input) return;
|
|
7442
8097
|
var projectPath = input.value.trim();
|
|
7443
8098
|
if (!projectPath) return;
|
|
8099
|
+
// If user pasted .../repo/.neohive, use repo root (matches server rules)
|
|
8100
|
+
projectPath = projectPath.replace(/[\\/]+\.neohive\/?$/i, '');
|
|
7444
8101
|
|
|
7445
8102
|
lttFetch('/api/projects', {
|
|
7446
8103
|
method: 'POST',
|
|
@@ -7458,13 +8115,14 @@ function addProject() {
|
|
|
7458
8115
|
switchProject();
|
|
7459
8116
|
}, 200);
|
|
7460
8117
|
} else {
|
|
7461
|
-
|
|
8118
|
+
showToast('!', res.error || 'Failed to add project');
|
|
7462
8119
|
}
|
|
7463
8120
|
}).catch(function(e) { console.error('Add project failed:', e); });
|
|
7464
8121
|
}
|
|
7465
8122
|
|
|
7466
8123
|
function discoverProjects() {
|
|
7467
8124
|
var resultsEl = document.getElementById('discover-results');
|
|
8125
|
+
if (!resultsEl) return;
|
|
7468
8126
|
resultsEl.style.display = 'block';
|
|
7469
8127
|
resultsEl.innerHTML = '<div style="font-size:11px;color:var(--text-muted);padding:4px;">Scanning...</div>';
|
|
7470
8128
|
|
|
@@ -7500,7 +8158,7 @@ function addDiscovered(projectPath, name) {
|
|
|
7500
8158
|
body: JSON.stringify({ path: projectPath, name: name })
|
|
7501
8159
|
}).then(function(r) { return r.json(); }).then(function(res) {
|
|
7502
8160
|
if (res.success) {
|
|
7503
|
-
document.getElementById('discover-results').style.display = 'none';
|
|
8161
|
+
var _dr = document.getElementById('discover-results'); if (_dr) _dr.style.display = 'none';
|
|
7504
8162
|
loadProjects();
|
|
7505
8163
|
}
|
|
7506
8164
|
}).catch(function() {});
|
|
@@ -7508,7 +8166,9 @@ function addDiscovered(projectPath, name) {
|
|
|
7508
8166
|
|
|
7509
8167
|
function removeProject() {
|
|
7510
8168
|
if (!activeProject) return;
|
|
7511
|
-
|
|
8169
|
+
var sel = document.getElementById('project-select');
|
|
8170
|
+
var label = sel && sel.options[sel.selectedIndex] ? sel.options[sel.selectedIndex].textContent : activeProject;
|
|
8171
|
+
if (!confirm('Remove “' + label + '” from the dashboard?\n\nYour .neohive folder on disk is not deleted — only this shortcut in the list.')) return;
|
|
7512
8172
|
|
|
7513
8173
|
lttFetch('/api/projects', {
|
|
7514
8174
|
method: 'DELETE',
|
|
@@ -7518,10 +8178,17 @@ function removeProject() {
|
|
|
7518
8178
|
if (res.success) {
|
|
7519
8179
|
activeProject = '';
|
|
7520
8180
|
document.getElementById('project-select').value = '';
|
|
8181
|
+
setProjectRemoveButtonsVisible(false);
|
|
7521
8182
|
loadProjects();
|
|
7522
8183
|
poll();
|
|
8184
|
+
showToast('\u2713', 'Project removed from list');
|
|
8185
|
+
} else {
|
|
8186
|
+
showToast('!', res.error || 'Could not remove project');
|
|
7523
8187
|
}
|
|
7524
|
-
}).catch(function(e) {
|
|
8188
|
+
}).catch(function(e) {
|
|
8189
|
+
console.error('Remove project failed:', e);
|
|
8190
|
+
showToast('!', 'Remove project failed');
|
|
8191
|
+
});
|
|
7525
8192
|
}
|
|
7526
8193
|
|
|
7527
8194
|
// ==================== SOUND NOTIFICATIONS ====================
|
|
@@ -7531,18 +8198,75 @@ var audioCtx = null;
|
|
|
7531
8198
|
|
|
7532
8199
|
function toggleSound() {
|
|
7533
8200
|
soundEnabled = !soundEnabled;
|
|
7534
|
-
var btn = document.getElementById('sound-toggle');
|
|
8201
|
+
var btn = document.getElementById('sound-toggle') || document.createElement('div');
|
|
7535
8202
|
if (soundEnabled) {
|
|
7536
8203
|
btn.classList.add('active');
|
|
7537
|
-
btn.innerHTML = '
|
|
7538
|
-
// Init audio context on first user interaction
|
|
8204
|
+
btn.innerHTML = '~';
|
|
7539
8205
|
if (!audioCtx) audioCtx = new (window.AudioContext || window.webkitAudioContext)();
|
|
7540
8206
|
} else {
|
|
7541
8207
|
btn.classList.remove('active');
|
|
7542
|
-
btn.innerHTML = '
|
|
8208
|
+
btn.innerHTML = 'Off';
|
|
7543
8209
|
}
|
|
7544
8210
|
}
|
|
7545
8211
|
|
|
8212
|
+
function toggleCombinedNotifications() {
|
|
8213
|
+
var label = document.getElementById('settings-notif-label');
|
|
8214
|
+
if (typeof Notification === 'undefined') {
|
|
8215
|
+
if (label) label.textContent = 'Notifications (not supported)';
|
|
8216
|
+
return;
|
|
8217
|
+
}
|
|
8218
|
+
if (!notifEnabled && !soundEnabled) {
|
|
8219
|
+
// Turn both on — request permission if needed
|
|
8220
|
+
if (notifPermission !== 'granted') {
|
|
8221
|
+
Notification.requestPermission().then(function(perm) {
|
|
8222
|
+
notifPermission = perm;
|
|
8223
|
+
if (perm === 'granted') {
|
|
8224
|
+
notifEnabled = true;
|
|
8225
|
+
soundEnabled = true;
|
|
8226
|
+
if (!audioCtx) audioCtx = new (window.AudioContext || window.webkitAudioContext)();
|
|
8227
|
+
localStorage.setItem('ltt-notif', 'true');
|
|
8228
|
+
updateNotifBtn();
|
|
8229
|
+
updateCombinedNotifLabel();
|
|
8230
|
+
} else {
|
|
8231
|
+
if (label) label.textContent = 'Notifications (blocked by browser)';
|
|
8232
|
+
}
|
|
8233
|
+
});
|
|
8234
|
+
} else {
|
|
8235
|
+
notifEnabled = true;
|
|
8236
|
+
soundEnabled = true;
|
|
8237
|
+
if (!audioCtx) audioCtx = new (window.AudioContext || window.webkitAudioContext)();
|
|
8238
|
+
localStorage.setItem('ltt-notif', 'true');
|
|
8239
|
+
updateNotifBtn();
|
|
8240
|
+
updateCombinedNotifLabel();
|
|
8241
|
+
}
|
|
8242
|
+
} else {
|
|
8243
|
+
// Turn both off
|
|
8244
|
+
notifEnabled = false;
|
|
8245
|
+
soundEnabled = false;
|
|
8246
|
+
localStorage.setItem('ltt-notif', 'false');
|
|
8247
|
+
updateNotifBtn();
|
|
8248
|
+
updateCombinedNotifLabel();
|
|
8249
|
+
}
|
|
8250
|
+
}
|
|
8251
|
+
|
|
8252
|
+
function updateCombinedNotifLabel() {
|
|
8253
|
+
var label = document.getElementById('settings-notif-label');
|
|
8254
|
+
if (!label) return;
|
|
8255
|
+
if (notifEnabled && soundEnabled) {
|
|
8256
|
+
label.textContent = 'Notifications: On';
|
|
8257
|
+
label.style.color = 'var(--green)';
|
|
8258
|
+
} else if (notifPermission === 'denied') {
|
|
8259
|
+
label.textContent = 'Notifications (blocked by browser)';
|
|
8260
|
+
label.style.color = 'var(--red)';
|
|
8261
|
+
} else {
|
|
8262
|
+
label.textContent = 'Notifications: Off';
|
|
8263
|
+
label.style.color = '';
|
|
8264
|
+
}
|
|
8265
|
+
}
|
|
8266
|
+
|
|
8267
|
+
// Init label on load
|
|
8268
|
+
setTimeout(updateCombinedNotifLabel, 500);
|
|
8269
|
+
|
|
7546
8270
|
function playNotifSound() {
|
|
7547
8271
|
if (!audioCtx) return;
|
|
7548
8272
|
try {
|
|
@@ -7588,14 +8312,6 @@ var pollLatency = 0;
|
|
|
7588
8312
|
|
|
7589
8313
|
function updateConnectionInfo(latency) {
|
|
7590
8314
|
pollLatency = latency;
|
|
7591
|
-
var detail = document.getElementById('conn-detail');
|
|
7592
|
-
if (latency < 100) {
|
|
7593
|
-
detail.textContent = ' ' + latency + 'ms';
|
|
7594
|
-
} else if (latency < 500) {
|
|
7595
|
-
detail.textContent = ' ' + latency + 'ms';
|
|
7596
|
-
} else {
|
|
7597
|
-
detail.textContent = ' slow';
|
|
7598
|
-
}
|
|
7599
8315
|
}
|
|
7600
8316
|
|
|
7601
8317
|
// ==================== REPLAY MODE ====================
|
|
@@ -7616,7 +8332,7 @@ function enterReplay() {
|
|
|
7616
8332
|
document.getElementById('search-bar').style.display = 'none';
|
|
7617
8333
|
document.getElementById('replay-slider').max = cachedHistory.length;
|
|
7618
8334
|
document.getElementById('replay-slider').value = 0;
|
|
7619
|
-
document.getElementById('replay-header-btn').style.display = 'none';
|
|
8335
|
+
var _rh = document.getElementById('replay-header-btn'); if(_rh) _rh.style.display = 'none';
|
|
7620
8336
|
renderReplayMessages();
|
|
7621
8337
|
}
|
|
7622
8338
|
|
|
@@ -7627,7 +8343,7 @@ function exitReplay() {
|
|
|
7627
8343
|
document.getElementById('replay-bar').classList.remove('visible');
|
|
7628
8344
|
document.getElementById('view-tabs').style.display = '';
|
|
7629
8345
|
document.getElementById('search-bar').style.display = '';
|
|
7630
|
-
document.getElementById('replay-header-btn').style.display = '';
|
|
8346
|
+
var _rh = document.getElementById('replay-header-btn'); if(_rh) _rh.style.display = '';
|
|
7631
8347
|
lastMessageCount = 0;
|
|
7632
8348
|
renderMessages(cachedHistory);
|
|
7633
8349
|
}
|
|
@@ -7744,13 +8460,13 @@ function toggleNotifications() {
|
|
|
7744
8460
|
}
|
|
7745
8461
|
|
|
7746
8462
|
function updateNotifBtn() {
|
|
7747
|
-
var btn = document.getElementById('notif-toggle');
|
|
8463
|
+
var btn = document.getElementById('notif-toggle') || document.createElement('div');
|
|
7748
8464
|
if (notifEnabled) {
|
|
7749
8465
|
btn.classList.add('active');
|
|
7750
|
-
btn.innerHTML = '
|
|
8466
|
+
btn.innerHTML = 'On';
|
|
7751
8467
|
} else {
|
|
7752
8468
|
btn.classList.remove('active');
|
|
7753
|
-
btn.innerHTML = '
|
|
8469
|
+
btn.innerHTML = 'x';
|
|
7754
8470
|
}
|
|
7755
8471
|
}
|
|
7756
8472
|
|
|
@@ -7761,7 +8477,7 @@ function sendBrowserNotification(msg) {
|
|
|
7761
8477
|
var preview = msg.content.substring(0, 80);
|
|
7762
8478
|
var n = new Notification(msg.from + ' sent a message', {
|
|
7763
8479
|
body: preview,
|
|
7764
|
-
icon: 'data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><rect rx="20" width="100" height="100" fill="%230d1117"/><path d="M20 30 Q20 20 30 20 H70 Q80 20 80 30 V55 Q80 65 70 65 H55 L40 80 V65 H30 Q20 65 20 55Z" fill="%
|
|
8480
|
+
icon: 'data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><rect rx="20" width="100" height="100" fill="%230d1117"/><path d="M20 30 Q20 20 30 20 H70 Q80 20 80 30 V55 Q80 65 70 65 H55 L40 80 V65 H30 Q20 65 20 55Z" fill="%23f59e0b"/></svg>',
|
|
7765
8481
|
tag: 'ltt-' + msg.id
|
|
7766
8482
|
});
|
|
7767
8483
|
n.onclick = function() { window.focus(); n.close(); };
|
|
@@ -7784,21 +8500,23 @@ function fetchLanState() {
|
|
|
7784
8500
|
|
|
7785
8501
|
function updateLanUI() {
|
|
7786
8502
|
// Update phone button indicator
|
|
7787
|
-
var phoneBtn = document.getElementById('phone-btn');
|
|
8503
|
+
var phoneBtn = document.getElementById('phone-btn') || document.createElement('div');
|
|
7788
8504
|
phoneBtn.classList.toggle('lan-active', lanState.lan_mode);
|
|
7789
8505
|
|
|
7790
8506
|
// Update LAN badge in connection area
|
|
7791
8507
|
var existing = document.getElementById('lan-badge');
|
|
7792
8508
|
if (lanState.lan_mode && lanState.lan_ip) {
|
|
7793
8509
|
if (!existing) {
|
|
7794
|
-
var conn = document.querySelector('.connection');
|
|
7795
|
-
|
|
7796
|
-
|
|
7797
|
-
|
|
7798
|
-
|
|
7799
|
-
|
|
8510
|
+
var conn = document.querySelector('.connection') || document.querySelector('.header-actions');
|
|
8511
|
+
if (conn) {
|
|
8512
|
+
var badge = document.createElement('span');
|
|
8513
|
+
badge.className = 'lan-badge';
|
|
8514
|
+
badge.id = 'lan-badge';
|
|
8515
|
+
conn.appendChild(badge);
|
|
8516
|
+
existing = badge;
|
|
8517
|
+
}
|
|
7800
8518
|
}
|
|
7801
|
-
existing.textContent = 'LAN ' + lanState.lan_ip + ':' + lanState.port;
|
|
8519
|
+
if (existing) existing.textContent = 'LAN ' + lanState.lan_ip + ':' + lanState.port;
|
|
7802
8520
|
} else if (existing) {
|
|
7803
8521
|
existing.remove();
|
|
7804
8522
|
}
|
|
@@ -7812,6 +8530,10 @@ function openPhoneAccess() {
|
|
|
7812
8530
|
fetchLanState().then(function() {
|
|
7813
8531
|
renderPhoneModalContent();
|
|
7814
8532
|
document.getElementById('phone-modal-overlay').classList.add('open');
|
|
8533
|
+
}).catch(function() {
|
|
8534
|
+
// Still open the modal even if fetch fails — show current state
|
|
8535
|
+
renderPhoneModalContent();
|
|
8536
|
+
document.getElementById('phone-modal-overlay').classList.add('open');
|
|
7815
8537
|
});
|
|
7816
8538
|
}
|
|
7817
8539
|
|
|
@@ -7903,87 +8625,336 @@ var selectedCli = 'claude';
|
|
|
7903
8625
|
var _launchWatchers = []; // [{name, cli, prompt, state}] for tracking launch status
|
|
7904
8626
|
var _launchWatchInterval = null;
|
|
7905
8627
|
|
|
8628
|
+
var ROLE_SKILLS = {
|
|
8629
|
+
lead: ['planning', 'delegation', 'tracking', 'review'],
|
|
8630
|
+
backend: ['javascript', 'typescript', 'nodejs', 'coding', 'debugging'],
|
|
8631
|
+
frontend: ['ui', 'ux', 'css', 'html', 'frontend', 'design'],
|
|
8632
|
+
quality: ['review', 'testing', 'quality', 'security', 'standards'],
|
|
8633
|
+
monitor: ['observability', 'logging', 'performance', 'health-checks']
|
|
8634
|
+
};
|
|
8635
|
+
var ROLE_PROMPTS = {
|
|
8636
|
+
lead: function(name) { return 'You are ' + name + ', the Coordinator in a multi-agent team. Register as "' + name + '".\n\nYour job is to:\n1. Break the user\'s request into tasks and delegate to team agents via send_message\n2. Use create_task() and create_workflow() to formally track work\n3. Monitor progress with workflow_status() and list_tasks()\n4. Use consume_messages() to check agent updates without blocking\n5. Synthesize results and present to the user\n\nYou MUST NOT edit files or write code. Delegate ALL code work to other agents. Your tools: send_message, create_task, create_workflow, advance_workflow, workflow_status, list_tasks, consume_messages, broadcast.'; },
|
|
8637
|
+
backend: function(name) { return 'You are ' + name + ', a Developer in a multi-agent team. Register as "' + name + '". Call listen() to wait for tasks.\n\nWhen you receive a task:\n1. Use lock_file() before editing shared code\n2. Implement the requested changes with clean, tested code\n3. Use unlock_file() when done editing\n4. Update your task with update_task(status="done")\n5. Send a summary to the Coordinator with file paths and key decisions\n6. Call listen() again for the next task\n\nFocus on production-quality code. Include file paths in reports.'; },
|
|
8638
|
+
frontend: function(name) { return 'You are ' + name + ', a Frontend Developer in a multi-agent team. Register as "' + name + '". Call listen() to wait for tasks.\n\nWhen you receive a task:\n1. Use lock_file() before editing shared frontend code\n2. Implement UI/UX changes following design conventions\n3. Use unlock_file() when done\n4. Update your task with update_task(status="done")\n5. Send a summary to the Coordinator with file paths and screenshots if relevant\n6. Call listen() again for the next task\n\nFocus on clean UI, accessibility, and responsive design.'; },
|
|
8639
|
+
quality: function(name) { return 'You are ' + name + ', a Reviewer in a multi-agent team. Register as "' + name + '". Call listen() to wait for review requests.\n\nWhen you receive work to review:\n1. Read the actual files that were changed\n2. Check for bugs, security issues, code style, edge cases\n3. Use submit_review() to formally approve or request changes\n4. Send structured feedback: blockers vs suggestions\n5. Call listen() again\n\nBe constructive and specific. Reference line numbers. Never let mediocre work pass.'; },
|
|
8640
|
+
monitor: function(name) { return 'You are ' + name + ', a System Monitor in a multi-agent team. Register as "' + name + '". Call listen() to wait for events.\n\nYour job:\n1. Watch for idle agents, stuck tasks, and circular escalations\n2. Use send_message to nudge idle agents\n3. Use update_task to reassign stuck tasks\n4. Log interventions to your workspace via workspace_write\n5. Call listen() again\n\nNever stop monitoring. You ARE the system intelligence.'; }
|
|
8641
|
+
};
|
|
8642
|
+
|
|
7906
8643
|
function renderLaunchPanel() {
|
|
7907
8644
|
var el = document.getElementById('launch-area');
|
|
7908
|
-
|
|
7909
|
-
|
|
7910
|
-
|
|
8645
|
+
var pq = projectParam();
|
|
8646
|
+
Promise.all([
|
|
8647
|
+
lttFetch('/api/templates').then(function(r) { return r.json(); }).catch(function() { return []; }),
|
|
8648
|
+
lttFetch('/api/agents' + pq).then(function(r) { return r.json(); }).catch(function() { return {}; })
|
|
8649
|
+
]).then(function(results) {
|
|
8650
|
+
var allTemplates = results[0];
|
|
8651
|
+
var existingAgents = results[1];
|
|
8652
|
+
var existingNames = Object.keys(existingAgents).map(function(n) { return n.toLowerCase(); });
|
|
8653
|
+
|
|
7911
8654
|
var simpleTemplates = [];
|
|
7912
8655
|
var convTemplates = [];
|
|
7913
8656
|
for (var k = 0; k < allTemplates.length; k++) {
|
|
7914
|
-
if (allTemplates[k].source === 'conversation-templates')
|
|
7915
|
-
|
|
7916
|
-
} else {
|
|
7917
|
-
simpleTemplates.push(allTemplates[k]);
|
|
7918
|
-
}
|
|
8657
|
+
if (allTemplates[k].source === 'conversation-templates') convTemplates.push(allTemplates[k]);
|
|
8658
|
+
else simpleTemplates.push(allTemplates[k]);
|
|
7919
8659
|
}
|
|
7920
8660
|
launchTemplates = simpleTemplates;
|
|
8661
|
+
window._convTemplates = convTemplates;
|
|
8662
|
+
|
|
8663
|
+
var html = '';
|
|
8664
|
+
// Section 1: Add Agent
|
|
8665
|
+
html += '<div class="launch-section">';
|
|
8666
|
+
html += '<h3>Add Agent</h3>';
|
|
8667
|
+
html += '<div class="launch-form-row">';
|
|
8668
|
+
html += '<div class="launch-form-field"><label>Agent Name</label><input type="text" id="launch-name" placeholder="e.g. Backend, Reviewer" maxlength="20" oninput="validateLaunchName()"><div class="field-hint" id="launch-name-hint">1-20 alphanumeric, underscore, or hyphen</div></div>';
|
|
8669
|
+
html += '<div class="launch-form-field"><label>Role</label><select id="launch-role" onchange="onLaunchRoleChange()"><option value="">Select role...</option><option value="lead">Lead</option><option value="backend">Backend</option><option value="frontend">Frontend</option><option value="quality">Quality</option><option value="monitor">Monitor</option></select></div>';
|
|
8670
|
+
html += '</div>';
|
|
8671
|
+
html += '<div class="launch-form-row">';
|
|
8672
|
+
html += '<div class="launch-form-field"><label>CLI Type</label><select id="launch-cli"><option value="claude"' + (selectedCli === 'claude' ? ' selected' : '') + '>Claude Code</option><option value="gemini"' + (selectedCli === 'gemini' ? ' selected' : '') + '>Gemini CLI</option><option value="codex"' + (selectedCli === 'codex' ? ' selected' : '') + '>Codex CLI</option><option value="cursor"' + (selectedCli === 'cursor' ? ' selected' : '') + '>Cursor IDE</option></select></div>';
|
|
8673
|
+
html += '<div class="launch-form-field"><label>Skills</label><input type="text" id="launch-skills" placeholder="Auto-filled from role" readonly style="color:var(--text-muted)"></div>';
|
|
8674
|
+
html += '</div>';
|
|
8675
|
+
html += '<div style="margin-top:8px"><button class="btn btn-primary" onclick="generateAgentPrompt()" style="margin-right:8px">Generate Prompt</button><button class="btn" onclick="doLaunch()">Launch Terminal</button></div>';
|
|
8676
|
+
html += '<div id="launch-prompt-output" style="display:none;margin-top:12px"></div>';
|
|
8677
|
+
html += '<div class="launch-result" id="launch-result"></div>';
|
|
8678
|
+
html += '</div>';
|
|
7921
8679
|
|
|
7922
|
-
//
|
|
7923
|
-
|
|
7924
|
-
|
|
7925
|
-
|
|
7926
|
-
|
|
7927
|
-
|
|
8680
|
+
// Section 2: Team Templates
|
|
8681
|
+
html += '<div class="launch-section">';
|
|
8682
|
+
html += '<h3 style="display:flex;justify-content:space-between;align-items:center">Team Templates <button class="btn btn-primary" onclick="showTemplateBuilder()" style="font-size:12px;padding:5px 12px">+ Create Template</button></h3>';
|
|
8683
|
+
if (allTemplates.length > 0) {
|
|
8684
|
+
html += '<div class="template-grid">';
|
|
8685
|
+
var allTpls = simpleTemplates.concat(convTemplates);
|
|
8686
|
+
for (var t = 0; t < allTpls.length; t++) {
|
|
8687
|
+
var tpl = allTpls[t];
|
|
8688
|
+
var agents = tpl.agents || [];
|
|
8689
|
+
var isConv = tpl.source === 'conversation-templates';
|
|
8690
|
+
html += '<div class="template-card" onclick="expandTemplate(' + t + ')">';
|
|
8691
|
+
html += '<div class="template-card-name">' + escapeHtml(tpl.name || tpl.id || 'Template') + '</div>';
|
|
8692
|
+
html += '<div class="template-card-desc">' + escapeHtml(tpl.description || '') + '</div>';
|
|
8693
|
+
html += '<div class="template-card-agents">';
|
|
8694
|
+
if (agents.length > 0) {
|
|
8695
|
+
for (var a = 0; a < agents.length; a++) {
|
|
8696
|
+
var chipLabel = agents[a].display_name || agents[a].name || 'Agent';
|
|
8697
|
+
if (agents[a].role) chipLabel += ' (' + agents[a].role + ')';
|
|
8698
|
+
html += '<span class="template-agent-chip">' + escapeHtml(chipLabel) + '</span>';
|
|
8699
|
+
}
|
|
8700
|
+
} else if (tpl.roles) {
|
|
8701
|
+
var roleNames = Object.keys(tpl.roles);
|
|
8702
|
+
for (var r = 0; r < roleNames.length; r++) {
|
|
8703
|
+
html += '<span class="template-agent-chip">' + escapeHtml(roleNames[r]) + '</span>';
|
|
8704
|
+
}
|
|
8705
|
+
}
|
|
8706
|
+
html += '</div></div>';
|
|
7928
8707
|
}
|
|
7929
|
-
|
|
8708
|
+
html += '</div>';
|
|
8709
|
+
html += '<div id="template-expanded" style="display:none;margin-top:16px"></div>';
|
|
8710
|
+
} else {
|
|
8711
|
+
html += '<div style="color:var(--text-muted);font-size:13px;padding:8px 0">No templates yet. Create one to get started.</div>';
|
|
7930
8712
|
}
|
|
7931
|
-
|
|
7932
|
-
|
|
7933
|
-
|
|
7934
|
-
|
|
7935
|
-
|
|
7936
|
-
|
|
8713
|
+
html += '<div id="template-builder" style="display:none;margin-top:16px"></div>';
|
|
8714
|
+
html += '</div>';
|
|
8715
|
+
|
|
8716
|
+
// Store data for JS functions
|
|
8717
|
+
window._launchExistingNames = existingNames;
|
|
8718
|
+
window._allLaunchTemplates = allTpls;
|
|
8719
|
+
|
|
8720
|
+
el.innerHTML = html;
|
|
8721
|
+
}).catch(function() {
|
|
8722
|
+
el.innerHTML = '<div class="launch-section"><h3>Add Agent</h3><p style="color:var(--text-dim)">Failed to load data.</p></div>';
|
|
8723
|
+
});
|
|
8724
|
+
}
|
|
8725
|
+
|
|
8726
|
+
function validateLaunchName() {
|
|
8727
|
+
var input = document.getElementById('launch-name');
|
|
8728
|
+
var hint = document.getElementById('launch-name-hint');
|
|
8729
|
+
var name = (input.value || '').trim();
|
|
8730
|
+
if (!name) { hint.className = 'field-hint'; hint.textContent = '1-20 alphanumeric, underscore, or hyphen'; input.classList.remove('invalid'); return; }
|
|
8731
|
+
if (!/^[a-zA-Z0-9_-]{1,20}$/.test(name)) { hint.className = 'field-hint error'; hint.textContent = 'Invalid: only letters, numbers, _ and -'; input.classList.add('invalid'); return; }
|
|
8732
|
+
if (window._launchExistingNames && window._launchExistingNames.indexOf(name.toLowerCase()) !== -1) { hint.className = 'field-hint error'; hint.textContent = 'Name already taken by an existing agent'; input.classList.add('invalid'); return; }
|
|
8733
|
+
hint.className = 'field-hint success'; hint.textContent = 'Name available'; input.classList.remove('invalid');
|
|
8734
|
+
}
|
|
8735
|
+
|
|
8736
|
+
function onLaunchRoleChange() {
|
|
8737
|
+
var role = document.getElementById('launch-role').value;
|
|
8738
|
+
var skillsInput = document.getElementById('launch-skills');
|
|
8739
|
+
if (role && ROLE_SKILLS[role]) {
|
|
8740
|
+
skillsInput.value = ROLE_SKILLS[role].join(', ');
|
|
8741
|
+
} else {
|
|
8742
|
+
skillsInput.value = '';
|
|
8743
|
+
}
|
|
8744
|
+
}
|
|
8745
|
+
|
|
8746
|
+
function generateAgentPrompt() {
|
|
8747
|
+
var name = (document.getElementById('launch-name').value || '').trim();
|
|
8748
|
+
var role = document.getElementById('launch-role').value;
|
|
8749
|
+
var cli = document.getElementById('launch-cli').value;
|
|
8750
|
+
if (!name) { showToast('!', 'Enter an agent name'); return; }
|
|
8751
|
+
if (!/^[a-zA-Z0-9_-]{1,20}$/.test(name)) { showToast('!', 'Invalid agent name'); return; }
|
|
8752
|
+
|
|
8753
|
+
var prompt;
|
|
8754
|
+
if (role && ROLE_PROMPTS[role]) {
|
|
8755
|
+
prompt = ROLE_PROMPTS[role](name);
|
|
8756
|
+
} else {
|
|
8757
|
+
prompt = 'You are ' + name + ' in a multi-agent team. Register as "' + name + '". Call listen() to wait for tasks. When you receive a task, implement it, report what you did, and call listen() again.';
|
|
8758
|
+
}
|
|
8759
|
+
|
|
8760
|
+
var outputEl = document.getElementById('launch-prompt-output');
|
|
8761
|
+
outputEl.style.display = 'block';
|
|
8762
|
+
outputEl.innerHTML = '<div class="launch-prompt-output" onclick="copyGeneratedPrompt()"><div style="font-size:11px;color:var(--text-muted);margin-bottom:6px">Click to copy</div>' + escapeHtml(prompt) + '</div>';
|
|
8763
|
+
window._generatedLaunchPrompt = prompt;
|
|
8764
|
+
}
|
|
8765
|
+
|
|
8766
|
+
function copyGeneratedPrompt() {
|
|
8767
|
+
if (window._generatedLaunchPrompt) {
|
|
8768
|
+
navigator.clipboard.writeText(window._generatedLaunchPrompt).then(function() {
|
|
8769
|
+
showToast('✓', 'Prompt copied to clipboard');
|
|
8770
|
+
});
|
|
8771
|
+
}
|
|
8772
|
+
}
|
|
8773
|
+
|
|
8774
|
+
function expandTemplate(index) {
|
|
8775
|
+
var tpls = window._allLaunchTemplates;
|
|
8776
|
+
if (!tpls || !tpls[index]) return;
|
|
8777
|
+
var tpl = tpls[index];
|
|
8778
|
+
var agents = tpl.agents || [];
|
|
8779
|
+
var expanded = document.getElementById('template-expanded');
|
|
8780
|
+
if (!expanded) return;
|
|
8781
|
+
|
|
8782
|
+
var html = '<div style="background:var(--surface-2);border:1px solid var(--border);border-radius:10px;padding:16px">';
|
|
8783
|
+
html += '<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:12px"><span style="font-size:14px;font-weight:600">' + escapeHtml(tpl.name) + '</span><button class="btn" onclick="document.getElementById(\'template-expanded\').style.display=\'none\'" style="font-size:11px;padding:4px 10px">Close</button></div>';
|
|
8784
|
+
|
|
8785
|
+
if (agents.length > 0) {
|
|
8786
|
+
for (var i = 0; i < agents.length; i++) {
|
|
8787
|
+
var ag = agents[i];
|
|
8788
|
+
var agPrompt = ag.prompt || 'You are ' + (ag.name || 'Agent') + '. Register as "' + (ag.name || 'Agent') + '". Call listen() for tasks.';
|
|
8789
|
+
html += '<div style="margin-bottom:10px;padding:10px;background:var(--surface);border:1px solid var(--border);border-radius:8px">';
|
|
8790
|
+
html += '<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:4px">';
|
|
8791
|
+
html += '<span style="font-weight:600;font-size:13px">' + escapeHtml(ag.display_name || ag.name || 'Agent') + '</span>';
|
|
8792
|
+
html += '<span style="font-size:11px;color:var(--text-muted)">' + escapeHtml(ag.role || '') + '</span>';
|
|
8793
|
+
html += '</div>';
|
|
8794
|
+
if (ag.skills && ag.skills.length) {
|
|
8795
|
+
html += '<div style="margin-bottom:4px;display:flex;gap:4px;flex-wrap:wrap">';
|
|
8796
|
+
for (var s = 0; s < ag.skills.length; s++) {
|
|
8797
|
+
html += '<span style="font-size:10px;padding:1px 6px;border-radius:4px;background:var(--accent-dim);color:var(--accent)">' + escapeHtml(ag.skills[s]) + '</span>';
|
|
8798
|
+
}
|
|
8799
|
+
html += '</div>';
|
|
7937
8800
|
}
|
|
7938
|
-
|
|
8801
|
+
if (ag.responsibilities && ag.responsibilities.length) {
|
|
8802
|
+
html += '<div style="font-size:11px;color:var(--text-dim);margin-bottom:6px">' + ag.responsibilities.map(function(r) { return escapeHtml(r); }).join(' / ') + '</div>';
|
|
8803
|
+
}
|
|
8804
|
+
html += '<div class="launch-prompt-output" style="font-size:11px;max-height:100px" onclick="navigator.clipboard.writeText(this.textContent).then(function(){showToast(\'✓\',\'Copied!\')})">' + escapeHtml(agPrompt) + '</div>';
|
|
8805
|
+
html += '</div>';
|
|
7939
8806
|
}
|
|
8807
|
+
} else if (tpl.roles) {
|
|
8808
|
+
var roleNames = Object.keys(tpl.roles);
|
|
8809
|
+
for (var r = 0; r < roleNames.length; r++) {
|
|
8810
|
+
var rolePrompt = tpl.roles[roleNames[r]].prompt || 'Register as "' + roleNames[r] + '" and call listen().';
|
|
8811
|
+
html += '<div style="margin-bottom:8px;padding:10px;background:var(--surface);border:1px solid var(--border);border-radius:8px">';
|
|
8812
|
+
html += '<div style="font-weight:600;font-size:13px;margin-bottom:4px">' + escapeHtml(roleNames[r]) + '</div>';
|
|
8813
|
+
html += '<div class="launch-prompt-output" style="font-size:11px;max-height:100px" onclick="navigator.clipboard.writeText(this.textContent).then(function(){showToast(\'✓\',\'Copied!\')})">' + escapeHtml(rolePrompt) + '</div>';
|
|
8814
|
+
html += '</div>';
|
|
8815
|
+
}
|
|
8816
|
+
}
|
|
8817
|
+
html += '</div>';
|
|
7940
8818
|
|
|
7941
|
-
|
|
7942
|
-
|
|
8819
|
+
expanded.innerHTML = html;
|
|
8820
|
+
expanded.style.display = 'block';
|
|
8821
|
+
expanded.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
|
8822
|
+
}
|
|
7943
8823
|
|
|
7944
|
-
|
|
7945
|
-
|
|
7946
|
-
|
|
7947
|
-
|
|
7948
|
-
|
|
7949
|
-
|
|
7950
|
-
|
|
7951
|
-
|
|
7952
|
-
|
|
7953
|
-
|
|
7954
|
-
|
|
7955
|
-
|
|
7956
|
-
|
|
7957
|
-
|
|
7958
|
-
|
|
7959
|
-
|
|
7960
|
-
|
|
7961
|
-
|
|
7962
|
-
|
|
7963
|
-
|
|
7964
|
-
|
|
7965
|
-
|
|
7966
|
-
|
|
7967
|
-
|
|
7968
|
-
|
|
7969
|
-
|
|
7970
|
-
|
|
7971
|
-
|
|
7972
|
-
|
|
7973
|
-
|
|
7974
|
-
|
|
7975
|
-
|
|
7976
|
-
|
|
7977
|
-
|
|
7978
|
-
|
|
7979
|
-
|
|
7980
|
-
|
|
7981
|
-
|
|
7982
|
-
|
|
7983
|
-
|
|
7984
|
-
|
|
8824
|
+
// ==================== CUSTOM TEMPLATE BUILDER ====================
|
|
8825
|
+
|
|
8826
|
+
var _tplBuilderAgents = [];
|
|
8827
|
+
|
|
8828
|
+
function showTemplateBuilder(prefill) {
|
|
8829
|
+
var el = document.getElementById('template-builder');
|
|
8830
|
+
if (!el) return;
|
|
8831
|
+
_tplBuilderAgents = prefill && prefill.agents ? prefill.agents.map(function(a) { return Object.assign({}, a); }) : [
|
|
8832
|
+
{ name: 'Coordinator', role: 'lead', skills: 'planning, delegation', prompt: '' },
|
|
8833
|
+
{ name: 'Developer', role: 'backend', skills: 'javascript, coding', prompt: '' }
|
|
8834
|
+
];
|
|
8835
|
+
|
|
8836
|
+
renderBuilderForm(el, prefill);
|
|
8837
|
+
el.style.display = 'block';
|
|
8838
|
+
el.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
|
8839
|
+
}
|
|
8840
|
+
|
|
8841
|
+
function renderBuilderForm(el, prefill) {
|
|
8842
|
+
var html = '<div style="background:var(--surface-2);border:1px solid var(--border);border-radius:10px;padding:20px">';
|
|
8843
|
+
html += '<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:16px"><span style="font-size:16px;font-weight:700;color:var(--text)">' + (prefill ? 'Edit Template' : 'Create Template') + '</span><button class="btn" onclick="hideTemplateBuilder()" style="font-size:11px;padding:4px 10px">Cancel</button></div>';
|
|
8844
|
+
|
|
8845
|
+
// Template info
|
|
8846
|
+
html += '<div class="launch-form-row">';
|
|
8847
|
+
html += '<div class="launch-form-field"><label>Template Name</label><input type="text" id="tpl-name" value="' + escapeHtml(prefill ? prefill.name || '' : '') + '" placeholder="e.g. My Dev Team"></div>';
|
|
8848
|
+
html += '<div class="launch-form-field"><label>Category</label><select id="tpl-category"><option value="development"' + (prefill && prefill.category === 'development' ? ' selected' : '') + '>Development</option><option value="research"' + (prefill && prefill.category === 'research' ? ' selected' : '') + '>Research</option><option value="review"' + (prefill && prefill.category === 'review' ? ' selected' : '') + '>Review</option><option value="custom"' + (prefill && prefill.category === 'custom' ? ' selected' : '') + '>Custom</option></select></div>';
|
|
8849
|
+
html += '</div>';
|
|
8850
|
+
html += '<div class="launch-form-field" style="margin-bottom:16px"><label>Description</label><input type="text" id="tpl-desc" value="' + escapeHtml(prefill ? prefill.description || '' : '') + '" placeholder="Brief description of this team template"></div>';
|
|
8851
|
+
html += '<div class="launch-form-field" style="margin-bottom:16px"><label>Mode</label><select id="tpl-mode"><option value="direct"' + (prefill && prefill.conversation_mode === 'direct' ? ' selected' : '') + '>Direct</option><option value="group"' + (prefill && prefill.conversation_mode === 'group' ? ' selected' : '') + '>Group</option><option value="managed"' + (prefill && prefill.conversation_mode === 'managed' ? ' selected' : '') + '>Managed</option></select></div>';
|
|
8852
|
+
|
|
8853
|
+
// Agents
|
|
8854
|
+
html += '<div style="font-size:13px;font-weight:600;color:var(--text);margin-bottom:8px">Agents</div>';
|
|
8855
|
+
for (var i = 0; i < _tplBuilderAgents.length; i++) {
|
|
8856
|
+
var ag = _tplBuilderAgents[i];
|
|
8857
|
+
html += '<div style="background:var(--surface);border:1px solid var(--border);border-radius:8px;padding:12px;margin-bottom:8px">';
|
|
8858
|
+
html += '<div class="launch-form-row">';
|
|
8859
|
+
html += '<div class="launch-form-field"><label>Name</label><input type="text" data-tpl-agent="' + i + '" data-field="name" value="' + escapeHtml(ag.name || '') + '" oninput="updateTplAgent(' + i + ',\'name\',this.value)"></div>';
|
|
8860
|
+
html += '<div class="launch-form-field"><label>Role</label><select data-tpl-agent="' + i + '" data-field="role" onchange="updateTplAgent(' + i + ',\'role\',this.value);autoFillTplSkills(' + i + ',this.value)"><option value="">Select...</option><option value="lead"' + (ag.role === 'lead' ? ' selected' : '') + '>Lead</option><option value="backend"' + (ag.role === 'backend' ? ' selected' : '') + '>Backend</option><option value="frontend"' + (ag.role === 'frontend' ? ' selected' : '') + '>Frontend</option><option value="quality"' + (ag.role === 'quality' ? ' selected' : '') + '>Quality</option><option value="monitor"' + (ag.role === 'monitor' ? ' selected' : '') + '>Monitor</option></select></div>';
|
|
8861
|
+
html += '</div>';
|
|
8862
|
+
html += '<div class="launch-form-field" style="margin-bottom:6px"><label>Skills</label><input type="text" data-tpl-agent="' + i + '" data-field="skills" value="' + escapeHtml(ag.skills || '') + '" oninput="updateTplAgent(' + i + ',\'skills\',this.value)" placeholder="comma-separated skills"></div>';
|
|
8863
|
+
html += '<div class="launch-form-field"><label>Prompt</label><textarea data-tpl-agent="' + i + '" data-field="prompt" oninput="updateTplAgent(' + i + ',\'prompt\',this.value)" style="min-height:60px;background:var(--surface-2);border:1px solid var(--border);border-radius:8px;padding:8px;font-size:12px;color:var(--text);font-family:inherit;resize:vertical;outline:none;width:100%">' + escapeHtml(ag.prompt || '') + '</textarea></div>';
|
|
8864
|
+
if (_tplBuilderAgents.length > 1) {
|
|
8865
|
+
html += '<button class="btn btn-danger" onclick="removeTplAgent(' + i + ')" style="font-size:11px;padding:3px 8px;margin-top:6px">Remove</button>';
|
|
8866
|
+
}
|
|
8867
|
+
html += '</div>';
|
|
8868
|
+
}
|
|
8869
|
+
html += '<button class="btn" onclick="addTplAgent()" style="font-size:12px;margin-bottom:16px">+ Add Agent</button>';
|
|
8870
|
+
|
|
8871
|
+
// Save
|
|
8872
|
+
html += '<div style="display:flex;gap:8px;justify-content:flex-end;border-top:1px solid var(--border);padding-top:12px">';
|
|
8873
|
+
if (prefill && prefill.id) {
|
|
8874
|
+
html += '<button class="btn btn-primary" onclick="saveCustomTemplate(\'' + escapeHtml(prefill.id) + '\')" style="font-size:12px">Update Template</button>';
|
|
8875
|
+
} else {
|
|
8876
|
+
html += '<button class="btn btn-primary" onclick="saveCustomTemplate()" style="font-size:12px">Save Template</button>';
|
|
8877
|
+
}
|
|
8878
|
+
html += '</div>';
|
|
8879
|
+
html += '</div>';
|
|
8880
|
+
el.innerHTML = html;
|
|
8881
|
+
}
|
|
8882
|
+
|
|
8883
|
+
function hideTemplateBuilder() {
|
|
8884
|
+
var el = document.getElementById('template-builder');
|
|
8885
|
+
if (el) el.style.display = 'none';
|
|
8886
|
+
}
|
|
8887
|
+
|
|
8888
|
+
function updateTplAgent(idx, field, value) {
|
|
8889
|
+
if (_tplBuilderAgents[idx]) _tplBuilderAgents[idx][field] = value;
|
|
8890
|
+
}
|
|
8891
|
+
|
|
8892
|
+
function autoFillTplSkills(idx, role) {
|
|
8893
|
+
if (ROLE_SKILLS[role]) {
|
|
8894
|
+
_tplBuilderAgents[idx].skills = ROLE_SKILLS[role].join(', ');
|
|
8895
|
+
var el = document.getElementById('template-builder');
|
|
8896
|
+
if (el) renderBuilderForm(el);
|
|
8897
|
+
}
|
|
8898
|
+
}
|
|
8899
|
+
|
|
8900
|
+
function addTplAgent() {
|
|
8901
|
+
_tplBuilderAgents.push({ name: '', role: '', skills: '', prompt: '' });
|
|
8902
|
+
var el = document.getElementById('template-builder');
|
|
8903
|
+
if (el) renderBuilderForm(el);
|
|
8904
|
+
}
|
|
8905
|
+
|
|
8906
|
+
function removeTplAgent(idx) {
|
|
8907
|
+
_tplBuilderAgents.splice(idx, 1);
|
|
8908
|
+
var el = document.getElementById('template-builder');
|
|
8909
|
+
if (el) renderBuilderForm(el);
|
|
8910
|
+
}
|
|
8911
|
+
|
|
8912
|
+
function saveCustomTemplate(existingId) {
|
|
8913
|
+
var name = (document.getElementById('tpl-name').value || '').trim();
|
|
8914
|
+
var desc = (document.getElementById('tpl-desc').value || '').trim();
|
|
8915
|
+
var category = document.getElementById('tpl-category').value;
|
|
8916
|
+
var mode = document.getElementById('tpl-mode').value;
|
|
8917
|
+
|
|
8918
|
+
if (!name) { showToast('!', 'Template name is required'); return; }
|
|
8919
|
+
if (_tplBuilderAgents.length < 1) { showToast('!', 'Add at least one agent'); return; }
|
|
8920
|
+
var hasEmptyName = _tplBuilderAgents.some(function(a) { return !a.name || !a.name.trim(); });
|
|
8921
|
+
if (hasEmptyName) { showToast('!', 'All agents must have a name'); return; }
|
|
8922
|
+
|
|
8923
|
+
var agents = _tplBuilderAgents.map(function(a) {
|
|
8924
|
+
return {
|
|
8925
|
+
name: a.name.trim(),
|
|
8926
|
+
role: a.role || '',
|
|
8927
|
+
display_name: a.name.trim(),
|
|
8928
|
+
skills: (a.skills || '').split(',').map(function(s) { return s.trim(); }).filter(Boolean),
|
|
8929
|
+
prompt: a.prompt || 'You are ' + a.name.trim() + '. Register as "' + a.name.trim() + '" and call listen() for tasks.'
|
|
8930
|
+
};
|
|
8931
|
+
});
|
|
8932
|
+
|
|
8933
|
+
var body = {
|
|
8934
|
+
name: name,
|
|
8935
|
+
description: desc,
|
|
8936
|
+
category: category,
|
|
8937
|
+
conversation_mode: mode,
|
|
8938
|
+
agents: agents
|
|
8939
|
+
};
|
|
8940
|
+
|
|
8941
|
+
var method = existingId ? 'PUT' : 'POST';
|
|
8942
|
+
if (existingId) body.id = existingId;
|
|
8943
|
+
|
|
8944
|
+
lttFetch('/api/custom-templates' + projectParam(), {
|
|
8945
|
+
method: method,
|
|
8946
|
+
headers: { 'Content-Type': 'application/json' },
|
|
8947
|
+
body: JSON.stringify(body)
|
|
8948
|
+
}).then(function(r) { return r.json(); }).then(function(data) {
|
|
8949
|
+
if (data.error) {
|
|
8950
|
+
showToast('!', data.error);
|
|
8951
|
+
} else {
|
|
8952
|
+
showToast('✓', 'Template saved');
|
|
8953
|
+
hideTemplateBuilder();
|
|
8954
|
+
renderLaunchPanel();
|
|
8955
|
+
}
|
|
7985
8956
|
}).catch(function() {
|
|
7986
|
-
|
|
8957
|
+
showToast('!', 'Failed to save template');
|
|
7987
8958
|
});
|
|
7988
8959
|
}
|
|
7989
8960
|
|
|
@@ -8087,10 +9058,10 @@ function fillAgentFromConvTemplate(templateId, agentName) {
|
|
|
8087
9058
|
|
|
8088
9059
|
// ==================== LAUNCH SINGLE AGENT ====================
|
|
8089
9060
|
function doLaunch() {
|
|
8090
|
-
var projectDir = document.getElementById('launch-project').value.trim();
|
|
8091
9061
|
var agentName = document.getElementById('launch-name').value.trim();
|
|
8092
|
-
var prompt = document.getElementById('launch-prompt').value.trim();
|
|
8093
9062
|
var resultEl = document.getElementById('launch-result');
|
|
9063
|
+
var cliSelect = document.getElementById('launch-cli');
|
|
9064
|
+
var cli = cliSelect ? cliSelect.value : selectedCli;
|
|
8094
9065
|
|
|
8095
9066
|
if (!agentName) {
|
|
8096
9067
|
resultEl.className = 'launch-result error';
|
|
@@ -8098,9 +9069,10 @@ function doLaunch() {
|
|
|
8098
9069
|
return;
|
|
8099
9070
|
}
|
|
8100
9071
|
|
|
8101
|
-
//
|
|
8102
|
-
var launchPrompt =
|
|
9072
|
+
// Use generated prompt if available, otherwise build a default
|
|
9073
|
+
var launchPrompt = window._generatedLaunchPrompt || 'You are agent "' + agentName + '". Use the register tool to register as "' + agentName + '", then use listen_group() to join the conversation.';
|
|
8103
9074
|
navigator.clipboard.writeText(launchPrompt).catch(function() {});
|
|
9075
|
+
selectedCli = cli;
|
|
8104
9076
|
|
|
8105
9077
|
resultEl.className = 'launch-result info';
|
|
8106
9078
|
resultEl.innerHTML = 'Launching terminal for <strong>' + escapeHtml(agentName) + '</strong>...';
|
|
@@ -8108,7 +9080,7 @@ function doLaunch() {
|
|
|
8108
9080
|
lttFetch('/api/launch', {
|
|
8109
9081
|
method: 'POST',
|
|
8110
9082
|
headers: { 'Content-Type': 'application/json' },
|
|
8111
|
-
body: JSON.stringify({ cli:
|
|
9083
|
+
body: JSON.stringify({ cli: cli, project_dir: undefined, agent_name: agentName, prompt: launchPrompt })
|
|
8112
9084
|
}).then(function(r) { return r.json(); }).then(function(data) {
|
|
8113
9085
|
if (data.error) {
|
|
8114
9086
|
resultEl.className = 'launch-result error';
|
|
@@ -8125,11 +9097,18 @@ function doLaunch() {
|
|
|
8125
9097
|
startLaunchWatcher(agentName);
|
|
8126
9098
|
} else {
|
|
8127
9099
|
resultEl.className = 'launch-result success';
|
|
8128
|
-
|
|
8129
|
-
|
|
8130
|
-
|
|
8131
|
-
|
|
8132
|
-
|
|
9100
|
+
if (data.cli === 'cursor') {
|
|
9101
|
+
resultEl.innerHTML =
|
|
9102
|
+
'<strong>.cursor/mcp.json updated</strong> with <code>NEOHIVE_DATA_DIR</code>. Open this project in Cursor, reload MCP tools if needed, then paste the prompt (already copied).<br>' +
|
|
9103
|
+
'<span style="font-size:11px;color:var(--text-dim)">' + escapeHtml(data.project_dir || '') + '</span><br><br>' +
|
|
9104
|
+
'<div class="launch-prompt-box" onclick="copyText(this)" data-text="' + escapeHtml(launchPrompt).replace(/"/g, '"') + '">' + escapeHtml(launchPrompt.substring(0, 300)) + (launchPrompt.length > 300 ? '...' : '') + '</div>';
|
|
9105
|
+
} else {
|
|
9106
|
+
var html = '<strong>MCP config written.</strong> Run this command in a terminal:<br>';
|
|
9107
|
+
html += '<div class="launch-prompt-box" onclick="copyText(this)" data-text="' + escapeHtml(data.command).replace(/"/g, '"') + '">' + escapeHtml(data.command) + '</div>';
|
|
9108
|
+
html += '<br>Then paste this prompt:<br>';
|
|
9109
|
+
html += '<div class="launch-prompt-box" onclick="copyText(this)" data-text="' + escapeHtml(launchPrompt).replace(/"/g, '"') + '">' + escapeHtml(launchPrompt.substring(0, 300)) + (launchPrompt.length > 300 ? '...' : '') + '</div>';
|
|
9110
|
+
resultEl.innerHTML = html;
|
|
9111
|
+
}
|
|
8133
9112
|
startLaunchWatcher(agentName);
|
|
8134
9113
|
}
|
|
8135
9114
|
}).catch(function(err) {
|
|
@@ -8276,7 +9255,9 @@ function renderLaunchStatusTracker() {
|
|
|
8276
9255
|
var h = '<div style="font-size:11px;font-weight:600;color:var(--text-dim);text-transform:uppercase;letter-spacing:0.5px;margin-bottom:8px">Launch Status</div>';
|
|
8277
9256
|
for (var i = 0; i < _launchWatchers.length; i++) {
|
|
8278
9257
|
var w = _launchWatchers[i];
|
|
8279
|
-
var
|
|
9258
|
+
var launchingLabel = w.cli === 'cursor' ? 'Writing MCP config...' : 'Opening terminal...';
|
|
9259
|
+
var launchedLabel = w.cli === 'cursor' ? 'Open Cursor \u2014 paste prompt' : 'Terminal open \u2014 paste prompt';
|
|
9260
|
+
var stateLabel = { pending: 'Waiting...', launching: launchingLabel, launched: launchedLabel, registered: 'Online', failed: 'Failed' }[w.state] || w.state;
|
|
8280
9261
|
if (w.state === 'failed' && w.error) stateLabel = 'Failed: ' + w.error;
|
|
8281
9262
|
h += '<div class="launch-status-item">' +
|
|
8282
9263
|
'<span class="ls-dot ' + w.state + '"></span>' +
|
|
@@ -8309,8 +9290,8 @@ function renderDocs() {
|
|
|
8309
9290
|
el.innerHTML =
|
|
8310
9291
|
'<div class="docs-container">' +
|
|
8311
9292
|
|
|
8312
|
-
'<h2>Neohive
|
|
8313
|
-
'<p class="docs-subtitle">True Autonomy Engine \u2014 AI agents that self-organize, self-verify, and never stop working. Works with Claude Code, Gemini CLI, and
|
|
9293
|
+
'<h2>Neohive v6.0</h2>' +
|
|
9294
|
+
'<p class="docs-subtitle">True Autonomy Engine \u2014 AI agents that self-organize, self-verify, and never stop working. Works with Claude Code, Gemini CLI, Codex CLI, and Cursor IDE.</p>' +
|
|
8314
9295
|
|
|
8315
9296
|
// Quick Start — One Command
|
|
8316
9297
|
'<div class="docs-section">' +
|
|
@@ -8423,7 +9404,7 @@ function renderDocs() {
|
|
|
8423
9404
|
'<div class="docs-section">' +
|
|
8424
9405
|
'<h3>Frequently Asked Questions</h3>' +
|
|
8425
9406
|
'<h4>Do agents need internet access?</h4>' +
|
|
8426
|
-
'<p>No. Agents communicate through local files in your project\'s <code>.neohive/</code> folder. No cloud servers, no API calls between agents. The AI CLIs themselves may need internet for their LLM providers, but
|
|
9407
|
+
'<p>No. Agents communicate through local files in your project\'s <code>.neohive/</code> folder. No cloud servers, no API calls between agents. The AI CLIs themselves may need internet for their LLM providers, but Neohive is fully local.</p>' +
|
|
8427
9408
|
'<h4>Can I mix different AI CLIs?</h4>' +
|
|
8428
9409
|
'<p>Yes! Claude Code, Gemini CLI, and Codex CLI can all talk to each other in the same conversation. Run <code>npx neohive init --all</code> to configure all of them.</p>' +
|
|
8429
9410
|
'<h4>What happens when message files get large?</h4>' +
|
|
@@ -8461,6 +9442,316 @@ function copyLaunchPrompt() {
|
|
|
8461
9442
|
});
|
|
8462
9443
|
}
|
|
8463
9444
|
|
|
9445
|
+
// ==================== SETTINGS MENU ====================
|
|
9446
|
+
|
|
9447
|
+
function toggleSettingsMenu() {
|
|
9448
|
+
var menu = document.getElementById('settings-menu');
|
|
9449
|
+
if (menu) menu.style.display = menu.style.display === 'none' ? 'block' : 'none';
|
|
9450
|
+
}
|
|
9451
|
+
// Close settings when clicking outside
|
|
9452
|
+
document.addEventListener('click', function(e) {
|
|
9453
|
+
var menu = document.getElementById('settings-menu');
|
|
9454
|
+
if (menu && menu.style.display === 'block' && !e.target.closest('.header-settings-btn') && !e.target.closest('#settings-menu')) {
|
|
9455
|
+
menu.style.display = 'none';
|
|
9456
|
+
}
|
|
9457
|
+
});
|
|
9458
|
+
|
|
9459
|
+
// ==================== SIDEBAR TOGGLE ====================
|
|
9460
|
+
|
|
9461
|
+
function toggleNavSidebar() {
|
|
9462
|
+
var sidebar = document.getElementById('nav-sidebar');
|
|
9463
|
+
if (!sidebar) return;
|
|
9464
|
+
var isExpanded = sidebar.classList.contains('expanded');
|
|
9465
|
+
sidebar.classList.toggle('expanded');
|
|
9466
|
+
sidebar.classList.toggle('collapsed');
|
|
9467
|
+
var btn = sidebar.querySelector('.nav-sidebar-toggle');
|
|
9468
|
+
if (btn) btn.innerHTML = isExpanded ? '▶' : '◀';
|
|
9469
|
+
try { localStorage.setItem('neohive-sidebar-collapsed', isExpanded ? '1' : '0'); } catch(e) {}
|
|
9470
|
+
}
|
|
9471
|
+
// Restore sidebar state from localStorage
|
|
9472
|
+
(function() {
|
|
9473
|
+
try {
|
|
9474
|
+
var saved = localStorage.getItem('neohive-sidebar-collapsed');
|
|
9475
|
+
if (saved === '1') {
|
|
9476
|
+
var sidebar = document.getElementById('nav-sidebar');
|
|
9477
|
+
if (sidebar) {
|
|
9478
|
+
sidebar.classList.remove('expanded');
|
|
9479
|
+
sidebar.classList.add('collapsed');
|
|
9480
|
+
var btn = sidebar.querySelector('.nav-sidebar-toggle');
|
|
9481
|
+
if (btn) btn.innerHTML = '▶';
|
|
9482
|
+
}
|
|
9483
|
+
}
|
|
9484
|
+
} catch(e) {}
|
|
9485
|
+
})();
|
|
9486
|
+
|
|
9487
|
+
// ==================== AGENT STATUS BAR ====================
|
|
9488
|
+
|
|
9489
|
+
var prevAgentBarState = {};
|
|
9490
|
+
|
|
9491
|
+
function updateAgentBar(agents) {
|
|
9492
|
+
var bar = document.getElementById('agent-bar');
|
|
9493
|
+
if (!bar || !agents) return;
|
|
9494
|
+
var html = '';
|
|
9495
|
+
var sorted = Object.entries(agents).sort(function(a, b) {
|
|
9496
|
+
var aAlive = a[1].alive ? 1 : 0;
|
|
9497
|
+
var bAlive = b[1].alive ? 1 : 0;
|
|
9498
|
+
return bAlive - aAlive;
|
|
9499
|
+
});
|
|
9500
|
+
for (var i = 0; i < sorted.length; i++) {
|
|
9501
|
+
var name = sorted[i][0];
|
|
9502
|
+
var agent = sorted[i][1];
|
|
9503
|
+
if (name === '__system__' || name === 'Dashboard') continue;
|
|
9504
|
+
var cls = 'offline';
|
|
9505
|
+
var activity = 'offline';
|
|
9506
|
+
if (agent.alive) {
|
|
9507
|
+
if (agent.is_listening || agent.status === 'listening') { cls = 'listening'; activity = 'listening'; }
|
|
9508
|
+
else if (agent.status === 'idle') { cls = 'idle'; activity = 'idle'; }
|
|
9509
|
+
else { cls = 'working'; activity = agent.status === 'working' ? 'working' : 'active'; }
|
|
9510
|
+
}
|
|
9511
|
+
// Toast on state change
|
|
9512
|
+
var prevState = prevAgentBarState[name];
|
|
9513
|
+
if (prevState !== undefined) {
|
|
9514
|
+
if (!prevState && agent.alive) showToast('<span style="color:var(--green)">+</span>', name + ' came online');
|
|
9515
|
+
if (prevState && !agent.alive) showToast('<span style="color:var(--text-muted)">-</span>', name + ' went offline');
|
|
9516
|
+
}
|
|
9517
|
+
prevAgentBarState[name] = agent.alive;
|
|
9518
|
+
html += '<div class="agent-pill ' + cls + '" onclick="showAgentPopup(\'' + name + '\')">' +
|
|
9519
|
+
'<span class="status-dot"></span>' +
|
|
9520
|
+
'<span class="agent-name">' + name + '</span>' +
|
|
9521
|
+
'<span class="agent-activity">' + activity + '</span>' +
|
|
9522
|
+
'</div>';
|
|
9523
|
+
}
|
|
9524
|
+
if (bar.innerHTML !== html) bar.innerHTML = html;
|
|
9525
|
+
}
|
|
9526
|
+
|
|
9527
|
+
// ==================== OVERVIEW PAGE ====================
|
|
9528
|
+
|
|
9529
|
+
function renderOverview() {
|
|
9530
|
+
var area = document.getElementById('overview-area');
|
|
9531
|
+
if (!area) return;
|
|
9532
|
+
var savedScroll = area.scrollTop;
|
|
9533
|
+
var pq = projectParam();
|
|
9534
|
+
var histQ = pq + (pq ? '&' : '?') + 'limit=5' + (activeBranch && activeBranch !== 'main' ? '&branch=' + encodeURIComponent(activeBranch) : '');
|
|
9535
|
+
Promise.all([
|
|
9536
|
+
lttFetch('/api/status' + pq).then(function(r) { return r.json(); }),
|
|
9537
|
+
lttFetch('/api/agents' + pq).then(function(r) { return r.json(); }),
|
|
9538
|
+
lttFetch('/api/history' + histQ).then(function(r) { return r.json(); }),
|
|
9539
|
+
lttFetch('/api/tasks' + pq).then(function(r) { return r.json(); }),
|
|
9540
|
+
lttFetch('/api/workflows' + pq).then(function(r) { return r.json(); }),
|
|
9541
|
+
lttFetch('/api/notifications' + pq + (pq ? '&' : '?') + 'limit=10').then(function(r) { return r.json(); }).catch(function() { return []; })
|
|
9542
|
+
]).then(function(results) {
|
|
9543
|
+
var status = results[0];
|
|
9544
|
+
var agents = results[1];
|
|
9545
|
+
var history = Array.isArray(results[2]) ? results[2] : (results[2].messages || []);
|
|
9546
|
+
var tasks = Array.isArray(results[3]) ? results[3] : [];
|
|
9547
|
+
var workflows = Array.isArray(results[4]) ? results[4] : [];
|
|
9548
|
+
var notifications = Array.isArray(results[5]) ? results[5] : [];
|
|
9549
|
+
|
|
9550
|
+
var agentEntries = Object.entries(agents).filter(function(e) { return e[0] !== '__system__'; });
|
|
9551
|
+
|
|
9552
|
+
// Data hash to skip unnecessary rebuilds (excludes timestamps)
|
|
9553
|
+
var overviewHash = (status.aliveCount || 0) + ':' + (status.messageCount || 0) + ':' + agentEntries.length + ':' + tasks.map(function(t) { return t.status; }).join(',') + ':' + workflows.map(function(w) { return w.status; }).join(',') + ':' + history.length + ':' + notifications.length + ':b:' + (activeBranch || 'main');
|
|
9554
|
+
if (overviewHash === renderOverview._lastHash) { return; }
|
|
9555
|
+
renderOverview._lastHash = overviewHash;
|
|
9556
|
+
|
|
9557
|
+
var pending = tasks.filter(function(t) { return t.status === 'pending'; }).length;
|
|
9558
|
+
var inProgress = tasks.filter(function(t) { return t.status === 'in_progress'; }).length;
|
|
9559
|
+
var done = tasks.filter(function(t) { return t.status === 'done'; }).length;
|
|
9560
|
+
var taskPct = tasks.length > 0 ? Math.round((done / tasks.length) * 100) : 0;
|
|
9561
|
+
var activeWfs = workflows.filter(function(w) { return w.status === 'active'; });
|
|
9562
|
+
var hasAgents = agentEntries.length > 0;
|
|
9563
|
+
var hasMessages = history.length > 0;
|
|
9564
|
+
var hasWorkflows = activeWfs.length > 0;
|
|
9565
|
+
var hasTasks = tasks.length > 0;
|
|
9566
|
+
|
|
9567
|
+
var html = '';
|
|
9568
|
+
|
|
9569
|
+
// Welcome header with coordinator mode toggle
|
|
9570
|
+
var coordMode = status.coordinator_mode || 'responsive';
|
|
9571
|
+
html += '<div class="overview-welcome" style="display:flex;justify-content:space-between;align-items:flex-start">';
|
|
9572
|
+
html += '<div><h2>Dashboard</h2>';
|
|
9573
|
+
html += '<p>' + (status.conversation_mode === 'managed' ? 'Managed mode' : 'Direct mode') + ' · ' + (status.agentCount || 0) + ' agents registered</p></div>';
|
|
9574
|
+
html += '<div style="display:flex;align-items:center;gap:8px;padding-top:4px">';
|
|
9575
|
+
html += '<span style="font-size:12px;color:var(--text-muted)">Coordinator</span>';
|
|
9576
|
+
html += '<div style="display:flex;border:1px solid var(--border);border-radius:8px;overflow:hidden;font-size:12px">';
|
|
9577
|
+
html += '<button onclick="setCoordinatorMode(\'responsive\')" style="padding:5px 12px;border:none;cursor:pointer;font-size:12px;font-weight:' + (coordMode === 'responsive' ? '600' : '400') + ';background:' + (coordMode === 'responsive' ? 'var(--accent-dim)' : 'var(--surface-2)') + ';color:' + (coordMode === 'responsive' ? 'var(--accent)' : 'var(--text-muted)') + ';transition:all 0.15s">Stay with me</button>';
|
|
9578
|
+
html += '<button onclick="setCoordinatorMode(\'autonomous\')" style="padding:5px 12px;border:none;border-left:1px solid var(--border);cursor:pointer;font-size:12px;font-weight:' + (coordMode === 'autonomous' ? '600' : '400') + ';background:' + (coordMode === 'autonomous' ? 'var(--green-dim)' : 'var(--surface-2)') + ';color:' + (coordMode === 'autonomous' ? 'var(--green)' : 'var(--text-muted)') + ';transition:all 0.15s">Run autonomously</button>';
|
|
9579
|
+
html += '</div></div>';
|
|
9580
|
+
html += '</div>';
|
|
9581
|
+
|
|
9582
|
+
// Metric cards with icons
|
|
9583
|
+
html += '<div class="overview-metrics">';
|
|
9584
|
+
html += '<div class="metric-card">';
|
|
9585
|
+
html += '<div class="metric-icon" style="background:var(--green-dim)"><svg viewBox="0 0 16 16" width="18" height="18" fill="none" stroke="var(--green)" stroke-width="1.5"><circle cx="8" cy="5" r="3"/><path d="M2 14c0-3.3 2.7-6 6-6s6 2.7 6 6"/></svg></div>';
|
|
9586
|
+
html += '<div class="metric-label">Active Agents</div>';
|
|
9587
|
+
html += '<div class="metric-value">' + (status.aliveCount || 0) + '</div>';
|
|
9588
|
+
html += '<div class="metric-sub">' + (status.sleepingCount || 0) + ' sleeping</div>';
|
|
9589
|
+
html += '</div>';
|
|
9590
|
+
|
|
9591
|
+
html += '<div class="metric-card">';
|
|
9592
|
+
html += '<div class="metric-icon" style="background:var(--accent-dim)"><svg viewBox="0 0 16 16" width="18" height="18" fill="none" stroke="var(--accent)" stroke-width="1.5"><path d="M2 3h12v8H6l-3 2.5V11H2z" stroke-linejoin="round"/></svg></div>';
|
|
9593
|
+
html += '<div class="metric-label">Messages</div>';
|
|
9594
|
+
html += '<div class="metric-value">' + (status.messageCount || 0) + '</div>';
|
|
9595
|
+
html += '<div class="metric-sub">' + (status.threadCount || 0) + ' threads</div>';
|
|
9596
|
+
html += '</div>';
|
|
9597
|
+
|
|
9598
|
+
html += '<div class="metric-card">';
|
|
9599
|
+
html += '<div class="metric-icon" style="background:' + (taskPct === 100 ? 'var(--green-dim)' : 'var(--purple-dim)') + '"><svg viewBox="0 0 16 16" width="18" height="18" fill="none" stroke="' + (taskPct === 100 ? 'var(--green)' : 'var(--purple)') + '" stroke-width="1.5"><rect x="2" y="2" width="12" height="12" rx="2"/><path d="M5 8l2 2 4-4"/></svg></div>';
|
|
9600
|
+
html += '<div class="metric-label">Task Progress</div>';
|
|
9601
|
+
html += '<div class="metric-value">' + taskPct + '<span style="font-size:18px;font-weight:600;color:var(--text-muted);margin-left:2px">%</span></div>';
|
|
9602
|
+
html += '<div class="metric-sub">' + done + ' done, ' + inProgress + ' active, ' + pending + ' pending</div>';
|
|
9603
|
+
html += '</div>';
|
|
9604
|
+
html += '</div>';
|
|
9605
|
+
|
|
9606
|
+
if (!hasAgents && !hasMessages && !hasWorkflows && !hasTasks) {
|
|
9607
|
+
// Empty state with branded feel
|
|
9608
|
+
html += '<div style="display:flex;flex-direction:column;align-items:center;justify-content:center;padding:60px 20px;text-align:center;background:var(--surface);border:1px solid var(--border);border-radius:16px">';
|
|
9609
|
+
html += '<div style="width:64px;height:64px;border-radius:16px;background:var(--accent-dim);display:flex;align-items:center;justify-content:center;margin-bottom:20px"><svg viewBox="0 0 16 16" width="28" height="28" fill="none" stroke="var(--accent)" stroke-width="1.2"><path d="M8 1.5L14 5v6l-6 3.5L2 11V5z"/></svg></div>';
|
|
9610
|
+
html += '<div style="font-size:18px;font-weight:700;color:var(--text);margin-bottom:6px">No agents online yet</div>';
|
|
9611
|
+
html += '<div style="font-size:13px;color:var(--text-muted);max-width:360px;line-height:1.6;margin-bottom:4px">Open a CLI terminal and register an agent to start collaborating.</div>';
|
|
9612
|
+
html += '<button class="overview-launch-btn" onclick="switchView(\'launch\')"><svg viewBox="0 0 16 16" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2"><path d="M8 2v12M2 8h12"/></svg> Launch Agents</button>';
|
|
9613
|
+
html += '</div>';
|
|
9614
|
+
} else {
|
|
9615
|
+
html += '<div class="overview-grid">';
|
|
9616
|
+
|
|
9617
|
+
// Active agents panel
|
|
9618
|
+
if (hasAgents) {
|
|
9619
|
+
html += '<div class="overview-panel"><h3>Agents <a onclick="switchView(\'messages\')">View messages →</a></h3>';
|
|
9620
|
+
for (var i = 0; i < agentEntries.length; i++) {
|
|
9621
|
+
var aName = agentEntries[i][0];
|
|
9622
|
+
var aInfo = agentEntries[i][1];
|
|
9623
|
+
var dotColor = !aInfo.alive ? '#6b7280' : aInfo.is_listening ? '#3b82f6' : aInfo.status === 'idle' ? '#f59e0b' : '#22c55e';
|
|
9624
|
+
var dotGlow = !aInfo.alive ? '' : ';box-shadow:0 0 6px ' + dotColor;
|
|
9625
|
+
var statusText = !aInfo.alive ? 'offline' : aInfo.is_listening ? 'listening' : aInfo.status === 'idle' ? 'idle' : 'working';
|
|
9626
|
+
html += '<div class="overview-agent-row"><span class="overview-agent-dot" style="background:' + dotColor + dotGlow + '"></span><span class="overview-agent-name">' + aName + '</span><span class="overview-agent-status">' + statusText + ' · ' + (aInfo.provider || '?') + '</span></div>';
|
|
9627
|
+
}
|
|
9628
|
+
html += '</div>';
|
|
9629
|
+
}
|
|
9630
|
+
|
|
9631
|
+
// Workflow progress panel
|
|
9632
|
+
if (hasWorkflows) {
|
|
9633
|
+
html += '<div class="overview-panel"><h3>Workflows <a onclick="switchView(\'workflows\')">View all →</a></h3>';
|
|
9634
|
+
for (var w = 0; w < activeWfs.length; w++) {
|
|
9635
|
+
var wf = activeWfs[w];
|
|
9636
|
+
var wfDone = wf.steps ? wf.steps.filter(function(s) { return s.status === 'done'; }).length : 0;
|
|
9637
|
+
var wfTotal = wf.steps ? wf.steps.length : 1;
|
|
9638
|
+
var wfPct = Math.round((wfDone / wfTotal) * 100);
|
|
9639
|
+
html += '<div style="margin-bottom:12px"><div style="display:flex;justify-content:space-between;align-items:baseline;margin-bottom:6px"><span style="font-size:13px;font-weight:600;color:var(--text)">' + (wf.name || 'Workflow') + '</span><span style="font-size:12px;color:var(--text-muted)">' + wfPct + '<span style="font-size:0.8em;margin-left:1px">%</span></span></div>';
|
|
9640
|
+
html += '<div style="height:6px;background:var(--surface-2);border-radius:3px;overflow:hidden"><div style="height:100%;width:' + wfPct + '%;background:var(--gradient-accent);border-radius:3px;transition:width 0.3s"></div></div></div>';
|
|
9641
|
+
}
|
|
9642
|
+
html += '</div>';
|
|
9643
|
+
}
|
|
9644
|
+
|
|
9645
|
+
// Task summary
|
|
9646
|
+
if (hasTasks && !hasWorkflows) {
|
|
9647
|
+
html += '<div class="overview-panel"><h3>Tasks <a onclick="switchView(\'tasks\')">View board →</a></h3>';
|
|
9648
|
+
html += '<div class="overview-task-summary">';
|
|
9649
|
+
if (pending > 0) html += '<span class="overview-task-badge pending">' + pending + ' pending</span>';
|
|
9650
|
+
if (inProgress > 0) html += '<span class="overview-task-badge active">' + inProgress + ' active</span>';
|
|
9651
|
+
if (done > 0) html += '<span class="overview-task-badge done">' + done + ' done</span>';
|
|
9652
|
+
html += '</div>';
|
|
9653
|
+
html += '</div>';
|
|
9654
|
+
}
|
|
9655
|
+
|
|
9656
|
+
// Recent messages
|
|
9657
|
+
if (hasMessages) {
|
|
9658
|
+
html += '<div class="overview-panel overview-full-width"><h3>Recent Activity <a onclick="switchView(\'messages\')">View all →</a></h3>';
|
|
9659
|
+
for (var m = 0; m < history.length; m++) {
|
|
9660
|
+
var msg = history[m];
|
|
9661
|
+
var time = new Date(msg.timestamp);
|
|
9662
|
+
var timeStr = time.getHours() + ':' + String(time.getMinutes()).padStart(2, '0');
|
|
9663
|
+
var preview = (msg.content || '').replace(/[#*`_~\[\]]/g, '').substring(0, 100);
|
|
9664
|
+
html += '<div class="overview-msg-row"><span class="overview-msg-from">' + (msg.from || '?') + '</span><span class="overview-msg-text">' + preview + '</span><span class="overview-msg-time">' + timeStr + '</span></div>';
|
|
9665
|
+
}
|
|
9666
|
+
html += '</div>';
|
|
9667
|
+
}
|
|
9668
|
+
|
|
9669
|
+
// Task summary (if workflows already shown, put tasks as full width)
|
|
9670
|
+
if (hasTasks && hasWorkflows) {
|
|
9671
|
+
html += '<div class="overview-panel overview-full-width"><h3>Tasks <a onclick="switchView(\'tasks\')">View board →</a></h3>';
|
|
9672
|
+
html += '<div class="overview-task-summary">';
|
|
9673
|
+
if (pending > 0) html += '<span class="overview-task-badge pending">' + pending + ' pending</span>';
|
|
9674
|
+
if (inProgress > 0) html += '<span class="overview-task-badge active">' + inProgress + ' active</span>';
|
|
9675
|
+
if (done > 0) html += '<span class="overview-task-badge done">' + done + ' done</span>';
|
|
9676
|
+
html += '</div>';
|
|
9677
|
+
html += '</div>';
|
|
9678
|
+
}
|
|
9679
|
+
|
|
9680
|
+
html += '</div>';
|
|
9681
|
+
}
|
|
9682
|
+
|
|
9683
|
+
// Notifications feed (always show if there are recent notifications)
|
|
9684
|
+
if (notifications.length > 0) {
|
|
9685
|
+
var NOTIF_ICONS = { task_done: '✓', workflow_advanced: '▶', agent_online: '+', agent_offline: '-', approval_needed: '!' };
|
|
9686
|
+
var NOTIF_COLORS = { task_done: 'var(--green)', workflow_advanced: 'var(--accent)', agent_online: 'var(--green)', agent_offline: 'var(--text-muted)', approval_needed: 'var(--orange)' };
|
|
9687
|
+
html += '<div style="background:var(--surface);border:1px solid var(--border);border-radius:12px;padding:16px 20px">';
|
|
9688
|
+
html += '<div style="font-size:14px;font-weight:600;color:var(--text);margin-bottom:12px">Recent Notifications</div>';
|
|
9689
|
+
var recent = notifications.slice(-8).reverse();
|
|
9690
|
+
for (var ni = 0; ni < recent.length; ni++) {
|
|
9691
|
+
var n = recent[ni];
|
|
9692
|
+
var ntime = new Date(n.timestamp);
|
|
9693
|
+
var ntimeStr = ntime.getHours() + ':' + String(ntime.getMinutes()).padStart(2, '0');
|
|
9694
|
+
var ncolor = NOTIF_COLORS[n.type] || 'var(--text-muted)';
|
|
9695
|
+
var nicon = NOTIF_ICONS[n.type] || '•';
|
|
9696
|
+
html += '<div style="display:flex;align-items:center;gap:10px;padding:6px 0;border-bottom:1px solid var(--border);font-size:13px">';
|
|
9697
|
+
html += '<span style="width:22px;height:22px;border-radius:6px;background:' + ncolor + '15;color:' + ncolor + ';display:flex;align-items:center;justify-content:center;font-size:11px;font-weight:700;flex-shrink:0">' + nicon + '</span>';
|
|
9698
|
+
html += '<span style="flex:1;color:var(--text-dim)">' + (n.summary || n.type) + '</span>';
|
|
9699
|
+
html += '<span style="font-size:11px;color:var(--text-muted);flex-shrink:0">' + ntimeStr + '</span>';
|
|
9700
|
+
html += '</div>';
|
|
9701
|
+
}
|
|
9702
|
+
html += '</div>';
|
|
9703
|
+
}
|
|
9704
|
+
|
|
9705
|
+
if (area.innerHTML !== html) {
|
|
9706
|
+
area.innerHTML = html;
|
|
9707
|
+
area.scrollTop = savedScroll;
|
|
9708
|
+
}
|
|
9709
|
+
}).catch(function(e) {
|
|
9710
|
+
area.innerHTML = '<div style="padding:28px 32px;color:var(--text-muted)">Failed to load overview: ' + e.message + '</div>';
|
|
9711
|
+
});
|
|
9712
|
+
}
|
|
9713
|
+
|
|
9714
|
+
// ==================== COORDINATOR MODE ====================
|
|
9715
|
+
|
|
9716
|
+
function setCoordinatorMode(mode) {
|
|
9717
|
+
lttFetch('/api/coordinator-mode', {
|
|
9718
|
+
method: 'POST',
|
|
9719
|
+
headers: { 'Content-Type': 'application/json' },
|
|
9720
|
+
body: JSON.stringify({ mode: mode })
|
|
9721
|
+
}).then(function(r) { return r.json(); }).then(function(data) {
|
|
9722
|
+
if (data.error) {
|
|
9723
|
+
showToast('!', 'Failed: ' + data.error);
|
|
9724
|
+
} else {
|
|
9725
|
+
showToast('✓', 'Coordinator mode: ' + (mode === 'responsive' ? 'Stay with me' : 'Run autonomously'));
|
|
9726
|
+
renderOverview();
|
|
9727
|
+
}
|
|
9728
|
+
}).catch(function() {
|
|
9729
|
+
showToast('!', 'Failed to change coordinator mode');
|
|
9730
|
+
});
|
|
9731
|
+
}
|
|
9732
|
+
|
|
9733
|
+
// ==================== TOAST NOTIFICATIONS ====================
|
|
9734
|
+
|
|
9735
|
+
var toastQueue = [];
|
|
9736
|
+
function showToast(icon, text) {
|
|
9737
|
+
var container = document.getElementById('toast-container');
|
|
9738
|
+
if (!container) return;
|
|
9739
|
+
// Max 3 toasts
|
|
9740
|
+
while (container.children.length >= 3) {
|
|
9741
|
+
container.removeChild(container.firstChild);
|
|
9742
|
+
}
|
|
9743
|
+
var toast = document.createElement('div');
|
|
9744
|
+
var isError = icon === '!' || text.toLowerCase().indexOf('failed') !== -1 || text.toLowerCase().indexOf('error') !== -1;
|
|
9745
|
+
var isSuccess = icon.indexOf('2713') !== -1 || icon.indexOf('+') !== -1;
|
|
9746
|
+
toast.className = 'toast' + (isError ? ' toast-error' : isSuccess ? ' toast-success' : '');
|
|
9747
|
+
toast.innerHTML = '<span class="toast-icon">' + icon + '</span><span class="toast-text">' + text + '</span>';
|
|
9748
|
+
container.appendChild(toast);
|
|
9749
|
+
setTimeout(function() {
|
|
9750
|
+
toast.classList.add('leaving');
|
|
9751
|
+
setTimeout(function() { if (toast.parentNode) toast.parentNode.removeChild(toast); }, 300);
|
|
9752
|
+
}, 5000);
|
|
9753
|
+
}
|
|
9754
|
+
|
|
8464
9755
|
// ==================== SSE + POLLING ====================
|
|
8465
9756
|
|
|
8466
9757
|
var sseConnected = false;
|
|
@@ -8470,19 +9761,16 @@ var ssePollFallback = null;
|
|
|
8470
9761
|
|
|
8471
9762
|
function setConnStatus(status) {
|
|
8472
9763
|
var dot = document.querySelector('.conn-dot');
|
|
8473
|
-
|
|
9764
|
+
if (!dot) return;
|
|
8474
9765
|
if (status === 'live') {
|
|
8475
9766
|
dot.style.background = 'var(--green)';
|
|
8476
9767
|
dot.style.animation = 'pulse 2s infinite';
|
|
8477
|
-
label.textContent = 'Live (SSE)';
|
|
8478
9768
|
} else if (status === 'reconnecting') {
|
|
8479
9769
|
dot.style.background = 'var(--yellow, #f0ad4e)';
|
|
8480
9770
|
dot.style.animation = 'pulse 0.8s infinite';
|
|
8481
|
-
label.textContent = 'Reconnecting...';
|
|
8482
9771
|
} else if (status === 'poll') {
|
|
8483
9772
|
dot.style.background = 'var(--green)';
|
|
8484
9773
|
dot.style.animation = 'pulse 2s infinite';
|
|
8485
|
-
label.textContent = 'Live (poll)';
|
|
8486
9774
|
}
|
|
8487
9775
|
}
|
|
8488
9776
|
|
|
@@ -8501,10 +9789,17 @@ function initSSE() {
|
|
|
8501
9789
|
if (needMessages || needAgents) {
|
|
8502
9790
|
// Full poll for messages/agents (they're coupled in rendering)
|
|
8503
9791
|
poll();
|
|
8504
|
-
|
|
8505
|
-
|
|
8506
|
-
|
|
8507
|
-
|
|
9792
|
+
// Also refresh overview if currently viewing it
|
|
9793
|
+
if (activeView === 'overview') renderOverview();
|
|
9794
|
+
}
|
|
9795
|
+
// Targeted fetch for tasks/workflows — refresh on any view that needs them
|
|
9796
|
+
if (needTasks) {
|
|
9797
|
+
if (activeView === 'tasks') fetchTasks();
|
|
9798
|
+
else if (activeView === 'overview') renderOverview();
|
|
9799
|
+
}
|
|
9800
|
+
if (needWorkflows) {
|
|
9801
|
+
if (activeView === 'workflows') fetchWorkflows();
|
|
9802
|
+
else if (activeView === 'overview') renderOverview();
|
|
8508
9803
|
}
|
|
8509
9804
|
};
|
|
8510
9805
|
eventSource.onopen = function() {
|
|
@@ -8556,16 +9851,31 @@ loadProjects().then(function() {
|
|
|
8556
9851
|
loadConversationList();
|
|
8557
9852
|
poll();
|
|
8558
9853
|
initSSE();
|
|
8559
|
-
switchView(
|
|
9854
|
+
switchView(activeView);
|
|
8560
9855
|
}).catch(function(e) {
|
|
8561
9856
|
console.error('[LTT] init: loadProjects failed, polling anyway:', e);
|
|
8562
9857
|
poll();
|
|
8563
9858
|
initSSE();
|
|
8564
|
-
switchView(
|
|
9859
|
+
switchView(activeView);
|
|
8565
9860
|
});
|
|
8566
9861
|
// Safety-net poll at 10s (SSE handles real-time, this catches any missed updates)
|
|
8567
9862
|
setInterval(poll, 10000);
|
|
8568
9863
|
</script>
|
|
8569
9864
|
|
|
9865
|
+
<!-- Workflow Detail Popup -->
|
|
9866
|
+
<div class="phone-modal-overlay" id="wf-detail-overlay" onclick="if(event.target===this)closeWfDetail()">
|
|
9867
|
+
<div class="phone-modal" style="width:620px;text-align:left;max-height:85vh;overflow-y:auto">
|
|
9868
|
+
<button class="phone-modal-close" onclick="closeWfDetail()">×</button>
|
|
9869
|
+
<div id="wf-detail-content"></div>
|
|
9870
|
+
</div>
|
|
9871
|
+
</div>
|
|
9872
|
+
<!-- Task Detail Popup -->
|
|
9873
|
+
<div class="phone-modal-overlay" id="task-detail-overlay" onclick="if(event.target===this)closeTaskDetail()">
|
|
9874
|
+
<div class="phone-modal" style="width:520px;text-align:left;max-height:80vh;overflow-y:auto">
|
|
9875
|
+
<button class="phone-modal-close" onclick="closeTaskDetail()">×</button>
|
|
9876
|
+
<div id="task-detail-content"></div>
|
|
9877
|
+
</div>
|
|
9878
|
+
</div>
|
|
9879
|
+
<div class="toast-container" id="toast-container"></div>
|
|
8570
9880
|
</body>
|
|
8571
9881
|
</html>
|