total-recall-brain 3.12.2 โ 3.13.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/frontend/package.json +2 -1
- package/frontend/src/App.tsx +44 -0
- package/frontend/src/api.ts +101 -1
- package/frontend/src/components/UsageChart.tsx +78 -0
- package/frontend/src/pages/DesignDocsPage.tsx +614 -0
- package/frontend/src/pages/InstructionsPage.tsx +583 -0
- package/frontend/src/pages/ModelsPage.tsx +280 -23
- package/frontend/src/pages/OkfPage.tsx +535 -0
- package/frontend/src/pages/OpenWikiPage.tsx +472 -0
- package/frontend/src/pages/SettingsPage.tsx +183 -0
- package/frontend/src/pages/SkillsPage.tsx +1054 -37
- package/frontend/src/pages/UsagePage.tsx +254 -1
- package/frontend/src/types.ts +160 -0
- package/package.json +1 -1
- package/src/server/api.mjs +2 -0
- package/src/server/auth.mjs +2 -2
- package/src/server/rest.mjs +207 -43
- package/src/server/route-manifest.json +12 -0
- package/src/server/routes/_shared.mjs +1 -1
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { useState, useEffect } from 'react';
|
|
1
|
+
import { useState, useEffect, useMemo } from 'react';
|
|
2
2
|
import { fetchUsageStats, fetchConfigJson } from '../api';
|
|
3
3
|
import type { ConfigJson } from '../types';
|
|
4
|
+
import { AreaChart, Area, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, Legend } from 'recharts';
|
|
4
5
|
|
|
5
6
|
interface UsageBreakdown {
|
|
6
7
|
dailyUsd: number;
|
|
@@ -17,6 +18,7 @@ interface UsageData {
|
|
|
17
18
|
gemini: UsageBreakdown;
|
|
18
19
|
claude: UsageBreakdown;
|
|
19
20
|
codex: UsageBreakdown;
|
|
21
|
+
openrouter?: UsageBreakdown;
|
|
20
22
|
};
|
|
21
23
|
}
|
|
22
24
|
|
|
@@ -72,6 +74,40 @@ export default function UsagePage() {
|
|
|
72
74
|
return 'var(--success)';
|
|
73
75
|
};
|
|
74
76
|
|
|
77
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps -- usage reference is stable per fetch cycle
|
|
78
|
+
const timeseriesData = useMemo(() => {
|
|
79
|
+
if ((usage as Record<string, unknown>)?.timeseries && Object.keys((usage as Record<string, unknown>).timeseries as Record<string, unknown>).length > 0) {
|
|
80
|
+
const ts = (usage as Record<string, unknown>).timeseries as Record<string, Record<string, { provider: string; cost: number }>>;
|
|
81
|
+
return Object.entries(ts).map(([date, models]) => {
|
|
82
|
+
const entry: Record<string, unknown> = { date: new Date(date).toLocaleDateString('en-US', { month: 'short', day: 'numeric' }) };
|
|
83
|
+
let geminiCost = 0, claudeCost = 0, openrouterCost = 0, codexCost = 0;
|
|
84
|
+
for (const [, info] of Object.entries(models)) {
|
|
85
|
+
const m = info as { provider: string; cost: number };
|
|
86
|
+
if (m.provider === 'gemini') geminiCost += m.cost;
|
|
87
|
+
else if (m.provider === 'claude') claudeCost += m.cost;
|
|
88
|
+
else if (m.provider === 'openrouter') openrouterCost += m.cost;
|
|
89
|
+
else codexCost += m.cost;
|
|
90
|
+
}
|
|
91
|
+
entry.gemini = +geminiCost.toFixed(3);
|
|
92
|
+
entry.claude = +claudeCost.toFixed(3);
|
|
93
|
+
entry.openrouter = +openrouterCost.toFixed(3);
|
|
94
|
+
entry.codex = +codexCost.toFixed(3);
|
|
95
|
+
return entry;
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
// Synthetic 30-day demo data
|
|
99
|
+
return Array.from({ length: 30 }, (_, i) => {
|
|
100
|
+
const d = new Date(); d.setDate(d.getDate() - (29 - i));
|
|
101
|
+
return {
|
|
102
|
+
date: d.toLocaleDateString('en-US', { month: 'short', day: 'numeric' }),
|
|
103
|
+
gemini: +(Math.random() * 0.8 + 0.1).toFixed(3),
|
|
104
|
+
claude: +(Math.random() * 0.5 + 0.05).toFixed(3),
|
|
105
|
+
openrouter: +(Math.random() * 0.3).toFixed(3),
|
|
106
|
+
codex: +(Math.random() * 0.2).toFixed(3),
|
|
107
|
+
};
|
|
108
|
+
});
|
|
109
|
+
}, [usage]);
|
|
110
|
+
|
|
75
111
|
return (
|
|
76
112
|
<div className="page" style={{ display: 'flex', flexDirection: 'column', height: '100%', overflowY: 'auto' }}>
|
|
77
113
|
<div className="page-header" style={{ marginBottom: 24 }}>
|
|
@@ -79,6 +115,30 @@ export default function UsagePage() {
|
|
|
79
115
|
<p>Monitor your token cost expenditure in real time against safety budget limits</p>
|
|
80
116
|
</div>
|
|
81
117
|
|
|
118
|
+
{/* Subscription Tier Banner */}
|
|
119
|
+
<div style={{
|
|
120
|
+
padding: '16px 20px',
|
|
121
|
+
marginBottom: 24,
|
|
122
|
+
background: 'rgba(18, 18, 26, 0.6)',
|
|
123
|
+
backdropFilter: 'blur(12px)',
|
|
124
|
+
border: '1px solid var(--border)',
|
|
125
|
+
borderLeft: '3px solid var(--accent)',
|
|
126
|
+
borderRadius: 10,
|
|
127
|
+
display: 'flex',
|
|
128
|
+
alignItems: 'center',
|
|
129
|
+
gap: 14,
|
|
130
|
+
}}>
|
|
131
|
+
<span style={{ fontSize: 24 }}>๐ก๏ธ</span>
|
|
132
|
+
<div>
|
|
133
|
+
<div style={{ fontSize: 14, fontWeight: 700, color: '#fff', letterSpacing: 0.3 }}>
|
|
134
|
+
Sovereign โ Self-Hosted
|
|
135
|
+
</div>
|
|
136
|
+
<div style={{ fontSize: 12, color: 'var(--text-secondary)', marginTop: 2 }}>
|
|
137
|
+
All processing stays on your infrastructure. No external billing.
|
|
138
|
+
</div>
|
|
139
|
+
</div>
|
|
140
|
+
</div>
|
|
141
|
+
|
|
82
142
|
{error && (
|
|
83
143
|
<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
144
|
โ ๏ธ {error}
|
|
@@ -251,7 +311,200 @@ export default function UsagePage() {
|
|
|
251
311
|
</div>
|
|
252
312
|
</div>
|
|
253
313
|
</div>
|
|
314
|
+
{/* OpenRouter */}
|
|
315
|
+
<div style={{ background: 'var(--bg-tertiary)', border: '1px solid var(--border)', padding: 16, borderRadius: 10, display: 'flex', flexDirection: 'column', gap: 12 }}>
|
|
316
|
+
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
|
317
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
|
|
318
|
+
<span style={{ fontSize: 18 }}>๐</span>
|
|
319
|
+
<span style={{ fontSize: 13, fontWeight: 600, color: '#fff' }}>OpenRouter</span>
|
|
320
|
+
</div>
|
|
321
|
+
<span className="badge" style={{ background: 'rgba(236, 72, 153, 0.1)', color: '#f472b6', border: '1px solid rgba(236, 72, 153, 0.2)' }}>
|
|
322
|
+
OR API
|
|
323
|
+
</span>
|
|
324
|
+
</div>
|
|
325
|
+
|
|
326
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: 6, fontSize: 12 }}>
|
|
327
|
+
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
|
328
|
+
<span style={{ color: 'var(--text-secondary)' }}>Daily Spend:</span>
|
|
329
|
+
<span style={{ fontWeight: 600, color: '#fff' }}>${usage?.breakdown.openrouter?.dailyUsd.toFixed(4) || '0.0000'}</span>
|
|
330
|
+
</div>
|
|
331
|
+
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
|
332
|
+
<span style={{ color: 'var(--text-secondary)' }}>Weekly Spend:</span>
|
|
333
|
+
<span style={{ fontWeight: 600, color: '#fff' }}>${usage?.breakdown.openrouter?.weeklyUsd.toFixed(4) || '0.0000'}</span>
|
|
334
|
+
</div>
|
|
335
|
+
<div style={{ display: 'flex', justifyContent: 'space-between', borderTop: '1px solid var(--border)', paddingTop: 6, marginTop: 4, fontSize: 11, color: 'var(--text-tertiary)' }}>
|
|
336
|
+
<span>Rolling Tokens:</span>
|
|
337
|
+
<span>{usage?.breakdown.openrouter?.dailyTokens?.toLocaleString() || 0} (24h)</span>
|
|
338
|
+
</div>
|
|
339
|
+
</div>
|
|
340
|
+
</div>
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
</div>
|
|
344
|
+
</div>
|
|
345
|
+
|
|
346
|
+
{/* โโ Timeseries Cost Chart โโ */}
|
|
347
|
+
<div className="card" style={{ padding: 24, marginTop: 24, background: 'rgba(18, 18, 26, 0.6)', backdropFilter: 'blur(8px)', border: '1px solid var(--border)' }}>
|
|
348
|
+
<h3 style={{ fontSize: 14, fontWeight: 700, color: 'var(--text-tertiary)', textTransform: 'uppercase', letterSpacing: 0.5, borderBottom: '1px solid var(--border)', paddingBottom: 10, marginBottom: 20 }}>
|
|
349
|
+
30-Day Cost Trend
|
|
350
|
+
</h3>
|
|
351
|
+
<ResponsiveContainer width="100%" height={300}>
|
|
352
|
+
<AreaChart data={timeseriesData} margin={{ top: 5, right: 20, left: 0, bottom: 5 }}>
|
|
353
|
+
<defs>
|
|
354
|
+
<linearGradient id="gradGemini" x1="0" y1="0" x2="0" y2="1">
|
|
355
|
+
<stop offset="5%" stopColor="#6c5ce7" stopOpacity={0.4} />
|
|
356
|
+
<stop offset="95%" stopColor="#6c5ce7" stopOpacity={0} />
|
|
357
|
+
</linearGradient>
|
|
358
|
+
<linearGradient id="gradClaude" x1="0" y1="0" x2="0" y2="1">
|
|
359
|
+
<stop offset="5%" stopColor="#e17055" stopOpacity={0.4} />
|
|
360
|
+
<stop offset="95%" stopColor="#e17055" stopOpacity={0} />
|
|
361
|
+
</linearGradient>
|
|
362
|
+
<linearGradient id="gradOpenRouter" x1="0" y1="0" x2="0" y2="1">
|
|
363
|
+
<stop offset="5%" stopColor="#74b9ff" stopOpacity={0.4} />
|
|
364
|
+
<stop offset="95%" stopColor="#74b9ff" stopOpacity={0} />
|
|
365
|
+
</linearGradient>
|
|
366
|
+
<linearGradient id="gradCodex" x1="0" y1="0" x2="0" y2="1">
|
|
367
|
+
<stop offset="5%" stopColor="#00cec9" stopOpacity={0.4} />
|
|
368
|
+
<stop offset="95%" stopColor="#00cec9" stopOpacity={0} />
|
|
369
|
+
</linearGradient>
|
|
370
|
+
</defs>
|
|
371
|
+
<CartesianGrid strokeDasharray="3 3" stroke="rgba(255,255,255,0.04)" />
|
|
372
|
+
<XAxis dataKey="date" tick={{ fontSize: 11, fill: 'var(--text-secondary)' }} axisLine={{ stroke: 'var(--border)' }} tickLine={false} />
|
|
373
|
+
<YAxis tick={{ fontSize: 11, fill: 'var(--text-secondary)' }} axisLine={{ stroke: 'var(--border)' }} tickLine={false} tickFormatter={(v: number) => `$${v.toFixed(2)}`} />
|
|
374
|
+
<Tooltip
|
|
375
|
+
contentStyle={{ background: '#1a1a2e', border: '1px solid rgba(255,255,255,0.1)', borderRadius: 8, fontSize: 12, color: '#e8e8e8' }}
|
|
376
|
+
labelStyle={{ color: '#fff', fontWeight: 600, marginBottom: 4 }}
|
|
377
|
+
formatter={(value: number, name: string) => [`$${value.toFixed(3)}`, name.charAt(0).toUpperCase() + name.slice(1)]}
|
|
378
|
+
/>
|
|
379
|
+
<Legend wrapperStyle={{ fontSize: 12, color: 'var(--text-secondary)' }} />
|
|
380
|
+
<Area type="monotone" dataKey="gemini" stackId="1" stroke="#6c5ce7" fill="url(#gradGemini)" strokeWidth={2} />
|
|
381
|
+
<Area type="monotone" dataKey="claude" stackId="1" stroke="#e17055" fill="url(#gradClaude)" strokeWidth={2} />
|
|
382
|
+
<Area type="monotone" dataKey="openrouter" stackId="1" stroke="#74b9ff" fill="url(#gradOpenRouter)" strokeWidth={2} />
|
|
383
|
+
<Area type="monotone" dataKey="codex" stackId="1" stroke="#00cec9" fill="url(#gradCodex)" strokeWidth={2} />
|
|
384
|
+
</AreaChart>
|
|
385
|
+
</ResponsiveContainer>
|
|
386
|
+
</div>
|
|
387
|
+
|
|
388
|
+
{/* โโ Provider Surface Cards โโ */}
|
|
389
|
+
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(220px, 1fr))', gap: 16, marginTop: 24 }}>
|
|
390
|
+
{([
|
|
391
|
+
{ name: 'Gemini', color: '#6c5ce7', emoji: 'โ', key: 'gemini' as const },
|
|
392
|
+
{ name: 'Claude', color: '#e17055', emoji: '๐', key: 'claude' as const },
|
|
393
|
+
{ name: 'OpenRouter', color: '#74b9ff', emoji: '๐', key: 'openrouter' as const },
|
|
394
|
+
{ name: 'Codex', color: '#00cec9', emoji: '๐ป', key: 'codex' as const },
|
|
395
|
+
] as const).map((provider) => {
|
|
396
|
+
const data = provider.key === 'openrouter' ? usage?.breakdown.openrouter : usage?.breakdown[provider.key];
|
|
397
|
+
return (
|
|
398
|
+
<div key={provider.key} style={{
|
|
399
|
+
background: 'rgba(18, 18, 26, 0.6)',
|
|
400
|
+
backdropFilter: 'blur(12px)',
|
|
401
|
+
border: '1px solid var(--border)',
|
|
402
|
+
borderTop: `4px solid ${provider.color}`,
|
|
403
|
+
borderRadius: 10,
|
|
404
|
+
padding: 20,
|
|
405
|
+
display: 'flex',
|
|
406
|
+
flexDirection: 'column',
|
|
407
|
+
gap: 12,
|
|
408
|
+
transition: 'transform 0.2s ease, box-shadow 0.2s ease',
|
|
409
|
+
}}
|
|
410
|
+
onMouseEnter={(e) => { (e.currentTarget as HTMLDivElement).style.transform = 'translateY(-2px)'; (e.currentTarget as HTMLDivElement).style.boxShadow = `0 8px 24px ${provider.color}22`; }}
|
|
411
|
+
onMouseLeave={(e) => { (e.currentTarget as HTMLDivElement).style.transform = 'translateY(0)'; (e.currentTarget as HTMLDivElement).style.boxShadow = 'none'; }}
|
|
412
|
+
>
|
|
413
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
|
|
414
|
+
<span style={{ fontSize: 20 }}>{provider.emoji}</span>
|
|
415
|
+
<span style={{ fontSize: 14, fontWeight: 700, color: '#fff' }}>{provider.name}</span>
|
|
416
|
+
</div>
|
|
417
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: 6, fontSize: 12 }}>
|
|
418
|
+
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
|
419
|
+
<span style={{ color: 'var(--text-secondary)' }}>Daily</span>
|
|
420
|
+
<span style={{ fontWeight: 600, color: provider.color }}>${data?.dailyUsd.toFixed(4) || '0.0000'}</span>
|
|
421
|
+
</div>
|
|
422
|
+
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
|
423
|
+
<span style={{ color: 'var(--text-secondary)' }}>Weekly</span>
|
|
424
|
+
<span style={{ fontWeight: 600, color: provider.color }}>${data?.weeklyUsd.toFixed(4) || '0.0000'}</span>
|
|
425
|
+
</div>
|
|
426
|
+
{data?.dailyTokens != null && (
|
|
427
|
+
<div style={{ display: 'flex', justifyContent: 'space-between', borderTop: '1px solid var(--border)', paddingTop: 6, marginTop: 2, fontSize: 11, color: 'var(--text-tertiary)' }}>
|
|
428
|
+
<span>Tokens (24h)</span>
|
|
429
|
+
<span>{data.dailyTokens.toLocaleString()}</span>
|
|
430
|
+
</div>
|
|
431
|
+
)}
|
|
432
|
+
</div>
|
|
433
|
+
</div>
|
|
434
|
+
);
|
|
435
|
+
})}
|
|
436
|
+
</div>
|
|
254
437
|
|
|
438
|
+
{/* โโ Per-Model Token Breakdown Table โโ */}
|
|
439
|
+
<div className="card" style={{ padding: 24, marginTop: 24, background: 'rgba(18, 18, 26, 0.6)', backdropFilter: 'blur(8px)', border: '1px solid var(--border)' }}>
|
|
440
|
+
<h3 style={{ fontSize: 14, fontWeight: 700, color: 'var(--text-tertiary)', textTransform: 'uppercase', letterSpacing: 0.5, borderBottom: '1px solid var(--border)', paddingBottom: 10, marginBottom: 16 }}>
|
|
441
|
+
Per-Model Breakdown
|
|
442
|
+
</h3>
|
|
443
|
+
<div style={{ overflowX: 'auto' }}>
|
|
444
|
+
<table style={{ width: '100%', borderCollapse: 'collapse', fontSize: 12 }}>
|
|
445
|
+
<thead>
|
|
446
|
+
<tr style={{ borderBottom: '1px solid var(--border)' }}>
|
|
447
|
+
{['Provider', 'Daily ($)', 'Weekly ($)', 'Daily Tokens', 'Weekly Tokens'].map((h) => (
|
|
448
|
+
<th key={h} style={{ textAlign: 'left', padding: '10px 12px', fontSize: 11, fontWeight: 700, color: 'var(--text-tertiary)', textTransform: 'uppercase', letterSpacing: 0.5 }}>{h}</th>
|
|
449
|
+
))}
|
|
450
|
+
</tr>
|
|
451
|
+
</thead>
|
|
452
|
+
<tbody>
|
|
453
|
+
{([
|
|
454
|
+
{ name: 'Gemini', key: 'gemini' as const, color: '#6c5ce7' },
|
|
455
|
+
{ name: 'Claude', key: 'claude' as const, color: '#e17055' },
|
|
456
|
+
{ name: 'Codex', key: 'codex' as const, color: '#00cec9' },
|
|
457
|
+
{ name: 'OpenRouter', key: 'openrouter' as const, color: '#74b9ff' },
|
|
458
|
+
] as const).map((row, idx) => {
|
|
459
|
+
const d = row.key === 'openrouter' ? usage?.breakdown.openrouter : usage?.breakdown[row.key];
|
|
460
|
+
return (
|
|
461
|
+
<tr key={row.key} style={{ background: idx % 2 === 1 ? 'rgba(255,255,255,0.02)' : 'transparent', borderBottom: '1px solid var(--border)' }}>
|
|
462
|
+
<td style={{ padding: '10px 12px', fontWeight: 600, color: row.color }}>{row.name}</td>
|
|
463
|
+
<td style={{ padding: '10px 12px', color: '#fff' }}>${d?.dailyUsd.toFixed(4) || '0.0000'}</td>
|
|
464
|
+
<td style={{ padding: '10px 12px', color: '#fff' }}>${d?.weeklyUsd.toFixed(4) || '0.0000'}</td>
|
|
465
|
+
<td style={{ padding: '10px 12px', color: 'var(--text-secondary)' }}>{d?.dailyTokens?.toLocaleString() || 'โ'}</td>
|
|
466
|
+
<td style={{ padding: '10px 12px', color: 'var(--text-secondary)' }}>{d?.weeklyTokens?.toLocaleString() || 'โ'}</td>
|
|
467
|
+
</tr>
|
|
468
|
+
);
|
|
469
|
+
})}
|
|
470
|
+
{/* Totals row */}
|
|
471
|
+
<tr style={{ borderTop: '2px solid var(--border)', background: 'rgba(108, 92, 231, 0.05)' }}>
|
|
472
|
+
<td style={{ padding: '10px 12px', fontWeight: 700, color: '#fff' }}>Total</td>
|
|
473
|
+
<td style={{ padding: '10px 12px', fontWeight: 700, color: '#fff' }}>
|
|
474
|
+
${(
|
|
475
|
+
(usage?.breakdown.gemini.dailyUsd || 0) +
|
|
476
|
+
(usage?.breakdown.claude.dailyUsd || 0) +
|
|
477
|
+
(usage?.breakdown.codex.dailyUsd || 0) +
|
|
478
|
+
(usage?.breakdown.openrouter?.dailyUsd || 0)
|
|
479
|
+
).toFixed(4)}
|
|
480
|
+
</td>
|
|
481
|
+
<td style={{ padding: '10px 12px', fontWeight: 700, color: '#fff' }}>
|
|
482
|
+
${(
|
|
483
|
+
(usage?.breakdown.gemini.weeklyUsd || 0) +
|
|
484
|
+
(usage?.breakdown.claude.weeklyUsd || 0) +
|
|
485
|
+
(usage?.breakdown.codex.weeklyUsd || 0) +
|
|
486
|
+
(usage?.breakdown.openrouter?.weeklyUsd || 0)
|
|
487
|
+
).toFixed(4)}
|
|
488
|
+
</td>
|
|
489
|
+
<td style={{ padding: '10px 12px', fontWeight: 700, color: 'var(--text-secondary)' }}>
|
|
490
|
+
{(
|
|
491
|
+
(usage?.breakdown.gemini.dailyTokens || 0) +
|
|
492
|
+
(usage?.breakdown.claude.dailyTokens || 0) +
|
|
493
|
+
(usage?.breakdown.codex.dailyTokens || 0) +
|
|
494
|
+
(usage?.breakdown.openrouter?.dailyTokens || 0)
|
|
495
|
+
).toLocaleString()}
|
|
496
|
+
</td>
|
|
497
|
+
<td style={{ padding: '10px 12px', fontWeight: 700, color: 'var(--text-secondary)' }}>
|
|
498
|
+
{(
|
|
499
|
+
(usage?.breakdown.gemini.weeklyTokens || 0) +
|
|
500
|
+
(usage?.breakdown.claude.weeklyTokens || 0) +
|
|
501
|
+
(usage?.breakdown.codex.weeklyTokens || 0) +
|
|
502
|
+
(usage?.breakdown.openrouter?.weeklyTokens || 0)
|
|
503
|
+
).toLocaleString()}
|
|
504
|
+
</td>
|
|
505
|
+
</tr>
|
|
506
|
+
</tbody>
|
|
507
|
+
</table>
|
|
255
508
|
</div>
|
|
256
509
|
</div>
|
|
257
510
|
|
package/frontend/src/types.ts
CHANGED
|
@@ -9,6 +9,15 @@ export interface ChatMessage {
|
|
|
9
9
|
currentVersionIndex?: number
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
+
export interface GeminiModelInfo {
|
|
13
|
+
id: string
|
|
14
|
+
displayName: string
|
|
15
|
+
pricing?: {
|
|
16
|
+
prompt: string
|
|
17
|
+
completion: string
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
12
21
|
export interface MemoryNode {
|
|
13
22
|
slug: string
|
|
14
23
|
title: string
|
|
@@ -119,6 +128,18 @@ export interface UsageData {
|
|
|
119
128
|
gemini: UsageBreakdown
|
|
120
129
|
claude: UsageBreakdown
|
|
121
130
|
codex: UsageBreakdown
|
|
131
|
+
openrouter?: UsageBreakdown
|
|
132
|
+
}
|
|
133
|
+
timeseries?: {
|
|
134
|
+
[dateStr: string]: {
|
|
135
|
+
[modelId: string]: {
|
|
136
|
+
input: number
|
|
137
|
+
output: number
|
|
138
|
+
tokens: number
|
|
139
|
+
provider: string
|
|
140
|
+
cost: number
|
|
141
|
+
}
|
|
142
|
+
}
|
|
122
143
|
}
|
|
123
144
|
}
|
|
124
145
|
|
|
@@ -161,6 +182,10 @@ export interface ConfigJson {
|
|
|
161
182
|
brain?: {
|
|
162
183
|
preferred_agent?: string
|
|
163
184
|
local_endpoint?: string
|
|
185
|
+
openrouter_model?: string
|
|
186
|
+
gemini_model?: string
|
|
187
|
+
claude_model?: string
|
|
188
|
+
openai_model?: string
|
|
164
189
|
}
|
|
165
190
|
secrets?: {
|
|
166
191
|
google_api_key?: string
|
|
@@ -171,6 +196,7 @@ export interface ConfigJson {
|
|
|
171
196
|
exa_api_key?: string
|
|
172
197
|
serper_api_key?: string
|
|
173
198
|
github_token?: string
|
|
199
|
+
openrouter_api_key?: string
|
|
174
200
|
}
|
|
175
201
|
}
|
|
176
202
|
|
|
@@ -180,3 +206,137 @@ export interface UpdateCheckResult {
|
|
|
180
206
|
updateAvailable: boolean
|
|
181
207
|
}
|
|
182
208
|
|
|
209
|
+
// โโโ Instruction Surface Types โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
210
|
+
|
|
211
|
+
export interface InstructionSurface {
|
|
212
|
+
name: string
|
|
213
|
+
filename: string
|
|
214
|
+
size: number
|
|
215
|
+
lastCompiled: string
|
|
216
|
+
active: boolean
|
|
217
|
+
content?: string
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
export interface InstructionsData {
|
|
221
|
+
surfaces: InstructionSurface[]
|
|
222
|
+
lastCompileTimestamp: string
|
|
223
|
+
totalNodes: number
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// โโโ Skill Detail Types โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
227
|
+
|
|
228
|
+
export interface SkillSubFile {
|
|
229
|
+
name: string
|
|
230
|
+
path: string
|
|
231
|
+
size: number
|
|
232
|
+
modified: string
|
|
233
|
+
isDirectory: boolean
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
export interface SkillDetail {
|
|
237
|
+
name: string
|
|
238
|
+
description: string
|
|
239
|
+
content: string
|
|
240
|
+
subFiles: {
|
|
241
|
+
scripts: SkillSubFile[]
|
|
242
|
+
references: SkillSubFile[]
|
|
243
|
+
evals: SkillSubFile[]
|
|
244
|
+
subagents: SkillSubFile[]
|
|
245
|
+
}
|
|
246
|
+
evalsStatus?: {
|
|
247
|
+
total: number
|
|
248
|
+
passed: number
|
|
249
|
+
failed: number
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// โโโ OKF Types โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
254
|
+
|
|
255
|
+
export interface OkfLintResult {
|
|
256
|
+
slug: string
|
|
257
|
+
field: string
|
|
258
|
+
severity: 'warning' | 'error'
|
|
259
|
+
message: string
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
export interface OkfExportOptions {
|
|
263
|
+
stripSsss: boolean
|
|
264
|
+
format: 'directory' | 'tar.gz'
|
|
265
|
+
scope: 'global' | 'project' | 'all'
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// โโโ OpenWiki Types โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
269
|
+
|
|
270
|
+
export interface OpenWikiNode {
|
|
271
|
+
slug: string
|
|
272
|
+
title: string
|
|
273
|
+
category: string
|
|
274
|
+
tags: string[]
|
|
275
|
+
body: string
|
|
276
|
+
source: string
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// โโโ UCW Bundle Types (SSSS ยง16 โ UltraChat Workspace) โโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
280
|
+
|
|
281
|
+
export interface UcwBundleManifest {
|
|
282
|
+
name: string
|
|
283
|
+
description: string
|
|
284
|
+
version: string
|
|
285
|
+
exported_at: string
|
|
286
|
+
ssss_core_version: string
|
|
287
|
+
required_extensions: string[]
|
|
288
|
+
export_profile: 'backup' | 'template' | 'sale'
|
|
289
|
+
primitive_inventory: Record<string, number>
|
|
290
|
+
provisioning: UcwProvisioningStep[]
|
|
291
|
+
parameters?: UcwParameter[]
|
|
292
|
+
source_workspace_id?: string
|
|
293
|
+
file_count: number
|
|
294
|
+
provenance: {
|
|
295
|
+
content_hash: string
|
|
296
|
+
exporter: string
|
|
297
|
+
signature?: string
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
export interface UcwProvisioningStep {
|
|
302
|
+
type: string
|
|
303
|
+
target: string
|
|
304
|
+
action: string
|
|
305
|
+
installMode?: 'optional' | 'recommended' | 'required'
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
export interface UcwParameter {
|
|
309
|
+
name: string
|
|
310
|
+
description: string
|
|
311
|
+
required: boolean
|
|
312
|
+
default?: string
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
export interface UcwBundle {
|
|
316
|
+
manifest: UcwBundleManifest
|
|
317
|
+
branding?: Record<string, string>
|
|
318
|
+
files: Array<{ path: string; content: string; frontmatter?: Record<string, unknown> }>
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// โโโ Design Docs Types โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
322
|
+
|
|
323
|
+
export interface DesignDoc {
|
|
324
|
+
name: string
|
|
325
|
+
path: string
|
|
326
|
+
size: number
|
|
327
|
+
modified: string
|
|
328
|
+
category: 'in-progress' | 'completed' | 'planned' | 'archived' | 'backlog' | 'root'
|
|
329
|
+
content?: string
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// โโโ Memory Stats Types โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
333
|
+
|
|
334
|
+
export interface MemoryVaultStats {
|
|
335
|
+
total: number
|
|
336
|
+
byCategory: Record<string, number>
|
|
337
|
+
byPriority: Record<string, number>
|
|
338
|
+
byStatus: Record<string, number>
|
|
339
|
+
avgImportance: number
|
|
340
|
+
avgConfidence: number
|
|
341
|
+
}
|
|
342
|
+
|
package/package.json
CHANGED
package/src/server/api.mjs
CHANGED
|
@@ -716,6 +716,8 @@ You are operating in a lightweight text-only mode. Answer the user's questions d
|
|
|
716
716
|
messages: currentMessages,
|
|
717
717
|
response: finalMessage,
|
|
718
718
|
tokens: promptTokens + completionTokens,
|
|
719
|
+
input_tokens: promptTokens,
|
|
720
|
+
output_tokens: completionTokens,
|
|
719
721
|
brain_id: brainId || 'global'
|
|
720
722
|
});
|
|
721
723
|
// Emit latency + tokens so the watchdog log monitor can react to anomalies.
|
package/src/server/auth.mjs
CHANGED
|
@@ -37,7 +37,7 @@ export function loadSecurityConfig() {
|
|
|
37
37
|
api: { pats: [], allow_static_pats: false },
|
|
38
38
|
network: { require_https: true, public_health: false, allowed_origins: [], trusted_proxies: [] },
|
|
39
39
|
bind: { host: '127.0.0.1', port: 3000, allow_public_bind: false },
|
|
40
|
-
rate_limits: { api_requests_per_minute:
|
|
40
|
+
rate_limits: { api_requests_per_minute: 1200 },
|
|
41
41
|
sandbox: { enabled: false }
|
|
42
42
|
};
|
|
43
43
|
}
|
|
@@ -50,7 +50,7 @@ export function loadSecurityConfig() {
|
|
|
50
50
|
|
|
51
51
|
export function apiRateLimiter() {
|
|
52
52
|
const config = loadSecurityConfig();
|
|
53
|
-
const limit = config.rate_limits?.api_requests_per_minute ||
|
|
53
|
+
const limit = config.rate_limits?.api_requests_per_minute || 1200;
|
|
54
54
|
return rateLimit({
|
|
55
55
|
windowMs: 60 * 1000,
|
|
56
56
|
max: limit,
|