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,888 @@
|
|
|
1
|
+
// app-panels.js - Config chip, usage panel, status panel, context panel
|
|
2
|
+
// Extracted from app.js (PR-30)
|
|
3
|
+
|
|
4
|
+
import { refreshIcons } from "./icons.js";
|
|
5
|
+
import { escapeHtml } from "./utils.js";
|
|
6
|
+
import { store } from './store.js';
|
|
7
|
+
import { getWs } from './ws-ref.js';
|
|
8
|
+
|
|
9
|
+
// --- Module-owned state (not in store) ---
|
|
10
|
+
var sessionUsage = { cost: 0, input: 0, output: 0, cacheRead: 0, cacheWrite: 0, turns: 0 };
|
|
11
|
+
var contextData = { contextWindow: 0, maxOutputTokens: 0, model: "-", cost: 0, input: 0, output: 0, cacheRead: 0, cacheWrite: 0, turns: 0 };
|
|
12
|
+
var ctxPopoverEl = null;
|
|
13
|
+
var ctxHoverTimer = null;
|
|
14
|
+
var statusRefreshTimer = null;
|
|
15
|
+
|
|
16
|
+
// --- DOM refs ---
|
|
17
|
+
var configChipWrap = null;
|
|
18
|
+
var configChip = null;
|
|
19
|
+
var configChipLabel = null;
|
|
20
|
+
var configPopover = null;
|
|
21
|
+
var configModelList = null;
|
|
22
|
+
var configModeList = null;
|
|
23
|
+
var configEffortSection = null;
|
|
24
|
+
var configEffortBar = null;
|
|
25
|
+
var configBetaSection = null;
|
|
26
|
+
var configBeta1mBtn = null;
|
|
27
|
+
var configThinkingSection = null;
|
|
28
|
+
var configThinkingBar = null;
|
|
29
|
+
var configThinkingBudgetRow = null;
|
|
30
|
+
var configThinkingBudgetInput = null;
|
|
31
|
+
|
|
32
|
+
var usagePanel = null;
|
|
33
|
+
var usagePanelClose = null;
|
|
34
|
+
var usageCostEl = null;
|
|
35
|
+
var usageInputEl = null;
|
|
36
|
+
var usageOutputEl = null;
|
|
37
|
+
var usageCacheReadEl = null;
|
|
38
|
+
var usageCacheWriteEl = null;
|
|
39
|
+
var usageTurnsEl = null;
|
|
40
|
+
|
|
41
|
+
var statusPanel = null;
|
|
42
|
+
var statusPanelClose = null;
|
|
43
|
+
var statusPidEl = null;
|
|
44
|
+
var statusUptimeEl = null;
|
|
45
|
+
var statusRssEl = null;
|
|
46
|
+
var statusHeapUsedEl = null;
|
|
47
|
+
var statusHeapTotalEl = null;
|
|
48
|
+
var statusExternalEl = null;
|
|
49
|
+
var statusSessionsEl = null;
|
|
50
|
+
var statusProcessingEl = null;
|
|
51
|
+
var statusClientsEl = null;
|
|
52
|
+
var statusTerminalsEl = null;
|
|
53
|
+
|
|
54
|
+
var contextPanel = null;
|
|
55
|
+
var contextPanelClose = null;
|
|
56
|
+
var contextPanelMinimize = null;
|
|
57
|
+
var contextBarFill = null;
|
|
58
|
+
var contextBarPct = null;
|
|
59
|
+
var contextUsedEl = null;
|
|
60
|
+
var contextWindowEl = null;
|
|
61
|
+
var contextMaxOutputEl = null;
|
|
62
|
+
var contextInputEl = null;
|
|
63
|
+
var contextOutputEl = null;
|
|
64
|
+
var contextCacheReadEl = null;
|
|
65
|
+
var contextCacheWriteEl = null;
|
|
66
|
+
var contextModelEl = null;
|
|
67
|
+
var contextCostEl = null;
|
|
68
|
+
var contextTurnsEl = null;
|
|
69
|
+
var contextMini = null;
|
|
70
|
+
var contextMiniFill = null;
|
|
71
|
+
var contextMiniLabel = null;
|
|
72
|
+
|
|
73
|
+
// --- Constants ---
|
|
74
|
+
var MODE_OPTIONS = [
|
|
75
|
+
{ value: "default", label: "Default" },
|
|
76
|
+
{ value: "plan", label: "Plan" },
|
|
77
|
+
{ value: "acceptEdits", label: "Auto-accept edits" },
|
|
78
|
+
];
|
|
79
|
+
var MODE_FULL_AUTO = { value: "bypassPermissions", label: "Full auto" };
|
|
80
|
+
var EFFORT_LEVELS = ["low", "medium", "high", "max"];
|
|
81
|
+
var THINKING_OPTIONS = ["disabled", "adaptive", "budget"];
|
|
82
|
+
var KNOWN_CONTEXT_WINDOWS = {
|
|
83
|
+
"opus-4-6": 1000000,
|
|
84
|
+
"claude-sonnet-4": 1000000
|
|
85
|
+
};
|
|
86
|
+
// Categories to hide from the legend (noise, not actionable)
|
|
87
|
+
var CTX_HIDDEN_CATS = { "Free space": 1, "Autocompact buffer": 1 };
|
|
88
|
+
|
|
89
|
+
// --- Non-store state accessors (module-owned, not in store) ---
|
|
90
|
+
export function getSessionUsage() { return sessionUsage; }
|
|
91
|
+
export function setSessionUsage(v) { sessionUsage = v; }
|
|
92
|
+
export function getContextData() { return contextData; }
|
|
93
|
+
export function setContextData(v) { contextData = v; }
|
|
94
|
+
|
|
95
|
+
// --- Internal helpers ---
|
|
96
|
+
|
|
97
|
+
function modelDisplayName(value, models) {
|
|
98
|
+
if (!value) return "";
|
|
99
|
+
if (models) {
|
|
100
|
+
for (var i = 0; i < models.length; i++) {
|
|
101
|
+
if (models[i].value === value && models[i].displayName) return models[i].displayName;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return value;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function modeDisplayName(value) {
|
|
108
|
+
for (var i = 0; i < MODE_OPTIONS.length; i++) {
|
|
109
|
+
if (MODE_OPTIONS[i].value === value) return MODE_OPTIONS[i].label;
|
|
110
|
+
}
|
|
111
|
+
if (value === "bypassPermissions") return "Full auto";
|
|
112
|
+
if (value === "dontAsk") return "Don\u2019t ask";
|
|
113
|
+
return value;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function effortDisplayName(value) {
|
|
117
|
+
if (!value) return "";
|
|
118
|
+
return value.charAt(0).toUpperCase() + value.slice(1);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function thinkingDisplayName(value) {
|
|
122
|
+
if (value === "disabled") return "Off";
|
|
123
|
+
if (value === "adaptive") return "Adaptive";
|
|
124
|
+
if (value === "budget") return "Budget";
|
|
125
|
+
return value || "Adaptive";
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function isSonnetModel(model) {
|
|
129
|
+
if (!model) return false;
|
|
130
|
+
var lower = model.toLowerCase();
|
|
131
|
+
return lower.indexOf("sonnet") !== -1;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function hasBeta(name) {
|
|
135
|
+
var betas = store.getState().currentBetas;
|
|
136
|
+
for (var i = 0; i < betas.length; i++) {
|
|
137
|
+
if (betas[i].indexOf(name) !== -1) return true;
|
|
138
|
+
}
|
|
139
|
+
return false;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function rebuildModelList() {
|
|
143
|
+
if (!configModelList) return;
|
|
144
|
+
configModelList.innerHTML = "";
|
|
145
|
+
var s = store.getState();
|
|
146
|
+
var list = s.currentModels.length > 0 ? s.currentModels : (s.currentModel ? [{ value: s.currentModel, displayName: s.currentModel }] : []);
|
|
147
|
+
for (var i = 0; i < list.length; i++) {
|
|
148
|
+
var item = list[i];
|
|
149
|
+
var value = item.value || "";
|
|
150
|
+
var label = item.displayName || value;
|
|
151
|
+
var btn = document.createElement("button");
|
|
152
|
+
btn.className = "config-radio-item";
|
|
153
|
+
if (value === s.currentModel) btn.classList.add("active");
|
|
154
|
+
btn.dataset.model = value;
|
|
155
|
+
btn.textContent = label;
|
|
156
|
+
btn.addEventListener("click", function () {
|
|
157
|
+
var model = this.dataset.model;
|
|
158
|
+
var ws = getWs();
|
|
159
|
+
if (ws && ws.readyState === 1) {
|
|
160
|
+
ws.send(JSON.stringify({ type: "set_model", model: model }));
|
|
161
|
+
}
|
|
162
|
+
configPopover.classList.add("hidden");
|
|
163
|
+
configChip.classList.remove("active");
|
|
164
|
+
});
|
|
165
|
+
configModelList.appendChild(btn);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function rebuildModeList() {
|
|
170
|
+
if (!configModeList) return;
|
|
171
|
+
configModeList.innerHTML = "";
|
|
172
|
+
var options = MODE_OPTIONS.slice();
|
|
173
|
+
if (store.getState().skipPermsEnabled) {
|
|
174
|
+
options.push(MODE_FULL_AUTO);
|
|
175
|
+
}
|
|
176
|
+
for (var i = 0; i < options.length; i++) {
|
|
177
|
+
var opt = options[i];
|
|
178
|
+
var btn = document.createElement("button");
|
|
179
|
+
btn.className = "config-radio-item";
|
|
180
|
+
if (opt.value === store.getState().currentMode) btn.classList.add("active");
|
|
181
|
+
btn.dataset.mode = opt.value;
|
|
182
|
+
btn.textContent = opt.label;
|
|
183
|
+
btn.addEventListener("click", function () {
|
|
184
|
+
var mode = this.dataset.mode;
|
|
185
|
+
var ws = getWs();
|
|
186
|
+
if (ws && ws.readyState === 1) {
|
|
187
|
+
ws.send(JSON.stringify({ type: "set_permission_mode", mode: mode }));
|
|
188
|
+
}
|
|
189
|
+
configPopover.classList.add("hidden");
|
|
190
|
+
configChip.classList.remove("active");
|
|
191
|
+
});
|
|
192
|
+
configModeList.appendChild(btn);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function rebuildEffortBar() {
|
|
197
|
+
if (!configEffortBar || !configEffortSection) return;
|
|
198
|
+
var supportsEffort = getModelSupportsEffort();
|
|
199
|
+
if (!supportsEffort) {
|
|
200
|
+
configEffortSection.style.display = "none";
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
configEffortSection.style.display = "";
|
|
204
|
+
configEffortBar.innerHTML = "";
|
|
205
|
+
var levels = getModelEffortLevels();
|
|
206
|
+
for (var i = 0; i < levels.length; i++) {
|
|
207
|
+
var level = levels[i];
|
|
208
|
+
var btn = document.createElement("button");
|
|
209
|
+
btn.className = "config-segment-btn";
|
|
210
|
+
if (level === store.getState().currentEffort) btn.classList.add("active");
|
|
211
|
+
btn.dataset.effort = level;
|
|
212
|
+
btn.textContent = effortDisplayName(level);
|
|
213
|
+
btn.addEventListener("click", function () {
|
|
214
|
+
var effort = this.dataset.effort;
|
|
215
|
+
var ws = getWs();
|
|
216
|
+
if (ws && ws.readyState === 1) {
|
|
217
|
+
ws.send(JSON.stringify({ type: "set_effort", effort: effort }));
|
|
218
|
+
}
|
|
219
|
+
configPopover.classList.add("hidden");
|
|
220
|
+
configChip.classList.remove("active");
|
|
221
|
+
});
|
|
222
|
+
configEffortBar.appendChild(btn);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function rebuildBetaSection() {
|
|
227
|
+
if (!configBetaSection || !configBeta1mBtn) return;
|
|
228
|
+
// Only show for Sonnet models
|
|
229
|
+
if (!isSonnetModel(store.getState().currentModel)) {
|
|
230
|
+
configBetaSection.style.display = "none";
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
configBetaSection.style.display = "";
|
|
234
|
+
var active = hasBeta("context-1m");
|
|
235
|
+
configBeta1mBtn.classList.toggle("active", active);
|
|
236
|
+
configBeta1mBtn.setAttribute("aria-checked", active ? "true" : "false");
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function rebuildThinkingSection() {
|
|
240
|
+
if (!configThinkingBar || !configThinkingSection) return;
|
|
241
|
+
configThinkingSection.style.display = "";
|
|
242
|
+
configThinkingBar.innerHTML = "";
|
|
243
|
+
var s = store.getState();
|
|
244
|
+
for (var i = 0; i < THINKING_OPTIONS.length; i++) {
|
|
245
|
+
var opt = THINKING_OPTIONS[i];
|
|
246
|
+
var btn = document.createElement("button");
|
|
247
|
+
btn.className = "config-segment-btn";
|
|
248
|
+
if (opt === s.currentThinking) btn.classList.add("active");
|
|
249
|
+
btn.dataset.thinking = opt;
|
|
250
|
+
btn.textContent = thinkingDisplayName(opt);
|
|
251
|
+
btn.addEventListener("click", function () {
|
|
252
|
+
var thinking = this.dataset.thinking;
|
|
253
|
+
var msg = { type: "set_thinking", thinking: thinking };
|
|
254
|
+
if (thinking === "budget") {
|
|
255
|
+
msg.budgetTokens = store.getState().currentThinkingBudget;
|
|
256
|
+
}
|
|
257
|
+
var ws = getWs();
|
|
258
|
+
if (ws && ws.readyState === 1) {
|
|
259
|
+
ws.send(JSON.stringify(msg));
|
|
260
|
+
}
|
|
261
|
+
});
|
|
262
|
+
configThinkingBar.appendChild(btn);
|
|
263
|
+
}
|
|
264
|
+
// Show/hide budget input
|
|
265
|
+
if (configThinkingBudgetRow) {
|
|
266
|
+
configThinkingBudgetRow.style.display = s.currentThinking === "budget" ? "" : "none";
|
|
267
|
+
}
|
|
268
|
+
if (configThinkingBudgetInput) {
|
|
269
|
+
configThinkingBudgetInput.value = s.currentThinkingBudget;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function escHtml(s) {
|
|
274
|
+
var div = document.createElement("div");
|
|
275
|
+
div.textContent = s;
|
|
276
|
+
return div.innerHTML;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function em(emoji) {
|
|
280
|
+
return '<span class="ctx-emoji">' + emoji + '</span>';
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// --- Exported functions ---
|
|
284
|
+
|
|
285
|
+
export function initPanels() {
|
|
286
|
+
var $ = function (id) { return document.getElementById(id); };
|
|
287
|
+
|
|
288
|
+
// Config chip DOM refs
|
|
289
|
+
configChipWrap = $("config-chip-wrap");
|
|
290
|
+
configChip = $("config-chip");
|
|
291
|
+
configChipLabel = $("config-chip-label");
|
|
292
|
+
configPopover = $("config-popover");
|
|
293
|
+
configModelList = $("config-model-list");
|
|
294
|
+
configModeList = $("config-mode-list");
|
|
295
|
+
configEffortSection = $("config-effort-section");
|
|
296
|
+
configEffortBar = $("config-effort-bar");
|
|
297
|
+
configBetaSection = $("config-beta-section");
|
|
298
|
+
configBeta1mBtn = $("config-beta-1m");
|
|
299
|
+
configThinkingSection = $("config-thinking-section");
|
|
300
|
+
configThinkingBar = $("config-thinking-bar");
|
|
301
|
+
configThinkingBudgetRow = $("config-thinking-budget-row");
|
|
302
|
+
configThinkingBudgetInput = $("config-thinking-budget");
|
|
303
|
+
|
|
304
|
+
// Usage panel DOM refs
|
|
305
|
+
usagePanel = $("usage-panel");
|
|
306
|
+
usagePanelClose = $("usage-panel-close");
|
|
307
|
+
usageCostEl = $("usage-cost");
|
|
308
|
+
usageInputEl = $("usage-input");
|
|
309
|
+
usageOutputEl = $("usage-output");
|
|
310
|
+
usageCacheReadEl = $("usage-cache-read");
|
|
311
|
+
usageCacheWriteEl = $("usage-cache-write");
|
|
312
|
+
usageTurnsEl = $("usage-turns");
|
|
313
|
+
|
|
314
|
+
// Status panel DOM refs
|
|
315
|
+
statusPanel = $("status-panel");
|
|
316
|
+
statusPanelClose = $("status-panel-close");
|
|
317
|
+
statusPidEl = $("status-pid");
|
|
318
|
+
statusUptimeEl = $("status-uptime");
|
|
319
|
+
statusRssEl = $("status-rss");
|
|
320
|
+
statusHeapUsedEl = $("status-heap-used");
|
|
321
|
+
statusHeapTotalEl = $("status-heap-total");
|
|
322
|
+
statusExternalEl = $("status-external");
|
|
323
|
+
statusSessionsEl = $("status-sessions");
|
|
324
|
+
statusProcessingEl = $("status-processing");
|
|
325
|
+
statusClientsEl = $("status-clients");
|
|
326
|
+
statusTerminalsEl = $("status-terminals");
|
|
327
|
+
|
|
328
|
+
// Context panel DOM refs
|
|
329
|
+
contextPanel = $("context-panel");
|
|
330
|
+
contextPanelClose = $("context-panel-close");
|
|
331
|
+
contextPanelMinimize = $("context-panel-minimize");
|
|
332
|
+
contextBarFill = $("context-bar-fill");
|
|
333
|
+
contextBarPct = $("context-bar-pct");
|
|
334
|
+
contextUsedEl = $("context-used");
|
|
335
|
+
contextWindowEl = $("context-window");
|
|
336
|
+
contextMaxOutputEl = $("context-max-output");
|
|
337
|
+
contextInputEl = $("context-input");
|
|
338
|
+
contextOutputEl = $("context-output");
|
|
339
|
+
contextCacheReadEl = $("context-cache-read");
|
|
340
|
+
contextCacheWriteEl = $("context-cache-write");
|
|
341
|
+
contextModelEl = $("context-model");
|
|
342
|
+
contextCostEl = $("context-cost");
|
|
343
|
+
contextTurnsEl = $("context-turns");
|
|
344
|
+
contextMini = $("context-mini");
|
|
345
|
+
contextMiniFill = $("context-mini-fill");
|
|
346
|
+
contextMiniLabel = $("context-mini-label");
|
|
347
|
+
|
|
348
|
+
// --- Event listeners ---
|
|
349
|
+
|
|
350
|
+
if (configThinkingBudgetInput) {
|
|
351
|
+
configThinkingBudgetInput.addEventListener("change", function () {
|
|
352
|
+
var val = parseInt(this.value, 10);
|
|
353
|
+
if (isNaN(val) || val < 1024) val = 1024;
|
|
354
|
+
if (val > 128000) val = 128000;
|
|
355
|
+
store.setState({ currentThinkingBudget: val });
|
|
356
|
+
this.value = val;
|
|
357
|
+
var ws = getWs();
|
|
358
|
+
if (ws && ws.readyState === 1) {
|
|
359
|
+
ws.send(JSON.stringify({ type: "set_thinking", thinking: "budget", budgetTokens: val }));
|
|
360
|
+
}
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
if (configBeta1mBtn) {
|
|
365
|
+
configBeta1mBtn.addEventListener("click", function (e) {
|
|
366
|
+
e.stopPropagation();
|
|
367
|
+
var active = hasBeta("context-1m");
|
|
368
|
+
var betas = store.getState().currentBetas;
|
|
369
|
+
var newBetas;
|
|
370
|
+
if (active) {
|
|
371
|
+
// Remove context-1m beta
|
|
372
|
+
newBetas = [];
|
|
373
|
+
for (var i = 0; i < betas.length; i++) {
|
|
374
|
+
if (betas[i].indexOf("context-1m") === -1) {
|
|
375
|
+
newBetas.push(betas[i]);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
} else {
|
|
379
|
+
// Add context-1m beta
|
|
380
|
+
newBetas = betas.slice();
|
|
381
|
+
newBetas.push("context-1m-2025-08-07");
|
|
382
|
+
}
|
|
383
|
+
var ws = getWs();
|
|
384
|
+
if (ws && ws.readyState === 1) {
|
|
385
|
+
ws.send(JSON.stringify({ type: "set_betas", betas: newBetas }));
|
|
386
|
+
}
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
if (configChip) {
|
|
391
|
+
configChip.addEventListener("click", function (e) {
|
|
392
|
+
e.stopPropagation();
|
|
393
|
+
var wasHidden = configPopover.classList.toggle("hidden");
|
|
394
|
+
configChip.classList.toggle("active", !wasHidden);
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
document.addEventListener("click", function (e) {
|
|
399
|
+
if (configPopover && configChip && !configPopover.contains(e.target) && e.target !== configChip) {
|
|
400
|
+
configPopover.classList.add("hidden");
|
|
401
|
+
configChip.classList.remove("active");
|
|
402
|
+
}
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
if (usagePanelClose) {
|
|
406
|
+
usagePanelClose.addEventListener("click", function () {
|
|
407
|
+
usagePanel.classList.add("hidden");
|
|
408
|
+
});
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
if (statusPanelClose) {
|
|
412
|
+
statusPanelClose.addEventListener("click", function () {
|
|
413
|
+
statusPanel.classList.add("hidden");
|
|
414
|
+
if (statusRefreshTimer) {
|
|
415
|
+
clearInterval(statusRefreshTimer);
|
|
416
|
+
statusRefreshTimer = null;
|
|
417
|
+
}
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
if (contextPanelClose) {
|
|
422
|
+
contextPanelClose.addEventListener("click", function () {
|
|
423
|
+
setContextView("off");
|
|
424
|
+
applyContextView("off");
|
|
425
|
+
});
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
if (contextPanelMinimize) {
|
|
429
|
+
contextPanelMinimize.addEventListener("click", minimizeContext);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
if (contextMini) {
|
|
433
|
+
contextMini.addEventListener("click", expandContext);
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
// Restore context view on load
|
|
437
|
+
applyContextView(getContextView());
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
// --- Config chip ---
|
|
441
|
+
|
|
442
|
+
export function updateConfigChip() {
|
|
443
|
+
if (!configChipWrap || !configChip) return;
|
|
444
|
+
configChipWrap.classList.remove("hidden");
|
|
445
|
+
var s = store.getState();
|
|
446
|
+
var parts = [modelDisplayName(s.currentModel, s.currentModels)];
|
|
447
|
+
parts.push(modeDisplayName(s.currentMode));
|
|
448
|
+
// Only show effort if model supports it
|
|
449
|
+
var modelSupportsEffort = getModelSupportsEffort();
|
|
450
|
+
if (modelSupportsEffort) {
|
|
451
|
+
parts.push(effortDisplayName(s.currentEffort));
|
|
452
|
+
}
|
|
453
|
+
if (s.currentThinking && s.currentThinking !== "adaptive") {
|
|
454
|
+
parts.push(thinkingDisplayName(s.currentThinking));
|
|
455
|
+
}
|
|
456
|
+
if (hasBeta("context-1m")) {
|
|
457
|
+
parts.push("1M");
|
|
458
|
+
}
|
|
459
|
+
configChipLabel.textContent = parts.join(" \u00b7 ");
|
|
460
|
+
rebuildModelList();
|
|
461
|
+
rebuildModeList();
|
|
462
|
+
rebuildEffortBar();
|
|
463
|
+
rebuildThinkingSection();
|
|
464
|
+
rebuildBetaSection();
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
export function getModelSupportsEffort() {
|
|
468
|
+
var s = store.getState();
|
|
469
|
+
if (!s.currentModels || s.currentModels.length === 0) return true; // assume yes if no info
|
|
470
|
+
for (var i = 0; i < s.currentModels.length; i++) {
|
|
471
|
+
if (s.currentModels[i].value === s.currentModel) {
|
|
472
|
+
if (s.currentModels[i].supportsEffort === false) return false;
|
|
473
|
+
return true;
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
return true;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
export function getModelEffortLevels() {
|
|
480
|
+
var s = store.getState();
|
|
481
|
+
if (!s.currentModels || s.currentModels.length === 0) return EFFORT_LEVELS;
|
|
482
|
+
for (var i = 0; i < s.currentModels.length; i++) {
|
|
483
|
+
if (s.currentModels[i].value === s.currentModel) {
|
|
484
|
+
if (s.currentModels[i].supportedEffortLevels && s.currentModels[i].supportedEffortLevels.length > 0) {
|
|
485
|
+
return s.currentModels[i].supportedEffortLevels;
|
|
486
|
+
}
|
|
487
|
+
return EFFORT_LEVELS;
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
return EFFORT_LEVELS;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
// --- Usage panel ---
|
|
494
|
+
|
|
495
|
+
export function formatTokens(n) {
|
|
496
|
+
if (n >= 1000000) return (n / 1000000).toFixed(1) + "M";
|
|
497
|
+
if (n >= 1000) return (n / 1000).toFixed(1) + "K";
|
|
498
|
+
return String(n);
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
export function updateUsagePanel() {
|
|
502
|
+
if (!usageCostEl) return;
|
|
503
|
+
usageCostEl.textContent = "$" + sessionUsage.cost.toFixed(4);
|
|
504
|
+
usageInputEl.textContent = formatTokens(sessionUsage.input);
|
|
505
|
+
usageOutputEl.textContent = formatTokens(sessionUsage.output);
|
|
506
|
+
usageCacheReadEl.textContent = formatTokens(sessionUsage.cacheRead);
|
|
507
|
+
usageCacheWriteEl.textContent = formatTokens(sessionUsage.cacheWrite);
|
|
508
|
+
usageTurnsEl.textContent = String(sessionUsage.turns);
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
export function accumulateUsage(cost, usage) {
|
|
512
|
+
// cost is the SDK's total_cost_usd -- a cumulative running total, not a delta.
|
|
513
|
+
// Assign directly instead of summing to avoid overcounting.
|
|
514
|
+
if (cost != null) sessionUsage.cost = cost;
|
|
515
|
+
if (usage) {
|
|
516
|
+
sessionUsage.input += usage.input_tokens || usage.inputTokens || 0;
|
|
517
|
+
sessionUsage.output += usage.output_tokens || usage.outputTokens || 0;
|
|
518
|
+
sessionUsage.cacheRead += usage.cache_read_input_tokens || usage.cacheReadInputTokens || 0;
|
|
519
|
+
sessionUsage.cacheWrite += usage.cache_creation_input_tokens || usage.cacheCreationInputTokens || 0;
|
|
520
|
+
}
|
|
521
|
+
sessionUsage.turns++;
|
|
522
|
+
if (!store.getState().replayingHistory) updateUsagePanel();
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
export function resetUsage() {
|
|
526
|
+
sessionUsage = { cost: 0, input: 0, output: 0, cacheRead: 0, cacheWrite: 0, turns: 0 };
|
|
527
|
+
updateUsagePanel();
|
|
528
|
+
if (usagePanel) usagePanel.classList.add("hidden");
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
export function toggleUsagePanel() {
|
|
532
|
+
if (!usagePanel) return;
|
|
533
|
+
usagePanel.classList.toggle("hidden");
|
|
534
|
+
refreshIcons();
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
// --- Status panel ---
|
|
538
|
+
|
|
539
|
+
export function formatBytes(n) {
|
|
540
|
+
if (n >= 1073741824) return (n / 1073741824).toFixed(1) + " GB";
|
|
541
|
+
if (n >= 1048576) return (n / 1048576).toFixed(1) + " MB";
|
|
542
|
+
if (n >= 1024) return (n / 1024).toFixed(1) + " KB";
|
|
543
|
+
return n + " B";
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
export function formatUptime(seconds) {
|
|
547
|
+
var d = Math.floor(seconds / 86400);
|
|
548
|
+
var h = Math.floor((seconds % 86400) / 3600);
|
|
549
|
+
var m = Math.floor((seconds % 3600) / 60);
|
|
550
|
+
var s = Math.floor(seconds % 60);
|
|
551
|
+
if (d > 0) return d + "d " + h + "h " + m + "m";
|
|
552
|
+
if (h > 0) return h + "h " + m + "m " + s + "s";
|
|
553
|
+
return m + "m " + s + "s";
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
export function updateStatusPanel(data) {
|
|
557
|
+
if (!statusPidEl) return;
|
|
558
|
+
statusPidEl.textContent = String(data.pid);
|
|
559
|
+
statusUptimeEl.textContent = formatUptime(data.uptime);
|
|
560
|
+
statusRssEl.textContent = formatBytes(data.memory.rss);
|
|
561
|
+
statusHeapUsedEl.textContent = formatBytes(data.memory.heapUsed);
|
|
562
|
+
statusHeapTotalEl.textContent = formatBytes(data.memory.heapTotal);
|
|
563
|
+
statusExternalEl.textContent = formatBytes(data.memory.external);
|
|
564
|
+
statusSessionsEl.textContent = String(data.sessions);
|
|
565
|
+
statusProcessingEl.textContent = String(data.processing);
|
|
566
|
+
statusClientsEl.textContent = String(data.clients);
|
|
567
|
+
statusTerminalsEl.textContent = String(data.terminals);
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
export function requestProcessStats() {
|
|
571
|
+
var ws = getWs();
|
|
572
|
+
if (ws && ws.readyState === 1) {
|
|
573
|
+
ws.send(JSON.stringify({ type: "process_stats" }));
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
export function toggleStatusPanel() {
|
|
578
|
+
if (!statusPanel) return;
|
|
579
|
+
var opening = statusPanel.classList.contains("hidden");
|
|
580
|
+
statusPanel.classList.toggle("hidden");
|
|
581
|
+
if (opening) {
|
|
582
|
+
requestProcessStats();
|
|
583
|
+
statusRefreshTimer = setInterval(requestProcessStats, 5000);
|
|
584
|
+
} else {
|
|
585
|
+
if (statusRefreshTimer) {
|
|
586
|
+
clearInterval(statusRefreshTimer);
|
|
587
|
+
statusRefreshTimer = null;
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
refreshIcons();
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
// --- Context panel ---
|
|
594
|
+
|
|
595
|
+
export function resolveContextWindow(model, sdkValue) {
|
|
596
|
+
if (sdkValue) return sdkValue;
|
|
597
|
+
var lc = (model || "").toLowerCase();
|
|
598
|
+
for (var key in KNOWN_CONTEXT_WINDOWS) {
|
|
599
|
+
if (lc.includes(key)) return KNOWN_CONTEXT_WINDOWS[key];
|
|
600
|
+
}
|
|
601
|
+
return 200000;
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
export function contextPctClass(pct) {
|
|
605
|
+
return pct >= 85 ? " danger" : pct >= 60 ? " warn" : "";
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
export function updateContextPanel() {
|
|
609
|
+
if (!contextUsedEl) return;
|
|
610
|
+
// Context window usage = input tokens only (includes cache read/write)
|
|
611
|
+
var used = contextData.input;
|
|
612
|
+
var win = contextData.contextWindow;
|
|
613
|
+
var pct = win > 0 ? Math.min(100, (used / win) * 100) : 0;
|
|
614
|
+
var cls = contextPctClass(pct);
|
|
615
|
+
// Panel bar
|
|
616
|
+
contextBarFill.style.width = pct.toFixed(1) + "%";
|
|
617
|
+
contextBarFill.className = "context-bar-fill" + cls;
|
|
618
|
+
contextBarPct.textContent = pct.toFixed(0) + "%";
|
|
619
|
+
// Mini bar
|
|
620
|
+
if (contextMiniFill) {
|
|
621
|
+
contextMiniFill.style.width = pct.toFixed(1) + "%";
|
|
622
|
+
contextMiniFill.className = "context-mini-fill" + cls;
|
|
623
|
+
}
|
|
624
|
+
if (contextMiniLabel) {
|
|
625
|
+
contextMiniLabel.textContent = (win > 0 ? formatTokens(used) + "/" + formatTokens(win) : "0%");
|
|
626
|
+
}
|
|
627
|
+
// Header bar
|
|
628
|
+
if (pct > 0) {
|
|
629
|
+
var statusArea = document.querySelector(".title-bar-content .status");
|
|
630
|
+
var hCtxEl = store.getState().headerContextEl;
|
|
631
|
+
if (statusArea && !hCtxEl) {
|
|
632
|
+
hCtxEl = document.createElement("div");
|
|
633
|
+
hCtxEl.className = "header-context";
|
|
634
|
+
hCtxEl.innerHTML = '<div class="header-context-bar"><div class="header-context-fill"></div></div><span class="header-context-label"></span>';
|
|
635
|
+
statusArea.insertBefore(hCtxEl, statusArea.firstChild);
|
|
636
|
+
hCtxEl.addEventListener("mouseenter", function() {
|
|
637
|
+
if (store.getState().richContextUsage) {
|
|
638
|
+
showCtxPopover();
|
|
639
|
+
}
|
|
640
|
+
});
|
|
641
|
+
hCtxEl.addEventListener("mouseleave", function() {
|
|
642
|
+
ctxHoverTimer = setTimeout(hideCtxPopover, 120);
|
|
643
|
+
});
|
|
644
|
+
store.setState({ headerContextEl: hCtxEl });
|
|
645
|
+
}
|
|
646
|
+
if (hCtxEl) {
|
|
647
|
+
var hFill = hCtxEl.querySelector(".header-context-fill");
|
|
648
|
+
var hLabel = hCtxEl.querySelector(".header-context-label");
|
|
649
|
+
hFill.style.width = pct.toFixed(1) + "%";
|
|
650
|
+
hFill.className = "header-context-fill" + cls;
|
|
651
|
+
hLabel.textContent = pct.toFixed(0) + "%";
|
|
652
|
+
// Use data-tip as fallback when rich data is not yet loaded
|
|
653
|
+
if (store.getState().richContextUsage) {
|
|
654
|
+
hCtxEl.removeAttribute("data-tip");
|
|
655
|
+
} else {
|
|
656
|
+
hCtxEl.dataset.tip = "Context window " + pct.toFixed(0) + "% used (" + formatTokens(used) + " / " + formatTokens(win) + " tokens)";
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
contextUsedEl.textContent = formatTokens(used);
|
|
661
|
+
contextWindowEl.textContent = win > 0 ? formatTokens(win) : "-";
|
|
662
|
+
contextMaxOutputEl.textContent = contextData.maxOutputTokens > 0 ? formatTokens(contextData.maxOutputTokens) : "-";
|
|
663
|
+
contextInputEl.textContent = formatTokens(contextData.input);
|
|
664
|
+
contextOutputEl.textContent = formatTokens(contextData.output);
|
|
665
|
+
contextCacheReadEl.textContent = formatTokens(contextData.cacheRead);
|
|
666
|
+
contextCacheWriteEl.textContent = formatTokens(contextData.cacheWrite);
|
|
667
|
+
contextModelEl.textContent = contextData.model;
|
|
668
|
+
contextCostEl.textContent = "$" + contextData.cost.toFixed(4);
|
|
669
|
+
contextTurnsEl.textContent = String(contextData.turns);
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
export function accumulateContext(cost, usage, modelUsage, lastStreamInputTokens) {
|
|
673
|
+
// cost is the SDK's total_cost_usd -- a cumulative running total, not a delta.
|
|
674
|
+
if (cost != null) contextData.cost = cost;
|
|
675
|
+
// Use latest turn values (not cumulative) since each turn's input_tokens
|
|
676
|
+
// already includes the full conversation context up to that point
|
|
677
|
+
if (usage) {
|
|
678
|
+
// Prefer per-call input_tokens from the last stream message_start event
|
|
679
|
+
// when available -- result.usage.input_tokens sums all API calls in a turn,
|
|
680
|
+
// inflating context usage when tools are involved.
|
|
681
|
+
// Falls back to the summed value for setups that don't emit message_start.
|
|
682
|
+
if (lastStreamInputTokens) {
|
|
683
|
+
contextData.input = lastStreamInputTokens;
|
|
684
|
+
} else {
|
|
685
|
+
contextData.input = (usage.input_tokens || usage.inputTokens || 0)
|
|
686
|
+
+ (usage.cache_read_input_tokens || usage.cacheReadInputTokens || 0);
|
|
687
|
+
}
|
|
688
|
+
contextData.output = usage.output_tokens || usage.outputTokens || 0;
|
|
689
|
+
contextData.cacheRead = usage.cache_read_input_tokens || usage.cacheReadInputTokens || 0;
|
|
690
|
+
contextData.cacheWrite = usage.cache_creation_input_tokens || usage.cacheCreationInputTokens || 0;
|
|
691
|
+
}
|
|
692
|
+
contextData.turns++;
|
|
693
|
+
if (modelUsage) {
|
|
694
|
+
var models = Object.keys(modelUsage);
|
|
695
|
+
if (models.length > 0) {
|
|
696
|
+
var m = models[0];
|
|
697
|
+
var mu = modelUsage[m];
|
|
698
|
+
contextData.model = m;
|
|
699
|
+
contextData.contextWindow = resolveContextWindow(m, mu.contextWindow);
|
|
700
|
+
if (mu.maxOutputTokens) contextData.maxOutputTokens = mu.maxOutputTokens;
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
if (!store.getState().replayingHistory) updateContextPanel();
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
// contextView: "off" | "mini" | "panel"
|
|
707
|
+
export function getContextView() {
|
|
708
|
+
try { return localStorage.getItem("clay-context-view") || "off"; } catch (e) { return "off"; }
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
export function setContextView(v) {
|
|
712
|
+
try { localStorage.setItem("clay-context-view", v); } catch (e) {}
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
export function applyContextView(view) {
|
|
716
|
+
if (contextPanel) contextPanel.classList.toggle("hidden", view !== "panel");
|
|
717
|
+
if (contextMini) contextMini.classList.toggle("hidden", view !== "mini");
|
|
718
|
+
if (view === "panel") refreshIcons();
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
export function resetContextData() {
|
|
722
|
+
contextData = { contextWindow: 0, maxOutputTokens: 0, model: "-", cost: 0, input: 0, output: 0, cacheRead: 0, cacheWrite: 0, turns: 0 };
|
|
723
|
+
store.setState({ richContextUsage: null });
|
|
724
|
+
hideCtxPopover();
|
|
725
|
+
updateContextPanel();
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
export function resetContext() {
|
|
729
|
+
resetContextData();
|
|
730
|
+
// Keep view state, just reset data
|
|
731
|
+
applyContextView(getContextView());
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
export function minimizeContext() {
|
|
735
|
+
setContextView("mini");
|
|
736
|
+
applyContextView("mini");
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
export function expandContext() {
|
|
740
|
+
setContextView("panel");
|
|
741
|
+
applyContextView("panel");
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
export function toggleContextPanel() {
|
|
745
|
+
if (!contextPanel) return;
|
|
746
|
+
var view = getContextView();
|
|
747
|
+
if (view === "panel") {
|
|
748
|
+
setContextView("mini");
|
|
749
|
+
applyContextView("mini");
|
|
750
|
+
} else {
|
|
751
|
+
setContextView("panel");
|
|
752
|
+
applyContextView("panel");
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
// --- Rich context usage popover ---
|
|
757
|
+
|
|
758
|
+
export function ensureCtxPopover() {
|
|
759
|
+
if (ctxPopoverEl) return;
|
|
760
|
+
ctxPopoverEl = document.createElement("div");
|
|
761
|
+
ctxPopoverEl.className = "context-usage-popover hidden";
|
|
762
|
+
// Keep popover open when hovering over it
|
|
763
|
+
ctxPopoverEl.addEventListener("mouseenter", function() {
|
|
764
|
+
if (ctxHoverTimer) { clearTimeout(ctxHoverTimer); ctxHoverTimer = null; }
|
|
765
|
+
});
|
|
766
|
+
ctxPopoverEl.addEventListener("mouseleave", function() {
|
|
767
|
+
hideCtxPopover();
|
|
768
|
+
});
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
export function showCtxPopover() {
|
|
772
|
+
var s = store.getState();
|
|
773
|
+
if (!s.headerContextEl || !s.richContextUsage) return;
|
|
774
|
+
if (ctxHoverTimer) { clearTimeout(ctxHoverTimer); ctxHoverTimer = null; }
|
|
775
|
+
ensureCtxPopover();
|
|
776
|
+
s.headerContextEl.appendChild(ctxPopoverEl);
|
|
777
|
+
renderCtxPopover();
|
|
778
|
+
ctxPopoverEl.classList.remove("hidden");
|
|
779
|
+
store.setState({ ctxPopoverVisible: true });
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
export function hideCtxPopover() {
|
|
783
|
+
if (!ctxPopoverEl) return;
|
|
784
|
+
ctxPopoverEl.classList.add("hidden");
|
|
785
|
+
store.setState({ ctxPopoverVisible: false });
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
export function renderCtxPopover() {
|
|
789
|
+
var richContextUsage = store.getState().richContextUsage;
|
|
790
|
+
if (!ctxPopoverEl || !richContextUsage) return;
|
|
791
|
+
var d = richContextUsage;
|
|
792
|
+
var cats = d.categories || [];
|
|
793
|
+
var total = d.totalTokens || 0;
|
|
794
|
+
var max = d.maxTokens || 0;
|
|
795
|
+
var pct = d.percentage != null ? d.percentage : (max > 0 ? (total / max) * 100 : 0);
|
|
796
|
+
|
|
797
|
+
var html = "";
|
|
798
|
+
|
|
799
|
+
// Header
|
|
800
|
+
html += '<div class="ctx-pop-header">';
|
|
801
|
+
html += '<span class="ctx-pop-model">' + escHtml(d.model || contextData.model || "-") + '</span>';
|
|
802
|
+
html += '<span class="ctx-pop-pct">' + pct.toFixed(0) + '%';
|
|
803
|
+
html += '<span class="ctx-pop-tokens">' + formatTokens(total) + ' / ' + formatTokens(max) + '</span>';
|
|
804
|
+
html += '</span>';
|
|
805
|
+
html += '</div>';
|
|
806
|
+
|
|
807
|
+
// Category emoji map
|
|
808
|
+
var CTX_EMOJI = {
|
|
809
|
+
"System prompt": "\ud83d\udcdc", "System tools": "\ud83d\udee0\ufe0f",
|
|
810
|
+
"Memory files": "\ud83d\udcc1", "Skills": "\u26a1", "Messages": "\ud83d\udcac",
|
|
811
|
+
"MCP tools": "\ud83d\udd0c", "Agents": "\ud83e\udd16", "Deferred tools": "\ud83d\udce6"
|
|
812
|
+
};
|
|
813
|
+
|
|
814
|
+
// Stacked bar
|
|
815
|
+
if (cats.length > 0 && max > 0) {
|
|
816
|
+
html += '<div class="ctx-cat-bar">';
|
|
817
|
+
for (var i = 0; i < cats.length; i++) {
|
|
818
|
+
var cat = cats[i];
|
|
819
|
+
if (cat.isDeferred || !cat.tokens || CTX_HIDDEN_CATS[cat.name]) continue;
|
|
820
|
+
var w = Math.max(0.3, (cat.tokens / max) * 100);
|
|
821
|
+
html += '<div style="width:' + w.toFixed(2) + '%;background:' + escHtml(cat.color) + '"></div>';
|
|
822
|
+
}
|
|
823
|
+
html += '</div>';
|
|
824
|
+
|
|
825
|
+
// Legend
|
|
826
|
+
html += '<div class="ctx-cat-legend">';
|
|
827
|
+
for (var j = 0; j < cats.length; j++) {
|
|
828
|
+
var c = cats[j];
|
|
829
|
+
if (c.isDeferred || !c.tokens || CTX_HIDDEN_CATS[c.name]) continue;
|
|
830
|
+
var emoji = CTX_EMOJI[c.name] || "\ud83d\udcca";
|
|
831
|
+
html += '<div class="ctx-cat-item">';
|
|
832
|
+
html += '<span class="ctx-cat-name">' + em(emoji) + ' ' + escHtml(c.name) + '</span>';
|
|
833
|
+
html += '<span class="ctx-cat-value">' + formatTokens(c.tokens) + '</span>';
|
|
834
|
+
html += '</div>';
|
|
835
|
+
}
|
|
836
|
+
html += '</div>';
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
// Message breakdown
|
|
840
|
+
var mb = d.messageBreakdown;
|
|
841
|
+
if (mb) {
|
|
842
|
+
html += '<div class="ctx-pop-divider"></div>';
|
|
843
|
+
html += '<div class="ctx-pop-section-label">' + em("\ud83d\udcac") + ' Messages</div>';
|
|
844
|
+
if (mb.userMessageTokens) {
|
|
845
|
+
html += '<div class="ctx-pop-row"><span class="ctx-pop-row-label">' + em("\ud83d\udc64") + ' User</span><span class="ctx-pop-row-value">' + formatTokens(mb.userMessageTokens) + '</span></div>';
|
|
846
|
+
}
|
|
847
|
+
if (mb.assistantMessageTokens) {
|
|
848
|
+
html += '<div class="ctx-pop-row"><span class="ctx-pop-row-label">' + em("\ud83e\udd16") + ' Assistant</span><span class="ctx-pop-row-value">' + formatTokens(mb.assistantMessageTokens) + '</span></div>';
|
|
849
|
+
}
|
|
850
|
+
if (mb.toolCallTokens) {
|
|
851
|
+
html += '<div class="ctx-pop-row"><span class="ctx-pop-row-label">' + em("\ud83d\udee0\ufe0f") + ' Tool calls</span><span class="ctx-pop-row-value">' + formatTokens(mb.toolCallTokens) + '</span></div>';
|
|
852
|
+
}
|
|
853
|
+
if (mb.toolResultTokens) {
|
|
854
|
+
html += '<div class="ctx-pop-row"><span class="ctx-pop-row-label">' + em("\ud83d\udccb") + ' Tool results</span><span class="ctx-pop-row-value">' + formatTokens(mb.toolResultTokens) + '</span></div>';
|
|
855
|
+
}
|
|
856
|
+
if (mb.attachmentTokens) {
|
|
857
|
+
html += '<div class="ctx-pop-row"><span class="ctx-pop-row-label">' + em("\ud83d\udcce") + ' Attachments</span><span class="ctx-pop-row-value">' + formatTokens(mb.attachmentTokens) + '</span></div>';
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
// Memory files
|
|
862
|
+
var mf = d.memoryFiles;
|
|
863
|
+
if (mf && mf.length > 0) {
|
|
864
|
+
html += '<div class="ctx-pop-divider"></div>';
|
|
865
|
+
html += '<div class="ctx-pop-section-label">' + em("\ud83d\udcc1") + ' Memory Files</div>';
|
|
866
|
+
var baseCount = {};
|
|
867
|
+
for (var mc = 0; mc < mf.length; mc++) {
|
|
868
|
+
var bn = mf[mc].path.split("/").pop() || mf[mc].path;
|
|
869
|
+
baseCount[bn] = (baseCount[bn] || 0) + 1;
|
|
870
|
+
}
|
|
871
|
+
for (var mi = 0; mi < mf.length; mi++) {
|
|
872
|
+
var fpath = mf[mi].path;
|
|
873
|
+
var fname = fpath.split("/").pop() || fpath;
|
|
874
|
+
if (baseCount[fname] > 1) {
|
|
875
|
+
var parts = fpath.split("/");
|
|
876
|
+
fname = parts.length >= 2 ? parts[parts.length - 2] + "/" + fname : fpath;
|
|
877
|
+
}
|
|
878
|
+
html += '<div class="ctx-pop-row"><span class="ctx-pop-row-label">' + em("\ud83d\udcc4") + ' ' + escHtml(fname) + '</span><span class="ctx-pop-row-value">' + formatTokens(mf[mi].tokens) + '</span></div>';
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
// Auto-compact note
|
|
883
|
+
if (d.isAutoCompactEnabled && d.autoCompactThreshold) {
|
|
884
|
+
html += '<div class="ctx-pop-note">' + em("\u267b\ufe0f") + ' Auto-compact at ' + formatTokens(d.autoCompactThreshold) + '</div>';
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
ctxPopoverEl.innerHTML = html;
|
|
888
|
+
}
|