@materialwebunofficial/antigravity-themes 0.0.0-stage → 1.0.4
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/LICENSE +201 -0
- package/NOTICE +30 -0
- package/README.md +294 -2
- package/bin/cli.mjs +324 -0
- package/lib/autostart.mjs +90 -0
- package/lib/bundle.mjs +73 -0
- package/lib/injector.mjs +176 -0
- package/lib/paths.mjs +35 -0
- package/package.json +40 -4
- package/src/code-tools.js +137 -0
- package/src/context-card.js +542 -0
- package/src/loading-indicator.js +74 -0
- package/src/progress-indicators.js +98 -0
- package/src/theme-host.js +904 -0
- package/src/turn-tokens.js +115 -0
- package/themes/index.json +27 -0
- package/themes/matte.css +588 -0
- package/themes/md3e-icons.css +244 -0
- package/themes/md3e-tokens.css +834 -0
- package/themes/md3e.css +1400 -0
- package/themes/nordic.css +370 -0
- package/themes/paper.css +414 -0
- package/vendor/md3e/LICENSE +176 -0
- package/vendor/md3e/NOTICE +686 -0
- package/vendor/md3e/Roboto-OFL.txt +93 -0
- package/vendor/md3e/SOURCE.txt +10 -0
- package/vendor/md3e/components.js +4682 -0
- package/vendor/md3e/roboto-variable.woff2 +0 -0
|
@@ -0,0 +1,542 @@
|
|
|
1
|
+
// AGY context card: a "Context" section at the top of the Overview panel —
|
|
2
|
+
// how full the conversation's context window is and what takes it up.
|
|
3
|
+
// Two looks, chosen in CSS from the page's data-agy-theme (:host-context), so
|
|
4
|
+
// a theme switch restyles it at once: Matte (the panel's own quiet rows, flat
|
|
5
|
+
// 4px bar) for every theme but MD3E, and M3 Expressive for MD3E. Segment and
|
|
6
|
+
// dot colors are per-theme custom properties (--k-*), never set from JS.
|
|
7
|
+
//
|
|
8
|
+
// Read-only, from the app's own state:
|
|
9
|
+
// used the latest model call's prompt: input + cache-read + cache-write
|
|
10
|
+
// tokens (CortexStepMetadata.modelUsage) in the conversation view's
|
|
11
|
+
// trajectorySlice (React props above the transcript viewport)
|
|
12
|
+
// groups Rules / Skills / MCP tools / ... from getTokenBase() on the app's
|
|
13
|
+
// agent service, the call its Customizations screen makes
|
|
14
|
+
// window by model family: the app's model configs carry no size; the bar,
|
|
15
|
+
// the chip and the warnings measure against it
|
|
16
|
+
// total the conversation's own running count: every model call the card
|
|
17
|
+
// sees is added once (by step index) and kept per conversation in
|
|
18
|
+
// the agy-usage cookie (last 20; cookies are the one store that
|
|
19
|
+
// survives the app's port changing per launch). Reading the whole
|
|
20
|
+
// history instead costs ~12 KB a step (SPEC); a conversation first
|
|
21
|
+
// seen part-way says which step its count starts from.
|
|
22
|
+
// Warnings at 80% and 95% of the window: one quiet line under the summary.
|
|
23
|
+
function agyContextCard() {
|
|
24
|
+
const WINDOWS = [[/^gemini/i, 1048576], [/^claude/i, 200000], [/gpt-oss/i, 131072]];
|
|
25
|
+
const LEVELS = [[0.95, 'critical', 'The context window is almost full.'],
|
|
26
|
+
[0.8, 'warn', 'The context window is filling up.']];
|
|
27
|
+
const GROUP_NAMES = { 1: 'System prompt', 2: 'Tools', 3: 'Rules', 4: 'MCP tools', 5: 'Skills', 6: 'Subagents', 7: 'Messages', 8: 'Workflows', 9: 'Plugins', 10: 'Flows' };
|
|
28
|
+
const GROUP_KINDS = { 3: 'rules', 4: 'mcp', 5: 'skills' };
|
|
29
|
+
const color = (kind) => `var(--k-${kind})`;
|
|
30
|
+
|
|
31
|
+
const TICK_MS = 2000;
|
|
32
|
+
const BASE_MS = 120000;
|
|
33
|
+
const CHEVRON = '<svg viewBox="0 -960 960 960" aria-hidden="true"><path d="M517.85-480l-184-184L376-706.15L602.15-480L376-253.85L333.85-296l184-184Z"/></svg>';
|
|
34
|
+
|
|
35
|
+
const num = (v) => Number(v ?? 0) || 0;
|
|
36
|
+
const fmt = (n) => (n >= 1e6 ? `${+(n / 1e6).toFixed(1)}M` : n >= 1000 ? `${+(n / 1000).toFixed(1)}k` : String(n));
|
|
37
|
+
const pct = (n, d) => {
|
|
38
|
+
const p = (n / d) * 100;
|
|
39
|
+
return p > 0 && p < 0.1 ? '<0.1%' : `${p.toFixed(p < 10 ? 1 : 0)}%`;
|
|
40
|
+
};
|
|
41
|
+
const fiberOf = (el) => el && el[Object.keys(el).find((k) => k.startsWith('__reactFiber$'))];
|
|
42
|
+
|
|
43
|
+
// ---- Data -------------------------------------------------------------------
|
|
44
|
+
function readSlice() {
|
|
45
|
+
let f = fiberOf(document.querySelector('[data-testid="autoscroll-viewport"]'));
|
|
46
|
+
for (let i = 0; f && i < 40; i++, f = f.return) {
|
|
47
|
+
if (f.memoizedProps?.trajectorySlice) return f.memoizedProps.trajectorySlice;
|
|
48
|
+
}
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
function readUsage(slice) {
|
|
52
|
+
const steps = slice?.stepsInSlice ?? [];
|
|
53
|
+
for (let i = steps.length - 1; i >= 0; i--) {
|
|
54
|
+
const u = steps[i]?.metadata?.modelUsage;
|
|
55
|
+
if (u) return num(u.inputTokens) + num(u.cacheReadTokens) + num(u.cacheWriteTokens);
|
|
56
|
+
}
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
// ---- Conversation total -----------------------------------------------------
|
|
60
|
+
// Cookie: "id:in.out.cache.from.last|…", numbers in base 36, newest first
|
|
61
|
+
const TOTALS_COOKIE = 'agy-usage';
|
|
62
|
+
const TOTALS_MAX = 20;
|
|
63
|
+
function readTotals() {
|
|
64
|
+
try {
|
|
65
|
+
const raw = (document.cookie.match(/(?:^|;\s*)agy-usage=([^;]*)/) || [])[1];
|
|
66
|
+
if (!raw) return [];
|
|
67
|
+
return decodeURIComponent(raw).split('|').map((entry) => {
|
|
68
|
+
const [id, nums = ''] = entry.split(':');
|
|
69
|
+
const [input, output, cache, from, last] = nums.split('.').map((n) => parseInt(n, 36));
|
|
70
|
+
return { id, input, output, cache, from, last };
|
|
71
|
+
}).filter((r) => r.id && [r.input, r.output, r.cache, r.from, r.last].every(Number.isFinite));
|
|
72
|
+
} catch {
|
|
73
|
+
return [];
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
function writeTotals(list) {
|
|
77
|
+
try {
|
|
78
|
+
const value = list.map((r) => `${r.id}:${[r.input, r.output, r.cache, r.from, r.last].map((n) => n.toString(36)).join('.')}`).join('|');
|
|
79
|
+
document.cookie = `${TOTALS_COOKIE}=${encodeURIComponent(value)}; Max-Age=31536000; Path=/; SameSite=Strict; Secure`;
|
|
80
|
+
} catch {}
|
|
81
|
+
}
|
|
82
|
+
let totals = readTotals();
|
|
83
|
+
function countTotal(slice) {
|
|
84
|
+
const id = slice?.conversationId;
|
|
85
|
+
if (!id) return null;
|
|
86
|
+
const start = num(slice.stepsSlice?.startIndex);
|
|
87
|
+
const length = num(slice.totalStepsLength);
|
|
88
|
+
let rec = totals.find((r) => r.id === id);
|
|
89
|
+
let changed = false;
|
|
90
|
+
if (!rec) {
|
|
91
|
+
rec = { id, input: 0, output: 0, cache: 0, from: start, last: start - 1 };
|
|
92
|
+
changed = true;
|
|
93
|
+
}
|
|
94
|
+
if (length && rec.last >= length) { // undone ("Undo to this point"): later steps get reused indices
|
|
95
|
+
rec.last = length - 1;
|
|
96
|
+
changed = true;
|
|
97
|
+
}
|
|
98
|
+
(slice.stepsInSlice ?? []).forEach((s, i) => {
|
|
99
|
+
const u = s?.metadata?.modelUsage;
|
|
100
|
+
if (!u || start + i <= rec.last) return;
|
|
101
|
+
rec.input += num(u.inputTokens) + num(u.cacheWriteTokens);
|
|
102
|
+
rec.output += num(u.outputTokens);
|
|
103
|
+
rec.cache += num(u.cacheReadTokens);
|
|
104
|
+
rec.last = start + i;
|
|
105
|
+
changed = true;
|
|
106
|
+
});
|
|
107
|
+
if (changed) {
|
|
108
|
+
totals = [rec, ...totals.filter((r) => r !== rec)].slice(0, TOTALS_MAX);
|
|
109
|
+
writeTotals(totals);
|
|
110
|
+
}
|
|
111
|
+
return rec;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function readWindow(slice) {
|
|
115
|
+
const name = slice?.generatorMetadatasInSlice?.at(-1)?.plannerConfig?.modelName
|
|
116
|
+
|| document.querySelector('[data-testid="model-selector-trigger"]')?.textContent || '';
|
|
117
|
+
return WINDOWS.find(([re]) => re.test(name.trim()))?.[1] ?? null;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
let service = null;
|
|
121
|
+
function agentService() {
|
|
122
|
+
if (service) return service;
|
|
123
|
+
const root = document.getElementById('root');
|
|
124
|
+
const key = root && Object.keys(root).find((k) => k.startsWith('__reactContainer$'));
|
|
125
|
+
const stack = [root?.[key]?.stateNode?.current ?? root?.[key]];
|
|
126
|
+
const seen = new Set();
|
|
127
|
+
while (stack.length && seen.size < 30000) {
|
|
128
|
+
const f = stack.pop();
|
|
129
|
+
if (!f || seen.has(f)) continue;
|
|
130
|
+
seen.add(f);
|
|
131
|
+
const v = f.memoizedProps?.value;
|
|
132
|
+
if (v && typeof v.getTokenBase === 'function') return (service = v);
|
|
133
|
+
if (f.sibling) stack.push(f.sibling);
|
|
134
|
+
if (f.child) stack.push(f.child);
|
|
135
|
+
}
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
let base = null, baseFor = null, baseAt = 0, baseLoading = false;
|
|
140
|
+
function refreshBase(slice) {
|
|
141
|
+
const convo = slice?.conversationId ?? null;
|
|
142
|
+
if (baseLoading || (convo === baseFor && Date.now() - baseAt < BASE_MS)) return;
|
|
143
|
+
const svc = agentService();
|
|
144
|
+
if (!svc) return;
|
|
145
|
+
baseLoading = true;
|
|
146
|
+
const uris = slice?.metadata?.workspaceUris;
|
|
147
|
+
svc.getTokenBase(Array.isArray(uris) ? { workspaceUris: uris } : {})
|
|
148
|
+
.then((r) => {
|
|
149
|
+
base = (r.customizationTokenBase?.groups ?? [])
|
|
150
|
+
.map((g) => ({
|
|
151
|
+
type: g.type,
|
|
152
|
+
name: GROUP_NAMES[g.type] || g.name,
|
|
153
|
+
tokens: num(g.numTokens),
|
|
154
|
+
children: (g.children ?? []).map((c) => ({ name: c.name, tokens: num(c.numTokens) })).sort((a, b) => b.tokens - a.tokens),
|
|
155
|
+
}))
|
|
156
|
+
.filter((g) => g.tokens > 0)
|
|
157
|
+
.sort((a, b) => b.tokens - a.tokens);
|
|
158
|
+
baseFor = convo;
|
|
159
|
+
baseAt = Date.now();
|
|
160
|
+
render();
|
|
161
|
+
})
|
|
162
|
+
.catch(() => {})
|
|
163
|
+
.finally(() => { baseLoading = false; });
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// ---- View -------------------------------------------------------------------
|
|
167
|
+
let hostEl = null, shadow = null, state = null, signature = '';
|
|
168
|
+
let open = true;
|
|
169
|
+
const openGroups = new Set();
|
|
170
|
+
|
|
171
|
+
function mount() {
|
|
172
|
+
const list = document.querySelector('[data-testid="overview-panel"] > .overflow-y-auto');
|
|
173
|
+
if (!list) return false;
|
|
174
|
+
if (hostEl?.parentElement === list) return true;
|
|
175
|
+
if (!hostEl) {
|
|
176
|
+
hostEl = document.createElement('div');
|
|
177
|
+
hostEl.id = 'agy-context-card';
|
|
178
|
+
shadow = hostEl.attachShadow({ mode: 'open' });
|
|
179
|
+
shadow.innerHTML = `
|
|
180
|
+
<style>
|
|
181
|
+
:host { display: block; padding: 4px; }
|
|
182
|
+
*, *::before, *::after { box-sizing: border-box; }
|
|
183
|
+
button { font: inherit; color: inherit; background: none; border: 0; padding: 0; cursor: default; text-align: start; }
|
|
184
|
+
svg { width: 16px; height: 16px; fill: currentColor; flex: none; }
|
|
185
|
+
|
|
186
|
+
/* =========================================================================
|
|
187
|
+
SHARED LAYOUT & ANIMATION
|
|
188
|
+
========================================================================= */
|
|
189
|
+
.body, .kids { display: grid; grid-template-rows: 1fr; transition: grid-template-rows 300ms cubic-bezier(0.2, 0, 0, 1); }
|
|
190
|
+
.body[hidden], .kids[hidden] { display: grid; grid-template-rows: 0fr; }
|
|
191
|
+
.clip { min-height: 0; overflow: hidden; }
|
|
192
|
+
[hidden] > .clip { visibility: hidden; transition: visibility 0s 300ms; }
|
|
193
|
+
ul { list-style: none; margin: 0; padding: 2px 0 0; }
|
|
194
|
+
.status[hidden], .total[hidden] { display: none; }
|
|
195
|
+
.total-value { font-variant-numeric: tabular-nums; }
|
|
196
|
+
.name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
197
|
+
.n, .p { text-align: end; }
|
|
198
|
+
.head:focus-visible, .row:focus-visible {
|
|
199
|
+
outline: 2px solid var(--md-sys-color-secondary, var(--ring, currentColor)); outline-offset: -2px;
|
|
200
|
+
}
|
|
201
|
+
@media (prefers-reduced-motion: reduce) { * { transition: none !important; } }
|
|
202
|
+
|
|
203
|
+
/* =========================================================================
|
|
204
|
+
MATTE THEME STYLING (matches stock Overview items & Background Commands)
|
|
205
|
+
========================================================================= */
|
|
206
|
+
:host-context(:root:not([data-agy-theme="md3e"])) {
|
|
207
|
+
color: var(--foreground, #dcdcde);
|
|
208
|
+
font-family: var(--font-sans, "Segoe UI Variable Text", "Segoe UI", system-ui, sans-serif);
|
|
209
|
+
/* Matte's own tokens (matte.css, light and dark), with its dark
|
|
210
|
+
values as fallbacks for any other non-MD3E theme */
|
|
211
|
+
--k-conversation: var(--m-text, #dcdcde);
|
|
212
|
+
--k-rules: var(--m-blue, #7aa2f7);
|
|
213
|
+
--k-skills: var(--m-amber, #e5a84b);
|
|
214
|
+
--k-mcp: var(--m-teal, #73daca);
|
|
215
|
+
--k-other: var(--m-text-3, #86868c);
|
|
216
|
+
--k-free: var(--m-line-3, rgba(255, 255, 255, 0.18)); /* legend dot; the bar's own track shows the free part */
|
|
217
|
+
--k-hover: var(--m-hover, rgba(255, 255, 255, 0.045));
|
|
218
|
+
--k-track: var(--m-line, rgba(255, 255, 255, 0.075));
|
|
219
|
+
}
|
|
220
|
+
:host-context(:root:not([data-agy-theme="md3e"])) .head:focus-visible svg { opacity: 0.7; }
|
|
221
|
+
:host-context(:root:not([data-agy-theme="md3e"])) .head {
|
|
222
|
+
display: flex; align-items: center; gap: 6px; width: 100%; height: 24px; min-height: 24px; padding: 0 6px 0 8px;
|
|
223
|
+
border-radius: 8px; font-size: 12px; font-weight: 500; cursor: pointer;
|
|
224
|
+
transition: background 150ms ease;
|
|
225
|
+
}
|
|
226
|
+
:host-context(:root:not([data-agy-theme="md3e"])) .head:hover {
|
|
227
|
+
background: var(--k-hover);
|
|
228
|
+
}
|
|
229
|
+
:host-context(:root:not([data-agy-theme="md3e"])) .title {
|
|
230
|
+
color: var(--foreground, #dcdcde);
|
|
231
|
+
opacity: 0.5;
|
|
232
|
+
transition: opacity 150ms ease;
|
|
233
|
+
}
|
|
234
|
+
:host-context(:root:not([data-agy-theme="md3e"])) .head:hover .title { opacity: 1; }
|
|
235
|
+
:host-context(:root:not([data-agy-theme="md3e"])) .chip {
|
|
236
|
+
padding: 1px 6px; border-radius: 9999px;
|
|
237
|
+
background: var(--border, rgba(255, 255, 255, 0.08));
|
|
238
|
+
color: var(--foreground, #dcdcde);
|
|
239
|
+
font-size: 10px; font-weight: 500; font-family: var(--font-mono, monospace); line-height: 14px;
|
|
240
|
+
opacity: 0.5;
|
|
241
|
+
transition: opacity 150ms ease;
|
|
242
|
+
}
|
|
243
|
+
:host-context(:root:not([data-agy-theme="md3e"])) .head:hover .chip { opacity: 1; }
|
|
244
|
+
:host-context(:root:not([data-agy-theme="md3e"])) .head svg {
|
|
245
|
+
margin-left: auto; width: 14px; height: 14px;
|
|
246
|
+
color: var(--muted-foreground, #86868c);
|
|
247
|
+
opacity: 0;
|
|
248
|
+
rotate: 90deg;
|
|
249
|
+
transition: opacity 150ms ease, rotate 150ms ease;
|
|
250
|
+
}
|
|
251
|
+
:host-context(:root:not([data-agy-theme="md3e"])) .head:hover svg { opacity: 0.7; }
|
|
252
|
+
:host-context(:root:not([data-agy-theme="md3e"])) .head[aria-expanded="false"] svg { rotate: 0deg; }
|
|
253
|
+
:host-context(:root:not([data-agy-theme="md3e"])) .summary {
|
|
254
|
+
padding: 2px 8px 6px; font-size: 11px; font-variant-numeric: tabular-nums;
|
|
255
|
+
color: var(--muted-foreground, #86868c);
|
|
256
|
+
}
|
|
257
|
+
:host-context(:root:not([data-agy-theme="md3e"])) .total {
|
|
258
|
+
padding: 0 8px 6px; font-size: 11px; line-height: 14px; color: var(--muted-foreground, #86868c);
|
|
259
|
+
}
|
|
260
|
+
:host-context(:root:not([data-agy-theme="md3e"])) .total-value { color: var(--secondary-foreground, #a3a3a8); }
|
|
261
|
+
:host-context(:root:not([data-agy-theme="md3e"])) .status {
|
|
262
|
+
padding: 0 8px 6px; font-size: 11px; line-height: 14px; font-variant-numeric: tabular-nums;
|
|
263
|
+
color: var(--muted-foreground, #86868c);
|
|
264
|
+
}
|
|
265
|
+
/* Warnings stay monochrome: the chip comes forward, then inverts */
|
|
266
|
+
:host-context(:root:not([data-agy-theme="md3e"])) :is([data-level="warn"], [data-level="critical"]) .chip { opacity: 1; }
|
|
267
|
+
:host-context(:root:not([data-agy-theme="md3e"])) [data-level="critical"] .chip {
|
|
268
|
+
background: var(--foreground, #dcdcde); color: var(--background, #111113);
|
|
269
|
+
}
|
|
270
|
+
:host-context(:root:not([data-agy-theme="md3e"])) [data-level="critical"] .status { color: var(--foreground, #dcdcde); }
|
|
271
|
+
:host-context(:root:not([data-agy-theme="md3e"])) .bar {
|
|
272
|
+
position: relative; display: flex; gap: 1px; height: 4px; margin: 0 8px 6px;
|
|
273
|
+
border-radius: 2px; background: var(--k-track); overflow: hidden;
|
|
274
|
+
}
|
|
275
|
+
:host-context(:root:not([data-agy-theme="md3e"])) .seg {
|
|
276
|
+
flex: var(--v) 1 0; min-width: 2px; border-radius: 0; background: var(--c);
|
|
277
|
+
transition: flex-grow 300ms ease;
|
|
278
|
+
}
|
|
279
|
+
:host-context(:root:not([data-agy-theme="md3e"])) .seg.free { background: transparent; }
|
|
280
|
+
:host-context(:root:not([data-agy-theme="md3e"])) .row {
|
|
281
|
+
display: grid; grid-template-columns: 6px 1fr auto 40px 14px; align-items: center; gap: 8px;
|
|
282
|
+
width: 100%; min-height: 24px; padding: 2px 8px; border-radius: 6px;
|
|
283
|
+
font-size: 12px; font-variant-numeric: tabular-nums;
|
|
284
|
+
color: var(--secondary-foreground, #dcdcde);
|
|
285
|
+
cursor: default;
|
|
286
|
+
transition: background 120ms ease;
|
|
287
|
+
}
|
|
288
|
+
:host-context(:root:not([data-agy-theme="md3e"])) .row[aria-expanded] { cursor: pointer; }
|
|
289
|
+
:host-context(:root:not([data-agy-theme="md3e"])) .row[aria-expanded]:hover { background: var(--k-hover); }
|
|
290
|
+
:host-context(:root:not([data-agy-theme="md3e"])) .dot { width: 6px; height: 6px; border-radius: 50%; background: var(--c); }
|
|
291
|
+
:host-context(:root:not([data-agy-theme="md3e"])) .n, :host-context(:root:not([data-agy-theme="md3e"])) .p {
|
|
292
|
+
font-size: 11px; font-family: var(--font-mono, monospace); color: var(--muted-foreground, #86868c);
|
|
293
|
+
}
|
|
294
|
+
:host-context(:root:not([data-agy-theme="md3e"])) .row svg {
|
|
295
|
+
width: 12px; height: 12px; color: var(--muted-foreground, #86868c); opacity: 0.4;
|
|
296
|
+
transition: rotate 200ms ease, opacity 150ms ease;
|
|
297
|
+
rotate: 0deg;
|
|
298
|
+
}
|
|
299
|
+
:host-context(:root:not([data-agy-theme="md3e"])) .row[aria-expanded="true"] svg { rotate: 90deg; opacity: 0.8; }
|
|
300
|
+
:host-context(:root:not([data-agy-theme="md3e"])) .kids .row { padding-left: 18px; min-height: 22px; opacity: 0.85; }
|
|
301
|
+
:host-context(:root:not([data-agy-theme="md3e"])) .more { padding: 2px 8px 4px 28px; font-size: 11px; color: var(--muted-foreground, #86868c); }
|
|
302
|
+
|
|
303
|
+
/* =========================================================================
|
|
304
|
+
MD3E THEME STYLING (Expressive tonal container, pill shapes, spring motion)
|
|
305
|
+
========================================================================= */
|
|
306
|
+
:host-context([data-agy-theme="md3e"]) {
|
|
307
|
+
color: var(--md-sys-color-on-surface, var(--foreground));
|
|
308
|
+
font-family: var(--font-sans, "Roboto Variable", Roboto, sans-serif);
|
|
309
|
+
/* tonal roles that stay apart in every seed (primary and
|
|
310
|
+
primary-container are near twins in the expressive schemes) */
|
|
311
|
+
--k-conversation: var(--md-sys-color-primary);
|
|
312
|
+
--k-rules: var(--md-sys-color-secondary);
|
|
313
|
+
--k-skills: var(--md-sys-color-tertiary);
|
|
314
|
+
--k-mcp: var(--md-sys-color-outline);
|
|
315
|
+
--k-other: var(--md-sys-color-outline-variant);
|
|
316
|
+
--k-free: var(--md-sys-color-secondary-container); /* the M3 progress track */
|
|
317
|
+
}
|
|
318
|
+
/* the Rounded symbol, as md3e-icons.css does for the app's own icons */
|
|
319
|
+
:host-context([data-agy-theme="md3e"]) svg path {
|
|
320
|
+
d: path("M522 -480 358 -644Q347 -655 347 -670Q347 -684 358 -695Q369 -706 384 -706Q398 -706 409 -695L599 -505Q604 -500 606 -494Q609 -487 609 -480Q609 -473 606 -466Q604 -460 599 -455L409 -265Q398 -254 384 -254Q370 -255 359 -266Q348 -277 348 -292Q348 -306 359 -317Z");
|
|
321
|
+
}
|
|
322
|
+
:host-context([data-agy-theme="md3e"]) .head {
|
|
323
|
+
display: flex; align-items: center; gap: 6px; width: 100%; min-height: 28px; padding: 4px 8px;
|
|
324
|
+
border-radius: var(--md-sys-shape-corner-full, 14px);
|
|
325
|
+
font: var(--md-sys-typescale-label-medium, 500 12px/16px Roboto, sans-serif);
|
|
326
|
+
cursor: pointer;
|
|
327
|
+
transition: background-color 15ms linear;
|
|
328
|
+
}
|
|
329
|
+
:host-context([data-agy-theme="md3e"]) .head:hover, :host-context([data-agy-theme="md3e"]) .row[aria-expanded]:hover {
|
|
330
|
+
background: color-mix(in srgb, currentColor 8%, transparent);
|
|
331
|
+
}
|
|
332
|
+
:host-context([data-agy-theme="md3e"]) .title {
|
|
333
|
+
color: var(--md-sys-color-on-surface-variant, var(--muted-foreground));
|
|
334
|
+
}
|
|
335
|
+
:host-context([data-agy-theme="md3e"]) .chip {
|
|
336
|
+
padding: 1px 8px; border-radius: var(--md-sys-shape-corner-full, 999px);
|
|
337
|
+
background: var(--md-sys-color-secondary-container, var(--border));
|
|
338
|
+
color: var(--md-sys-color-on-secondary-container, inherit);
|
|
339
|
+
font-size: 11px; line-height: 16px; font-variant-numeric: tabular-nums;
|
|
340
|
+
transition: background-color var(--md-color-fast, 150ms), color var(--md-color-fast, 150ms), border-radius var(--md-fast, 300ms);
|
|
341
|
+
}
|
|
342
|
+
/* Warnings: tertiary at 80%, error at 95%; the chip also changes
|
|
343
|
+
shape (full -> 6px) to say the state changed */
|
|
344
|
+
:host-context([data-agy-theme="md3e"]) [data-level="warn"] .chip {
|
|
345
|
+
background: var(--md-sys-color-tertiary-container); color: var(--md-sys-color-on-tertiary-container);
|
|
346
|
+
}
|
|
347
|
+
:host-context([data-agy-theme="md3e"]) [data-level="critical"] .chip {
|
|
348
|
+
border-radius: 6px;
|
|
349
|
+
background: var(--md-sys-color-error-container); color: var(--md-sys-color-on-error-container);
|
|
350
|
+
}
|
|
351
|
+
:host-context([data-agy-theme="md3e"]) .status {
|
|
352
|
+
padding: 0 8px 8px; font: var(--md-sys-typescale-body-small, 400 12px/16px Roboto, sans-serif);
|
|
353
|
+
color: var(--md-sys-color-tertiary);
|
|
354
|
+
}
|
|
355
|
+
:host-context([data-agy-theme="md3e"]) [data-level="critical"] .status { color: var(--md-sys-color-error); }
|
|
356
|
+
:host-context([data-agy-theme="md3e"]) [data-level="critical"] .bar::after { background: var(--md-sys-color-error); }
|
|
357
|
+
:host-context([data-agy-theme="md3e"]) .total { padding: 6px 8px 8px; }
|
|
358
|
+
:host-context([data-agy-theme="md3e"]) .total-label {
|
|
359
|
+
font: var(--md-sys-typescale-label-small, 500 11px/16px Roboto, sans-serif); color: var(--md-sys-color-on-surface-variant, var(--muted-foreground));
|
|
360
|
+
}
|
|
361
|
+
:host-context([data-agy-theme="md3e"]) .total-value {
|
|
362
|
+
font: var(--md-sys-typescale-body-small, 400 12px/16px Roboto, sans-serif); color: var(--md-sys-color-on-surface, var(--foreground));
|
|
363
|
+
}
|
|
364
|
+
:host-context([data-agy-theme="md3e"]) .head svg {
|
|
365
|
+
margin-left: auto; width: 16px; height: 16px;
|
|
366
|
+
color: var(--md-sys-color-on-surface-variant, var(--muted-foreground));
|
|
367
|
+
rotate: 90deg;
|
|
368
|
+
transition: rotate 300ms cubic-bezier(0.2, 0, 0, 1.2);
|
|
369
|
+
}
|
|
370
|
+
:host-context([data-agy-theme="md3e"]) .head[aria-expanded="false"] svg { rotate: 0deg; }
|
|
371
|
+
:host-context([data-agy-theme="md3e"]) .summary {
|
|
372
|
+
padding: 0 8px 8px; color: var(--md-sys-color-on-surface-variant, var(--muted-foreground));
|
|
373
|
+
font: var(--md-sys-typescale-body-small, 400 12px/16px Roboto, sans-serif); font-variant-numeric: tabular-nums;
|
|
374
|
+
}
|
|
375
|
+
:host-context([data-agy-theme="md3e"]) .bar {
|
|
376
|
+
position: relative; display: flex; gap: 3px; height: 6px; margin: 0 8px 4px;
|
|
377
|
+
}
|
|
378
|
+
:host-context([data-agy-theme="md3e"]) .seg {
|
|
379
|
+
flex: var(--v) 1 0; min-width: 3px; border-radius: 3px; background: var(--c);
|
|
380
|
+
transition: flex-grow var(--md-sys-motion-spatial-default-duration, 500ms) var(--md-sys-motion-spatial-default-easing, cubic-bezier(0.2, 0, 0, 1));
|
|
381
|
+
}
|
|
382
|
+
:host-context([data-agy-theme="md3e"]) .seg.free {
|
|
383
|
+
background: var(--k-free);
|
|
384
|
+
}
|
|
385
|
+
:host-context([data-agy-theme="md3e"]) .bar::after {
|
|
386
|
+
content: ""; position: absolute; right: 1px; top: 1px; width: 4px; height: 4px; border-radius: 50%;
|
|
387
|
+
background: var(--md-sys-color-primary, var(--primary));
|
|
388
|
+
}
|
|
389
|
+
:host-context([data-agy-theme="md3e"]) .row {
|
|
390
|
+
display: grid; grid-template-columns: 8px 1fr auto 44px 16px; align-items: center; gap: 8px;
|
|
391
|
+
width: 100%; min-height: 28px; padding: 2px 8px; border-radius: var(--md-sys-shape-corner-full, 14px);
|
|
392
|
+
font: var(--md-sys-typescale-body-small, 400 12px/16px Roboto, sans-serif); font-variant-numeric: tabular-nums;
|
|
393
|
+
transition: background-color 15ms linear;
|
|
394
|
+
}
|
|
395
|
+
:host-context([data-agy-theme="md3e"]) .dot { width: 8px; height: 8px; border-radius: 50%; background: var(--c); }
|
|
396
|
+
:host-context([data-agy-theme="md3e"]) .n, :host-context([data-agy-theme="md3e"]) .p {
|
|
397
|
+
text-align: end; color: var(--md-sys-color-on-surface-variant, var(--muted-foreground));
|
|
398
|
+
}
|
|
399
|
+
:host-context([data-agy-theme="md3e"]) .row svg {
|
|
400
|
+
width: 14px; height: 14px; color: var(--md-sys-color-on-surface-variant, var(--muted-foreground));
|
|
401
|
+
transition: rotate 300ms cubic-bezier(0.2, 0, 0, 1.2);
|
|
402
|
+
rotate: 0deg;
|
|
403
|
+
}
|
|
404
|
+
:host-context([data-agy-theme="md3e"]) .row[aria-expanded="true"] svg { rotate: 90deg; }
|
|
405
|
+
:host-context([data-agy-theme="md3e"]) .kids .row { grid-template-columns: 8px 1fr auto 44px 16px; min-height: 24px; opacity: 0.85; padding-left: 16px; }
|
|
406
|
+
:host-context([data-agy-theme="md3e"]) .more { padding: 2px 8px 4px 32px; font-size: 11px; color: var(--md-sys-color-on-surface-variant, var(--muted-foreground)); }
|
|
407
|
+
</style>
|
|
408
|
+
<section aria-label="Context window" data-level="">
|
|
409
|
+
<button class="head" aria-expanded="true"><span class="title">Context</span><span class="chip" hidden></span>${CHEVRON}</button>
|
|
410
|
+
<div class="summary"></div>
|
|
411
|
+
<div class="status" role="status" hidden></div>
|
|
412
|
+
<div class="bar" role="meter" aria-label="Context used" aria-valuemin="0" aria-valuemax="100"></div>
|
|
413
|
+
<div class="total" hidden><div class="total-label"></div><div class="total-value"></div></div>
|
|
414
|
+
<div class="body"><div class="clip"><ul class="legend"></ul></div></div>
|
|
415
|
+
</section>`;
|
|
416
|
+
state = {
|
|
417
|
+
section: shadow.querySelector('section'), head: shadow.querySelector('.head'), chip: shadow.querySelector('.chip'),
|
|
418
|
+
summary: shadow.querySelector('.summary'), status: shadow.querySelector('.status'), bar: shadow.querySelector('.bar'),
|
|
419
|
+
total: shadow.querySelector('.total'), totalLabel: shadow.querySelector('.total-label'), totalValue: shadow.querySelector('.total-value'),
|
|
420
|
+
body: shadow.querySelector('.body'), legend: shadow.querySelector('.legend'),
|
|
421
|
+
};
|
|
422
|
+
state.head.addEventListener('click', () => {
|
|
423
|
+
open = !open;
|
|
424
|
+
state.head.setAttribute('aria-expanded', String(open));
|
|
425
|
+
state.body.hidden = !open;
|
|
426
|
+
});
|
|
427
|
+
const toggleGroup = (e) => {
|
|
428
|
+
const row = e.target.closest('.row[aria-expanded]');
|
|
429
|
+
if (!row) return;
|
|
430
|
+
const name = row.dataset.group;
|
|
431
|
+
openGroups.has(name) ? openGroups.delete(name) : openGroups.add(name);
|
|
432
|
+
row.setAttribute('aria-expanded', String(openGroups.has(name)));
|
|
433
|
+
row.nextElementSibling.hidden = !openGroups.has(name);
|
|
434
|
+
};
|
|
435
|
+
state.legend.addEventListener('click', toggleGroup);
|
|
436
|
+
state.legend.addEventListener('keydown', (e) => {
|
|
437
|
+
if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); toggleGroup(e); }
|
|
438
|
+
});
|
|
439
|
+
}
|
|
440
|
+
list.prepend(hostEl);
|
|
441
|
+
signature = '';
|
|
442
|
+
return true;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
const esc = (s) => String(s).replace(/[&<>"]/g, (c) => ({ '&': '&', '<': '<', '>': '>', '"': '"' })[c]);
|
|
446
|
+
const row = (color, name, tokens, share, group) => `
|
|
447
|
+
<div class="row"${group ? ` aria-expanded="${openGroups.has(group)}" data-group="${esc(group)}" role="button" tabindex="0"` : ''} style="--c:${color}">
|
|
448
|
+
<span class="dot"></span><span class="name">${esc(name)}</span><span class="n">${fmt(tokens)}</span><span class="p">${share}</span>${group ? CHEVRON : '<span></span>'}
|
|
449
|
+
</div>`;
|
|
450
|
+
|
|
451
|
+
let last = null;
|
|
452
|
+
function render() {
|
|
453
|
+
if (!state || !last) return;
|
|
454
|
+
const { used, windowSize, total: conv } = last;
|
|
455
|
+
const groups = base ?? [];
|
|
456
|
+
const custom = groups.reduce((s, g) => s + g.tokens, 0);
|
|
457
|
+
const parts = [];
|
|
458
|
+
if (used != null) parts.push({ id: 'conversation', name: 'Conversation & system', tokens: Math.max(0, used - custom), color: color('conversation') });
|
|
459
|
+
for (const g of groups) {
|
|
460
|
+
parts.push({ id: `g${g.type}`, name: g.name, tokens: g.tokens, color: color(GROUP_KINDS[g.type] || 'other'), children: g.children });
|
|
461
|
+
}
|
|
462
|
+
const total = windowSize || Math.max(used ?? 0, custom) || 1;
|
|
463
|
+
const free = windowSize && used != null ? Math.max(0, windowSize - used) : null;
|
|
464
|
+
const share = windowSize && used != null ? used / windowSize : null;
|
|
465
|
+
const [, level = '', message = ''] = (share != null && LEVELS.find(([at]) => share >= at)) || [];
|
|
466
|
+
|
|
467
|
+
const convText = conv && (conv.input || conv.output)
|
|
468
|
+
? [`${fmt(conv.input)} in`, `${fmt(conv.output)} out`, conv.cache ? `${fmt(conv.cache)} cache` : ''].filter(Boolean).join(' · ')
|
|
469
|
+
: '';
|
|
470
|
+
const convLabel = conv?.from > 0 ? `This conversation, counted from step ${conv.from.toLocaleString('en-US')}` : 'This conversation';
|
|
471
|
+
|
|
472
|
+
const sig = JSON.stringify([used, windowSize, convText, convLabel, parts.map((p) => [p.id, p.tokens]), [...openGroups]]);
|
|
473
|
+
if (sig === signature) return;
|
|
474
|
+
signature = sig;
|
|
475
|
+
|
|
476
|
+
hostEl.hidden = used == null && !groups.length;
|
|
477
|
+
state.section.dataset.level = level;
|
|
478
|
+
state.chip.hidden = share == null;
|
|
479
|
+
state.chip.textContent = share != null ? pct(used, windowSize) : '';
|
|
480
|
+
state.summary.textContent = used == null
|
|
481
|
+
? 'No model response yet'
|
|
482
|
+
: windowSize ? `${fmt(used)} of ${fmt(windowSize)} tokens used` : `${fmt(used)} tokens used`;
|
|
483
|
+
state.status.hidden = !message;
|
|
484
|
+
state.status.textContent = message;
|
|
485
|
+
state.total.hidden = !convText;
|
|
486
|
+
state.totalLabel.textContent = convLabel;
|
|
487
|
+
state.totalValue.textContent = convText;
|
|
488
|
+
state.bar.setAttribute('aria-valuenow', share != null ? String(Math.min(100, Math.round(share * 100))) : '0');
|
|
489
|
+
|
|
490
|
+
// Segments keep their elements so the widths spring to new values
|
|
491
|
+
const segs = [...parts, ...(free != null ? [{ id: 'free', tokens: free }] : [])];
|
|
492
|
+
const ids = segs.map((s) => s.id).join();
|
|
493
|
+
if (state.bar.dataset.ids !== ids) {
|
|
494
|
+
state.bar.dataset.ids = ids;
|
|
495
|
+
state.bar.innerHTML = segs.map((s) => `<span class="seg${s.id === 'free' ? ' free' : ''}" data-id="${s.id}"></span>`).join('');
|
|
496
|
+
}
|
|
497
|
+
for (const s of segs) {
|
|
498
|
+
const el = state.bar.querySelector(`[data-id="${s.id}"]`);
|
|
499
|
+
if (el) {
|
|
500
|
+
el.style.setProperty('--v', String(Math.max(s.tokens / total, 0.004)));
|
|
501
|
+
if (s.color) el.style.setProperty('--c', s.color);
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
state.legend.innerHTML = [
|
|
506
|
+
...parts.map((p) => {
|
|
507
|
+
const share = pct(p.tokens, total);
|
|
508
|
+
if (!p.children?.length) return `<li>${row(p.color, p.name, p.tokens, share)}</li>`;
|
|
509
|
+
const kids = p.children.slice(0, 8).map((c) => row(p.color, c.name, c.tokens, pct(c.tokens, total))).join('');
|
|
510
|
+
const more = p.children.length > 8 ? `<div class="more">and ${p.children.length - 8} more</div>` : '';
|
|
511
|
+
return `<li>${row(p.color, p.name, p.tokens, share, p.name)}<div class="kids"${openGroups.has(p.name) ? '' : ' hidden'}><div class="clip">${kids}${more}</div></div></li>`;
|
|
512
|
+
}),
|
|
513
|
+
free != null ? `<li>${row(color('free'), 'Free space', free, pct(free, total))}</li>` : '',
|
|
514
|
+
].join('');
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
function tick() {
|
|
518
|
+
try {
|
|
519
|
+
// Count even while the card is hidden: the app keeps only the last ~240
|
|
520
|
+
// steps, so calls made meanwhile would otherwise fall out uncounted
|
|
521
|
+
const slice = readSlice();
|
|
522
|
+
const total = countTotal(slice);
|
|
523
|
+
if (!mount()) return;
|
|
524
|
+
if (document.hidden) return;
|
|
525
|
+
last = { used: readUsage(slice), windowSize: readWindow(slice), total };
|
|
526
|
+
refreshBase(slice);
|
|
527
|
+
render();
|
|
528
|
+
} catch {
|
|
529
|
+
// app internals changed: leave the card as it was
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
const timer = setInterval(tick, TICK_MS);
|
|
534
|
+
tick();
|
|
535
|
+
|
|
536
|
+
return {
|
|
537
|
+
dispose() {
|
|
538
|
+
clearInterval(timer);
|
|
539
|
+
hostEl?.remove();
|
|
540
|
+
},
|
|
541
|
+
};
|
|
542
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
// Loading indicator: in MD3E, the agent's "Working" / "Compacting" row
|
|
2
|
+
// gets the package's own <md-loading-indicator> in front of its text: the
|
|
3
|
+
// Compose LoadingIndicator port in vendor/md3e/components.js (seven matched cubic morphs,
|
|
4
|
+
// spring 0.6 / 200, a new shape every 650ms with a quarter turn, one full
|
|
5
|
+
// turn every 4666ms), drawn in primary. md3e.css stills the row's shimmer
|
|
6
|
+
// text, so the indicator is the one thing moving.
|
|
7
|
+
//
|
|
8
|
+
// Other themes keep the app's row as it is: this module's own rule hides the
|
|
9
|
+
// element unless the page is MD3E (theme CSS comes and goes with the theme,
|
|
10
|
+
// and the element's :host display would beat a UA [hidden]).
|
|
11
|
+
// The element lives in the row React renders; when the run ends the row
|
|
12
|
+
// unmounts and the component stops its own animation frame loop.
|
|
13
|
+
function agyLoadingIndicator() {
|
|
14
|
+
const ROW = '[data-testid="agent-loading"]';
|
|
15
|
+
if (!customElements.get('md-loading-indicator')) return null; // vendor script missing
|
|
16
|
+
|
|
17
|
+
const style = document.createElement('style');
|
|
18
|
+
style.id = 'agy-loader-css';
|
|
19
|
+
style.textContent = `
|
|
20
|
+
.agy-loader { display: none; }
|
|
21
|
+
:root[data-agy-theme="md3e"] .agy-loader { display: flex; }`; /* flex: no line box around the 24px canvas */
|
|
22
|
+
(document.head || document.documentElement).appendChild(style);
|
|
23
|
+
|
|
24
|
+
function decorate(row) {
|
|
25
|
+
if (row.querySelector(':scope > .agy-loader')) return;
|
|
26
|
+
const el = document.createElement('md-loading-indicator');
|
|
27
|
+
el.className = 'agy-loader';
|
|
28
|
+
el.setAttribute('size', '24');
|
|
29
|
+
el.setAttribute('aria-hidden', 'true'); // the row's text already says it
|
|
30
|
+
row.prepend(el);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// The row mounts about a second into a run: look inside what the
|
|
34
|
+
// transcript adds, and rescan every 2s for a replaced viewport
|
|
35
|
+
let viewport = null, observer = null;
|
|
36
|
+
function onMutations(records) {
|
|
37
|
+
try {
|
|
38
|
+
for (const r of records) {
|
|
39
|
+
for (const n of r.addedNodes) {
|
|
40
|
+
if (n.nodeType !== 1 || n.classList.contains('agy-loader')) continue;
|
|
41
|
+
if (n.matches(ROW)) decorate(n);
|
|
42
|
+
else n.querySelectorAll(ROW).forEach(decorate);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
} catch {
|
|
46
|
+
// app internals changed: leave the row as it is
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
function scan() {
|
|
50
|
+
if (document.hidden) return;
|
|
51
|
+
const vp = document.querySelector('[data-testid="autoscroll-viewport"]');
|
|
52
|
+
if (vp !== viewport) {
|
|
53
|
+
observer?.disconnect();
|
|
54
|
+
viewport = vp;
|
|
55
|
+
if (vp) {
|
|
56
|
+
observer = new MutationObserver(onMutations);
|
|
57
|
+
observer.observe(vp, { childList: true, subtree: true });
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
viewport?.querySelectorAll(ROW).forEach(decorate);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const timer = setInterval(scan, 2000);
|
|
64
|
+
scan();
|
|
65
|
+
|
|
66
|
+
return {
|
|
67
|
+
dispose() {
|
|
68
|
+
clearInterval(timer);
|
|
69
|
+
observer?.disconnect();
|
|
70
|
+
document.querySelectorAll('.agy-loader').forEach((el) => el.remove());
|
|
71
|
+
style.remove();
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
}
|