@polderlabs/bizar 4.5.0 → 4.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bizar-dash/dist/assets/__vite-browser-external-BIHI7g3E.js +1 -0
- package/bizar-dash/dist/assets/__vite-browser-external-BIHI7g3E.js.map +1 -0
- package/bizar-dash/dist/assets/main-B4OfGAwz.js +361 -0
- package/bizar-dash/dist/assets/main-B4OfGAwz.js.map +1 -0
- package/bizar-dash/dist/assets/main-DAlLdW8I.css +1 -0
- package/bizar-dash/dist/assets/{mobile--0FBIKX3.js → mobile-BRhoDOUz.js} +1 -2
- package/bizar-dash/dist/assets/{mobile--0FBIKX3.js.map → mobile-BRhoDOUz.js.map} +1 -1
- package/bizar-dash/dist/assets/{mobile-OgRp8VIb.js → mobile-lbH6szyX.js} +2 -3
- package/bizar-dash/dist/assets/mobile-lbH6szyX.js.map +1 -0
- package/bizar-dash/dist/index.html +3 -3
- package/bizar-dash/dist/mobile.html +2 -2
- package/bizar-dash/skills/bizar/SKILL.md +20 -0
- package/bizar-dash/skills/headroom/SKILL.md +94 -0
- package/bizar-dash/skills/lightrag/SKILL.md +11 -0
- package/bizar-dash/skills/obsidian/SKILL.md +13 -0
- package/bizar-dash/src/server/api.mjs +6 -0
- package/bizar-dash/src/server/headroom.mjs +647 -0
- package/bizar-dash/src/server/memory-lightrag.mjs +281 -6
- package/bizar-dash/src/server/memory-obsidian.mjs +230 -0
- package/bizar-dash/src/server/memory-store.mjs +151 -0
- package/bizar-dash/src/server/mods-loader.mjs +13 -5
- package/bizar-dash/src/server/providers-store.mjs +73 -1
- package/bizar-dash/src/server/routes/_shared.mjs +17 -0
- package/bizar-dash/src/server/routes/chat.mjs +38 -0
- package/bizar-dash/src/server/routes/headroom.mjs +126 -0
- package/bizar-dash/src/server/routes/lightrag.mjs +6 -2
- package/bizar-dash/src/server/routes/memory.mjs +439 -4
- package/bizar-dash/src/server/schedules-runner.mjs +4 -4
- package/bizar-dash/src/server/server.mjs +55 -37
- package/bizar-dash/src/server/watcher.mjs +2 -2
- package/bizar-dash/src/web/App.tsx +42 -6
- package/bizar-dash/src/web/components/HeadroomSettings.tsx +418 -0
- package/bizar-dash/src/web/components/HeadroomStatus.tsx +158 -0
- package/bizar-dash/src/web/components/Toast.tsx +1 -1
- package/bizar-dash/src/web/components/Topbar.tsx +3 -1
- package/bizar-dash/src/web/lib/api.ts +12 -11
- package/bizar-dash/src/web/lib/types.ts +16 -0
- package/bizar-dash/src/web/main.tsx +1 -0
- package/bizar-dash/src/web/styles/chat.css +2 -0
- package/bizar-dash/src/web/styles/main.css +52 -23
- package/bizar-dash/src/web/styles/memory.css +955 -0
- package/bizar-dash/src/web/styles/tasks.css +2 -0
- package/bizar-dash/src/web/views/Memory.tsx +141 -0
- package/bizar-dash/src/web/views/MiniMaxUsage.tsx +3 -2
- package/bizar-dash/src/web/views/Overview.tsx +7 -2
- package/bizar-dash/src/web/views/Settings.tsx +32 -1
- package/bizar-dash/src/web/views/Skills.tsx +3 -2
- package/bizar-dash/src/web/views/Tasks.tsx +4 -3
- package/bizar-dash/src/web/views/memory/ConfigPanel.tsx +289 -0
- package/bizar-dash/src/web/views/memory/GitSyncPanel.tsx +220 -0
- package/bizar-dash/src/web/views/memory/LightragPanel.tsx +307 -0
- package/bizar-dash/src/web/views/memory/MemoryOverview.tsx +356 -0
- package/bizar-dash/src/web/views/memory/MemoryStatusCard.tsx +160 -0
- package/bizar-dash/src/web/views/memory/ObsidianPanel.tsx +642 -0
- package/bizar-dash/src/web/views/memory/SemanticSearchPanel.tsx +194 -0
- package/bizar-dash/tests/cli-bugfixes.test.mjs +151 -0
- package/bizar-dash/tests/frontend-bugfixes.test.mjs +151 -0
- package/bizar-dash/tests/headroom-install.test.mjs +173 -0
- package/bizar-dash/tests/headroom-settings.test.mjs +126 -0
- package/bizar-dash/tests/headroom-status.test.mjs +117 -0
- package/bizar-dash/tests/memory-lightrag-extended.test.mjs +162 -0
- package/bizar-dash/tests/memory-obsidian.test.mjs +269 -0
- package/bizar-dash/tests/memory-tab.test.mjs +322 -0
- package/bizar-dash/tests/mod-upgrade.node.test.mjs +1 -1
- package/bizar-dash/tests/server-bugfixes.test.mjs +318 -0
- package/bizar-dash/tests/submit-feedback.test.mjs +6 -6
- package/cli/artifact.mjs +11 -4
- package/cli/bin.mjs +211 -12
- package/cli/doctor.mjs +5 -3
- package/cli/memory.mjs +65 -21
- package/cli/provision.mjs +8 -2
- package/install.sh +1 -2
- package/package.json +3 -3
- package/bizar-dash/dist/assets/main-CDFKHzBg.css +0 -1
- package/bizar-dash/dist/assets/main-NYFpS2wY.js +0 -312
- package/bizar-dash/dist/assets/main-NYFpS2wY.js.map +0 -1
- package/bizar-dash/dist/assets/mobile-OgRp8VIb.js.map +0 -1
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
// src/web/views/memory/MemoryOverview.tsx — Memory tab overview panel (KPIs + sub-system pills).
|
|
2
|
+
import { useEffect, useState } from 'react';
|
|
3
|
+
import {
|
|
4
|
+
Activity,
|
|
5
|
+
Brain,
|
|
6
|
+
CheckCircle2,
|
|
7
|
+
Database,
|
|
8
|
+
FileText,
|
|
9
|
+
GitBranch,
|
|
10
|
+
RefreshCw,
|
|
11
|
+
Search,
|
|
12
|
+
ShieldCheck,
|
|
13
|
+
Sparkles,
|
|
14
|
+
XCircle,
|
|
15
|
+
AlertTriangle,
|
|
16
|
+
} from 'lucide-react';
|
|
17
|
+
import { Button } from '../../components/Button';
|
|
18
|
+
import { Card, CardMeta, CardTitle } from '../../components/Card';
|
|
19
|
+
import { Spinner } from '../../components/Spinner';
|
|
20
|
+
import { useToast } from '../../components/Toast';
|
|
21
|
+
import { api } from '../../lib/api';
|
|
22
|
+
import { formatTime, cn } from '../../lib/utils';
|
|
23
|
+
|
|
24
|
+
export type HealthResponse = {
|
|
25
|
+
score: number;
|
|
26
|
+
status: 'healthy' | 'degraded' | 'unhealthy' | 'unconfigured';
|
|
27
|
+
checks: Array<{ name: string; pass: boolean; detail: string }>;
|
|
28
|
+
message: string;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export type VaultStats = {
|
|
32
|
+
exists: boolean;
|
|
33
|
+
vaultRoot: string;
|
|
34
|
+
mode: string;
|
|
35
|
+
noteCount: number;
|
|
36
|
+
totalSize: number;
|
|
37
|
+
folderCount: number;
|
|
38
|
+
folders: string[];
|
|
39
|
+
lastModified: number | null;
|
|
40
|
+
gitClean: boolean | null;
|
|
41
|
+
gitBranch: string | null;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
type MemoryStatus = {
|
|
45
|
+
initialized: boolean;
|
|
46
|
+
mode?: string;
|
|
47
|
+
projectId?: string;
|
|
48
|
+
vaultRoot?: string;
|
|
49
|
+
branch?: string;
|
|
50
|
+
gitClean?: boolean | null;
|
|
51
|
+
noteCount?: number;
|
|
52
|
+
lastSecretScan?: string | null;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
type LightragStats = {
|
|
56
|
+
running: boolean;
|
|
57
|
+
pid: number | null;
|
|
58
|
+
host: string;
|
|
59
|
+
port: number;
|
|
60
|
+
indexedApprox: number;
|
|
61
|
+
queryCountLast24h: number;
|
|
62
|
+
lastReindexAt: string | null;
|
|
63
|
+
avgResponseMs?: number | null;
|
|
64
|
+
noteCount?: number;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
type StorageStats = {
|
|
68
|
+
total: number;
|
|
69
|
+
breakdown: Array<{ name: string; path: string; size: number }>;
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
export type MemoryOverviewData = {
|
|
73
|
+
health: HealthResponse;
|
|
74
|
+
vault: VaultStats;
|
|
75
|
+
lightrag: {
|
|
76
|
+
running: boolean;
|
|
77
|
+
pid: number | null;
|
|
78
|
+
host: string;
|
|
79
|
+
port: number;
|
|
80
|
+
indexedApprox: number;
|
|
81
|
+
queryCountLast24h: number;
|
|
82
|
+
lastReindexAt: string | null;
|
|
83
|
+
};
|
|
84
|
+
storage: {
|
|
85
|
+
total: number;
|
|
86
|
+
breakdown: Array<{ name: string; path: string; size: number }>;
|
|
87
|
+
};
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
type Props = {
|
|
91
|
+
refreshKey: number;
|
|
92
|
+
onRefresh: () => void;
|
|
93
|
+
setActiveSubPanel: (panel: 'overview' | 'lightrag' | 'obsidian' | 'git' | 'semantic' | 'config') => void;
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
export function MemoryOverview({ refreshKey, onRefresh, setActiveSubPanel }: Props) {
|
|
97
|
+
const toast = useToast();
|
|
98
|
+
const [data, setData] = useState<MemoryOverviewData | null>(null);
|
|
99
|
+
const [loading, setLoading] = useState(true);
|
|
100
|
+
|
|
101
|
+
const reload = async (signal?: AbortSignal) => {
|
|
102
|
+
setLoading(true);
|
|
103
|
+
try {
|
|
104
|
+
const [health, status, lightrag, storage] = await Promise.all([
|
|
105
|
+
api.get<HealthResponse>('/memory/health', signal).catch(() => null),
|
|
106
|
+
api.get<MemoryStatus>('/memory/status', signal).catch(() => null),
|
|
107
|
+
api.get<LightragStats>('/memory/lightrag/stats', signal).catch(() => null),
|
|
108
|
+
api.get<StorageStats>('/memory/storage', signal).catch(() => null),
|
|
109
|
+
]);
|
|
110
|
+
setData({
|
|
111
|
+
health: health || {
|
|
112
|
+
score: 0,
|
|
113
|
+
status: 'unconfigured',
|
|
114
|
+
checks: [],
|
|
115
|
+
message: 'memory not initialised',
|
|
116
|
+
},
|
|
117
|
+
vault: {
|
|
118
|
+
exists: !!status?.initialized,
|
|
119
|
+
vaultRoot: status?.vaultRoot || '',
|
|
120
|
+
mode: status?.mode || 'local-only',
|
|
121
|
+
noteCount: status?.noteCount || 0,
|
|
122
|
+
totalSize: storage?.total || 0,
|
|
123
|
+
folderCount: 0,
|
|
124
|
+
folders: [],
|
|
125
|
+
lastModified: null,
|
|
126
|
+
gitClean: status?.gitClean ?? null,
|
|
127
|
+
gitBranch: status?.branch || null,
|
|
128
|
+
},
|
|
129
|
+
lightrag: lightrag || {
|
|
130
|
+
running: false,
|
|
131
|
+
pid: null,
|
|
132
|
+
host: '127.0.0.1',
|
|
133
|
+
port: 9621,
|
|
134
|
+
indexedApprox: 0,
|
|
135
|
+
queryCountLast24h: 0,
|
|
136
|
+
lastReindexAt: null,
|
|
137
|
+
},
|
|
138
|
+
storage: storage || { total: 0, breakdown: [] },
|
|
139
|
+
});
|
|
140
|
+
} catch (err) {
|
|
141
|
+
toast.error(`Memory overview failed: ${(err as Error).message}`);
|
|
142
|
+
} finally {
|
|
143
|
+
setLoading(false);
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
useEffect(() => {
|
|
148
|
+
const ctrl = new AbortController();
|
|
149
|
+
void reload(ctrl.signal);
|
|
150
|
+
return () => ctrl.abort();
|
|
151
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
152
|
+
}, [refreshKey]);
|
|
153
|
+
|
|
154
|
+
if (loading && !data) {
|
|
155
|
+
return (
|
|
156
|
+
<div className="view-loading">
|
|
157
|
+
<Spinner size="lg" />
|
|
158
|
+
<p>Loading memory overview…</p>
|
|
159
|
+
</div>
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
if (!data) {
|
|
163
|
+
return <div className="muted">No memory data available.</div>;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const { health, vault, lightrag, storage } = data;
|
|
167
|
+
const scoreColor =
|
|
168
|
+
health.score >= 80 ? 'var(--success)' :
|
|
169
|
+
health.score >= 50 ? 'var(--warning)' :
|
|
170
|
+
'var(--error)';
|
|
171
|
+
|
|
172
|
+
return (
|
|
173
|
+
<div className="memory-overview">
|
|
174
|
+
{/* ── Health score hero ─────────────────────────────────────────── */}
|
|
175
|
+
<Card variant="elevated" className="memory-health-hero">
|
|
176
|
+
<div className="memory-health-score" style={{ borderColor: scoreColor }}>
|
|
177
|
+
<span className="memory-health-score-value" style={{ color: scoreColor }}>
|
|
178
|
+
{health.score}
|
|
179
|
+
</span>
|
|
180
|
+
<span className="memory-health-score-label">/ 100</span>
|
|
181
|
+
</div>
|
|
182
|
+
<div className="memory-health-meta">
|
|
183
|
+
<h3 className="memory-health-title">
|
|
184
|
+
<Activity size={16} />
|
|
185
|
+
Memory health ·{' '}
|
|
186
|
+
<span style={{ color: scoreColor }}>{health.status}</span>
|
|
187
|
+
</h3>
|
|
188
|
+
<p className="memory-health-message">{health.message}</p>
|
|
189
|
+
<Button variant="ghost" size="sm" onClick={onRefresh}>
|
|
190
|
+
<RefreshCw size={12} /> Refresh
|
|
191
|
+
</Button>
|
|
192
|
+
</div>
|
|
193
|
+
</Card>
|
|
194
|
+
|
|
195
|
+
{/* ── Source pills ─────────────────────────────────────────────── */}
|
|
196
|
+
<div className="memory-source-pills">
|
|
197
|
+
<button
|
|
198
|
+
type="button"
|
|
199
|
+
className="memory-source-pill"
|
|
200
|
+
onClick={() => setActiveSubPanel('lightrag')}
|
|
201
|
+
>
|
|
202
|
+
<Brain size={14} />
|
|
203
|
+
<span>LightRAG</span>
|
|
204
|
+
<span className={cn('memory-source-pill-status', lightrag.running ? 'on' : 'off')}>
|
|
205
|
+
{lightrag.running ? 'running' : 'stopped'}
|
|
206
|
+
</span>
|
|
207
|
+
</button>
|
|
208
|
+
<button
|
|
209
|
+
type="button"
|
|
210
|
+
className="memory-source-pill"
|
|
211
|
+
onClick={() => setActiveSubPanel('obsidian')}
|
|
212
|
+
>
|
|
213
|
+
<FileText size={14} />
|
|
214
|
+
<span>Obsidian Vault</span>
|
|
215
|
+
<span className={cn('memory-source-pill-status', vault.exists ? 'on' : 'off')}>
|
|
216
|
+
{vault.noteCount} notes
|
|
217
|
+
</span>
|
|
218
|
+
</button>
|
|
219
|
+
<button
|
|
220
|
+
type="button"
|
|
221
|
+
className="memory-source-pill"
|
|
222
|
+
onClick={() => setActiveSubPanel('git')}
|
|
223
|
+
>
|
|
224
|
+
<GitBranch size={14} />
|
|
225
|
+
<span>Git Sync</span>
|
|
226
|
+
<span className={cn(
|
|
227
|
+
'memory-source-pill-status',
|
|
228
|
+
vault.gitClean === null ? 'na' : vault.gitClean ? 'on' : 'warn',
|
|
229
|
+
)}>
|
|
230
|
+
{vault.gitClean === null ? '—' : vault.gitClean ? 'clean' : 'dirty'}
|
|
231
|
+
</span>
|
|
232
|
+
</button>
|
|
233
|
+
<button
|
|
234
|
+
type="button"
|
|
235
|
+
className="memory-source-pill"
|
|
236
|
+
onClick={() => setActiveSubPanel('semantic')}
|
|
237
|
+
>
|
|
238
|
+
<Search size={14} />
|
|
239
|
+
<span>Semantic Search</span>
|
|
240
|
+
<span className="memory-source-pill-status na">cross-source</span>
|
|
241
|
+
</button>
|
|
242
|
+
</div>
|
|
243
|
+
|
|
244
|
+
{/* ── KPI grid ─────────────────────────────────────────────────── */}
|
|
245
|
+
<div className="memory-stat-grid">
|
|
246
|
+
<StatCard
|
|
247
|
+
icon={<Brain size={16} />}
|
|
248
|
+
label="LightRAG"
|
|
249
|
+
value={lightrag.running ? `running · ${lightrag.queryCountLast24h} q/24h` : 'stopped'}
|
|
250
|
+
sub={lightrag.running ? `${lightrag.indexedApprox} indexed chunks` : 'start from the LightRAG panel'}
|
|
251
|
+
onClick={() => setActiveSubPanel('lightrag')}
|
|
252
|
+
/>
|
|
253
|
+
<StatCard
|
|
254
|
+
icon={<FileText size={16} />}
|
|
255
|
+
label="Obsidian"
|
|
256
|
+
value={`${vault.noteCount} notes`}
|
|
257
|
+
sub={vault.folderCount > 0 ? `${vault.folderCount} folders` : 'no folders yet'}
|
|
258
|
+
onClick={() => setActiveSubPanel('obsidian')}
|
|
259
|
+
/>
|
|
260
|
+
<StatCard
|
|
261
|
+
icon={<GitBranch size={16} />}
|
|
262
|
+
label="Git"
|
|
263
|
+
value={vault.gitBranch ? `branch ${vault.gitBranch}` : 'local-only'}
|
|
264
|
+
sub={
|
|
265
|
+
vault.gitClean === null ? 'not configured' :
|
|
266
|
+
vault.gitClean ? 'clean working tree' :
|
|
267
|
+
'dirty — commit or pull'
|
|
268
|
+
}
|
|
269
|
+
onClick={() => setActiveSubPanel('git')}
|
|
270
|
+
/>
|
|
271
|
+
<StatCard
|
|
272
|
+
icon={<Database size={16} />}
|
|
273
|
+
label="Storage"
|
|
274
|
+
value={formatBytes(storage.total)}
|
|
275
|
+
sub={storage.breakdown.length > 0
|
|
276
|
+
? `${storage.breakdown.length} dirs tracked`
|
|
277
|
+
: 'no memory data on disk'}
|
|
278
|
+
/>
|
|
279
|
+
<StatCard
|
|
280
|
+
icon={<Sparkles size={16} />}
|
|
281
|
+
label="Last reindex"
|
|
282
|
+
value={lightrag.lastReindexAt ? formatTime(lightrag.lastReindexAt) : 'never'}
|
|
283
|
+
sub="lightrag ingest"
|
|
284
|
+
onClick={() => setActiveSubPanel('lightrag')}
|
|
285
|
+
/>
|
|
286
|
+
<StatCard
|
|
287
|
+
icon={<ShieldCheck size={16} />}
|
|
288
|
+
label="Health"
|
|
289
|
+
value={`${health.score}/100`}
|
|
290
|
+
sub={health.status}
|
|
291
|
+
onClick={() => setActiveSubPanel('config')}
|
|
292
|
+
/>
|
|
293
|
+
</div>
|
|
294
|
+
|
|
295
|
+
{/* ── Health checks detail ─────────────────────────────────────── */}
|
|
296
|
+
{health.checks.length > 0 && (
|
|
297
|
+
<Card>
|
|
298
|
+
<CardTitle>Health checks</CardTitle>
|
|
299
|
+
<CardMeta>Composite score across all memory subsystems</CardMeta>
|
|
300
|
+
<ul className="memory-check-list">
|
|
301
|
+
{health.checks.map((c) => (
|
|
302
|
+
<li key={c.name} className="memory-check-row">
|
|
303
|
+
<span className="memory-check-icon">
|
|
304
|
+
{c.pass
|
|
305
|
+
? <CheckCircle2 size={14} style={{ color: 'var(--success)' }} />
|
|
306
|
+
: c.name.includes('secrets') || c.name.includes('schema')
|
|
307
|
+
? <AlertTriangle size={14} style={{ color: 'var(--warning)' }} />
|
|
308
|
+
: <XCircle size={14} style={{ color: 'var(--error)' }} />}
|
|
309
|
+
</span>
|
|
310
|
+
<span className="memory-check-name">{c.name}</span>
|
|
311
|
+
<span className="memory-check-detail muted">{c.detail}</span>
|
|
312
|
+
</li>
|
|
313
|
+
))}
|
|
314
|
+
</ul>
|
|
315
|
+
</Card>
|
|
316
|
+
)}
|
|
317
|
+
</div>
|
|
318
|
+
);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
function StatCard({
|
|
322
|
+
icon,
|
|
323
|
+
label,
|
|
324
|
+
value,
|
|
325
|
+
sub,
|
|
326
|
+
onClick,
|
|
327
|
+
}: {
|
|
328
|
+
icon: React.ReactNode;
|
|
329
|
+
label: string;
|
|
330
|
+
value: string;
|
|
331
|
+
sub: string;
|
|
332
|
+
onClick?: () => void;
|
|
333
|
+
}) {
|
|
334
|
+
const Comp = onClick ? 'button' : 'div';
|
|
335
|
+
return (
|
|
336
|
+
<Comp
|
|
337
|
+
type={onClick ? 'button' : undefined}
|
|
338
|
+
onClick={onClick}
|
|
339
|
+
className={cn('memory-stat-card', onClick && 'memory-stat-card-clickable')}
|
|
340
|
+
>
|
|
341
|
+
<div className="memory-stat-icon">{icon}</div>
|
|
342
|
+
<div className="memory-stat-body">
|
|
343
|
+
<div className="memory-stat-label">{label}</div>
|
|
344
|
+
<div className="memory-stat-value">{value}</div>
|
|
345
|
+
<div className="memory-stat-sub muted">{sub}</div>
|
|
346
|
+
</div>
|
|
347
|
+
</Comp>
|
|
348
|
+
);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
function formatBytes(n: number): string {
|
|
352
|
+
if (n < 1024) return `${n} B`;
|
|
353
|
+
if (n < 1024 * 1024) return `${(n / 1024).toFixed(1)} KB`;
|
|
354
|
+
if (n < 1024 * 1024 * 1024) return `${(n / (1024 * 1024)).toFixed(1)} MB`;
|
|
355
|
+
return `${(n / (1024 * 1024 * 1024)).toFixed(2)} GB`;
|
|
356
|
+
}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
// src/web/views/memory/MemoryStatusCard.tsx — overview status card for the Memory subsystem.
|
|
2
|
+
//
|
|
3
|
+
// Slimmed-down version of MemoryOverview: a single Card with lightrag status,
|
|
4
|
+
// vault note count, git sync state, and a button to open the full Memory tab.
|
|
5
|
+
import { useEffect, useState } from 'react';
|
|
6
|
+
import {
|
|
7
|
+
Activity,
|
|
8
|
+
Brain,
|
|
9
|
+
ExternalLink,
|
|
10
|
+
FileText,
|
|
11
|
+
GitBranch,
|
|
12
|
+
RefreshCw,
|
|
13
|
+
} from 'lucide-react';
|
|
14
|
+
import { Button } from '../../components/Button';
|
|
15
|
+
import { Card, CardMeta, CardTitle } from '../../components/Card';
|
|
16
|
+
import { api } from '../../lib/api';
|
|
17
|
+
import { formatTime, cn } from '../../lib/utils';
|
|
18
|
+
|
|
19
|
+
type MemoryCardData = {
|
|
20
|
+
initialized: boolean;
|
|
21
|
+
mode?: string;
|
|
22
|
+
projectId?: string;
|
|
23
|
+
vaultRoot?: string;
|
|
24
|
+
branch?: string;
|
|
25
|
+
gitClean?: boolean | null;
|
|
26
|
+
noteCount?: number;
|
|
27
|
+
healthScore?: number;
|
|
28
|
+
healthStatus?: 'healthy' | 'degraded' | 'unhealthy' | 'unconfigured';
|
|
29
|
+
lightrag?: { running: boolean; indexedApprox: number };
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
type Props = {
|
|
33
|
+
setActiveTab: (id: string) => void;
|
|
34
|
+
refreshKey?: number;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export function MemoryStatusCard({ setActiveTab, refreshKey = 0 }: Props) {
|
|
38
|
+
const [data, setData] = useState<MemoryCardData | null>(null);
|
|
39
|
+
const [loading, setLoading] = useState(true);
|
|
40
|
+
|
|
41
|
+
const reload = async () => {
|
|
42
|
+
setLoading(true);
|
|
43
|
+
try {
|
|
44
|
+
const [status, health, lightrag] = await Promise.all([
|
|
45
|
+
api.get<MemoryCardData>('/memory/status').catch(() => null),
|
|
46
|
+
api.get<{ score: number; status: 'healthy' | 'degraded' | 'unhealthy' | 'unconfigured' }>(
|
|
47
|
+
'/memory/health',
|
|
48
|
+
).catch(() => null),
|
|
49
|
+
api.get<{ running: boolean; indexedApprox: number }>('/memory/lightrag/stats').catch(() => null),
|
|
50
|
+
]);
|
|
51
|
+
setData({
|
|
52
|
+
initialized: !!status?.initialized,
|
|
53
|
+
mode: status?.mode,
|
|
54
|
+
projectId: status?.projectId,
|
|
55
|
+
vaultRoot: status?.vaultRoot,
|
|
56
|
+
branch: status?.branch,
|
|
57
|
+
gitClean: status?.gitClean,
|
|
58
|
+
noteCount: status?.noteCount || 0,
|
|
59
|
+
healthScore: health?.score,
|
|
60
|
+
healthStatus: health?.status,
|
|
61
|
+
lightrag: lightrag ? { running: lightrag.running, indexedApprox: lightrag.indexedApprox } : undefined,
|
|
62
|
+
});
|
|
63
|
+
} catch {
|
|
64
|
+
setData({ initialized: false });
|
|
65
|
+
} finally {
|
|
66
|
+
setLoading(false);
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
useEffect(() => {
|
|
71
|
+
reload();
|
|
72
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
73
|
+
}, [refreshKey]);
|
|
74
|
+
|
|
75
|
+
const openMemory = () => setActiveTab('memory');
|
|
76
|
+
|
|
77
|
+
const healthColor =
|
|
78
|
+
data?.healthStatus === 'healthy' ? 'var(--success)' :
|
|
79
|
+
data?.healthStatus === 'degraded' ? 'var(--warning)' :
|
|
80
|
+
data?.healthStatus === 'unhealthy' ? 'var(--error)' :
|
|
81
|
+
'var(--text-dim)';
|
|
82
|
+
|
|
83
|
+
return (
|
|
84
|
+
<Card className="memory-overview-card">
|
|
85
|
+
<CardTitle>
|
|
86
|
+
<Brain size={14} /> Memory
|
|
87
|
+
<Button variant="ghost" size="sm" style={{ marginLeft: 'auto' }} onClick={openMemory}>
|
|
88
|
+
Open Memory <ExternalLink size={12} />
|
|
89
|
+
</Button>
|
|
90
|
+
<Button variant="ghost" size="sm" onClick={reload} aria-label="Refresh" title="Refresh">
|
|
91
|
+
<RefreshCw size={12} />
|
|
92
|
+
</Button>
|
|
93
|
+
</CardTitle>
|
|
94
|
+
<CardMeta>
|
|
95
|
+
LightRAG + Obsidian vault + git sync — opens the dedicated Memory tab.
|
|
96
|
+
</CardMeta>
|
|
97
|
+
|
|
98
|
+
{!data?.initialized ? (
|
|
99
|
+
<div className="muted">
|
|
100
|
+
Memory not initialised. Click <strong>Open Memory</strong> to set it up.
|
|
101
|
+
</div>
|
|
102
|
+
) : (
|
|
103
|
+
<div className="memory-status-card-grid">
|
|
104
|
+
<div className="memory-status-card-cell">
|
|
105
|
+
<div className="memory-status-card-label">
|
|
106
|
+
<Activity size={11} /> Health
|
|
107
|
+
</div>
|
|
108
|
+
<div className="memory-status-card-value" style={{ color: healthColor }}>
|
|
109
|
+
{data.healthScore !== undefined ? `${data.healthScore}/100` : '—'}
|
|
110
|
+
</div>
|
|
111
|
+
<div className="memory-status-card-sub muted">{data.healthStatus || '—'}</div>
|
|
112
|
+
</div>
|
|
113
|
+
|
|
114
|
+
<div className="memory-status-card-cell">
|
|
115
|
+
<div className="memory-status-card-label">
|
|
116
|
+
<Brain size={11} /> LightRAG
|
|
117
|
+
</div>
|
|
118
|
+
<div className="memory-status-card-value">
|
|
119
|
+
{data.lightrag?.running ? 'running' : 'stopped'}
|
|
120
|
+
</div>
|
|
121
|
+
<div className="memory-status-card-sub muted">
|
|
122
|
+
{data.lightrag ? `${data.lightrag.indexedApprox} chunks` : '—'}
|
|
123
|
+
</div>
|
|
124
|
+
</div>
|
|
125
|
+
|
|
126
|
+
<div className="memory-status-card-cell">
|
|
127
|
+
<div className="memory-status-card-label">
|
|
128
|
+
<FileText size={11} /> Vault
|
|
129
|
+
</div>
|
|
130
|
+
<div className="memory-status-card-value">{data.noteCount} notes</div>
|
|
131
|
+
<div className="memory-status-card-sub muted ellipsis" title={data.vaultRoot || ''}>
|
|
132
|
+
{data.vaultRoot ? data.vaultRoot.split('/').slice(-2).join('/') : '—'}
|
|
133
|
+
</div>
|
|
134
|
+
</div>
|
|
135
|
+
|
|
136
|
+
<div className="memory-status-card-cell">
|
|
137
|
+
<div className="memory-status-card-label">
|
|
138
|
+
<GitBranch size={11} /> Git
|
|
139
|
+
</div>
|
|
140
|
+
<div className="memory-status-card-value">
|
|
141
|
+
{data.gitClean === null
|
|
142
|
+
? data.mode === 'local-only' ? 'local-only' : '—'
|
|
143
|
+
: data.gitClean ? 'clean' : 'dirty'}
|
|
144
|
+
</div>
|
|
145
|
+
<div className="memory-status-card-sub muted">
|
|
146
|
+
{data.branch ? `branch ${data.branch}` : formatTime(data.branch || '') || '—'}
|
|
147
|
+
</div>
|
|
148
|
+
</div>
|
|
149
|
+
</div>
|
|
150
|
+
)}
|
|
151
|
+
|
|
152
|
+
{loading && !data && (
|
|
153
|
+
<div className="muted text-sm">Loading…</div>
|
|
154
|
+
)}
|
|
155
|
+
</Card>
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Allow css class composition without TS complaining about missing module declarations.
|
|
160
|
+
void cn;
|