wendkeep 0.71.1 → 0.72.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/CHANGELOG.md +28 -0
- package/README.en.md +6 -2
- package/README.md +6 -2
- package/docs/en/commands/observer.md +54 -35
- package/docs/pt-BR/commands/observer.md +56 -37
- package/hooks/observer-publish.mjs +1 -0
- package/package.json +2 -2
- package/packages/integrations/src/host-hooks.mjs +2 -0
- package/schema/observer/001-authority.sql +107 -0
- package/schema/observer/002-usage.sql +72 -0
- package/schema/observer/003-transcripts.sql +21 -0
- package/src/observer-memory-publish.mjs +2 -1
- package/src/observer-publish.mjs +15 -13
- package/src/observer-server.mjs +223 -40
- package/src/observer-sql-migrate.mjs +335 -0
- package/src/observer-sql-publish.mjs +398 -0
- package/src/observer-sql-store.mjs +542 -0
- package/src/observer-transcript-store.mjs +49 -0
- package/src/observer.mjs +23 -5
- package/web/observer/app.mjs +248 -1
- package/web/observer/index.html +1 -0
- package/web/observer/styles.css +34 -1
package/web/observer/app.mjs
CHANGED
|
@@ -96,11 +96,82 @@ export async function loadProjectMemory(fetchImpl = globalThis.fetch, projectId
|
|
|
96
96
|
return { tree, sync };
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
+
export function usageQuery(filters = {}) {
|
|
100
|
+
const params = new URLSearchParams();
|
|
101
|
+
const keys = ['from', 'to', 'agent_id', 'subagent_id', 'provider', 'model_provider', 'model', 'change', 'session_id', 'role'];
|
|
102
|
+
for (const key of keys) {
|
|
103
|
+
const value = filters[key] ?? filters[key.replace('_id', 'Id')] ?? '';
|
|
104
|
+
if (String(value).trim()) params.set(key, String(value));
|
|
105
|
+
}
|
|
106
|
+
const query = params.toString();
|
|
107
|
+
return query ? `?${query}` : '';
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export async function loadProjectUsage(fetchImpl = globalThis.fetch, projectId = '', filters = {}) {
|
|
111
|
+
const id = encodeURIComponent(projectId);
|
|
112
|
+
const query = usageQuery(filters);
|
|
113
|
+
const [summary, breakdown, calls] = await Promise.all([
|
|
114
|
+
requestJson(fetchImpl, `/v1/projects/${id}/usage/summary${query}`),
|
|
115
|
+
requestJson(fetchImpl, `/v1/projects/${id}/usage/breakdown${query}`),
|
|
116
|
+
requestJson(fetchImpl, `/v1/projects/${id}/usage/calls${query}`),
|
|
117
|
+
]);
|
|
118
|
+
return { summary, breakdown, calls, filters: { ...filters } };
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export function buildUsageViewModel(usage = {}) {
|
|
122
|
+
const summary = usage.summary || {};
|
|
123
|
+
const agents = (Array.isArray(usage.breakdown?.agents) ? usage.breakdown.agents : []).map((agent) => ({ ...agent, children: [] }));
|
|
124
|
+
const byId = new Map(agents.map((agent) => [agent.agent_id, agent]));
|
|
125
|
+
for (const agent of agents) {
|
|
126
|
+
const parent = agent.parent_agent_id ? byId.get(agent.parent_agent_id) : null;
|
|
127
|
+
if (parent && parent !== agent) parent.children.push(agent);
|
|
128
|
+
}
|
|
129
|
+
const roots = agents.filter((agent) => !agent.parent_agent_id || !byId.has(agent.parent_agent_id));
|
|
130
|
+
const tokens = {
|
|
131
|
+
input: Number(summary.tokens?.input || 0),
|
|
132
|
+
cacheWrite: Number(summary.tokens?.cache_write || 0),
|
|
133
|
+
cacheRead: Number(summary.tokens?.cache_read || 0),
|
|
134
|
+
output: Number(summary.tokens?.output || 0),
|
|
135
|
+
reasoning: Number(summary.tokens?.reasoning || 0),
|
|
136
|
+
total: Number(summary.tokens?.total || 0),
|
|
137
|
+
};
|
|
138
|
+
const coverage = summary.coverage || {};
|
|
139
|
+
const complete = Number(coverage.complete || 0);
|
|
140
|
+
const summaryOnly = Number(coverage.summary_only || 0);
|
|
141
|
+
return {
|
|
142
|
+
totalCost: Number(summary.cost_usd || 0),
|
|
143
|
+
mainCost: Number(summary.main_cost_usd || 0),
|
|
144
|
+
subagentCost: Number(summary.subagent_cost_usd || 0),
|
|
145
|
+
wastedCost: Number(summary.wasted_usd || 0),
|
|
146
|
+
calls: Array.isArray(usage.calls?.calls) ? usage.calls.calls : [],
|
|
147
|
+
callCount: Number(summary.calls || usage.calls?.total || 0),
|
|
148
|
+
sessions: Number(summary.sessions || 0),
|
|
149
|
+
agentsCount: Number(summary.agents || 0),
|
|
150
|
+
subagentsCount: Number(summary.subagents || 0),
|
|
151
|
+
modelsCount: Number(summary.models || 0),
|
|
152
|
+
tokens,
|
|
153
|
+
trend: Array.isArray(summary.by_day) ? summary.by_day : [],
|
|
154
|
+
agents: roots,
|
|
155
|
+
allAgents: agents,
|
|
156
|
+
coverage: {
|
|
157
|
+
complete,
|
|
158
|
+
summaryOnly,
|
|
159
|
+
total: Number(coverage.transcripts || complete + summaryOnly),
|
|
160
|
+
label: `${complete} completo${complete === 1 ? '' : 's'} · ${summaryOnly} agregado${summaryOnly === 1 ? '' : 's'}`,
|
|
161
|
+
},
|
|
162
|
+
hasUnknownPricing: Number(summary.unknown_priced_rollups || 0) > 0,
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
|
|
99
166
|
export async function loadMemoryDocument(fetchImpl = globalThis.fetch, projectId = '', logicalPath = '') {
|
|
100
167
|
const query = new URLSearchParams({ path: logicalPath });
|
|
101
168
|
return requestJson(fetchImpl, '/v1/projects/' + encodeURIComponent(projectId) + '/memory/document?' + query.toString());
|
|
102
169
|
}
|
|
103
170
|
|
|
171
|
+
export async function loadProjectTranscript(fetchImpl = globalThis.fetch, projectId = '', transcriptId = '') {
|
|
172
|
+
return requestJson(fetchImpl, `/v1/projects/${encodeURIComponent(projectId)}/transcripts/${encodeURIComponent(transcriptId)}`);
|
|
173
|
+
}
|
|
174
|
+
|
|
104
175
|
export async function searchProjectMemory(fetchImpl = globalThis.fetch, projectId = '', query = '') {
|
|
105
176
|
const params = new URLSearchParams({ q: query });
|
|
106
177
|
return requestJson(fetchImpl, '/v1/projects/' + encodeURIComponent(projectId) + '/memory/search?' + params.toString());
|
|
@@ -401,6 +472,165 @@ function renderSync(container, sync, projectId = '') {
|
|
|
401
472
|
container.replaceChildren(heading, facts, note, exportLink);
|
|
402
473
|
}
|
|
403
474
|
|
|
475
|
+
function formatNumber(value) {
|
|
476
|
+
return new Intl.NumberFormat('pt-BR').format(Number(value) || 0);
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
function formatUsd(value) {
|
|
480
|
+
return new Intl.NumberFormat('pt-BR', { style: 'currency', currency: 'USD', minimumFractionDigits: 4 }).format(Number(value) || 0);
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
function usageFilter(label, name, value, type = 'text') {
|
|
484
|
+
const wrapper = node('label', 'usage-filter');
|
|
485
|
+
wrapper.append(node('span', '', label));
|
|
486
|
+
const input = node('input');
|
|
487
|
+
input.name = name;
|
|
488
|
+
input.type = type;
|
|
489
|
+
input.value = String(value || '');
|
|
490
|
+
wrapper.append(input);
|
|
491
|
+
return wrapper;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
function usageSelect(label, name, value, options) {
|
|
495
|
+
const wrapper = node('label', 'usage-filter');
|
|
496
|
+
wrapper.append(node('span', '', label));
|
|
497
|
+
const select = node('select');
|
|
498
|
+
select.name = name;
|
|
499
|
+
for (const [optionValue, optionLabel] of options) {
|
|
500
|
+
const option = node('option', '', optionLabel);
|
|
501
|
+
option.value = optionValue;
|
|
502
|
+
option.selected = optionValue === String(value || '');
|
|
503
|
+
select.append(option);
|
|
504
|
+
}
|
|
505
|
+
wrapper.append(select);
|
|
506
|
+
return wrapper;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
function renderUsage(container, projectId, usage, { onFiltersChanged, onTranscript } = {}) {
|
|
510
|
+
const view = buildUsageViewModel(usage);
|
|
511
|
+
const filters = usage?.filters || {};
|
|
512
|
+
const heading = node('div', 'workspace-section-heading');
|
|
513
|
+
heading.append(node('p', 'eyebrow', 'PROJECT CONSUMPTION'), node('h2', '', 'Consumo'));
|
|
514
|
+
|
|
515
|
+
const filterForm = node('form', 'usage-filters');
|
|
516
|
+
filterForm.append(
|
|
517
|
+
usageFilter('De', 'from', filters.from, 'date'),
|
|
518
|
+
usageFilter('Até', 'to', filters.to, 'date'),
|
|
519
|
+
usageSelect('Escopo', 'role', filters.role, [['', 'Principal + subagentes'], ['main', 'Principal'], ['subagent', 'Subagentes']]),
|
|
520
|
+
usageFilter('Agente', 'agent_id', filters.agent_id || filters.agentId),
|
|
521
|
+
usageFilter('Subagente', 'subagent_id', filters.subagent_id || filters.subagentId),
|
|
522
|
+
usageFilter('Provedor', 'provider', filters.provider),
|
|
523
|
+
usageFilter('Provedor do modelo', 'model_provider', filters.model_provider || filters.modelProvider),
|
|
524
|
+
usageFilter('Modelo', 'model', filters.model),
|
|
525
|
+
usageFilter('Change', 'change', filters.change || filters.changeSlug),
|
|
526
|
+
usageFilter('Sessão', 'session_id', filters.session_id || filters.sessionId),
|
|
527
|
+
node('button', 'button-primary', 'Aplicar filtros'),
|
|
528
|
+
);
|
|
529
|
+
filterForm.addEventListener('submit', (event) => {
|
|
530
|
+
event.preventDefault();
|
|
531
|
+
const form = new FormData(filterForm);
|
|
532
|
+
onFiltersChanged?.({
|
|
533
|
+
from: form.get('from') || '', to: form.get('to') || '', role: form.get('role') || '',
|
|
534
|
+
agent_id: form.get('agent_id') || '', subagent_id: form.get('subagent_id') || '',
|
|
535
|
+
provider: form.get('provider') || '', model_provider: form.get('model_provider') || '', model: form.get('model') || '',
|
|
536
|
+
change: form.get('change') || '', session_id: form.get('session_id') || '',
|
|
537
|
+
});
|
|
538
|
+
});
|
|
539
|
+
|
|
540
|
+
const cards = node('div', 'usage-summary-grid');
|
|
541
|
+
const summaryCards = [
|
|
542
|
+
['Custo total', formatUsd(view.totalCost), `${formatUsd(view.mainCost)} principal · ${formatUsd(view.subagentCost)} subagentes`],
|
|
543
|
+
['Tokens', formatNumber(view.tokens.total), `entrada ${formatNumber(view.tokens.input)} · saída ${formatNumber(view.tokens.output)}`],
|
|
544
|
+
['Chamadas', formatNumber(view.callCount), `${formatNumber(view.sessions)} sessões · ${formatNumber(view.modelsCount)} modelos`],
|
|
545
|
+
['Cobertura', view.coverage.label, `${formatNumber(view.coverage.total)} transcript(s)`],
|
|
546
|
+
];
|
|
547
|
+
for (const [label, value, caption] of summaryCards) {
|
|
548
|
+
const card = node('article', 'usage-summary-card');
|
|
549
|
+
card.append(node('span', 'metric-label', label), node('strong', 'usage-summary-value', value), node('span', 'metric-caption', caption));
|
|
550
|
+
cards.append(card);
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
const content = [];
|
|
554
|
+
if (view.hasUnknownPricing) content.push(node('div', 'usage-warning', 'Há modelos sem tarifa conhecida. Os custos históricos registrados não foram recalculados.'));
|
|
555
|
+
if (view.wastedCost > 0) content.push(node('div', 'usage-callout', `Desperdício registrado em workflows interrompidos: ${formatUsd(view.wastedCost)}.`));
|
|
556
|
+
|
|
557
|
+
const tokenPanel = node('section', 'usage-panel');
|
|
558
|
+
tokenPanel.append(node('h3', '', 'Tokens por categoria'));
|
|
559
|
+
const tokenGrid = node('div', 'usage-token-grid');
|
|
560
|
+
for (const [label, value] of [['Entrada', view.tokens.input], ['Cache write', view.tokens.cacheWrite], ['Cache read', view.tokens.cacheRead], ['Saída', view.tokens.output], ['Reasoning', view.tokens.reasoning], ['Total', view.tokens.total]]) {
|
|
561
|
+
tokenGrid.append(fact(label, formatNumber(value)));
|
|
562
|
+
}
|
|
563
|
+
tokenPanel.append(tokenGrid);
|
|
564
|
+
content.push(tokenPanel);
|
|
565
|
+
|
|
566
|
+
const trendPanel = node('section', 'usage-panel');
|
|
567
|
+
trendPanel.append(node('h3', '', 'Tendência diária'));
|
|
568
|
+
const trend = node('div', 'usage-trend');
|
|
569
|
+
if (!view.trend.length) trend.append(node('p', 'muted', 'Sem atividade no período selecionado.'));
|
|
570
|
+
else for (const day of view.trend) {
|
|
571
|
+
const row = node('div', 'usage-trend-row');
|
|
572
|
+
row.append(node('span', '', day.date), node('span', '', `${formatNumber(day.tokens_total)} tokens`), node('strong', '', formatUsd(day.cost_usd)));
|
|
573
|
+
trend.append(row);
|
|
574
|
+
}
|
|
575
|
+
trendPanel.append(trend);
|
|
576
|
+
content.push(trendPanel);
|
|
577
|
+
|
|
578
|
+
const hierarchy = node('section', 'usage-panel usage-hierarchy');
|
|
579
|
+
hierarchy.append(node('h3', '', 'Agentes, modelos e subagentes'));
|
|
580
|
+
if (!view.agents.length) hierarchy.append(node('p', 'muted', 'Nenhum agente com consumo no período.'));
|
|
581
|
+
const renderAgent = (agent) => {
|
|
582
|
+
const details = node('details', 'usage-agent');
|
|
583
|
+
const summary = node('summary');
|
|
584
|
+
summary.append(node('strong', '', agent.agent_name || agent.agent_id), node('span', 'usage-agent-meta', `${agent.role} · ${formatNumber(agent.tokens_total)} tokens · ${formatUsd(agent.cost_usd)}`));
|
|
585
|
+
details.append(summary);
|
|
586
|
+
const models = node('div', 'usage-model-list');
|
|
587
|
+
for (const model of agent.models || []) {
|
|
588
|
+
const row = node('div', 'usage-model-row');
|
|
589
|
+
row.append(node('span', '', `${model.model_provider || '—'} · ${model.model || 'modelo sem nome'}`), node('span', '', `${formatNumber(model.tokens_total)} tokens`), node('strong', '', formatUsd(model.cost_usd)));
|
|
590
|
+
models.append(row);
|
|
591
|
+
}
|
|
592
|
+
details.append(models);
|
|
593
|
+
for (const child of agent.children || []) details.append(renderAgent(child));
|
|
594
|
+
return details;
|
|
595
|
+
};
|
|
596
|
+
for (const agent of view.agents) hierarchy.append(renderAgent(agent));
|
|
597
|
+
content.push(hierarchy);
|
|
598
|
+
|
|
599
|
+
const callsPanel = node('section', 'usage-panel usage-calls');
|
|
600
|
+
callsPanel.append(node('h3', '', 'Chamadas e transcripts'));
|
|
601
|
+
if (!view.calls.length) callsPanel.append(node('p', 'muted', 'Nenhuma chamada individual disponível; históricos agregados aparecem acima.'));
|
|
602
|
+
for (const call of view.calls) {
|
|
603
|
+
const details = node('details', 'usage-call');
|
|
604
|
+
const summary = node('summary');
|
|
605
|
+
summary.append(node('strong', '', call.model || 'modelo sem nome'), node('span', 'usage-agent-meta', `${call.role || 'principal'} · ${formatNumber(call.tokens?.total)} tokens · ${formatUsd(call.cost_usd)}`));
|
|
606
|
+
details.append(summary);
|
|
607
|
+
const copy = node('div', 'usage-call-copy');
|
|
608
|
+
copy.append(node('p', '', `Sessão: ${call.session_id || '—'} · Agente: ${call.agent_id || '—'}`));
|
|
609
|
+
copy.append(node('h4', '', 'Prompt'), node('pre', '', call.prompt || '—'), node('h4', '', 'Resposta'), node('pre', '', call.response || '—'));
|
|
610
|
+
if (call.transcript_id) {
|
|
611
|
+
const transcriptButton = node('button', 'reader-toggle', 'Abrir transcript completo');
|
|
612
|
+
const transcriptTarget = node('pre', 'usage-transcript', 'Carregando transcript…');
|
|
613
|
+
transcriptTarget.hidden = true;
|
|
614
|
+
transcriptButton.addEventListener('click', async () => {
|
|
615
|
+
transcriptTarget.hidden = false;
|
|
616
|
+
if (transcriptTarget.dataset.loaded) return;
|
|
617
|
+
try {
|
|
618
|
+
const transcript = await onTranscript?.(call.transcript_id);
|
|
619
|
+
transcriptTarget.textContent = transcript?.content || 'Transcript vazio.';
|
|
620
|
+
transcriptTarget.dataset.loaded = 'true';
|
|
621
|
+
} catch (error) {
|
|
622
|
+
transcriptTarget.textContent = error.message || 'Transcript indisponível.';
|
|
623
|
+
}
|
|
624
|
+
});
|
|
625
|
+
copy.append(transcriptButton, transcriptTarget);
|
|
626
|
+
}
|
|
627
|
+
details.append(copy);
|
|
628
|
+
callsPanel.append(details);
|
|
629
|
+
}
|
|
630
|
+
content.push(callsPanel);
|
|
631
|
+
container.replaceChildren(heading, filterForm, cards, ...content);
|
|
632
|
+
}
|
|
633
|
+
|
|
404
634
|
function renderReader(container, document) {
|
|
405
635
|
const heading = node('div', 'reader-heading');
|
|
406
636
|
heading.append(node('p', 'eyebrow', document.category), node('h2', '', document.title));
|
|
@@ -438,6 +668,8 @@ function startDashboardV2() {
|
|
|
438
668
|
selectedId: '',
|
|
439
669
|
filter: '',
|
|
440
670
|
memory: new Map(),
|
|
671
|
+
usage: new Map(),
|
|
672
|
+
usageFilters: new Map(),
|
|
441
673
|
route: parseObserverRoute(globalThis.location?.hash || ''),
|
|
442
674
|
};
|
|
443
675
|
const dashboardError = byId('dashboard-error');
|
|
@@ -497,6 +729,12 @@ function startDashboardV2() {
|
|
|
497
729
|
if (!state.memory.has(projectId)) state.memory.set(projectId, await loadProjectMemory(fetchJson, projectId));
|
|
498
730
|
return state.memory.get(projectId);
|
|
499
731
|
};
|
|
732
|
+
const ensureProjectUsage = async (projectId) => {
|
|
733
|
+
const filters = state.usageFilters.get(projectId) || {};
|
|
734
|
+
const usage = await loadProjectUsage(fetchJson, projectId, filters);
|
|
735
|
+
state.usage.set(projectId, usage);
|
|
736
|
+
return usage;
|
|
737
|
+
};
|
|
500
738
|
const renderSearchRoute = async (query) => {
|
|
501
739
|
const model = state.models.find((item) => item.projectId === state.selectedId) || state.models[0];
|
|
502
740
|
setHidden(dashboardPanel, true);
|
|
@@ -542,7 +780,16 @@ function startDashboardV2() {
|
|
|
542
780
|
renderReader(workspaceContent, buildMemoryDocumentViewModel(payload, payload.content));
|
|
543
781
|
return;
|
|
544
782
|
}
|
|
545
|
-
if (route.section === '
|
|
783
|
+
if (route.section === 'usage') {
|
|
784
|
+
const usage = await ensureProjectUsage(model.projectId);
|
|
785
|
+
renderUsage(workspaceContent, model.projectId, usage, {
|
|
786
|
+
onFiltersChanged: (filters) => {
|
|
787
|
+
state.usageFilters.set(model.projectId, filters);
|
|
788
|
+
renderWorkspaceRoute({ ...route });
|
|
789
|
+
},
|
|
790
|
+
onTranscript: (transcriptId) => loadProjectTranscript(fetchJson, model.projectId, transcriptId),
|
|
791
|
+
});
|
|
792
|
+
} else if (route.section === 'sessions') renderSessions(workspaceContent, model.projectId, documents);
|
|
546
793
|
else if (route.section === 'memory') renderMemory(workspaceContent, model.projectId, documents);
|
|
547
794
|
else if (route.section === 'changes') renderChanges(workspaceContent, model.projectId, documents);
|
|
548
795
|
else if (route.section === 'sync') renderSync(workspaceContent, memory.sync, model.projectId);
|
package/web/observer/index.html
CHANGED
|
@@ -96,6 +96,7 @@
|
|
|
96
96
|
<aside class="workspace-rail panel" aria-label="Navegação do projeto">
|
|
97
97
|
<nav id="workspace-nav" class="workspace-nav">
|
|
98
98
|
<a data-workspace-section="overview" href="#project/project-a/overview">Overview</a>
|
|
99
|
+
<a data-workspace-section="usage" href="#project/project-a/usage">Consumo</a>
|
|
99
100
|
<a data-workspace-section="sessions" href="#project/project-a/sessions">Sessões</a>
|
|
100
101
|
<a data-workspace-section="memory" href="#project/project-a/memory">Memória</a>
|
|
101
102
|
<a data-workspace-section="changes" href="#project/project-a/changes">Changes</a>
|
package/web/observer/styles.css
CHANGED
|
@@ -183,6 +183,33 @@ input::placeholder { color: var(--faint); }
|
|
|
183
183
|
.markdown-gap { height: .35em; }
|
|
184
184
|
.sync-callout, .workspace-error { margin-top: 24px; padding: 17px; border: 1px solid rgba(156, 224, 198, .2); border-radius: var(--radius-sm); color: var(--muted); background: rgba(156, 224, 198, .05); font-size: .8rem; line-height: 1.5; }
|
|
185
185
|
.workspace-error { border-color: rgba(245, 139, 131, .28); color: var(--red); background: rgba(245, 139, 131, .07); }
|
|
186
|
+
.usage-filters { display: flex; flex-wrap: wrap; align-items: end; gap: 9px; margin: 20px 0; padding: 13px; border: 1px solid var(--line); border-radius: var(--radius-sm); background: rgba(7, 16, 21, .28); }
|
|
187
|
+
.usage-filter { display: grid; gap: 5px; min-width: 112px; color: var(--faint); font-size: .61rem; font-weight: 700; letter-spacing: .08em; text-transform: uppercase; }
|
|
188
|
+
.usage-filter input, .usage-filter select { min-width: 0; padding: 8px 9px; border: 1px solid var(--line); border-radius: 6px; color: var(--text); background: rgba(7, 16, 21, .65); font-size: .73rem; text-transform: none; }
|
|
189
|
+
.button-primary { padding: 9px 12px; border: 1px solid rgba(156, 224, 198, .3); border-radius: 7px; color: var(--ink-950); background: var(--mint); font-size: .7rem; font-weight: 700; }
|
|
190
|
+
.button-primary:hover { background: var(--mint-bright); }
|
|
191
|
+
.usage-summary-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 9px; margin-bottom: 14px; }
|
|
192
|
+
.usage-summary-card { min-width: 0; padding: 15px; border: 1px solid var(--line); border-radius: var(--radius-md); background: rgba(20, 43, 50, .45); }
|
|
193
|
+
.usage-summary-value { display: block; overflow: hidden; margin: 10px 0 5px; color: var(--mint-bright); font-family: Georgia, serif; font-size: clamp(1.15rem, 2vw, 1.75rem); font-weight: 400; text-overflow: ellipsis; white-space: nowrap; }
|
|
194
|
+
.usage-warning { margin-bottom: 12px; padding: 13px 15px; border: 1px solid rgba(246, 189, 114, .3); border-radius: var(--radius-sm); color: var(--amber); background: rgba(246, 189, 114, .07); font-size: .76rem; line-height: 1.45; }
|
|
195
|
+
.usage-panel { margin-top: 14px; padding: 17px; border: 1px solid var(--line); border-radius: var(--radius-md); background: rgba(7, 16, 21, .25); }
|
|
196
|
+
.usage-panel h3 { margin: 0 0 13px; color: var(--mint-bright); font-size: .82rem; letter-spacing: .06em; text-transform: uppercase; }
|
|
197
|
+
.usage-token-grid { display: grid; grid-template-columns: repeat(6, 1fr); gap: 7px; }
|
|
198
|
+
.usage-token-grid .fact { min-width: 0; padding: 10px; }
|
|
199
|
+
.usage-trend { display: grid; gap: 6px; }
|
|
200
|
+
.usage-trend-row, .usage-model-row { display: grid; grid-template-columns: minmax(100px, 1fr) auto auto; gap: 12px; align-items: center; padding: 9px 11px; border-radius: 7px; color: var(--muted); background: rgba(20, 43, 50, .38); font-size: .72rem; }
|
|
201
|
+
.usage-trend-row strong, .usage-model-row strong { color: var(--mint); font-weight: 600; }
|
|
202
|
+
.usage-agent { border-top: 1px solid var(--line); }
|
|
203
|
+
.usage-agent:first-of-type { border-top: 0; }
|
|
204
|
+
.usage-agent summary { display: flex; justify-content: space-between; gap: 12px; padding: 12px 2px; cursor: pointer; color: var(--text); font-size: .78rem; }
|
|
205
|
+
.usage-agent-meta { color: var(--muted); font-size: .68rem; font-weight: 400; }
|
|
206
|
+
.usage-model-list { display: grid; gap: 6px; padding: 0 0 12px 17px; }
|
|
207
|
+
.usage-call { border-top: 1px solid var(--line); }
|
|
208
|
+
.usage-call:first-of-type { border-top: 0; }
|
|
209
|
+
.usage-call summary { display: flex; justify-content: space-between; gap: 12px; padding: 11px 2px; cursor: pointer; font-size: .76rem; }
|
|
210
|
+
.usage-call-copy { padding: 0 0 15px 17px; color: var(--muted); font-size: .72rem; }
|
|
211
|
+
.usage-call-copy h4 { margin: 13px 0 6px; color: var(--faint); font-size: .63rem; letter-spacing: .1em; text-transform: uppercase; }
|
|
212
|
+
.usage-call-copy pre, .usage-transcript { max-height: 260px; overflow: auto; margin: 6px 0; padding: 11px; border: 1px solid var(--line); border-radius: 6px; color: var(--mint-bright); background: rgba(7, 16, 21, .62); font-family: "Cascadia Code", "Consolas", monospace; font-size: .7rem; line-height: 1.5; white-space: pre-wrap; }
|
|
186
213
|
.footer-note { display: flex; justify-content: center; gap: 8px; padding: 18px 0 28px; color: var(--faint); font-size: .7rem; }
|
|
187
214
|
.footer-separator { color: var(--line-strong); }
|
|
188
215
|
.sr-only { position: absolute; width: 1px; height: 1px; padding: 0; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0; }
|
|
@@ -196,7 +223,7 @@ input::placeholder { color: var(--faint); }
|
|
|
196
223
|
.detail-panel { min-height: 420px; }
|
|
197
224
|
.workspace-layout { grid-template-columns: 1fr; }
|
|
198
225
|
.workspace-rail { order: 0; }
|
|
199
|
-
.workspace-nav { grid-template-columns: repeat(
|
|
226
|
+
.workspace-nav { grid-template-columns: repeat(6, 1fr); }
|
|
200
227
|
.workspace-nav a { border-left: 0; border-bottom: 2px solid transparent; padding: 9px 6px; text-align: center; }
|
|
201
228
|
.workspace-nav a:hover, .workspace-nav a.is-active { border-bottom-color: var(--amber); border-left-color: transparent; }
|
|
202
229
|
.workspace-meta { display: none; }
|
|
@@ -212,12 +239,18 @@ input::placeholder { color: var(--faint); }
|
|
|
212
239
|
.panel-heading { flex-direction: column; }
|
|
213
240
|
.search-field, .search-field input { width: 100%; }
|
|
214
241
|
.detail-facts { grid-template-columns: 1fr; }
|
|
242
|
+
.usage-summary-grid { grid-template-columns: repeat(2, 1fr); }
|
|
243
|
+
.usage-token-grid { grid-template-columns: repeat(3, 1fr); }
|
|
215
244
|
.detail-header { flex-direction: column; }
|
|
216
245
|
.workspace-header { grid-template-columns: 1fr auto; align-items: start; gap: 13px; }
|
|
217
246
|
.workspace-header .back-link { grid-column: 1 / -1; }
|
|
218
247
|
.workspace-header h1 { font-size: 2.8rem; }
|
|
219
248
|
.workspace-content { padding: 20px 15px; }
|
|
220
249
|
.workspace-nav { grid-template-columns: repeat(3, 1fr); }
|
|
250
|
+
.usage-filters { align-items: stretch; flex-direction: column; }
|
|
251
|
+
.usage-filter, .usage-filter input, .usage-filter select, .button-primary { width: 100%; }
|
|
252
|
+
.usage-summary-grid { grid-template-columns: 1fr; }
|
|
253
|
+
.usage-token-grid { grid-template-columns: repeat(2, 1fr); }
|
|
221
254
|
.memory-document-row { grid-template-columns: 28px minmax(0, 1fr); }
|
|
222
255
|
.memory-document-size { display: none; }
|
|
223
256
|
.footer-note { flex-direction: column; align-items: center; gap: 4px; }
|