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,451 @@
|
|
|
1
|
+
// app-rate-limit.js - Rate limit UI, scheduled messages, fast mode indicator
|
|
2
|
+
// Extracted from app.js (PR-26)
|
|
3
|
+
|
|
4
|
+
import { iconHtml, refreshIcons } from './icons.js';
|
|
5
|
+
import { store } from './store.js';
|
|
6
|
+
import { getWs } from './ws-ref.js';
|
|
7
|
+
import { addToMessages, scrollToBottom } from './app-rendering.js';
|
|
8
|
+
import { userAvatarUrl } from './avatar.js';
|
|
9
|
+
import { setScheduleDelayMs, clearScheduleDelay } from './input.js';
|
|
10
|
+
|
|
11
|
+
// --- Module-owned state ---
|
|
12
|
+
var rateLimitCountdownTimer = null;
|
|
13
|
+
var rateLimitIndicatorEl = null;
|
|
14
|
+
var rateLimitResetsAt = null;
|
|
15
|
+
var rateLimitResetTimer = null;
|
|
16
|
+
var rateLimitUsageEl = null;
|
|
17
|
+
var rateLimitResetState = {};
|
|
18
|
+
var rateLimitTickTimer = null;
|
|
19
|
+
var scheduledMsgEl = null;
|
|
20
|
+
var scheduledCountdownTimer = null;
|
|
21
|
+
var fastModeIndicatorEl = null;
|
|
22
|
+
|
|
23
|
+
// --- Internal helpers ---
|
|
24
|
+
|
|
25
|
+
function rateLimitTypeLabel(type) {
|
|
26
|
+
if (!type) return "Usage";
|
|
27
|
+
var labels = {
|
|
28
|
+
"five_hour": "5-hour",
|
|
29
|
+
"seven_day": "7-day",
|
|
30
|
+
"seven_day_opus": "7-day Opus",
|
|
31
|
+
"seven_day_sonnet": "7-day Sonnet",
|
|
32
|
+
"overage": "Overage",
|
|
33
|
+
};
|
|
34
|
+
return labels[type] || type;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function startRateLimitCountdown(el, resetsAt, cardEl) {
|
|
38
|
+
if (rateLimitCountdownTimer) clearInterval(rateLimitCountdownTimer);
|
|
39
|
+
|
|
40
|
+
function tick() {
|
|
41
|
+
var remaining = resetsAt - Date.now();
|
|
42
|
+
if (remaining <= 0) {
|
|
43
|
+
clearInterval(rateLimitCountdownTimer);
|
|
44
|
+
rateLimitCountdownTimer = null;
|
|
45
|
+
clearRateLimitIndicator();
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
// Update pill text with countdown
|
|
49
|
+
if (rateLimitIndicatorEl) {
|
|
50
|
+
var pillText = rateLimitIndicatorEl.querySelector(".header-pill-text");
|
|
51
|
+
if (pillText) {
|
|
52
|
+
var mins = Math.floor(remaining / 60000);
|
|
53
|
+
var secs = Math.floor((remaining % 60000) / 1000);
|
|
54
|
+
if (mins >= 60) {
|
|
55
|
+
var hrs = Math.floor(mins / 60);
|
|
56
|
+
mins = mins % 60;
|
|
57
|
+
pillText.textContent = hrs + "h " + mins + "m";
|
|
58
|
+
} else {
|
|
59
|
+
pillText.textContent = mins + "m " + secs + "s";
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
tick();
|
|
66
|
+
rateLimitCountdownTimer = setInterval(tick, 1000);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function updateRateLimitIndicator(msg) {
|
|
70
|
+
var statusArea = document.querySelector(".title-bar-content .status");
|
|
71
|
+
if (!statusArea) return;
|
|
72
|
+
|
|
73
|
+
if (!rateLimitIndicatorEl) {
|
|
74
|
+
rateLimitIndicatorEl = document.createElement("span");
|
|
75
|
+
rateLimitIndicatorEl.className = "header-rate-limit-wrap";
|
|
76
|
+
statusArea.insertBefore(rateLimitIndicatorEl, statusArea.firstChild);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
var isRejected = msg.status === "rejected";
|
|
80
|
+
var pillClass = "header-rate-limit" + (isRejected ? " rejected" : " warning");
|
|
81
|
+
var label = isRejected ? "Rate limited" : "Rate warning";
|
|
82
|
+
rateLimitIndicatorEl.innerHTML =
|
|
83
|
+
'<span class="' + pillClass + '">' +
|
|
84
|
+
iconHtml("alert-triangle") +
|
|
85
|
+
'<span class="header-pill-text">' + label + "</span>" +
|
|
86
|
+
'<a href="https://claude.ai/settings/usage" target="_blank" rel="noopener" class="rate-limit-link">' +
|
|
87
|
+
iconHtml("external-link") +
|
|
88
|
+
"</a>" +
|
|
89
|
+
"</span>";
|
|
90
|
+
refreshIcons();
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function showRateLimitPopover(text, isRejected) {
|
|
94
|
+
if (!rateLimitIndicatorEl) return;
|
|
95
|
+
// Remove existing popover
|
|
96
|
+
var old = rateLimitIndicatorEl.querySelector(".rate-limit-popover");
|
|
97
|
+
if (old) old.remove();
|
|
98
|
+
|
|
99
|
+
var pop = document.createElement("div");
|
|
100
|
+
pop.className = "rate-limit-popover" + (isRejected ? " rejected" : "");
|
|
101
|
+
pop.textContent = text;
|
|
102
|
+
rateLimitIndicatorEl.appendChild(pop);
|
|
103
|
+
|
|
104
|
+
// Auto-dismiss after 5s
|
|
105
|
+
setTimeout(function () {
|
|
106
|
+
pop.classList.add("fade-out");
|
|
107
|
+
setTimeout(function () { if (pop.parentNode) pop.remove(); }, 300);
|
|
108
|
+
}, 5000);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function clearRateLimitIndicator() {
|
|
112
|
+
if (rateLimitIndicatorEl) {
|
|
113
|
+
rateLimitIndicatorEl.remove();
|
|
114
|
+
rateLimitIndicatorEl = null;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function formatResetTime(resetsAt) {
|
|
119
|
+
if (!resetsAt) return "";
|
|
120
|
+
var d = new Date(resetsAt);
|
|
121
|
+
var now = new Date();
|
|
122
|
+
var diff = resetsAt - now.getTime();
|
|
123
|
+
if (diff <= 0) return "";
|
|
124
|
+
var hrs = Math.floor(diff / 3600000);
|
|
125
|
+
var mins = Math.floor((diff % 3600000) / 60000);
|
|
126
|
+
if (hrs > 0) return hrs + "h " + mins + "m";
|
|
127
|
+
return mins + "m";
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function rateLimitTypeShortLabel(type) {
|
|
131
|
+
if (type === "five_hour") return "5h";
|
|
132
|
+
if (type === "seven_day") return "7d";
|
|
133
|
+
if (type === "seven_day_opus") return "7d opus";
|
|
134
|
+
if (type === "seven_day_sonnet") return "7d sonnet";
|
|
135
|
+
return type || "";
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function tickRateLimitUsage() {
|
|
139
|
+
if (!rateLimitUsageEl) return;
|
|
140
|
+
var parts = [];
|
|
141
|
+
var types = ["five_hour", "seven_day", "seven_day_opus", "seven_day_sonnet"];
|
|
142
|
+
for (var i = 0; i < types.length; i++) {
|
|
143
|
+
var entry = rateLimitResetState[types[i]];
|
|
144
|
+
if (!entry || !entry.resetsAt) continue;
|
|
145
|
+
var timeStr = formatResetTime(entry.resetsAt);
|
|
146
|
+
if (!timeStr) { delete rateLimitResetState[types[i]]; continue; }
|
|
147
|
+
parts.push(rateLimitTypeShortLabel(types[i]) + " resets " + timeStr);
|
|
148
|
+
}
|
|
149
|
+
if (parts.length === 0) {
|
|
150
|
+
rateLimitUsageEl.innerHTML = iconHtml("activity") + '<span>Check usage</span>' + iconHtml("external-link");
|
|
151
|
+
refreshIcons();
|
|
152
|
+
if (rateLimitTickTimer) { clearInterval(rateLimitTickTimer); rateLimitTickTimer = null; }
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
var label = parts.join(" · ");
|
|
156
|
+
rateLimitUsageEl.innerHTML = iconHtml("activity") + '<span>' + label + '</span>' + iconHtml("external-link");
|
|
157
|
+
refreshIcons();
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// --- Exported functions ---
|
|
161
|
+
|
|
162
|
+
export function initRateLimit() {}
|
|
163
|
+
|
|
164
|
+
export function handleRateLimitEvent(msg) {
|
|
165
|
+
var isRejected = msg.status === "rejected";
|
|
166
|
+
var typeLabel = rateLimitTypeLabel(msg.rateLimitType);
|
|
167
|
+
var popoverText = "";
|
|
168
|
+
|
|
169
|
+
if (isRejected && msg.resetsAt) {
|
|
170
|
+
// Check if already expired (history replay) — skip popover
|
|
171
|
+
if (msg.resetsAt < Date.now()) {
|
|
172
|
+
updateRateLimitIndicator(msg);
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
popoverText = typeLabel + " limit exceeded";
|
|
176
|
+
updateRateLimitIndicator(msg);
|
|
177
|
+
startRateLimitCountdown(null, msg.resetsAt, null);
|
|
178
|
+
// Track rate limit reset time
|
|
179
|
+
rateLimitResetsAt = msg.resetsAt;
|
|
180
|
+
if (rateLimitResetTimer) clearTimeout(rateLimitResetTimer);
|
|
181
|
+
// Auto-switch input to schedule mode: any message typed will be queued for after reset
|
|
182
|
+
var delayUntilReset = msg.resetsAt - Date.now();
|
|
183
|
+
if (delayUntilReset > 0) {
|
|
184
|
+
setScheduleDelayMs(delayUntilReset + 60000); // +1min buffer after reset
|
|
185
|
+
}
|
|
186
|
+
rateLimitResetTimer = setTimeout(function () {
|
|
187
|
+
rateLimitResetsAt = null;
|
|
188
|
+
rateLimitResetTimer = null;
|
|
189
|
+
// Clear schedule mode when rate limit resets
|
|
190
|
+
clearScheduleDelay();
|
|
191
|
+
}, msg.resetsAt - Date.now() + 1000);
|
|
192
|
+
} else {
|
|
193
|
+
var pct = msg.utilization ? Math.round(msg.utilization * 100) : null;
|
|
194
|
+
popoverText = typeLabel + " warning" + (pct ? " (" + pct + "% used)" : "");
|
|
195
|
+
updateRateLimitIndicator(msg);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
showRateLimitPopover(popoverText, isRejected);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
export function updateRateLimitUsage(msg) {
|
|
202
|
+
if (msg.rateLimitType && msg.resetsAt) {
|
|
203
|
+
rateLimitResetState[msg.rateLimitType] = { resetsAt: msg.resetsAt, status: msg.status };
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
var topBarActions = document.querySelector("#top-bar .top-bar-actions");
|
|
207
|
+
if (!topBarActions) return;
|
|
208
|
+
|
|
209
|
+
if (!rateLimitUsageEl) {
|
|
210
|
+
rateLimitUsageEl = document.createElement("a");
|
|
211
|
+
rateLimitUsageEl.id = "rate-limit-usage-link";
|
|
212
|
+
rateLimitUsageEl.className = "top-bar-pill pill-dim usage-check-link";
|
|
213
|
+
rateLimitUsageEl.href = "https://claude.ai/settings/usage";
|
|
214
|
+
rateLimitUsageEl.target = "_blank";
|
|
215
|
+
rateLimitUsageEl.rel = "noopener";
|
|
216
|
+
rateLimitUsageEl.title = "Check usage on claude.ai";
|
|
217
|
+
var ref = document.getElementById("skip-perms-pill");
|
|
218
|
+
topBarActions.insertBefore(rateLimitUsageEl, ref);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Build label from available reset times
|
|
222
|
+
var parts = [];
|
|
223
|
+
var types = ["five_hour", "seven_day", "seven_day_opus", "seven_day_sonnet"];
|
|
224
|
+
for (var i = 0; i < types.length; i++) {
|
|
225
|
+
var entry = rateLimitResetState[types[i]];
|
|
226
|
+
if (!entry || !entry.resetsAt) continue;
|
|
227
|
+
var timeStr = formatResetTime(entry.resetsAt);
|
|
228
|
+
if (!timeStr) continue;
|
|
229
|
+
parts.push(rateLimitTypeShortLabel(types[i]) + " resets " + timeStr);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
var label = parts.length > 0 ? parts.join(" · ") : "Check usage";
|
|
233
|
+
rateLimitUsageEl.innerHTML = iconHtml("activity") + '<span>' + label + '</span>' + iconHtml("external-link");
|
|
234
|
+
refreshIcons();
|
|
235
|
+
|
|
236
|
+
// Start or stop live countdown tick
|
|
237
|
+
if (parts.length > 0 && !rateLimitTickTimer) {
|
|
238
|
+
rateLimitTickTimer = setInterval(tickRateLimitUsage, 30000);
|
|
239
|
+
} else if (parts.length === 0 && rateLimitTickTimer) {
|
|
240
|
+
clearInterval(rateLimitTickTimer);
|
|
241
|
+
rateLimitTickTimer = null;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
export function addScheduledMessageBubble(text, resetsAt) {
|
|
246
|
+
removeScheduledMessageBubble();
|
|
247
|
+
var isChannel = document.body.classList.contains("wide-view");
|
|
248
|
+
var wrap = document.createElement("div");
|
|
249
|
+
wrap.className = "msg-user scheduled-msg-wrap";
|
|
250
|
+
wrap.id = "scheduled-msg-bubble";
|
|
251
|
+
|
|
252
|
+
var countdownEl;
|
|
253
|
+
var cancelBtn;
|
|
254
|
+
|
|
255
|
+
if (isChannel) {
|
|
256
|
+
// Channel mode: avatar + header with scheduled badge + message
|
|
257
|
+
var _me = store.getState().cachedAllUsers.find(function (u) { return u.id === store.getState().myUserId; });
|
|
258
|
+
if (!_me) { try { _me = JSON.parse(localStorage.getItem("clay_my_user") || "null"); } catch(e) {} }
|
|
259
|
+
var _myName = document.body.dataset.myDisplayName || (_me && (_me.displayName || _me.username)) || "Me";
|
|
260
|
+
|
|
261
|
+
var avi = document.createElement("img");
|
|
262
|
+
avi.className = "dm-bubble-avatar dm-bubble-avatar-me";
|
|
263
|
+
avi.src = document.body.dataset.myAvatarUrl || userAvatarUrl(_me || { id: store.getState().myUserId }, 36);
|
|
264
|
+
wrap.appendChild(avi);
|
|
265
|
+
|
|
266
|
+
var content = document.createElement("div");
|
|
267
|
+
content.className = "dm-bubble-content";
|
|
268
|
+
|
|
269
|
+
var header = document.createElement("div");
|
|
270
|
+
header.className = "dm-bubble-header";
|
|
271
|
+
|
|
272
|
+
var nameSpan = document.createElement("span");
|
|
273
|
+
nameSpan.className = "dm-bubble-name";
|
|
274
|
+
nameSpan.textContent = _myName;
|
|
275
|
+
header.appendChild(nameSpan);
|
|
276
|
+
|
|
277
|
+
var badge = document.createElement("span");
|
|
278
|
+
badge.className = "scheduled-msg-badge";
|
|
279
|
+
badge.innerHTML = iconHtml("clock");
|
|
280
|
+
countdownEl = document.createElement("span");
|
|
281
|
+
countdownEl.className = "scheduled-msg-countdown";
|
|
282
|
+
badge.appendChild(countdownEl);
|
|
283
|
+
header.appendChild(badge);
|
|
284
|
+
|
|
285
|
+
var actions = document.createElement("span");
|
|
286
|
+
actions.className = "scheduled-msg-actions";
|
|
287
|
+
|
|
288
|
+
var sendNowBtn = document.createElement("button");
|
|
289
|
+
sendNowBtn.className = "scheduled-msg-send-now";
|
|
290
|
+
sendNowBtn.textContent = "Send now";
|
|
291
|
+
sendNowBtn.addEventListener("click", function () {
|
|
292
|
+
var ws = getWs();
|
|
293
|
+
if (ws && ws.readyState === 1) {
|
|
294
|
+
ws.send(JSON.stringify({ type: "send_scheduled_now" }));
|
|
295
|
+
}
|
|
296
|
+
});
|
|
297
|
+
actions.appendChild(sendNowBtn);
|
|
298
|
+
|
|
299
|
+
var sep = document.createElement("span");
|
|
300
|
+
sep.className = "scheduled-msg-sep";
|
|
301
|
+
sep.textContent = "\u00b7";
|
|
302
|
+
actions.appendChild(sep);
|
|
303
|
+
|
|
304
|
+
cancelBtn = document.createElement("button");
|
|
305
|
+
cancelBtn.className = "scheduled-msg-cancel";
|
|
306
|
+
cancelBtn.textContent = "Cancel";
|
|
307
|
+
cancelBtn.addEventListener("click", function () {
|
|
308
|
+
var ws = getWs();
|
|
309
|
+
if (ws && ws.readyState === 1) {
|
|
310
|
+
ws.send(JSON.stringify({ type: "cancel_scheduled_message" }));
|
|
311
|
+
}
|
|
312
|
+
});
|
|
313
|
+
actions.appendChild(cancelBtn);
|
|
314
|
+
|
|
315
|
+
header.appendChild(actions);
|
|
316
|
+
|
|
317
|
+
content.appendChild(header);
|
|
318
|
+
|
|
319
|
+
var bubble = document.createElement("div");
|
|
320
|
+
bubble.className = "bubble scheduled-msg-bubble";
|
|
321
|
+
var textEl = document.createElement("span");
|
|
322
|
+
textEl.textContent = text;
|
|
323
|
+
bubble.appendChild(textEl);
|
|
324
|
+
content.appendChild(bubble);
|
|
325
|
+
|
|
326
|
+
wrap.appendChild(content);
|
|
327
|
+
} else {
|
|
328
|
+
// Bubble mode: original layout
|
|
329
|
+
var bubble = document.createElement("div");
|
|
330
|
+
bubble.className = "bubble scheduled-msg-bubble";
|
|
331
|
+
|
|
332
|
+
var textEl = document.createElement("span");
|
|
333
|
+
textEl.textContent = text;
|
|
334
|
+
bubble.appendChild(textEl);
|
|
335
|
+
|
|
336
|
+
var metaEl = document.createElement("div");
|
|
337
|
+
metaEl.className = "scheduled-msg-meta";
|
|
338
|
+
|
|
339
|
+
var clockIcon = document.createElement("span");
|
|
340
|
+
clockIcon.className = "scheduled-msg-icon";
|
|
341
|
+
clockIcon.innerHTML = iconHtml("clock");
|
|
342
|
+
metaEl.appendChild(clockIcon);
|
|
343
|
+
|
|
344
|
+
countdownEl = document.createElement("span");
|
|
345
|
+
countdownEl.className = "scheduled-msg-countdown";
|
|
346
|
+
metaEl.appendChild(countdownEl);
|
|
347
|
+
|
|
348
|
+
var sendNowBtn2 = document.createElement("button");
|
|
349
|
+
sendNowBtn2.className = "scheduled-msg-send-now";
|
|
350
|
+
sendNowBtn2.textContent = "Send now";
|
|
351
|
+
sendNowBtn2.addEventListener("click", function () {
|
|
352
|
+
var ws = getWs();
|
|
353
|
+
if (ws && ws.readyState === 1) {
|
|
354
|
+
ws.send(JSON.stringify({ type: "send_scheduled_now" }));
|
|
355
|
+
}
|
|
356
|
+
});
|
|
357
|
+
metaEl.appendChild(sendNowBtn2);
|
|
358
|
+
|
|
359
|
+
var sep2 = document.createElement("span");
|
|
360
|
+
sep2.className = "scheduled-msg-sep";
|
|
361
|
+
sep2.textContent = "\u00b7";
|
|
362
|
+
metaEl.appendChild(sep2);
|
|
363
|
+
|
|
364
|
+
cancelBtn = document.createElement("button");
|
|
365
|
+
cancelBtn.className = "scheduled-msg-cancel";
|
|
366
|
+
cancelBtn.textContent = "Cancel";
|
|
367
|
+
cancelBtn.addEventListener("click", function () {
|
|
368
|
+
var ws = getWs();
|
|
369
|
+
if (ws && ws.readyState === 1) {
|
|
370
|
+
ws.send(JSON.stringify({ type: "cancel_scheduled_message" }));
|
|
371
|
+
}
|
|
372
|
+
});
|
|
373
|
+
metaEl.appendChild(cancelBtn);
|
|
374
|
+
|
|
375
|
+
wrap.appendChild(bubble);
|
|
376
|
+
wrap.appendChild(metaEl);
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
addToMessages(wrap);
|
|
380
|
+
scheduledMsgEl = wrap;
|
|
381
|
+
scrollToBottom();
|
|
382
|
+
|
|
383
|
+
// Start countdown
|
|
384
|
+
function updateCountdown() {
|
|
385
|
+
var remaining = resetsAt - Date.now();
|
|
386
|
+
if (remaining <= 0) {
|
|
387
|
+
countdownEl.textContent = "Sending...";
|
|
388
|
+
if (scheduledCountdownTimer) { clearInterval(scheduledCountdownTimer); scheduledCountdownTimer = null; }
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
var hrs = Math.floor(remaining / 3600000);
|
|
392
|
+
var mins = Math.floor((remaining % 3600000) / 60000);
|
|
393
|
+
var secs = Math.floor((remaining % 60000) / 1000);
|
|
394
|
+
var timeStr = "";
|
|
395
|
+
if (hrs > 0) timeStr += hrs + "h ";
|
|
396
|
+
if (mins > 0 || hrs > 0) timeStr += mins + "m ";
|
|
397
|
+
timeStr += secs + "s";
|
|
398
|
+
|
|
399
|
+
var sendDate = new Date(resetsAt);
|
|
400
|
+
var absTime = sendDate.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" });
|
|
401
|
+
countdownEl.textContent = "Sends at " + absTime + " (" + timeStr + ")";
|
|
402
|
+
}
|
|
403
|
+
updateCountdown();
|
|
404
|
+
scheduledCountdownTimer = setInterval(updateCountdown, 1000);
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
export function removeScheduledMessageBubble() {
|
|
408
|
+
if (scheduledMsgEl) {
|
|
409
|
+
scheduledMsgEl.remove();
|
|
410
|
+
scheduledMsgEl = null;
|
|
411
|
+
}
|
|
412
|
+
if (scheduledCountdownTimer) {
|
|
413
|
+
clearInterval(scheduledCountdownTimer);
|
|
414
|
+
scheduledCountdownTimer = null;
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
export function handleFastModeState(state) {
|
|
419
|
+
var statusArea = document.querySelector(".title-bar-content .status");
|
|
420
|
+
if (!statusArea) return;
|
|
421
|
+
|
|
422
|
+
if (state === "off") {
|
|
423
|
+
if (fastModeIndicatorEl) {
|
|
424
|
+
fastModeIndicatorEl.remove();
|
|
425
|
+
fastModeIndicatorEl = null;
|
|
426
|
+
}
|
|
427
|
+
return;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
if (!fastModeIndicatorEl) {
|
|
431
|
+
fastModeIndicatorEl = document.createElement("span");
|
|
432
|
+
statusArea.insertBefore(fastModeIndicatorEl, statusArea.firstChild);
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
if (state === "cooldown") {
|
|
436
|
+
fastModeIndicatorEl.className = "header-fast-mode cooldown";
|
|
437
|
+
fastModeIndicatorEl.innerHTML = iconHtml("timer") + '<span class="header-pill-text">Cooldown</span>';
|
|
438
|
+
} else if (state === "on") {
|
|
439
|
+
fastModeIndicatorEl.className = "header-fast-mode active";
|
|
440
|
+
fastModeIndicatorEl.innerHTML = iconHtml("zap") + '<span class="header-pill-text">Fast mode</span>';
|
|
441
|
+
}
|
|
442
|
+
refreshIcons();
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
export function getScheduledMsgEl() { return scheduledMsgEl; }
|
|
446
|
+
|
|
447
|
+
export function resetRateLimitState() {
|
|
448
|
+
clearRateLimitIndicator();
|
|
449
|
+
if (rateLimitCountdownTimer) { clearInterval(rateLimitCountdownTimer); rateLimitCountdownTimer = null; }
|
|
450
|
+
if (fastModeIndicatorEl) { fastModeIndicatorEl.remove(); fastModeIndicatorEl = null; }
|
|
451
|
+
}
|