let-them-talk 3.3.3 → 3.4.1
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 +68 -0
- package/LICENSE +1 -1
- package/README.md +17 -9
- package/cli.js +103 -2
- package/dashboard.html +693 -125
- package/dashboard.js +373 -7
- package/package.json +2 -1
- package/server.js +80 -4
package/dashboard.html
CHANGED
|
@@ -5,55 +5,72 @@
|
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
6
|
<title>Let Them Talk</title>
|
|
7
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='%2358a6ff'/><circle cx='38' cy='42' r='5' fill='%230d1117'/><circle cx='55' cy='42' r='5' fill='%230d1117'/></svg>">
|
|
8
|
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
9
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
10
|
+
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet">
|
|
8
11
|
<style>
|
|
9
12
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
10
13
|
|
|
11
14
|
:root {
|
|
12
|
-
--bg: #
|
|
13
|
-
--surface: #
|
|
14
|
-
--surface-2: #
|
|
15
|
-
--surface-3: #
|
|
16
|
-
--border:
|
|
17
|
-
--border-light:
|
|
18
|
-
--text: #
|
|
19
|
-
--text-dim: #
|
|
20
|
-
--text-muted: #
|
|
21
|
-
--accent: #
|
|
22
|
-
--accent-dim: rgba(
|
|
23
|
-
--
|
|
24
|
-
--green
|
|
25
|
-
--
|
|
26
|
-
--red
|
|
27
|
-
--
|
|
28
|
-
--orange
|
|
29
|
-
--
|
|
30
|
-
--purple
|
|
31
|
-
--
|
|
15
|
+
--bg: #080b12;
|
|
16
|
+
--surface: #0f1318;
|
|
17
|
+
--surface-2: #161c24;
|
|
18
|
+
--surface-3: #1e2530;
|
|
19
|
+
--border: rgba(255, 255, 255, 0.06);
|
|
20
|
+
--border-light: rgba(255, 255, 255, 0.1);
|
|
21
|
+
--text: #f0f4f8;
|
|
22
|
+
--text-dim: #94a3b8;
|
|
23
|
+
--text-muted: #5a6578;
|
|
24
|
+
--accent: #6c8aff;
|
|
25
|
+
--accent-dim: rgba(108, 138, 255, 0.12);
|
|
26
|
+
--accent-glow: rgba(108, 138, 255, 0.25);
|
|
27
|
+
--green: #34d399;
|
|
28
|
+
--green-dim: rgba(52, 211, 153, 0.12);
|
|
29
|
+
--red: #f87171;
|
|
30
|
+
--red-dim: rgba(248, 113, 113, 0.12);
|
|
31
|
+
--orange: #fbbf24;
|
|
32
|
+
--orange-dim: rgba(251, 191, 36, 0.12);
|
|
33
|
+
--purple: #c084fc;
|
|
34
|
+
--purple-dim: rgba(192, 132, 252, 0.12);
|
|
35
|
+
--yellow: #fbbf24;
|
|
32
36
|
--sidebar-w: 280px;
|
|
33
37
|
--header-h: 56px;
|
|
38
|
+
--glow: 0 0 20px rgba(108, 138, 255, 0.08);
|
|
39
|
+
--shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.3);
|
|
40
|
+
--shadow-md: 0 4px 12px rgba(0, 0, 0, 0.4);
|
|
41
|
+
--shadow-lg: 0 8px 32px rgba(0, 0, 0, 0.5);
|
|
42
|
+
--gradient-accent: linear-gradient(135deg, #6c8aff, #a78bfa);
|
|
43
|
+
--gradient-surface: linear-gradient(180deg, rgba(255,255,255,0.03) 0%, transparent 100%);
|
|
34
44
|
}
|
|
35
45
|
|
|
36
46
|
[data-theme="light"] {
|
|
37
|
-
--bg: #
|
|
47
|
+
--bg: #f0f2f5;
|
|
38
48
|
--surface: #ffffff;
|
|
39
|
-
--surface-2: #
|
|
40
|
-
--surface-3: #
|
|
41
|
-
--border:
|
|
42
|
-
--border-light:
|
|
43
|
-
--text: #
|
|
44
|
-
--text-dim: #
|
|
45
|
-
--text-muted: #
|
|
46
|
-
--accent: #
|
|
47
|
-
--accent-dim: rgba(
|
|
48
|
-
--
|
|
49
|
-
--green
|
|
50
|
-
--
|
|
51
|
-
--red
|
|
52
|
-
--
|
|
53
|
-
--orange
|
|
54
|
-
--
|
|
55
|
-
--purple
|
|
56
|
-
--
|
|
49
|
+
--surface-2: #f7f8fa;
|
|
50
|
+
--surface-3: #eef0f4;
|
|
51
|
+
--border: rgba(0, 0, 0, 0.08);
|
|
52
|
+
--border-light: rgba(0, 0, 0, 0.12);
|
|
53
|
+
--text: #111827;
|
|
54
|
+
--text-dim: #4b5563;
|
|
55
|
+
--text-muted: #9ca3af;
|
|
56
|
+
--accent: #4f6eff;
|
|
57
|
+
--accent-dim: rgba(79, 110, 255, 0.08);
|
|
58
|
+
--accent-glow: rgba(79, 110, 255, 0.15);
|
|
59
|
+
--green: #059669;
|
|
60
|
+
--green-dim: rgba(5, 150, 105, 0.08);
|
|
61
|
+
--red: #dc2626;
|
|
62
|
+
--red-dim: rgba(220, 38, 38, 0.08);
|
|
63
|
+
--orange: #d97706;
|
|
64
|
+
--orange-dim: rgba(217, 119, 6, 0.08);
|
|
65
|
+
--purple: #7c3aed;
|
|
66
|
+
--purple-dim: rgba(124, 58, 237, 0.08);
|
|
67
|
+
--yellow: #d97706;
|
|
68
|
+
--glow: 0 0 20px rgba(79, 110, 255, 0.05);
|
|
69
|
+
--shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.08);
|
|
70
|
+
--shadow-md: 0 4px 12px rgba(0, 0, 0, 0.1);
|
|
71
|
+
--shadow-lg: 0 8px 32px rgba(0, 0, 0, 0.12);
|
|
72
|
+
--gradient-accent: linear-gradient(135deg, #4f6eff, #7c3aed);
|
|
73
|
+
--gradient-surface: linear-gradient(180deg, rgba(0,0,0,0.01) 0%, transparent 100%);
|
|
57
74
|
}
|
|
58
75
|
|
|
59
76
|
.theme-toggle {
|
|
@@ -84,16 +101,20 @@
|
|
|
84
101
|
}
|
|
85
102
|
|
|
86
103
|
body {
|
|
87
|
-
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif;
|
|
104
|
+
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif;
|
|
88
105
|
background: var(--bg);
|
|
89
106
|
color: var(--text);
|
|
90
107
|
min-height: 100vh;
|
|
91
108
|
overflow: hidden;
|
|
109
|
+
-webkit-font-smoothing: antialiased;
|
|
110
|
+
-moz-osx-font-smoothing: grayscale;
|
|
92
111
|
}
|
|
93
112
|
|
|
94
113
|
/* ===== HEADER ===== */
|
|
95
114
|
.header {
|
|
96
|
-
background:
|
|
115
|
+
background: rgba(15, 19, 24, 0.8);
|
|
116
|
+
backdrop-filter: blur(16px) saturate(180%);
|
|
117
|
+
-webkit-backdrop-filter: blur(16px) saturate(180%);
|
|
97
118
|
border-bottom: 1px solid var(--border);
|
|
98
119
|
padding: 0 20px;
|
|
99
120
|
height: var(--header-h);
|
|
@@ -107,6 +128,10 @@
|
|
|
107
128
|
z-index: 100;
|
|
108
129
|
}
|
|
109
130
|
|
|
131
|
+
[data-theme="light"] .header {
|
|
132
|
+
background: rgba(255, 255, 255, 0.85);
|
|
133
|
+
}
|
|
134
|
+
|
|
110
135
|
.header-left {
|
|
111
136
|
display: flex;
|
|
112
137
|
align-items: center;
|
|
@@ -115,12 +140,13 @@
|
|
|
115
140
|
|
|
116
141
|
.logo {
|
|
117
142
|
font-size: 18px;
|
|
118
|
-
font-weight:
|
|
119
|
-
background:
|
|
143
|
+
font-weight: 800;
|
|
144
|
+
background: var(--gradient-accent);
|
|
120
145
|
-webkit-background-clip: text;
|
|
121
146
|
-webkit-text-fill-color: transparent;
|
|
122
147
|
background-clip: text;
|
|
123
148
|
white-space: nowrap;
|
|
149
|
+
letter-spacing: -0.3px;
|
|
124
150
|
}
|
|
125
151
|
|
|
126
152
|
.header-stats {
|
|
@@ -163,6 +189,7 @@
|
|
|
163
189
|
height: 6px;
|
|
164
190
|
border-radius: 50%;
|
|
165
191
|
background: var(--green);
|
|
192
|
+
box-shadow: 0 0 6px var(--green);
|
|
166
193
|
animation: pulse 2s infinite;
|
|
167
194
|
}
|
|
168
195
|
|
|
@@ -176,19 +203,21 @@
|
|
|
176
203
|
color: var(--text);
|
|
177
204
|
border: 1px solid var(--border);
|
|
178
205
|
padding: 6px 12px;
|
|
179
|
-
border-radius:
|
|
206
|
+
border-radius: 8px;
|
|
180
207
|
cursor: pointer;
|
|
181
208
|
font-size: 12px;
|
|
182
209
|
font-weight: 500;
|
|
183
|
-
transition: all 0.
|
|
210
|
+
transition: all 0.2s ease;
|
|
184
211
|
white-space: nowrap;
|
|
212
|
+
letter-spacing: 0.01em;
|
|
185
213
|
}
|
|
186
214
|
|
|
187
|
-
.btn:hover { border-color: var(--border-light); background: var(--surface-3); }
|
|
215
|
+
.btn:hover { border-color: var(--border-light); background: var(--surface-3); transform: translateY(-1px); box-shadow: var(--shadow-sm); }
|
|
216
|
+
.btn:active { transform: translateY(0); }
|
|
188
217
|
.btn-danger { color: var(--red); }
|
|
189
|
-
.btn-danger:hover { background: var(--red-dim); border-color: var(--red); }
|
|
218
|
+
.btn-danger:hover { background: var(--red-dim); border-color: var(--red); box-shadow: 0 0 12px rgba(248, 113, 113, 0.15); }
|
|
190
219
|
.btn-primary { color: var(--accent); }
|
|
191
|
-
.btn-primary:hover { background: var(--accent-dim); border-color: var(--accent); }
|
|
220
|
+
.btn-primary:hover { background: var(--accent-dim); border-color: var(--accent); box-shadow: 0 0 12px var(--accent-glow); }
|
|
192
221
|
|
|
193
222
|
.mobile-toggle {
|
|
194
223
|
display: none;
|
|
@@ -212,6 +241,7 @@
|
|
|
212
241
|
width: var(--sidebar-w);
|
|
213
242
|
min-width: var(--sidebar-w);
|
|
214
243
|
background: var(--surface);
|
|
244
|
+
background-image: var(--gradient-surface);
|
|
215
245
|
border-right: 1px solid var(--border);
|
|
216
246
|
display: flex;
|
|
217
247
|
flex-direction: column;
|
|
@@ -256,13 +286,16 @@
|
|
|
256
286
|
/* ===== AGENT CARDS ===== */
|
|
257
287
|
.agent-card {
|
|
258
288
|
background: var(--surface-2);
|
|
289
|
+
background-image: var(--gradient-surface);
|
|
259
290
|
border: 1px solid var(--border);
|
|
260
|
-
border-radius:
|
|
291
|
+
border-radius: 10px;
|
|
261
292
|
padding: 10px 12px;
|
|
262
293
|
margin-bottom: 6px;
|
|
263
|
-
transition: all 0.
|
|
294
|
+
transition: all 0.25s ease;
|
|
264
295
|
}
|
|
265
296
|
|
|
297
|
+
.agent-card:hover { border-color: var(--border-light); box-shadow: var(--shadow-sm); }
|
|
298
|
+
|
|
266
299
|
.agent-card.sleeping {
|
|
267
300
|
border-color: var(--orange);
|
|
268
301
|
border-left: 3px solid var(--orange);
|
|
@@ -360,7 +393,7 @@
|
|
|
360
393
|
display: inline-block;
|
|
361
394
|
}
|
|
362
395
|
|
|
363
|
-
.listen-dot.on { background: var(--green); box-shadow: 0 0
|
|
396
|
+
.listen-dot.on { background: var(--green); box-shadow: 0 0 8px var(--green); }
|
|
364
397
|
.listen-dot.off { background: var(--red); }
|
|
365
398
|
|
|
366
399
|
@keyframes pulseAlert {
|
|
@@ -428,15 +461,16 @@
|
|
|
428
461
|
background: var(--surface-2);
|
|
429
462
|
color: var(--text);
|
|
430
463
|
border: 1px solid var(--border);
|
|
431
|
-
border-radius:
|
|
464
|
+
border-radius: 8px;
|
|
432
465
|
padding: 7px 10px;
|
|
433
466
|
font-size: 12px;
|
|
434
467
|
cursor: pointer;
|
|
435
468
|
outline: none;
|
|
436
469
|
margin-bottom: 6px;
|
|
470
|
+
transition: all 0.2s ease;
|
|
437
471
|
}
|
|
438
472
|
|
|
439
|
-
.project-select:focus { border-color: var(--accent); }
|
|
473
|
+
.project-select:focus { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-dim); }
|
|
440
474
|
|
|
441
475
|
.project-actions {
|
|
442
476
|
display: flex;
|
|
@@ -530,12 +564,12 @@
|
|
|
530
564
|
display: flex;
|
|
531
565
|
gap: 10px;
|
|
532
566
|
padding: 10px 14px;
|
|
533
|
-
border-radius:
|
|
534
|
-
transition:
|
|
567
|
+
border-radius: 10px;
|
|
568
|
+
transition: all 0.2s ease;
|
|
569
|
+
position: relative;
|
|
535
570
|
}
|
|
536
571
|
|
|
537
|
-
.message {
|
|
538
|
-
.message:hover { background: var(--surface); }
|
|
572
|
+
.message:hover { background: var(--surface); box-shadow: var(--glow); }
|
|
539
573
|
|
|
540
574
|
/* ===== REACTIONS ===== */
|
|
541
575
|
.msg-actions {
|
|
@@ -570,12 +604,14 @@
|
|
|
570
604
|
top: -4px;
|
|
571
605
|
right: 56px;
|
|
572
606
|
background: var(--surface-2);
|
|
573
|
-
|
|
574
|
-
|
|
607
|
+
backdrop-filter: blur(12px);
|
|
608
|
+
-webkit-backdrop-filter: blur(12px);
|
|
609
|
+
border: 1px solid var(--border-light);
|
|
610
|
+
border-radius: 10px;
|
|
575
611
|
padding: 4px;
|
|
576
612
|
gap: 2px;
|
|
577
613
|
z-index: 20;
|
|
578
|
-
box-shadow:
|
|
614
|
+
box-shadow: var(--shadow-lg);
|
|
579
615
|
}
|
|
580
616
|
|
|
581
617
|
.react-picker.open { display: flex; }
|
|
@@ -701,9 +737,10 @@
|
|
|
701
737
|
|
|
702
738
|
.badge {
|
|
703
739
|
font-size: 9px;
|
|
704
|
-
padding:
|
|
705
|
-
border-radius:
|
|
740
|
+
padding: 2px 6px;
|
|
741
|
+
border-radius: 6px;
|
|
706
742
|
font-weight: 600;
|
|
743
|
+
letter-spacing: 0.03em;
|
|
707
744
|
}
|
|
708
745
|
|
|
709
746
|
.badge-ack { background: var(--green-dim); color: var(--green); }
|
|
@@ -737,16 +774,17 @@
|
|
|
737
774
|
}
|
|
738
775
|
|
|
739
776
|
.msg-content pre {
|
|
740
|
-
background: var(--
|
|
777
|
+
background: var(--bg);
|
|
741
778
|
border: 1px solid var(--border);
|
|
742
|
-
border-radius:
|
|
743
|
-
padding:
|
|
744
|
-
margin:
|
|
779
|
+
border-radius: 8px;
|
|
780
|
+
padding: 14px 16px;
|
|
781
|
+
margin: 8px 0;
|
|
745
782
|
overflow-x: auto;
|
|
746
783
|
font-size: 12px;
|
|
747
|
-
line-height: 1.
|
|
784
|
+
line-height: 1.6;
|
|
748
785
|
font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', monospace;
|
|
749
786
|
position: relative;
|
|
787
|
+
box-shadow: inset 0 1px 3px rgba(0,0,0,0.2);
|
|
750
788
|
}
|
|
751
789
|
|
|
752
790
|
.msg-content pre code {
|
|
@@ -812,11 +850,33 @@
|
|
|
812
850
|
|
|
813
851
|
.msg-content a:hover { text-decoration: underline; }
|
|
814
852
|
|
|
853
|
+
/* ===== READ RECEIPTS ===== */
|
|
854
|
+
.read-receipts {
|
|
855
|
+
display: flex;
|
|
856
|
+
gap: 2px;
|
|
857
|
+
margin-top: 4px;
|
|
858
|
+
align-items: center;
|
|
859
|
+
}
|
|
860
|
+
.read-receipt-dot {
|
|
861
|
+
width: 14px;
|
|
862
|
+
height: 14px;
|
|
863
|
+
border-radius: 50%;
|
|
864
|
+
background: var(--surface-3);
|
|
865
|
+
display: flex;
|
|
866
|
+
align-items: center;
|
|
867
|
+
justify-content: center;
|
|
868
|
+
font-size: 8px;
|
|
869
|
+
font-weight: 700;
|
|
870
|
+
color: var(--text-dim);
|
|
871
|
+
cursor: default;
|
|
872
|
+
}
|
|
873
|
+
|
|
815
874
|
/* ===== MESSAGE INPUT ===== */
|
|
816
875
|
.msg-input-bar {
|
|
817
876
|
border-top: 1px solid var(--border);
|
|
818
877
|
background: var(--surface);
|
|
819
|
-
|
|
878
|
+
background-image: var(--gradient-surface);
|
|
879
|
+
padding: 14px 20px;
|
|
820
880
|
display: flex;
|
|
821
881
|
gap: 8px;
|
|
822
882
|
align-items: flex-end;
|
|
@@ -840,14 +900,15 @@
|
|
|
840
900
|
background: var(--surface-2);
|
|
841
901
|
color: var(--text);
|
|
842
902
|
border: 1px solid var(--border);
|
|
843
|
-
border-radius:
|
|
903
|
+
border-radius: 8px;
|
|
844
904
|
padding: 7px 10px;
|
|
845
905
|
font-size: 12px;
|
|
846
906
|
cursor: pointer;
|
|
847
907
|
outline: none;
|
|
908
|
+
transition: all 0.2s ease;
|
|
848
909
|
}
|
|
849
910
|
|
|
850
|
-
.input-target select:focus { border-color: var(--accent); }
|
|
911
|
+
.input-target select:focus { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-dim); }
|
|
851
912
|
|
|
852
913
|
.input-msg {
|
|
853
914
|
flex: 1;
|
|
@@ -867,7 +928,7 @@
|
|
|
867
928
|
background: var(--surface-2);
|
|
868
929
|
color: var(--text);
|
|
869
930
|
border: 1px solid var(--border);
|
|
870
|
-
border-radius:
|
|
931
|
+
border-radius: 8px;
|
|
871
932
|
padding: 7px 10px;
|
|
872
933
|
font-size: 12px;
|
|
873
934
|
font-family: inherit;
|
|
@@ -875,26 +936,30 @@
|
|
|
875
936
|
height: 34px;
|
|
876
937
|
max-height: 80px;
|
|
877
938
|
outline: none;
|
|
939
|
+
transition: all 0.2s ease;
|
|
878
940
|
}
|
|
879
941
|
|
|
880
|
-
.input-msg textarea:focus { border-color: var(--accent); }
|
|
942
|
+
.input-msg textarea:focus { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-dim); }
|
|
881
943
|
|
|
882
944
|
.send-btn {
|
|
883
|
-
background: var(--accent);
|
|
945
|
+
background: var(--gradient-accent);
|
|
884
946
|
color: #fff;
|
|
885
947
|
border: none;
|
|
886
|
-
border-radius:
|
|
887
|
-
padding: 7px
|
|
948
|
+
border-radius: 8px;
|
|
949
|
+
padding: 7px 20px;
|
|
888
950
|
font-size: 12px;
|
|
889
951
|
font-weight: 600;
|
|
890
952
|
cursor: pointer;
|
|
891
953
|
white-space: nowrap;
|
|
892
|
-
transition:
|
|
954
|
+
transition: all 0.2s ease;
|
|
893
955
|
height: 34px;
|
|
956
|
+
box-shadow: 0 2px 8px var(--accent-glow);
|
|
957
|
+
letter-spacing: 0.02em;
|
|
894
958
|
}
|
|
895
959
|
|
|
896
|
-
.send-btn:hover {
|
|
897
|
-
.send-btn:
|
|
960
|
+
.send-btn:hover { transform: translateY(-1px); box-shadow: 0 4px 16px var(--accent-glow); }
|
|
961
|
+
.send-btn:active { transform: translateY(0); }
|
|
962
|
+
.send-btn:disabled { opacity: 0.4; cursor: not-allowed; transform: none; box-shadow: none; }
|
|
898
963
|
|
|
899
964
|
/* ===== EMPTY STATE ===== */
|
|
900
965
|
.empty-state {
|
|
@@ -912,25 +977,27 @@
|
|
|
912
977
|
.empty-sub { font-size: 12px; color: var(--text-muted); }
|
|
913
978
|
|
|
914
979
|
/* ===== MESSAGE FLASH ===== */
|
|
915
|
-
.message-new { animation: flashIn 0.
|
|
980
|
+
.message-new { animation: flashIn 0.8s ease-out; }
|
|
916
981
|
|
|
917
982
|
@keyframes flashIn {
|
|
918
|
-
|
|
919
|
-
|
|
983
|
+
0% { background: var(--accent-dim); box-shadow: inset 0 0 20px var(--accent-glow); }
|
|
984
|
+
100% { background: transparent; box-shadow: none; }
|
|
920
985
|
}
|
|
921
986
|
|
|
922
987
|
/* ===== SCROLLBAR ===== */
|
|
923
|
-
::-webkit-scrollbar { width:
|
|
988
|
+
::-webkit-scrollbar { width: 5px; }
|
|
924
989
|
::-webkit-scrollbar-track { background: transparent; }
|
|
925
|
-
::-webkit-scrollbar-thumb { background:
|
|
926
|
-
::-webkit-scrollbar-thumb:hover { background:
|
|
990
|
+
::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.08); border-radius: 4px; }
|
|
991
|
+
::-webkit-scrollbar-thumb:hover { background: rgba(255,255,255,0.15); }
|
|
992
|
+
[data-theme="light"] ::-webkit-scrollbar-thumb { background: rgba(0,0,0,0.1); }
|
|
993
|
+
[data-theme="light"] ::-webkit-scrollbar-thumb:hover { background: rgba(0,0,0,0.2); }
|
|
927
994
|
|
|
928
995
|
/* ===== SCROLL TO BOTTOM ===== */
|
|
929
996
|
.scroll-bottom {
|
|
930
997
|
position: absolute;
|
|
931
998
|
bottom: 80px;
|
|
932
999
|
right: 24px;
|
|
933
|
-
background: var(--accent);
|
|
1000
|
+
background: var(--gradient-accent);
|
|
934
1001
|
color: #fff;
|
|
935
1002
|
border: none;
|
|
936
1003
|
border-radius: 50%;
|
|
@@ -941,13 +1008,13 @@
|
|
|
941
1008
|
justify-content: center;
|
|
942
1009
|
cursor: pointer;
|
|
943
1010
|
font-size: 16px;
|
|
944
|
-
box-shadow: 0
|
|
1011
|
+
box-shadow: 0 4px 16px var(--accent-glow);
|
|
945
1012
|
z-index: 10;
|
|
946
|
-
transition:
|
|
1013
|
+
transition: all 0.25s ease;
|
|
947
1014
|
}
|
|
948
1015
|
|
|
949
1016
|
.scroll-bottom.visible { display: flex; }
|
|
950
|
-
.scroll-bottom:hover {
|
|
1017
|
+
.scroll-bottom:hover { transform: translateY(-2px); box-shadow: 0 6px 24px var(--accent-glow); }
|
|
951
1018
|
|
|
952
1019
|
.scroll-bottom .new-count {
|
|
953
1020
|
position: absolute;
|
|
@@ -981,16 +1048,43 @@
|
|
|
981
1048
|
background: var(--surface-2);
|
|
982
1049
|
color: var(--text);
|
|
983
1050
|
border: 1px solid var(--border);
|
|
984
|
-
border-radius:
|
|
985
|
-
padding:
|
|
1051
|
+
border-radius: 8px;
|
|
1052
|
+
padding: 7px 12px;
|
|
986
1053
|
font-size: 12px;
|
|
987
1054
|
font-family: inherit;
|
|
988
1055
|
outline: none;
|
|
1056
|
+
transition: all 0.2s ease;
|
|
989
1057
|
}
|
|
990
1058
|
|
|
991
|
-
.search-input:focus { border-color: var(--accent); }
|
|
1059
|
+
.search-input:focus { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-dim); }
|
|
992
1060
|
.search-input::placeholder { color: var(--text-muted); }
|
|
993
1061
|
|
|
1062
|
+
.compact-toggle {
|
|
1063
|
+
background: var(--surface-2);
|
|
1064
|
+
border: 1px solid var(--border);
|
|
1065
|
+
border-radius: 6px;
|
|
1066
|
+
color: var(--text-dim);
|
|
1067
|
+
font-size: 11px;
|
|
1068
|
+
padding: 4px 8px;
|
|
1069
|
+
cursor: pointer;
|
|
1070
|
+
white-space: nowrap;
|
|
1071
|
+
transition: background 0.15s, color 0.15s;
|
|
1072
|
+
}
|
|
1073
|
+
.compact-toggle:hover { background: var(--surface-3); color: var(--text); }
|
|
1074
|
+
.compact-toggle.active { background: var(--accent-dim); color: var(--accent); border-color: var(--accent); }
|
|
1075
|
+
|
|
1076
|
+
/* Compact mode styles */
|
|
1077
|
+
.messages-area.compact-mode { gap: 0; padding: 8px 12px; }
|
|
1078
|
+
.messages-area.compact-mode .message { padding: 3px 8px; gap: 6px; }
|
|
1079
|
+
.messages-area.compact-mode .msg-avatar,
|
|
1080
|
+
.messages-area.compact-mode .msg-avatar-img { display: none; }
|
|
1081
|
+
.messages-area.compact-mode .msg-header { margin-bottom: 0; display: inline; }
|
|
1082
|
+
.messages-area.compact-mode .msg-body { display: inline; }
|
|
1083
|
+
.messages-area.compact-mode .msg-from { font-size: 12px; }
|
|
1084
|
+
.messages-area.compact-mode .msg-time { font-size: 9px; }
|
|
1085
|
+
.messages-area.compact-mode .msg-content { display: inline; font-size: 12px; }
|
|
1086
|
+
.messages-area.compact-mode .msg-content p { display: inline; margin: 0; }
|
|
1087
|
+
|
|
994
1088
|
.search-count {
|
|
995
1089
|
font-size: 11px;
|
|
996
1090
|
color: var(--text-muted);
|
|
@@ -1076,6 +1170,7 @@
|
|
|
1076
1170
|
gap: 16px;
|
|
1077
1171
|
font-size: 10px;
|
|
1078
1172
|
color: var(--text-muted);
|
|
1173
|
+
letter-spacing: 0.02em;
|
|
1079
1174
|
}
|
|
1080
1175
|
|
|
1081
1176
|
.app-footer a { color: var(--text-dim); text-decoration: none; }
|
|
@@ -1177,17 +1272,18 @@
|
|
|
1177
1272
|
.view-tab {
|
|
1178
1273
|
flex: 1;
|
|
1179
1274
|
text-align: center;
|
|
1180
|
-
padding: 8px;
|
|
1275
|
+
padding: 10px 8px;
|
|
1181
1276
|
font-size: 12px;
|
|
1182
1277
|
font-weight: 600;
|
|
1183
|
-
color: var(--text-
|
|
1278
|
+
color: var(--text-muted);
|
|
1184
1279
|
cursor: pointer;
|
|
1185
1280
|
border-bottom: 2px solid transparent;
|
|
1186
|
-
transition: all 0.
|
|
1281
|
+
transition: all 0.2s ease;
|
|
1282
|
+
letter-spacing: 0.02em;
|
|
1187
1283
|
}
|
|
1188
1284
|
|
|
1189
|
-
.view-tab:hover { color: var(--text); }
|
|
1190
|
-
.view-tab.active { color: var(--accent); border-bottom-color: var(--accent); }
|
|
1285
|
+
.view-tab:hover { color: var(--text); background: rgba(255,255,255,0.02); }
|
|
1286
|
+
.view-tab.active { color: var(--accent); border-bottom-color: var(--accent); background: var(--accent-dim); }
|
|
1191
1287
|
|
|
1192
1288
|
/* ===== LAN BADGE ===== */
|
|
1193
1289
|
.lan-badge {
|
|
@@ -1231,13 +1327,14 @@
|
|
|
1231
1327
|
|
|
1232
1328
|
.phone-modal {
|
|
1233
1329
|
background: var(--surface);
|
|
1234
|
-
|
|
1235
|
-
border
|
|
1330
|
+
background-image: var(--gradient-surface);
|
|
1331
|
+
border: 1px solid var(--border-light);
|
|
1332
|
+
border-radius: 16px;
|
|
1236
1333
|
padding: 28px;
|
|
1237
1334
|
width: 360px;
|
|
1238
1335
|
max-width: 90vw;
|
|
1239
1336
|
text-align: center;
|
|
1240
|
-
box-shadow: 0
|
|
1337
|
+
box-shadow: var(--shadow-lg), 0 0 60px rgba(0,0,0,0.3);
|
|
1241
1338
|
position: relative;
|
|
1242
1339
|
}
|
|
1243
1340
|
|
|
@@ -1411,14 +1508,19 @@
|
|
|
1411
1508
|
|
|
1412
1509
|
.task-card {
|
|
1413
1510
|
background: var(--surface-2);
|
|
1511
|
+
background-image: var(--gradient-surface);
|
|
1414
1512
|
border: 1px solid var(--border);
|
|
1415
|
-
border-radius:
|
|
1513
|
+
border-radius: 8px;
|
|
1416
1514
|
padding: 10px;
|
|
1417
1515
|
margin-bottom: 6px;
|
|
1418
|
-
transition:
|
|
1516
|
+
transition: all 0.2s ease;
|
|
1419
1517
|
}
|
|
1420
1518
|
|
|
1421
|
-
.task-card:hover { border-color: var(--border-light); }
|
|
1519
|
+
.task-card:hover { border-color: var(--border-light); transform: translateY(-1px); box-shadow: var(--shadow-sm); }
|
|
1520
|
+
.task-card[draggable="true"] { cursor: grab; }
|
|
1521
|
+
.task-card[draggable="true"]:active { cursor: grabbing; }
|
|
1522
|
+
.task-card.dragging { opacity: 0.4; transform: scale(0.95); }
|
|
1523
|
+
.kanban-col.drag-over { background: var(--surface-2); border-radius: 8px; outline: 2px dashed var(--accent); outline-offset: -2px; transition: background 0.15s, outline 0.15s; }
|
|
1422
1524
|
|
|
1423
1525
|
.task-title {
|
|
1424
1526
|
font-size: 13px;
|
|
@@ -1945,11 +2047,14 @@
|
|
|
1945
2047
|
position: fixed;
|
|
1946
2048
|
z-index: 300;
|
|
1947
2049
|
background: var(--surface);
|
|
1948
|
-
|
|
1949
|
-
border
|
|
1950
|
-
|
|
2050
|
+
background-image: var(--gradient-surface);
|
|
2051
|
+
border: 1px solid var(--border-light);
|
|
2052
|
+
border-radius: 14px;
|
|
2053
|
+
padding: 18px;
|
|
1951
2054
|
width: 260px;
|
|
1952
|
-
box-shadow: 0
|
|
2055
|
+
box-shadow: var(--shadow-lg), 0 0 40px rgba(0,0,0,0.2);
|
|
2056
|
+
backdrop-filter: blur(8px);
|
|
2057
|
+
-webkit-backdrop-filter: blur(8px);
|
|
1953
2058
|
}
|
|
1954
2059
|
|
|
1955
2060
|
.profile-popup.open { display: block; }
|
|
@@ -2201,6 +2306,122 @@
|
|
|
2201
2306
|
|
|
2202
2307
|
.launch-area.visible { display: block; }
|
|
2203
2308
|
|
|
2309
|
+
/* ===== STATS VIEW ===== */
|
|
2310
|
+
.stats-area {
|
|
2311
|
+
flex: 1;
|
|
2312
|
+
overflow-y: auto;
|
|
2313
|
+
padding: 20px;
|
|
2314
|
+
display: none;
|
|
2315
|
+
}
|
|
2316
|
+
.stats-area.visible { display: block; }
|
|
2317
|
+
|
|
2318
|
+
.stats-grid {
|
|
2319
|
+
display: grid;
|
|
2320
|
+
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
|
|
2321
|
+
gap: 12px;
|
|
2322
|
+
margin-bottom: 20px;
|
|
2323
|
+
}
|
|
2324
|
+
|
|
2325
|
+
.stat-card {
|
|
2326
|
+
background: var(--surface);
|
|
2327
|
+
border: 1px solid var(--border);
|
|
2328
|
+
border-radius: 8px;
|
|
2329
|
+
padding: 14px;
|
|
2330
|
+
}
|
|
2331
|
+
.stat-card-label {
|
|
2332
|
+
font-size: 10px;
|
|
2333
|
+
text-transform: uppercase;
|
|
2334
|
+
letter-spacing: 0.5px;
|
|
2335
|
+
color: var(--text-muted);
|
|
2336
|
+
margin-bottom: 4px;
|
|
2337
|
+
}
|
|
2338
|
+
.stat-card-value {
|
|
2339
|
+
font-size: 24px;
|
|
2340
|
+
font-weight: 700;
|
|
2341
|
+
color: var(--text);
|
|
2342
|
+
}
|
|
2343
|
+
.stat-card-sub {
|
|
2344
|
+
font-size: 11px;
|
|
2345
|
+
color: var(--text-dim);
|
|
2346
|
+
margin-top: 2px;
|
|
2347
|
+
}
|
|
2348
|
+
|
|
2349
|
+
.stats-section {
|
|
2350
|
+
background: var(--surface);
|
|
2351
|
+
border: 1px solid var(--border);
|
|
2352
|
+
border-radius: 8px;
|
|
2353
|
+
padding: 16px;
|
|
2354
|
+
margin-bottom: 16px;
|
|
2355
|
+
}
|
|
2356
|
+
.stats-section-title {
|
|
2357
|
+
font-size: 12px;
|
|
2358
|
+
font-weight: 700;
|
|
2359
|
+
text-transform: uppercase;
|
|
2360
|
+
letter-spacing: 0.5px;
|
|
2361
|
+
color: var(--text-dim);
|
|
2362
|
+
margin-bottom: 12px;
|
|
2363
|
+
}
|
|
2364
|
+
|
|
2365
|
+
.stats-bar-row {
|
|
2366
|
+
display: flex;
|
|
2367
|
+
align-items: center;
|
|
2368
|
+
gap: 8px;
|
|
2369
|
+
margin-bottom: 6px;
|
|
2370
|
+
}
|
|
2371
|
+
.stats-bar-label {
|
|
2372
|
+
font-size: 12px;
|
|
2373
|
+
color: var(--text);
|
|
2374
|
+
min-width: 80px;
|
|
2375
|
+
text-align: right;
|
|
2376
|
+
}
|
|
2377
|
+
.stats-bar-track {
|
|
2378
|
+
flex: 1;
|
|
2379
|
+
height: 18px;
|
|
2380
|
+
background: var(--surface-2);
|
|
2381
|
+
border-radius: 4px;
|
|
2382
|
+
overflow: hidden;
|
|
2383
|
+
}
|
|
2384
|
+
.stats-bar-fill {
|
|
2385
|
+
height: 100%;
|
|
2386
|
+
border-radius: 4px;
|
|
2387
|
+
transition: width 0.3s;
|
|
2388
|
+
display: flex;
|
|
2389
|
+
align-items: center;
|
|
2390
|
+
padding-left: 6px;
|
|
2391
|
+
font-size: 10px;
|
|
2392
|
+
font-weight: 600;
|
|
2393
|
+
color: #fff;
|
|
2394
|
+
white-space: nowrap;
|
|
2395
|
+
}
|
|
2396
|
+
|
|
2397
|
+
.stats-hour-chart {
|
|
2398
|
+
display: flex;
|
|
2399
|
+
align-items: flex-end;
|
|
2400
|
+
gap: 2px;
|
|
2401
|
+
height: 80px;
|
|
2402
|
+
}
|
|
2403
|
+
.stats-hour-bar {
|
|
2404
|
+
flex: 1;
|
|
2405
|
+
background: var(--accent);
|
|
2406
|
+
border-radius: 2px 2px 0 0;
|
|
2407
|
+
min-width: 4px;
|
|
2408
|
+
opacity: 0.7;
|
|
2409
|
+
transition: opacity 0.15s;
|
|
2410
|
+
position: relative;
|
|
2411
|
+
}
|
|
2412
|
+
.stats-hour-bar:hover { opacity: 1; }
|
|
2413
|
+
.stats-hour-labels {
|
|
2414
|
+
display: flex;
|
|
2415
|
+
gap: 2px;
|
|
2416
|
+
margin-top: 4px;
|
|
2417
|
+
}
|
|
2418
|
+
.stats-hour-labels span {
|
|
2419
|
+
flex: 1;
|
|
2420
|
+
text-align: center;
|
|
2421
|
+
font-size: 8px;
|
|
2422
|
+
color: var(--text-muted);
|
|
2423
|
+
}
|
|
2424
|
+
|
|
2204
2425
|
.launch-panel {
|
|
2205
2426
|
max-width: 560px;
|
|
2206
2427
|
margin: 0 auto;
|
|
@@ -2459,6 +2680,7 @@
|
|
|
2459
2680
|
<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">
|
|
2460
2681
|
<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>
|
|
2461
2682
|
<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>
|
|
2683
|
+
<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>
|
|
2462
2684
|
</div>
|
|
2463
2685
|
</div>
|
|
2464
2686
|
<button class="btn btn-danger" onclick="doReset()">Reset</button>
|
|
@@ -2479,6 +2701,21 @@
|
|
|
2479
2701
|
</div>
|
|
2480
2702
|
</div>
|
|
2481
2703
|
|
|
2704
|
+
<!-- v3.4: EDIT MESSAGE MODAL -->
|
|
2705
|
+
<div class="phone-modal-overlay" id="edit-modal-overlay" onclick="if(event.target===this)closeEditModal()">
|
|
2706
|
+
<div class="phone-modal" style="width:500px;text-align:left">
|
|
2707
|
+
<button class="phone-modal-close" onclick="closeEditModal()">×</button>
|
|
2708
|
+
<h3>Edit Message</h3>
|
|
2709
|
+
<input type="hidden" id="edit-msg-id">
|
|
2710
|
+
<textarea id="edit-msg-content" style="width:100%;min-height:120px;background:var(--surface-2);color:var(--text);border:1px solid var(--border);border-radius:8px;padding:12px;font-size:13px;font-family:inherit;resize:vertical;outline:none;margin:12px 0 8px"></textarea>
|
|
2711
|
+
<div id="edit-history" style="max-height:150px;overflow-y:auto;margin-bottom:12px"></div>
|
|
2712
|
+
<div style="display:flex;gap:8px;justify-content:flex-end">
|
|
2713
|
+
<button class="btn" onclick="closeEditModal()">Cancel</button>
|
|
2714
|
+
<button class="btn btn-primary" onclick="saveEditMessage()" style="background:var(--accent);color:#fff;border-color:var(--accent)">Save Edit</button>
|
|
2715
|
+
</div>
|
|
2716
|
+
</div>
|
|
2717
|
+
</div>
|
|
2718
|
+
|
|
2482
2719
|
<!-- APP LAYOUT -->
|
|
2483
2720
|
<div class="app">
|
|
2484
2721
|
<div class="sidebar-overlay" id="sidebar-overlay" onclick="toggleSidebar()"></div>
|
|
@@ -2566,11 +2803,13 @@
|
|
|
2566
2803
|
<div class="view-tab" id="tab-workspaces" onclick="switchView('workspaces')">Workspaces</div>
|
|
2567
2804
|
<div class="view-tab" id="tab-workflows" onclick="switchView('workflows')">Workflows</div>
|
|
2568
2805
|
<div class="view-tab" id="tab-launch" onclick="switchView('launch')">Launch</div>
|
|
2806
|
+
<div class="view-tab" id="tab-stats" onclick="switchView('stats')">Stats</div>
|
|
2569
2807
|
</div>
|
|
2570
2808
|
<div class="branch-tabs" id="branch-tabs"></div>
|
|
2571
2809
|
<div class="search-bar" id="search-bar">
|
|
2572
2810
|
<input class="search-input" id="search-input" placeholder="Search messages... ( / )" oninput="onSearch()">
|
|
2573
2811
|
<span class="search-count" id="search-count"></span>
|
|
2812
|
+
<button class="compact-toggle" id="compact-toggle" onclick="toggleCompactMode()" title="Toggle compact view">Compact</button>
|
|
2574
2813
|
</div>
|
|
2575
2814
|
<div class="pinned-section" id="pinned-section">
|
|
2576
2815
|
<div class="pinned-header" onclick="togglePinnedSection()"><span>Pinned Messages</span><span class="pinned-toggle" id="pinned-toggle">Hide</span></div>
|
|
@@ -2581,6 +2820,7 @@
|
|
|
2581
2820
|
<div class="workspaces-area" id="workspaces-area"></div>
|
|
2582
2821
|
<div class="workflows-area" id="workflows-area"></div>
|
|
2583
2822
|
<div class="launch-area" id="launch-area"></div>
|
|
2823
|
+
<div class="stats-area" id="stats-area"></div>
|
|
2584
2824
|
<button class="scroll-bottom" id="scroll-bottom" onclick="scrollToBottom()">↓<span class="new-count" id="new-msg-count" style="display:none">0</span></button>
|
|
2585
2825
|
<div class="typing-bar" id="typing-bar"></div>
|
|
2586
2826
|
|
|
@@ -2602,7 +2842,7 @@
|
|
|
2602
2842
|
</div>
|
|
2603
2843
|
</div>
|
|
2604
2844
|
<div class="app-footer">
|
|
2605
|
-
<span>Let Them Talk v3.
|
|
2845
|
+
<span>Let Them Talk v3.4.1</span>
|
|
2606
2846
|
</div>
|
|
2607
2847
|
<div class="profile-popup" id="profile-popup" onclick="event.stopPropagation()">
|
|
2608
2848
|
<div class="profile-popup-header">
|
|
@@ -3205,6 +3445,7 @@ function renderMessages(messages) {
|
|
|
3205
3445
|
var badges = '';
|
|
3206
3446
|
if (m.acked) badges += '<span class="badge badge-ack">ACK</span>';
|
|
3207
3447
|
if (m.thread_id) badges += '<span class="badge badge-thread">Thread</span>';
|
|
3448
|
+
if (m.edited) badges += '<span class="badge" style="background:var(--orange-dim);color:var(--orange)" title="Edited ' + (m.edited_at ? new Date(m.edited_at).toLocaleString() : '') + '">edited</span>';
|
|
3208
3449
|
|
|
3209
3450
|
var msgAvatarHtml = getMsgAvatar(m.from, color);
|
|
3210
3451
|
|
|
@@ -3228,6 +3469,7 @@ function renderMessages(messages) {
|
|
|
3228
3469
|
'</div>' +
|
|
3229
3470
|
'<div class="msg-content">' + renderMarkdown(m.content) + '</div>' +
|
|
3230
3471
|
buildReactionsHtml(m.id) +
|
|
3472
|
+
buildReadReceipts(m.id) +
|
|
3231
3473
|
'</div></div>';
|
|
3232
3474
|
lastFrom = m.from;
|
|
3233
3475
|
lastTo = m.to;
|
|
@@ -3249,6 +3491,7 @@ function renderMessages(messages) {
|
|
|
3249
3491
|
'<div class="file-meta"><span class="file-icon">📄</span>' + escapeHtml(fileName) + '<span class="file-size">' + fileSize + '</span></div>' +
|
|
3250
3492
|
'<div class="msg-content">' + renderMarkdown(m.content) + '</div>' +
|
|
3251
3493
|
buildReactionsHtml(m.id) +
|
|
3494
|
+
buildReadReceipts(m.id) +
|
|
3252
3495
|
'</div></div>';
|
|
3253
3496
|
lastFrom = m.from;
|
|
3254
3497
|
lastTo = m.to;
|
|
@@ -3266,6 +3509,7 @@ function renderMessages(messages) {
|
|
|
3266
3509
|
'</div>' +
|
|
3267
3510
|
'<div class="msg-content">' + renderMarkdown(m.content) + '</div>' +
|
|
3268
3511
|
buildReactionsHtml(m.id) +
|
|
3512
|
+
buildReadReceipts(m.id) +
|
|
3269
3513
|
'</div></div>';
|
|
3270
3514
|
lastFrom = m.from;
|
|
3271
3515
|
lastTo = m.to;
|
|
@@ -3330,6 +3574,28 @@ function scrollToBottom() {
|
|
|
3330
3574
|
document.getElementById('new-msg-count').style.display = 'none';
|
|
3331
3575
|
}
|
|
3332
3576
|
|
|
3577
|
+
// ==================== COMPACT MODE ====================
|
|
3578
|
+
|
|
3579
|
+
var compactMode = localStorage.getItem('compactMode') === 'true';
|
|
3580
|
+
|
|
3581
|
+
function initCompactMode() {
|
|
3582
|
+
var area = document.getElementById('messages');
|
|
3583
|
+
var btn = document.getElementById('compact-toggle');
|
|
3584
|
+
if (compactMode) {
|
|
3585
|
+
area.classList.add('compact-mode');
|
|
3586
|
+
btn.classList.add('active');
|
|
3587
|
+
}
|
|
3588
|
+
}
|
|
3589
|
+
|
|
3590
|
+
function toggleCompactMode() {
|
|
3591
|
+
compactMode = !compactMode;
|
|
3592
|
+
localStorage.setItem('compactMode', compactMode);
|
|
3593
|
+
var area = document.getElementById('messages');
|
|
3594
|
+
var btn = document.getElementById('compact-toggle');
|
|
3595
|
+
area.classList.toggle('compact-mode', compactMode);
|
|
3596
|
+
btn.classList.toggle('active', compactMode);
|
|
3597
|
+
}
|
|
3598
|
+
|
|
3333
3599
|
// ==================== SEARCH ====================
|
|
3334
3600
|
|
|
3335
3601
|
var searchQuery = '';
|
|
@@ -3340,6 +3606,33 @@ function onSearch() {
|
|
|
3340
3606
|
renderMessages(cachedHistory);
|
|
3341
3607
|
}
|
|
3342
3608
|
|
|
3609
|
+
// ==================== READ RECEIPTS ====================
|
|
3610
|
+
|
|
3611
|
+
var cachedReadReceipts = {};
|
|
3612
|
+
|
|
3613
|
+
function fetchReadReceipts() {
|
|
3614
|
+
var pq = activeProject ? '?project=' + encodeURIComponent(activeProject) : '';
|
|
3615
|
+
fetch('/api/read-receipts' + pq).then(function(r) { return r.json(); }).then(function(data) {
|
|
3616
|
+
cachedReadReceipts = data || {};
|
|
3617
|
+
}).catch(function() {});
|
|
3618
|
+
}
|
|
3619
|
+
|
|
3620
|
+
function buildReadReceipts(msgId) {
|
|
3621
|
+
var receipts = cachedReadReceipts[msgId];
|
|
3622
|
+
if (!receipts) return '';
|
|
3623
|
+
var agents = Object.keys(receipts);
|
|
3624
|
+
if (!agents.length) return '';
|
|
3625
|
+
var html = '<div class="read-receipts">';
|
|
3626
|
+
for (var i = 0; i < agents.length; i++) {
|
|
3627
|
+
var agent = agents[i];
|
|
3628
|
+
var time = receipts[agent] ? new Date(receipts[agent]).toLocaleTimeString() : '';
|
|
3629
|
+
var color = getColor(agent);
|
|
3630
|
+
html += '<div class="read-receipt-dot" style="background:' + color + '" title="Read by ' + escapeHtml(agent) + (time ? ' at ' + time : '') + '">' + initial(agent) + '</div>';
|
|
3631
|
+
}
|
|
3632
|
+
html += '</div>';
|
|
3633
|
+
return html;
|
|
3634
|
+
}
|
|
3635
|
+
|
|
3343
3636
|
// ==================== REACTIONS ====================
|
|
3344
3637
|
|
|
3345
3638
|
var REACTION_EMOJIS = ['\ud83d\udc4d', '\u2705', '\u2764\ufe0f', '\ud83e\udd14', '\ud83d\udd25'];
|
|
@@ -3425,16 +3718,106 @@ function buildMsgActions(msgId) {
|
|
|
3425
3718
|
var starClass = isBookmarked ? ' active' : '';
|
|
3426
3719
|
var pinClass = isPinned ? ' active' : '';
|
|
3427
3720
|
|
|
3721
|
+
var msg = cachedHistory.find(function(m) { return m.id === msgId; });
|
|
3722
|
+
var isDeletable = msg && (msg.from === 'dashboard' || msg.from === 'Dashboard' || msg.from === 'system' || msg.from === '__system__');
|
|
3723
|
+
var deleteBtn = isDeletable ? '<button class="msg-action-btn" onclick="deleteMessage(\'' + msgId + '\')" title="Delete" style="color:var(--red,#e74c3c)">🗑️</button>' : '';
|
|
3724
|
+
|
|
3428
3725
|
return '<div class="msg-actions">' +
|
|
3429
3726
|
'<button class="msg-action-btn" onclick="toggleReactPicker(\'' + msgId + '\')" title="React">😀</button>' +
|
|
3430
3727
|
'<button class="msg-action-btn' + pinClass + '" onclick="togglePin(\'' + msgId + '\')" title="Pin">\ud83d\udccc</button>' +
|
|
3431
3728
|
'<button class="msg-action-btn' + starClass + '" onclick="toggleBookmark(\'' + msgId + '\')" title="Bookmark">' + starChar + '</button>' +
|
|
3729
|
+
'<button class="msg-action-btn" onclick="copyMessage(\'' + msgId + '\', this)" title="Copy">📋</button>' +
|
|
3730
|
+
'<button class="msg-action-btn" onclick="openEditMessage(\'' + msgId + '\')" title="Edit">✏️</button>' +
|
|
3731
|
+
deleteBtn +
|
|
3432
3732
|
'<div class="react-picker" id="react-' + msgId + '">' +
|
|
3433
3733
|
REACTION_EMOJIS.map(function(e) { return '<button class="react-emoji" onclick="addReaction(\'' + msgId + '\',\'' + e + '\')">' + e + '</button>'; }).join('') +
|
|
3434
3734
|
'</div>' +
|
|
3435
3735
|
'</div>';
|
|
3436
3736
|
}
|
|
3437
3737
|
|
|
3738
|
+
function copyMessage(msgId, btn) {
|
|
3739
|
+
var msg = cachedHistory.find(function(m) { return m.id === msgId; });
|
|
3740
|
+
if (!msg) return;
|
|
3741
|
+
navigator.clipboard.writeText(msg.content).then(function() {
|
|
3742
|
+
var orig = btn.innerHTML;
|
|
3743
|
+
btn.innerHTML = '✅';
|
|
3744
|
+
btn.title = 'Copied!';
|
|
3745
|
+
setTimeout(function() { btn.innerHTML = orig; btn.title = 'Copy'; }, 1500);
|
|
3746
|
+
});
|
|
3747
|
+
}
|
|
3748
|
+
|
|
3749
|
+
function deleteMessage(msgId) {
|
|
3750
|
+
var msg = cachedHistory.find(function(m) { return m.id === msgId; });
|
|
3751
|
+
if (!msg) return;
|
|
3752
|
+
var preview = msg.content.substring(0, 60) + (msg.content.length > 60 ? '...' : '');
|
|
3753
|
+
if (!confirm('Delete this message from ' + msg.from + '?\n\n"' + preview + '"')) return;
|
|
3754
|
+
var pq = activeProject ? '?project=' + encodeURIComponent(activeProject) : '';
|
|
3755
|
+
fetch('/api/message' + pq, {
|
|
3756
|
+
method: 'DELETE',
|
|
3757
|
+
headers: { 'Content-Type': 'application/json' },
|
|
3758
|
+
body: JSON.stringify({ id: msgId })
|
|
3759
|
+
}).then(function(r) { return r.json(); }).then(function(data) {
|
|
3760
|
+
if (data.error) {
|
|
3761
|
+
console.error('Delete failed: ' + data.error);
|
|
3762
|
+
} else {
|
|
3763
|
+
cachedHistory = cachedHistory.filter(function(m) { return m.id !== msgId; });
|
|
3764
|
+
lastMessageCount = 0;
|
|
3765
|
+
renderMessages(cachedHistory);
|
|
3766
|
+
}
|
|
3767
|
+
}).catch(function(err) {
|
|
3768
|
+
console.error('Delete failed: ' + err.message);
|
|
3769
|
+
});
|
|
3770
|
+
}
|
|
3771
|
+
|
|
3772
|
+
// ==================== v3.4: MESSAGE EDIT ====================
|
|
3773
|
+
|
|
3774
|
+
function openEditMessage(msgId) {
|
|
3775
|
+
var msg = cachedHistory.find(function(m) { return m.id === msgId; });
|
|
3776
|
+
if (!msg) return;
|
|
3777
|
+
var overlay = document.getElementById('edit-modal-overlay');
|
|
3778
|
+
document.getElementById('edit-msg-id').value = msgId;
|
|
3779
|
+
document.getElementById('edit-msg-content').value = msg.content;
|
|
3780
|
+
var historyEl = document.getElementById('edit-history');
|
|
3781
|
+
if (msg.edit_history && msg.edit_history.length > 0) {
|
|
3782
|
+
historyEl.innerHTML = '<div style="font-size:10px;color:var(--text-muted);margin-bottom:4px">Edit history (' + msg.edit_history.length + ' edits):</div>' +
|
|
3783
|
+
msg.edit_history.map(function(h) {
|
|
3784
|
+
return '<div style="font-size:11px;color:var(--text-dim);padding:4px 8px;background:var(--surface-2);border-radius:4px;margin-bottom:2px">' +
|
|
3785
|
+
'<span style="color:var(--text-muted)">' + new Date(h.edited_at).toLocaleTimeString() + '</span> ' +
|
|
3786
|
+
escapeHtml(h.content.substring(0, 100)) + (h.content.length > 100 ? '...' : '') + '</div>';
|
|
3787
|
+
}).join('');
|
|
3788
|
+
historyEl.style.display = 'block';
|
|
3789
|
+
} else {
|
|
3790
|
+
historyEl.style.display = 'none';
|
|
3791
|
+
}
|
|
3792
|
+
overlay.classList.add('open');
|
|
3793
|
+
document.getElementById('edit-msg-content').focus();
|
|
3794
|
+
}
|
|
3795
|
+
|
|
3796
|
+
function closeEditModal() {
|
|
3797
|
+
document.getElementById('edit-modal-overlay').classList.remove('open');
|
|
3798
|
+
}
|
|
3799
|
+
|
|
3800
|
+
function saveEditMessage() {
|
|
3801
|
+
var msgId = document.getElementById('edit-msg-id').value;
|
|
3802
|
+
var content = document.getElementById('edit-msg-content').value.trim();
|
|
3803
|
+
if (!content) return;
|
|
3804
|
+
var pq = activeProject ? '?project=' + encodeURIComponent(activeProject) : '';
|
|
3805
|
+
fetch('/api/message' + pq, {
|
|
3806
|
+
method: 'PUT',
|
|
3807
|
+
headers: { 'Content-Type': 'application/json' },
|
|
3808
|
+
body: JSON.stringify({ id: msgId, content: content })
|
|
3809
|
+
}).then(function(r) { return r.json(); }).then(function(data) {
|
|
3810
|
+
if (data.success) {
|
|
3811
|
+
closeEditModal();
|
|
3812
|
+
poll();
|
|
3813
|
+
} else {
|
|
3814
|
+
alert('Edit failed: ' + (data.error || 'Unknown error'));
|
|
3815
|
+
}
|
|
3816
|
+
}).catch(function(err) {
|
|
3817
|
+
alert('Edit failed: ' + err.message);
|
|
3818
|
+
});
|
|
3819
|
+
}
|
|
3820
|
+
|
|
3438
3821
|
function buildReactionsHtml(msgId) {
|
|
3439
3822
|
if (!reactions[msgId]) return '';
|
|
3440
3823
|
var emojis = Object.keys(reactions[msgId]);
|
|
@@ -3512,6 +3895,8 @@ document.addEventListener('keydown', function(e) {
|
|
|
3512
3895
|
if (e.key === '4') { switchView('workflows'); return; }
|
|
3513
3896
|
// 5 — Launch tab
|
|
3514
3897
|
if (e.key === '5') { switchView('launch'); return; }
|
|
3898
|
+
// 6 — Stats tab
|
|
3899
|
+
if (e.key === '6') { switchView('stats'); return; }
|
|
3515
3900
|
});
|
|
3516
3901
|
|
|
3517
3902
|
// ==================== RELATIVE TIMESTAMPS ====================
|
|
@@ -3531,7 +3916,7 @@ function relativeTime(ts) {
|
|
|
3531
3916
|
// ==================== COPY TO CLIPBOARD ====================
|
|
3532
3917
|
|
|
3533
3918
|
function copyText(el) {
|
|
3534
|
-
var text = el.getAttribute('data-text').replace(/"/g, '"');
|
|
3919
|
+
var text = el.getAttribute('data-text').replace(/"/g, '"').replace(/</g, '<').replace(/>/g, '>').replace(/&/g, '&');
|
|
3535
3920
|
navigator.clipboard.writeText(text).then(function() {
|
|
3536
3921
|
el.classList.add('copied');
|
|
3537
3922
|
el.querySelector('.copy-hint').textContent = 'copied!';
|
|
@@ -3740,6 +4125,21 @@ function exportConversation() {
|
|
|
3740
4125
|
URL.revokeObjectURL(url);
|
|
3741
4126
|
}
|
|
3742
4127
|
|
|
4128
|
+
function exportJSON() {
|
|
4129
|
+
if (!cachedHistory.length) return;
|
|
4130
|
+
var data = cachedHistory.map(function(m) {
|
|
4131
|
+
return { id: m.id, from: m.from, to: m.to, content: m.content, timestamp: m.timestamp, thread_id: m.thread_id || null };
|
|
4132
|
+
});
|
|
4133
|
+
var json = JSON.stringify(data, null, 2);
|
|
4134
|
+
var blob = new Blob([json], { type: 'application/json' });
|
|
4135
|
+
var url = URL.createObjectURL(blob);
|
|
4136
|
+
var a = document.createElement('a');
|
|
4137
|
+
a.href = url;
|
|
4138
|
+
a.download = 'conversation-' + new Date().toISOString().slice(0, 10) + '.json';
|
|
4139
|
+
a.click();
|
|
4140
|
+
URL.revokeObjectURL(url);
|
|
4141
|
+
}
|
|
4142
|
+
|
|
3743
4143
|
// ==================== VIEW SWITCHING ====================
|
|
3744
4144
|
|
|
3745
4145
|
var activeView = 'messages';
|
|
@@ -3751,16 +4151,19 @@ function switchView(view) {
|
|
|
3751
4151
|
document.getElementById('tab-workspaces').classList.toggle('active', view === 'workspaces');
|
|
3752
4152
|
document.getElementById('tab-workflows').classList.toggle('active', view === 'workflows');
|
|
3753
4153
|
document.getElementById('tab-launch').classList.toggle('active', view === 'launch');
|
|
4154
|
+
document.getElementById('tab-stats').classList.toggle('active', view === 'stats');
|
|
3754
4155
|
document.getElementById('messages').style.display = view === 'messages' ? 'flex' : 'none';
|
|
3755
4156
|
document.getElementById('tasks-area').classList.toggle('visible', view === 'tasks');
|
|
3756
4157
|
document.getElementById('workspaces-area').classList.toggle('visible', view === 'workspaces');
|
|
3757
4158
|
document.getElementById('workflows-area').classList.toggle('visible', view === 'workflows');
|
|
3758
4159
|
document.getElementById('launch-area').classList.toggle('visible', view === 'launch');
|
|
4160
|
+
document.getElementById('stats-area').classList.toggle('visible', view === 'stats');
|
|
3759
4161
|
document.getElementById('search-bar').style.display = view === 'messages' ? 'flex' : 'none';
|
|
3760
4162
|
if (view === 'tasks') fetchTasks();
|
|
3761
4163
|
if (view === 'workspaces') fetchWorkspaces();
|
|
3762
4164
|
if (view === 'workflows') fetchWorkflows();
|
|
3763
4165
|
if (view === 'launch') renderLaunchPanel();
|
|
4166
|
+
if (view === 'stats') fetchStats();
|
|
3764
4167
|
// Auto-close sidebar on mobile after view switch
|
|
3765
4168
|
if (isMobile) closeSidebar();
|
|
3766
4169
|
}
|
|
@@ -3824,7 +4227,7 @@ function renderTasks() {
|
|
|
3824
4227
|
for (var c = 0; c < cols.length; c++) {
|
|
3825
4228
|
var col = cols[c];
|
|
3826
4229
|
var tasks = groups[col.key] || [];
|
|
3827
|
-
html += '<div class="kanban-col">';
|
|
4230
|
+
html += '<div class="kanban-col" data-status="' + col.key + '" ondragover="onKanbanDragOver(event)" ondragleave="onKanbanDragLeave(event)" ondrop="onKanbanDrop(event)">';
|
|
3828
4231
|
html += '<div class="kanban-title ' + col.key + '">' + col.label + '<span class="kanban-count">' + tasks.length + '</span></div>';
|
|
3829
4232
|
for (var j = 0; j < tasks.length; j++) {
|
|
3830
4233
|
html += buildTaskCard(tasks[j]);
|
|
@@ -3850,7 +4253,7 @@ function buildTaskCard(t) {
|
|
|
3850
4253
|
'<option value="blocked"' + (t.status === 'blocked' ? ' selected' : '') + '>Blocked</option>' +
|
|
3851
4254
|
'</select>';
|
|
3852
4255
|
|
|
3853
|
-
return '<div class="task-card">' +
|
|
4256
|
+
return '<div class="task-card" draggable="true" data-task-id="' + t.id + '" ondragstart="onTaskDragStart(event)" ondragend="onTaskDragEnd(event)">' +
|
|
3854
4257
|
'<div class="task-title">' + escapeHtml(t.title || 'Untitled') + '</div>' +
|
|
3855
4258
|
(t.description ? '<div class="task-desc">' + escapeHtml(t.description) + '</div>' : '') +
|
|
3856
4259
|
'<div class="task-footer">' +
|
|
@@ -3871,6 +4274,135 @@ function updateTaskStatus(taskId, newStatus) {
|
|
|
3871
4274
|
}).catch(function(e) { console.error('Task update failed:', e); });
|
|
3872
4275
|
}
|
|
3873
4276
|
|
|
4277
|
+
// ==================== KANBAN DRAG-AND-DROP ====================
|
|
4278
|
+
|
|
4279
|
+
var draggedTaskId = null;
|
|
4280
|
+
|
|
4281
|
+
function onTaskDragStart(e) {
|
|
4282
|
+
draggedTaskId = e.target.getAttribute('data-task-id');
|
|
4283
|
+
e.dataTransfer.effectAllowed = 'move';
|
|
4284
|
+
e.dataTransfer.setData('text/plain', draggedTaskId);
|
|
4285
|
+
e.target.classList.add('dragging');
|
|
4286
|
+
}
|
|
4287
|
+
|
|
4288
|
+
function onTaskDragEnd(e) {
|
|
4289
|
+
e.target.classList.remove('dragging');
|
|
4290
|
+
draggedTaskId = null;
|
|
4291
|
+
var cols = document.querySelectorAll('.kanban-col');
|
|
4292
|
+
for (var i = 0; i < cols.length; i++) cols[i].classList.remove('drag-over');
|
|
4293
|
+
}
|
|
4294
|
+
|
|
4295
|
+
function onKanbanDragOver(e) {
|
|
4296
|
+
e.preventDefault();
|
|
4297
|
+
e.dataTransfer.dropEffect = 'move';
|
|
4298
|
+
var col = e.currentTarget;
|
|
4299
|
+
if (!col.classList.contains('drag-over')) col.classList.add('drag-over');
|
|
4300
|
+
}
|
|
4301
|
+
|
|
4302
|
+
function onKanbanDragLeave(e) {
|
|
4303
|
+
if (!e.currentTarget.contains(e.relatedTarget)) {
|
|
4304
|
+
e.currentTarget.classList.remove('drag-over');
|
|
4305
|
+
}
|
|
4306
|
+
}
|
|
4307
|
+
|
|
4308
|
+
function onKanbanDrop(e) {
|
|
4309
|
+
e.preventDefault();
|
|
4310
|
+
var col = e.currentTarget;
|
|
4311
|
+
col.classList.remove('drag-over');
|
|
4312
|
+
var taskId = e.dataTransfer.getData('text/plain');
|
|
4313
|
+
var newStatus = col.getAttribute('data-status');
|
|
4314
|
+
if (taskId && newStatus) {
|
|
4315
|
+
updateTaskStatus(taskId, newStatus);
|
|
4316
|
+
}
|
|
4317
|
+
}
|
|
4318
|
+
|
|
4319
|
+
// ==================== STATS VIEW ====================
|
|
4320
|
+
|
|
4321
|
+
var AGENT_COLORS = ['#58a6ff', '#f78166', '#7ee787', '#d2a8ff', '#ffa657', '#ff7b72', '#79c0ff', '#56d364'];
|
|
4322
|
+
|
|
4323
|
+
function fetchStats() {
|
|
4324
|
+
var pq = activeProject ? '?project=' + encodeURIComponent(activeProject) : '';
|
|
4325
|
+
fetch('/api/stats' + pq).then(function(r) { return r.json(); }).then(function(data) {
|
|
4326
|
+
renderStats(data);
|
|
4327
|
+
}).catch(function(e) { console.error('Stats fetch failed:', e); });
|
|
4328
|
+
}
|
|
4329
|
+
|
|
4330
|
+
function renderStats(data) {
|
|
4331
|
+
var el = document.getElementById('stats-area');
|
|
4332
|
+
if (!data || !data.total_messages) {
|
|
4333
|
+
el.innerHTML = '<div class="tasks-empty">No messages yet. Stats will appear once agents start talking.</div>';
|
|
4334
|
+
return;
|
|
4335
|
+
}
|
|
4336
|
+
|
|
4337
|
+
var agentNames = Object.keys(data.agents);
|
|
4338
|
+
var maxMessages = 0;
|
|
4339
|
+
for (var i = 0; i < agentNames.length; i++) {
|
|
4340
|
+
if (data.agents[agentNames[i]].messages > maxMessages) maxMessages = data.agents[agentNames[i]].messages;
|
|
4341
|
+
}
|
|
4342
|
+
|
|
4343
|
+
// Overview cards
|
|
4344
|
+
var html = '<div class="stats-grid">';
|
|
4345
|
+
html += '<div class="stat-card"><div class="stat-card-label">Total Messages</div><div class="stat-card-value">' + data.total_messages + '</div></div>';
|
|
4346
|
+
html += '<div class="stat-card"><div class="stat-card-label">Busiest Agent</div><div class="stat-card-value" style="font-size:18px">' + escapeHtml(data.busiest_agent || '-') + '</div></div>';
|
|
4347
|
+
html += '<div class="stat-card"><div class="stat-card-label">Velocity</div><div class="stat-card-value">' + data.velocity_per_min + '</div><div class="stat-card-sub">msgs/min (last 10m)</div></div>';
|
|
4348
|
+
html += '<div class="stat-card"><div class="stat-card-label">Active Agents</div><div class="stat-card-value">' + agentNames.length + '</div></div>';
|
|
4349
|
+
html += '</div>';
|
|
4350
|
+
|
|
4351
|
+
// Per-agent message bars
|
|
4352
|
+
html += '<div class="stats-section"><div class="stats-section-title">Messages per Agent</div>';
|
|
4353
|
+
for (var j = 0; j < agentNames.length; j++) {
|
|
4354
|
+
var name = agentNames[j];
|
|
4355
|
+
var agent = data.agents[name];
|
|
4356
|
+
var pct = maxMessages ? Math.round((agent.messages / maxMessages) * 100) : 0;
|
|
4357
|
+
var color = AGENT_COLORS[j % AGENT_COLORS.length];
|
|
4358
|
+
html += '<div class="stats-bar-row">';
|
|
4359
|
+
html += '<div class="stats-bar-label">' + escapeHtml(name) + '</div>';
|
|
4360
|
+
html += '<div class="stats-bar-track"><div class="stats-bar-fill" style="width:' + pct + '%;background:' + color + '">' + agent.messages + '</div></div>';
|
|
4361
|
+
html += '</div>';
|
|
4362
|
+
}
|
|
4363
|
+
html += '</div>';
|
|
4364
|
+
|
|
4365
|
+
// Per-agent response times
|
|
4366
|
+
var hasResponseTimes = false;
|
|
4367
|
+
for (var k = 0; k < agentNames.length; k++) {
|
|
4368
|
+
if (data.agents[agentNames[k]].avg_response_ms) { hasResponseTimes = true; break; }
|
|
4369
|
+
}
|
|
4370
|
+
if (hasResponseTimes) {
|
|
4371
|
+
html += '<div class="stats-section"><div class="stats-section-title">Avg Response Time</div>';
|
|
4372
|
+
for (var l = 0; l < agentNames.length; l++) {
|
|
4373
|
+
var aName = agentNames[l];
|
|
4374
|
+
var aData = data.agents[aName];
|
|
4375
|
+
if (!aData.avg_response_ms) continue;
|
|
4376
|
+
var secs = (aData.avg_response_ms / 1000).toFixed(1);
|
|
4377
|
+
var maxBar = 60000;
|
|
4378
|
+
var rtPct = Math.min(Math.round((aData.avg_response_ms / maxBar) * 100), 100);
|
|
4379
|
+
var rtColor = AGENT_COLORS[l % AGENT_COLORS.length];
|
|
4380
|
+
html += '<div class="stats-bar-row">';
|
|
4381
|
+
html += '<div class="stats-bar-label">' + escapeHtml(aName) + '</div>';
|
|
4382
|
+
html += '<div class="stats-bar-track"><div class="stats-bar-fill" style="width:' + rtPct + '%;background:' + rtColor + '">' + secs + 's</div></div>';
|
|
4383
|
+
html += '</div>';
|
|
4384
|
+
}
|
|
4385
|
+
html += '</div>';
|
|
4386
|
+
}
|
|
4387
|
+
|
|
4388
|
+
// Hourly distribution chart
|
|
4389
|
+
var maxHour = Math.max.apply(null, data.hour_distribution);
|
|
4390
|
+
html += '<div class="stats-section"><div class="stats-section-title">Activity by Hour</div>';
|
|
4391
|
+
html += '<div class="stats-hour-chart">';
|
|
4392
|
+
for (var h = 0; h < 24; h++) {
|
|
4393
|
+
var hPct = maxHour ? Math.round((data.hour_distribution[h] / maxHour) * 100) : 0;
|
|
4394
|
+
html += '<div class="stats-hour-bar" style="height:' + Math.max(hPct, 2) + '%" title="' + h + ':00 — ' + data.hour_distribution[h] + ' msgs"></div>';
|
|
4395
|
+
}
|
|
4396
|
+
html += '</div>';
|
|
4397
|
+
html += '<div class="stats-hour-labels">';
|
|
4398
|
+
for (var hh = 0; hh < 24; hh++) {
|
|
4399
|
+
html += '<span>' + (hh % 6 === 0 ? hh + 'h' : '') + '</span>';
|
|
4400
|
+
}
|
|
4401
|
+
html += '</div></div>';
|
|
4402
|
+
|
|
4403
|
+
el.innerHTML = html;
|
|
4404
|
+
}
|
|
4405
|
+
|
|
3874
4406
|
// ==================== EXPORT MENU ====================
|
|
3875
4407
|
|
|
3876
4408
|
function toggleExportMenu() {
|
|
@@ -4292,6 +4824,8 @@ function poll() {
|
|
|
4292
4824
|
if (activeView === 'tasks') fetchTasks();
|
|
4293
4825
|
if (activeView === 'workspaces') fetchWorkspaces();
|
|
4294
4826
|
if (activeView === 'workflows') fetchWorkflows();
|
|
4827
|
+
if (activeView === 'stats') fetchStats();
|
|
4828
|
+
fetchReadReceipts();
|
|
4295
4829
|
}).catch(function(e) {
|
|
4296
4830
|
console.error('Poll failed:', e);
|
|
4297
4831
|
document.getElementById('conn-detail').textContent = ' ERR: ' + e.message;
|
|
@@ -4300,7 +4834,7 @@ function poll() {
|
|
|
4300
4834
|
|
|
4301
4835
|
function doReset() {
|
|
4302
4836
|
if (!confirm('Clear all messages, agents, and history?')) return;
|
|
4303
|
-
fetch('/api/reset', { method: 'POST' }).then(function() {
|
|
4837
|
+
fetch('/api/reset' + projectParam(), { method: 'POST' }).then(function() {
|
|
4304
4838
|
lastMessageCount = 0;
|
|
4305
4839
|
activeThread = null;
|
|
4306
4840
|
cachedHistory = [];
|
|
@@ -4436,7 +4970,7 @@ function discoverProjects() {
|
|
|
4436
4970
|
resultsEl.style.display = 'block';
|
|
4437
4971
|
resultsEl.innerHTML = '<div style="font-size:11px;color:var(--text-muted);padding:4px;">Scanning...</div>';
|
|
4438
4972
|
|
|
4439
|
-
fetch('/api/discover').then(function(r) { return r.json(); }).then(function(found) {
|
|
4973
|
+
fetch('/api/discover', { method: 'POST' }).then(function(r) { return r.json(); }).then(function(found) {
|
|
4440
4974
|
if (!found.length) {
|
|
4441
4975
|
resultsEl.innerHTML = '<div style="font-size:11px;color:var(--text-muted);padding:4px;">No new projects found (all discovered projects already added)</div>';
|
|
4442
4976
|
setTimeout(function() { resultsEl.style.display = 'none'; }, 3000);
|
|
@@ -4862,10 +5396,6 @@ function renderLaunchPanel() {
|
|
|
4862
5396
|
templateOpts += '<option value="' + escapeHtml(templates[i].name) + '">' + escapeHtml(templates[i].name) + ' — ' + escapeHtml(templates[i].description) + '</option>';
|
|
4863
5397
|
}
|
|
4864
5398
|
|
|
4865
|
-
var activeProject = '';
|
|
4866
|
-
var projSelect = document.getElementById('project-selector');
|
|
4867
|
-
if (projSelect && projSelect.value) activeProject = projSelect.value;
|
|
4868
|
-
|
|
4869
5399
|
el.innerHTML =
|
|
4870
5400
|
'<div class="launch-panel">' +
|
|
4871
5401
|
'<h3>Launch Agent Terminal</h3>' +
|
|
@@ -4899,11 +5429,16 @@ function renderLaunchPanel() {
|
|
|
4899
5429
|
'</div>' +
|
|
4900
5430
|
'<div class="launch-result" id="launch-result"></div>' +
|
|
4901
5431
|
'</div>';
|
|
5432
|
+
renderConversationTemplates(el);
|
|
4902
5433
|
}).catch(function() {
|
|
4903
5434
|
el.innerHTML = '<div class="launch-panel"><h3>Launch Agent Terminal</h3><p style="color:var(--text-dim)">Failed to load templates.</p></div>';
|
|
4904
5435
|
});
|
|
4905
5436
|
}
|
|
4906
5437
|
|
|
5438
|
+
// ==================== v3.4: CONVERSATION TEMPLATES ====================
|
|
5439
|
+
function renderConversationTemplates(p){fetch('/api/conversation-templates').then(function(r){return r.json()}).then(function(t){if(!t.length)return;var s=document.createElement('div');s.className='launch-panel';s.style.marginTop='20px';var h='<h3 style="margin-bottom:4px">Conversation Templates</h3><p style="font-size:12px;color:var(--text-dim);margin-bottom:16px">Pre-built multi-agent workflows. Click to see agent prompts.</p><div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(220px,1fr));gap:12px">';for(var i=0;i<t.length;i++){var c=t[i];var n=c.agents.map(function(a){return a.name}).join(', ');h+='<div style="background:var(--surface-2);border:1px solid var(--border);border-radius:8px;padding:14px;cursor:pointer;transition:all 0.15s" onclick="showConvTemplate(\''+escapeHtml(c.id)+'\')" onmouseover="this.style.borderColor=\'var(--accent)\'" onmouseout="this.style.borderColor=\'var(--border)\'"><div style="font-weight:600;font-size:13px;margin-bottom:4px">'+escapeHtml(c.name)+'</div><div style="font-size:11px;color:var(--text-dim);margin-bottom:8px">'+escapeHtml(c.description)+'</div><div style="font-size:10px;color:var(--text-muted)">Agents: '+escapeHtml(n)+'</div>'+(c.workflow?'<div style="font-size:10px;color:var(--purple);margin-top:4px">Workflow: '+escapeHtml(c.workflow.steps.join(' \u2192 '))+'</div>':'')+'</div>'}h+='</div><div id="conv-template-detail" style="margin-top:16px"></div>';s.innerHTML=h;p.appendChild(s)}).catch(function(){})}
|
|
5440
|
+
function showConvTemplate(tid){var pq=activeProject?'?project='+encodeURIComponent(activeProject):'';fetch('/api/conversation-templates/launch'+pq,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({template_id:tid})}).then(function(r){return r.json()}).then(function(d){if(d.error){alert(d.error);return}var el=document.getElementById('conv-template-detail');var h='<div style="background:var(--surface);border:1px solid var(--border);border-radius:8px;padding:16px"><div style="font-weight:700;font-size:15px;margin-bottom:4px">'+escapeHtml(d.template.name)+'</div><div style="font-size:12px;color:var(--text-dim);margin-bottom:12px">'+escapeHtml(d.template.description)+'</div>';if(d.template.workflow){h+='<div style="display:flex;gap:6px;align-items:center;margin-bottom:14px;flex-wrap:wrap">';var st=d.template.workflow.steps;for(var s=0;s<st.length;s++){if(s>0)h+='<span style="color:var(--text-muted);font-size:12px">\u2192</span>';h+='<span style="background:var(--purple-dim);color:var(--purple);padding:3px 10px;border-radius:12px;font-size:11px;font-weight:600">'+escapeHtml(st[s])+'</span>'}h+='</div>'}h+='<div style="font-weight:600;font-size:12px;color:var(--text-muted);text-transform:uppercase;letter-spacing:0.5px;margin-bottom:8px">Agent Prompts (copy each into a separate terminal)</div>';for(var i=0;i<d.instructions.length;i++){var inst=d.instructions[i];h+='<div style="margin-bottom:10px"><div style="display:flex;align-items:center;gap:8px;margin-bottom:4px"><span style="font-weight:600;font-size:13px">'+escapeHtml(inst.agent_name)+'</span><span class="role-badge">'+escapeHtml(inst.role)+'</span></div><div class="copy-block" onclick="copyText(this)" data-text="'+inst.prompt.replace(/&/g,'&').replace(/"/g,'"').replace(/</g,'<').replace(/>/g,'>')+'"><span class="copy-hint">click to copy</span><span style="font-size:11px;color:var(--text-dim)">'+escapeHtml(inst.prompt.substring(0,200))+(inst.prompt.length>200?'...':'')+'</span></div></div>'}h+='</div>';el.innerHTML=h;el.scrollIntoView({behavior:'smooth',block:'nearest'})})}
|
|
5441
|
+
|
|
4907
5442
|
function selectCli(cli) {
|
|
4908
5443
|
selectedCli = cli;
|
|
4909
5444
|
var btns = document.querySelectorAll('.cli-btn');
|
|
@@ -4980,6 +5515,27 @@ function copyLaunchPrompt() {
|
|
|
4980
5515
|
// ==================== SSE + POLLING ====================
|
|
4981
5516
|
|
|
4982
5517
|
var sseConnected = false;
|
|
5518
|
+
var sseRetryDelay = 1000;
|
|
5519
|
+
var sseRetryTimer = null;
|
|
5520
|
+
var ssePollFallback = null;
|
|
5521
|
+
|
|
5522
|
+
function setConnStatus(status) {
|
|
5523
|
+
var dot = document.querySelector('.conn-dot');
|
|
5524
|
+
var label = document.getElementById('conn-label');
|
|
5525
|
+
if (status === 'live') {
|
|
5526
|
+
dot.style.background = 'var(--green)';
|
|
5527
|
+
dot.style.animation = 'pulse 2s infinite';
|
|
5528
|
+
label.textContent = 'Live (SSE)';
|
|
5529
|
+
} else if (status === 'reconnecting') {
|
|
5530
|
+
dot.style.background = 'var(--yellow, #f0ad4e)';
|
|
5531
|
+
dot.style.animation = 'pulse 0.8s infinite';
|
|
5532
|
+
label.textContent = 'Reconnecting...';
|
|
5533
|
+
} else if (status === 'poll') {
|
|
5534
|
+
dot.style.background = 'var(--green)';
|
|
5535
|
+
dot.style.animation = 'pulse 2s infinite';
|
|
5536
|
+
label.textContent = 'Live (poll)';
|
|
5537
|
+
}
|
|
5538
|
+
}
|
|
4983
5539
|
|
|
4984
5540
|
function initSSE() {
|
|
4985
5541
|
try {
|
|
@@ -4991,20 +5547,29 @@ function initSSE() {
|
|
|
4991
5547
|
};
|
|
4992
5548
|
eventSource.onopen = function() {
|
|
4993
5549
|
sseConnected = true;
|
|
4994
|
-
|
|
5550
|
+
sseRetryDelay = 1000;
|
|
5551
|
+
if (sseRetryTimer) { clearTimeout(sseRetryTimer); sseRetryTimer = null; }
|
|
5552
|
+
if (ssePollFallback) { clearInterval(ssePollFallback); ssePollFallback = null; }
|
|
5553
|
+
setConnStatus('live');
|
|
4995
5554
|
};
|
|
4996
5555
|
eventSource.onerror = function() {
|
|
4997
|
-
|
|
4998
|
-
|
|
4999
|
-
|
|
5000
|
-
|
|
5001
|
-
|
|
5002
|
-
setInterval(poll, POLL_INTERVAL);
|
|
5556
|
+
sseConnected = false;
|
|
5557
|
+
eventSource.close();
|
|
5558
|
+
console.warn('SSE disconnected, retrying in ' + sseRetryDelay + 'ms');
|
|
5559
|
+
setConnStatus('reconnecting');
|
|
5560
|
+
if (!ssePollFallback) {
|
|
5561
|
+
ssePollFallback = setInterval(poll, POLL_INTERVAL);
|
|
5003
5562
|
}
|
|
5563
|
+
sseRetryTimer = setTimeout(function() {
|
|
5564
|
+
initSSE();
|
|
5565
|
+
}, sseRetryDelay);
|
|
5566
|
+
sseRetryDelay = Math.min(sseRetryDelay * 2, 30000);
|
|
5004
5567
|
};
|
|
5005
5568
|
} catch (e) {
|
|
5006
|
-
|
|
5007
|
-
|
|
5569
|
+
setConnStatus('poll');
|
|
5570
|
+
if (!ssePollFallback) {
|
|
5571
|
+
ssePollFallback = setInterval(poll, POLL_INTERVAL);
|
|
5572
|
+
}
|
|
5008
5573
|
}
|
|
5009
5574
|
}
|
|
5010
5575
|
|
|
@@ -5020,6 +5585,9 @@ function initSSE() {
|
|
|
5020
5585
|
}
|
|
5021
5586
|
})();
|
|
5022
5587
|
|
|
5588
|
+
// Init UI preferences
|
|
5589
|
+
initCompactMode();
|
|
5590
|
+
|
|
5023
5591
|
// Load projects first, then poll (so auto-select works before first data fetch)
|
|
5024
5592
|
loadProjects().then(function() {
|
|
5025
5593
|
console.log('[LTT] init: projects loaded, starting poll + SSE');
|