total-recall-brain 3.12.2 → 3.13.1
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/README.md +91 -108
- package/bin/antigravity.mjs +81 -52
- package/bin/total-recall.mjs +32 -57
- package/extension/background.js +1 -1
- package/extension/options/options.js +9 -3
- package/extension/sidepanel/sidepanel.js +0 -2
- package/frontend/index.html +4 -3
- package/frontend/package.json +3 -1
- package/frontend/src/App.tsx +112 -111
- package/frontend/src/api.ts +700 -18
- package/frontend/src/components/BrainSelector.tsx +211 -199
- package/frontend/src/components/DocumentEditorModal.tsx +57 -0
- package/frontend/src/components/DocumentTable.tsx +47 -0
- package/frontend/src/components/Graph3D.tsx +120 -71
- package/frontend/src/components/UsageChart.tsx +78 -0
- package/frontend/src/components/brand/BrandMark.tsx +95 -0
- package/frontend/src/index.css +286 -86
- package/frontend/src/pages/ApiKeysPage.tsx +1878 -215
- package/frontend/src/pages/ChatPage.tsx +103 -45
- package/frontend/src/pages/DesignDocsPage.tsx +614 -0
- package/frontend/src/pages/FilesPage.tsx +10 -6
- package/frontend/src/pages/GraphPage.tsx +5 -3
- package/frontend/src/pages/HelpPage.tsx +2 -2
- package/frontend/src/pages/InboxPage.tsx +82 -0
- package/frontend/src/pages/InstructionsPage.tsx +583 -0
- package/frontend/src/pages/IntegrationsPage.tsx +21 -10
- package/frontend/src/pages/LoginPage.tsx +95 -77
- package/frontend/src/pages/MemoryPage.tsx +31 -8
- package/frontend/src/pages/ModelsPage.tsx +280 -23
- package/frontend/src/pages/OkfPage.tsx +535 -0
- package/frontend/src/pages/OnboardingPage.tsx +690 -0
- package/frontend/src/pages/OpenWikiPage.tsx +561 -0
- package/frontend/src/pages/SettingsPage.tsx +826 -740
- package/frontend/src/pages/SkillsPage.tsx +1259 -117
- package/frontend/src/pages/UsagePage.tsx +256 -11
- package/frontend/src/pages/VaultPage.tsx +223 -0
- package/frontend/src/types.ts +173 -0
- package/package.json +4 -4
- package/scaffold/.agent/skills/total-recall/SKILL.md +43 -4
- package/scaffold/.agent/skills/total-recall/modules/README.md +10 -0
- package/scaffold/.agent/skills/total-recall/modules/agents/README.md +5 -0
- package/scaffold/.agent/skills/total-recall/{skills/tr-cli-agents → modules/agents}/agents.yml +8 -0
- package/scaffold/.agent/skills/total-recall/modules/skill-deploy/README.md +9 -0
- package/scaffold/.agent/skills/total-recall/openwiki/README.md +10 -0
- package/scaffold/.agent/skills/total-recall/openwiki/architecture.md +5 -0
- package/scaffold/.agent/skills/total-recall/openwiki/ide.md +10 -0
- package/scaffold/.agent/skills/total-recall/openwiki/memory.md +11 -0
- package/scaffold/.agent/skills/total-recall/openwiki/secrets.md +7 -0
- package/scaffold/.agent/skills/total-recall/openwiki/skills.md +7 -0
- package/src/cli/brain.mjs +63 -5
- package/src/cli/connect.mjs +11 -19
- package/src/cli/daemon.mjs +5 -4
- package/src/cli/deploy-ui.mjs +8 -3
- package/src/cli/deploy.mjs +84 -27
- package/src/cli/ingest/index.mjs +1 -1
- package/src/cli/init.mjs +63 -34
- package/src/cli/remember.mjs +3 -3
- package/src/cli/secret.mjs +812 -0
- package/src/cli/setup.mjs +34 -7
- package/src/cli/share.mjs +2 -2
- package/src/cli/skill.mjs +396 -35
- package/src/cli/sync.mjs +4 -4
- package/src/cli/task.mjs +287 -0
- package/src/core/clarity-rewriter.mjs +5 -5
- package/src/core/config.mjs +44 -2
- package/src/core/conflict-detector.mjs +5 -5
- package/src/core/crons.mjs +54 -0
- package/src/core/daemon-loop.mjs +87 -235
- package/src/core/dream.mjs +170 -10
- package/src/core/env-import.mjs +456 -0
- package/src/core/fact-seeker.mjs +14 -14
- package/src/core/import-rules.mjs +20 -7
- package/src/core/inference-engine.mjs +3 -3
- package/src/core/migrate.mjs +14 -8
- package/src/core/okf-adapter.mjs +2 -2
- package/src/core/operation-validator.mjs +39 -331
- package/src/core/optimizer.mjs +7 -0
- package/src/core/post-mortem.mjs +2 -2
- package/src/core/project-brain.mjs +349 -0
- package/src/core/provider-catalog.mjs +296 -0
- package/src/core/quick-capture.mjs +1 -1
- package/src/core/remote-vault-sync.mjs +194 -0
- package/src/core/research-queue.mjs +7 -11
- package/src/core/runtime.mjs +33 -9
- package/src/core/scheduler.mjs +59 -5
- package/src/core/schema.mjs +14 -0
- package/src/core/search.mjs +1 -1
- package/src/core/secrets-env-export.mjs +323 -0
- package/src/core/secrets-rotate.mjs +245 -0
- package/src/core/secrets-store.mjs +645 -0
- package/src/core/semantic-index.mjs +3 -3
- package/src/core/skills-registry.mjs +1062 -0
- package/src/core/source-adapters.mjs +4 -1
- package/src/core/ssss-host-extension.mjs +94 -0
- package/src/core/ssss-kernel-bridge.mjs +530 -0
- package/src/core/surface.mjs +14 -0
- package/src/core/task-envelope.mjs +298 -0
- package/src/core/task-executors.mjs +600 -0
- package/src/core/validated-write.mjs +124 -56
- package/src/core/vault.mjs +19 -34
- package/src/core/webauthn-store.mjs +272 -0
- package/src/server/api.mjs +28 -16
- package/src/server/auth.mjs +53 -2
- package/src/server/index.mjs +3 -3
- package/src/server/keys.mjs +30 -4
- package/src/server/rest.mjs +405 -68
- package/src/server/route-manifest.json +168 -0
- package/src/server/routes/_shared.mjs +40 -1
- package/src/server/routes/docs.mjs +260 -0
- package/src/server/routes/memory.mjs +145 -32
- package/src/server/routes/research.mjs +30 -0
- package/src/server/routes/secrets.mjs +470 -0
- package/src/server/routes/share.mjs +2 -2
- package/src/server/routes/skills.mjs +452 -54
- package/src/server/routes/sync.mjs +48 -0
- package/src/server/routes/webauthn.mjs +184 -0
- package/src/server/update-route-manifest.mjs +39 -0
- package/templates/onboarding-interview.md +32 -22
- package/templates/openwiki/README.md +10 -0
- package/templates/openwiki/architecture.md +5 -0
- package/templates/openwiki/ide.md +10 -0
- package/templates/openwiki/memory.md +11 -0
- package/templates/openwiki/secrets.md +7 -0
- package/templates/openwiki/skills.md +7 -0
- package/scaffold/.agent/skills/total-recall/skills/tr-cli-agents/SKILL.md +0 -121
- package/scaffold/.agent/skills/total-recall/skills/tr-cli-agents/references/antigravity.txt +0 -20
- package/scaffold/.agent/skills/total-recall/skills/tr-cli-agents/references/claude.txt +0 -58
- package/scaffold/.agent/skills/total-recall/skills/tr-cli-agents/references/codex.txt +0 -81
- package/scaffold/.agent/skills/total-recall/skills/tr-cli-agents/references/gemini.txt +0 -44
- package/scaffold/.agent/skills/total-recall/skills/tr-cli-agents/scripts/.gitkeep +0 -0
- package/scaffold/.agent/skills/total-recall/skills/tr-cli-agents/subagents/.gitkeep +0 -0
- package/scaffold/.agent/skills/total-recall/skills/tr-research/SKILL.md +0 -338
- package/scaffold/.agent/skills/total-recall/skills/tr-skill/SKILL.md +0 -282
- package/scaffold/.agent/skills/total-recall/skills/tr-skill/assets/skill-template/SKILL.md +0 -20
- package/scaffold/.agent/skills/total-recall/skills/tr-skill/evals/evals.json +0 -20
- package/scaffold/.agent/skills/total-recall/skills/tr-skill/hooks/on-skill-add.sh +0 -14
- package/scaffold/.agent/skills/total-recall/skills/tr-skill/hooks/on-skill-delete.sh +0 -14
- package/scaffold/.agent/skills/total-recall/skills/tr-skill/learnings/.gitkeep +0 -0
- package/scaffold/.agent/skills/total-recall/skills/tr-skill/references/.gitkeep +0 -0
- package/scaffold/.agent/skills/total-recall/skills/tr-skill/scripts/.gitkeep +0 -0
- package/scaffold/.agent/skills/total-recall/skills/tr-skill/scripts/create-skill.sh +0 -65
- package/scaffold/.agent/skills/total-recall/skills/tr-skill/scripts/enforce-skill-optimization.mjs +0 -135
- package/scaffold/.agent/skills/total-recall/skills/tr-skill/scripts/skill-watcher.mjs +0 -64
- package/scaffold/.agent/skills/total-recall/skills/tr-skill/subagents/audit-all-skills.md +0 -62
- package/scaffold/.agent/skills/total-recall/skills/tr-ssss/SKILL.md +0 -298
- package/scaffold/.agent/skills/total-recall/skills/tr-ssss/evals/evals.json +0 -17
- package/scaffold/.agent/skills/total-recall/skills/tr-ssss/references/CHANGELOG.md +0 -124
- package/scaffold/.agent/skills/total-recall/skills/tr-ssss/references/ssss-spec.md +0 -1325
- package/scaffold/.agent/skills/total-recall/skills/tr-ssss/scripts/validate-schema.mjs +0 -232
- package/scaffold/.agent/skills/total-recall/skills/tr-ssss/subagents/audit-ssss-node.md +0 -9
- /package/scaffold/.agent/skills/total-recall/{skills/tr-cli-agents/evals → modules/skill-deploy/scripts}/.gitkeep +0 -0
- /package/scaffold/.agent/skills/total-recall/{skills/tr-skill → modules/skill-deploy}/scripts/find-skills.mjs +0 -0
- /package/scaffold/.agent/skills/total-recall/{skills/tr-skill → modules/skill-deploy}/scripts/install-skill.mjs +0 -0
- /package/scaffold/.agent/skills/total-recall/{skills/tr-skill → modules/skill-deploy}/scripts/scan-skill.mjs +0 -0
|
@@ -12,6 +12,8 @@ interface Graph3DProps {
|
|
|
12
12
|
onOpenThread: (threadId: string) => void
|
|
13
13
|
onGroundMemoryNode: (slug: string) => void
|
|
14
14
|
selectedGroundingNodes: string[]
|
|
15
|
+
/** When false, canvas ignores pointer events (chat background decoration). Default true. */
|
|
16
|
+
interactive?: boolean
|
|
15
17
|
}
|
|
16
18
|
|
|
17
19
|
interface VisualNode {
|
|
@@ -48,6 +50,7 @@ export default function Graph3D({
|
|
|
48
50
|
onOpenThread,
|
|
49
51
|
onGroundMemoryNode,
|
|
50
52
|
selectedGroundingNodes,
|
|
53
|
+
interactive = true,
|
|
51
54
|
}: Graph3DProps) {
|
|
52
55
|
const containerRef = useRef<HTMLDivElement>(null)
|
|
53
56
|
const canvasRef = useRef<HTMLCanvasElement>(null)
|
|
@@ -60,7 +63,7 @@ export default function Graph3D({
|
|
|
60
63
|
const [visibleTypes, setVisibleTypes] = useState({
|
|
61
64
|
research: true,
|
|
62
65
|
observations: true,
|
|
63
|
-
rules:
|
|
66
|
+
rules: true,
|
|
64
67
|
threads: false,
|
|
65
68
|
})
|
|
66
69
|
|
|
@@ -191,7 +194,7 @@ export default function Graph3D({
|
|
|
191
194
|
source: nodeA,
|
|
192
195
|
target: targetNode,
|
|
193
196
|
opacity: 0.7,
|
|
194
|
-
color: "rgba(
|
|
197
|
+
color: "rgba(96, 165, 250, 0.55)"
|
|
195
198
|
})
|
|
196
199
|
}
|
|
197
200
|
})
|
|
@@ -362,7 +365,7 @@ export default function Graph3D({
|
|
|
362
365
|
}
|
|
363
366
|
|
|
364
367
|
if (isHighlight) {
|
|
365
|
-
ctx.strokeStyle = color || (source.type === "memory" ? "rgba(
|
|
368
|
+
ctx.strokeStyle = color || (source.type === "memory" ? "rgba(96, 165, 250, 0.55)" : source.type === "thread" ? "rgba(34, 211, 238, 0.55)" : "rgba(251, 191, 36, 0.55)")
|
|
366
369
|
ctx.lineWidth = 1.8
|
|
367
370
|
} else {
|
|
368
371
|
ctx.strokeStyle = color
|
|
@@ -398,22 +401,37 @@ export default function Graph3D({
|
|
|
398
401
|
size * (isHovered || isSelected ? 2.5 : 1.5)
|
|
399
402
|
)
|
|
400
403
|
|
|
401
|
-
|
|
402
|
-
let
|
|
403
|
-
|
|
404
|
+
// Brand-aligned palette: readable on dark, category-distinct
|
|
405
|
+
let color = "#60a5fa" // facts / default memory (sky)
|
|
406
|
+
let glowColor = "rgba(96, 165, 250, 0.28)"
|
|
407
|
+
|
|
404
408
|
if (node.type === "memory") {
|
|
405
|
-
const category = ((node.originalData as MemoryNode).category ||
|
|
406
|
-
const
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
409
|
+
const category = ((node.originalData as MemoryNode).category || "general").toLowerCase()
|
|
410
|
+
const palette: Record<string, [string, string]> = {
|
|
411
|
+
invariants: ["#38bdf8", "rgba(56, 189, 248, 0.3)"], // cyan-bright — must-rules
|
|
412
|
+
preferences: ["#818cf8", "rgba(129, 140, 248, 0.28)"], // soft indigo
|
|
413
|
+
patterns: ["#34d399", "rgba(52, 211, 153, 0.28)"], // emerald
|
|
414
|
+
"anti-patterns": ["#f87171", "rgba(248, 113, 113, 0.28)"], // rose
|
|
415
|
+
facts: ["#60a5fa", "rgba(96, 165, 250, 0.28)"], // sky blue
|
|
416
|
+
concepts: ["#a78bfa", "rgba(167, 139, 250, 0.28)"], // violet
|
|
417
|
+
decisions: ["#fbbf24", "rgba(251, 191, 36, 0.28)"], // amber
|
|
418
|
+
lore: ["#c084fc", "rgba(192, 132, 252, 0.25)"], // orchid
|
|
419
|
+
corrections: ["#fb923c", "rgba(251, 146, 60, 0.28)"], // orange
|
|
420
|
+
}
|
|
421
|
+
const pair = palette[category]
|
|
422
|
+
if (pair) {
|
|
423
|
+
color = pair[0]
|
|
424
|
+
glowColor = pair[1]
|
|
425
|
+
} else if (["invariants", "preferences", "patterns", "anti-patterns"].includes(category)) {
|
|
426
|
+
color = "#818cf8"
|
|
427
|
+
glowColor = "rgba(129, 140, 248, 0.28)"
|
|
410
428
|
}
|
|
411
429
|
} else if (node.type === "thread") {
|
|
412
|
-
color = "#
|
|
413
|
-
glowColor = "rgba(
|
|
430
|
+
color = "#22d3ee" // cyan — sessions
|
|
431
|
+
glowColor = "rgba(34, 211, 238, 0.28)"
|
|
414
432
|
} else if (node.type === "research") {
|
|
415
|
-
color = "#
|
|
416
|
-
glowColor = "rgba(
|
|
433
|
+
color = "#fbbf24" // amber — research
|
|
434
|
+
glowColor = "rgba(251, 191, 36, 0.28)"
|
|
417
435
|
}
|
|
418
436
|
|
|
419
437
|
gradient.addColorStop(0, color)
|
|
@@ -598,76 +616,110 @@ export default function Graph3D({
|
|
|
598
616
|
}
|
|
599
617
|
|
|
600
618
|
return (
|
|
601
|
-
<div
|
|
619
|
+
<div
|
|
620
|
+
className="graph-3d-wrapper"
|
|
621
|
+
ref={containerRef}
|
|
622
|
+
style={{
|
|
623
|
+
position: 'relative',
|
|
624
|
+
width: '100%',
|
|
625
|
+
height: '100%',
|
|
626
|
+
background: 'radial-gradient(ellipse at 50% 40%, #0c1a33 0%, #070b14 55%, #030712 100%)',
|
|
627
|
+
overflow: 'hidden',
|
|
628
|
+
// Isolate stacking so UI chrome always sits above the canvas
|
|
629
|
+
isolation: 'isolate',
|
|
630
|
+
}}
|
|
631
|
+
>
|
|
602
632
|
|
|
603
|
-
{/* 3D Projection Canvas */}
|
|
633
|
+
{/* 3D Projection Canvas — always under UI chrome */}
|
|
604
634
|
<canvas
|
|
605
635
|
ref={canvasRef}
|
|
606
|
-
onMouseDown={(e) => handleStartDrag(e.clientX, e.clientY)}
|
|
607
|
-
onMouseMove={handleMouseMove}
|
|
608
|
-
onMouseUp={handleEndDrag}
|
|
609
|
-
onMouseLeave={handleEndDrag}
|
|
610
|
-
onClick={handleCanvasClick}
|
|
611
|
-
onWheel={handleWheel}
|
|
612
|
-
onTouchStart={handleTouchStart}
|
|
613
|
-
onTouchMove={handleTouchMove}
|
|
614
|
-
onTouchEnd={handleEndDrag}
|
|
615
|
-
style={{
|
|
636
|
+
onMouseDown={interactive ? (e) => handleStartDrag(e.clientX, e.clientY) : undefined}
|
|
637
|
+
onMouseMove={interactive ? handleMouseMove : undefined}
|
|
638
|
+
onMouseUp={interactive ? handleEndDrag : undefined}
|
|
639
|
+
onMouseLeave={interactive ? handleEndDrag : undefined}
|
|
640
|
+
onClick={interactive ? handleCanvasClick : undefined}
|
|
641
|
+
onWheel={interactive ? handleWheel : undefined}
|
|
642
|
+
onTouchStart={interactive ? handleTouchStart : undefined}
|
|
643
|
+
onTouchMove={interactive ? handleTouchMove : undefined}
|
|
644
|
+
onTouchEnd={interactive ? handleEndDrag : undefined}
|
|
645
|
+
style={{
|
|
646
|
+
position: 'absolute',
|
|
647
|
+
inset: 0,
|
|
648
|
+
zIndex: 1,
|
|
649
|
+
width: '100%',
|
|
650
|
+
height: '100%',
|
|
651
|
+
display: 'block',
|
|
652
|
+
pointerEvents: interactive ? 'auto' : 'none',
|
|
653
|
+
cursor: interactive
|
|
654
|
+
? dragging
|
|
655
|
+
? 'grabbing'
|
|
656
|
+
: hoveredNode
|
|
657
|
+
? 'pointer'
|
|
658
|
+
: 'grab'
|
|
659
|
+
: 'default',
|
|
660
|
+
}}
|
|
616
661
|
/>
|
|
617
662
|
|
|
618
663
|
{/* Cosmic Floating Background Dust overlay */}
|
|
619
|
-
<div style={{ position: 'absolute',
|
|
664
|
+
<div style={{ position: 'absolute', inset: 0, zIndex: 2, pointerEvents: 'none', background: 'radial-gradient(circle at 50% 45%, rgba(59, 130, 246, 0.08) 0%, transparent 55%)' }} />
|
|
620
665
|
|
|
621
666
|
{/* Floating UI Elements */}
|
|
622
|
-
<div style={{ position: 'absolute', top:
|
|
623
|
-
<h3 style={{ margin: 0, fontSize: 18, color: '#
|
|
624
|
-
<p style={{ margin: 0, fontSize: 11, color: 'rgba(
|
|
667
|
+
<div style={{ position: 'absolute', top: 16, left: 16, zIndex: 30, display: 'flex', flexDirection: 'column', gap: 6, pointerEvents: 'none', maxWidth: 280 }}>
|
|
668
|
+
<h3 style={{ margin: 0, fontSize: 18, color: '#f1f5f9', fontWeight: 700, letterSpacing: '-0.02em' }}>Memory Constellation</h3>
|
|
669
|
+
<p style={{ margin: 0, fontSize: 11, color: 'rgba(148,163,184,0.85)' }}>Vault · sessions · research — use filters to toggle types</p>
|
|
625
670
|
</div>
|
|
626
671
|
|
|
627
672
|
{/* Controls Overlay */}
|
|
628
|
-
|
|
673
|
+
{interactive && (
|
|
674
|
+
<div style={{ position: 'absolute', bottom: 20, left: 20, zIndex: 40, display: 'flex', gap: 8, pointerEvents: 'auto' }}>
|
|
629
675
|
<button
|
|
676
|
+
type="button"
|
|
630
677
|
onClick={() => setZoom(prev => Math.min(3.0, prev + 0.2))}
|
|
631
|
-
style={{ width: 32, height: 32, background: 'rgba(15,23,42,0.
|
|
678
|
+
style={{ width: 32, height: 32, background: 'rgba(15,23,42,0.85)', border: '1px solid rgba(255,255,255,0.15)', borderRadius: 8, color: '#fff', fontSize: 16, cursor: 'pointer', display: 'flex', alignItems: 'center', justifyContent: 'center' }}
|
|
632
679
|
title="Zoom In"
|
|
633
680
|
>
|
|
634
681
|
+
|
|
635
682
|
</button>
|
|
636
683
|
<button
|
|
684
|
+
type="button"
|
|
637
685
|
onClick={() => setZoom(prev => Math.max(0.5, prev - 0.2))}
|
|
638
|
-
style={{ width: 32, height: 32, background: 'rgba(15,23,42,0.
|
|
686
|
+
style={{ width: 32, height: 32, background: 'rgba(15,23,42,0.85)', border: '1px solid rgba(255,255,255,0.15)', borderRadius: 8, color: '#fff', fontSize: 16, cursor: 'pointer', display: 'flex', alignItems: 'center', justifyContent: 'center' }}
|
|
639
687
|
title="Zoom Out"
|
|
640
688
|
>
|
|
641
689
|
-
|
|
642
690
|
</button>
|
|
643
691
|
<button
|
|
692
|
+
type="button"
|
|
644
693
|
onClick={() => { angleX.current = 0.3; angleY.current = 0.5; setZoom(1.2) }}
|
|
645
|
-
style={{ width: 32, height: 32, background: 'rgba(15,23,42,0.
|
|
694
|
+
style={{ width: 32, height: 32, background: 'rgba(15,23,42,0.85)', border: '1px solid rgba(255,255,255,0.15)', borderRadius: 8, color: '#fff', fontSize: 11, cursor: 'pointer', display: 'flex', alignItems: 'center', justifyContent: 'center' }}
|
|
646
695
|
title="Reset Camera"
|
|
647
696
|
>
|
|
648
697
|
⟲
|
|
649
698
|
</button>
|
|
650
699
|
</div>
|
|
700
|
+
)}
|
|
651
701
|
|
|
652
|
-
{/* Floating Filter Panel (
|
|
702
|
+
{/* Floating Filter Panel — left side under title so node drawer (right) never covers it */}
|
|
703
|
+
{interactive && (
|
|
653
704
|
<div
|
|
654
|
-
className="glass"
|
|
705
|
+
className="glass graph-type-filters"
|
|
655
706
|
style={{
|
|
656
707
|
position: 'absolute',
|
|
657
|
-
top:
|
|
658
|
-
|
|
708
|
+
top: 72,
|
|
709
|
+
left: 16,
|
|
659
710
|
display: 'flex',
|
|
660
711
|
flexDirection: 'column',
|
|
661
712
|
gap: 10,
|
|
662
|
-
background: 'rgba(10, 15, 30, 0.
|
|
713
|
+
background: 'rgba(10, 15, 30, 0.92)',
|
|
663
714
|
backdropFilter: 'blur(20px)',
|
|
664
715
|
WebkitBackdropFilter: 'blur(20px)',
|
|
665
716
|
padding: '12px 16px',
|
|
666
717
|
borderRadius: 12,
|
|
667
|
-
border: '1px solid rgba(255, 255, 255, 0.
|
|
668
|
-
boxShadow: '0 8px 32px rgba(0,0,0,0.
|
|
718
|
+
border: '1px solid rgba(255, 255, 255, 0.12)',
|
|
719
|
+
boxShadow: '0 8px 32px rgba(0,0,0,0.45)',
|
|
669
720
|
width: 220,
|
|
670
|
-
zIndex:
|
|
721
|
+
zIndex: 60,
|
|
722
|
+
pointerEvents: 'auto',
|
|
671
723
|
}}
|
|
672
724
|
>
|
|
673
725
|
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
|
@@ -680,63 +732,60 @@ export default function Graph3D({
|
|
|
680
732
|
</div>
|
|
681
733
|
|
|
682
734
|
<div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
|
|
683
|
-
{/* 1. Research Projects */}
|
|
684
735
|
<label style={{ display: 'flex', alignItems: 'center', gap: 8, fontSize: 11, color: 'var(--text-secondary)', cursor: 'pointer', userSelect: 'none' }}>
|
|
685
|
-
<input
|
|
686
|
-
type="checkbox"
|
|
687
|
-
checked={visibleTypes.research}
|
|
736
|
+
<input
|
|
737
|
+
type="checkbox"
|
|
738
|
+
checked={visibleTypes.research}
|
|
688
739
|
onChange={() => setVisibleTypes(prev => ({ ...prev, research: !prev.research }))}
|
|
689
|
-
style={{ accentColor: '#
|
|
740
|
+
style={{ accentColor: '#fbbf24', cursor: 'pointer' }}
|
|
690
741
|
/>
|
|
691
|
-
<span style={{ display: 'inline-block', width: 6, height: 6, borderRadius: '50%', background: '#
|
|
692
|
-
<span style={{ flex: 1 }}>Research
|
|
742
|
+
<span style={{ display: 'inline-block', width: 6, height: 6, borderRadius: '50%', background: '#fbbf24', boxShadow: '0 0 6px rgba(251,191,36,0.5)' }} />
|
|
743
|
+
<span style={{ flex: 1 }}>Research</span>
|
|
693
744
|
<span style={{ fontSize: 9, color: 'var(--text-tertiary)' }}>({researchItems.length})</span>
|
|
694
745
|
</label>
|
|
695
746
|
|
|
696
|
-
{/* 2. Observations & Facts */}
|
|
697
747
|
<label style={{ display: 'flex', alignItems: 'center', gap: 8, fontSize: 11, color: 'var(--text-secondary)', cursor: 'pointer', userSelect: 'none' }}>
|
|
698
|
-
<input
|
|
699
|
-
type="checkbox"
|
|
700
|
-
checked={visibleTypes.observations}
|
|
748
|
+
<input
|
|
749
|
+
type="checkbox"
|
|
750
|
+
checked={visibleTypes.observations}
|
|
701
751
|
onChange={() => setVisibleTypes(prev => ({ ...prev, observations: !prev.observations }))}
|
|
702
|
-
style={{ accentColor: '#
|
|
752
|
+
style={{ accentColor: '#60a5fa', cursor: 'pointer' }}
|
|
703
753
|
/>
|
|
704
|
-
<span style={{ display: 'inline-block', width: 6, height: 6, borderRadius: '50%', background: '#
|
|
705
|
-
<span style={{ flex: 1 }}>
|
|
754
|
+
<span style={{ display: 'inline-block', width: 6, height: 6, borderRadius: '50%', background: '#60a5fa', boxShadow: '0 0 6px rgba(96,165,250,0.5)' }} />
|
|
755
|
+
<span style={{ flex: 1 }}>Memory</span>
|
|
706
756
|
<span style={{ fontSize: 9, color: 'var(--text-tertiary)' }}>
|
|
707
757
|
({memoryNodes.filter(m => !['invariants', 'preferences', 'patterns', 'anti-patterns'].includes((m.category || 'general').toLowerCase())).length})
|
|
708
758
|
</span>
|
|
709
759
|
</label>
|
|
710
760
|
|
|
711
|
-
{/* 3. Developer Rules */}
|
|
712
761
|
<label style={{ display: 'flex', alignItems: 'center', gap: 8, fontSize: 11, color: 'var(--text-secondary)', cursor: 'pointer', userSelect: 'none' }}>
|
|
713
|
-
<input
|
|
714
|
-
type="checkbox"
|
|
715
|
-
checked={visibleTypes.rules}
|
|
762
|
+
<input
|
|
763
|
+
type="checkbox"
|
|
764
|
+
checked={visibleTypes.rules}
|
|
716
765
|
onChange={() => setVisibleTypes(prev => ({ ...prev, rules: !prev.rules }))}
|
|
717
|
-
style={{ accentColor: '#
|
|
766
|
+
style={{ accentColor: '#38bdf8', cursor: 'pointer' }}
|
|
718
767
|
/>
|
|
719
|
-
<span style={{ display: 'inline-block', width: 6, height: 6, borderRadius: '50%', background: '
|
|
720
|
-
<span style={{ flex: 1 }}>
|
|
768
|
+
<span style={{ display: 'inline-block', width: 6, height: 6, borderRadius: '50%', background: 'linear-gradient(90deg,#38bdf8,#f87171)', boxShadow: '0 0 6px rgba(56,189,248,0.4)' }} />
|
|
769
|
+
<span style={{ flex: 1 }}>Rules</span>
|
|
721
770
|
<span style={{ fontSize: 9, color: 'var(--text-tertiary)' }}>
|
|
722
771
|
({memoryNodes.filter(m => ['invariants', 'preferences', 'patterns', 'anti-patterns'].includes((m.category || 'general').toLowerCase())).length})
|
|
723
772
|
</span>
|
|
724
773
|
</label>
|
|
725
774
|
|
|
726
|
-
{/* 4. Chat Threads */}
|
|
727
775
|
<label style={{ display: 'flex', alignItems: 'center', gap: 8, fontSize: 11, color: 'var(--text-secondary)', cursor: 'pointer', userSelect: 'none' }}>
|
|
728
|
-
<input
|
|
729
|
-
type="checkbox"
|
|
730
|
-
checked={visibleTypes.threads}
|
|
776
|
+
<input
|
|
777
|
+
type="checkbox"
|
|
778
|
+
checked={visibleTypes.threads}
|
|
731
779
|
onChange={() => setVisibleTypes(prev => ({ ...prev, threads: !prev.threads }))}
|
|
732
|
-
style={{ accentColor: '#
|
|
780
|
+
style={{ accentColor: '#22d3ee', cursor: 'pointer' }}
|
|
733
781
|
/>
|
|
734
|
-
<span style={{ display: 'inline-block', width: 6, height: 6, borderRadius: '50%', background: '#
|
|
735
|
-
<span style={{ flex: 1 }}>
|
|
782
|
+
<span style={{ display: 'inline-block', width: 6, height: 6, borderRadius: '50%', background: '#22d3ee', boxShadow: '0 0 6px rgba(34,211,238,0.5)' }} />
|
|
783
|
+
<span style={{ flex: 1 }}>Sessions</span>
|
|
736
784
|
<span style={{ fontSize: 9, color: 'var(--text-tertiary)' }}>({threads.length})</span>
|
|
737
785
|
</label>
|
|
738
786
|
</div>
|
|
739
787
|
</div>
|
|
788
|
+
)}
|
|
740
789
|
|
|
741
790
|
{/* Selected Node Glass detail overlay drawer */}
|
|
742
791
|
{selectedNode && (
|
|
@@ -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: unknown[];
|
|
8
|
+
claudeModels: unknown[];
|
|
9
|
+
openaiModels: unknown[];
|
|
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
|
+
};
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/** Official Total Recall brand marks from portfolio-site assets/logos. */
|
|
2
|
+
|
|
3
|
+
export type BrandVariant = 'lockup' | 'icon' | 'badge' | 'mark'
|
|
4
|
+
|
|
5
|
+
interface BrandMarkProps {
|
|
6
|
+
variant?: BrandVariant
|
|
7
|
+
/** Height in px (width auto). */
|
|
8
|
+
height?: number
|
|
9
|
+
className?: string
|
|
10
|
+
alt?: string
|
|
11
|
+
/** Light plate behind lockup for dark surfaces (PNG has white bg). */
|
|
12
|
+
plate?: boolean
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const SRC: Record<BrandVariant, string> = {
|
|
16
|
+
lockup: '/brand/total-recall-lockup.png',
|
|
17
|
+
icon: '/brand/total-recall-icon.svg',
|
|
18
|
+
badge: '/brand/total-recall-badge.jpg',
|
|
19
|
+
mark: '/brand/total-recall-mark.svg',
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export default function BrandMark({
|
|
23
|
+
variant = 'lockup',
|
|
24
|
+
height = 40,
|
|
25
|
+
className = '',
|
|
26
|
+
alt = 'Total Recall',
|
|
27
|
+
plate = false,
|
|
28
|
+
}: BrandMarkProps) {
|
|
29
|
+
const isSquare = variant === 'icon' || variant === 'badge' || variant === 'mark'
|
|
30
|
+
const img = (
|
|
31
|
+
<img
|
|
32
|
+
src={SRC[variant]}
|
|
33
|
+
alt={alt}
|
|
34
|
+
height={height}
|
|
35
|
+
width={isSquare ? height : undefined}
|
|
36
|
+
className={className}
|
|
37
|
+
style={{
|
|
38
|
+
height,
|
|
39
|
+
width: isSquare ? height : 'auto',
|
|
40
|
+
maxWidth: '100%',
|
|
41
|
+
objectFit: 'contain',
|
|
42
|
+
display: 'block',
|
|
43
|
+
}}
|
|
44
|
+
draggable={false}
|
|
45
|
+
/>
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
if (!plate) return img
|
|
49
|
+
|
|
50
|
+
return (
|
|
51
|
+
<div
|
|
52
|
+
className="brand-plate"
|
|
53
|
+
style={{
|
|
54
|
+
display: 'inline-flex',
|
|
55
|
+
alignItems: 'center',
|
|
56
|
+
justifyContent: 'center',
|
|
57
|
+
padding: variant === 'lockup' ? '10px 14px' : 8,
|
|
58
|
+
borderRadius: variant === 'lockup' ? 14 : 16,
|
|
59
|
+
background: 'linear-gradient(180deg, #f8fafc 0%, #eef2ff 100%)',
|
|
60
|
+
boxShadow:
|
|
61
|
+
'0 1px 0 rgba(255,255,255,0.6) inset, 0 12px 32px rgba(15, 23, 42, 0.35), 0 0 0 1px rgba(148,163,184,0.25)',
|
|
62
|
+
}}
|
|
63
|
+
>
|
|
64
|
+
{img}
|
|
65
|
+
</div>
|
|
66
|
+
)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** Compact cube-style brand chip for nav / floating chrome. */
|
|
70
|
+
export function BrandChip({ size = 36 }: { size?: number }) {
|
|
71
|
+
return (
|
|
72
|
+
<div
|
|
73
|
+
className="brand-chip"
|
|
74
|
+
style={{
|
|
75
|
+
width: size,
|
|
76
|
+
height: size,
|
|
77
|
+
borderRadius: Math.round(size * 0.28),
|
|
78
|
+
overflow: 'hidden',
|
|
79
|
+
flexShrink: 0,
|
|
80
|
+
boxShadow:
|
|
81
|
+
'0 0 0 1px rgba(96,165,250,0.35), 0 8px 24px rgba(37,99,235,0.25)',
|
|
82
|
+
background: 'linear-gradient(145deg, #1e3a8a, #0b1220)',
|
|
83
|
+
}}
|
|
84
|
+
>
|
|
85
|
+
<img
|
|
86
|
+
src="/brand/total-recall-icon.svg"
|
|
87
|
+
alt=""
|
|
88
|
+
width={size}
|
|
89
|
+
height={size}
|
|
90
|
+
style={{ width: '100%', height: '100%', objectFit: 'cover' }}
|
|
91
|
+
draggable={false}
|
|
92
|
+
/>
|
|
93
|
+
</div>
|
|
94
|
+
)
|
|
95
|
+
}
|