total-recall-brain 3.1.2 → 3.2.2

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.
Files changed (63) hide show
  1. package/extension/background.js +99 -0
  2. package/extension/content-script.css +16 -0
  3. package/extension/content-script.js +134 -0
  4. package/extension/icons/README.md +27 -0
  5. package/extension/icons/icon-128.png +0 -0
  6. package/extension/icons/icon-16.png +0 -0
  7. package/extension/icons/icon-48.png +0 -0
  8. package/extension/lib/brain-client.js +47 -0
  9. package/extension/manifest.json +39 -0
  10. package/extension/options/options.css +218 -0
  11. package/extension/options/options.html +72 -0
  12. package/extension/options/options.js +99 -0
  13. package/extension/popup/popup.css +221 -0
  14. package/extension/popup/popup.html +44 -0
  15. package/extension/popup/popup.js +107 -0
  16. package/extension/sidepanel/sidepanel.css +340 -0
  17. package/extension/sidepanel/sidepanel.html +38 -0
  18. package/extension/sidepanel/sidepanel.js +229 -0
  19. package/frontend/README.md +73 -0
  20. package/frontend/index.html +17 -0
  21. package/frontend/package.json +31 -0
  22. package/frontend/src/App.css +2 -0
  23. package/frontend/src/App.tsx +390 -0
  24. package/frontend/src/api.ts +688 -0
  25. package/frontend/src/assets/hero.png +0 -0
  26. package/frontend/src/assets/react.svg +1 -0
  27. package/frontend/src/assets/vite.svg +1 -0
  28. package/frontend/src/components/BrainSelector.tsx +272 -0
  29. package/frontend/src/components/DaemonLogsTab.tsx +54 -0
  30. package/frontend/src/components/Graph3D.tsx +850 -0
  31. package/frontend/src/components/MarkdownUtils.tsx +99 -0
  32. package/frontend/src/components/ResearchAgendaTab.tsx +707 -0
  33. package/frontend/src/components/TaskQueueTab.tsx +185 -0
  34. package/frontend/src/index.css +1589 -0
  35. package/frontend/src/main.tsx +10 -0
  36. package/frontend/src/pages/ApiKeysPage.tsx +288 -0
  37. package/frontend/src/pages/AutomationsPage.tsx +354 -0
  38. package/frontend/src/pages/ChatPage.tsx +831 -0
  39. package/frontend/src/pages/DeploymentsPage.tsx +275 -0
  40. package/frontend/src/pages/FilesPage.tsx +384 -0
  41. package/frontend/src/pages/HealthPage.tsx +91 -0
  42. package/frontend/src/pages/IntegrationsPage.tsx +382 -0
  43. package/frontend/src/pages/LoginPage.tsx +237 -0
  44. package/frontend/src/pages/MemoryPage.tsx +1192 -0
  45. package/frontend/src/pages/SandboxPage.tsx +113 -0
  46. package/frontend/src/pages/SettingsPage.tsx +662 -0
  47. package/frontend/src/pages/SkillsPage.tsx +618 -0
  48. package/frontend/src/pages/TasksPage.tsx +392 -0
  49. package/frontend/src/pages/UsagePage.tsx +260 -0
  50. package/frontend/src/types.ts +163 -0
  51. package/frontend/tsconfig.app.json +25 -0
  52. package/frontend/tsconfig.json +7 -0
  53. package/frontend/tsconfig.node.json +24 -0
  54. package/frontend/vite.config.ts +24 -0
  55. package/package.json +8 -1
  56. package/scaffold/.agent/skills/total-recall/memory-vault/concepts/cli-help-reference.md +35 -8
  57. package/scaffold/.agent/skills/total-recall/memory-vault/invariants/operating-instructions.md +13 -8
  58. package/scaffold/.agent/skills/total-recall/memory-vault/preferences/always-websearch-gap.md +28 -1
  59. package/scaffold/.agent/skills/total-recall/memory-vault/preferences/topic-research-sop.md +28 -1
  60. package/scaffold/.agent/skills/total-recall/skills/ssss/references/ssss-spec.md +830 -81
  61. package/src/cli/backup.mjs +172 -1
  62. package/src/cli/setup.mjs +24 -0
  63. package/src/server/rest.mjs +56 -7
@@ -0,0 +1,392 @@
1
+ import { useState, useCallback, useEffect } from 'react'
2
+ import {
3
+ listTasks,
4
+ createTask,
5
+ listResearch,
6
+ createResearch,
7
+ fetchLogs,
8
+ triggerRecompile,
9
+ readMemory
10
+ } from '../api'
11
+ import type { Task, MemoryNode } from '../types'
12
+ import TaskQueueTab from '../components/TaskQueueTab'
13
+ import ResearchAgendaTab from '../components/ResearchAgendaTab'
14
+ import type { ResearchItem } from '../components/ResearchAgendaTab'
15
+ import DaemonLogsTab from '../components/DaemonLogsTab'
16
+
17
+ export default function TasksPage() {
18
+ const [tasks, setTasks] = useState<Task[]>([])
19
+ const [researchItems, setResearchItems] = useState<ResearchItem[]>([])
20
+ const [researchCounts, setResearchCounts] = useState({ pending: 0, in_progress: 0, done: 0, failed: 0 })
21
+ const [logs, setLogs] = useState('(loading live cognitive stream...)')
22
+ const [activeTab, setActiveTab] = useState<'tasks' | 'research' | 'logs'>('tasks')
23
+
24
+ const [loading, setLoading] = useState(true)
25
+ const [error, setError] = useState('')
26
+ const [recompiling, setRecompiling] = useState(false)
27
+ const [actionSuccess, setActionSuccess] = useState('')
28
+
29
+ // Expanded research and lazy-loaded nodes
30
+ const [expandedResearchId, setExpandedResearchId] = useState<string | null>(null)
31
+ const [loadedDiscoveries, setLoadedDiscoveries] = useState<Record<string, MemoryNode | null>>({})
32
+ const [loadingNodeSlugs, setLoadingNodeSlugs] = useState<Record<string, boolean>>({})
33
+
34
+ const handleToggleExpand = async (item: ResearchItem) => {
35
+ if (expandedResearchId === item.id) {
36
+ setExpandedResearchId(null)
37
+ return
38
+ }
39
+
40
+ setExpandedResearchId(item.id)
41
+
42
+ if (item.node_slug && !loadedDiscoveries[item.node_slug] && !loadingNodeSlugs[item.node_slug]) {
43
+ setLoadingNodeSlugs(prev => ({ ...prev, [item.node_slug!]: true }))
44
+ try {
45
+ const node = await readMemory(item.node_slug)
46
+ setLoadedDiscoveries(prev => ({ ...prev, [item.node_slug!]: node }))
47
+ } catch (e) {
48
+ console.error('Failed to load memory node for research', e)
49
+ } finally {
50
+ setLoadingNodeSlugs(prev => ({ ...prev, [item.node_slug!]: false }))
51
+ }
52
+ }
53
+ }
54
+
55
+ // Task creation form state
56
+ const [showTaskForm, setShowTaskForm] = useState(false)
57
+ const [taskCategory, setTaskCategory] = useState('fact-seeker')
58
+ const [taskTarget, setTaskTarget] = useState('')
59
+ const [taskBody, setTaskBody] = useState('')
60
+ const [taskSubmitting, setTaskSubmitting] = useState(false)
61
+
62
+ // Research creation form state
63
+ const [showResearchForm, setShowResearchForm] = useState(false)
64
+ const [researchTopic, setResearchTopic] = useState('')
65
+ const [researchPriority, setResearchPriority] = useState<'low' | 'medium' | 'high' | 'critical'>('medium')
66
+ const [researchNotes, setResearchNotes] = useState('')
67
+ const [researchSubmitting, setResearchSubmitting] = useState(false)
68
+
69
+ // Fetch Scheduler Tasks
70
+ const fetchTasks = useCallback(async () => {
71
+ try {
72
+ const data = await listTasks()
73
+ setTasks(data || [])
74
+ setError('')
75
+ } catch (e) {
76
+ setError((e as Error).message)
77
+ }
78
+ }, [])
79
+
80
+ // Fetch Research Agenda
81
+ const fetchResearch = useCallback(async () => {
82
+ try {
83
+ const res = await listResearch('all')
84
+ setResearchItems(res.items || [])
85
+ setResearchCounts(res.counts || { pending: 0, in_progress: 0, done: 0, failed: 0 })
86
+ setError('')
87
+ } catch (e) {
88
+ setError((e as Error).message)
89
+ }
90
+ }, [])
91
+
92
+ // Fetch Daemon (Cognitive Engine) Logs
93
+ const fetchDaemonLogs = useCallback(async () => {
94
+ try {
95
+ const data = await fetchLogs('daemon')
96
+ setLogs(data.content || '(no logs streamed yet)')
97
+ } catch (e) {
98
+ setLogs(`Error connecting to cognitive logs: ${(e as Error).message}`)
99
+ }
100
+ }, [])
101
+
102
+ // Combined Refresh
103
+ const refreshAll = useCallback(async () => {
104
+ setLoading(true)
105
+ await Promise.all([fetchTasks(), fetchResearch(), fetchDaemonLogs()])
106
+ setLoading(false)
107
+ }, [fetchTasks, fetchResearch, fetchDaemonLogs])
108
+
109
+ // Polling loop
110
+ useEffect(() => {
111
+ const init = async () => {
112
+ await Promise.all([fetchTasks(), fetchResearch(), fetchDaemonLogs()])
113
+ setLoading(false)
114
+ }
115
+ void init()
116
+
117
+ const interval = setInterval(() => {
118
+ void fetchTasks()
119
+ void fetchResearch()
120
+ void fetchDaemonLogs()
121
+ }, 3000)
122
+
123
+ return () => clearInterval(interval)
124
+ }, [fetchTasks, fetchResearch, fetchDaemonLogs])
125
+
126
+ // Trigger Brain Recompilation
127
+ const handleRecompile = async () => {
128
+ setRecompiling(true)
129
+ setError('')
130
+ setActionSuccess('')
131
+ try {
132
+ const res = await triggerRecompile()
133
+ setActionSuccess(res.message || 'Brain surface recompiled successfully!')
134
+ setTimeout(() => setActionSuccess(''), 4000)
135
+ void refreshAll()
136
+ } catch (e) {
137
+ setError((e as Error).message)
138
+ } finally {
139
+ setRecompiling(false)
140
+ }
141
+ }
142
+
143
+ // Seed Autonomous Gaps to fill the queue
144
+ const handleSeedGaps = async () => {
145
+ setError('')
146
+ setActionSuccess('')
147
+ try {
148
+ await createTask(
149
+ 'fact-seeker',
150
+ 'Audit latest Chrome Extension MV3 API updates',
151
+ 'Verify if chrome.declarativeNetRequest rulesets have new declarative properties.'
152
+ )
153
+ await createTask(
154
+ 'post-mortem-daily',
155
+ 'Ingest yesterday\'s IDE compile session logs',
156
+ 'Analyze TypeScript build latency and suggest performance optimizations.'
157
+ )
158
+ await createTask(
159
+ 'clarity-rewriter',
160
+ 'Harden security.yml authentication encryption',
161
+ 'Enforce bcrypt rounds to 12 and verify cross-origin session domain locks.'
162
+ )
163
+
164
+ await createResearch(
165
+ 'TypeScript 5.4 advanced mapped types and template literal constraints',
166
+ 'high',
167
+ 'Evaluate mapped type performance on large monorepos.'
168
+ )
169
+ await createResearch(
170
+ 'Glassmorphism backdrop-filter performance optimizations on mobile Safari',
171
+ 'medium',
172
+ 'Prevent GPU compositing lag by limiting blur radius.'
173
+ )
174
+ await createResearch(
175
+ 'Local LLM quantization (GGUF vs EXL2) memory efficiency on macOS',
176
+ 'high',
177
+ 'Check metal buffer usage in Ollama backend models.'
178
+ )
179
+
180
+ setActionSuccess('Autonomous gaps seeded successfully! Task queue and research agenda are now full.')
181
+ setTimeout(() => setActionSuccess(''), 5000)
182
+ void refreshAll()
183
+ } catch (e) {
184
+ setError((e as Error).message)
185
+ }
186
+ }
187
+
188
+ // Create Task Handler
189
+ const handleCreateTask = async (e: React.FormEvent) => {
190
+ e.preventDefault()
191
+ if (!taskTarget.trim()) return
192
+ setTaskSubmitting(true)
193
+ setError('')
194
+ try {
195
+ await createTask(taskCategory, taskTarget.trim(), taskBody.trim())
196
+ setTaskTarget('')
197
+ setTaskBody('')
198
+ setShowTaskForm(false)
199
+ setActionSuccess('Task enqueued successfully!')
200
+ setTimeout(() => setActionSuccess(''), 3000)
201
+ void fetchTasks()
202
+ } catch (err) {
203
+ setError((err as Error).message)
204
+ } finally {
205
+ setTaskSubmitting(false)
206
+ }
207
+ }
208
+
209
+ // Create Research Handler
210
+ const handleCreateResearch = async (e: React.FormEvent) => {
211
+ e.preventDefault()
212
+ if (!researchTopic.trim()) return
213
+ setResearchSubmitting(true)
214
+ setError('')
215
+ try {
216
+ await createResearch(researchTopic.trim(), researchPriority, researchNotes.trim() || undefined)
217
+ setResearchTopic('')
218
+ setResearchNotes('')
219
+ setShowResearchForm(false)
220
+ setActionSuccess('Research project enqueued successfully!')
221
+ setTimeout(() => setActionSuccess(''), 3000)
222
+ void fetchResearch()
223
+ } catch (err) {
224
+ setError((err as Error).message)
225
+ } finally {
226
+ setResearchSubmitting(false)
227
+ }
228
+ }
229
+
230
+ return (
231
+ <div className="page">
232
+ <div className="page-header" style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', flexWrap: 'wrap', gap: 16 }}>
233
+ <div>
234
+ <h1>Cognitive Dashboard</h1>
235
+ <p>Real-time background reasoning engine, autonomous research, and active schedulers</p>
236
+ </div>
237
+
238
+ <div style={{ display: 'flex', gap: 10 }}>
239
+ <button
240
+ className="btn btn-ghost btn-sm"
241
+ onClick={handleSeedGaps}
242
+ style={{
243
+ border: '1px solid rgba(59, 130, 246, 0.4)',
244
+ background: 'rgba(59, 130, 246, 0.05)',
245
+ color: '#60a5fa'
246
+ }}
247
+ >
248
+ 🔥 Seed Gaps
249
+ </button>
250
+
251
+ <button
252
+ className="btn btn-sm"
253
+ onClick={handleRecompile}
254
+ disabled={recompiling}
255
+ style={{
256
+ background: recompiling ? 'var(--bg-secondary)' : 'linear-gradient(135deg, #3b82f6, #1d4ed8)',
257
+ color: '#ffffff',
258
+ border: 'none',
259
+ cursor: recompiling ? 'not-allowed' : 'pointer'
260
+ }}
261
+ >
262
+ {recompiling ? (
263
+ <span style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
264
+ <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" style={{ animation: 'spin 1s linear infinite' }}>
265
+ <path d="M21 12a9 9 0 1 1-6.219-8.56"/>
266
+ </svg>
267
+ Compiling...
268
+ </span>
269
+ ) : 'Recompile Surface'}
270
+ </button>
271
+ </div>
272
+ </div>
273
+
274
+ {error && <div className="badge badge-error" style={{ marginBottom: 16, display: 'inline-flex' }}>⚠️ {error}</div>}
275
+ {actionSuccess && <div className="badge badge-success" style={{ marginBottom: 16, display: 'inline-flex', background: 'rgba(16, 185, 129, 0.1)', border: '1px solid #10b981', color: '#34d399' }}>✓ {actionSuccess}</div>}
276
+
277
+ {/* Tabs Layout */}
278
+ <div style={{
279
+ display: 'flex',
280
+ borderBottom: '1px solid var(--border)',
281
+ marginBottom: 20,
282
+ gap: 20,
283
+ }}>
284
+ <button
285
+ onClick={() => setActiveTab('tasks')}
286
+ style={{
287
+ background: 'none',
288
+ border: 'none',
289
+ borderBottom: activeTab === 'tasks' ? '2px solid #3b82f6' : '2px solid transparent',
290
+ color: activeTab === 'tasks' ? 'var(--text-primary)' : 'var(--text-tertiary)',
291
+ padding: '10px 4px',
292
+ cursor: 'pointer',
293
+ fontSize: 14,
294
+ fontWeight: 500,
295
+ display: 'flex',
296
+ alignItems: 'center',
297
+ gap: 8
298
+ }}
299
+ >
300
+ 📅 Task Queue
301
+ <span className="badge badge-accent" style={{ fontSize: 10, padding: '2px 6px' }}>{tasks.length}</span>
302
+ </button>
303
+
304
+ <button
305
+ onClick={() => setActiveTab('research')}
306
+ style={{
307
+ background: 'none',
308
+ border: 'none',
309
+ borderBottom: activeTab === 'research' ? '2px solid #3b82f6' : '2px solid transparent',
310
+ color: activeTab === 'research' ? 'var(--text-primary)' : 'var(--text-tertiary)',
311
+ padding: '10px 4px',
312
+ cursor: 'pointer',
313
+ fontSize: 14,
314
+ fontWeight: 500,
315
+ display: 'flex',
316
+ alignItems: 'center',
317
+ gap: 8
318
+ }}
319
+ >
320
+ 🔬 Research Agenda
321
+ <span className="badge" style={{ fontSize: 10, padding: '2px 6px', background: 'rgba(16, 185, 129, 0.1)', color: '#34d399', border: '1px solid rgba(16, 185, 129, 0.2)' }}>
322
+ {researchCounts.pending + researchCounts.in_progress}
323
+ </span>
324
+ </button>
325
+
326
+ <button
327
+ onClick={() => setActiveTab('logs')}
328
+ style={{
329
+ background: 'none',
330
+ border: 'none',
331
+ borderBottom: activeTab === 'logs' ? '2px solid #3b82f6' : '2px solid transparent',
332
+ color: activeTab === 'logs' ? 'var(--text-primary)' : 'var(--text-tertiary)',
333
+ padding: '10px 4px',
334
+ cursor: 'pointer',
335
+ fontSize: 14,
336
+ fontWeight: 500,
337
+ display: 'flex',
338
+ alignItems: 'center',
339
+ gap: 8
340
+ }}
341
+ >
342
+ 📟 Live Brain Stream
343
+ <span className="pulse" style={{ background: '#10b981', width: 6, height: 6, borderRadius: '50%' }} />
344
+ </button>
345
+ </div>
346
+
347
+ {/* Tab Panels */}
348
+ {activeTab === 'tasks' && (
349
+ <TaskQueueTab
350
+ tasks={tasks}
351
+ loading={loading}
352
+ showTaskForm={showTaskForm}
353
+ setShowTaskForm={setShowTaskForm}
354
+ taskCategory={taskCategory}
355
+ setTaskCategory={setTaskCategory}
356
+ taskTarget={taskTarget}
357
+ setTaskTarget={setTaskTarget}
358
+ taskBody={taskBody}
359
+ setTaskBody={setTaskBody}
360
+ taskSubmitting={taskSubmitting}
361
+ handleCreateTask={handleCreateTask}
362
+ />
363
+ )}
364
+
365
+ {activeTab === 'research' && (
366
+ <ResearchAgendaTab
367
+ researchItems={researchItems}
368
+ loading={loading}
369
+ showResearchForm={showResearchForm}
370
+ setShowResearchForm={setShowResearchForm}
371
+ researchTopic={researchTopic}
372
+ setResearchTopic={setResearchTopic}
373
+ researchPriority={researchPriority}
374
+ setResearchPriority={setResearchPriority}
375
+ researchNotes={researchNotes}
376
+ setResearchNotes={setResearchNotes}
377
+ researchSubmitting={researchSubmitting}
378
+ handleCreateResearch={handleCreateResearch}
379
+ expandedResearchId={expandedResearchId}
380
+ handleToggleExpand={handleToggleExpand}
381
+ loadedDiscoveries={loadedDiscoveries}
382
+ loadingNodeSlugs={loadingNodeSlugs}
383
+ refreshResearch={fetchResearch}
384
+ />
385
+ )}
386
+
387
+ {activeTab === 'logs' && (
388
+ <DaemonLogsTab logs={logs} activeTab={activeTab} />
389
+ )}
390
+ </div>
391
+ )
392
+ }
@@ -0,0 +1,260 @@
1
+ import { useState, useEffect } from 'react';
2
+ import { fetchUsageStats, fetchConfigJson } from '../api';
3
+ import type { ConfigJson } from '../types';
4
+
5
+ interface UsageBreakdown {
6
+ dailyUsd: number;
7
+ weeklyUsd: number;
8
+ dailyTokens?: number;
9
+ weeklyTokens?: number;
10
+ }
11
+
12
+ interface UsageData {
13
+ timestamp: string;
14
+ dailyUsd: number;
15
+ weeklyUsd: number;
16
+ breakdown: {
17
+ gemini: UsageBreakdown;
18
+ claude: UsageBreakdown;
19
+ codex: UsageBreakdown;
20
+ };
21
+ }
22
+
23
+ export default function UsagePage() {
24
+ const [usage, setUsage] = useState<UsageData | null>(null);
25
+ const [config, setConfig] = useState<ConfigJson | null>(null);
26
+ const [loading, setLoading] = useState(true);
27
+ const [error, setError] = useState<string | null>(null);
28
+
29
+ const fetchUsageAndConfig = async () => {
30
+ try {
31
+ const stats = await fetchUsageStats();
32
+ setUsage(stats);
33
+
34
+ const configData = await fetchConfigJson();
35
+ setConfig(configData);
36
+ setError(null);
37
+ } catch (err: unknown) {
38
+ setError((err as Error).message || 'Failed to sync budget consumption stats.');
39
+ } finally {
40
+ setLoading(false);
41
+ }
42
+ };
43
+
44
+ useEffect(() => {
45
+ // eslint-disable-next-line react-hooks/set-state-in-effect -- legitimate statistics sync on mount
46
+ void fetchUsageAndConfig();
47
+ const interval = setInterval(fetchUsageAndConfig, 5000);
48
+ return () => clearInterval(interval);
49
+ }, []);
50
+
51
+ if (loading) {
52
+ return (
53
+ <div className="page" style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', height: '100%' }}>
54
+ <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 }}>
55
+ <path d="M21 12a9 9 0 1 1-6.219-8.56"/>
56
+ </svg>
57
+ <div style={{ color: 'var(--text-secondary)' }}>Aggregating token expenditures...</div>
58
+ </div>
59
+ );
60
+ }
61
+
62
+ const dailyCap = config?.budget?.budget?.daily_cap_usd ?? 5.0;
63
+ const weeklyCap = config?.budget?.budget?.weekly_cap_usd ?? 25.0;
64
+ const budgetEnabled = config?.budget?.budget?.enabled !== false;
65
+
66
+ const dailyPercent = Math.min(100, ((usage?.dailyUsd || 0) / dailyCap) * 100);
67
+ const weeklyPercent = Math.min(100, ((usage?.weeklyUsd || 0) / weeklyCap) * 100);
68
+
69
+ const getProgressColor = (percent: number) => {
70
+ if (percent > 85) return 'var(--error)';
71
+ if (percent > 60) return 'var(--warning)';
72
+ return 'var(--success)';
73
+ };
74
+
75
+ return (
76
+ <div className="page" style={{ display: 'flex', flexDirection: 'column', height: '100%', overflowY: 'auto' }}>
77
+ <div className="page-header" style={{ marginBottom: 24 }}>
78
+ <h1>Token & Budget Statistics</h1>
79
+ <p>Monitor your token cost expenditure in real time against safety budget limits</p>
80
+ </div>
81
+
82
+ {error && (
83
+ <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' }}>
84
+ ⚠️ {error}
85
+ </div>
86
+ )}
87
+
88
+ {/* Overview dollar cards */}
89
+ <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(340px, 1fr))', gap: 24, marginBottom: 24 }}>
90
+
91
+ {/* Daily Cap progress card */}
92
+ <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 }}>
93
+ <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start' }}>
94
+ <div>
95
+ <h3 style={{ fontSize: 13, fontWeight: 700, color: 'var(--text-tertiary)', textTransform: 'uppercase', letterSpacing: 0.5 }}>Daily Expenditure</h3>
96
+ <div style={{ fontSize: 28, fontWeight: 700, marginTop: 4, color: '#fff' }}>
97
+ ${usage?.dailyUsd.toFixed(4) || '0.0000'}
98
+ </div>
99
+ </div>
100
+ <span className="badge" style={{ background: 'var(--bg-elevated)', color: 'var(--text-secondary)', border: '1px solid var(--border)', fontSize: 11 }}>
101
+ Cap: ${dailyCap.toFixed(2)}
102
+ </span>
103
+ </div>
104
+
105
+ <div style={{ display: 'flex', flexDirection: 'column', gap: 6, marginTop: 8 }}>
106
+ <div style={{ display: 'flex', justifyContent: 'space-between', fontSize: 11, color: 'var(--text-secondary)' }}>
107
+ <span>Consumption Ratio</span>
108
+ <span>{dailyPercent.toFixed(1)}%</span>
109
+ </div>
110
+ <div style={{ height: 6, background: 'var(--bg-elevated)', borderRadius: 3, overflow: 'hidden' }}>
111
+ <div style={{
112
+ height: '100%',
113
+ width: `${dailyPercent}%`,
114
+ background: getProgressColor(dailyPercent),
115
+ boxShadow: `0 0 8px ${getProgressColor(dailyPercent)}`,
116
+ transition: 'width 0.4s ease'
117
+ }} />
118
+ </div>
119
+ </div>
120
+
121
+ <div style={{ fontSize: 10, color: 'var(--text-tertiary)', borderTop: '1px solid var(--border)', paddingTop: 10, marginTop: 4 }}>
122
+ Status: {budgetEnabled ? '🛡️ Budget supervision active' : '⚠️ Limits unmonitored (disabled)'}
123
+ </div>
124
+ </div>
125
+
126
+ {/* Weekly Cap progress card */}
127
+ <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 }}>
128
+ <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start' }}>
129
+ <div>
130
+ <h3 style={{ fontSize: 13, fontWeight: 700, color: 'var(--text-tertiary)', textTransform: 'uppercase', letterSpacing: 0.5 }}>Weekly Expenditure</h3>
131
+ <div style={{ fontSize: 28, fontWeight: 700, marginTop: 4, color: '#fff' }}>
132
+ ${usage?.weeklyUsd.toFixed(4) || '0.0000'}
133
+ </div>
134
+ </div>
135
+ <span className="badge" style={{ background: 'var(--bg-elevated)', color: 'var(--text-secondary)', border: '1px solid var(--border)', fontSize: 11 }}>
136
+ Cap: ${weeklyCap.toFixed(2)}
137
+ </span>
138
+ </div>
139
+
140
+ <div style={{ display: 'flex', flexDirection: 'column', gap: 6, marginTop: 8 }}>
141
+ <div style={{ display: 'flex', justifyContent: 'space-between', fontSize: 11, color: 'var(--text-secondary)' }}>
142
+ <span>Consumption Ratio</span>
143
+ <span>{weeklyPercent.toFixed(1)}%</span>
144
+ </div>
145
+ <div style={{ height: 6, background: 'var(--bg-elevated)', borderRadius: 3, overflow: 'hidden' }}>
146
+ <div style={{
147
+ height: '100%',
148
+ width: `${weeklyPercent}%`,
149
+ background: getProgressColor(weeklyPercent),
150
+ boxShadow: `0 0 8px ${getProgressColor(weeklyPercent)}`,
151
+ transition: 'width 0.4s ease'
152
+ }} />
153
+ </div>
154
+ </div>
155
+
156
+ <div style={{ fontSize: 10, color: 'var(--text-tertiary)', borderTop: '1px solid var(--border)', paddingTop: 10, marginTop: 4 }}>
157
+ Rolling window: Last 7 days in local system time
158
+ </div>
159
+ </div>
160
+
161
+ </div>
162
+
163
+ {/* Per Model breakdown list */}
164
+ <div className="card" style={{ padding: 24, background: 'rgba(18, 18, 26, 0.6)', border: '1px solid var(--border)' }}>
165
+ <h3 style={{ fontSize: 14, fontWeight: 700, color: 'var(--text-tertiary)', textTransform: 'uppercase', letterSpacing: 0.5, borderBottom: '1px solid var(--border)', paddingBottom: 10, marginBottom: 16 }}>
166
+ Model Engine Spend breakdown
167
+ </h3>
168
+
169
+ <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(280px, 1fr))', gap: 20 }}>
170
+
171
+ {/* Gemini */}
172
+ <div style={{ background: 'var(--bg-tertiary)', border: '1px solid var(--border)', padding: 16, borderRadius: 10, display: 'flex', flexDirection: 'column', gap: 12 }}>
173
+ <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
174
+ <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
175
+ <span style={{ fontSize: 18 }}>♊</span>
176
+ <span style={{ fontSize: 13, fontWeight: 600, color: '#fff' }}>Gemini</span>
177
+ </div>
178
+ <span className="badge" style={{ background: 'rgba(59, 130, 246, 0.1)', color: '#60a5fa', border: '1px solid rgba(59, 130, 246, 0.2)' }}>
179
+ Google API
180
+ </span>
181
+ </div>
182
+
183
+ <div style={{ display: 'flex', flexDirection: 'column', gap: 6, fontSize: 12 }}>
184
+ <div style={{ display: 'flex', justifyContent: 'space-between' }}>
185
+ <span style={{ color: 'var(--text-secondary)' }}>Daily Spend:</span>
186
+ <span style={{ fontWeight: 600, color: '#fff' }}>${usage?.breakdown.gemini.dailyUsd.toFixed(4)}</span>
187
+ </div>
188
+ <div style={{ display: 'flex', justifyContent: 'space-between' }}>
189
+ <span style={{ color: 'var(--text-secondary)' }}>Weekly Spend:</span>
190
+ <span style={{ fontWeight: 600, color: '#fff' }}>${usage?.breakdown.gemini.weeklyUsd.toFixed(4)}</span>
191
+ </div>
192
+ <div style={{ display: 'flex', justifyContent: 'space-between', borderTop: '1px solid var(--border)', paddingTop: 6, marginTop: 4, fontSize: 11, color: 'var(--text-tertiary)' }}>
193
+ <span>Rolling Tokens:</span>
194
+ <span>{usage?.breakdown.gemini.dailyTokens?.toLocaleString() || 0} (24h)</span>
195
+ </div>
196
+ </div>
197
+ </div>
198
+
199
+ {/* Claude */}
200
+ <div style={{ background: 'var(--bg-tertiary)', border: '1px solid var(--border)', padding: 16, borderRadius: 10, display: 'flex', flexDirection: 'column', gap: 12 }}>
201
+ <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
202
+ <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
203
+ <span style={{ fontSize: 18 }}>🍊</span>
204
+ <span style={{ fontSize: 13, fontWeight: 600, color: '#fff' }}>Claude</span>
205
+ </div>
206
+ <span className="badge" style={{ background: 'rgba(239, 107, 107, 0.1)', color: '#f87171', border: '1px solid rgba(239, 107, 107, 0.2)' }}>
207
+ Anthropic API
208
+ </span>
209
+ </div>
210
+
211
+ <div style={{ display: 'flex', flexDirection: 'column', gap: 6, fontSize: 12 }}>
212
+ <div style={{ display: 'flex', justifyContent: 'space-between' }}>
213
+ <span style={{ color: 'var(--text-secondary)' }}>Daily Spend:</span>
214
+ <span style={{ fontWeight: 600, color: '#fff' }}>${usage?.breakdown.claude.dailyUsd.toFixed(4)}</span>
215
+ </div>
216
+ <div style={{ display: 'flex', justifyContent: 'space-between' }}>
217
+ <span style={{ color: 'var(--text-secondary)' }}>Weekly Spend:</span>
218
+ <span style={{ fontWeight: 600, color: '#fff' }}>${usage?.breakdown.claude.weeklyUsd.toFixed(4)}</span>
219
+ </div>
220
+ <div style={{ display: 'flex', justifyContent: 'space-between', borderTop: '1px solid var(--border)', paddingTop: 6, marginTop: 4, fontSize: 11, color: 'var(--text-tertiary)' }}>
221
+ <span>Rolling Tokens:</span>
222
+ <span>(Ingested stats-cache)</span>
223
+ </div>
224
+ </div>
225
+ </div>
226
+
227
+ {/* Codex */}
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
+ <span style={{ fontSize: 13, fontWeight: 600, color: '#fff' }}>Codex</span>
233
+ </div>
234
+ <span className="badge" style={{ background: 'rgba(16, 185, 129, 0.1)', color: '#34d399', border: '1px solid rgba(16, 185, 129, 0.2)' }}>
235
+ OpenAI API
236
+ </span>
237
+ </div>
238
+
239
+ <div style={{ display: 'flex', flexDirection: 'column', gap: 6, fontSize: 12 }}>
240
+ <div style={{ display: 'flex', justifyContent: 'space-between' }}>
241
+ <span style={{ color: 'var(--text-secondary)' }}>Daily Spend:</span>
242
+ <span style={{ fontWeight: 600, color: '#fff' }}>${usage?.breakdown.codex.dailyUsd.toFixed(4)}</span>
243
+ </div>
244
+ <div style={{ display: 'flex', justifyContent: 'space-between' }}>
245
+ <span style={{ color: 'var(--text-secondary)' }}>Weekly Spend:</span>
246
+ <span style={{ fontWeight: 600, color: '#fff' }}>${usage?.breakdown.codex.weeklyUsd.toFixed(4)}</span>
247
+ </div>
248
+ <div style={{ display: 'flex', justifyContent: 'space-between', borderTop: '1px solid var(--border)', paddingTop: 6, marginTop: 4, fontSize: 11, color: 'var(--text-tertiary)' }}>
249
+ <span>Rolling Tokens:</span>
250
+ <span>{usage?.breakdown.codex.dailyTokens?.toLocaleString() || 0} (24h)</span>
251
+ </div>
252
+ </div>
253
+ </div>
254
+
255
+ </div>
256
+ </div>
257
+
258
+ </div>
259
+ );
260
+ }