clay-server 2.27.0-beta.8 → 2.27.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -0
- package/lib/daemon-projects.js +164 -0
- package/lib/daemon.js +13 -126
- package/lib/mates-identity.js +132 -0
- package/lib/mates-knowledge.js +113 -0
- package/lib/mates-prompts.js +398 -0
- package/lib/mates.js +40 -599
- package/lib/project-connection.js +2 -0
- package/lib/project-debate.js +19 -12
- package/lib/project-http.js +4 -2
- package/lib/project-loop.js +110 -48
- package/lib/project-mate-interaction.js +4 -0
- package/lib/project-notifications.js +210 -0
- package/lib/project-sessions.js +5 -2
- package/lib/project-user-message.js +2 -1
- package/lib/project.js +26 -2
- package/lib/public/app.js +1193 -8521
- package/lib/public/css/command-palette.css +14 -0
- package/lib/public/css/loop.css +301 -0
- package/lib/public/css/notifications-center.css +190 -0
- package/lib/public/css/rewind.css +6 -0
- package/lib/public/index.html +89 -35
- package/lib/public/modules/app-connection.js +160 -0
- package/lib/public/modules/app-cursors.js +473 -0
- package/lib/public/modules/app-debate-ui.js +389 -0
- package/lib/public/modules/app-dm.js +627 -0
- package/lib/public/modules/app-favicon.js +212 -0
- package/lib/public/modules/app-header.js +229 -0
- package/lib/public/modules/app-home-hub.js +600 -0
- package/lib/public/modules/app-loop-ui.js +589 -0
- package/lib/public/modules/app-loop-wizard.js +439 -0
- package/lib/public/modules/app-messages.js +1560 -0
- package/lib/public/modules/app-misc.js +299 -0
- package/lib/public/modules/app-notifications.js +372 -0
- package/lib/public/modules/app-panels.js +888 -0
- package/lib/public/modules/app-projects.js +798 -0
- package/lib/public/modules/app-rate-limit.js +451 -0
- package/lib/public/modules/app-rendering.js +597 -0
- package/lib/public/modules/app-skills-install.js +234 -0
- package/lib/public/modules/command-palette.js +27 -4
- package/lib/public/modules/input.js +31 -20
- package/lib/public/modules/scheduler-config.js +1532 -0
- package/lib/public/modules/scheduler-history.js +79 -0
- package/lib/public/modules/scheduler.js +33 -1554
- package/lib/public/modules/session-search.js +13 -1
- package/lib/public/modules/sidebar-mates.js +812 -0
- package/lib/public/modules/sidebar-mobile.js +1269 -0
- package/lib/public/modules/sidebar-projects.js +1449 -0
- package/lib/public/modules/sidebar-sessions.js +986 -0
- package/lib/public/modules/sidebar.js +232 -4591
- package/lib/public/modules/store.js +27 -0
- package/lib/public/modules/ws-ref.js +7 -0
- package/lib/public/style.css +1 -0
- package/lib/sdk-bridge.js +96 -717
- package/lib/sdk-message-processor.js +587 -0
- package/lib/sdk-message-queue.js +42 -0
- package/lib/sdk-skill-discovery.js +131 -0
- package/lib/server-admin.js +712 -0
- package/lib/server-auth.js +737 -0
- package/lib/server-dm.js +221 -0
- package/lib/server-mates.js +281 -0
- package/lib/server-palette.js +110 -0
- package/lib/server-settings.js +479 -0
- package/lib/server-skills.js +280 -0
- package/lib/server.js +246 -2755
- package/lib/sessions.js +11 -4
- package/lib/users-auth.js +146 -0
- package/lib/users-permissions.js +118 -0
- package/lib/users-preferences.js +210 -0
- package/lib/users.js +48 -398
- package/lib/ws-schema.js +498 -0
- package/package.json +1 -1
|
@@ -0,0 +1,597 @@
|
|
|
1
|
+
// app-rendering.js - Message rendering, streaming, scroll management, system messages
|
|
2
|
+
// Extracted from app.js (PR-28)
|
|
3
|
+
|
|
4
|
+
import { store } from './store.js';
|
|
5
|
+
|
|
6
|
+
var _ctx = null;
|
|
7
|
+
|
|
8
|
+
// --- Module-owned state (not in store) ---
|
|
9
|
+
var turnCounter = 0;
|
|
10
|
+
var prependAnchor = null;
|
|
11
|
+
var activityEl = null;
|
|
12
|
+
var matePreThinkingTimer = null;
|
|
13
|
+
var highlightTimer = null;
|
|
14
|
+
var streamBuffer = "";
|
|
15
|
+
var streamDrainTimer = null;
|
|
16
|
+
var isUserScrolledUp = false;
|
|
17
|
+
var scrollThreshold = 150;
|
|
18
|
+
|
|
19
|
+
export function initRendering(ctx) {
|
|
20
|
+
_ctx = ctx;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// --- State accessors (module-local, not in store) ---
|
|
24
|
+
export function getTurnCounter() { return turnCounter; }
|
|
25
|
+
export function setTurnCounter(v) { turnCounter = v; }
|
|
26
|
+
export function getPrependAnchor() { return prependAnchor; }
|
|
27
|
+
export function setPrependAnchor(v) { prependAnchor = v; }
|
|
28
|
+
export function getActivityEl() { return activityEl; }
|
|
29
|
+
export function setActivityEl(v) { activityEl = v; }
|
|
30
|
+
export function getIsUserScrolledUp() { return isUserScrolledUp; }
|
|
31
|
+
export function setIsUserScrolledUp(v) { isUserScrolledUp = v; }
|
|
32
|
+
|
|
33
|
+
// --- Rendering functions ---
|
|
34
|
+
|
|
35
|
+
export function addToMessages(el) {
|
|
36
|
+
var messagesEl = _ctx.messagesEl;
|
|
37
|
+
if (prependAnchor) messagesEl.insertBefore(el, prependAnchor);
|
|
38
|
+
else messagesEl.appendChild(el);
|
|
39
|
+
var _sme = _ctx.getScheduledMsgEl();
|
|
40
|
+
if (_sme && el !== _sme && _sme.parentNode === messagesEl) {
|
|
41
|
+
messagesEl.appendChild(_sme);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function scrollToBottom() {
|
|
46
|
+
if (prependAnchor) return;
|
|
47
|
+
if (isUserScrolledUp) {
|
|
48
|
+
_ctx.newMsgBtn.textContent = _ctx.newMsgBtnActivity;
|
|
49
|
+
_ctx.newMsgBtn.classList.remove("hidden");
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
var messagesEl = _ctx.messagesEl;
|
|
53
|
+
requestAnimationFrame(function () {
|
|
54
|
+
messagesEl.scrollTop = messagesEl.scrollHeight;
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function forceScrollToBottom() {
|
|
59
|
+
if (prependAnchor) return;
|
|
60
|
+
isUserScrolledUp = false;
|
|
61
|
+
_ctx.newMsgBtn.classList.add("hidden");
|
|
62
|
+
_ctx.newMsgBtn.textContent = _ctx.newMsgBtnDefault;
|
|
63
|
+
var messagesEl = _ctx.messagesEl;
|
|
64
|
+
requestAnimationFrame(function () {
|
|
65
|
+
messagesEl.scrollTop = messagesEl.scrollHeight;
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function getMsgTime() {
|
|
70
|
+
var _ts = store.getState().currentMsgTs;
|
|
71
|
+
var d = _ts ? new Date(_ts) : new Date();
|
|
72
|
+
var time = String(d.getHours()).padStart(2, "0") + ":" + String(d.getMinutes()).padStart(2, "0");
|
|
73
|
+
var now = new Date();
|
|
74
|
+
if (d.getFullYear() === now.getFullYear() && d.getMonth() === now.getMonth() && d.getDate() === now.getDate()) {
|
|
75
|
+
return time;
|
|
76
|
+
}
|
|
77
|
+
return (d.getMonth() + 1) + "/" + d.getDate() + " " + time;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function shouldGroupMessage(senderClass) {
|
|
81
|
+
var _s = store.getState();
|
|
82
|
+
if (_s.replayingHistory && !_s.currentMsgTs) return false;
|
|
83
|
+
var prev = _ctx.messagesEl.lastElementChild;
|
|
84
|
+
if (!prev || !prev.classList.contains(senderClass)) return false;
|
|
85
|
+
var prevTime = prev.querySelector(".dm-bubble-time");
|
|
86
|
+
if (!prevTime) return false;
|
|
87
|
+
return prevTime.textContent === getMsgTime();
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function ensureAssistantBlock() {
|
|
91
|
+
var _el = store.getState().currentMsgEl;
|
|
92
|
+
if (!_el) {
|
|
93
|
+
_el = document.createElement("div");
|
|
94
|
+
_el.className = "msg-assistant";
|
|
95
|
+
_el.dataset.turn = turnCounter;
|
|
96
|
+
|
|
97
|
+
var grouped = shouldGroupMessage("msg-assistant");
|
|
98
|
+
if (grouped) _el.classList.add("grouped");
|
|
99
|
+
|
|
100
|
+
var _isDm2 = document.body.classList.contains("mate-dm-active") && document.body.dataset.mateAvatarUrl;
|
|
101
|
+
var avi = document.createElement("img");
|
|
102
|
+
avi.className = "dm-bubble-avatar dm-bubble-avatar-mate";
|
|
103
|
+
avi.src = _isDm2 ? document.body.dataset.mateAvatarUrl : _ctx.CLAUDE_CODE_AVATAR;
|
|
104
|
+
_el.appendChild(avi);
|
|
105
|
+
|
|
106
|
+
var contentWrap = document.createElement("div");
|
|
107
|
+
contentWrap.className = "dm-bubble-content";
|
|
108
|
+
|
|
109
|
+
var header = document.createElement("div");
|
|
110
|
+
header.className = "dm-bubble-header";
|
|
111
|
+
var nameSpan = document.createElement("span");
|
|
112
|
+
nameSpan.className = "dm-bubble-name";
|
|
113
|
+
nameSpan.textContent = _isDm2 ? ((_ctx.getDmTargetUser() && _ctx.getDmTargetUser().displayName) || "Mate") : "Claude Code";
|
|
114
|
+
header.appendChild(nameSpan);
|
|
115
|
+
var timeSpan = document.createElement("span");
|
|
116
|
+
timeSpan.className = "dm-bubble-time";
|
|
117
|
+
timeSpan.textContent = getMsgTime();
|
|
118
|
+
header.appendChild(timeSpan);
|
|
119
|
+
contentWrap.appendChild(header);
|
|
120
|
+
|
|
121
|
+
var mdDiv = document.createElement("div");
|
|
122
|
+
mdDiv.className = "md-content";
|
|
123
|
+
mdDiv.dir = "auto";
|
|
124
|
+
contentWrap.appendChild(mdDiv);
|
|
125
|
+
_el.appendChild(contentWrap);
|
|
126
|
+
addToMessages(_el);
|
|
127
|
+
store.setState({ currentMsgEl: _el, currentFullText: "" });
|
|
128
|
+
}
|
|
129
|
+
return _el;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export function addCopyHandler(msgEl, rawText) {
|
|
133
|
+
var primed = false;
|
|
134
|
+
var resetTimer = null;
|
|
135
|
+
|
|
136
|
+
var isTouchDevice = "ontouchstart" in window;
|
|
137
|
+
|
|
138
|
+
var hint = document.createElement("div");
|
|
139
|
+
hint.className = "msg-copy-hint";
|
|
140
|
+
hint.textContent = (isTouchDevice ? "Tap" : "Click") + " to grab this";
|
|
141
|
+
msgEl.appendChild(hint);
|
|
142
|
+
|
|
143
|
+
function reset() {
|
|
144
|
+
primed = false;
|
|
145
|
+
msgEl.classList.remove("copy-primed", "copy-done");
|
|
146
|
+
hint.textContent = (isTouchDevice ? "Tap" : "Click") + " to grab this";
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
msgEl.addEventListener("click", function (e) {
|
|
150
|
+
if (e.target.closest("a, pre, code")) return;
|
|
151
|
+
var sel = window.getSelection();
|
|
152
|
+
if (sel && sel.toString().length > 0) return;
|
|
153
|
+
|
|
154
|
+
if (!primed) {
|
|
155
|
+
primed = true;
|
|
156
|
+
msgEl.classList.add("copy-primed");
|
|
157
|
+
hint.textContent = isTouchDevice ? "Tap again to grab" : "Click again to grab";
|
|
158
|
+
clearTimeout(resetTimer);
|
|
159
|
+
resetTimer = setTimeout(reset, 3000);
|
|
160
|
+
} else {
|
|
161
|
+
clearTimeout(resetTimer);
|
|
162
|
+
_ctx.copyToClipboard(rawText).then(function () {
|
|
163
|
+
msgEl.classList.remove("copy-primed");
|
|
164
|
+
msgEl.classList.add("copy-done");
|
|
165
|
+
hint.textContent = "Grabbed!";
|
|
166
|
+
resetTimer = setTimeout(reset, 1500);
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
document.addEventListener("click", function (e) {
|
|
172
|
+
if (primed && !msgEl.contains(e.target)) reset();
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export function appendDelta(text) {
|
|
177
|
+
ensureAssistantBlock();
|
|
178
|
+
streamBuffer += text;
|
|
179
|
+
if (!streamDrainTimer) {
|
|
180
|
+
streamDrainTimer = requestAnimationFrame(drainStreamTick);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function drainStreamTick() {
|
|
185
|
+
streamDrainTimer = null;
|
|
186
|
+
var _s = store.getState();
|
|
187
|
+
if (!_s.currentMsgEl || streamBuffer.length === 0) return;
|
|
188
|
+
|
|
189
|
+
var n;
|
|
190
|
+
var len = streamBuffer.length;
|
|
191
|
+
if (len > 200) { n = Math.ceil(len / 4); }
|
|
192
|
+
else if (len > 80) { n = 8; }
|
|
193
|
+
else if (len > 30) { n = 5; }
|
|
194
|
+
else if (len > 10) { n = 2; }
|
|
195
|
+
else { n = 1; }
|
|
196
|
+
|
|
197
|
+
var chunk = streamBuffer.slice(0, n);
|
|
198
|
+
streamBuffer = streamBuffer.slice(n);
|
|
199
|
+
var newText = _s.currentFullText + chunk;
|
|
200
|
+
store.setState({ currentFullText: newText });
|
|
201
|
+
|
|
202
|
+
var contentEl = _s.currentMsgEl.querySelector(".md-content");
|
|
203
|
+
contentEl.innerHTML = _ctx.renderMarkdown(newText);
|
|
204
|
+
|
|
205
|
+
if (highlightTimer) clearTimeout(highlightTimer);
|
|
206
|
+
highlightTimer = setTimeout(function () {
|
|
207
|
+
_ctx.highlightCodeBlocks(contentEl);
|
|
208
|
+
}, 150);
|
|
209
|
+
|
|
210
|
+
scrollToBottom();
|
|
211
|
+
|
|
212
|
+
if (streamBuffer.length > 0) {
|
|
213
|
+
streamDrainTimer = requestAnimationFrame(drainStreamTick);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
export function flushStreamBuffer() {
|
|
218
|
+
if (streamDrainTimer) { cancelAnimationFrame(streamDrainTimer); streamDrainTimer = null; }
|
|
219
|
+
if (streamBuffer.length > 0) {
|
|
220
|
+
store.setState({ currentFullText: store.getState().currentFullText + streamBuffer });
|
|
221
|
+
streamBuffer = "";
|
|
222
|
+
}
|
|
223
|
+
var _s = store.getState();
|
|
224
|
+
if (_s.currentMsgEl) {
|
|
225
|
+
var contentEl = _s.currentMsgEl.querySelector(".md-content");
|
|
226
|
+
if (contentEl) {
|
|
227
|
+
contentEl.innerHTML = _ctx.renderMarkdown(_s.currentFullText);
|
|
228
|
+
_ctx.highlightCodeBlocks(contentEl);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
export function finalizeAssistantBlock() {
|
|
234
|
+
flushStreamBuffer();
|
|
235
|
+
var _s = store.getState();
|
|
236
|
+
if (_s.currentMsgEl) {
|
|
237
|
+
var contentEl = _s.currentMsgEl.querySelector(".md-content");
|
|
238
|
+
if (contentEl) {
|
|
239
|
+
_ctx.highlightCodeBlocks(contentEl);
|
|
240
|
+
_ctx.renderMermaidBlocks(contentEl);
|
|
241
|
+
}
|
|
242
|
+
if (_s.currentFullText) {
|
|
243
|
+
addCopyHandler(_s.currentMsgEl, _s.currentFullText);
|
|
244
|
+
}
|
|
245
|
+
_ctx.closeToolGroup();
|
|
246
|
+
}
|
|
247
|
+
store.setState({ currentMsgEl: null, currentFullText: "" });
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
export function addUserMessage(text, images, pastes, fromUserId, fromUserName) {
|
|
251
|
+
if (!text && (!images || images.length === 0) && (!pastes || pastes.length === 0)) return;
|
|
252
|
+
var isOtherUser = fromUserId && fromUserId !== _ctx.myUserId;
|
|
253
|
+
var div = document.createElement("div");
|
|
254
|
+
div.className = "msg-user" + (isOtherUser ? " msg-user-other" : "");
|
|
255
|
+
div.dataset.turn = ++turnCounter;
|
|
256
|
+
if (shouldGroupMessage("msg-user")) div.classList.add("grouped");
|
|
257
|
+
var bubble = document.createElement("div");
|
|
258
|
+
bubble.className = "bubble";
|
|
259
|
+
bubble.dir = "auto";
|
|
260
|
+
|
|
261
|
+
if (images && images.length > 0) {
|
|
262
|
+
var imgRow = document.createElement("div");
|
|
263
|
+
imgRow.className = "bubble-images";
|
|
264
|
+
for (var i = 0; i < images.length; i++) {
|
|
265
|
+
var img = document.createElement("img");
|
|
266
|
+
if (images[i].url) {
|
|
267
|
+
img.src = images[i].url;
|
|
268
|
+
} else if (images[i].data) {
|
|
269
|
+
img.src = "data:" + images[i].mediaType + ";base64," + images[i].data;
|
|
270
|
+
}
|
|
271
|
+
img.loading = "lazy";
|
|
272
|
+
img.className = "bubble-img";
|
|
273
|
+
img.addEventListener("click", function () { _ctx.showImageModal(this.src); });
|
|
274
|
+
img.addEventListener("error", function () {
|
|
275
|
+
var placeholder = document.createElement("div");
|
|
276
|
+
placeholder.className = "bubble-img-expired";
|
|
277
|
+
placeholder.textContent = "Image deleted";
|
|
278
|
+
this.parentNode.replaceChild(placeholder, this);
|
|
279
|
+
});
|
|
280
|
+
imgRow.appendChild(img);
|
|
281
|
+
}
|
|
282
|
+
bubble.appendChild(imgRow);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
if (pastes && pastes.length > 0) {
|
|
286
|
+
var pasteRow = document.createElement("div");
|
|
287
|
+
pasteRow.className = "bubble-pastes";
|
|
288
|
+
for (var p = 0; p < pastes.length; p++) {
|
|
289
|
+
(function (pasteText) {
|
|
290
|
+
var chip = document.createElement("div");
|
|
291
|
+
chip.className = "bubble-paste";
|
|
292
|
+
var preview = pasteText.substring(0, 60).replace(/\n/g, " ");
|
|
293
|
+
if (pasteText.length > 60) preview += "...";
|
|
294
|
+
chip.innerHTML = '<span class="bubble-paste-preview">' + _ctx.escapeHtml(preview) + '</span><span class="bubble-paste-label">PASTED</span>';
|
|
295
|
+
chip.addEventListener("click", function (e) {
|
|
296
|
+
e.stopPropagation();
|
|
297
|
+
_ctx.showPasteModal(pasteText);
|
|
298
|
+
});
|
|
299
|
+
pasteRow.appendChild(chip);
|
|
300
|
+
})(pastes[p]);
|
|
301
|
+
}
|
|
302
|
+
bubble.appendChild(pasteRow);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
if (text) {
|
|
306
|
+
var textEl = document.createElement("span");
|
|
307
|
+
textEl.textContent = text;
|
|
308
|
+
bubble.appendChild(textEl);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
var _targetUser;
|
|
313
|
+
var _displayName;
|
|
314
|
+
if (isOtherUser) {
|
|
315
|
+
_targetUser = _ctx.cachedAllUsers.find(function (u) { return u.id === fromUserId; });
|
|
316
|
+
_displayName = fromUserName || (_targetUser && (_targetUser.displayName || _targetUser.username)) || "User";
|
|
317
|
+
} else {
|
|
318
|
+
_targetUser = _ctx.cachedAllUsers.find(function (u) { return u.id === _ctx.myUserId; });
|
|
319
|
+
if (!_targetUser) {
|
|
320
|
+
try { _targetUser = JSON.parse(localStorage.getItem("clay_my_user") || "null"); } catch(e) {}
|
|
321
|
+
}
|
|
322
|
+
_displayName = document.body.dataset.myDisplayName || "";
|
|
323
|
+
if (!_displayName) {
|
|
324
|
+
_displayName = (_targetUser && (_targetUser.displayName || _targetUser.username)) || "Me";
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
var avi = document.createElement("img");
|
|
329
|
+
avi.className = "dm-bubble-avatar" + (isOtherUser ? " dm-bubble-avatar-other" : " dm-bubble-avatar-me");
|
|
330
|
+
avi.src = isOtherUser
|
|
331
|
+
? _ctx.userAvatarUrl(_targetUser || { id: fromUserId }, 36)
|
|
332
|
+
: (document.body.dataset.myAvatarUrl || _ctx.userAvatarUrl(_targetUser || { id: _ctx.myUserId }, 36));
|
|
333
|
+
div.appendChild(avi);
|
|
334
|
+
|
|
335
|
+
var contentWrap = document.createElement("div");
|
|
336
|
+
contentWrap.className = "dm-bubble-content";
|
|
337
|
+
|
|
338
|
+
var header = document.createElement("div");
|
|
339
|
+
header.className = "dm-bubble-header";
|
|
340
|
+
var nameSpan = document.createElement("span");
|
|
341
|
+
nameSpan.className = "dm-bubble-name";
|
|
342
|
+
nameSpan.textContent = _displayName;
|
|
343
|
+
header.appendChild(nameSpan);
|
|
344
|
+
var timeSpan = document.createElement("span");
|
|
345
|
+
timeSpan.className = "dm-bubble-time";
|
|
346
|
+
timeSpan.textContent = getMsgTime();
|
|
347
|
+
header.appendChild(timeSpan);
|
|
348
|
+
contentWrap.appendChild(header);
|
|
349
|
+
contentWrap.appendChild(bubble);
|
|
350
|
+
div.appendChild(contentWrap);
|
|
351
|
+
|
|
352
|
+
var actions = document.createElement("div");
|
|
353
|
+
actions.className = "msg-actions";
|
|
354
|
+
actions.innerHTML =
|
|
355
|
+
'<span class="msg-action-time">' + getMsgTime() + '</span>' +
|
|
356
|
+
'<button class="msg-action-btn msg-action-copy" type="button" title="Copy">' + _ctx.iconHtml("copy") + '</button>' +
|
|
357
|
+
'<button class="msg-action-btn msg-action-fork" type="button" title="Fork">' + _ctx.iconHtml("git-branch") + '</button>' +
|
|
358
|
+
'<button class="msg-action-btn msg-action-rewind msg-user-rewind-btn" type="button" title="Rewind">' + _ctx.iconHtml("rotate-ccw") + '</button>' +
|
|
359
|
+
'<button class="msg-action-btn msg-action-hidden msg-action-edit" type="button" title="Edit">' + _ctx.iconHtml("pencil") + '</button>';
|
|
360
|
+
div.appendChild(actions);
|
|
361
|
+
|
|
362
|
+
actions.querySelector(".msg-action-copy").addEventListener("click", function () {
|
|
363
|
+
var self = this;
|
|
364
|
+
_ctx.copyToClipboard(text || "");
|
|
365
|
+
self.innerHTML = _ctx.iconHtml("check");
|
|
366
|
+
_ctx.refreshIcons();
|
|
367
|
+
setTimeout(function () { self.innerHTML = _ctx.iconHtml("copy"); _ctx.refreshIcons(); }, 1200);
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
addToMessages(div);
|
|
371
|
+
_ctx.refreshIcons();
|
|
372
|
+
forceScrollToBottom();
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
export function addSystemMessage(text, isError) {
|
|
376
|
+
var div = document.createElement("div");
|
|
377
|
+
div.className = "sys-msg" + (isError ? " error" : "");
|
|
378
|
+
div.innerHTML = '<span class="sys-text"></span>';
|
|
379
|
+
div.querySelector(".sys-text").textContent = text;
|
|
380
|
+
addToMessages(div);
|
|
381
|
+
scrollToBottom();
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
export function addConflictMessage(msg) {
|
|
385
|
+
var div = document.createElement("div");
|
|
386
|
+
div.className = "conflict-msg";
|
|
387
|
+
var header = document.createElement("div");
|
|
388
|
+
header.className = "conflict-header";
|
|
389
|
+
header.textContent = msg.text || "Another Claude Code process is already running.";
|
|
390
|
+
div.appendChild(header);
|
|
391
|
+
|
|
392
|
+
var hint = document.createElement("div");
|
|
393
|
+
hint.className = "conflict-hint";
|
|
394
|
+
hint.textContent = "Kill the conflicting process to continue, or use the existing Claude Code session.";
|
|
395
|
+
div.appendChild(hint);
|
|
396
|
+
|
|
397
|
+
for (var i = 0; i < msg.processes.length; i++) {
|
|
398
|
+
var p = msg.processes[i];
|
|
399
|
+
var row = document.createElement("div");
|
|
400
|
+
row.className = "conflict-process";
|
|
401
|
+
|
|
402
|
+
var info = document.createElement("span");
|
|
403
|
+
info.className = "conflict-pid";
|
|
404
|
+
info.textContent = "PID " + p.pid;
|
|
405
|
+
row.appendChild(info);
|
|
406
|
+
|
|
407
|
+
var cmd = document.createElement("code");
|
|
408
|
+
cmd.className = "conflict-cmd";
|
|
409
|
+
cmd.textContent = p.command.length > 80 ? p.command.substring(0, 80) + "..." : p.command;
|
|
410
|
+
cmd.title = p.command;
|
|
411
|
+
row.appendChild(cmd);
|
|
412
|
+
|
|
413
|
+
var killBtn = document.createElement("button");
|
|
414
|
+
killBtn.className = "conflict-kill-btn";
|
|
415
|
+
killBtn.textContent = "Kill Process";
|
|
416
|
+
killBtn.setAttribute("data-pid", p.pid);
|
|
417
|
+
killBtn.addEventListener("click", function() {
|
|
418
|
+
var pid = parseInt(this.getAttribute("data-pid"), 10);
|
|
419
|
+
_ctx.getWs().send(JSON.stringify({ type: "kill_process", pid: pid }));
|
|
420
|
+
this.disabled = true;
|
|
421
|
+
this.textContent = "Killing...";
|
|
422
|
+
});
|
|
423
|
+
row.appendChild(killBtn);
|
|
424
|
+
div.appendChild(row);
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
addToMessages(div);
|
|
428
|
+
scrollToBottom();
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
export function addContextOverflowMessage(msg) {
|
|
432
|
+
var div = document.createElement("div");
|
|
433
|
+
div.className = "context-overflow-msg";
|
|
434
|
+
|
|
435
|
+
var header = document.createElement("div");
|
|
436
|
+
header.className = "context-overflow-header";
|
|
437
|
+
header.textContent = msg.text || "Conversation too long to continue.";
|
|
438
|
+
div.appendChild(header);
|
|
439
|
+
|
|
440
|
+
var hint = document.createElement("div");
|
|
441
|
+
hint.className = "context-overflow-hint";
|
|
442
|
+
hint.textContent = "The conversation has exceeded the model's context limit. Please start a new conversation to continue.";
|
|
443
|
+
div.appendChild(hint);
|
|
444
|
+
|
|
445
|
+
var btn = document.createElement("button");
|
|
446
|
+
btn.className = "context-overflow-btn";
|
|
447
|
+
btn.textContent = "New Conversation";
|
|
448
|
+
btn.addEventListener("click", function() {
|
|
449
|
+
_ctx.getWs().send(JSON.stringify({ type: "new_session" }));
|
|
450
|
+
});
|
|
451
|
+
div.appendChild(btn);
|
|
452
|
+
|
|
453
|
+
addToMessages(div);
|
|
454
|
+
scrollToBottom();
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
export function addAuthRequiredMessage(msg) {
|
|
458
|
+
var div = document.createElement("div");
|
|
459
|
+
div.className = "auth-required-msg";
|
|
460
|
+
|
|
461
|
+
var header = document.createElement("div");
|
|
462
|
+
header.className = "auth-required-header";
|
|
463
|
+
header.textContent = msg.text || "Claude Code is not logged in.";
|
|
464
|
+
div.appendChild(header);
|
|
465
|
+
|
|
466
|
+
var hint = document.createElement("div");
|
|
467
|
+
hint.className = "auth-required-hint";
|
|
468
|
+
|
|
469
|
+
if (msg.canAutoLogin) {
|
|
470
|
+
if (msg.linuxUser) {
|
|
471
|
+
hint.textContent = "Opening a terminal as " + msg.linuxUser + " to log in...";
|
|
472
|
+
} else {
|
|
473
|
+
hint.textContent = "Opening a terminal to log in...";
|
|
474
|
+
}
|
|
475
|
+
div.appendChild(hint);
|
|
476
|
+
|
|
477
|
+
var guide = document.createElement("div");
|
|
478
|
+
guide.className = "auth-required-guide";
|
|
479
|
+
guide.textContent = "When a login URL appears in the terminal, click it to open in your browser. Do not press 'c' as it will try to open the browser on the server.";
|
|
480
|
+
div.appendChild(guide);
|
|
481
|
+
|
|
482
|
+
var sessionHint = document.createElement("div");
|
|
483
|
+
sessionHint.className = "auth-required-guide";
|
|
484
|
+
sessionHint.textContent = "After logging in, start a new session to continue.";
|
|
485
|
+
div.appendChild(sessionHint);
|
|
486
|
+
|
|
487
|
+
var loginBtn = document.createElement("button");
|
|
488
|
+
loginBtn.className = "auth-required-btn";
|
|
489
|
+
loginBtn.textContent = "Open terminal & log in";
|
|
490
|
+
loginBtn.addEventListener("click", function () {
|
|
491
|
+
_ctx.setPendingTermCommand("claude\n");
|
|
492
|
+
_ctx.getWs().send(JSON.stringify({ type: "term_create", cols: 80, rows: 24 }));
|
|
493
|
+
_ctx.openTerminal();
|
|
494
|
+
});
|
|
495
|
+
div.appendChild(loginBtn);
|
|
496
|
+
|
|
497
|
+
addToMessages(div);
|
|
498
|
+
scrollToBottom();
|
|
499
|
+
|
|
500
|
+
var inputArea = document.getElementById("input-area");
|
|
501
|
+
if (inputArea) inputArea.classList.add("hidden");
|
|
502
|
+
|
|
503
|
+
if (!store.getState().replayingHistory) {
|
|
504
|
+
_ctx.setPendingTermCommand("claude\n");
|
|
505
|
+
_ctx.getWs().send(JSON.stringify({ type: "term_create", cols: 80, rows: 24 }));
|
|
506
|
+
_ctx.openTerminal();
|
|
507
|
+
}
|
|
508
|
+
} else {
|
|
509
|
+
hint.textContent = "Please ask an administrator to log in to Claude Code.";
|
|
510
|
+
div.appendChild(hint);
|
|
511
|
+
addToMessages(div);
|
|
512
|
+
scrollToBottom();
|
|
513
|
+
|
|
514
|
+
_ctx.inputEl.disabled = true;
|
|
515
|
+
_ctx.inputEl.placeholder = "Login required. Start a new session after logging in.";
|
|
516
|
+
_ctx.sendBtn.disabled = true;
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
// --- Pre-thinking (instant dots before server responds) ---
|
|
521
|
+
|
|
522
|
+
export function showClaudePreThinking() {
|
|
523
|
+
if (_ctx.getChatLayout() !== "channel") return;
|
|
524
|
+
var claudeAvatar = _ctx.CLAUDE_CODE_AVATAR;
|
|
525
|
+
doShowMatePreThinking("Claude Code", claudeAvatar);
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
export function showMatePreThinking() {
|
|
529
|
+
removeMatePreThinking();
|
|
530
|
+
var mateName = _ctx.getDmTargetUser() ? (_ctx.getDmTargetUser().displayName || "Mate") : "Mate";
|
|
531
|
+
var mateAvatar = document.body.dataset.mateAvatarUrl || "";
|
|
532
|
+
doShowMatePreThinking(mateName, mateAvatar);
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
function doShowMatePreThinking(mateName, mateAvatar) {
|
|
536
|
+
var _el = document.createElement("div");
|
|
537
|
+
_el.className = "thinking-item mate-thinking mate-pre-thinking";
|
|
538
|
+
_el.innerHTML =
|
|
539
|
+
'<img class="dm-bubble-avatar dm-bubble-avatar-mate" src="' + _ctx.escapeHtml(mateAvatar) + '" alt="" style="display:block">' +
|
|
540
|
+
'<div class="dm-bubble-content">' +
|
|
541
|
+
'<div class="dm-bubble-header"><span class="dm-bubble-name">' + _ctx.escapeHtml(mateName) + '</span></div>' +
|
|
542
|
+
'<div class="mate-thinking-dots"><span></span><span></span><span></span></div>' +
|
|
543
|
+
'</div>';
|
|
544
|
+
store.setState({ matePreThinkingEl: _el });
|
|
545
|
+
if (activityEl && activityEl.parentNode) {
|
|
546
|
+
activityEl.parentNode.insertBefore(_el, activityEl);
|
|
547
|
+
} else {
|
|
548
|
+
addToMessages(_el);
|
|
549
|
+
}
|
|
550
|
+
_ctx.refreshIcons();
|
|
551
|
+
scrollToBottom();
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
export function removeMatePreThinking() {
|
|
555
|
+
if (matePreThinkingTimer) {
|
|
556
|
+
clearTimeout(matePreThinkingTimer);
|
|
557
|
+
matePreThinkingTimer = null;
|
|
558
|
+
}
|
|
559
|
+
var _el = store.getState().matePreThinkingEl;
|
|
560
|
+
if (_el) {
|
|
561
|
+
_el.remove();
|
|
562
|
+
store.setState({ matePreThinkingEl: null });
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
// --- Prompt suggestion chips ---
|
|
567
|
+
|
|
568
|
+
export function showSuggestionChips(suggestion) {
|
|
569
|
+
if (!suggestion || _ctx.getProcessing()) return;
|
|
570
|
+
_ctx.suggestionChipsEl.innerHTML = "";
|
|
571
|
+
var chip = document.createElement("button");
|
|
572
|
+
chip.className = "suggestion-chip";
|
|
573
|
+
chip.innerHTML = _ctx.iconHtml("sparkles") +
|
|
574
|
+
'<span class="suggestion-chip-text">' + _ctx.escapeHtml(suggestion) + '</span>';
|
|
575
|
+
chip.addEventListener("click", function () {
|
|
576
|
+
_ctx.inputEl.value = suggestion;
|
|
577
|
+
hideSuggestionChips();
|
|
578
|
+
_ctx.sendMessage();
|
|
579
|
+
});
|
|
580
|
+
_ctx.suggestionChipsEl.appendChild(chip);
|
|
581
|
+
_ctx.suggestionChipsEl.classList.remove("hidden");
|
|
582
|
+
_ctx.refreshIcons();
|
|
583
|
+
var messagesEl = _ctx.messagesEl;
|
|
584
|
+
requestAnimationFrame(function () {
|
|
585
|
+
var chipHeight = _ctx.suggestionChipsEl.offsetHeight || 0;
|
|
586
|
+
if (chipHeight > 0) {
|
|
587
|
+
messagesEl.style.paddingBottom = chipHeight + "px";
|
|
588
|
+
scrollToBottom();
|
|
589
|
+
}
|
|
590
|
+
});
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
export function hideSuggestionChips() {
|
|
594
|
+
_ctx.suggestionChipsEl.innerHTML = "";
|
|
595
|
+
_ctx.suggestionChipsEl.classList.add("hidden");
|
|
596
|
+
_ctx.messagesEl.style.paddingBottom = "";
|
|
597
|
+
}
|