clay-server 2.18.0-beta.9 → 2.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/project.js +1445 -32
- package/lib/public/app.js +309 -125
- package/lib/public/css/debate.css +1039 -0
- package/lib/public/css/mates.css +125 -0
- package/lib/public/css/scheduler-modal.css +110 -392
- package/lib/public/css/scheduler.css +10 -3
- package/lib/public/css/sidebar.css +80 -7
- package/lib/public/index.html +53 -106
- package/lib/public/modules/debate.js +633 -0
- package/lib/public/modules/input.js +14 -5
- package/lib/public/modules/mate-sidebar.js +169 -2
- package/lib/public/modules/mention.js +6 -3
- package/lib/public/modules/scheduler.js +373 -252
- package/lib/public/modules/sidebar.js +158 -28
- package/lib/public/modules/tools.js +13 -3
- package/lib/public/modules/tooltip.js +2 -0
- package/lib/public/style.css +1 -0
- package/lib/scheduler.js +59 -1
- package/lib/sessions.js +9 -2
- package/lib/user-presence.js +92 -0
- package/package.json +1 -1
|
@@ -0,0 +1,633 @@
|
|
|
1
|
+
import { mateAvatarUrl } from './avatar.js';
|
|
2
|
+
import { renderMarkdown, highlightCodeBlocks } from './markdown.js';
|
|
3
|
+
import { escapeHtml } from './utils.js';
|
|
4
|
+
import { iconHtml, refreshIcons } from './icons.js';
|
|
5
|
+
|
|
6
|
+
var ctx;
|
|
7
|
+
|
|
8
|
+
// --- State ---
|
|
9
|
+
var debateActive = false;
|
|
10
|
+
var debateTopic = "";
|
|
11
|
+
var debateRound = 0;
|
|
12
|
+
var debatePhase = "idle"; // idle | live | ended
|
|
13
|
+
|
|
14
|
+
// Current turn streaming state
|
|
15
|
+
var currentTurnEl = null;
|
|
16
|
+
var currentTurnMateId = null;
|
|
17
|
+
var turnFullText = "";
|
|
18
|
+
var turnStreamBuffer = "";
|
|
19
|
+
var turnDrainTimer = null;
|
|
20
|
+
|
|
21
|
+
// --- Init ---
|
|
22
|
+
export function initDebate(_ctx) {
|
|
23
|
+
ctx = _ctx;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function buildAvatarUrl(meta) {
|
|
27
|
+
return "https://api.dicebear.com/7.x/" + (meta.avatarStyle || "bottts") + "/svg?seed=" + encodeURIComponent(meta.avatarSeed || meta.mateId || "mate");
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// --- Float info panel ---
|
|
31
|
+
function showDebateInfoFloat(msg) {
|
|
32
|
+
var floatEl = document.getElementById("debate-info-float");
|
|
33
|
+
if (!floatEl) return;
|
|
34
|
+
|
|
35
|
+
var html = '<div class="debate-info-float-inner">';
|
|
36
|
+
html += '<span class="debate-info-mod">' + iconHtml("mic") + ' ' + escapeHtml(msg.moderatorName || "Moderator") + '</span>';
|
|
37
|
+
html += '<span class="debate-info-sep">|</span>';
|
|
38
|
+
html += '<span class="debate-info-label">Panel:</span>';
|
|
39
|
+
|
|
40
|
+
if (msg.panelists) {
|
|
41
|
+
for (var i = 0; i < msg.panelists.length; i++) {
|
|
42
|
+
var p = msg.panelists[i];
|
|
43
|
+
if (i > 0) html += '<span class="debate-info-comma">,</span>';
|
|
44
|
+
html += '<span class="debate-info-chip">';
|
|
45
|
+
html += '<img class="debate-info-avatar" src="' + buildAvatarUrl(p) + '" width="14" height="14" />';
|
|
46
|
+
html += '<span>' + escapeHtml(p.name || "") + '</span>';
|
|
47
|
+
if (p.role) html += '<span class="debate-info-role">(' + escapeHtml(p.role) + ')</span>';
|
|
48
|
+
html += '</span>';
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
html += '</div>';
|
|
53
|
+
floatEl.innerHTML = html;
|
|
54
|
+
floatEl.classList.remove("hidden");
|
|
55
|
+
refreshIcons();
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function hideDebateInfoFloat() {
|
|
59
|
+
var floatEl = document.getElementById("debate-info-float");
|
|
60
|
+
if (floatEl) {
|
|
61
|
+
floatEl.classList.add("hidden");
|
|
62
|
+
floatEl.innerHTML = "";
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// --- Handlers ---
|
|
67
|
+
|
|
68
|
+
export function handleDebateResumed(msg) {
|
|
69
|
+
debateActive = true;
|
|
70
|
+
debatePhase = "live";
|
|
71
|
+
if (msg.topic) debateTopic = msg.topic;
|
|
72
|
+
if (msg.round) debateRound = msg.round;
|
|
73
|
+
|
|
74
|
+
// Show float info panel again if we have it
|
|
75
|
+
showDebateInfoFloat(msg);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function handleDebateStarted(msg) {
|
|
79
|
+
debateActive = true;
|
|
80
|
+
debateTopic = msg.topic || "";
|
|
81
|
+
debateRound = 1;
|
|
82
|
+
debatePhase = "live";
|
|
83
|
+
|
|
84
|
+
// Show float info panel
|
|
85
|
+
showDebateInfoFloat(msg);
|
|
86
|
+
|
|
87
|
+
if (ctx.scrollToBottom) ctx.scrollToBottom();
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function handleDebateTurn(msg) {
|
|
91
|
+
debateRound = msg.round || debateRound;
|
|
92
|
+
|
|
93
|
+
if (!ctx.messagesEl) return;
|
|
94
|
+
|
|
95
|
+
var turnEl = document.createElement("div");
|
|
96
|
+
turnEl.className = "debate-turn";
|
|
97
|
+
|
|
98
|
+
// Speaker header
|
|
99
|
+
var speakerRow = document.createElement("div");
|
|
100
|
+
speakerRow.className = "debate-speaker";
|
|
101
|
+
|
|
102
|
+
var avi = document.createElement("img");
|
|
103
|
+
avi.className = "debate-speaker-avatar";
|
|
104
|
+
avi.src = buildAvatarUrl(msg);
|
|
105
|
+
avi.width = 24;
|
|
106
|
+
avi.height = 24;
|
|
107
|
+
speakerRow.appendChild(avi);
|
|
108
|
+
|
|
109
|
+
var nameSpan = document.createElement("span");
|
|
110
|
+
nameSpan.className = "debate-speaker-name";
|
|
111
|
+
nameSpan.textContent = msg.mateName || "Speaker";
|
|
112
|
+
speakerRow.appendChild(nameSpan);
|
|
113
|
+
|
|
114
|
+
var roleSpan = document.createElement("span");
|
|
115
|
+
roleSpan.className = "debate-speaker-role";
|
|
116
|
+
roleSpan.textContent = msg.role || "";
|
|
117
|
+
speakerRow.appendChild(roleSpan);
|
|
118
|
+
|
|
119
|
+
turnEl.appendChild(speakerRow);
|
|
120
|
+
|
|
121
|
+
// Activity indicator
|
|
122
|
+
var activityDiv = document.createElement("div");
|
|
123
|
+
activityDiv.className = "activity-inline debate-activity-bar";
|
|
124
|
+
activityDiv.innerHTML =
|
|
125
|
+
'<span class="activity-icon">' + iconHtml("sparkles") + '</span>' +
|
|
126
|
+
'<span class="activity-text">Thinking...</span>';
|
|
127
|
+
turnEl.appendChild(activityDiv);
|
|
128
|
+
|
|
129
|
+
// Content area
|
|
130
|
+
var contentDiv = document.createElement("div");
|
|
131
|
+
contentDiv.className = "md-content debate-turn-content";
|
|
132
|
+
contentDiv.dir = "auto";
|
|
133
|
+
turnEl.appendChild(contentDiv);
|
|
134
|
+
|
|
135
|
+
ctx.messagesEl.appendChild(turnEl);
|
|
136
|
+
|
|
137
|
+
// Set as current streaming target
|
|
138
|
+
currentTurnEl = turnEl;
|
|
139
|
+
currentTurnMateId = msg.mateId;
|
|
140
|
+
turnFullText = "";
|
|
141
|
+
turnStreamBuffer = "";
|
|
142
|
+
|
|
143
|
+
refreshIcons();
|
|
144
|
+
if (ctx.scrollToBottom) ctx.scrollToBottom();
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export function handleDebateActivity(msg) {
|
|
148
|
+
if (!currentTurnEl || msg.mateId !== currentTurnMateId) return;
|
|
149
|
+
|
|
150
|
+
var bar = currentTurnEl.querySelector(".debate-activity-bar");
|
|
151
|
+
if (msg.activity) {
|
|
152
|
+
if (!bar) {
|
|
153
|
+
bar = document.createElement("div");
|
|
154
|
+
bar.className = "activity-inline debate-activity-bar";
|
|
155
|
+
bar.innerHTML =
|
|
156
|
+
'<span class="activity-icon">' + iconHtml("sparkles") + '</span>' +
|
|
157
|
+
'<span class="activity-text"></span>';
|
|
158
|
+
var contentEl = currentTurnEl.querySelector(".debate-turn-content");
|
|
159
|
+
if (contentEl) {
|
|
160
|
+
currentTurnEl.insertBefore(bar, contentEl);
|
|
161
|
+
} else {
|
|
162
|
+
currentTurnEl.appendChild(bar);
|
|
163
|
+
}
|
|
164
|
+
refreshIcons();
|
|
165
|
+
}
|
|
166
|
+
var textEl = bar.querySelector(".activity-text");
|
|
167
|
+
if (textEl) {
|
|
168
|
+
textEl.textContent = msg.activity === "thinking" ? "Thinking..." : msg.activity;
|
|
169
|
+
}
|
|
170
|
+
bar.style.display = "";
|
|
171
|
+
} else {
|
|
172
|
+
if (bar) bar.style.display = "none";
|
|
173
|
+
}
|
|
174
|
+
if (ctx.scrollToBottom) ctx.scrollToBottom();
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export function handleDebateStream(msg) {
|
|
178
|
+
if (!currentTurnEl || msg.mateId !== currentTurnMateId) return;
|
|
179
|
+
|
|
180
|
+
// Hide activity bar on first text
|
|
181
|
+
var bar = currentTurnEl.querySelector(".debate-activity-bar");
|
|
182
|
+
if (bar) bar.style.display = "none";
|
|
183
|
+
|
|
184
|
+
turnStreamBuffer += msg.delta;
|
|
185
|
+
if (!turnDrainTimer) {
|
|
186
|
+
turnDrainTimer = requestAnimationFrame(drainTurnStream);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function drainTurnStream() {
|
|
191
|
+
turnDrainTimer = null;
|
|
192
|
+
if (!currentTurnEl || turnStreamBuffer.length === 0) return;
|
|
193
|
+
|
|
194
|
+
var len = turnStreamBuffer.length;
|
|
195
|
+
var n;
|
|
196
|
+
if (len > 200) n = Math.ceil(len / 4);
|
|
197
|
+
else if (len > 80) n = 8;
|
|
198
|
+
else if (len > 30) n = 5;
|
|
199
|
+
else if (len > 10) n = 2;
|
|
200
|
+
else n = 1;
|
|
201
|
+
|
|
202
|
+
var chunk = turnStreamBuffer.slice(0, n);
|
|
203
|
+
turnStreamBuffer = turnStreamBuffer.slice(n);
|
|
204
|
+
turnFullText += chunk;
|
|
205
|
+
|
|
206
|
+
var contentEl = currentTurnEl.querySelector(".debate-turn-content");
|
|
207
|
+
if (contentEl) {
|
|
208
|
+
contentEl.innerHTML = renderMarkdown(turnFullText);
|
|
209
|
+
highlightCodeBlocks(contentEl);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if (ctx.scrollToBottom) ctx.scrollToBottom();
|
|
213
|
+
|
|
214
|
+
if (turnStreamBuffer.length > 0) {
|
|
215
|
+
turnDrainTimer = requestAnimationFrame(drainTurnStream);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function flushTurnStream() {
|
|
220
|
+
if (turnDrainTimer) {
|
|
221
|
+
cancelAnimationFrame(turnDrainTimer);
|
|
222
|
+
turnDrainTimer = null;
|
|
223
|
+
}
|
|
224
|
+
if (turnStreamBuffer.length > 0) {
|
|
225
|
+
turnFullText += turnStreamBuffer;
|
|
226
|
+
turnStreamBuffer = "";
|
|
227
|
+
}
|
|
228
|
+
if (currentTurnEl) {
|
|
229
|
+
var contentEl = currentTurnEl.querySelector(".debate-turn-content");
|
|
230
|
+
if (contentEl) {
|
|
231
|
+
contentEl.innerHTML = renderMarkdown(turnFullText);
|
|
232
|
+
highlightCodeBlocks(contentEl);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
export function handleDebateTurnDone(msg) {
|
|
238
|
+
flushTurnStream();
|
|
239
|
+
|
|
240
|
+
if (currentTurnEl) {
|
|
241
|
+
var bar = currentTurnEl.querySelector(".debate-activity-bar");
|
|
242
|
+
if (bar) bar.style.display = "none";
|
|
243
|
+
if (ctx.addCopyHandler && turnFullText) {
|
|
244
|
+
ctx.addCopyHandler(currentTurnEl, turnFullText);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
currentTurnEl = null;
|
|
249
|
+
currentTurnMateId = null;
|
|
250
|
+
turnFullText = "";
|
|
251
|
+
if (ctx.scrollToBottom) ctx.scrollToBottom();
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
export function handleDebateCommentQueued(msg) {
|
|
255
|
+
if (!ctx.messagesEl) return;
|
|
256
|
+
|
|
257
|
+
var commentEl = document.createElement("div");
|
|
258
|
+
commentEl.className = "debate-user-comment";
|
|
259
|
+
|
|
260
|
+
var label = document.createElement("span");
|
|
261
|
+
label.className = "debate-comment-label";
|
|
262
|
+
label.innerHTML = iconHtml("hand") + " You raised your hand:";
|
|
263
|
+
|
|
264
|
+
var textEl = document.createElement("div");
|
|
265
|
+
textEl.className = "debate-comment-text";
|
|
266
|
+
textEl.textContent = msg.text || "";
|
|
267
|
+
|
|
268
|
+
commentEl.appendChild(label);
|
|
269
|
+
commentEl.appendChild(textEl);
|
|
270
|
+
ctx.messagesEl.appendChild(commentEl);
|
|
271
|
+
|
|
272
|
+
refreshIcons();
|
|
273
|
+
if (ctx.scrollToBottom) ctx.scrollToBottom();
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
export function handleDebateCommentInjected(msg) {
|
|
277
|
+
// Comment was delivered to moderator, no extra UI needed
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
export function handleDebateEnded(msg) {
|
|
281
|
+
debateActive = false;
|
|
282
|
+
debatePhase = "ended";
|
|
283
|
+
|
|
284
|
+
flushTurnStream();
|
|
285
|
+
currentTurnEl = null;
|
|
286
|
+
currentTurnMateId = null;
|
|
287
|
+
|
|
288
|
+
// Hide float info panel
|
|
289
|
+
hideDebateInfoFloat();
|
|
290
|
+
|
|
291
|
+
if (ctx.messagesEl) {
|
|
292
|
+
renderEndedBanner(msg);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
if (ctx.scrollToBottom) ctx.scrollToBottom();
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
function renderEndedBanner(entry) {
|
|
299
|
+
if (!ctx.messagesEl) return;
|
|
300
|
+
|
|
301
|
+
// Remove existing ended banner (prevent duplicates)
|
|
302
|
+
var existing = ctx.messagesEl.querySelector(".debate-ended-banner");
|
|
303
|
+
if (existing) existing.remove();
|
|
304
|
+
|
|
305
|
+
var endBanner = document.createElement("div");
|
|
306
|
+
endBanner.className = "debate-ended-banner";
|
|
307
|
+
|
|
308
|
+
var reasonText = entry.reason === "natural" ? "Debate concluded" :
|
|
309
|
+
entry.reason === "user_stopped" ? "Debate stopped by user" :
|
|
310
|
+
"Debate ended due to error";
|
|
311
|
+
|
|
312
|
+
var statusRow = document.createElement("div");
|
|
313
|
+
statusRow.className = "debate-ended-status";
|
|
314
|
+
statusRow.innerHTML = iconHtml("check-circle") + " " + escapeHtml(reasonText) + " (" + (entry.rounds || 0) + " rounds)";
|
|
315
|
+
endBanner.appendChild(statusRow);
|
|
316
|
+
|
|
317
|
+
// Resume row
|
|
318
|
+
var resumeRow = document.createElement("div");
|
|
319
|
+
resumeRow.className = "debate-ended-resume";
|
|
320
|
+
|
|
321
|
+
var resumeInput = document.createElement("textarea");
|
|
322
|
+
resumeInput.className = "debate-ended-resume-input";
|
|
323
|
+
resumeInput.rows = 1;
|
|
324
|
+
resumeInput.placeholder = "Continue with a new direction...";
|
|
325
|
+
resumeRow.appendChild(resumeInput);
|
|
326
|
+
|
|
327
|
+
var resumeBtn = document.createElement("button");
|
|
328
|
+
resumeBtn.className = "debate-ended-resume-btn";
|
|
329
|
+
resumeBtn.textContent = "Resume";
|
|
330
|
+
resumeBtn.addEventListener("click", function () {
|
|
331
|
+
var text = resumeInput.value.trim();
|
|
332
|
+
if (ctx.ws && ctx.ws.readyState === 1) {
|
|
333
|
+
ctx.ws.send(JSON.stringify({ type: "debate_conclude_response", action: "continue", text: text }));
|
|
334
|
+
}
|
|
335
|
+
endBanner.remove();
|
|
336
|
+
});
|
|
337
|
+
resumeRow.appendChild(resumeBtn);
|
|
338
|
+
|
|
339
|
+
endBanner.appendChild(resumeRow);
|
|
340
|
+
|
|
341
|
+
// Enter in textarea = resume
|
|
342
|
+
resumeInput.addEventListener("keydown", function (e) {
|
|
343
|
+
if (e.key === "Enter" && !e.shiftKey) {
|
|
344
|
+
e.preventDefault();
|
|
345
|
+
resumeBtn.click();
|
|
346
|
+
}
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
ctx.messagesEl.appendChild(endBanner);
|
|
350
|
+
refreshIcons();
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
export function handleDebateError(msg) {
|
|
354
|
+
if (ctx.messagesEl && debateActive) {
|
|
355
|
+
var errEl = document.createElement("div");
|
|
356
|
+
errEl.className = "debate-error";
|
|
357
|
+
errEl.textContent = "Error: " + (msg.error || "Unknown error");
|
|
358
|
+
ctx.messagesEl.appendChild(errEl);
|
|
359
|
+
if (ctx.scrollToBottom) ctx.scrollToBottom();
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// --- History replay ---
|
|
364
|
+
export function renderDebateStarted(entry) {
|
|
365
|
+
handleDebateStarted(entry);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
export function renderDebateTurnDone(entry) {
|
|
369
|
+
if (!ctx.messagesEl) return;
|
|
370
|
+
|
|
371
|
+
var turnEl = document.createElement("div");
|
|
372
|
+
turnEl.className = "debate-turn";
|
|
373
|
+
|
|
374
|
+
var speakerRow = document.createElement("div");
|
|
375
|
+
speakerRow.className = "debate-speaker";
|
|
376
|
+
|
|
377
|
+
if (entry.avatarStyle || entry.avatarSeed || entry.mateId) {
|
|
378
|
+
var avi = document.createElement("img");
|
|
379
|
+
avi.className = "debate-speaker-avatar";
|
|
380
|
+
avi.src = buildAvatarUrl(entry);
|
|
381
|
+
avi.width = 24;
|
|
382
|
+
avi.height = 24;
|
|
383
|
+
speakerRow.appendChild(avi);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
var nameSpan = document.createElement("span");
|
|
387
|
+
nameSpan.className = "debate-speaker-name";
|
|
388
|
+
nameSpan.textContent = entry.mateName || "Speaker";
|
|
389
|
+
speakerRow.appendChild(nameSpan);
|
|
390
|
+
|
|
391
|
+
var roleSpan = document.createElement("span");
|
|
392
|
+
roleSpan.className = "debate-speaker-role";
|
|
393
|
+
roleSpan.textContent = entry.role || "";
|
|
394
|
+
speakerRow.appendChild(roleSpan);
|
|
395
|
+
|
|
396
|
+
turnEl.appendChild(speakerRow);
|
|
397
|
+
|
|
398
|
+
var contentDiv = document.createElement("div");
|
|
399
|
+
contentDiv.className = "md-content debate-turn-content";
|
|
400
|
+
contentDiv.dir = "auto";
|
|
401
|
+
contentDiv.innerHTML = renderMarkdown(entry.text || "");
|
|
402
|
+
highlightCodeBlocks(contentDiv);
|
|
403
|
+
turnEl.appendChild(contentDiv);
|
|
404
|
+
|
|
405
|
+
ctx.messagesEl.appendChild(turnEl);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
export function renderDebateUserResume(entry) {
|
|
409
|
+
if (!ctx.messagesEl) return;
|
|
410
|
+
|
|
411
|
+
// Remove the ended banner since we're resuming
|
|
412
|
+
var endedBanner = ctx.messagesEl.querySelector(".debate-ended-banner");
|
|
413
|
+
if (endedBanner) endedBanner.remove();
|
|
414
|
+
|
|
415
|
+
// Also remove conclude confirm if present
|
|
416
|
+
var confirmEl = document.getElementById("debate-conclude-confirm");
|
|
417
|
+
if (confirmEl) confirmEl.remove();
|
|
418
|
+
|
|
419
|
+
var el = document.createElement("div");
|
|
420
|
+
el.className = "debate-user-comment";
|
|
421
|
+
|
|
422
|
+
var label = document.createElement("span");
|
|
423
|
+
label.className = "debate-comment-label";
|
|
424
|
+
label.innerHTML = iconHtml("play") + " Debate resumed:";
|
|
425
|
+
|
|
426
|
+
var textEl = document.createElement("div");
|
|
427
|
+
textEl.className = "debate-comment-text";
|
|
428
|
+
textEl.textContent = entry.text || "";
|
|
429
|
+
|
|
430
|
+
el.appendChild(label);
|
|
431
|
+
el.appendChild(textEl);
|
|
432
|
+
ctx.messagesEl.appendChild(el);
|
|
433
|
+
refreshIcons();
|
|
434
|
+
if (ctx.scrollToBottom) ctx.scrollToBottom();
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
export function renderDebateEnded(entry) {
|
|
438
|
+
if (!ctx.messagesEl) return;
|
|
439
|
+
|
|
440
|
+
hideDebateInfoFloat();
|
|
441
|
+
renderEndedBanner(entry);
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
export function renderDebateCommentInjected(entry) {
|
|
445
|
+
if (!ctx.messagesEl) return;
|
|
446
|
+
|
|
447
|
+
var commentEl = document.createElement("div");
|
|
448
|
+
commentEl.className = "debate-user-comment";
|
|
449
|
+
|
|
450
|
+
var label = document.createElement("span");
|
|
451
|
+
label.className = "debate-comment-label";
|
|
452
|
+
label.innerHTML = iconHtml("hand") + " User comment:";
|
|
453
|
+
|
|
454
|
+
var textEl = document.createElement("div");
|
|
455
|
+
textEl.className = "debate-comment-text";
|
|
456
|
+
textEl.textContent = entry.text || "";
|
|
457
|
+
|
|
458
|
+
commentEl.appendChild(label);
|
|
459
|
+
commentEl.appendChild(textEl);
|
|
460
|
+
ctx.messagesEl.appendChild(commentEl);
|
|
461
|
+
refreshIcons();
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
export function isDebateActive() {
|
|
465
|
+
return debateActive;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
// --- Debate modal ---
|
|
469
|
+
var modalEl = null;
|
|
470
|
+
var selectedPanelists = [];
|
|
471
|
+
|
|
472
|
+
export function openDebateModal() {
|
|
473
|
+
modalEl = document.getElementById("debate-modal");
|
|
474
|
+
if (!modalEl) return;
|
|
475
|
+
|
|
476
|
+
modalEl.classList.remove("hidden");
|
|
477
|
+
|
|
478
|
+
var topicInput = document.getElementById("debate-topic-input");
|
|
479
|
+
if (topicInput) {
|
|
480
|
+
topicInput.value = "";
|
|
481
|
+
topicInput.focus();
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
// Populate panelist list from mates (exclude current mate = moderator)
|
|
485
|
+
var panelList = document.getElementById("debate-panel-list");
|
|
486
|
+
if (panelList) {
|
|
487
|
+
panelList.innerHTML = "";
|
|
488
|
+
selectedPanelists = [];
|
|
489
|
+
var mates = ctx.matesList ? ctx.matesList() : [];
|
|
490
|
+
var currentMateId = ctx.currentMateId ? ctx.currentMateId() : null;
|
|
491
|
+
for (var i = 0; i < mates.length; i++) {
|
|
492
|
+
var m = mates[i];
|
|
493
|
+
if (m.status === "interviewing") continue;
|
|
494
|
+
if (m.id === currentMateId) continue; // moderator, skip
|
|
495
|
+
var item = createPanelItem(m);
|
|
496
|
+
panelList.appendChild(item);
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
// Close button
|
|
501
|
+
var closeBtn = document.getElementById("debate-modal-close");
|
|
502
|
+
if (closeBtn) {
|
|
503
|
+
closeBtn.onclick = closeDebateModal;
|
|
504
|
+
}
|
|
505
|
+
var cancelBtn = document.getElementById("debate-modal-cancel");
|
|
506
|
+
if (cancelBtn) {
|
|
507
|
+
cancelBtn.onclick = closeDebateModal;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
// Backdrop click to close
|
|
511
|
+
var backdrop = modalEl.querySelector(".debate-modal-backdrop");
|
|
512
|
+
if (backdrop) {
|
|
513
|
+
backdrop.onclick = closeDebateModal;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
// Start button
|
|
517
|
+
var startBtn = document.getElementById("debate-modal-start");
|
|
518
|
+
if (startBtn) {
|
|
519
|
+
startBtn.onclick = function () {
|
|
520
|
+
var topic = topicInput ? topicInput.value.trim() : "";
|
|
521
|
+
if (!topic) {
|
|
522
|
+
topicInput.focus();
|
|
523
|
+
return;
|
|
524
|
+
}
|
|
525
|
+
if (selectedPanelists.length === 0) return;
|
|
526
|
+
|
|
527
|
+
var currentMateId = ctx.currentMateId ? ctx.currentMateId() : null;
|
|
528
|
+
if (!currentMateId) return;
|
|
529
|
+
|
|
530
|
+
// Create a new session first, then send debate_start after switch
|
|
531
|
+
if (ctx.ws) {
|
|
532
|
+
var debatePayload = {
|
|
533
|
+
type: "debate_start",
|
|
534
|
+
moderatorId: currentMateId,
|
|
535
|
+
topic: topic,
|
|
536
|
+
panelists: selectedPanelists.map(function (id) {
|
|
537
|
+
return { mateId: id, role: "", brief: "" };
|
|
538
|
+
}),
|
|
539
|
+
};
|
|
540
|
+
|
|
541
|
+
// Listen for session_switched once, then send debate_start
|
|
542
|
+
var onMessage = function (evt) {
|
|
543
|
+
try {
|
|
544
|
+
var data = JSON.parse(evt.data);
|
|
545
|
+
if (data.type === "session_switched") {
|
|
546
|
+
ctx.ws.removeEventListener("message", onMessage);
|
|
547
|
+
ctx.ws.send(JSON.stringify(debatePayload));
|
|
548
|
+
}
|
|
549
|
+
} catch (e) {}
|
|
550
|
+
};
|
|
551
|
+
ctx.ws.addEventListener("message", onMessage);
|
|
552
|
+
ctx.ws.send(JSON.stringify({ type: "new_session" }));
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
closeDebateModal();
|
|
556
|
+
};
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
refreshIcons();
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
function createPanelItem(mate) {
|
|
563
|
+
var item = document.createElement("div");
|
|
564
|
+
item.className = "debate-panel-item";
|
|
565
|
+
item.dataset.mateId = mate.id;
|
|
566
|
+
|
|
567
|
+
var cb = document.createElement("input");
|
|
568
|
+
cb.type = "checkbox";
|
|
569
|
+
item.appendChild(cb);
|
|
570
|
+
|
|
571
|
+
var avatarSrc = "https://api.dicebear.com/7.x/" +
|
|
572
|
+
((mate.profile && mate.profile.avatarStyle) || "bottts") +
|
|
573
|
+
"/svg?seed=" + encodeURIComponent((mate.profile && mate.profile.avatarSeed) || mate.id);
|
|
574
|
+
var avi = document.createElement("img");
|
|
575
|
+
avi.className = "debate-panel-item-avatar";
|
|
576
|
+
avi.src = avatarSrc;
|
|
577
|
+
item.appendChild(avi);
|
|
578
|
+
|
|
579
|
+
var info = document.createElement("div");
|
|
580
|
+
info.className = "debate-panel-item-info";
|
|
581
|
+
|
|
582
|
+
var nameSpan = document.createElement("div");
|
|
583
|
+
nameSpan.className = "debate-panel-item-name";
|
|
584
|
+
nameSpan.textContent = (mate.profile && mate.profile.displayName) || mate.name || "Mate";
|
|
585
|
+
info.appendChild(nameSpan);
|
|
586
|
+
|
|
587
|
+
if (mate.bio) {
|
|
588
|
+
var bioSpan = document.createElement("div");
|
|
589
|
+
bioSpan.className = "debate-panel-item-bio";
|
|
590
|
+
bioSpan.textContent = mate.bio;
|
|
591
|
+
info.appendChild(bioSpan);
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
item.appendChild(info);
|
|
595
|
+
|
|
596
|
+
// Toggle selection
|
|
597
|
+
function toggle() {
|
|
598
|
+
var idx = selectedPanelists.indexOf(mate.id);
|
|
599
|
+
if (idx === -1) {
|
|
600
|
+
selectedPanelists.push(mate.id);
|
|
601
|
+
item.classList.add("selected");
|
|
602
|
+
cb.checked = true;
|
|
603
|
+
} else {
|
|
604
|
+
selectedPanelists.splice(idx, 1);
|
|
605
|
+
item.classList.remove("selected");
|
|
606
|
+
cb.checked = false;
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
item.addEventListener("click", function (e) {
|
|
611
|
+
if (e.target === cb) return; // let checkbox handle itself
|
|
612
|
+
toggle();
|
|
613
|
+
});
|
|
614
|
+
cb.addEventListener("change", function () {
|
|
615
|
+
var idx = selectedPanelists.indexOf(mate.id);
|
|
616
|
+
if (cb.checked && idx === -1) {
|
|
617
|
+
selectedPanelists.push(mate.id);
|
|
618
|
+
item.classList.add("selected");
|
|
619
|
+
} else if (!cb.checked && idx !== -1) {
|
|
620
|
+
selectedPanelists.splice(idx, 1);
|
|
621
|
+
item.classList.remove("selected");
|
|
622
|
+
}
|
|
623
|
+
});
|
|
624
|
+
|
|
625
|
+
return item;
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
export function closeDebateModal() {
|
|
629
|
+
if (modalEl) {
|
|
630
|
+
modalEl.classList.add("hidden");
|
|
631
|
+
}
|
|
632
|
+
selectedPanelists = [];
|
|
633
|
+
}
|
|
@@ -14,6 +14,15 @@ var slashFiltered = [];
|
|
|
14
14
|
var isComposing = false;
|
|
15
15
|
var isRemoteInput = false;
|
|
16
16
|
|
|
17
|
+
export function hasSendableContent() {
|
|
18
|
+
return !!(
|
|
19
|
+
(ctx && ctx.inputEl && ctx.inputEl.value.trim()) ||
|
|
20
|
+
pendingPastes.length > 0 ||
|
|
21
|
+
pendingImages.length > 0 ||
|
|
22
|
+
pendingFiles.length > 0
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
|
|
17
26
|
export var builtinCommands = [
|
|
18
27
|
{ name: "clear", desc: "Clear conversation" },
|
|
19
28
|
{ name: "context", desc: "Context window usage" },
|
|
@@ -145,7 +154,7 @@ export function autoResize() {
|
|
|
145
154
|
ctx.inputEl.style.height = Math.min(ctx.inputEl.scrollHeight, 120) + "px";
|
|
146
155
|
// Defensive: sync send/stop button whenever input size changes
|
|
147
156
|
if (ctx.processing && ctx.setSendBtnMode) {
|
|
148
|
-
ctx.setSendBtnMode(
|
|
157
|
+
ctx.setSendBtnMode(hasSendableContent() ? "send" : "stop");
|
|
149
158
|
}
|
|
150
159
|
}
|
|
151
160
|
|
|
@@ -494,7 +503,7 @@ export function handleInputSync(text) {
|
|
|
494
503
|
isRemoteInput = false;
|
|
495
504
|
// Sync send/stop button state
|
|
496
505
|
if (ctx.processing && ctx.setSendBtnMode) {
|
|
497
|
-
ctx.setSendBtnMode(
|
|
506
|
+
ctx.setSendBtnMode(hasSendableContent() ? "send" : "stop");
|
|
498
507
|
}
|
|
499
508
|
}
|
|
500
509
|
|
|
@@ -639,7 +648,7 @@ export function initInput(_ctx) {
|
|
|
639
648
|
}
|
|
640
649
|
// Toggle send/stop button based on input content during processing
|
|
641
650
|
if (ctx.processing && ctx.setSendBtnMode) {
|
|
642
|
-
ctx.setSendBtnMode(
|
|
651
|
+
ctx.setSendBtnMode(hasSendableContent() ? "send" : "stop");
|
|
643
652
|
}
|
|
644
653
|
});
|
|
645
654
|
|
|
@@ -712,9 +721,9 @@ export function initInput(_ctx) {
|
|
|
712
721
|
ctx.inputEl.setAttribute("enterkeyhint", "enter");
|
|
713
722
|
}
|
|
714
723
|
|
|
715
|
-
// Send/Stop button — if
|
|
724
|
+
// Send/Stop button — if sendable content exists, always send; otherwise stop
|
|
716
725
|
ctx.sendBtn.addEventListener("click", function () {
|
|
717
|
-
if (
|
|
726
|
+
if (hasSendableContent()) {
|
|
718
727
|
sendMessage();
|
|
719
728
|
return;
|
|
720
729
|
}
|