@polderlabs/bizar 4.4.13 → 4.5.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/bizar-dash/CHANGELOG.md +37 -276
- package/bizar-dash/dist/assets/__vite-browser-external-BIHI7g3E.js +2 -0
- package/bizar-dash/dist/assets/__vite-browser-external-BIHI7g3E.js.map +1 -0
- package/bizar-dash/dist/assets/main-eWZ4NlCL.css +1 -0
- package/bizar-dash/dist/assets/main-usWhlPWa.js +362 -0
- package/bizar-dash/dist/assets/main-usWhlPWa.js.map +1 -0
- package/bizar-dash/dist/assets/{mobile-DSb-t42Y.js → mobile-DQLFCjwJ.js} +2 -2
- package/bizar-dash/dist/assets/{mobile-DSb-t42Y.js.map → mobile-DQLFCjwJ.js.map} +1 -1
- package/bizar-dash/dist/assets/mobile-O6ANdD4W.js +352 -0
- package/bizar-dash/dist/assets/mobile-O6ANdD4W.js.map +1 -0
- package/bizar-dash/dist/index.html +3 -3
- package/bizar-dash/dist/mobile.html +2 -2
- package/bizar-dash/skills/agent-baseline/SKILL.md +80 -0
- package/bizar-dash/skills/bizar/SKILL.md +116 -0
- package/bizar-dash/skills/chat/SKILL.md +74 -0
- package/bizar-dash/skills/headroom/SKILL.md +94 -0
- package/bizar-dash/skills/lightrag/SKILL.md +86 -0
- package/bizar-dash/skills/minimax/SKILL.md +80 -0
- package/bizar-dash/skills/obsidian/SKILL.md +68 -0
- package/bizar-dash/skills/providers/SKILL.md +75 -0
- package/bizar-dash/skills/sdk/SKILL.md +138 -0
- package/bizar-dash/skills/self-improvement/SKILL.md +53 -0
- package/bizar-dash/skills/skills-cli/SKILL.md +94 -0
- package/bizar-dash/skills/usage/SKILL.md +62 -0
- package/bizar-dash/src/server/api.mjs +18 -0
- package/bizar-dash/src/server/headroom.mjs +645 -0
- package/bizar-dash/src/server/memory-lightrag.mjs +272 -2
- package/bizar-dash/src/server/memory-obsidian.mjs +230 -0
- package/bizar-dash/src/server/memory-store.mjs +189 -0
- package/bizar-dash/src/server/minimax-usage-store.mjs +372 -0
- package/bizar-dash/src/server/minimax.mjs +196 -5
- package/bizar-dash/src/server/providers-store.mjs +956 -0
- package/bizar-dash/src/server/routes/_shared.mjs +17 -0
- package/bizar-dash/src/server/routes/config.mjs +52 -1
- package/bizar-dash/src/server/routes/env-vars.mjs +165 -0
- package/bizar-dash/src/server/routes/headroom.mjs +126 -0
- package/bizar-dash/src/server/routes/lightrag.mjs +154 -0
- package/bizar-dash/src/server/routes/memory.mjs +668 -1
- package/bizar-dash/src/server/routes/opencode-session-detail.mjs +14 -29
- package/bizar-dash/src/server/routes/opencode-sessions.mjs +205 -3
- package/bizar-dash/src/server/routes/providers.mjs +266 -5
- package/bizar-dash/src/server/routes/skills.mjs +32 -43
- package/bizar-dash/src/server/routes/update.mjs +340 -0
- package/bizar-dash/src/server/routes/usage.mjs +136 -0
- package/bizar-dash/src/server/serve-info.mjs +135 -4
- package/bizar-dash/src/server/server.mjs +20 -0
- package/bizar-dash/src/server/skills-store.mjs +152 -262
- package/bizar-dash/src/web/App.tsx +120 -29
- package/bizar-dash/src/web/components/EnvVarManager.tsx +247 -0
- 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/SettingsSearch.tsx +213 -0
- package/bizar-dash/src/web/components/Topbar.tsx +2 -1
- package/bizar-dash/src/web/components/UsageChart.tsx +250 -0
- package/bizar-dash/src/web/components/UsageTable.tsx +90 -0
- package/bizar-dash/src/web/components/chat/ChatComposer.tsx +21 -25
- package/bizar-dash/src/web/components/chat/ChatInfoPanel.tsx +199 -37
- package/bizar-dash/src/web/components/chat/ChatThread.tsx +29 -17
- package/bizar-dash/src/web/components/chat/FloatingComposer.tsx +7 -1
- package/bizar-dash/src/web/components/chat/InfoPanel.tsx +71 -6
- package/bizar-dash/src/web/components/chat/useChat.ts +751 -257
- package/bizar-dash/src/web/lib/api.ts +43 -0
- package/bizar-dash/src/web/lib/types.ts +16 -0
- package/bizar-dash/src/web/main.tsx +2 -0
- package/bizar-dash/src/web/mobile/views/MobileChat.tsx +110 -35
- package/bizar-dash/src/web/styles/chat.css +135 -1
- package/bizar-dash/src/web/styles/main.css +46 -0
- package/bizar-dash/src/web/styles/memory.css +955 -0
- package/bizar-dash/src/web/styles/minimax-usage.css +335 -0
- package/bizar-dash/src/web/styles/settings.css +418 -0
- package/bizar-dash/src/web/styles/skills.css +302 -0
- package/bizar-dash/src/web/styles/tasks.css +288 -0
- package/bizar-dash/src/web/views/Chat.tsx +276 -48
- package/bizar-dash/src/web/views/Config.tsx +3 -2065
- package/bizar-dash/src/web/views/Memory.tsx +140 -0
- package/bizar-dash/src/web/views/MiniMaxUsage.tsx +476 -461
- package/bizar-dash/src/web/views/Overview.tsx +3 -0
- package/bizar-dash/src/web/views/Settings.tsx +36 -0
- package/bizar-dash/src/web/views/Skills.tsx +208 -260
- package/bizar-dash/src/web/views/Tasks.tsx +348 -1119
- 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 +354 -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/chat-session-create.test.mjs +391 -0
- package/bizar-dash/tests/chat-session-stream.test.mjs +308 -0
- package/bizar-dash/tests/env-vars-store.test.mjs +216 -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/lightrag-defaults.node.test.mjs +118 -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/minimax-chat-usage.test.mjs +178 -0
- package/bizar-dash/tests/minimax-usage-store.node.test.mjs +293 -0
- package/bizar-dash/tests/mod-upgrade.node.test.mjs +1 -1
- package/bizar-dash/tests/opencode-sessions-detail.test.mjs +12 -9
- package/bizar-dash/tests/providers-store-backup-keys.node.test.mjs +479 -3
- package/bizar-dash/tests/providers-store-search.node.test.mjs +166 -0
- package/bizar-dash/tests/skills-list.test.mjs +232 -0
- package/bizar-dash/tests/skills-search.test.mjs +222 -0
- package/bizar-dash/tests/submit-feedback.test.mjs +6 -6
- package/bizar-dash/tests/tasks-create.test.mjs +187 -0
- package/bizar-dash/tests/update-check.test.mjs +127 -0
- package/bizar-dash/tests/update-run.test.mjs +266 -0
- package/cli/bin.mjs +247 -1
- package/cli/provision.mjs +118 -4
- package/config/agents/_shared/SKILLS.md +109 -0
- package/package.json +1 -1
- package/bizar-dash/dist/assets/main-BB5mJurD.js +0 -352
- package/bizar-dash/dist/assets/main-BB5mJurD.js.map +0 -1
- package/bizar-dash/dist/assets/main-BsnQLXdh.css +0 -1
- package/bizar-dash/dist/assets/mobile-Dl1q7Cyq.js +0 -354
- package/bizar-dash/dist/assets/mobile-Dl1q7Cyq.js.map +0 -1
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
// src/web/views/memory/ConfigPanel.tsx — global memory config + per-system toggles.
|
|
2
|
+
import { useEffect, useState } from 'react';
|
|
3
|
+
import {
|
|
4
|
+
Brain,
|
|
5
|
+
Database,
|
|
6
|
+
FileText,
|
|
7
|
+
GitBranch,
|
|
8
|
+
Loader2,
|
|
9
|
+
Plug,
|
|
10
|
+
Save,
|
|
11
|
+
Sparkles,
|
|
12
|
+
} from 'lucide-react';
|
|
13
|
+
import { Button } from '../../components/Button';
|
|
14
|
+
import { Card, CardMeta, CardTitle } from '../../components/Card';
|
|
15
|
+
import { Spinner } from '../../components/Spinner';
|
|
16
|
+
import { useToast } from '../../components/Toast';
|
|
17
|
+
import { api } from '../../lib/api';
|
|
18
|
+
import { cn } from '../../lib/utils';
|
|
19
|
+
|
|
20
|
+
type GlobalConfig = {
|
|
21
|
+
lightrag?: { enabled?: boolean; url?: string; llm?: string; embedding?: string };
|
|
22
|
+
obsidian?: { vaultPath?: string; syncInterval?: number };
|
|
23
|
+
git?: { repoPath?: string; remoteUrl?: string; branch?: string; autoSync?: boolean };
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
type Props = { refreshKey: number };
|
|
27
|
+
|
|
28
|
+
export function ConfigPanel({ refreshKey }: Props) {
|
|
29
|
+
const toast = useToast();
|
|
30
|
+
const [cfg, setCfg] = useState<GlobalConfig | null>(null);
|
|
31
|
+
const [loading, setLoading] = useState(true);
|
|
32
|
+
const [saving, setSaving] = useState(false);
|
|
33
|
+
const [tests, setTests] = useState<Array<{ name: string; pass: boolean; detail: string }> | null>(null);
|
|
34
|
+
const [testing, setTesting] = useState(false);
|
|
35
|
+
|
|
36
|
+
const reload = async () => {
|
|
37
|
+
setLoading(true);
|
|
38
|
+
try {
|
|
39
|
+
const r = await api.get<{ config: GlobalConfig }>('/memory/config/global');
|
|
40
|
+
setCfg(r.config);
|
|
41
|
+
} catch (err) {
|
|
42
|
+
toast.error(`Config load failed: ${(err as Error).message}`);
|
|
43
|
+
} finally {
|
|
44
|
+
setLoading(false);
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
useEffect(() => {
|
|
49
|
+
reload();
|
|
50
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
51
|
+
}, [refreshKey]);
|
|
52
|
+
|
|
53
|
+
const update = (block: keyof GlobalConfig, key: string, value: unknown) => {
|
|
54
|
+
setCfg((cur) => cur ? { ...cur, [block]: { ...(cur[block] || {}), [key]: value } } : cur);
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const onSave = async () => {
|
|
58
|
+
if (!cfg) return;
|
|
59
|
+
setSaving(true);
|
|
60
|
+
try {
|
|
61
|
+
await api.put('/memory/config/global', cfg);
|
|
62
|
+
toast.success('Config saved.');
|
|
63
|
+
} catch (err) {
|
|
64
|
+
toast.error(`Save failed: ${(err as Error).message}`);
|
|
65
|
+
} finally {
|
|
66
|
+
setSaving(false);
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const onTest = async () => {
|
|
71
|
+
setTesting(true);
|
|
72
|
+
try {
|
|
73
|
+
const r = await api.post<{ ok: boolean; checks: Array<{ name: string; pass: boolean; detail: string }> }>(
|
|
74
|
+
'/memory/test-git',
|
|
75
|
+
{},
|
|
76
|
+
);
|
|
77
|
+
setTests(r.checks);
|
|
78
|
+
} catch (err) {
|
|
79
|
+
toast.error(`Test failed: ${(err as Error).message}`);
|
|
80
|
+
} finally {
|
|
81
|
+
setTesting(false);
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
if (loading && !cfg) {
|
|
86
|
+
return (
|
|
87
|
+
<div className="view-loading">
|
|
88
|
+
<Spinner size="md" />
|
|
89
|
+
<p>Loading config…</p>
|
|
90
|
+
</div>
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
if (!cfg) return <div className="muted">No config available.</div>;
|
|
94
|
+
|
|
95
|
+
return (
|
|
96
|
+
<div className="memory-panel-content">
|
|
97
|
+
{/* ── LightRAG ──────────────────────────────────────────────── */}
|
|
98
|
+
<Card>
|
|
99
|
+
<CardTitle><Brain size={14} /> LightRAG</CardTitle>
|
|
100
|
+
<CardMeta>URL + model overrides. Server-side defaults from <code>opencode Zen free tier</code>.</CardMeta>
|
|
101
|
+
<div className="memory-config-form">
|
|
102
|
+
<Row label="Enabled" inline>
|
|
103
|
+
<label className="memory-switch">
|
|
104
|
+
<input
|
|
105
|
+
type="checkbox"
|
|
106
|
+
checked={!!cfg.lightrag?.enabled}
|
|
107
|
+
onChange={(e) => update('lightrag', 'enabled', e.target.checked)}
|
|
108
|
+
/>
|
|
109
|
+
<span>{cfg.lightrag?.enabled ? 'on' : 'off'}</span>
|
|
110
|
+
</label>
|
|
111
|
+
</Row>
|
|
112
|
+
<Row label="URL">
|
|
113
|
+
<input
|
|
114
|
+
type="text"
|
|
115
|
+
className="input mono"
|
|
116
|
+
value={cfg.lightrag?.url || ''}
|
|
117
|
+
onChange={(e) => update('lightrag', 'url', e.target.value)}
|
|
118
|
+
placeholder="http://127.0.0.1:9621"
|
|
119
|
+
/>
|
|
120
|
+
</Row>
|
|
121
|
+
<Row label="LLM model">
|
|
122
|
+
<input
|
|
123
|
+
type="text"
|
|
124
|
+
className="input mono"
|
|
125
|
+
value={cfg.lightrag?.llm || ''}
|
|
126
|
+
onChange={(e) => update('lightrag', 'llm', e.target.value)}
|
|
127
|
+
placeholder="opencode/gpt-5-nano"
|
|
128
|
+
/>
|
|
129
|
+
</Row>
|
|
130
|
+
<Row label="Embedding">
|
|
131
|
+
<input
|
|
132
|
+
type="text"
|
|
133
|
+
className="input mono"
|
|
134
|
+
value={cfg.lightrag?.embedding || ''}
|
|
135
|
+
onChange={(e) => update('lightrag', 'embedding', e.target.value)}
|
|
136
|
+
placeholder="opencode/text-embedding-3-small"
|
|
137
|
+
/>
|
|
138
|
+
</Row>
|
|
139
|
+
</div>
|
|
140
|
+
</Card>
|
|
141
|
+
|
|
142
|
+
{/* ── Obsidian ──────────────────────────────────────────────── */}
|
|
143
|
+
<Card>
|
|
144
|
+
<CardTitle><FileText size={14} /> Obsidian vault</CardTitle>
|
|
145
|
+
<CardMeta>Path + sync cadence for the markdown vault.</CardMeta>
|
|
146
|
+
<div className="memory-config-form">
|
|
147
|
+
<Row label="Vault path">
|
|
148
|
+
<input
|
|
149
|
+
type="text"
|
|
150
|
+
className="input mono"
|
|
151
|
+
value={cfg.obsidian?.vaultPath || ''}
|
|
152
|
+
onChange={(e) => update('obsidian', 'vaultPath', e.target.value)}
|
|
153
|
+
placeholder="/home/me/vault"
|
|
154
|
+
/>
|
|
155
|
+
</Row>
|
|
156
|
+
<Row label="Sync interval (seconds)">
|
|
157
|
+
<input
|
|
158
|
+
type="number"
|
|
159
|
+
min={0}
|
|
160
|
+
max={2592000}
|
|
161
|
+
className="input mono"
|
|
162
|
+
value={cfg.obsidian?.syncInterval ?? 300}
|
|
163
|
+
onChange={(e) => update('obsidian', 'syncInterval', Number(e.target.value))}
|
|
164
|
+
/>
|
|
165
|
+
</Row>
|
|
166
|
+
</div>
|
|
167
|
+
</Card>
|
|
168
|
+
|
|
169
|
+
{/* ── Git ───────────────────────────────────────────────────── */}
|
|
170
|
+
<Card>
|
|
171
|
+
<CardTitle><GitBranch size={14} /> Git sync</CardTitle>
|
|
172
|
+
<CardMeta>Repo path, remote, branch, and auto-sync toggle.</CardMeta>
|
|
173
|
+
<div className="memory-config-form">
|
|
174
|
+
<Row label="Repo path">
|
|
175
|
+
<input
|
|
176
|
+
type="text"
|
|
177
|
+
className="input mono"
|
|
178
|
+
value={cfg.git?.repoPath || ''}
|
|
179
|
+
onChange={(e) => update('git', 'repoPath', e.target.value)}
|
|
180
|
+
placeholder="/path/to/repo"
|
|
181
|
+
/>
|
|
182
|
+
</Row>
|
|
183
|
+
<Row label="Remote URL">
|
|
184
|
+
<input
|
|
185
|
+
type="text"
|
|
186
|
+
className="input mono"
|
|
187
|
+
value={cfg.git?.remoteUrl || ''}
|
|
188
|
+
onChange={(e) => update('git', 'remoteUrl', e.target.value)}
|
|
189
|
+
placeholder="git@github.com:org/repo.git"
|
|
190
|
+
/>
|
|
191
|
+
</Row>
|
|
192
|
+
<Row label="Branch">
|
|
193
|
+
<input
|
|
194
|
+
type="text"
|
|
195
|
+
className="input mono"
|
|
196
|
+
value={cfg.git?.branch || 'main'}
|
|
197
|
+
onChange={(e) => update('git', 'branch', e.target.value)}
|
|
198
|
+
placeholder="main"
|
|
199
|
+
/>
|
|
200
|
+
</Row>
|
|
201
|
+
<Row label="Auto-sync" inline>
|
|
202
|
+
<label className="memory-switch">
|
|
203
|
+
<input
|
|
204
|
+
type="checkbox"
|
|
205
|
+
checked={!!cfg.git?.autoSync}
|
|
206
|
+
onChange={(e) => update('git', 'autoSync', e.target.checked)}
|
|
207
|
+
/>
|
|
208
|
+
<span>{cfg.git?.autoSync ? 'on' : 'off'}</span>
|
|
209
|
+
</label>
|
|
210
|
+
</Row>
|
|
211
|
+
</div>
|
|
212
|
+
</Card>
|
|
213
|
+
|
|
214
|
+
{/* ── CCR (placeholder when Headroom is installed) ─────────── */}
|
|
215
|
+
<Card>
|
|
216
|
+
<CardTitle>
|
|
217
|
+
<Sparkles size={14} /> CCR (Compress-Cache-Retrieve)
|
|
218
|
+
</CardTitle>
|
|
219
|
+
<CardMeta>
|
|
220
|
+
Reversible compression for very long conversations. Enabled automatically
|
|
221
|
+
when the Headroom mod is installed.
|
|
222
|
+
</CardMeta>
|
|
223
|
+
<div className="muted text-sm">
|
|
224
|
+
See Settings → Headroom for CCR configuration. This panel will surface
|
|
225
|
+
retention + compression-ratio controls when the mod is active.
|
|
226
|
+
</div>
|
|
227
|
+
</Card>
|
|
228
|
+
|
|
229
|
+
{/* ── Test all connections ─────────────────────────────────── */}
|
|
230
|
+
<Card>
|
|
231
|
+
<CardTitle>
|
|
232
|
+
<Plug size={14} /> Connection tests
|
|
233
|
+
</CardTitle>
|
|
234
|
+
<CardMeta>Verify the configured git repo, remote, and path.</CardMeta>
|
|
235
|
+
<div className="memory-action-row">
|
|
236
|
+
<Button variant="secondary" onClick={onTest} disabled={testing}>
|
|
237
|
+
{testing ? <Loader2 size={12} className="memory-spin" /> : <Plug size={12} />}
|
|
238
|
+
Test git connection
|
|
239
|
+
</Button>
|
|
240
|
+
<Button variant="primary" onClick={onSave} disabled={saving}>
|
|
241
|
+
{saving ? <Loader2 size={12} className="memory-spin" /> : <Save size={12} />}
|
|
242
|
+
Save config
|
|
243
|
+
</Button>
|
|
244
|
+
</div>
|
|
245
|
+
{tests && tests.length > 0 && (
|
|
246
|
+
<ul className="memory-check-list" style={{ marginTop: 12 }}>
|
|
247
|
+
{tests.map((t, i) => (
|
|
248
|
+
<li key={i} className="memory-check-row">
|
|
249
|
+
<span className="memory-check-icon">
|
|
250
|
+
<span className={cn('memory-pill', t.pass ? 'memory-pill-ok' : 'memory-pill-warn')}>
|
|
251
|
+
{t.pass ? 'OK' : 'FAIL'}
|
|
252
|
+
</span>
|
|
253
|
+
</span>
|
|
254
|
+
<span className="memory-check-name">{t.name}</span>
|
|
255
|
+
<span className="memory-check-detail muted">{t.detail}</span>
|
|
256
|
+
</li>
|
|
257
|
+
))}
|
|
258
|
+
</ul>
|
|
259
|
+
)}
|
|
260
|
+
</Card>
|
|
261
|
+
</div>
|
|
262
|
+
);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function Row({
|
|
266
|
+
label,
|
|
267
|
+
children,
|
|
268
|
+
inline,
|
|
269
|
+
}: {
|
|
270
|
+
label: string;
|
|
271
|
+
children: React.ReactNode;
|
|
272
|
+
inline?: boolean;
|
|
273
|
+
}) {
|
|
274
|
+
if (inline) {
|
|
275
|
+
return (
|
|
276
|
+
<div className="memory-config-row-inline">
|
|
277
|
+
<div className="memory-config-row-label">{label}</div>
|
|
278
|
+
<div className="memory-config-row-control">{children}</div>
|
|
279
|
+
</div>
|
|
280
|
+
);
|
|
281
|
+
}
|
|
282
|
+
return (
|
|
283
|
+
<label className="memory-config-row-stack">
|
|
284
|
+
<span>{label}</span>
|
|
285
|
+
{children}
|
|
286
|
+
</label>
|
|
287
|
+
);
|
|
288
|
+
}
|
|
289
|
+
|
|
@@ -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
|
+
}
|