agentgui 1.0.77 → 1.0.79
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/CLAUDE.md +98 -1874
- package/package.json +2 -4
- package/static/index.html +261 -52
- package/static/js/conversations.js +160 -0
- package/.prd +0 -238
- package/.prd-browser +0 -607
- package/browser-test.js +0 -409
- package/run-e2e-test.sh +0 -88
- package/test-browser.js +0 -457
- package/test-runner.js +0 -182
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agentgui",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.79",
|
|
4
4
|
"description": "Multi-agent ACP client with real-time communication",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "server.js",
|
|
@@ -25,7 +25,5 @@
|
|
|
25
25
|
"better-sqlite3": "^12.6.2",
|
|
26
26
|
"ws": "^8.14.2"
|
|
27
27
|
},
|
|
28
|
-
"devDependencies": {
|
|
29
|
-
"playwright": "^1.58.1"
|
|
30
|
-
}
|
|
28
|
+
"devDependencies": {}
|
|
31
29
|
}
|
package/static/index.html
CHANGED
|
@@ -58,6 +58,124 @@
|
|
|
58
58
|
gap: 0;
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
+
.layout-with-sidebar {
|
|
62
|
+
display: grid;
|
|
63
|
+
grid-template-columns: 300px 1fr;
|
|
64
|
+
gap: 0;
|
|
65
|
+
min-height: 0;
|
|
66
|
+
flex: 1;
|
|
67
|
+
overflow: hidden;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/* Sidebar */
|
|
71
|
+
.sidebar {
|
|
72
|
+
display: flex;
|
|
73
|
+
flex-direction: column;
|
|
74
|
+
background-color: var(--color-bg-secondary);
|
|
75
|
+
border-right: 1px solid var(--color-border);
|
|
76
|
+
overflow: hidden;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
.sidebar-header {
|
|
80
|
+
padding: 1rem;
|
|
81
|
+
border-bottom: 1px solid var(--color-border);
|
|
82
|
+
display: flex;
|
|
83
|
+
justify-content: space-between;
|
|
84
|
+
align-items: center;
|
|
85
|
+
flex-shrink: 0;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.sidebar-header h2 {
|
|
89
|
+
margin: 0;
|
|
90
|
+
font-size: 0.875rem;
|
|
91
|
+
font-weight: 600;
|
|
92
|
+
text-transform: uppercase;
|
|
93
|
+
letter-spacing: 0.05em;
|
|
94
|
+
color: var(--color-text-secondary);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
.sidebar-new-btn {
|
|
98
|
+
padding: 0.25rem 0.5rem;
|
|
99
|
+
font-size: 0.75rem;
|
|
100
|
+
background-color: var(--color-primary);
|
|
101
|
+
color: white;
|
|
102
|
+
border: none;
|
|
103
|
+
border-radius: 0.25rem;
|
|
104
|
+
cursor: pointer;
|
|
105
|
+
transition: background-color 0.2s;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
.sidebar-new-btn:hover {
|
|
109
|
+
background-color: var(--color-primary-dark);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
.sidebar-list {
|
|
113
|
+
flex: 1;
|
|
114
|
+
overflow-y: auto;
|
|
115
|
+
overflow-x: hidden;
|
|
116
|
+
list-style: none;
|
|
117
|
+
margin: 0;
|
|
118
|
+
padding: 0.5rem 0;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
.conversation-item {
|
|
122
|
+
padding: 0.75rem 0.5rem;
|
|
123
|
+
margin: 0.25rem 0.5rem;
|
|
124
|
+
border-radius: 0.375rem;
|
|
125
|
+
cursor: pointer;
|
|
126
|
+
transition: background-color 0.2s, transform 0.1s;
|
|
127
|
+
border-left: 3px solid transparent;
|
|
128
|
+
user-select: none;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
.conversation-item:hover {
|
|
132
|
+
background-color: var(--color-bg-primary);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
.conversation-item.active {
|
|
136
|
+
background-color: var(--color-primary);
|
|
137
|
+
color: white;
|
|
138
|
+
border-left-color: var(--color-primary-dark);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
.conversation-item-title {
|
|
142
|
+
font-weight: 500;
|
|
143
|
+
font-size: 0.875rem;
|
|
144
|
+
margin: 0;
|
|
145
|
+
white-space: nowrap;
|
|
146
|
+
overflow: hidden;
|
|
147
|
+
text-overflow: ellipsis;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
.conversation-item-meta {
|
|
151
|
+
font-size: 0.75rem;
|
|
152
|
+
color: var(--color-text-secondary);
|
|
153
|
+
margin-top: 0.25rem;
|
|
154
|
+
white-space: nowrap;
|
|
155
|
+
overflow: hidden;
|
|
156
|
+
text-overflow: ellipsis;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
.conversation-item.active .conversation-item-meta {
|
|
160
|
+
color: rgba(255, 255, 255, 0.7);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
.sidebar-empty {
|
|
164
|
+
padding: 2rem 1rem;
|
|
165
|
+
text-align: center;
|
|
166
|
+
color: var(--color-text-secondary);
|
|
167
|
+
font-size: 0.875rem;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/* Main content area - flex column with full height */
|
|
171
|
+
.main-content {
|
|
172
|
+
display: flex;
|
|
173
|
+
flex-direction: column;
|
|
174
|
+
overflow: hidden;
|
|
175
|
+
flex: 1;
|
|
176
|
+
min-height: 0;
|
|
177
|
+
}
|
|
178
|
+
|
|
61
179
|
header {
|
|
62
180
|
background-color: var(--color-bg-secondary);
|
|
63
181
|
border-bottom: 1px solid var(--color-border);
|
|
@@ -66,6 +184,8 @@
|
|
|
66
184
|
justify-content: space-between;
|
|
67
185
|
align-items: center;
|
|
68
186
|
gap: 1rem;
|
|
187
|
+
flex-shrink: 0;
|
|
188
|
+
height: 60px;
|
|
69
189
|
}
|
|
70
190
|
|
|
71
191
|
.header-title {
|
|
@@ -79,6 +199,7 @@
|
|
|
79
199
|
display: flex;
|
|
80
200
|
gap: 1rem;
|
|
81
201
|
align-items: center;
|
|
202
|
+
flex-shrink: 0;
|
|
82
203
|
}
|
|
83
204
|
|
|
84
205
|
.status-badge {
|
|
@@ -120,24 +241,34 @@
|
|
|
120
241
|
}
|
|
121
242
|
}
|
|
122
243
|
|
|
123
|
-
/*
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
244
|
+
/* Output area - scrollable, fills available space */
|
|
245
|
+
#output-scroll {
|
|
246
|
+
flex: 1;
|
|
247
|
+
overflow-y: auto;
|
|
248
|
+
overflow-x: hidden;
|
|
249
|
+
display: flex;
|
|
250
|
+
flex-direction: column;
|
|
129
251
|
min-height: 0;
|
|
130
|
-
|
|
252
|
+
padding: 1rem;
|
|
131
253
|
}
|
|
132
254
|
|
|
255
|
+
#output {
|
|
256
|
+
display: flex;
|
|
257
|
+
flex-direction: column;
|
|
258
|
+
gap: 0.5rem;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/* Execution panel - sticky at bottom */
|
|
133
262
|
.execution-panel {
|
|
134
263
|
background-color: var(--color-bg-secondary);
|
|
135
|
-
border-
|
|
264
|
+
border-top: 1px solid var(--color-border);
|
|
136
265
|
padding: 1.5rem;
|
|
137
266
|
display: grid;
|
|
138
267
|
grid-template-columns: 1fr auto;
|
|
139
268
|
gap: 1rem;
|
|
140
269
|
align-items: end;
|
|
270
|
+
flex-shrink: 0;
|
|
271
|
+
min-height: auto;
|
|
141
272
|
}
|
|
142
273
|
|
|
143
274
|
.execution-input-group {
|
|
@@ -172,6 +303,7 @@
|
|
|
172
303
|
cursor: pointer;
|
|
173
304
|
transition: all 0.2s;
|
|
174
305
|
font-size: 0.875rem;
|
|
306
|
+
flex-shrink: 0;
|
|
175
307
|
}
|
|
176
308
|
|
|
177
309
|
.btn-primary {
|
|
@@ -198,23 +330,6 @@
|
|
|
198
330
|
cursor: pointer;
|
|
199
331
|
}
|
|
200
332
|
|
|
201
|
-
/* Output area */
|
|
202
|
-
#output-scroll {
|
|
203
|
-
overflow-y: auto;
|
|
204
|
-
overflow-x: hidden;
|
|
205
|
-
display: flex;
|
|
206
|
-
flex-direction: column;
|
|
207
|
-
min-height: 0;
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
#output {
|
|
211
|
-
flex: 1;
|
|
212
|
-
padding: 1rem;
|
|
213
|
-
display: flex;
|
|
214
|
-
flex-direction: column;
|
|
215
|
-
gap: 0.5rem;
|
|
216
|
-
}
|
|
217
|
-
|
|
218
333
|
/* Event cards */
|
|
219
334
|
.card {
|
|
220
335
|
background-color: var(--color-bg-primary);
|
|
@@ -246,16 +361,93 @@
|
|
|
246
361
|
|
|
247
362
|
/* Responsive */
|
|
248
363
|
@media (max-width: 768px) {
|
|
364
|
+
.layout-with-sidebar {
|
|
365
|
+
grid-template-columns: 1fr;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
.sidebar {
|
|
369
|
+
display: none;
|
|
370
|
+
position: fixed;
|
|
371
|
+
top: 60px;
|
|
372
|
+
left: 0;
|
|
373
|
+
width: 250px;
|
|
374
|
+
height: calc(100vh - 60px);
|
|
375
|
+
z-index: 1000;
|
|
376
|
+
overflow-y: auto;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
.sidebar.mobile-visible {
|
|
380
|
+
display: flex;
|
|
381
|
+
}
|
|
382
|
+
|
|
249
383
|
.execution-panel {
|
|
250
384
|
grid-template-columns: 1fr;
|
|
385
|
+
padding: 1rem;
|
|
251
386
|
}
|
|
252
387
|
|
|
253
388
|
.header-controls {
|
|
254
389
|
flex-wrap: wrap;
|
|
390
|
+
gap: 0.5rem;
|
|
255
391
|
}
|
|
256
392
|
|
|
257
393
|
.execution-input-group {
|
|
258
394
|
grid-template-columns: 1fr;
|
|
395
|
+
gap: 0.5rem;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
#output-scroll {
|
|
399
|
+
padding: 0.75rem;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
header {
|
|
403
|
+
padding: 0.75rem;
|
|
404
|
+
height: 50px;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
.header-title {
|
|
408
|
+
font-size: 1.25rem;
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
@media (max-width: 480px) {
|
|
413
|
+
.layout-with-sidebar {
|
|
414
|
+
grid-template-columns: 1fr;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
.sidebar {
|
|
418
|
+
width: 100%;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
.execution-panel {
|
|
422
|
+
padding: 0.75rem;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
.execution-textarea {
|
|
426
|
+
min-height: 2.5rem;
|
|
427
|
+
max-height: 6rem;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
.btn {
|
|
431
|
+
padding: 0.5rem 1rem;
|
|
432
|
+
font-size: 0.8rem;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
#output-scroll {
|
|
436
|
+
padding: 0.5rem;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
header {
|
|
440
|
+
padding: 0.5rem;
|
|
441
|
+
height: 45px;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
.header-title {
|
|
445
|
+
font-size: 1rem;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
.status-badge {
|
|
449
|
+
padding: 0.25rem 0.5rem;
|
|
450
|
+
font-size: 0.75rem;
|
|
259
451
|
}
|
|
260
452
|
}
|
|
261
453
|
|
|
@@ -440,34 +632,48 @@
|
|
|
440
632
|
</div>
|
|
441
633
|
</header>
|
|
442
634
|
|
|
443
|
-
<!-- Main Content -->
|
|
444
|
-
<
|
|
445
|
-
<!--
|
|
446
|
-
<
|
|
447
|
-
<div class="
|
|
448
|
-
<
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
635
|
+
<!-- Main Content with Sidebar -->
|
|
636
|
+
<div class="layout-with-sidebar">
|
|
637
|
+
<!-- Sidebar -->
|
|
638
|
+
<aside class="sidebar" data-sidebar>
|
|
639
|
+
<div class="sidebar-header">
|
|
640
|
+
<h2>History</h2>
|
|
641
|
+
<button class="sidebar-new-btn" data-new-conversation title="Start new conversation">+ New</button>
|
|
642
|
+
</div>
|
|
643
|
+
<ul class="sidebar-list" data-conversation-list>
|
|
644
|
+
<li class="sidebar-empty" data-conversation-empty>No conversations yet</li>
|
|
645
|
+
</ul>
|
|
646
|
+
</aside>
|
|
647
|
+
|
|
648
|
+
<!-- Main Content Area -->
|
|
649
|
+
<main id="app" class="main-content">
|
|
650
|
+
<!-- Output Container - Scrollable -->
|
|
651
|
+
<div id="output-scroll" role="region" aria-label="Execution output" aria-live="polite" aria-atomic="false">
|
|
652
|
+
<div id="output" class="output"></div>
|
|
459
653
|
</div>
|
|
460
654
|
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
655
|
+
<!-- Execution Panel - Fixed at bottom -->
|
|
656
|
+
<div class="execution-panel">
|
|
657
|
+
<div class="execution-input-group">
|
|
658
|
+
<select class="agent-selector" data-agent-selector title="Select agent">
|
|
659
|
+
<option value="claude-code">Claude Code</option>
|
|
660
|
+
<option value="opencode">OpenCode</option>
|
|
661
|
+
</select>
|
|
662
|
+
|
|
663
|
+
<textarea
|
|
664
|
+
class="execution-textarea"
|
|
665
|
+
data-message-input
|
|
666
|
+
placeholder="Enter command or prompt... (Ctrl+Enter to submit)"
|
|
667
|
+
aria-label="Execution prompt input"
|
|
668
|
+
></textarea>
|
|
669
|
+
</div>
|
|
670
|
+
|
|
671
|
+
<button class="btn btn-primary" data-send-button title="Start execution (Ctrl+Enter)">
|
|
672
|
+
Execute
|
|
673
|
+
</button>
|
|
674
|
+
</div>
|
|
675
|
+
</main>
|
|
676
|
+
</div>
|
|
471
677
|
</div>
|
|
472
678
|
|
|
473
679
|
<!-- Scripts - Order matters! -->
|
|
@@ -489,7 +695,10 @@
|
|
|
489
695
|
<!-- 6. UI Components (no dependencies) -->
|
|
490
696
|
<script src="/gm/js/ui-components.js"></script>
|
|
491
697
|
|
|
492
|
-
<!-- 7.
|
|
698
|
+
<!-- 7. Conversations Manager (loads conversation history) -->
|
|
699
|
+
<script src="/gm/js/conversations.js"></script>
|
|
700
|
+
|
|
701
|
+
<!-- 8. Main Client (depends on all above) -->
|
|
493
702
|
<script src="/gm/js/client.js"></script>
|
|
494
703
|
|
|
495
704
|
<!-- Inline initialization -->
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Conversations Module
|
|
3
|
+
* Manages conversation list sidebar with real-time updates
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
class ConversationManager {
|
|
7
|
+
constructor() {
|
|
8
|
+
this.conversations = [];
|
|
9
|
+
this.activeId = null;
|
|
10
|
+
this.listEl = document.querySelector('[data-conversation-list]');
|
|
11
|
+
this.emptyEl = document.querySelector('[data-conversation-empty]');
|
|
12
|
+
this.newBtn = document.querySelector('[data-new-conversation]');
|
|
13
|
+
this.sidebarEl = document.querySelector('[data-sidebar]');
|
|
14
|
+
|
|
15
|
+
if (!this.listEl) return;
|
|
16
|
+
|
|
17
|
+
this.init();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async init() {
|
|
21
|
+
this.newBtn?.addEventListener('click', () => this.createNew());
|
|
22
|
+
this.loadConversations();
|
|
23
|
+
this.setupWebSocketListener();
|
|
24
|
+
|
|
25
|
+
// Auto-refresh every 30 seconds
|
|
26
|
+
setInterval(() => this.loadConversations(), 30000);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async loadConversations() {
|
|
30
|
+
try {
|
|
31
|
+
const res = await fetch('/gm/api/conversations');
|
|
32
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
33
|
+
|
|
34
|
+
const data = await res.json();
|
|
35
|
+
this.conversations = data.conversations || [];
|
|
36
|
+
this.render();
|
|
37
|
+
} catch (err) {
|
|
38
|
+
console.error('Failed to load conversations:', err);
|
|
39
|
+
this.showEmpty('Failed to load conversations');
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
render() {
|
|
44
|
+
if (!this.listEl) return;
|
|
45
|
+
|
|
46
|
+
if (this.conversations.length === 0) {
|
|
47
|
+
this.showEmpty();
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
this.listEl.innerHTML = '';
|
|
52
|
+
this.emptyEl.style.display = 'none';
|
|
53
|
+
|
|
54
|
+
// Sort by most recent first
|
|
55
|
+
const sorted = [...this.conversations].sort((a, b) =>
|
|
56
|
+
new Date(b.createdAt || 0) - new Date(a.createdAt || 0)
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
sorted.forEach(conv => {
|
|
60
|
+
const item = this.createConversationItem(conv);
|
|
61
|
+
this.listEl.appendChild(item);
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
createConversationItem(conv) {
|
|
66
|
+
const li = document.createElement('li');
|
|
67
|
+
li.className = 'conversation-item';
|
|
68
|
+
if (conv.id === this.activeId) li.classList.add('active');
|
|
69
|
+
|
|
70
|
+
const title = conv.title || `Conversation ${conv.id.slice(0, 8)}`;
|
|
71
|
+
const timestamp = conv.createdAt ? new Date(conv.createdAt).toLocaleDateString() : 'Unknown';
|
|
72
|
+
const agent = conv.agentId || 'unknown';
|
|
73
|
+
|
|
74
|
+
li.innerHTML = `
|
|
75
|
+
<div class="conversation-item-title">${this.escapeHtml(title)}</div>
|
|
76
|
+
<div class="conversation-item-meta">${agent} • ${timestamp}</div>
|
|
77
|
+
`;
|
|
78
|
+
|
|
79
|
+
li.addEventListener('click', () => this.select(conv.id));
|
|
80
|
+
return li;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
select(convId) {
|
|
84
|
+
this.activeId = convId;
|
|
85
|
+
|
|
86
|
+
// Update active indicator
|
|
87
|
+
document.querySelectorAll('.conversation-item').forEach(item => {
|
|
88
|
+
item.classList.remove('active');
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
const active = document.querySelector(`[data-conv-id="${convId}"]`);
|
|
92
|
+
if (active) active.classList.add('active');
|
|
93
|
+
|
|
94
|
+
// Emit event for client.js to handle
|
|
95
|
+
window.dispatchEvent(new CustomEvent('conversation-selected', {
|
|
96
|
+
detail: { conversationId: convId }
|
|
97
|
+
}));
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
createNew() {
|
|
101
|
+
window.dispatchEvent(new CustomEvent('create-new-conversation'));
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
showEmpty(message = 'No conversations yet') {
|
|
105
|
+
if (!this.listEl) return;
|
|
106
|
+
this.listEl.innerHTML = '';
|
|
107
|
+
this.emptyEl.textContent = message;
|
|
108
|
+
this.emptyEl.style.display = 'block';
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
addConversation(conv) {
|
|
112
|
+
// Add to beginning (most recent)
|
|
113
|
+
this.conversations.unshift(conv);
|
|
114
|
+
this.render();
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
updateConversation(convId, updates) {
|
|
118
|
+
const conv = this.conversations.find(c => c.id === convId);
|
|
119
|
+
if (conv) {
|
|
120
|
+
Object.assign(conv, updates);
|
|
121
|
+
this.render();
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
deleteConversation(convId) {
|
|
126
|
+
this.conversations = this.conversations.filter(c => c.id !== convId);
|
|
127
|
+
if (this.activeId === convId) this.activeId = null;
|
|
128
|
+
this.render();
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
setupWebSocketListener() {
|
|
132
|
+
window.addEventListener('ws-message', (event) => {
|
|
133
|
+
const msg = event.detail;
|
|
134
|
+
|
|
135
|
+
if (msg.type === 'conversation_created') {
|
|
136
|
+
this.addConversation(msg.conversation);
|
|
137
|
+
} else if (msg.type === 'conversation_updated') {
|
|
138
|
+
this.updateConversation(msg.conversation.id, msg.conversation);
|
|
139
|
+
} else if (msg.type === 'conversation_deleted') {
|
|
140
|
+
this.deleteConversation(msg.conversationId);
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
escapeHtml(text) {
|
|
146
|
+
const map = { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' };
|
|
147
|
+
return text.replace(/[&<>"']/g, c => map[c]);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Initialize when DOM is ready
|
|
152
|
+
if (document.readyState === 'loading') {
|
|
153
|
+
document.addEventListener('DOMContentLoaded', () => {
|
|
154
|
+
window.conversationManager = new ConversationManager();
|
|
155
|
+
});
|
|
156
|
+
} else {
|
|
157
|
+
window.conversationManager = new ConversationManager();
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export { ConversationManager };
|