@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,220 @@
|
|
|
1
|
+
// src/web/views/memory/GitSyncPanel.tsx — git status, pull/push/commit/fetch, diff viewer.
|
|
2
|
+
import { useEffect, useState } from 'react';
|
|
3
|
+
import {
|
|
4
|
+
ArrowDown,
|
|
5
|
+
ArrowUp,
|
|
6
|
+
CheckCircle2,
|
|
7
|
+
GitBranch,
|
|
8
|
+
GitCommit,
|
|
9
|
+
GitMerge,
|
|
10
|
+
GitPullRequest,
|
|
11
|
+
Loader2,
|
|
12
|
+
RefreshCw,
|
|
13
|
+
Settings,
|
|
14
|
+
} from 'lucide-react';
|
|
15
|
+
import { Button } from '../../components/Button';
|
|
16
|
+
import { Card, CardMeta, CardTitle } from '../../components/Card';
|
|
17
|
+
import { Spinner } from '../../components/Spinner';
|
|
18
|
+
import { useToast } from '../../components/Toast';
|
|
19
|
+
import { api } from '../../lib/api';
|
|
20
|
+
import { formatTime, cn } from '../../lib/utils';
|
|
21
|
+
|
|
22
|
+
type GitStatus = {
|
|
23
|
+
ok: boolean;
|
|
24
|
+
clean?: boolean;
|
|
25
|
+
branch?: string;
|
|
26
|
+
ahead?: number;
|
|
27
|
+
behind?: number;
|
|
28
|
+
modified?: string[];
|
|
29
|
+
untracked?: string[];
|
|
30
|
+
mode?: string;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
type GitDiff = {
|
|
34
|
+
hasDiff: boolean;
|
|
35
|
+
lines: string[];
|
|
36
|
+
files: string[];
|
|
37
|
+
mode: string;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
type Props = { refreshKey: number };
|
|
41
|
+
|
|
42
|
+
export function GitSyncPanel({ refreshKey }: Props) {
|
|
43
|
+
const toast = useToast();
|
|
44
|
+
const [status, setStatus] = useState<GitStatus | null>(null);
|
|
45
|
+
const [diff, setDiff] = useState<GitDiff | null>(null);
|
|
46
|
+
const [loading, setLoading] = useState(true);
|
|
47
|
+
const [busy, setBusy] = useState<string | null>(null);
|
|
48
|
+
const [config, setConfig] = useState<{ repoPath?: string; remoteUrl?: string; branch?: string; autoSync?: boolean } | null>(null);
|
|
49
|
+
|
|
50
|
+
const reload = async () => {
|
|
51
|
+
setLoading(true);
|
|
52
|
+
try {
|
|
53
|
+
const [s, d, g] = await Promise.all([
|
|
54
|
+
api.get<GitStatus>('/memory/git/status').catch(() => null),
|
|
55
|
+
api.get<GitDiff>('/memory/git/diff').catch(() => null),
|
|
56
|
+
api.get<{ config?: { git?: { repoPath?: string; remoteUrl?: string; branch?: string; autoSync?: boolean } } }>(
|
|
57
|
+
'/memory/config/global',
|
|
58
|
+
).catch(() => null),
|
|
59
|
+
]);
|
|
60
|
+
setStatus(s);
|
|
61
|
+
setDiff(d);
|
|
62
|
+
setConfig(g?.config?.git || null);
|
|
63
|
+
} catch (err) {
|
|
64
|
+
toast.error(`Git status failed: ${(err as Error).message}`);
|
|
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 run = async (key: string, fn: () => Promise<unknown>, msg: string) => {
|
|
76
|
+
setBusy(key);
|
|
77
|
+
try {
|
|
78
|
+
await fn();
|
|
79
|
+
toast.success(msg);
|
|
80
|
+
await reload();
|
|
81
|
+
} catch (err) {
|
|
82
|
+
toast.error(`${key} failed: ${(err as Error).message}`);
|
|
83
|
+
} finally {
|
|
84
|
+
setBusy(null);
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const onPull = () => run('pull', () => api.post('/memory/git/pull', {}), 'Pull complete.');
|
|
89
|
+
const onPush = () => run('push', () => api.post('/memory/git/push', {}), 'Push complete.');
|
|
90
|
+
const onCommit = () => {
|
|
91
|
+
const message = prompt('Commit message:', `[memory-sync] ${new Date().toISOString().slice(0, 10)} vault sync`);
|
|
92
|
+
if (!message) return;
|
|
93
|
+
run('commit', () => api.post('/memory/git/commit', { message }), 'Committed.');
|
|
94
|
+
};
|
|
95
|
+
const onFetch = () => run('fetch', () => api.post('/memory/git/sync', { push: false }), 'Fetch complete.');
|
|
96
|
+
|
|
97
|
+
if (loading && !status) {
|
|
98
|
+
return (
|
|
99
|
+
<div className="view-loading">
|
|
100
|
+
<Spinner size="lg" />
|
|
101
|
+
<p>Loading git status…</p>
|
|
102
|
+
</div>
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
if (!status) {
|
|
106
|
+
return <div className="muted">No git data available.</div>;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const isLocal = status.mode === 'local-only';
|
|
110
|
+
|
|
111
|
+
return (
|
|
112
|
+
<div className="memory-panel-content">
|
|
113
|
+
{/* ── Status card ───────────────────────────────────────────── */}
|
|
114
|
+
<Card variant="elevated">
|
|
115
|
+
<CardTitle>
|
|
116
|
+
<GitBranch size={14} /> Git sync
|
|
117
|
+
{isLocal ? (
|
|
118
|
+
<span className="memory-source-pill-status na">local-only</span>
|
|
119
|
+
) : (
|
|
120
|
+
<span className={cn('memory-source-pill-status', status.clean ? 'on' : 'warn')}>
|
|
121
|
+
{status.clean ? 'clean' : 'dirty'}
|
|
122
|
+
</span>
|
|
123
|
+
)}
|
|
124
|
+
</CardTitle>
|
|
125
|
+
<CardMeta>
|
|
126
|
+
{isLocal
|
|
127
|
+
? 'Memory is in local-only mode. Configure a shared repo in Settings → Memory to enable git sync.'
|
|
128
|
+
: <>Branch <code>{status.branch || '—'}</code>{status.ahead ? <> · ahead {status.ahead}</> : null}{status.behind ? <> · behind {status.behind}</> : null}</>}
|
|
129
|
+
</CardMeta>
|
|
130
|
+
|
|
131
|
+
{!isLocal && (
|
|
132
|
+
<div className="memory-git-status-row">
|
|
133
|
+
<Pill ok={status.clean ?? false} label="Working tree" />
|
|
134
|
+
<Pill ok={(status.ahead ?? 0) === 0} label={`Ahead: ${status.ahead ?? 0}`} />
|
|
135
|
+
<Pill ok={(status.behind ?? 0) === 0} label={`Behind: ${status.behind ?? 0}`} />
|
|
136
|
+
<Pill ok={(status.modified?.length ?? 0) === 0} label={`Modified: ${status.modified?.length ?? 0}`} />
|
|
137
|
+
<Pill ok={(status.untracked?.length ?? 0) === 0} label={`Untracked: ${status.untracked?.length ?? 0}`} />
|
|
138
|
+
</div>
|
|
139
|
+
)}
|
|
140
|
+
|
|
141
|
+
<div className="memory-action-row">
|
|
142
|
+
<Button variant="secondary" size="sm" onClick={onPull} disabled={!!busy || isLocal}>
|
|
143
|
+
{busy === 'pull' ? <Loader2 size={12} className="memory-spin" /> : <ArrowDown size={12} />}
|
|
144
|
+
Pull
|
|
145
|
+
</Button>
|
|
146
|
+
<Button variant="secondary" size="sm" onClick={onPush} disabled={!!busy || isLocal}>
|
|
147
|
+
{busy === 'push' ? <Loader2 size={12} className="memory-spin" /> : <ArrowUp size={12} />}
|
|
148
|
+
Push
|
|
149
|
+
</Button>
|
|
150
|
+
<Button variant="secondary" size="sm" onClick={onCommit} disabled={!!busy || isLocal}>
|
|
151
|
+
{busy === 'commit' ? <Loader2 size={12} className="memory-spin" /> : <GitCommit size={12} />}
|
|
152
|
+
Commit
|
|
153
|
+
</Button>
|
|
154
|
+
<Button variant="ghost" size="sm" onClick={onFetch} disabled={!!busy || isLocal}>
|
|
155
|
+
{busy === 'fetch' ? <Loader2 size={12} className="memory-spin" /> : <GitPullRequest size={12} />}
|
|
156
|
+
Fetch + sync
|
|
157
|
+
</Button>
|
|
158
|
+
<Button variant="ghost" size="sm" onClick={reload} disabled={!!busy}>
|
|
159
|
+
<RefreshCw size={12} /> Refresh
|
|
160
|
+
</Button>
|
|
161
|
+
</div>
|
|
162
|
+
</Card>
|
|
163
|
+
|
|
164
|
+
{/* ── Repo config snapshot ──────────────────────────────────── */}
|
|
165
|
+
{config && (
|
|
166
|
+
<Card>
|
|
167
|
+
<CardTitle>
|
|
168
|
+
<Settings size={14} /> Repository
|
|
169
|
+
</CardTitle>
|
|
170
|
+
<CardMeta>Snapshot of <code>~/.config/bizar/memory-config.json</code> → git block.</CardMeta>
|
|
171
|
+
<dl className="memory-config-row">
|
|
172
|
+
<dt>Path</dt>
|
|
173
|
+
<dd className="mono ellipsis" title={config.repoPath}>{config.repoPath || '—'}</dd>
|
|
174
|
+
<dt>Remote</dt>
|
|
175
|
+
<dd className="mono ellipsis" title={config.remoteUrl}>{config.remoteUrl || '—'}</dd>
|
|
176
|
+
<dt>Branch</dt>
|
|
177
|
+
<dd><code>{config.branch || 'main'}</code></dd>
|
|
178
|
+
<dt>Auto-sync</dt>
|
|
179
|
+
<dd>{config.autoSync ? 'enabled' : 'disabled'}</dd>
|
|
180
|
+
</dl>
|
|
181
|
+
</Card>
|
|
182
|
+
)}
|
|
183
|
+
|
|
184
|
+
{/* ── Working-tree diff ─────────────────────────────────────── */}
|
|
185
|
+
<Card>
|
|
186
|
+
<CardTitle>
|
|
187
|
+
<GitMerge size={14} /> Working-tree diff
|
|
188
|
+
</CardTitle>
|
|
189
|
+
<CardMeta>
|
|
190
|
+
{diff?.hasDiff
|
|
191
|
+
? `${diff.files.length} file(s) changed`
|
|
192
|
+
: 'no working-tree changes'}
|
|
193
|
+
</CardMeta>
|
|
194
|
+
{diff && diff.files.length > 0 && (
|
|
195
|
+
<ul className="memory-diff-files">
|
|
196
|
+
{diff.files.map((f) => (
|
|
197
|
+
<li key={f}>
|
|
198
|
+
<code>{f}</code>
|
|
199
|
+
</li>
|
|
200
|
+
))}
|
|
201
|
+
</ul>
|
|
202
|
+
)}
|
|
203
|
+
{diff && diff.lines.length > 1 && (
|
|
204
|
+
<pre className="memory-log-tail mono text-xs">
|
|
205
|
+
{diff.lines.slice(0, 400).join('\n')}
|
|
206
|
+
</pre>
|
|
207
|
+
)}
|
|
208
|
+
</Card>
|
|
209
|
+
</div>
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function Pill({ ok, label }: { ok: boolean; label: string }) {
|
|
214
|
+
return (
|
|
215
|
+
<span className={cn('memory-git-status-pill', ok ? 'ok' : 'warn')}>
|
|
216
|
+
{ok ? <CheckCircle2 size={11} /> : <Loader2 size={11} className="memory-spin" />}
|
|
217
|
+
{label}
|
|
218
|
+
</span>
|
|
219
|
+
);
|
|
220
|
+
}
|
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
// src/web/views/memory/LightragPanel.tsx — LightRAG controls, stats, query.
|
|
2
|
+
import { useEffect, useMemo, useState } from 'react';
|
|
3
|
+
import {
|
|
4
|
+
Brain,
|
|
5
|
+
CheckCircle2,
|
|
6
|
+
Database,
|
|
7
|
+
Loader2,
|
|
8
|
+
Play,
|
|
9
|
+
Power,
|
|
10
|
+
PowerOff,
|
|
11
|
+
RefreshCw,
|
|
12
|
+
RotateCw,
|
|
13
|
+
Search as SearchIcon,
|
|
14
|
+
Sparkles,
|
|
15
|
+
XCircle,
|
|
16
|
+
Zap,
|
|
17
|
+
} from 'lucide-react';
|
|
18
|
+
import { Button } from '../../components/Button';
|
|
19
|
+
import { Card, CardMeta, CardTitle } from '../../components/Card';
|
|
20
|
+
import { EmptyState } from '../../components/EmptyState';
|
|
21
|
+
import { Spinner } from '../../components/Spinner';
|
|
22
|
+
import { useToast } from '../../components/Toast';
|
|
23
|
+
import { api } from '../../lib/api';
|
|
24
|
+
import { formatTime, cn } from '../../lib/utils';
|
|
25
|
+
|
|
26
|
+
type Stats = {
|
|
27
|
+
running: boolean;
|
|
28
|
+
pid: number | null;
|
|
29
|
+
host: string;
|
|
30
|
+
port: number;
|
|
31
|
+
workingDir: string;
|
|
32
|
+
lastReindexAt: string | null;
|
|
33
|
+
lastReindexOk: boolean | null;
|
|
34
|
+
lastReindexInserted: number | null;
|
|
35
|
+
lastReindexFailed: number | null;
|
|
36
|
+
noteCount: number;
|
|
37
|
+
indexedApprox: number;
|
|
38
|
+
queryCountLast24h: number;
|
|
39
|
+
avgResponseMs: number | null;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
type Config = {
|
|
43
|
+
enabled: boolean;
|
|
44
|
+
host: string;
|
|
45
|
+
port: number;
|
|
46
|
+
workingDir: string;
|
|
47
|
+
llmBinding: string;
|
|
48
|
+
embeddingBinding: string;
|
|
49
|
+
llmModel: string;
|
|
50
|
+
embeddingModel: string;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
type Props = { refreshKey: number };
|
|
54
|
+
|
|
55
|
+
export function LightragPanel({ refreshKey }: Props) {
|
|
56
|
+
const toast = useToast();
|
|
57
|
+
const [stats, setStats] = useState<Stats | null>(null);
|
|
58
|
+
const [config, setConfig] = useState<Config | null>(null);
|
|
59
|
+
const [logTail, setLogTail] = useState<string[]>([]);
|
|
60
|
+
const [loading, setLoading] = useState(true);
|
|
61
|
+
const [busy, setBusy] = useState<string | null>(null);
|
|
62
|
+
const [query, setQuery] = useState('');
|
|
63
|
+
const [queryResult, setQueryResult] = useState<{ ok: boolean; text: string } | null>(null);
|
|
64
|
+
|
|
65
|
+
const reload = async () => {
|
|
66
|
+
setLoading(true);
|
|
67
|
+
try {
|
|
68
|
+
const [s, c, log] = await Promise.all([
|
|
69
|
+
api.get<Stats>('/memory/lightrag/stats').catch(() => null),
|
|
70
|
+
api.get<{ lightrag?: Config }>('/memory/config').then((r) => r?.lightrag || null).catch(() => null),
|
|
71
|
+
api.get<{ lines: string[] }>('/memory/lightrag/log').catch(() => ({ lines: [] })),
|
|
72
|
+
]);
|
|
73
|
+
setStats(s);
|
|
74
|
+
setConfig(c);
|
|
75
|
+
setLogTail(log?.lines || []);
|
|
76
|
+
} catch (err) {
|
|
77
|
+
toast.error(`LightRAG status failed: ${(err as Error).message}`);
|
|
78
|
+
} finally {
|
|
79
|
+
setLoading(false);
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
useEffect(() => {
|
|
84
|
+
reload();
|
|
85
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
86
|
+
}, [refreshKey]);
|
|
87
|
+
|
|
88
|
+
const runCommand = async (key: string, fn: () => Promise<unknown>, successMsg: string) => {
|
|
89
|
+
setBusy(key);
|
|
90
|
+
try {
|
|
91
|
+
await fn();
|
|
92
|
+
toast.success(successMsg);
|
|
93
|
+
await reload();
|
|
94
|
+
} catch (err) {
|
|
95
|
+
toast.error(`${key} failed: ${(err as Error).message}`);
|
|
96
|
+
} finally {
|
|
97
|
+
setBusy(null);
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
const onStart = () => runCommand('start', () => api.post('/memory/lightrag/start', {}), 'LightRAG starting…');
|
|
102
|
+
const onStop = () => runCommand('stop', () => api.post('/memory/lightrag/stop', {}), 'LightRAG stopping…');
|
|
103
|
+
const onRestart = async () => {
|
|
104
|
+
setBusy('restart');
|
|
105
|
+
try {
|
|
106
|
+
await api.post('/memory/lightrag/stop', {}).catch(() => {});
|
|
107
|
+
await new Promise((r) => setTimeout(r, 600));
|
|
108
|
+
await api.post('/memory/lightrag/start', {});
|
|
109
|
+
toast.success('LightRAG restarted.');
|
|
110
|
+
await reload();
|
|
111
|
+
} catch (err) {
|
|
112
|
+
toast.error(`Restart failed: ${(err as Error).message}`);
|
|
113
|
+
} finally {
|
|
114
|
+
setBusy(null);
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
const onReindex = () => runCommand('reindex', () => api.post('/memory/lightrag/reindex', {}), 'Reindex complete.');
|
|
118
|
+
const onRebuild = () => runCommand('rebuild', () => api.post('/memory/lightrag/rebuild-graph', {}), 'Graph rebuilt.');
|
|
119
|
+
|
|
120
|
+
const onQuery = async () => {
|
|
121
|
+
if (!query.trim()) return;
|
|
122
|
+
setBusy('query');
|
|
123
|
+
try {
|
|
124
|
+
const r = await api.get<{ ok: boolean; q: string; semantic?: { ok: boolean; response?: unknown; error?: string } }>(
|
|
125
|
+
`/memory/query?q=${encodeURIComponent(query)}&topK=8`,
|
|
126
|
+
);
|
|
127
|
+
const text = r?.semantic?.response
|
|
128
|
+
? (typeof r.semantic.response === 'string'
|
|
129
|
+
? r.semantic.response
|
|
130
|
+
: JSON.stringify(r.semantic.response))
|
|
131
|
+
: (r?.semantic?.error || 'No response');
|
|
132
|
+
setQueryResult({ ok: !!r?.semantic?.ok, text });
|
|
133
|
+
} catch (err) {
|
|
134
|
+
setQueryResult({ ok: false, text: (err as Error).message });
|
|
135
|
+
} finally {
|
|
136
|
+
setBusy(null);
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
if (loading && !stats) {
|
|
141
|
+
return (
|
|
142
|
+
<div className="view-loading">
|
|
143
|
+
<Spinner size="lg" />
|
|
144
|
+
<p>Loading LightRAG status…</p>
|
|
145
|
+
</div>
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
if (!stats) {
|
|
149
|
+
return <div className="muted">No LightRAG data available.</div>;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return (
|
|
153
|
+
<div className="memory-panel-content">
|
|
154
|
+
{/* ── Status header ───────────────────────────────────────────── */}
|
|
155
|
+
<Card variant="elevated">
|
|
156
|
+
<CardTitle>
|
|
157
|
+
<Brain size={14} />
|
|
158
|
+
LightRAG
|
|
159
|
+
<span className={cn('memory-source-pill-status', stats.running ? 'on' : 'off')}>
|
|
160
|
+
{stats.running ? 'running' : 'stopped'}
|
|
161
|
+
</span>
|
|
162
|
+
</CardTitle>
|
|
163
|
+
<CardMeta>
|
|
164
|
+
{stats.running
|
|
165
|
+
? <>PID <code>{stats.pid}</code> · {stats.host}:{stats.port}</>
|
|
166
|
+
: <>Server is not running. Start it from the controls below.</>}
|
|
167
|
+
</CardMeta>
|
|
168
|
+
<div className="memory-action-row">
|
|
169
|
+
<Button variant="primary" size="sm" onClick={onStart} disabled={!!busy || stats.running}>
|
|
170
|
+
{busy === 'start' ? <Loader2 size={12} className="memory-spin" /> : <Play size={12} />}
|
|
171
|
+
Start
|
|
172
|
+
</Button>
|
|
173
|
+
<Button variant="secondary" size="sm" onClick={onStop} disabled={!!busy || !stats.running}>
|
|
174
|
+
{busy === 'stop' ? <Loader2 size={12} className="memory-spin" /> : <PowerOff size={12} />}
|
|
175
|
+
Stop
|
|
176
|
+
</Button>
|
|
177
|
+
<Button variant="secondary" size="sm" onClick={onRestart} disabled={!!busy}>
|
|
178
|
+
{busy === 'restart' ? <Loader2 size={12} className="memory-spin" /> : <Power size={12} />}
|
|
179
|
+
Restart
|
|
180
|
+
</Button>
|
|
181
|
+
<Button variant="ghost" size="sm" onClick={onReindex} disabled={!!busy}>
|
|
182
|
+
{busy === 'reindex' ? <Loader2 size={12} className="memory-spin" /> : <RefreshCw size={12} />}
|
|
183
|
+
Reindex all
|
|
184
|
+
</Button>
|
|
185
|
+
<Button variant="ghost" size="sm" onClick={onRebuild} disabled={!!busy} title="Wipe working dir + reindex from scratch">
|
|
186
|
+
{busy === 'rebuild' ? <Loader2 size={12} className="memory-spin" /> : <RotateCw size={12} />}
|
|
187
|
+
Rebuild graph
|
|
188
|
+
</Button>
|
|
189
|
+
</div>
|
|
190
|
+
</Card>
|
|
191
|
+
|
|
192
|
+
{/* ── Stat cards ─────────────────────────────────────────────── */}
|
|
193
|
+
<div className="memory-stat-grid">
|
|
194
|
+
<StatBox icon={<Database size={14} />} label="Indexed chunks (approx)" value={String(stats.indexedApprox)} />
|
|
195
|
+
<StatBox icon={<FileTextCount n={stats.noteCount} />} label="Notes in vault" value={String(stats.noteCount)} />
|
|
196
|
+
<StatBox icon={<Zap size={14} />} label="Queries last 24h" value={String(stats.queryCountLast24h)} />
|
|
197
|
+
<StatBox
|
|
198
|
+
icon={<Sparkles size={14} />}
|
|
199
|
+
label="Avg response"
|
|
200
|
+
value={stats.avgResponseMs !== null ? `${stats.avgResponseMs} ms` : '—'}
|
|
201
|
+
/>
|
|
202
|
+
<StatBox
|
|
203
|
+
icon={stats.lastReindexOk ? <CheckCircle2 size={14} /> : stats.lastReindexOk === false ? <XCircle size={14} /> : <RefreshCw size={14} />}
|
|
204
|
+
label="Last reindex"
|
|
205
|
+
value={stats.lastReindexAt ? formatTime(stats.lastReindexAt) : 'never'}
|
|
206
|
+
sub={stats.lastReindexInserted !== null
|
|
207
|
+
? `${stats.lastReindexInserted} inserted${stats.lastReindexFailed ? `, ${stats.lastReindexFailed} failed` : ''}`
|
|
208
|
+
: undefined}
|
|
209
|
+
/>
|
|
210
|
+
</div>
|
|
211
|
+
|
|
212
|
+
{/* ── Quick search ───────────────────────────────────────────── */}
|
|
213
|
+
<Card>
|
|
214
|
+
<CardTitle>
|
|
215
|
+
<SearchIcon size={14} /> Quick search
|
|
216
|
+
</CardTitle>
|
|
217
|
+
<CardMeta>Send a natural-language query to the LightRAG index.</CardMeta>
|
|
218
|
+
<div className="memory-search-row">
|
|
219
|
+
<input
|
|
220
|
+
type="text"
|
|
221
|
+
className="input"
|
|
222
|
+
placeholder="e.g. how does the memory service write notes?"
|
|
223
|
+
value={query}
|
|
224
|
+
onChange={(e) => setQuery(e.target.value)}
|
|
225
|
+
onKeyDown={(e) => {
|
|
226
|
+
if (e.key === 'Enter') onQuery();
|
|
227
|
+
}}
|
|
228
|
+
disabled={busy === 'query'}
|
|
229
|
+
/>
|
|
230
|
+
<Button variant="primary" onClick={onQuery} disabled={busy === 'query' || !query.trim()}>
|
|
231
|
+
{busy === 'query' ? <Loader2 size={12} className="memory-spin" /> : <SearchIcon size={12} />}
|
|
232
|
+
Query
|
|
233
|
+
</Button>
|
|
234
|
+
</div>
|
|
235
|
+
{queryResult && (
|
|
236
|
+
<div className={cn('memory-query-result', queryResult.ok ? 'ok' : 'err')}>
|
|
237
|
+
<pre className="mono text-sm">{queryResult.text}</pre>
|
|
238
|
+
</div>
|
|
239
|
+
)}
|
|
240
|
+
</Card>
|
|
241
|
+
|
|
242
|
+
{/* ── Config snapshot ────────────────────────────────────────── */}
|
|
243
|
+
{config && (
|
|
244
|
+
<Card>
|
|
245
|
+
<CardTitle>Configuration</CardTitle>
|
|
246
|
+
<CardMeta>Effective values from <code>.bizar/memory.json</code> + env defaults.</CardMeta>
|
|
247
|
+
<dl className="memory-config-row">
|
|
248
|
+
<dt>LLM binding</dt>
|
|
249
|
+
<dd><code>{config.llmBinding}</code> · <code>{config.llmModel}</code></dd>
|
|
250
|
+
<dt>Embedding</dt>
|
|
251
|
+
<dd><code>{config.embeddingBinding}</code> · <code>{config.embeddingModel}</code></dd>
|
|
252
|
+
<dt>Host</dt>
|
|
253
|
+
<dd><code>{config.host}:{config.port}</code></dd>
|
|
254
|
+
<dt>Working dir</dt>
|
|
255
|
+
<dd className="mono ellipsis" title={config.workingDir}>{config.workingDir}</dd>
|
|
256
|
+
</dl>
|
|
257
|
+
</Card>
|
|
258
|
+
)}
|
|
259
|
+
|
|
260
|
+
{/* ── Log tail ───────────────────────────────────────────────── */}
|
|
261
|
+
{logTail.length > 0 && (
|
|
262
|
+
<Card>
|
|
263
|
+
<CardTitle>
|
|
264
|
+
<RefreshCw size={14} /> Server log (tail)
|
|
265
|
+
</CardTitle>
|
|
266
|
+
<CardMeta>Last {logTail.length} lines from <code>lightrag.log</code></CardMeta>
|
|
267
|
+
<pre className="memory-log-tail mono text-xs">
|
|
268
|
+
{logTail.join('\n')}
|
|
269
|
+
</pre>
|
|
270
|
+
</Card>
|
|
271
|
+
)}
|
|
272
|
+
</div>
|
|
273
|
+
);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function StatBox({
|
|
277
|
+
icon,
|
|
278
|
+
label,
|
|
279
|
+
value,
|
|
280
|
+
sub,
|
|
281
|
+
}: {
|
|
282
|
+
icon: React.ReactNode;
|
|
283
|
+
label: string;
|
|
284
|
+
value: string;
|
|
285
|
+
sub?: string;
|
|
286
|
+
}) {
|
|
287
|
+
return (
|
|
288
|
+
<div className="memory-stat-card">
|
|
289
|
+
<div className="memory-stat-icon">{icon}</div>
|
|
290
|
+
<div className="memory-stat-body">
|
|
291
|
+
<div className="memory-stat-label">{label}</div>
|
|
292
|
+
<div className="memory-stat-value">{value}</div>
|
|
293
|
+
{sub && <div className="memory-stat-sub muted">{sub}</div>}
|
|
294
|
+
</div>
|
|
295
|
+
</div>
|
|
296
|
+
);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
function FileTextCount({ n }: { n: number }) {
|
|
300
|
+
// Stable icon — use FileText with a small badge
|
|
301
|
+
return (
|
|
302
|
+
<span style={{ position: 'relative', display: 'inline-block' }}>
|
|
303
|
+
<SearchIcon size={14} />
|
|
304
|
+
<span className="memory-mini-badge">{n}</span>
|
|
305
|
+
</span>
|
|
306
|
+
);
|
|
307
|
+
}
|