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
package/frontend/package.json
CHANGED
package/frontend/src/App.tsx
CHANGED
|
@@ -21,6 +21,10 @@ import UsagePage from './pages/UsagePage'
|
|
|
21
21
|
import CollabPage from './pages/CollabPage'
|
|
22
22
|
import HelpPage from './pages/HelpPage'
|
|
23
23
|
import GraphPage from './pages/GraphPage'
|
|
24
|
+
import InstructionsPage from './pages/InstructionsPage'
|
|
25
|
+
import DesignDocsPage from './pages/DesignDocsPage'
|
|
26
|
+
import OkfPage from './pages/OkfPage'
|
|
27
|
+
import OpenWikiPage from './pages/OpenWikiPage'
|
|
24
28
|
|
|
25
29
|
// ─── Auth state type ──────────────────────────────────────────────────────────
|
|
26
30
|
type AuthState = 'loading' | 'authed' | 'unauthed'
|
|
@@ -152,6 +156,42 @@ function Sidebar({ onLogout, health, activeBrainId, onBrainChange }: SidebarProp
|
|
|
152
156
|
</svg>
|
|
153
157
|
Collaboration
|
|
154
158
|
</NavLink>
|
|
159
|
+
<NavLink to="/instructions" className={({ isActive }) => `nav-link ${isActive ? 'active' : ''}`} id="nav-instructions">
|
|
160
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
|
|
161
|
+
<path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z" />
|
|
162
|
+
<polyline points="14 2 14 8 20 8" />
|
|
163
|
+
<line x1="16" y1="13" x2="8" y2="13" />
|
|
164
|
+
<line x1="16" y1="17" x2="8" y2="17" />
|
|
165
|
+
<polyline points="10 9 9 9 8 9" />
|
|
166
|
+
</svg>
|
|
167
|
+
Instructions
|
|
168
|
+
</NavLink>
|
|
169
|
+
<NavLink to="/design" className={({ isActive }) => `nav-link ${isActive ? 'active' : ''}`} id="nav-design">
|
|
170
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
|
|
171
|
+
<rect x="3" y="3" width="18" height="18" rx="2" ry="2" />
|
|
172
|
+
<line x1="3" y1="9" x2="21" y2="9" />
|
|
173
|
+
<line x1="9" y1="21" x2="9" y2="9" />
|
|
174
|
+
</svg>
|
|
175
|
+
Design Docs
|
|
176
|
+
</NavLink>
|
|
177
|
+
<NavLink to="/okf" className={({ isActive }) => `nav-link ${isActive ? 'active' : ''}`} id="nav-okf">
|
|
178
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
|
|
179
|
+
<path d="M21 16V8a2 2 0 00-1-1.73l-7-4a2 2 0 00-2 0l-7 4A2 2 0 003 8v8a2 2 0 001 1.73l7 4a2 2 0 002 0l7-4A2 2 0 0021 16z" />
|
|
180
|
+
<polyline points="7.5 4.21 12 6.81 16.5 4.21" />
|
|
181
|
+
<polyline points="7.5 19.79 7.5 14.6 3 12" />
|
|
182
|
+
<polyline points="21 12 16.5 14.6 16.5 19.79" />
|
|
183
|
+
<polyline points="3.27 6.96 12 12.01 20.73 6.96" />
|
|
184
|
+
<line x1="12" y1="22.08" x2="12" y2="12" />
|
|
185
|
+
</svg>
|
|
186
|
+
OKF Manager
|
|
187
|
+
</NavLink>
|
|
188
|
+
<NavLink to="/openwiki" className={({ isActive }) => `nav-link ${isActive ? 'active' : ''}`} id="nav-openwiki">
|
|
189
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
|
|
190
|
+
<path d="M2 3h6a4 4 0 014 4v14a3 3 0 00-3-3H2z" />
|
|
191
|
+
<path d="M22 3h-6a4 4 0 00-4 4v14a3 3 0 013-3h7z" />
|
|
192
|
+
</svg>
|
|
193
|
+
OpenWiki
|
|
194
|
+
</NavLink>
|
|
155
195
|
<NavLink to="/help" className={({ isActive }) => `nav-link ${isActive ? 'active' : ''}`} id="nav-help">
|
|
156
196
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
|
|
157
197
|
<circle cx="12" cy="12" r="10" />
|
|
@@ -317,6 +357,10 @@ function MainContent({ activeBrainId, onBrainChange }: { activeBrainId: string;
|
|
|
317
357
|
<Route path="/integrations" element={<IntegrationsPage />} />
|
|
318
358
|
<Route path="/skills" element={<SkillsPage />} />
|
|
319
359
|
<Route path="/collab" element={<CollabPage />} />
|
|
360
|
+
<Route path="/instructions" element={<InstructionsPage />} />
|
|
361
|
+
<Route path="/design" element={<DesignDocsPage />} />
|
|
362
|
+
<Route path="/okf" element={<OkfPage />} />
|
|
363
|
+
<Route path="/openwiki" element={<OpenWikiPage />} />
|
|
320
364
|
<Route path="/help" element={<HelpPage />} />
|
|
321
365
|
<Route path="/graph" element={<GraphPage activeBrainId={activeBrainId} />} />
|
|
322
366
|
</Routes>
|
package/frontend/src/api.ts
CHANGED
|
@@ -667,12 +667,33 @@ export interface GeminiModelInfo {
|
|
|
667
667
|
}
|
|
668
668
|
|
|
669
669
|
export async function fetchGeminiModels(): Promise<GeminiModelInfo[]> {
|
|
670
|
-
const res = await apiFetch(`${API_BASE}/api/gemini-models`)
|
|
670
|
+
const res = await apiFetch(`${API_BASE}/api/gemini-models?t=${Date.now()}`)
|
|
671
671
|
if (!res.ok) throw new Error(`Gemini models API error: ${res.status}`)
|
|
672
672
|
const data = await res.json()
|
|
673
673
|
return data.models ?? []
|
|
674
674
|
}
|
|
675
675
|
|
|
676
|
+
export async function fetchClaudeModels(): Promise<GeminiModelInfo[]> {
|
|
677
|
+
const res = await apiFetch(`${API_BASE}/api/claude-models?t=${Date.now()}`)
|
|
678
|
+
if (!res.ok) throw new Error(`Claude models API error: ${res.status}`)
|
|
679
|
+
const data = await res.json()
|
|
680
|
+
return data.models ?? []
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
export async function fetchOpenaiModels(): Promise<GeminiModelInfo[]> {
|
|
684
|
+
const res = await apiFetch(`${API_BASE}/api/openai-models?t=${Date.now()}`)
|
|
685
|
+
if (!res.ok) throw new Error(`OpenAI models API error: ${res.status}`)
|
|
686
|
+
const data = await res.json()
|
|
687
|
+
return data.models ?? []
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
export async function fetchOpenRouterModels(): Promise<GeminiModelInfo[]> {
|
|
691
|
+
const res = await apiFetch(`${API_BASE}/api/openrouter-models?t=${Date.now()}`)
|
|
692
|
+
if (!res.ok) throw new Error(`OpenRouter models API error: ${res.status}`)
|
|
693
|
+
const data = await res.json()
|
|
694
|
+
return data.models ?? []
|
|
695
|
+
}
|
|
696
|
+
|
|
676
697
|
// ─── Share API ──────────────────────────────────────────────────────────────────
|
|
677
698
|
|
|
678
699
|
export async function shareToApi(payload: { url: string; action: string; title?: string; tags?: string[] }): Promise<{ success: boolean; message?: string }> {
|
|
@@ -732,3 +753,82 @@ export async function fetchHelpContent(topicId: string): Promise<{ topic: string
|
|
|
732
753
|
if (!res.ok) throw new Error('Failed to load help content')
|
|
733
754
|
return res.json()
|
|
734
755
|
}
|
|
756
|
+
|
|
757
|
+
// ─── Instructions APIs ─────────────────────────────────────────────────────────
|
|
758
|
+
|
|
759
|
+
export async function fetchInstructions(): Promise<{ surfaces: Array<{ name: string; filename: string; size: number; lastCompiled: string; active: boolean }>; lastCompileTimestamp: string; totalNodes: number }> {
|
|
760
|
+
const res = await apiFetch(`${API_BASE}/api/instructions`)
|
|
761
|
+
if (!res.ok) throw new Error(`Instructions API error: ${res.status}`)
|
|
762
|
+
return res.json()
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
export async function fetchInstructionContent(name: string): Promise<{ name: string; content: string }> {
|
|
766
|
+
const res = await apiFetch(`${API_BASE}/api/ssss/instructions?surface=${encodeURIComponent(name)}`)
|
|
767
|
+
if (!res.ok) throw new Error(`Instruction content error: ${res.status}`)
|
|
768
|
+
return res.json()
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
// ─── OKF APIs ──────────────────────────────────────────────────────────────────
|
|
772
|
+
|
|
773
|
+
export async function runOkfLint(): Promise<{ results: Array<{ slug: string; field: string; severity: string; message: string }>; summary: { total: number; errors: number; warnings: number } }> {
|
|
774
|
+
const res = await apiFetch(`${API_BASE}/api/sandbox`, {
|
|
775
|
+
method: 'POST',
|
|
776
|
+
headers: { 'Content-Type': 'application/json' },
|
|
777
|
+
body: JSON.stringify({ code: 'const { execSync } = require("child_process"); console.log(JSON.stringify({ output: execSync("npx total-recall lint --okf 2>&1 || true", { encoding: "utf-8" }) }));', timeout: 30000 }),
|
|
778
|
+
})
|
|
779
|
+
if (!res.ok) throw new Error(`OKF lint error: ${res.status}`)
|
|
780
|
+
return res.json()
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
export async function triggerOkfExport(path: string, options: { stripSsss?: boolean; format?: string; scope?: string }): Promise<{ success: boolean; message: string; path?: string }> {
|
|
784
|
+
const args = [`export ${path} --okf`]
|
|
785
|
+
if (options.stripSsss) args.push('--strip-ssss')
|
|
786
|
+
if (options.format) args.push(`--format ${options.format}`)
|
|
787
|
+
if (options.scope === 'global') args.push('--global')
|
|
788
|
+
if (options.scope === 'project') args.push('--project')
|
|
789
|
+
|
|
790
|
+
const res = await apiFetch(`${API_BASE}/api/sandbox`, {
|
|
791
|
+
method: 'POST',
|
|
792
|
+
headers: { 'Content-Type': 'application/json' },
|
|
793
|
+
body: JSON.stringify({ code: `const { execSync } = require("child_process"); try { const out = execSync("npx total-recall ${args.join(' ')} 2>&1", { encoding: "utf-8" }); console.log(JSON.stringify({ success: true, message: out })); } catch(e) { console.log(JSON.stringify({ success: false, message: e.stdout || e.message })); }`, timeout: 60000 }),
|
|
794
|
+
})
|
|
795
|
+
if (!res.ok) throw new Error(`OKF export error: ${res.status}`)
|
|
796
|
+
return res.json()
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
// ─── OpenWiki APIs ─────────────────────────────────────────────────────────────
|
|
800
|
+
|
|
801
|
+
export async function fetchOpenWikiNodes(): Promise<import('./types').MemoryNode[]> {
|
|
802
|
+
const res = await apiFetch(`${API_BASE}/api/memory?tags=openwiki`)
|
|
803
|
+
if (!res.ok) throw new Error(`OpenWiki nodes error: ${res.status}`)
|
|
804
|
+
const data = await res.json()
|
|
805
|
+
return Array.isArray(data) ? data : (data.nodes ?? [])
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
// UCW export/import/validate handled via runSandbox + @ssss/cli (SSSS §16)
|
|
809
|
+
|
|
810
|
+
export async function fetchVaultStatus(): Promise<{ totalNodes: number; categories: Record<string, number>; embeddings: number; lastCompiled: string }> {
|
|
811
|
+
const res = await apiFetch(`${API_BASE}/api/vault/status`)
|
|
812
|
+
if (!res.ok) throw new Error(`Vault status error: ${res.status}`)
|
|
813
|
+
return res.json()
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
export async function fetchMemoryStats(): Promise<{ total: number; byCategory: Record<string, number>; byPriority: Record<string, number>; byStatus: Record<string, number> }> {
|
|
817
|
+
const res = await apiFetch(`${API_BASE}/api/memory/stats`)
|
|
818
|
+
if (!res.ok) throw new Error(`Memory stats error: ${res.status}`)
|
|
819
|
+
return res.json()
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
// ─── Design Docs APIs ──────────────────────────────────────────────────────────
|
|
823
|
+
|
|
824
|
+
export async function fetchDesignDocs(): Promise<Array<{ name: string; path: string; size: number; modified: string; category: string }>> {
|
|
825
|
+
const res = await apiFetch(`${API_BASE}/api/files?path=docs`)
|
|
826
|
+
if (!res.ok) throw new Error(`Design docs error: ${res.status}`)
|
|
827
|
+
return res.json()
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
export async function fetchDesignDocContent(path: string): Promise<{ content: string }> {
|
|
831
|
+
const res = await apiFetch(`${API_BASE}/api/files?path=${encodeURIComponent(path)}&content=true`)
|
|
832
|
+
if (!res.ok) throw new Error(`Design doc content error: ${res.status}`)
|
|
833
|
+
return res.json()
|
|
834
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { BarChart, Bar, XAxis, YAxis, Tooltip, ResponsiveContainer, Legend, CartesianGrid } from 'recharts';
|
|
3
|
+
import type { UsageData } from '../types';
|
|
4
|
+
|
|
5
|
+
interface UsageChartProps {
|
|
6
|
+
usageData: UsageData | null;
|
|
7
|
+
geminiModels: any[];
|
|
8
|
+
claudeModels: any[];
|
|
9
|
+
openaiModels: any[];
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const UsageChart: React.FC<UsageChartProps> = ({ usageData }) => {
|
|
13
|
+
if (!usageData || !usageData.timeseries) return null;
|
|
14
|
+
|
|
15
|
+
// Transform timeseries into chart data
|
|
16
|
+
const data = Object.keys(usageData.timeseries).sort().map(dateStr => {
|
|
17
|
+
const dayData = usageData.timeseries![dateStr];
|
|
18
|
+
|
|
19
|
+
let totalUsd = 0;
|
|
20
|
+
const providersCost: Record<string, number> = { gemini: 0, claude: 0, codex: 0, openrouter: 0 };
|
|
21
|
+
|
|
22
|
+
Object.keys(dayData).forEach(model => {
|
|
23
|
+
const { cost, provider } = dayData[model];
|
|
24
|
+
|
|
25
|
+
// Safety fallback to ensure numbers
|
|
26
|
+
const finalCost = typeof cost === 'number' && !isNaN(cost) ? cost : 0;
|
|
27
|
+
|
|
28
|
+
totalUsd += finalCost;
|
|
29
|
+
if (providersCost[provider] !== undefined) {
|
|
30
|
+
providersCost[provider] += finalCost;
|
|
31
|
+
} else if (provider) {
|
|
32
|
+
providersCost[provider] = finalCost;
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
return {
|
|
37
|
+
date: dateStr.split('-').slice(1).join('/'), // MM/DD
|
|
38
|
+
gemini: parseFloat(providersCost.gemini.toFixed(4)),
|
|
39
|
+
claude: parseFloat(providersCost.claude.toFixed(4)),
|
|
40
|
+
codex: parseFloat(providersCost.codex.toFixed(4)),
|
|
41
|
+
openrouter: parseFloat((providersCost.openrouter || 0).toFixed(4)),
|
|
42
|
+
total: parseFloat(totalUsd.toFixed(4))
|
|
43
|
+
};
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
if (data.length === 0) return null;
|
|
47
|
+
|
|
48
|
+
return (
|
|
49
|
+
<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 }}>
|
|
50
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: 10, borderBottom: '1px solid var(--border)', paddingBottom: 12 }}>
|
|
51
|
+
<span style={{ fontSize: 20 }}>📊</span>
|
|
52
|
+
<div>
|
|
53
|
+
<h3 style={{ fontSize: 14, fontWeight: 600 }}>API Costs Over Time</h3>
|
|
54
|
+
<p style={{ fontSize: 11, color: 'var(--text-tertiary)' }}>Historical cost breakdown by provider (USD)</p>
|
|
55
|
+
</div>
|
|
56
|
+
</div>
|
|
57
|
+
<div style={{ width: '100%', height: 250, marginTop: 10 }}>
|
|
58
|
+
<ResponsiveContainer width="100%" height="100%">
|
|
59
|
+
<BarChart data={data} margin={{ top: 5, right: 20, left: -20, bottom: 5 }}>
|
|
60
|
+
<CartesianGrid strokeDasharray="3 3" stroke="rgba(255,255,255,0.05)" />
|
|
61
|
+
<XAxis dataKey="date" stroke="var(--text-tertiary)" fontSize={11} tickLine={false} axisLine={false} />
|
|
62
|
+
<YAxis stroke="var(--text-tertiary)" fontSize={11} tickLine={false} axisLine={false} tickFormatter={(v) => `$${v}`} />
|
|
63
|
+
<Tooltip
|
|
64
|
+
contentStyle={{ background: 'var(--bg-tertiary)', border: '1px solid var(--border)', borderRadius: 8, fontSize: 12 }}
|
|
65
|
+
itemStyle={{ fontSize: 12 }}
|
|
66
|
+
formatter={(value: any) => [`$${Number(value).toFixed(4)}`, undefined]}
|
|
67
|
+
/>
|
|
68
|
+
<Legend wrapperStyle={{ fontSize: 11 }} />
|
|
69
|
+
<Bar isAnimationActive={false} dataKey="gemini" name="Gemini" stackId="a" fill="#10b981" radius={[0,0,0,0]} />
|
|
70
|
+
<Bar isAnimationActive={false} dataKey="claude" name="Claude" stackId="a" fill="#d97757" radius={[0,0,0,0]} />
|
|
71
|
+
<Bar isAnimationActive={false} dataKey="codex" name="OpenAI" stackId="a" fill="#3b82f6" radius={[0,0,0,0]} />
|
|
72
|
+
<Bar isAnimationActive={false} dataKey="openrouter" name="OpenRouter" stackId="a" fill="#8b5cf6" radius={[4,4,0,0]} />
|
|
73
|
+
</BarChart>
|
|
74
|
+
</ResponsiveContainer>
|
|
75
|
+
</div>
|
|
76
|
+
</div>
|
|
77
|
+
);
|
|
78
|
+
};
|