total-recall-brain 3.21.2 → 3.22.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/dist/assets/index-C7_Xpk27.css +1 -0
- package/frontend/dist/assets/{index-D0XeeXhG.js → index-DJjuZ3t3.js} +106 -106
- package/frontend/dist/index.html +2 -2
- package/frontend/src/App.spec.tsx +1 -1
- package/frontend/src/api/_base.ts +3 -3
- package/frontend/src/api/embeddings.ts +46 -0
- package/frontend/src/api/mesh.ts +47 -0
- package/frontend/src/api/network.spec.ts +9 -9
- package/frontend/src/api/network.ts +4 -4
- package/frontend/src/api/notifications.ts +5 -4
- package/frontend/src/api/webhooks.ts +1 -1
- package/frontend/src/components/BrainSelector.spec.tsx +4 -4
- package/frontend/src/components/BrainSelector.tsx +1 -1
- package/frontend/src/components/ContextualHelp.spec.tsx +14 -14
- package/frontend/src/components/ContextualHelp.tsx +3 -1
- package/frontend/src/components/DocumentTable.spec.tsx +1 -1
- package/frontend/src/components/EmbeddingProviderPanel.spec.tsx +127 -0
- package/frontend/src/components/EmbeddingProviderPanel.tsx +177 -0
- package/frontend/src/components/Graph3D.spec.tsx +4 -4
- package/frontend/src/components/Graph3D.tsx +3 -3
- package/frontend/src/components/MobilePairing.spec.tsx +1 -1
- package/frontend/src/components/MobilePairing.tsx +1 -1
- package/frontend/src/components/UsageChart.spec.tsx +4 -4
- package/frontend/src/components/UsageChart.tsx +1 -1
- package/frontend/src/components/VoiceInput.spec.tsx +6 -6
- package/frontend/src/components/VoiceInput.tsx +19 -4
- package/frontend/src/components/brand/BrandMark.spec.tsx +3 -4
- package/frontend/src/index.css +6 -0
- package/frontend/src/pages/AutomationsPage.spec.tsx +4 -4
- package/frontend/src/pages/ChatPage.spec.tsx +3 -3
- package/frontend/src/pages/CollabPage.spec.tsx +1 -2
- package/frontend/src/pages/DesignDocsPage.spec.tsx +1 -1
- package/frontend/src/pages/FilesPage.spec.tsx +2 -2
- package/frontend/src/pages/GraphPage.spec.tsx +1 -1
- package/frontend/src/pages/HealthPage.spec.tsx +2 -2
- package/frontend/src/pages/HelpPage.spec.tsx +5 -5
- package/frontend/src/pages/InboxPage.spec.tsx +4 -4
- package/frontend/src/pages/InstructionsPage.spec.tsx +5 -5
- package/frontend/src/pages/IntegrationsPage.spec.tsx +7 -7
- package/frontend/src/pages/MemoryPage.spec.tsx +2 -2
- package/frontend/src/pages/MeshPage.spec.tsx +111 -2
- package/frontend/src/pages/MeshPage.tsx +95 -11
- package/frontend/src/pages/NetworkPage.spec.tsx +7 -8
- package/frontend/src/pages/NetworkPage.tsx +12 -12
- package/frontend/src/pages/NotificationsPage.tsx +9 -9
- package/frontend/src/pages/OnboardingPage.spec.tsx +7 -7
- package/frontend/src/pages/OpenWikiPage.tsx +3 -3
- package/frontend/src/pages/RulesPage.tsx +3 -3
- package/frontend/src/pages/SecretsPage.spec.tsx +5 -5
- package/frontend/src/pages/SecretsPage.tsx +13 -14
- package/frontend/src/pages/SettingsPage.spec.tsx +6 -6
- package/frontend/src/pages/SettingsPage.tsx +2 -2
- package/frontend/src/pages/SkillsPage.spec.tsx +1 -2
- package/frontend/src/pages/SkillsPage.tsx +3 -3
- package/frontend/src/pages/TasksPage.spec.tsx +3 -3
- package/frontend/src/pages/UsagePage.spec.tsx +2 -2
- package/frontend/src/pages/UsagePage.tsx +1 -1
- package/frontend/src/pages/WebhooksPage.spec.tsx +1 -1
- package/frontend/src/pages/WebhooksPage.tsx +19 -19
- package/frontend/src/setupTests.ts +4 -4
- package/package.json +1 -1
- package/scaffold/.agent/skills/total-recall/SKILL.md +92 -0
- package/scaffold/.agent/skills/total-recall/modules/agents/agents.yml +15 -13
- package/scaffold/.agent/skills/total-recall/research-queue.jsonl +0 -1
- package/src/cli/secret.mjs +93 -12
- package/src/core/browser-session.mjs +185 -0
- package/src/core/daemon-loop.mjs +28 -1
- package/src/core/embeddings.mjs +52 -10
- package/src/core/headscale-client.mjs +227 -0
- package/src/core/mesh-enroll.mjs +439 -0
- package/src/core/ollama-embeddings.mjs +336 -0
- package/src/core/provider-catalog.mjs +175 -6
- package/src/core/provider-rotation-recipes.mjs +299 -0
- package/src/core/rotation-capability.mjs +189 -0
- package/src/core/secrets-rotate.mjs +192 -0
- package/src/server/rest.mjs +2 -0
- package/src/server/route-manifest.json +20 -0
- package/src/server/routes/embeddings.mjs +111 -0
- package/src/server/routes/headscale.mjs +21 -81
- package/src/server/routes/mesh.mjs +41 -0
- package/frontend/dist/assets/index-B4RASWbV.css +0 -1
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { useCallback, useEffect, useState } from 'react'
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
fetchEmbeddingProvider,
|
|
5
|
+
setEmbeddingModel,
|
|
6
|
+
rediscoverEmbeddingProvider,
|
|
7
|
+
type EmbeddingProviderStatus,
|
|
8
|
+
type EmbeddingCandidate,
|
|
9
|
+
} from '../api/embeddings'
|
|
10
|
+
|
|
11
|
+
function formatSize(bytes: number | null): string {
|
|
12
|
+
if (!bytes) return '—'
|
|
13
|
+
if (bytes >= 1e9) return `${(bytes / 1e9).toFixed(1)} GB`
|
|
14
|
+
return `${Math.round(bytes / 1e6)} MB`
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Explain why a model cannot be used, rather than hiding it. An operator who
|
|
19
|
+
* pulled the wrong model needs to see it listed with the reason attached.
|
|
20
|
+
*/
|
|
21
|
+
function incompatibilityReason(c: EmbeddingCandidate, dims: number): string {
|
|
22
|
+
if (!c.embedding) return 'chat model — cannot embed'
|
|
23
|
+
if (c.dims !== dims) return `${c.dims ?? '?'} dims — index needs ${dims}`
|
|
24
|
+
return ''
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function EmbeddingProviderPanel() {
|
|
28
|
+
const [status, setStatus] = useState<EmbeddingProviderStatus | null>(null)
|
|
29
|
+
const [error, setError] = useState<string | null>(null)
|
|
30
|
+
const [busy, setBusy] = useState(false)
|
|
31
|
+
|
|
32
|
+
const load = useCallback(async () => {
|
|
33
|
+
try {
|
|
34
|
+
setStatus(await fetchEmbeddingProvider())
|
|
35
|
+
setError(null)
|
|
36
|
+
} catch (err) {
|
|
37
|
+
setError(err instanceof Error ? err.message : String(err))
|
|
38
|
+
}
|
|
39
|
+
}, [])
|
|
40
|
+
|
|
41
|
+
useEffect(() => {
|
|
42
|
+
void load()
|
|
43
|
+
}, [load])
|
|
44
|
+
|
|
45
|
+
const onPin = useCallback(
|
|
46
|
+
async (model: string | null) => {
|
|
47
|
+
setBusy(true)
|
|
48
|
+
try {
|
|
49
|
+
await setEmbeddingModel(model)
|
|
50
|
+
await load()
|
|
51
|
+
setError(null)
|
|
52
|
+
} catch (err) {
|
|
53
|
+
setError(err instanceof Error ? err.message : String(err))
|
|
54
|
+
} finally {
|
|
55
|
+
setBusy(false)
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
[load],
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
const onRediscover = useCallback(async () => {
|
|
62
|
+
setBusy(true)
|
|
63
|
+
try {
|
|
64
|
+
await rediscoverEmbeddingProvider()
|
|
65
|
+
await load()
|
|
66
|
+
setError(null)
|
|
67
|
+
} catch (err) {
|
|
68
|
+
setError(err instanceof Error ? err.message : String(err))
|
|
69
|
+
} finally {
|
|
70
|
+
setBusy(false)
|
|
71
|
+
}
|
|
72
|
+
}, [load])
|
|
73
|
+
|
|
74
|
+
if (error && !status) {
|
|
75
|
+
return (
|
|
76
|
+
<div className="panel">
|
|
77
|
+
<h3 style={{ marginTop: 0 }}>Embedding Provider</h3>
|
|
78
|
+
<p role="alert">{error}</p>
|
|
79
|
+
</div>
|
|
80
|
+
)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (!status) {
|
|
84
|
+
return (
|
|
85
|
+
<div className="panel">
|
|
86
|
+
<h3 style={{ marginTop: 0 }}>Embedding Provider</h3>
|
|
87
|
+
<p>Loading…</p>
|
|
88
|
+
</div>
|
|
89
|
+
)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const { local, fallbacks, dims } = status
|
|
93
|
+
|
|
94
|
+
return (
|
|
95
|
+
<div className="panel">
|
|
96
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: 12, flexWrap: 'wrap' }}>
|
|
97
|
+
<h3 style={{ margin: 0 }}>Embedding Provider</h3>
|
|
98
|
+
<span
|
|
99
|
+
data-testid="embed-availability"
|
|
100
|
+
style={{ color: local.available ? 'var(--ok, #2ea043)' : 'var(--warn, #d29922)' }}
|
|
101
|
+
>
|
|
102
|
+
{local.available ? '● local' : '○ hosted fallback'}
|
|
103
|
+
</span>
|
|
104
|
+
<button type="button" onClick={onRediscover} disabled={busy}>
|
|
105
|
+
Re-discover
|
|
106
|
+
</button>
|
|
107
|
+
</div>
|
|
108
|
+
|
|
109
|
+
<dl style={{ display: 'grid', gridTemplateColumns: 'auto 1fr', gap: '4px 12px', margin: '12px 0' }}>
|
|
110
|
+
<dt>Endpoint</dt>
|
|
111
|
+
<dd data-testid="embed-endpoint" style={{ margin: 0 }}>
|
|
112
|
+
{local.endpoint ?? 'none reachable'}
|
|
113
|
+
</dd>
|
|
114
|
+
<dt>Model</dt>
|
|
115
|
+
<dd data-testid="embed-selected" style={{ margin: 0 }}>
|
|
116
|
+
{local.selected ?? '—'}
|
|
117
|
+
{local.preferred ? ' (pinned)' : local.selected ? ' (auto)' : ''}
|
|
118
|
+
</dd>
|
|
119
|
+
<dt>Vector width</dt>
|
|
120
|
+
<dd style={{ margin: 0 }}>{dims}</dd>
|
|
121
|
+
</dl>
|
|
122
|
+
|
|
123
|
+
{local.reason && <p role="status">{local.reason}</p>}
|
|
124
|
+
{error && <p role="alert">{error}</p>}
|
|
125
|
+
|
|
126
|
+
{local.candidates.length > 0 && (
|
|
127
|
+
<table style={{ width: '100%', borderCollapse: 'collapse' }}>
|
|
128
|
+
<thead>
|
|
129
|
+
<tr>
|
|
130
|
+
<th style={{ textAlign: 'left' }}>Model</th>
|
|
131
|
+
<th style={{ textAlign: 'left' }}>Size</th>
|
|
132
|
+
<th style={{ textAlign: 'left' }}>Status</th>
|
|
133
|
+
<th />
|
|
134
|
+
</tr>
|
|
135
|
+
</thead>
|
|
136
|
+
<tbody>
|
|
137
|
+
{local.candidates.map((c) => {
|
|
138
|
+
const reason = incompatibilityReason(c, dims)
|
|
139
|
+
const isSelected = c.name === local.selected
|
|
140
|
+
return (
|
|
141
|
+
<tr key={c.name}>
|
|
142
|
+
<td>{c.name}</td>
|
|
143
|
+
<td>{formatSize(c.size)}</td>
|
|
144
|
+
<td style={{ opacity: c.compatible ? 1 : 0.6 }}>
|
|
145
|
+
{c.compatible ? `${c.dims} dims` : reason}
|
|
146
|
+
</td>
|
|
147
|
+
<td style={{ textAlign: 'right' }}>
|
|
148
|
+
{c.compatible && !isSelected && (
|
|
149
|
+
<button type="button" onClick={() => onPin(c.name)} disabled={busy}>
|
|
150
|
+
Use
|
|
151
|
+
</button>
|
|
152
|
+
)}
|
|
153
|
+
{isSelected && local.preferred && (
|
|
154
|
+
<button type="button" onClick={() => onPin(null)} disabled={busy}>
|
|
155
|
+
Unpin
|
|
156
|
+
</button>
|
|
157
|
+
)}
|
|
158
|
+
</td>
|
|
159
|
+
</tr>
|
|
160
|
+
)
|
|
161
|
+
})}
|
|
162
|
+
</tbody>
|
|
163
|
+
</table>
|
|
164
|
+
)}
|
|
165
|
+
|
|
166
|
+
<div style={{ marginTop: 12, fontSize: '0.9em', opacity: 0.8 }}>
|
|
167
|
+
Fallback chain:{' '}
|
|
168
|
+
{fallbacks.map((f, i) => (
|
|
169
|
+
<span key={f.provider}>
|
|
170
|
+
{i > 0 && ' → '}
|
|
171
|
+
<span style={{ textDecoration: f.configured ? 'none' : 'line-through' }}>{f.provider}</span>
|
|
172
|
+
</span>
|
|
173
|
+
))}
|
|
174
|
+
</div>
|
|
175
|
+
</div>
|
|
176
|
+
)
|
|
177
|
+
}
|
|
@@ -29,7 +29,7 @@ describe('Graph3D', () => {
|
|
|
29
29
|
});
|
|
30
30
|
|
|
31
31
|
// Mock requestAnimationFrame
|
|
32
|
-
vi.spyOn(window, 'requestAnimationFrame').mockImplementation((
|
|
32
|
+
vi.spyOn(window, 'requestAnimationFrame').mockImplementation(() => {
|
|
33
33
|
// Don't actually loop, just call once for coverage
|
|
34
34
|
return 1;
|
|
35
35
|
});
|
|
@@ -66,9 +66,9 @@ describe('Graph3D', () => {
|
|
|
66
66
|
render(
|
|
67
67
|
<Graph3D
|
|
68
68
|
{...defaultProps}
|
|
69
|
-
threads={[{ id: 't1', title: 'Thread 1', turns: 2, lastUpdated: new Date().toISOString() } as
|
|
70
|
-
memoryNodes={[{ slug: 'm1', title: 'Memory 1', category: 'facts', content: 'test', confidence: 0.9 } as
|
|
71
|
-
researchItems={[{ id: 'r1', topic: 'Research 1', status: 'active', created_at: new Date().toISOString() } as
|
|
69
|
+
threads={[{ id: 't1', title: 'Thread 1', turns: 2, lastUpdated: new Date().toISOString() } as never]}
|
|
70
|
+
memoryNodes={[{ slug: 'm1', title: 'Memory 1', category: 'facts', content: 'test', confidence: 0.9 } as never]}
|
|
71
|
+
researchItems={[{ id: 'r1', topic: 'Research 1', status: 'active', created_at: new Date().toISOString() } as never]}
|
|
72
72
|
/>
|
|
73
73
|
);
|
|
74
74
|
|
|
@@ -341,9 +341,9 @@ export default function Graph3D({
|
|
|
341
341
|
const isFocus = !hoverNode || srcId === hoverNode?.id || tgtId === hoverNode?.id;
|
|
342
342
|
|
|
343
343
|
const hex = link.colorHex || '#475569';
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
344
|
+
const r = parseInt(hex.slice(1, 3), 16) || 71;
|
|
345
|
+
const g = parseInt(hex.slice(3, 5), 16) || 85;
|
|
346
|
+
const b = parseInt(hex.slice(5, 7), 16) || 105;
|
|
347
347
|
|
|
348
348
|
const baseOpacity = link.type === 'implicit' ? 0.2 : 0.6;
|
|
349
349
|
const opacity = isFocus ? baseOpacity : 0.02;
|
|
@@ -7,8 +7,8 @@ vi.mock('recharts', () => {
|
|
|
7
7
|
const OriginalRecharts = vi.importActual('recharts');
|
|
8
8
|
return {
|
|
9
9
|
...OriginalRecharts,
|
|
10
|
-
ResponsiveContainer: ({ children }:
|
|
11
|
-
BarChart: ({ children, data }:
|
|
10
|
+
ResponsiveContainer: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
|
|
11
|
+
BarChart: ({ children, data }: { children: React.ReactNode, data: unknown }) => (
|
|
12
12
|
<div data-testid="bar-chart" data-data={JSON.stringify(data)}>
|
|
13
13
|
{children}
|
|
14
14
|
</div>
|
|
@@ -39,7 +39,7 @@ describe('UsageChart', () => {
|
|
|
39
39
|
const { container } = render(<UsageChart {...defaultProps} usageData={{
|
|
40
40
|
breakdown: { gemini: { cost: 0 }, claude: { cost: 0 }, codex: { cost: 0 } },
|
|
41
41
|
timeseries: {}
|
|
42
|
-
} as
|
|
42
|
+
} as never} />);
|
|
43
43
|
expect(container).toBeEmptyDOMElement();
|
|
44
44
|
});
|
|
45
45
|
|
|
@@ -57,7 +57,7 @@ describe('UsageChart', () => {
|
|
|
57
57
|
}
|
|
58
58
|
};
|
|
59
59
|
|
|
60
|
-
render(<UsageChart {...defaultProps} usageData={usageData as
|
|
60
|
+
render(<UsageChart {...defaultProps} usageData={usageData as never} />);
|
|
61
61
|
|
|
62
62
|
expect(screen.getByText('API Costs Over Time')).toBeInTheDocument();
|
|
63
63
|
|
|
@@ -63,7 +63,7 @@ export const UsageChart: React.FC<UsageChartProps> = ({ usageData }) => {
|
|
|
63
63
|
<Tooltip
|
|
64
64
|
contentStyle={{ background: 'var(--bg-tertiary)', border: '1px solid var(--border)', borderRadius: 8, fontSize: 12 }}
|
|
65
65
|
itemStyle={{ fontSize: 12 }}
|
|
66
|
-
formatter={(value:
|
|
66
|
+
formatter={(value: unknown) => [`$${Number(value).toFixed(4)}`, undefined]}
|
|
67
67
|
/>
|
|
68
68
|
<Legend wrapperStyle={{ fontSize: 11 }} />
|
|
69
69
|
<Bar isAnimationActive={false} dataKey="gemini" name="Gemini" stackId="a" fill="#10b981" radius={[0,0,0,0]} />
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
-
import { render, screen, fireEvent } from '@testing-library/react';
|
|
2
|
+
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
|
|
3
3
|
import { VoiceInput } from './VoiceInput';
|
|
4
4
|
|
|
5
5
|
describe('VoiceInput', () => {
|
|
6
|
-
it('renders disabled button if SpeechRecognition is not supported', () => {
|
|
6
|
+
it('renders disabled button if SpeechRecognition is not supported', async () => {
|
|
7
7
|
// Delete any mock to simulate unsupported browser
|
|
8
|
-
delete (window as
|
|
9
|
-
delete (window as
|
|
8
|
+
delete (window as typeof window & { SpeechRecognition?: unknown }).SpeechRecognition;
|
|
9
|
+
delete (window as typeof window & { webkitSpeechRecognition?: unknown }).webkitSpeechRecognition;
|
|
10
10
|
|
|
11
11
|
render(<VoiceInput onTranscript={() => {}} />);
|
|
12
12
|
const button = screen.getByRole('button');
|
|
13
|
-
expect(button).toBeDisabled();
|
|
13
|
+
await waitFor(() => expect(button).toBeDisabled());
|
|
14
14
|
expect(button.title).toBe('Voice input not supported');
|
|
15
15
|
});
|
|
16
16
|
|
|
@@ -25,7 +25,7 @@ describe('VoiceInput', () => {
|
|
|
25
25
|
stop = mockStop;
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
(window as
|
|
28
|
+
(window as typeof window & { SpeechRecognition?: unknown }).SpeechRecognition = MockSpeechRecognition;
|
|
29
29
|
|
|
30
30
|
render(<VoiceInput onTranscript={() => {}} />);
|
|
31
31
|
const button = screen.getByRole('button');
|
|
@@ -5,20 +5,35 @@ interface VoiceInputProps {
|
|
|
5
5
|
isRecording?: boolean;
|
|
6
6
|
}
|
|
7
7
|
|
|
8
|
+
interface SpeechRecognitionObj {
|
|
9
|
+
continuous: boolean;
|
|
10
|
+
interimResults: boolean;
|
|
11
|
+
onresult: ((event: { results: { transcript: string }[][] }) => void) | null;
|
|
12
|
+
onerror: (() => void) | null;
|
|
13
|
+
onend: (() => void) | null;
|
|
14
|
+
start: () => void;
|
|
15
|
+
stop: () => void;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
interface SpeechRecognitionConstructor {
|
|
19
|
+
new (): SpeechRecognitionObj;
|
|
20
|
+
}
|
|
21
|
+
|
|
8
22
|
export function VoiceInput({ onTranscript, isRecording = false }: VoiceInputProps) {
|
|
9
23
|
const [recording, setRecording] = useState(isRecording);
|
|
10
|
-
const recognitionRef = useRef<
|
|
24
|
+
const recognitionRef = useRef<SpeechRecognitionObj | null>(null);
|
|
11
25
|
const [supported, setSupported] = useState(true);
|
|
12
26
|
|
|
13
27
|
useEffect(() => {
|
|
14
28
|
if (typeof window !== 'undefined') {
|
|
15
|
-
const
|
|
29
|
+
const win = window as unknown as { SpeechRecognition?: SpeechRecognitionConstructor, webkitSpeechRecognition?: SpeechRecognitionConstructor };
|
|
30
|
+
const SpeechRecognition = win.SpeechRecognition || win.webkitSpeechRecognition;
|
|
16
31
|
if (SpeechRecognition) {
|
|
17
32
|
recognitionRef.current = new SpeechRecognition();
|
|
18
33
|
recognitionRef.current.continuous = false;
|
|
19
34
|
recognitionRef.current.interimResults = false;
|
|
20
35
|
|
|
21
|
-
recognitionRef.current.onresult = (event
|
|
36
|
+
recognitionRef.current.onresult = (event) => {
|
|
22
37
|
const transcript = event.results[0][0].transcript;
|
|
23
38
|
onTranscript(transcript);
|
|
24
39
|
setRecording(false);
|
|
@@ -32,7 +47,7 @@ export function VoiceInput({ onTranscript, isRecording = false }: VoiceInputProp
|
|
|
32
47
|
setRecording(false);
|
|
33
48
|
};
|
|
34
49
|
} else {
|
|
35
|
-
setSupported(false);
|
|
50
|
+
setTimeout(() => setSupported(false), 0);
|
|
36
51
|
}
|
|
37
52
|
}
|
|
38
53
|
}, [onTranscript]);
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
import { describe, it, expect, vi } from 'vitest';
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
3
2
|
import { render, screen } from '@testing-library/react';
|
|
4
3
|
import BrandMark, { BrandChip } from './BrandMark';
|
|
5
4
|
|
|
@@ -58,7 +57,7 @@ describe('BrandMark', () => {
|
|
|
58
57
|
describe('BrandChip', () => {
|
|
59
58
|
it('renders with default size', () => {
|
|
60
59
|
render(<BrandChip />);
|
|
61
|
-
const chip = document.querySelector('.brand-chip');
|
|
60
|
+
const chip = document.querySelector('.brand-chip') as HTMLElement | null;
|
|
62
61
|
expect(chip).toBeInTheDocument();
|
|
63
62
|
expect(chip?.style.width).toBe('36px');
|
|
64
63
|
expect(chip?.style.height).toBe('36px');
|
|
@@ -66,7 +65,7 @@ describe('BrandChip', () => {
|
|
|
66
65
|
|
|
67
66
|
it('renders with custom size', () => {
|
|
68
67
|
render(<BrandChip size={64} />);
|
|
69
|
-
const chip = document.querySelector('.brand-chip');
|
|
68
|
+
const chip = document.querySelector('.brand-chip') as HTMLElement | null;
|
|
70
69
|
expect(chip?.style.width).toBe('64px');
|
|
71
70
|
expect(chip?.style.height).toBe('64px');
|
|
72
71
|
});
|
package/frontend/src/index.css
CHANGED
|
@@ -377,6 +377,12 @@ code, pre { font-family: var(--font-mono); }
|
|
|
377
377
|
color: var(--success);
|
|
378
378
|
}
|
|
379
379
|
|
|
380
|
+
.alert-warning {
|
|
381
|
+
background: var(--warning-muted);
|
|
382
|
+
border-color: rgba(251, 191, 36, 0.25);
|
|
383
|
+
color: var(--warning);
|
|
384
|
+
}
|
|
385
|
+
|
|
380
386
|
/* ─── Form Controls ──────────────────────────────────────────────────────────── */
|
|
381
387
|
|
|
382
388
|
.input, .select {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
|
|
2
2
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
3
3
|
import { render, screen, waitFor, fireEvent } from '@testing-library/react';
|
|
4
4
|
import AutomationsPage from './AutomationsPage';
|
|
@@ -13,7 +13,7 @@ describe('AutomationsPage', () => {
|
|
|
13
13
|
});
|
|
14
14
|
|
|
15
15
|
it('renders correctly and triggers dream cycle', async () => {
|
|
16
|
-
vi.mocked(api.triggerDream).mockResolvedValue({ status: 'completed' });
|
|
16
|
+
vi.mocked(api.triggerDream).mockResolvedValue({ status: 'completed' } as never);
|
|
17
17
|
|
|
18
18
|
render(<AutomationsPage />);
|
|
19
19
|
|
|
@@ -29,7 +29,7 @@ describe('AutomationsPage', () => {
|
|
|
29
29
|
});
|
|
30
30
|
|
|
31
31
|
it('triggers recompilation', async () => {
|
|
32
|
-
vi.mocked(api.triggerRecompile).mockResolvedValue({ message: 'Brain surfaces recompiled successfully!' });
|
|
32
|
+
vi.mocked(api.triggerRecompile).mockResolvedValue({ message: 'Brain surfaces recompiled successfully!' } as never);
|
|
33
33
|
|
|
34
34
|
render(<AutomationsPage />);
|
|
35
35
|
|
|
@@ -43,7 +43,7 @@ describe('AutomationsPage', () => {
|
|
|
43
43
|
});
|
|
44
44
|
|
|
45
45
|
it('enqueues a task', async () => {
|
|
46
|
-
vi.mocked(api.createTask).mockResolvedValue({} as
|
|
46
|
+
vi.mocked(api.createTask).mockResolvedValue({} as never);
|
|
47
47
|
|
|
48
48
|
render(<AutomationsPage />);
|
|
49
49
|
|
|
@@ -14,10 +14,10 @@ describe('ChatPage', () => {
|
|
|
14
14
|
|
|
15
15
|
it('renders correctly', async () => {
|
|
16
16
|
vi.mocked(api.fetchChatThreads).mockResolvedValue([]);
|
|
17
|
-
vi.mocked(api.listResearch).mockResolvedValue({ items: [] } as
|
|
18
|
-
vi.mocked(api.fetchHealth).mockResolvedValue({ cli_agents: ['antigravity', 'claude'] } as
|
|
17
|
+
vi.mocked(api.listResearch).mockResolvedValue({ items: [] } as never);
|
|
18
|
+
vi.mocked(api.fetchHealth).mockResolvedValue({ cli_agents: ['antigravity', 'claude'] } as never);
|
|
19
19
|
vi.mocked(api.fetchGeminiModels).mockResolvedValue([]);
|
|
20
|
-
vi.mocked(api.checkUpdate).mockResolvedValue({ updateAvailable: false } as
|
|
20
|
+
vi.mocked(api.checkUpdate).mockResolvedValue({ updateAvailable: false } as never);
|
|
21
21
|
vi.mocked(api.fetchExtensionStatus).mockResolvedValue({ available: false, connected: false });
|
|
22
22
|
vi.mocked(api.listMemory).mockResolvedValue([]);
|
|
23
23
|
vi.mocked(api.fetchChatHistory).mockResolvedValue([]);
|
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
// @ts-nocheck
|
|
2
1
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
3
2
|
import { render, screen, waitFor, fireEvent } from '@testing-library/react';
|
|
4
3
|
import CollabPage from './CollabPage';
|
|
5
4
|
|
|
6
5
|
// Mock fetch globally
|
|
7
6
|
const mockFetch = vi.fn();
|
|
8
|
-
|
|
7
|
+
globalThis.fetch = mockFetch as unknown as typeof fetch;
|
|
9
8
|
|
|
10
9
|
describe('CollabPage', () => {
|
|
11
10
|
beforeEach(() => {
|
|
@@ -13,7 +13,7 @@ describe('DesignDocsPage', () => {
|
|
|
13
13
|
it('renders correctly', async () => {
|
|
14
14
|
vi.mocked(api.fetchDesignDocs).mockResolvedValue([
|
|
15
15
|
{ name: 'Architecture Overview', path: 'docs/architecture.md', category: 'Core' }
|
|
16
|
-
] as
|
|
16
|
+
] as never);
|
|
17
17
|
vi.mocked(api.fetchDesignDocContent).mockResolvedValue({ content: '# Architecture Overview' });
|
|
18
18
|
|
|
19
19
|
render(<DesignDocsPage />);
|
|
@@ -13,7 +13,7 @@ describe('FilesPage', () => {
|
|
|
13
13
|
it('renders correctly and loads files', async () => {
|
|
14
14
|
vi.mocked(api.listFiles).mockResolvedValue([
|
|
15
15
|
{ name: 'test.txt', size: 1024, modified: new Date().toISOString(), isDirectory: false }
|
|
16
|
-
] as
|
|
16
|
+
] as never);
|
|
17
17
|
vi.mocked(api.listSkills).mockResolvedValue([]);
|
|
18
18
|
|
|
19
19
|
render(<FilesPage />);
|
|
@@ -31,7 +31,7 @@ describe('FilesPage', () => {
|
|
|
31
31
|
vi.mocked(api.listSkills).mockResolvedValue([]);
|
|
32
32
|
vi.mocked(api.listScripts).mockResolvedValue([
|
|
33
33
|
{ name: 'test-script.mjs', size: 500, modified: new Date().toISOString(), isDirectory: false }
|
|
34
|
-
] as
|
|
34
|
+
] as never);
|
|
35
35
|
|
|
36
36
|
render(<FilesPage />);
|
|
37
37
|
|
|
@@ -12,7 +12,7 @@ describe('GraphPage', () => {
|
|
|
12
12
|
|
|
13
13
|
it('renders without crashing', async () => {
|
|
14
14
|
vi.mocked(api.listMemory).mockResolvedValue([]);
|
|
15
|
-
vi.mocked(api.listResearch).mockResolvedValue({ items: [] } as
|
|
15
|
+
vi.mocked(api.listResearch).mockResolvedValue({ items: [] } as never);
|
|
16
16
|
vi.mocked(api.fetchChatThreads).mockResolvedValue([]);
|
|
17
17
|
|
|
18
18
|
render(<GraphPage activeBrainId="global" />);
|
|
@@ -16,8 +16,8 @@ describe('HealthPage', () => {
|
|
|
16
16
|
version: '1.0.0',
|
|
17
17
|
uptime_seconds: 3600,
|
|
18
18
|
timestamp: new Date().toISOString()
|
|
19
|
-
} as
|
|
20
|
-
vi.mocked(api.checkUpdate).mockResolvedValue({ updateAvailable: false } as
|
|
19
|
+
} as never);
|
|
20
|
+
vi.mocked(api.checkUpdate).mockResolvedValue({ updateAvailable: false } as never);
|
|
21
21
|
|
|
22
22
|
render(<HealthPage />);
|
|
23
23
|
|
|
@@ -16,11 +16,11 @@ describe('HelpPage', () => {
|
|
|
16
16
|
{ id: 'cli', title: 'CLI Reference', description: 'Commands for CLI' },
|
|
17
17
|
{ id: 'vault', title: 'Vault Structure', description: 'How files are stored' }
|
|
18
18
|
]
|
|
19
|
-
} as
|
|
19
|
+
} as never);
|
|
20
20
|
|
|
21
21
|
vi.mocked(api.fetchHelpContent).mockResolvedValue({
|
|
22
22
|
content: '# CLI Reference\n\nSome helpful text.'
|
|
23
|
-
} as
|
|
23
|
+
} as never);
|
|
24
24
|
|
|
25
25
|
await act(async () => {
|
|
26
26
|
render(<HelpPage />);
|
|
@@ -39,13 +39,13 @@ describe('HelpPage', () => {
|
|
|
39
39
|
{ id: 'cli', title: 'CLI Reference', description: 'Commands for CLI' },
|
|
40
40
|
{ id: 'vault', title: 'Vault Structure', description: 'How files are stored' }
|
|
41
41
|
]
|
|
42
|
-
} as
|
|
42
|
+
} as never);
|
|
43
43
|
|
|
44
44
|
vi.mocked(api.fetchHelpContent).mockResolvedValueOnce({
|
|
45
45
|
content: 'CLI content'
|
|
46
|
-
} as
|
|
46
|
+
} as never).mockResolvedValueOnce({
|
|
47
47
|
content: 'Vault content'
|
|
48
|
-
} as
|
|
48
|
+
} as never);
|
|
49
49
|
|
|
50
50
|
await act(async () => {
|
|
51
51
|
render(<HelpPage />);
|
|
@@ -15,7 +15,7 @@ describe('InboxPage', () => {
|
|
|
15
15
|
docs: [
|
|
16
16
|
{ path: 'proposals/p1.md', name: 'Proposal 1', type: 'proposal', status: 'pending_approval' }
|
|
17
17
|
]
|
|
18
|
-
} as
|
|
18
|
+
} as never);
|
|
19
19
|
|
|
20
20
|
render(<InboxPage />);
|
|
21
21
|
|
|
@@ -32,10 +32,10 @@ describe('InboxPage', () => {
|
|
|
32
32
|
docs: [
|
|
33
33
|
{ path: 'proposals/p2.md', name: 'Proposal 2', type: 'proposal', status: 'pending_approval' }
|
|
34
34
|
]
|
|
35
|
-
} as
|
|
35
|
+
} as never);
|
|
36
36
|
|
|
37
|
-
vi.mocked(api.readDoc).mockResolvedValue({ raw: 'status: pending_approval' } as
|
|
38
|
-
vi.mocked(api.postDecision).mockResolvedValue({} as
|
|
37
|
+
vi.mocked(api.readDoc).mockResolvedValue({ raw: 'status: pending_approval' } as never);
|
|
38
|
+
vi.mocked(api.postDecision).mockResolvedValue({} as never);
|
|
39
39
|
|
|
40
40
|
render(<InboxPage />);
|
|
41
41
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
|
|
2
2
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
3
3
|
import { render, screen, waitFor, fireEvent } from '@testing-library/react';
|
|
4
4
|
import InstructionsPage from './InstructionsPage';
|
|
@@ -18,9 +18,9 @@ describe('InstructionsPage', () => {
|
|
|
18
18
|
],
|
|
19
19
|
lastCompileTimestamp: new Date().toISOString(),
|
|
20
20
|
totalNodes: 10
|
|
21
|
-
} as
|
|
21
|
+
} as never);
|
|
22
22
|
|
|
23
|
-
vi.mocked(api.fetchInstructionContent).mockResolvedValue({ content: '# AGENTS\n\nRule 1' } as
|
|
23
|
+
vi.mocked(api.fetchInstructionContent).mockResolvedValue({ content: '# AGENTS\n\nRule 1' } as never);
|
|
24
24
|
|
|
25
25
|
render(<InstructionsPage />);
|
|
26
26
|
|
|
@@ -36,9 +36,9 @@ describe('InstructionsPage', () => {
|
|
|
36
36
|
surfaces: [],
|
|
37
37
|
lastCompileTimestamp: new Date().toISOString(),
|
|
38
38
|
totalNodes: 0
|
|
39
|
-
} as
|
|
39
|
+
} as never);
|
|
40
40
|
|
|
41
|
-
vi.mocked(api.triggerRecompile).mockResolvedValue({ message: 'Success' });
|
|
41
|
+
vi.mocked(api.triggerRecompile).mockResolvedValue({ message: 'Success' } as never);
|
|
42
42
|
|
|
43
43
|
render(<InstructionsPage />);
|
|
44
44
|
|
|
@@ -16,9 +16,9 @@ describe('IntegrationsPage', () => {
|
|
|
16
16
|
keys: [
|
|
17
17
|
{ id: '1', name: 'Antigravity', token_preview: 'abc', created_at: '', last_used_at: '', hit_count: 1, revoked: false, scopes: [] }
|
|
18
18
|
]
|
|
19
|
-
} as
|
|
20
|
-
vi.mocked(api.fetchActiveIntegrations).mockResolvedValue({ success: true, active: ['antigravity'] } as
|
|
21
|
-
vi.mocked(api.fetchExtensionStatus).mockResolvedValue({ available: true, connected: true } as
|
|
19
|
+
} as never);
|
|
20
|
+
vi.mocked(api.fetchActiveIntegrations).mockResolvedValue({ success: true, active: ['antigravity'] } as never);
|
|
21
|
+
vi.mocked(api.fetchExtensionStatus).mockResolvedValue({ available: true, connected: true } as never);
|
|
22
22
|
|
|
23
23
|
await act(async () => {
|
|
24
24
|
render(<IntegrationsPage />);
|
|
@@ -32,10 +32,10 @@ describe('IntegrationsPage', () => {
|
|
|
32
32
|
|
|
33
33
|
it('handles connect', async () => {
|
|
34
34
|
vi.mocked(api.getApiBase).mockReturnValue('http://localhost:3000');
|
|
35
|
-
vi.mocked(api.listApiKeys).mockResolvedValue({ keys: [] } as
|
|
36
|
-
vi.mocked(api.fetchActiveIntegrations).mockResolvedValue({ success: true, active: [] } as
|
|
37
|
-
vi.mocked(api.fetchExtensionStatus).mockResolvedValue({ available: false, connected: false } as
|
|
38
|
-
vi.mocked(api.connectClient).mockResolvedValue({ success: true, message: 'Connected' } as
|
|
35
|
+
vi.mocked(api.listApiKeys).mockResolvedValue({ keys: [] } as never);
|
|
36
|
+
vi.mocked(api.fetchActiveIntegrations).mockResolvedValue({ success: true, active: [] } as never);
|
|
37
|
+
vi.mocked(api.fetchExtensionStatus).mockResolvedValue({ available: false, connected: false } as never);
|
|
38
|
+
vi.mocked(api.connectClient).mockResolvedValue({ success: true, message: 'Connected' } as never);
|
|
39
39
|
|
|
40
40
|
await act(async () => {
|
|
41
41
|
render(<IntegrationsPage />);
|
|
@@ -18,7 +18,7 @@ describe('MemoryPage', () => {
|
|
|
18
18
|
it('renders correctly and loads nodes', async () => {
|
|
19
19
|
vi.mocked(api.listMemory).mockResolvedValue([
|
|
20
20
|
{ slug: 'test-node', title: 'Test Node', category: 'invariants', content: 'test content' }
|
|
21
|
-
] as
|
|
21
|
+
] as never);
|
|
22
22
|
|
|
23
23
|
renderWithRouter(<MemoryPage />);
|
|
24
24
|
|
|
@@ -36,7 +36,7 @@ describe('MemoryPage', () => {
|
|
|
36
36
|
{ id: 'sess-1', brain_id: 'default', total_chunks: 5, created_at: new Date().toISOString(), updated_at: new Date().toISOString(), status: 'active' }
|
|
37
37
|
],
|
|
38
38
|
total: 1
|
|
39
|
-
} as
|
|
39
|
+
} as never);
|
|
40
40
|
|
|
41
41
|
renderWithRouter(<MemoryPage />);
|
|
42
42
|
|