total-recall-brain 3.41.4 → 3.41.6
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/bin/total-recall.mjs +10 -6
- package/docs/reference/cli-reference.md +18 -0
- package/frontend/dist/assets/index-4tGQfTa2.js +5605 -0
- package/frontend/dist/assets/index-DtwsDyFM.css +1 -0
- package/frontend/dist/index.html +2 -2
- package/frontend/package.json +11 -0
- package/frontend/src/App.tsx +47 -122
- package/frontend/src/components/plugins/plugin-frame.ts +1 -0
- package/frontend/src/index.css +152 -86
- package/frontend/src/pages/HelpPage.tsx +13 -10
- package/frontend/src/pages/MemoryPage.tsx +28 -30
- package/frontend/src/pages/NetworkPage.tsx +3 -3
- package/frontend/src/pages/PluginsPage.tsx +6 -5
- package/frontend/src/pages/RulesPage.tsx +16 -12
- package/package.json +4 -3
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/package-lock.json +3114 -36
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/package.json +4 -1
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/package-lock.json +3114 -36
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/package.json +4 -1
- package/scaffold/.agent/skills/total-recall/plugins/embeddings/package-lock.json +3121 -33
- package/scaffold/.agent/skills/total-recall/plugins/embeddings/package.json +4 -1
- package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/package-lock.json +3122 -34
- package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/package.json +4 -1
- package/scaffold/.agent/skills/total-recall/plugins/mesh/package-lock.json +3127 -49
- package/scaffold/.agent/skills/total-recall/plugins/mesh/package.json +5 -2
- package/scaffold/.agent/skills/total-recall/plugins/mesh/src/core/mesh.mjs +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/capability/index.mjs +136 -7
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/cli.mjs +135 -26
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/package-lock.json +3121 -33
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/package.json +5 -2
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/plugin.json +228 -9
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/skills/meta-harness/SKILL.md +24 -30
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/alerts.mjs +29 -16
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/cascade.mjs +73 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/coordinator.mjs +182 -365
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/dispatch.mjs +33 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/documentation.mjs +40 -14
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/executor.mjs +4 -4
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/agy.mjs +187 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/claude.mjs +275 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/cline.mjs +132 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/codex.mjs +187 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/cursor.mjs +158 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/devin.mjs +157 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/dsh.mjs +183 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/grok.mjs +132 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/hermes.mjs +173 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/index.mjs +87 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/openclaw.mjs +173 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/pi.mjs +129 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate.mjs +12 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/json-reply.mjs +22 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/learner.mjs +72 -70
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/model-catalog.mjs +195 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/paths.mjs +11 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/pool.mjs +36 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/review.mjs +169 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/router.mjs +136 -346
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/runtime.mjs +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/ssss-store.mjs +20 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/telemetry.mjs +43 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/threads.mjs +156 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/worker.mjs +117 -35
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/worktree.mjs +51 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/card.js +398 -57
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/cli.mjs +304 -46
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/package-lock.json +367 -41
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/package.json +5 -2
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/plugin.json +33 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/config.mjs +2 -2
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/generate.mjs +159 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/graph.mjs +197 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/lint.mjs +153 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/mesh-vaults.mjs +261 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/refresh.mjs +151 -4
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/search.mjs +114 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/token.mjs +96 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/card.js +899 -662
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/vendor/openwiki.mjs +8987 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/vendor/skills/mermaid-diagrams/SKILL.md +44 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/vendor/skills/write-connector/SKILL.md +56 -0
- package/scaffold/.agent/skills/total-recall/plugins/security/package-lock.json +3097 -81
- package/scaffold/.agent/skills/total-recall/plugins/security/package.json +4 -1
- package/src/cli/agent.mjs +12 -1
- package/src/cli/compact.mjs +108 -0
- package/src/cli/connect.mjs +56 -1
- package/src/cli/context.mjs +55 -23
- package/src/cli/doctor.mjs +5 -2
- package/src/cli/forget.mjs +2 -0
- package/src/cli/harness.mjs +62 -50
- package/src/cli/memory-entry.mjs +2 -2
- package/src/cli/preflight.mjs +152 -16
- package/src/cli/release.mjs +386 -0
- package/src/cli/remember.mjs +19 -1
- package/src/cli/rules.mjs +54 -12
- package/src/cli/startup.mjs +1 -1
- package/src/cli/status.mjs +12 -2
- package/src/core/claude-hooks.mjs +41 -3
- package/src/core/cli-commands.mjs +2 -0
- package/src/core/cli-semantic-index.mjs +3 -2
- package/src/core/conflict-detector.mjs +23 -12
- package/src/core/context-compactor.mjs +145 -0
- package/src/core/context-policy.mjs +20 -0
- package/src/core/duplicate-guard.mjs +350 -0
- package/src/core/meta-harness.mjs +31 -11
- package/src/core/preflight-gate.mjs +2 -2
- package/src/core/preflight-receipt.mjs +29 -1
- package/src/core/scaffold-plugins.mjs +7 -0
- package/src/core/stdio-flush.mjs +23 -0
- package/src/core/surface.mjs +33 -10
- package/src/server/invoke-durability.mjs +39 -0
- package/src/server/invoke-write-child.mjs +25 -0
- package/src/server/routes/invoke.mjs +7 -0
- package/src/server/routes/plugins.mjs +2 -2
- package/frontend/dist/assets/index-BIQmqDKP.css +0 -1
- package/frontend/dist/assets/index-CRzjQLu7.js +0 -5631
- package/frontend/src/components/DaemonLogsTab.tsx +0 -54
- package/frontend/src/components/ResearchAgendaTab.tsx +0 -712
- package/frontend/src/components/TaskQueueTab.tsx +0 -193
- package/frontend/src/components/VoiceInput.tsx +0 -112
- package/frontend/src/pages/ChatPage.tsx +0 -1059
- package/frontend/src/pages/CollabPage.tsx +0 -470
- package/frontend/src/pages/DesignDocsPage.tsx +0 -621
- package/frontend/src/pages/FilesPage.tsx +0 -393
- package/frontend/src/pages/HealthPage.tsx +0 -359
- package/frontend/src/pages/InboxPage.tsx +0 -83
- package/frontend/src/pages/InstructionsPage.tsx +0 -583
- package/frontend/src/pages/IntegrationsPage.tsx +0 -761
- package/frontend/src/pages/MeshPage.css +0 -58
- package/frontend/src/pages/MeshPage.tsx +0 -1219
- package/frontend/src/pages/OkfPage.tsx +0 -535
- package/frontend/src/pages/SandboxPage.tsx +0 -113
- package/frontend/src/pages/SkillsPage.tsx +0 -1994
- package/frontend/src/pages/TasksPage.tsx +0 -427
- package/frontend/src/pages/UsagePage.tsx +0 -702
- package/scaffold/.agent/skills/total-recall/plugins/cli-agents/README.md +0 -65
- package/scaffold/.agent/skills/total-recall/plugins/cli-agents/cli.mjs +0 -76
- package/scaffold/.agent/skills/total-recall/plugins/cli-agents/package.json +0 -19
- package/scaffold/.agent/skills/total-recall/plugins/cli-agents/plugin.json +0 -266
- package/scaffold/.agent/skills/total-recall/plugins/cli-agents/src/adapters/dsh.mjs +0 -446
- package/scaffold/.agent/skills/total-recall/plugins/cli-agents/src/documentation.mjs +0 -152
- package/scaffold/.agent/skills/total-recall/plugins/cli-agents/src/executor.mjs +0 -71
- package/scaffold/.agent/skills/total-recall/plugins/cli-agents/src/runtime.mjs +0 -36
- package/scaffold/.agent/skills/total-recall/plugins/cli-agents/src/setup.mjs +0 -35
- package/scaffold/.agent/skills/total-recall/plugins/cli-agents/src/worker.mjs +0 -175
- package/scaffold/.agent/skills/total-recall/plugins/cli-agents/ui/DESIGN.md +0 -32
- package/scaffold/.agent/skills/total-recall/plugins/cli-agents/ui/card.js +0 -575
|
@@ -1,702 +0,0 @@
|
|
|
1
|
-
import { useState, useEffect, useMemo } from 'react';
|
|
2
|
-
import { fetchUsageStats, fetchConfigJson, fetchProviderUsage } from '../api';
|
|
3
|
-
import type { ProviderUsage } from '../api';
|
|
4
|
-
import type { ConfigJson, UsageData } from '../types';
|
|
5
|
-
import { AreaChart, Area, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, Legend } from 'recharts';
|
|
6
|
-
|
|
7
|
-
export default function UsagePage() {
|
|
8
|
-
const [usage, setUsage] = useState<UsageData | null>(null);
|
|
9
|
-
const [config, setConfig] = useState<ConfigJson | null>(null);
|
|
10
|
-
const [loading, setLoading] = useState(true);
|
|
11
|
-
const [error, setError] = useState<string | null>(null);
|
|
12
|
-
const [providerUsage, setProviderUsage] = useState<ProviderUsage | null>(null);
|
|
13
|
-
const [providerLoading, setProviderLoading] = useState(false);
|
|
14
|
-
const [providerError, setProviderError] = useState<string | null>(null);
|
|
15
|
-
|
|
16
|
-
const fetchUsageAndConfig = async () => {
|
|
17
|
-
try {
|
|
18
|
-
const stats = await fetchUsageStats();
|
|
19
|
-
setUsage(stats);
|
|
20
|
-
|
|
21
|
-
const configData = await fetchConfigJson();
|
|
22
|
-
setConfig(configData);
|
|
23
|
-
setError(null);
|
|
24
|
-
} catch (err: unknown) {
|
|
25
|
-
setError((err as Error).message || 'Failed to sync budget consumption stats.');
|
|
26
|
-
} finally {
|
|
27
|
-
setLoading(false);
|
|
28
|
-
}
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
const loadProviderUsage = async (refresh = false) => {
|
|
32
|
-
setProviderLoading(true);
|
|
33
|
-
try {
|
|
34
|
-
setProviderUsage(await fetchProviderUsage(refresh));
|
|
35
|
-
setProviderError(null);
|
|
36
|
-
} catch (err: unknown) {
|
|
37
|
-
setProviderError((err as Error).message || 'Failed to read provider billing data.');
|
|
38
|
-
} finally {
|
|
39
|
-
setProviderLoading(false);
|
|
40
|
-
}
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
useEffect(() => {
|
|
44
|
-
// eslint-disable-next-line react-hooks/set-state-in-effect -- legitimate statistics sync on mount
|
|
45
|
-
void fetchUsageAndConfig();
|
|
46
|
-
const interval = setInterval(fetchUsageAndConfig, 5000);
|
|
47
|
-
return () => clearInterval(interval);
|
|
48
|
-
}, []);
|
|
49
|
-
|
|
50
|
-
useEffect(() => {
|
|
51
|
-
// Deliberately not on the 5s ledger interval — this hits live provider
|
|
52
|
-
// billing APIs. The server caches hourly; refreshing is a manual action.
|
|
53
|
-
// eslint-disable-next-line react-hooks/set-state-in-effect -- initial fetch on mount
|
|
54
|
-
void loadProviderUsage();
|
|
55
|
-
}, []);
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
const dailyCap = config?.budget?.budget?.daily_cap_usd ?? 5.0;
|
|
60
|
-
const weeklyCap = config?.budget?.budget?.weekly_cap_usd ?? 25.0;
|
|
61
|
-
const budgetEnabled = config?.budget?.budget?.enabled !== false;
|
|
62
|
-
|
|
63
|
-
const dailyPercent = Math.min(100, ((usage?.dailyUsd || 0) / dailyCap) * 100);
|
|
64
|
-
const weeklyPercent = Math.min(100, ((usage?.weeklyUsd || 0) / weeklyCap) * 100);
|
|
65
|
-
|
|
66
|
-
const getProgressColor = (percent: number) => {
|
|
67
|
-
if (percent > 85) return 'var(--error)';
|
|
68
|
-
if (percent > 60) return 'var(--warning)';
|
|
69
|
-
return 'var(--success)';
|
|
70
|
-
};
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
const timeseriesData = useMemo(() => {
|
|
74
|
-
if ((usage as unknown as Record<string, unknown>)?.timeseries && Object.keys((usage as unknown as Record<string, unknown>).timeseries as Record<string, unknown>).length > 0) {
|
|
75
|
-
const ts = (usage as unknown as Record<string, unknown>).timeseries as Record<string, Record<string, { provider: string; cost: number }>>;
|
|
76
|
-
return Object.entries(ts).map(([date, models]) => {
|
|
77
|
-
const entry: Record<string, unknown> = { date: new Date(date).toLocaleDateString('en-US', { month: 'short', day: 'numeric' }) };
|
|
78
|
-
let geminiCost = 0, claudeCost = 0, openrouterCost = 0, codexCost = 0;
|
|
79
|
-
for (const [, info] of Object.entries(models)) {
|
|
80
|
-
const m = info as { provider: string; cost: number };
|
|
81
|
-
if (m.provider === 'gemini') geminiCost += m.cost;
|
|
82
|
-
else if (m.provider === 'claude') claudeCost += m.cost;
|
|
83
|
-
else if (m.provider === 'openrouter') openrouterCost += m.cost;
|
|
84
|
-
else codexCost += m.cost;
|
|
85
|
-
}
|
|
86
|
-
entry.gemini = +geminiCost.toFixed(3);
|
|
87
|
-
entry.claude = +claudeCost.toFixed(3);
|
|
88
|
-
entry.openrouter = +openrouterCost.toFixed(3);
|
|
89
|
-
entry.codex = +codexCost.toFixed(3);
|
|
90
|
-
return entry;
|
|
91
|
-
});
|
|
92
|
-
}
|
|
93
|
-
return [];
|
|
94
|
-
}, [usage]);
|
|
95
|
-
|
|
96
|
-
if (loading) {
|
|
97
|
-
return (
|
|
98
|
-
<div className="page" style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', height: '100%' }}>
|
|
99
|
-
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="var(--text-tertiary)" strokeWidth="2.5" style={{ animation: 'spin 1s linear infinite', marginBottom: 12 }}>
|
|
100
|
-
<path d="M21 12a9 9 0 1 1-6.219-8.56"/>
|
|
101
|
-
</svg>
|
|
102
|
-
<div style={{ color: 'var(--text-secondary)' }}>Aggregating token expenditures...</div>
|
|
103
|
-
</div>
|
|
104
|
-
);
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
return (
|
|
108
|
-
<div className="page" style={{ display: 'flex', flexDirection: 'column', height: '100%', overflowY: 'auto' }}>
|
|
109
|
-
<div className="page-header" style={{ marginBottom: 24 }}>
|
|
110
|
-
<h1>Token & Budget Statistics</h1>
|
|
111
|
-
<p>Monitor your token cost expenditure in real time against safety budget limits</p>
|
|
112
|
-
</div>
|
|
113
|
-
|
|
114
|
-
{/* Subscription Tier Banner */}
|
|
115
|
-
<div style={{
|
|
116
|
-
padding: '16px 20px',
|
|
117
|
-
marginBottom: 24,
|
|
118
|
-
background: 'rgba(18, 18, 26, 0.6)',
|
|
119
|
-
backdropFilter: 'blur(12px)',
|
|
120
|
-
border: '1px solid var(--border)',
|
|
121
|
-
borderLeft: '3px solid var(--accent)',
|
|
122
|
-
borderRadius: 10,
|
|
123
|
-
display: 'flex',
|
|
124
|
-
alignItems: 'center',
|
|
125
|
-
gap: 14,
|
|
126
|
-
}}>
|
|
127
|
-
<span style={{ fontSize: 24 }}>🛡️</span>
|
|
128
|
-
<div>
|
|
129
|
-
<div style={{ fontSize: 14, fontWeight: 700, color: '#fff', letterSpacing: 0.3 }}>
|
|
130
|
-
Local-first · Self-hosted
|
|
131
|
-
</div>
|
|
132
|
-
<div style={{ fontSize: 12, color: 'var(--text-secondary)', marginTop: 2 }}>
|
|
133
|
-
Memory and processing stay on your machine. No vendor lock-in.
|
|
134
|
-
</div>
|
|
135
|
-
</div>
|
|
136
|
-
</div>
|
|
137
|
-
|
|
138
|
-
{error && (
|
|
139
|
-
<div className="badge badge-error" style={{ marginBottom: 20, display: 'inline-flex', padding: '6px 12px', background: 'rgba(239, 68, 68, 0.1)', border: '1px solid #ef4444', color: '#f87171' }}>
|
|
140
|
-
⚠️ {error}
|
|
141
|
-
</div>
|
|
142
|
-
)}
|
|
143
|
-
|
|
144
|
-
{/* Overview dollar cards */}
|
|
145
|
-
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(340px, 1fr))', gap: 24, marginBottom: 24 }}>
|
|
146
|
-
|
|
147
|
-
{/* Daily Cap progress card */}
|
|
148
|
-
<div className="card" style={{ padding: 24, background: 'rgba(18, 18, 26, 0.6)', backdropFilter: 'blur(8px)', border: '1px solid var(--border)', display: 'flex', flexDirection: 'column', gap: 16 }}>
|
|
149
|
-
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start' }}>
|
|
150
|
-
<div>
|
|
151
|
-
<h3 style={{ fontSize: 13, fontWeight: 700, color: 'var(--text-tertiary)', textTransform: 'uppercase', letterSpacing: 0.5 }}>Daily Expenditure</h3>
|
|
152
|
-
<div style={{ fontSize: 28, fontWeight: 700, marginTop: 4, color: '#fff' }}>
|
|
153
|
-
${usage?.dailyUsd?.toFixed(4) ?? '0.0000'}
|
|
154
|
-
</div>
|
|
155
|
-
</div>
|
|
156
|
-
<span className="badge" style={{ background: 'var(--bg-elevated)', color: 'var(--text-secondary)', border: '1px solid var(--border)', fontSize: 11 }}>
|
|
157
|
-
Cap: ${dailyCap.toFixed(2)}
|
|
158
|
-
</span>
|
|
159
|
-
</div>
|
|
160
|
-
|
|
161
|
-
<div style={{ display: 'flex', flexDirection: 'column', gap: 6, marginTop: 8 }}>
|
|
162
|
-
<div style={{ display: 'flex', justifyContent: 'space-between', fontSize: 11, color: 'var(--text-secondary)' }}>
|
|
163
|
-
<span>Consumption Ratio</span>
|
|
164
|
-
<span>{dailyPercent.toFixed(1)}%</span>
|
|
165
|
-
</div>
|
|
166
|
-
<div style={{ height: 6, background: 'var(--bg-elevated)', borderRadius: 3, overflow: 'hidden' }}>
|
|
167
|
-
<div style={{
|
|
168
|
-
height: '100%',
|
|
169
|
-
width: `${dailyPercent}%`,
|
|
170
|
-
background: getProgressColor(dailyPercent),
|
|
171
|
-
boxShadow: `0 0 8px ${getProgressColor(dailyPercent)}`,
|
|
172
|
-
transition: 'width 0.4s ease'
|
|
173
|
-
}} />
|
|
174
|
-
</div>
|
|
175
|
-
</div>
|
|
176
|
-
|
|
177
|
-
<div style={{ fontSize: 10, color: 'var(--text-tertiary)', borderTop: '1px solid var(--border)', paddingTop: 10, marginTop: 4 }}>
|
|
178
|
-
Status: {budgetEnabled ? '🛡️ Budget supervision active' : '⚠️ Limits unmonitored (disabled)'}
|
|
179
|
-
</div>
|
|
180
|
-
</div>
|
|
181
|
-
|
|
182
|
-
{/* Weekly Cap progress card */}
|
|
183
|
-
<div className="card" style={{ padding: 24, background: 'rgba(18, 18, 26, 0.6)', backdropFilter: 'blur(8px)', border: '1px solid var(--border)', display: 'flex', flexDirection: 'column', gap: 16 }}>
|
|
184
|
-
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start' }}>
|
|
185
|
-
<div>
|
|
186
|
-
<h3 style={{ fontSize: 13, fontWeight: 700, color: 'var(--text-tertiary)', textTransform: 'uppercase', letterSpacing: 0.5 }}>Weekly Expenditure</h3>
|
|
187
|
-
<div style={{ fontSize: 28, fontWeight: 700, marginTop: 4, color: '#fff' }}>
|
|
188
|
-
${usage?.weeklyUsd?.toFixed(4) ?? '0.0000'}
|
|
189
|
-
</div>
|
|
190
|
-
</div>
|
|
191
|
-
<span className="badge" style={{ background: 'var(--bg-elevated)', color: 'var(--text-secondary)', border: '1px solid var(--border)', fontSize: 11 }}>
|
|
192
|
-
Cap: ${weeklyCap.toFixed(2)}
|
|
193
|
-
</span>
|
|
194
|
-
</div>
|
|
195
|
-
|
|
196
|
-
<div style={{ display: 'flex', flexDirection: 'column', gap: 6, marginTop: 8 }}>
|
|
197
|
-
<div style={{ display: 'flex', justifyContent: 'space-between', fontSize: 11, color: 'var(--text-secondary)' }}>
|
|
198
|
-
<span>Consumption Ratio</span>
|
|
199
|
-
<span>{weeklyPercent.toFixed(1)}%</span>
|
|
200
|
-
</div>
|
|
201
|
-
<div style={{ height: 6, background: 'var(--bg-elevated)', borderRadius: 3, overflow: 'hidden' }}>
|
|
202
|
-
<div style={{
|
|
203
|
-
height: '100%',
|
|
204
|
-
width: `${weeklyPercent}%`,
|
|
205
|
-
background: getProgressColor(weeklyPercent),
|
|
206
|
-
boxShadow: `0 0 8px ${getProgressColor(weeklyPercent)}`,
|
|
207
|
-
transition: 'width 0.4s ease'
|
|
208
|
-
}} />
|
|
209
|
-
</div>
|
|
210
|
-
</div>
|
|
211
|
-
|
|
212
|
-
<div style={{ fontSize: 10, color: 'var(--text-tertiary)', borderTop: '1px solid var(--border)', paddingTop: 10, marginTop: 4 }}>
|
|
213
|
-
Rolling window: Last 7 days in local system time
|
|
214
|
-
</div>
|
|
215
|
-
</div>
|
|
216
|
-
|
|
217
|
-
</div>
|
|
218
|
-
|
|
219
|
-
{/* Per Model breakdown list */}
|
|
220
|
-
<div className="card" style={{ padding: 24, background: 'rgba(18, 18, 26, 0.6)', border: '1px solid var(--border)' }}>
|
|
221
|
-
<h3 style={{ fontSize: 14, fontWeight: 700, color: 'var(--text-tertiary)', textTransform: 'uppercase', letterSpacing: 0.5, borderBottom: '1px solid var(--border)', paddingBottom: 10, marginBottom: 16 }}>
|
|
222
|
-
Model Engine Spend breakdown
|
|
223
|
-
</h3>
|
|
224
|
-
|
|
225
|
-
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(280px, 1fr))', gap: 20 }}>
|
|
226
|
-
|
|
227
|
-
{/* Gemini */}
|
|
228
|
-
<div style={{ background: 'var(--bg-tertiary)', border: '1px solid var(--border)', padding: 16, borderRadius: 10, display: 'flex', flexDirection: 'column', gap: 12 }}>
|
|
229
|
-
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
|
230
|
-
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
|
|
231
|
-
<span style={{ fontSize: 18 }}>♊</span>
|
|
232
|
-
<div style={{ display: 'flex', flexDirection: 'column' }}>
|
|
233
|
-
<span style={{ fontSize: 13, fontWeight: 600, color: '#fff' }}>Gemini CLI</span>
|
|
234
|
-
<span style={{ fontSize: 10, color: 'var(--text-tertiary)' }}>~/.gemini/tmp</span>
|
|
235
|
-
</div>
|
|
236
|
-
</div>
|
|
237
|
-
<span className="badge" style={{ background: 'rgba(59, 130, 246, 0.1)', color: '#60a5fa', border: '1px solid rgba(59, 130, 246, 0.2)' }}>
|
|
238
|
-
Google API
|
|
239
|
-
</span>
|
|
240
|
-
</div>
|
|
241
|
-
|
|
242
|
-
<div style={{ display: 'flex', flexDirection: 'column', gap: 6, fontSize: 12 }}>
|
|
243
|
-
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
|
244
|
-
<span style={{ color: 'var(--text-secondary)' }}>Daily Spend:</span>
|
|
245
|
-
<span style={{ fontWeight: 600, color: '#fff' }}>${usage?.breakdown?.gemini?.dailyUsd?.toFixed(4) ?? '0.0000'}</span>
|
|
246
|
-
</div>
|
|
247
|
-
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
|
248
|
-
<span style={{ color: 'var(--text-secondary)' }}>Weekly Spend:</span>
|
|
249
|
-
<span style={{ fontWeight: 600, color: '#fff' }}>${usage?.breakdown?.gemini?.weeklyUsd?.toFixed(4) ?? '0.0000'}</span>
|
|
250
|
-
</div>
|
|
251
|
-
<div style={{ display: 'flex', justifyContent: 'space-between', borderTop: '1px solid var(--border)', paddingTop: 6, marginTop: 4, fontSize: 11, color: 'var(--text-tertiary)' }}>
|
|
252
|
-
<span>Rolling Tokens:</span>
|
|
253
|
-
<span>{usage?.breakdown?.gemini?.dailyTokens?.toLocaleString() || 0} (24h)</span>
|
|
254
|
-
</div>
|
|
255
|
-
</div>
|
|
256
|
-
</div>
|
|
257
|
-
|
|
258
|
-
{/* Claude */}
|
|
259
|
-
<div style={{ background: 'var(--bg-tertiary)', border: '1px solid var(--border)', padding: 16, borderRadius: 10, display: 'flex', flexDirection: 'column', gap: 12 }}>
|
|
260
|
-
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
|
261
|
-
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
|
|
262
|
-
<span style={{ fontSize: 18 }}>🍊</span>
|
|
263
|
-
<div style={{ display: 'flex', flexDirection: 'column' }}>
|
|
264
|
-
<span style={{ fontSize: 13, fontWeight: 600, color: '#fff' }}>Claude Code CLI</span>
|
|
265
|
-
<span style={{ fontSize: 10, color: 'var(--text-tertiary)' }}>~/.claude</span>
|
|
266
|
-
</div>
|
|
267
|
-
</div>
|
|
268
|
-
<span className="badge" style={{ background: 'rgba(239, 107, 107, 0.1)', color: '#f87171', border: '1px solid rgba(239, 107, 107, 0.2)' }}>
|
|
269
|
-
Anthropic API
|
|
270
|
-
</span>
|
|
271
|
-
</div>
|
|
272
|
-
|
|
273
|
-
<div style={{ display: 'flex', flexDirection: 'column', gap: 6, fontSize: 12 }}>
|
|
274
|
-
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
|
275
|
-
<span style={{ color: 'var(--text-secondary)' }}>Daily Spend:</span>
|
|
276
|
-
<span style={{ fontWeight: 600, color: '#fff' }}>${usage?.breakdown?.claude?.dailyUsd?.toFixed(4) ?? '0.0000'}</span>
|
|
277
|
-
</div>
|
|
278
|
-
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
|
279
|
-
<span style={{ color: 'var(--text-secondary)' }}>Weekly Spend:</span>
|
|
280
|
-
<span style={{ fontWeight: 600, color: '#fff' }}>${usage?.breakdown?.claude?.weeklyUsd?.toFixed(4) ?? '0.0000'}</span>
|
|
281
|
-
</div>
|
|
282
|
-
<div style={{ display: 'flex', justifyContent: 'space-between', borderTop: '1px solid var(--border)', paddingTop: 6, marginTop: 4, fontSize: 11, color: 'var(--text-tertiary)' }}>
|
|
283
|
-
<span>Rolling Tokens:</span>
|
|
284
|
-
<span>{usage?.breakdown?.claude?.dailyTokens?.toLocaleString() || 0} (24h)</span>
|
|
285
|
-
</div>
|
|
286
|
-
</div>
|
|
287
|
-
</div>
|
|
288
|
-
|
|
289
|
-
{/* Codex */}
|
|
290
|
-
<div style={{ background: 'var(--bg-tertiary)', border: '1px solid var(--border)', padding: 16, borderRadius: 10, display: 'flex', flexDirection: 'column', gap: 12 }}>
|
|
291
|
-
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
|
292
|
-
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
|
|
293
|
-
<span style={{ fontSize: 18 }}>💻</span>
|
|
294
|
-
<div style={{ display: 'flex', flexDirection: 'column' }}>
|
|
295
|
-
<span style={{ fontSize: 13, fontWeight: 600, color: '#fff' }}>Codex CLI</span>
|
|
296
|
-
<span style={{ fontSize: 10, color: 'var(--text-tertiary)' }}>~/.codex/sessions</span>
|
|
297
|
-
</div>
|
|
298
|
-
</div>
|
|
299
|
-
<span className="badge" style={{ background: 'rgba(16, 185, 129, 0.1)', color: '#34d399', border: '1px solid rgba(16, 185, 129, 0.2)' }}>
|
|
300
|
-
OpenAI API
|
|
301
|
-
</span>
|
|
302
|
-
</div>
|
|
303
|
-
|
|
304
|
-
<div style={{ display: 'flex', flexDirection: 'column', gap: 6, fontSize: 12 }}>
|
|
305
|
-
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
|
306
|
-
<span style={{ color: 'var(--text-secondary)' }}>Daily Spend:</span>
|
|
307
|
-
<span style={{ fontWeight: 600, color: '#fff' }}>${usage?.breakdown?.codex?.dailyUsd?.toFixed(4) ?? '0.0000'}</span>
|
|
308
|
-
</div>
|
|
309
|
-
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
|
310
|
-
<span style={{ color: 'var(--text-secondary)' }}>Weekly Spend:</span>
|
|
311
|
-
<span style={{ fontWeight: 600, color: '#fff' }}>${usage?.breakdown?.codex?.weeklyUsd?.toFixed(4) ?? '0.0000'}</span>
|
|
312
|
-
</div>
|
|
313
|
-
<div style={{ display: 'flex', justifyContent: 'space-between', borderTop: '1px solid var(--border)', paddingTop: 6, marginTop: 4, fontSize: 11, color: 'var(--text-tertiary)' }}>
|
|
314
|
-
<span>Rolling Tokens:</span>
|
|
315
|
-
<span>{usage?.breakdown?.codex?.dailyTokens?.toLocaleString() || 0} (24h)</span>
|
|
316
|
-
</div>
|
|
317
|
-
</div>
|
|
318
|
-
</div>
|
|
319
|
-
{/* OpenRouter */}
|
|
320
|
-
<div style={{ background: 'var(--bg-tertiary)', border: '1px solid var(--border)', padding: 16, borderRadius: 10, display: 'flex', flexDirection: 'column', gap: 12 }}>
|
|
321
|
-
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
|
322
|
-
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
|
|
323
|
-
<span style={{ fontSize: 18 }}>🌐</span>
|
|
324
|
-
<span style={{ fontSize: 13, fontWeight: 600, color: '#fff' }}>OpenRouter</span>
|
|
325
|
-
</div>
|
|
326
|
-
<span className="badge" style={{ background: 'rgba(236, 72, 153, 0.1)', color: '#f472b6', border: '1px solid rgba(236, 72, 153, 0.2)' }}>
|
|
327
|
-
OR API
|
|
328
|
-
</span>
|
|
329
|
-
</div>
|
|
330
|
-
|
|
331
|
-
<div style={{ display: 'flex', flexDirection: 'column', gap: 6, fontSize: 12 }}>
|
|
332
|
-
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
|
333
|
-
<span style={{ color: 'var(--text-secondary)' }}>Daily Spend:</span>
|
|
334
|
-
<span style={{ fontWeight: 600, color: '#fff' }}>${usage?.breakdown?.openrouter?.dailyUsd?.toFixed(4) ?? '0.0000'}</span>
|
|
335
|
-
</div>
|
|
336
|
-
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
|
337
|
-
<span style={{ color: 'var(--text-secondary)' }}>Weekly Spend:</span>
|
|
338
|
-
<span style={{ fontWeight: 600, color: '#fff' }}>${usage?.breakdown?.openrouter?.weeklyUsd?.toFixed(4) ?? '0.0000'}</span>
|
|
339
|
-
</div>
|
|
340
|
-
<div style={{ display: 'flex', justifyContent: 'space-between', borderTop: '1px solid var(--border)', paddingTop: 6, marginTop: 4, fontSize: 11, color: 'var(--text-tertiary)' }}>
|
|
341
|
-
<span>Rolling Tokens:</span>
|
|
342
|
-
<span>{usage?.breakdown?.openrouter?.dailyTokens?.toLocaleString() || 0} (24h)</span>
|
|
343
|
-
</div>
|
|
344
|
-
</div>
|
|
345
|
-
</div>
|
|
346
|
-
|
|
347
|
-
{/* Tavily */}
|
|
348
|
-
<div style={{ background: 'var(--bg-tertiary)', border: '1px solid var(--border)', padding: 16, borderRadius: 10, display: 'flex', flexDirection: 'column', gap: 12 }}>
|
|
349
|
-
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
|
350
|
-
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
|
|
351
|
-
<span style={{ fontSize: 18 }}>🔎</span>
|
|
352
|
-
<span style={{ fontSize: 13, fontWeight: 600, color: '#fff' }}>Tavily Search</span>
|
|
353
|
-
</div>
|
|
354
|
-
<span className="badge" style={{ background: 'rgba(234, 179, 8, 0.1)', color: '#eab308', border: '1px solid rgba(234, 179, 8, 0.2)' }}>
|
|
355
|
-
Web API
|
|
356
|
-
</span>
|
|
357
|
-
</div>
|
|
358
|
-
|
|
359
|
-
<div style={{ display: 'flex', flexDirection: 'column', gap: 6, fontSize: 12 }}>
|
|
360
|
-
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
|
361
|
-
<span style={{ color: 'var(--text-secondary)' }}>Daily Spend:</span>
|
|
362
|
-
<span style={{ fontWeight: 600, color: '#fff' }}>${usage?.breakdown?.tavily?.dailyUsd?.toFixed(4) ?? '0.0000'}</span>
|
|
363
|
-
</div>
|
|
364
|
-
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
|
365
|
-
<span style={{ color: 'var(--text-secondary)' }}>Weekly Spend:</span>
|
|
366
|
-
<span style={{ fontWeight: 600, color: '#fff' }}>${usage?.breakdown?.tavily?.weeklyUsd?.toFixed(4) ?? '0.0000'}</span>
|
|
367
|
-
</div>
|
|
368
|
-
<div style={{ display: 'flex', justifyContent: 'space-between', borderTop: '1px solid var(--border)', paddingTop: 6, marginTop: 4, fontSize: 11, color: 'var(--text-tertiary)' }}>
|
|
369
|
-
<span>Rolling Tokens:</span>
|
|
370
|
-
<span>{usage?.breakdown?.tavily?.dailyTokens?.toLocaleString() || 0} (24h)</span>
|
|
371
|
-
</div>
|
|
372
|
-
</div>
|
|
373
|
-
</div>
|
|
374
|
-
|
|
375
|
-
{/* Brave */}
|
|
376
|
-
<div style={{ background: 'var(--bg-tertiary)', border: '1px solid var(--border)', padding: 16, borderRadius: 10, display: 'flex', flexDirection: 'column', gap: 12 }}>
|
|
377
|
-
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
|
378
|
-
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
|
|
379
|
-
<span style={{ fontSize: 18 }}>🦁</span>
|
|
380
|
-
<span style={{ fontSize: 13, fontWeight: 600, color: '#fff' }}>Brave Search</span>
|
|
381
|
-
</div>
|
|
382
|
-
<span className="badge" style={{ background: 'rgba(234, 179, 8, 0.1)', color: '#eab308', border: '1px solid rgba(234, 179, 8, 0.2)' }}>
|
|
383
|
-
Web API
|
|
384
|
-
</span>
|
|
385
|
-
</div>
|
|
386
|
-
|
|
387
|
-
<div style={{ display: 'flex', flexDirection: 'column', gap: 6, fontSize: 12 }}>
|
|
388
|
-
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
|
389
|
-
<span style={{ color: 'var(--text-secondary)' }}>Daily Spend:</span>
|
|
390
|
-
<span style={{ fontWeight: 600, color: '#fff' }}>${usage?.breakdown?.brave?.dailyUsd?.toFixed(4) ?? '0.0000'}</span>
|
|
391
|
-
</div>
|
|
392
|
-
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
|
393
|
-
<span style={{ color: 'var(--text-secondary)' }}>Weekly Spend:</span>
|
|
394
|
-
<span style={{ fontWeight: 600, color: '#fff' }}>${usage?.breakdown?.brave?.weeklyUsd?.toFixed(4) ?? '0.0000'}</span>
|
|
395
|
-
</div>
|
|
396
|
-
<div style={{ display: 'flex', justifyContent: 'space-between', borderTop: '1px solid var(--border)', paddingTop: 6, marginTop: 4, fontSize: 11, color: 'var(--text-tertiary)' }}>
|
|
397
|
-
<span>Rolling Tokens:</span>
|
|
398
|
-
<span>{usage?.breakdown?.brave?.dailyTokens?.toLocaleString() || 0} (24h)</span>
|
|
399
|
-
</div>
|
|
400
|
-
</div>
|
|
401
|
-
</div>
|
|
402
|
-
|
|
403
|
-
{/* Exa */}
|
|
404
|
-
<div style={{ background: 'var(--bg-tertiary)', border: '1px solid var(--border)', padding: 16, borderRadius: 10, display: 'flex', flexDirection: 'column', gap: 12 }}>
|
|
405
|
-
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
|
406
|
-
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
|
|
407
|
-
<span style={{ fontSize: 18 }}>✨</span>
|
|
408
|
-
<span style={{ fontSize: 13, fontWeight: 600, color: '#fff' }}>Exa Search</span>
|
|
409
|
-
</div>
|
|
410
|
-
<span className="badge" style={{ background: 'rgba(234, 179, 8, 0.1)', color: '#eab308', border: '1px solid rgba(234, 179, 8, 0.2)' }}>
|
|
411
|
-
Web API
|
|
412
|
-
</span>
|
|
413
|
-
</div>
|
|
414
|
-
|
|
415
|
-
<div style={{ display: 'flex', flexDirection: 'column', gap: 6, fontSize: 12 }}>
|
|
416
|
-
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
|
417
|
-
<span style={{ color: 'var(--text-secondary)' }}>Daily Spend:</span>
|
|
418
|
-
<span style={{ fontWeight: 600, color: '#fff' }}>${usage?.breakdown?.exa?.dailyUsd?.toFixed(4) ?? '0.0000'}</span>
|
|
419
|
-
</div>
|
|
420
|
-
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
|
421
|
-
<span style={{ color: 'var(--text-secondary)' }}>Weekly Spend:</span>
|
|
422
|
-
<span style={{ fontWeight: 600, color: '#fff' }}>${usage?.breakdown?.exa?.weeklyUsd?.toFixed(4) ?? '0.0000'}</span>
|
|
423
|
-
</div>
|
|
424
|
-
<div style={{ display: 'flex', justifyContent: 'space-between', borderTop: '1px solid var(--border)', paddingTop: 6, marginTop: 4, fontSize: 11, color: 'var(--text-tertiary)' }}>
|
|
425
|
-
<span>Rolling Tokens:</span>
|
|
426
|
-
<span>{usage?.breakdown?.exa?.dailyTokens?.toLocaleString() || 0} (24h)</span>
|
|
427
|
-
</div>
|
|
428
|
-
</div>
|
|
429
|
-
</div>
|
|
430
|
-
|
|
431
|
-
{/* Serper */}
|
|
432
|
-
<div style={{ background: 'var(--bg-tertiary)', border: '1px solid var(--border)', padding: 16, borderRadius: 10, display: 'flex', flexDirection: 'column', gap: 12 }}>
|
|
433
|
-
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
|
434
|
-
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
|
|
435
|
-
<span style={{ fontSize: 18 }}>🔍</span>
|
|
436
|
-
<span style={{ fontSize: 13, fontWeight: 600, color: '#fff' }}>Serper Search</span>
|
|
437
|
-
</div>
|
|
438
|
-
<span className="badge" style={{ background: 'rgba(234, 179, 8, 0.1)', color: '#eab308', border: '1px solid rgba(234, 179, 8, 0.2)' }}>
|
|
439
|
-
Web API
|
|
440
|
-
</span>
|
|
441
|
-
</div>
|
|
442
|
-
|
|
443
|
-
<div style={{ display: 'flex', flexDirection: 'column', gap: 6, fontSize: 12 }}>
|
|
444
|
-
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
|
445
|
-
<span style={{ color: 'var(--text-secondary)' }}>Daily Spend:</span>
|
|
446
|
-
<span style={{ fontWeight: 600, color: '#fff' }}>${usage?.breakdown?.serper?.dailyUsd?.toFixed(4) ?? '0.0000'}</span>
|
|
447
|
-
</div>
|
|
448
|
-
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
|
449
|
-
<span style={{ color: 'var(--text-secondary)' }}>Weekly Spend:</span>
|
|
450
|
-
<span style={{ fontWeight: 600, color: '#fff' }}>${usage?.breakdown?.serper?.weeklyUsd?.toFixed(4) ?? '0.0000'}</span>
|
|
451
|
-
</div>
|
|
452
|
-
<div style={{ display: 'flex', justifyContent: 'space-between', borderTop: '1px solid var(--border)', paddingTop: 6, marginTop: 4, fontSize: 11, color: 'var(--text-tertiary)' }}>
|
|
453
|
-
<span>Rolling Tokens:</span>
|
|
454
|
-
<span>{usage?.breakdown?.serper?.dailyTokens?.toLocaleString() || 0} (24h)</span>
|
|
455
|
-
</div>
|
|
456
|
-
</div>
|
|
457
|
-
</div>
|
|
458
|
-
|
|
459
|
-
</div>
|
|
460
|
-
</div>
|
|
461
|
-
|
|
462
|
-
{/* ── Timeseries Cost Chart ── */}
|
|
463
|
-
<div className="card" style={{ padding: 24, marginTop: 24, background: 'rgba(18, 18, 26, 0.6)', backdropFilter: 'blur(8px)', border: '1px solid var(--border)' }}>
|
|
464
|
-
<h3 style={{ fontSize: 14, fontWeight: 700, color: 'var(--text-tertiary)', textTransform: 'uppercase', letterSpacing: 0.5, borderBottom: '1px solid var(--border)', paddingBottom: 10, marginBottom: 20 }}>
|
|
465
|
-
30-Day Cost Trend
|
|
466
|
-
</h3>
|
|
467
|
-
{timeseriesData.length === 0 ? (
|
|
468
|
-
<div style={{ height: 300, display: 'flex', alignItems: 'center', justifyContent: 'center', color: 'var(--text-secondary)' }}>
|
|
469
|
-
No usage data available for the last 30 days.
|
|
470
|
-
</div>
|
|
471
|
-
) : (
|
|
472
|
-
<ResponsiveContainer width="100%" height={300}>
|
|
473
|
-
<AreaChart data={timeseriesData} margin={{ top: 5, right: 20, left: 0, bottom: 5 }}>
|
|
474
|
-
<defs>
|
|
475
|
-
<linearGradient id="gradGemini" x1="0" y1="0" x2="0" y2="1">
|
|
476
|
-
<stop offset="5%" stopColor="#3b82f6" stopOpacity={0.4} />
|
|
477
|
-
<stop offset="95%" stopColor="#3b82f6" stopOpacity={0} />
|
|
478
|
-
</linearGradient>
|
|
479
|
-
<linearGradient id="gradClaude" x1="0" y1="0" x2="0" y2="1">
|
|
480
|
-
<stop offset="5%" stopColor="#e17055" stopOpacity={0.4} />
|
|
481
|
-
<stop offset="95%" stopColor="#e17055" stopOpacity={0} />
|
|
482
|
-
</linearGradient>
|
|
483
|
-
<linearGradient id="gradOpenRouter" x1="0" y1="0" x2="0" y2="1">
|
|
484
|
-
<stop offset="5%" stopColor="#74b9ff" stopOpacity={0.4} />
|
|
485
|
-
<stop offset="95%" stopColor="#74b9ff" stopOpacity={0} />
|
|
486
|
-
</linearGradient>
|
|
487
|
-
<linearGradient id="gradCodex" x1="0" y1="0" x2="0" y2="1">
|
|
488
|
-
<stop offset="5%" stopColor="#00cec9" stopOpacity={0.4} />
|
|
489
|
-
<stop offset="95%" stopColor="#00cec9" stopOpacity={0} />
|
|
490
|
-
</linearGradient>
|
|
491
|
-
</defs>
|
|
492
|
-
<CartesianGrid strokeDasharray="3 3" stroke="rgba(255,255,255,0.04)" />
|
|
493
|
-
<XAxis dataKey="date" tick={{ fontSize: 11, fill: 'var(--text-secondary)' }} axisLine={{ stroke: 'var(--border)' }} tickLine={false} />
|
|
494
|
-
<YAxis tick={{ fontSize: 11, fill: 'var(--text-secondary)' }} axisLine={{ stroke: 'var(--border)' }} tickLine={false} tickFormatter={(v: number) => `$${v.toFixed(2)}`} />
|
|
495
|
-
<Tooltip
|
|
496
|
-
contentStyle={{ background: '#1a1a2e', border: '1px solid rgba(255,255,255,0.1)', borderRadius: 8, fontSize: 12, color: '#e8e8e8' }}
|
|
497
|
-
labelStyle={{ color: '#fff', fontWeight: 600, marginBottom: 4 }}
|
|
498
|
-
formatter={(value: unknown, name: unknown) => [`$${Number(value).toFixed(3)}`, String(name).charAt(0).toUpperCase() + String(name).slice(1)]}
|
|
499
|
-
/>
|
|
500
|
-
<Legend wrapperStyle={{ fontSize: 12, color: 'var(--text-secondary)' }} />
|
|
501
|
-
<Area type="monotone" dataKey="gemini" stackId="1" stroke="#3b82f6" fill="url(#gradGemini)" strokeWidth={2} />
|
|
502
|
-
<Area type="monotone" dataKey="claude" stackId="1" stroke="#e17055" fill="url(#gradClaude)" strokeWidth={2} />
|
|
503
|
-
<Area type="monotone" dataKey="openrouter" stackId="1" stroke="#74b9ff" fill="url(#gradOpenRouter)" strokeWidth={2} />
|
|
504
|
-
<Area type="monotone" dataKey="codex" stackId="1" stroke="#00cec9" fill="url(#gradCodex)" strokeWidth={2} />
|
|
505
|
-
</AreaChart>
|
|
506
|
-
</ResponsiveContainer>
|
|
507
|
-
)}
|
|
508
|
-
</div>
|
|
509
|
-
|
|
510
|
-
{/* ── Provider Surface Cards ── */}
|
|
511
|
-
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(220px, 1fr))', gap: 16, marginTop: 24 }}>
|
|
512
|
-
{([
|
|
513
|
-
{ name: 'Gemini', color: '#3b82f6', emoji: '♊', key: 'gemini' as const },
|
|
514
|
-
{ name: 'Claude', color: '#e17055', emoji: '🍊', key: 'claude' as const },
|
|
515
|
-
{ name: 'OpenRouter', color: '#74b9ff', emoji: '🌐', key: 'openrouter' as const },
|
|
516
|
-
{ name: 'Codex', color: '#00cec9', emoji: '💻', key: 'codex' as const },
|
|
517
|
-
{ name: 'Tavily', color: '#eab308', emoji: '🔎', key: 'tavily' as const },
|
|
518
|
-
{ name: 'Brave', color: '#eab308', emoji: '🦁', key: 'brave' as const },
|
|
519
|
-
{ name: 'Exa', color: '#eab308', emoji: '✨', key: 'exa' as const },
|
|
520
|
-
{ name: 'Serper', color: '#eab308', emoji: '🔍', key: 'serper' as const },
|
|
521
|
-
] as const).map((provider) => {
|
|
522
|
-
const data = usage?.breakdown?.[provider.key as keyof typeof usage.breakdown];
|
|
523
|
-
return (
|
|
524
|
-
<div key={provider.key} style={{
|
|
525
|
-
background: 'rgba(18, 18, 26, 0.6)',
|
|
526
|
-
backdropFilter: 'blur(12px)',
|
|
527
|
-
border: '1px solid var(--border)',
|
|
528
|
-
borderTop: `4px solid ${provider.color}`,
|
|
529
|
-
borderRadius: 10,
|
|
530
|
-
padding: 20,
|
|
531
|
-
display: 'flex',
|
|
532
|
-
flexDirection: 'column',
|
|
533
|
-
gap: 12,
|
|
534
|
-
transition: 'transform 0.2s ease, box-shadow 0.2s ease',
|
|
535
|
-
}}
|
|
536
|
-
onMouseEnter={(e) => { (e.currentTarget as HTMLDivElement).style.transform = 'translateY(-2px)'; (e.currentTarget as HTMLDivElement).style.boxShadow = `0 8px 24px ${provider.color}22`; }}
|
|
537
|
-
onMouseLeave={(e) => { (e.currentTarget as HTMLDivElement).style.transform = 'translateY(0)'; (e.currentTarget as HTMLDivElement).style.boxShadow = 'none'; }}
|
|
538
|
-
>
|
|
539
|
-
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
|
|
540
|
-
<span style={{ fontSize: 20 }}>{provider.emoji}</span>
|
|
541
|
-
<span style={{ fontSize: 14, fontWeight: 700, color: '#fff' }}>{provider.name}</span>
|
|
542
|
-
</div>
|
|
543
|
-
<div style={{ display: 'flex', flexDirection: 'column', gap: 6, fontSize: 12 }}>
|
|
544
|
-
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
|
545
|
-
<span style={{ color: 'var(--text-secondary)' }}>Daily</span>
|
|
546
|
-
<span style={{ fontWeight: 600, color: provider.color }}>${data?.dailyUsd?.toFixed(4) ?? '0.0000'}</span>
|
|
547
|
-
</div>
|
|
548
|
-
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
|
549
|
-
<span style={{ color: 'var(--text-secondary)' }}>Weekly</span>
|
|
550
|
-
<span style={{ fontWeight: 600, color: provider.color }}>${data?.weeklyUsd?.toFixed(4) ?? '0.0000'}</span>
|
|
551
|
-
</div>
|
|
552
|
-
{data?.dailyTokens != null && (
|
|
553
|
-
<div style={{ display: 'flex', justifyContent: 'space-between', borderTop: '1px solid var(--border)', paddingTop: 6, marginTop: 2, fontSize: 11, color: 'var(--text-tertiary)' }}>
|
|
554
|
-
<span>Tokens (24h)</span>
|
|
555
|
-
<span>{data.dailyTokens.toLocaleString()}</span>
|
|
556
|
-
</div>
|
|
557
|
-
)}
|
|
558
|
-
</div>
|
|
559
|
-
</div>
|
|
560
|
-
);
|
|
561
|
-
})}
|
|
562
|
-
</div>
|
|
563
|
-
|
|
564
|
-
{/* ── Per-Model Token Breakdown Table ── */}
|
|
565
|
-
<div className="card" style={{ padding: 24, marginTop: 24, background: 'rgba(18, 18, 26, 0.6)', backdropFilter: 'blur(8px)', border: '1px solid var(--border)' }}>
|
|
566
|
-
<h3 style={{ fontSize: 14, fontWeight: 700, color: 'var(--text-tertiary)', textTransform: 'uppercase', letterSpacing: 0.5, borderBottom: '1px solid var(--border)', paddingBottom: 10, marginBottom: 16 }}>
|
|
567
|
-
Per-Model Breakdown
|
|
568
|
-
</h3>
|
|
569
|
-
<div style={{ overflowX: 'auto' }}>
|
|
570
|
-
<table style={{ width: '100%', borderCollapse: 'collapse', fontSize: 12 }}>
|
|
571
|
-
<thead>
|
|
572
|
-
<tr style={{ borderBottom: '1px solid var(--border)' }}>
|
|
573
|
-
{['Provider', 'Daily ($)', 'Weekly ($)', 'Daily Tokens', 'Weekly Tokens'].map((h) => (
|
|
574
|
-
<th key={h} style={{ textAlign: 'left', padding: '10px 12px', fontSize: 11, fontWeight: 700, color: 'var(--text-tertiary)', textTransform: 'uppercase', letterSpacing: 0.5 }}>{h}</th>
|
|
575
|
-
))}
|
|
576
|
-
</tr>
|
|
577
|
-
</thead>
|
|
578
|
-
<tbody>
|
|
579
|
-
{([
|
|
580
|
-
{ name: 'Gemini', key: 'gemini' as const, color: '#3b82f6' },
|
|
581
|
-
{ name: 'Claude', key: 'claude' as const, color: '#e17055' },
|
|
582
|
-
{ name: 'Codex', key: 'codex' as const, color: '#00cec9' },
|
|
583
|
-
{ name: 'OpenRouter', key: 'openrouter' as const, color: '#74b9ff' },
|
|
584
|
-
] as const).map((row, idx) => {
|
|
585
|
-
const d = row.key === 'openrouter' ? usage?.breakdown?.openrouter : usage?.breakdown?.[row.key];
|
|
586
|
-
return (
|
|
587
|
-
<tr key={row.key} style={{ background: idx % 2 === 1 ? 'rgba(255,255,255,0.02)' : 'transparent', borderBottom: '1px solid var(--border)' }}>
|
|
588
|
-
<td style={{ padding: '10px 12px', fontWeight: 600, color: row.color }}>{row.name}</td>
|
|
589
|
-
<td style={{ padding: '10px 12px', color: '#fff' }}>${d?.dailyUsd?.toFixed(4) ?? '0.0000'}</td>
|
|
590
|
-
<td style={{ padding: '10px 12px', color: '#fff' }}>${d?.weeklyUsd?.toFixed(4) ?? '0.0000'}</td>
|
|
591
|
-
<td style={{ padding: '10px 12px', color: 'var(--text-secondary)' }}>{d?.dailyTokens?.toLocaleString() || '—'}</td>
|
|
592
|
-
<td style={{ padding: '10px 12px', color: 'var(--text-secondary)' }}>{d?.weeklyTokens?.toLocaleString() || '—'}</td>
|
|
593
|
-
</tr>
|
|
594
|
-
);
|
|
595
|
-
})}
|
|
596
|
-
{/* Totals row */}
|
|
597
|
-
<tr style={{ borderTop: '2px solid var(--border)', background: 'rgba(108, 92, 231, 0.05)' }}>
|
|
598
|
-
<td style={{ padding: '10px 12px', fontWeight: 700, color: '#fff' }}>Total</td>
|
|
599
|
-
<td style={{ padding: '10px 12px', fontWeight: 700, color: '#fff' }}>
|
|
600
|
-
${(
|
|
601
|
-
(usage?.breakdown?.gemini?.dailyUsd || 0) +
|
|
602
|
-
(usage?.breakdown?.claude?.dailyUsd || 0) +
|
|
603
|
-
(usage?.breakdown?.codex?.dailyUsd || 0) +
|
|
604
|
-
(usage?.breakdown?.openrouter?.dailyUsd || 0)
|
|
605
|
-
).toFixed(4)}
|
|
606
|
-
</td>
|
|
607
|
-
<td style={{ padding: '10px 12px', fontWeight: 700, color: '#fff' }}>
|
|
608
|
-
${(
|
|
609
|
-
(usage?.breakdown?.gemini?.weeklyUsd || 0) +
|
|
610
|
-
(usage?.breakdown?.claude?.weeklyUsd || 0) +
|
|
611
|
-
(usage?.breakdown?.codex?.weeklyUsd || 0) +
|
|
612
|
-
(usage?.breakdown?.openrouter?.weeklyUsd || 0)
|
|
613
|
-
).toFixed(4)}
|
|
614
|
-
</td>
|
|
615
|
-
<td style={{ padding: '10px 12px', fontWeight: 700, color: 'var(--text-secondary)' }}>
|
|
616
|
-
{(
|
|
617
|
-
(usage?.breakdown?.gemini?.dailyTokens || 0) +
|
|
618
|
-
(usage?.breakdown?.claude?.dailyTokens || 0) +
|
|
619
|
-
(usage?.breakdown?.codex?.dailyTokens || 0) +
|
|
620
|
-
(usage?.breakdown?.openrouter?.dailyTokens || 0)
|
|
621
|
-
).toLocaleString()}
|
|
622
|
-
</td>
|
|
623
|
-
<td style={{ padding: '10px 12px', fontWeight: 700, color: 'var(--text-secondary)' }}>
|
|
624
|
-
{(
|
|
625
|
-
(usage?.breakdown?.gemini?.weeklyTokens || 0) +
|
|
626
|
-
(usage?.breakdown?.claude?.weeklyTokens || 0) +
|
|
627
|
-
(usage?.breakdown?.codex?.weeklyTokens || 0) +
|
|
628
|
-
(usage?.breakdown?.openrouter?.weeklyTokens || 0)
|
|
629
|
-
).toLocaleString()}
|
|
630
|
-
</td>
|
|
631
|
-
</tr>
|
|
632
|
-
</tbody>
|
|
633
|
-
</table>
|
|
634
|
-
</div>
|
|
635
|
-
</div>
|
|
636
|
-
|
|
637
|
-
{/* ── Provider-Reported Spend ──
|
|
638
|
-
Everything above is estimated locally: observed tokens × a hardcoded
|
|
639
|
-
price table. This panel is what the providers themselves bill. */}
|
|
640
|
-
<div className="card" style={{ padding: 24, marginTop: 24, background: 'rgba(18, 18, 26, 0.6)', backdropFilter: 'blur(8px)', border: '1px solid var(--border)' }}>
|
|
641
|
-
<div style={{ display: 'flex', alignItems: 'baseline', justifyContent: 'space-between', borderBottom: '1px solid var(--border)', paddingBottom: 10, marginBottom: 16 }}>
|
|
642
|
-
<h3 style={{ fontSize: 14, fontWeight: 700, color: 'var(--text-tertiary)', textTransform: 'uppercase', letterSpacing: 0.5, margin: 0 }}>
|
|
643
|
-
Reported by Provider
|
|
644
|
-
</h3>
|
|
645
|
-
<div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
|
|
646
|
-
{providerUsage?.fetched_at && (
|
|
647
|
-
<span style={{ fontSize: 11, color: 'var(--text-tertiary)' }}>
|
|
648
|
-
{new Date(providerUsage.fetched_at).toLocaleString()}
|
|
649
|
-
{providerUsage.from_cache ? ' (cached)' : ''}
|
|
650
|
-
</span>
|
|
651
|
-
)}
|
|
652
|
-
<button
|
|
653
|
-
type="button"
|
|
654
|
-
onClick={() => { void loadProviderUsage(true); }}
|
|
655
|
-
disabled={providerLoading}
|
|
656
|
-
style={{ fontSize: 11, padding: '4px 10px', borderRadius: 6, border: '1px solid var(--border)', background: 'transparent', color: 'var(--text-secondary)', cursor: providerLoading ? 'wait' : 'pointer' }}
|
|
657
|
-
>
|
|
658
|
-
{providerLoading ? 'Refreshing…' : 'Refresh'}
|
|
659
|
-
</button>
|
|
660
|
-
</div>
|
|
661
|
-
</div>
|
|
662
|
-
|
|
663
|
-
{providerError && (
|
|
664
|
-
<div style={{ fontSize: 12, color: 'var(--error)', marginBottom: 12 }}>{providerError}</div>
|
|
665
|
-
)}
|
|
666
|
-
|
|
667
|
-
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(260px, 1fr))', gap: 16 }}>
|
|
668
|
-
{Object.entries(providerUsage?.providers || {}).map(([id, p]) => (
|
|
669
|
-
<div key={id} style={{
|
|
670
|
-
border: '1px solid var(--border)',
|
|
671
|
-
borderLeft: `3px solid ${p.state === 'ok' ? 'var(--success)' : p.state === 'error' ? 'var(--error)' : 'var(--warning)'}`,
|
|
672
|
-
borderRadius: 8,
|
|
673
|
-
padding: 16,
|
|
674
|
-
}}>
|
|
675
|
-
<div style={{ fontSize: 13, fontWeight: 700, color: '#fff', marginBottom: 8 }}>{p.label}</div>
|
|
676
|
-
{p.state === 'ok' ? (
|
|
677
|
-
<>
|
|
678
|
-
<div style={{ fontSize: 22, fontWeight: 700, color: '#fff' }}>
|
|
679
|
-
${(p.total_cost ?? 0).toFixed(2)}
|
|
680
|
-
</div>
|
|
681
|
-
<div style={{ fontSize: 11, color: 'var(--text-tertiary)', marginTop: 4 }}>
|
|
682
|
-
{/* Never label a lifetime figure as a windowed one. */}
|
|
683
|
-
{p.lifetime ? 'lifetime to date' : `last ${providerUsage?.window_days ?? 30} days`}
|
|
684
|
-
{typeof p.balance === 'number' ? ` · $${p.balance.toFixed(2)} remaining` : ''}
|
|
685
|
-
</div>
|
|
686
|
-
</>
|
|
687
|
-
) : (
|
|
688
|
-
<div style={{ fontSize: 11, color: 'var(--text-secondary)', lineHeight: 1.5 }}>
|
|
689
|
-
{p.reason}
|
|
690
|
-
</div>
|
|
691
|
-
)}
|
|
692
|
-
</div>
|
|
693
|
-
))}
|
|
694
|
-
{!providerUsage && !providerError && (
|
|
695
|
-
<div style={{ fontSize: 12, color: 'var(--text-tertiary)' }}>Loading provider billing data…</div>
|
|
696
|
-
)}
|
|
697
|
-
</div>
|
|
698
|
-
</div>
|
|
699
|
-
|
|
700
|
-
</div>
|
|
701
|
-
);
|
|
702
|
-
}
|