@polderlabs/bizar 5.0.0 → 5.0.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/{icons-CFqu2M-c.js → icons-Bo0iH9EC.js} +166 -121
- package/bizar-dash/dist/assets/icons-Bo0iH9EC.js.map +1 -0
- package/bizar-dash/dist/assets/main-C1cpttnv.js +19 -0
- package/bizar-dash/dist/assets/main-C1cpttnv.js.map +1 -0
- package/bizar-dash/dist/assets/main-DTkNlLrw.css +1 -0
- package/bizar-dash/dist/assets/mobile-BtxQJftK.js +2 -0
- package/bizar-dash/dist/assets/mobile-BtxQJftK.js.map +1 -0
- package/bizar-dash/dist/assets/{mobile-DHXXbn1A.js → mobile-DD-FZrTC.js} +1 -1
- package/bizar-dash/dist/assets/{mobile-DHXXbn1A.js.map → mobile-DD-FZrTC.js.map} +1 -1
- package/bizar-dash/dist/index.html +4 -4
- package/bizar-dash/dist/mobile.html +3 -3
- package/bizar-dash/node_modules/.package-lock.json +6 -0
- package/bizar-dash/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -1
- package/bizar-dash/package-lock.json +6 -0
- package/bizar-dash/src/server/api.mjs +7 -0
- package/bizar-dash/src/server/diagnostics-store.mjs +452 -2
- package/bizar-dash/src/server/memory-store.mjs +46 -0
- package/bizar-dash/src/server/providers-store.mjs +11 -4
- package/bizar-dash/src/server/routes/_shared.mjs +2 -2
- package/bizar-dash/src/server/routes/doctor.mjs +71 -0
- package/bizar-dash/src/server/routes/env-vars.mjs +67 -1
- package/bizar-dash/src/server/routes/memory.mjs +3 -0
- package/bizar-dash/src/server/routes/opencode-session-detail.mjs +61 -42
- package/bizar-dash/src/server/routes/schedules.mjs +55 -0
- package/bizar-dash/src/server/serve-info.mjs +172 -0
- package/bizar-dash/src/web/App.tsx +48 -11
- package/bizar-dash/src/web/components/AutosaveField.tsx +50 -0
- package/bizar-dash/src/web/components/DoctorPanel.tsx +160 -0
- package/bizar-dash/src/web/components/EnvVarManager.tsx +245 -62
- package/bizar-dash/src/web/components/ScheduleTemplateCard.tsx +71 -0
- package/bizar-dash/src/web/components/SettingsNav.tsx +101 -0
- package/bizar-dash/src/web/components/Sidebar.tsx +25 -1
- package/bizar-dash/src/web/components/StatusBadge.tsx +33 -2
- package/bizar-dash/src/web/components/Topbar.tsx +11 -0
- package/bizar-dash/src/web/components/chat/ChatInfoPanel.tsx +64 -0
- package/bizar-dash/src/web/components/chat/Composer.tsx +1 -1
- package/bizar-dash/src/web/components/chat/useChat.ts +118 -1
- package/bizar-dash/src/web/hooks/useAutosave.ts +107 -0
- package/bizar-dash/src/web/lib/types.ts +134 -0
- package/bizar-dash/src/web/styles/chat.css +8 -5
- package/bizar-dash/src/web/styles/main.css +77 -2
- package/bizar-dash/src/web/styles/settings.css +265 -0
- package/bizar-dash/src/web/views/Chat.tsx +15 -1
- package/bizar-dash/src/web/views/Doctor.tsx +317 -0
- package/bizar-dash/src/web/views/MiniMaxUsage.tsx +1 -0
- package/bizar-dash/src/web/views/Schedules.tsx +94 -12
- package/bizar-dash/src/web/views/Settings.tsx +76 -46
- package/bizar-dash/src/web/views/memory/ConfigPanel.tsx +114 -133
- package/bizar-dash/src/web/views/settings/AgentSection.tsx +23 -12
- package/bizar-dash/src/web/views/settings/EnvVarsSection.tsx +5 -6
- package/bizar-dash/src/web/views/settings/GeneralSection.tsx +38 -15
- package/bizar-dash/src/web/views/settings/MemorySection.tsx +92 -8
- package/bizar-dash/tests/autosave.test.tsx +276 -0
- package/bizar-dash/tests/chat-composer.test.tsx +140 -0
- package/bizar-dash/tests/components/doctor-panel.test.tsx +105 -0
- package/bizar-dash/tests/diagnostics-store.test.mjs +206 -0
- package/bizar-dash/tests/env-vars-extended.test.mjs +190 -0
- package/bizar-dash/tests/layout-spacing.test.mjs +101 -0
- package/bizar-dash/tests/memory-default-vault.test.mjs +98 -0
- package/bizar-dash/tests/minimax-bar.test.tsx +113 -0
- package/bizar-dash/tests/minimax-models.test.mjs +123 -0
- package/bizar-dash/tests/opencode-sessions-detail.test.mjs +158 -0
- package/bizar-dash/tests/routes-doctor.test.mjs +159 -0
- package/bizar-dash/tests/schedules-templates.test.mjs +144 -0
- package/bizar-dash/tests/settings-layout.test.tsx +129 -0
- package/bizar-dash/tests/settings-mode-wiring.test.tsx +151 -0
- package/bizar-dash/tests/settings-nav.test.tsx +126 -0
- package/cli/commands/dash.mjs +5 -1
- package/cli/commands/minimax.mjs +5 -2
- package/cli/tests/minimax-cli.test.mjs +79 -0
- package/config/agents/frigg.md +1 -1
- package/config/agents/heimdall.md +1 -1
- package/config/agents/mimir.md +1 -1
- package/config/agents/quick.md +1 -1
- package/config/agents/semble-search.md +1 -1
- package/config/agents/vor.md +1 -1
- package/config/opencode.json +28 -21
- package/config/opencode.json.template +8 -1
- package/package.json +1 -1
- package/plugins/bizar/index.ts +77 -0
- package/plugins/bizar/src/compaction.d.mts +48 -0
- package/plugins/bizar/src/compaction.mjs +192 -0
- package/plugins/bizar/tests/compaction.test.ts +264 -0
- package/templates/schedules/daily-backup.json +12 -0
- package/templates/schedules/daily-cleanup.json +12 -0
- package/templates/schedules/hourly-health-check.json +12 -0
- package/templates/schedules/webhook-on-push.json +13 -0
- package/templates/schedules/weekly-digest.json +13 -0
- package/bizar-dash/dist/assets/icons-CFqu2M-c.js.map +0 -1
- package/bizar-dash/dist/assets/main-Dl8yY5_H.js +0 -16
- package/bizar-dash/dist/assets/main-Dl8yY5_H.js.map +0 -1
- package/bizar-dash/dist/assets/main-ZAfGKENE.css +0 -1
- package/bizar-dash/dist/assets/mobile-C2gysFOZ.js +0 -2
- package/bizar-dash/dist/assets/mobile-C2gysFOZ.js.map +0 -1
- package/bizar-dash/src/web/views/settings/BackupSection.tsx +0 -16
- package/bizar-dash/src/web/views/settings/ProvidersSection.tsx +0 -16
- package/bizar-dash/src/web/views/settings/SkillsSection.tsx +0 -16
|
@@ -1,14 +1,13 @@
|
|
|
1
|
-
// src/web/views/memory/ConfigPanel.tsx —
|
|
1
|
+
// src/web/views/memory/ConfigPanel.tsx — simplified memory config.
|
|
2
2
|
import { useEffect, useState } from 'react';
|
|
3
3
|
import {
|
|
4
|
-
Brain,
|
|
5
|
-
Database,
|
|
6
|
-
FileText,
|
|
7
4
|
GitBranch,
|
|
5
|
+
GitCommitHorizontal,
|
|
6
|
+
Download,
|
|
7
|
+
Upload,
|
|
8
8
|
Loader2,
|
|
9
9
|
Plug,
|
|
10
10
|
Save,
|
|
11
|
-
Sparkles,
|
|
12
11
|
} from 'lucide-react';
|
|
13
12
|
import { Button } from '../../components/Button';
|
|
14
13
|
import { Card, CardMeta, CardTitle } from '../../components/Card';
|
|
@@ -17,10 +16,19 @@ import { useToast } from '../../components/Toast';
|
|
|
17
16
|
import { api } from '../../lib/api';
|
|
18
17
|
import { cn } from '../../lib/utils';
|
|
19
18
|
|
|
19
|
+
type GitConfig = {
|
|
20
|
+
remoteUrl?: string;
|
|
21
|
+
};
|
|
22
|
+
|
|
20
23
|
type GlobalConfig = {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
+
git?: GitConfig;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
type SyncStatus = {
|
|
28
|
+
clean?: boolean;
|
|
29
|
+
branch?: string;
|
|
30
|
+
modified?: number;
|
|
31
|
+
untracked?: number;
|
|
24
32
|
};
|
|
25
33
|
|
|
26
34
|
type Props = { refreshKey: number };
|
|
@@ -30,6 +38,10 @@ export function ConfigPanel({ refreshKey }: Props) {
|
|
|
30
38
|
const [cfg, setCfg] = useState<GlobalConfig | null>(null);
|
|
31
39
|
const [loading, setLoading] = useState(true);
|
|
32
40
|
const [saving, setSaving] = useState(false);
|
|
41
|
+
const [remoteUrl, setRemoteUrl] = useState('');
|
|
42
|
+
const [vaultPath] = useState<string>('');
|
|
43
|
+
const [syncStatus, setSyncStatus] = useState<SyncStatus | null>(null);
|
|
44
|
+
const [actionLoading, setActionLoading] = useState<string | null>(null);
|
|
33
45
|
const [tests, setTests] = useState<Array<{ name: string; pass: boolean; detail: string }> | null>(null);
|
|
34
46
|
const [testing, setTesting] = useState(false);
|
|
35
47
|
|
|
@@ -38,6 +50,9 @@ export function ConfigPanel({ refreshKey }: Props) {
|
|
|
38
50
|
try {
|
|
39
51
|
const r = await api.get<{ config: GlobalConfig }>('/memory/config/global');
|
|
40
52
|
setCfg(r.config);
|
|
53
|
+
setRemoteUrl(r.config?.git?.remoteUrl || '');
|
|
54
|
+
// Also fetch git status from the per-project vault
|
|
55
|
+
await loadSyncStatus();
|
|
41
56
|
} catch (err) {
|
|
42
57
|
toast.error(`Config load failed: ${(err as Error).message}`);
|
|
43
58
|
} finally {
|
|
@@ -45,20 +60,32 @@ export function ConfigPanel({ refreshKey }: Props) {
|
|
|
45
60
|
}
|
|
46
61
|
};
|
|
47
62
|
|
|
63
|
+
const loadSyncStatus = async () => {
|
|
64
|
+
try {
|
|
65
|
+
const r = await api.get<{ ok: boolean; clean?: boolean; branch?: string; modified?: number; untracked?: number }>('/memory/git/status');
|
|
66
|
+
if (r.ok) {
|
|
67
|
+
setSyncStatus({
|
|
68
|
+
clean: r.clean,
|
|
69
|
+
branch: r.branch,
|
|
70
|
+
modified: r.modified,
|
|
71
|
+
untracked: r.untracked,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
} catch {
|
|
75
|
+
// git status is best-effort
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
|
|
48
79
|
useEffect(() => {
|
|
49
80
|
reload();
|
|
50
81
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
51
82
|
}, [refreshKey]);
|
|
52
83
|
|
|
53
|
-
const update = (block: keyof GlobalConfig, key: string, value: unknown) => {
|
|
54
|
-
setCfg((cur) => cur ? { ...cur, [block]: { ...(cur[block] || {}), [key]: value } } : cur);
|
|
55
|
-
};
|
|
56
|
-
|
|
57
84
|
const onSave = async () => {
|
|
58
85
|
if (!cfg) return;
|
|
59
86
|
setSaving(true);
|
|
60
87
|
try {
|
|
61
|
-
await api.put('/memory/config/global',
|
|
88
|
+
await api.put('/memory/config/global', { git: { remoteUrl } });
|
|
62
89
|
toast.success('Config saved.');
|
|
63
90
|
} catch (err) {
|
|
64
91
|
toast.error(`Save failed: ${(err as Error).message}`);
|
|
@@ -67,6 +94,30 @@ export function ConfigPanel({ refreshKey }: Props) {
|
|
|
67
94
|
}
|
|
68
95
|
};
|
|
69
96
|
|
|
97
|
+
const runAction = async (action: 'pull' | 'push' | 'commit') => {
|
|
98
|
+
setActionLoading(action);
|
|
99
|
+
try {
|
|
100
|
+
if (action === 'pull') {
|
|
101
|
+
const r = await api.post<{ ok: boolean; output?: string }>('/memory/git/pull', {});
|
|
102
|
+
if (r.ok) toast.success('Pull successful.');
|
|
103
|
+
else toast.error(`Pull failed: ${r.output || 'unknown error'}`);
|
|
104
|
+
} else if (action === 'push') {
|
|
105
|
+
const r = await api.post<{ ok: boolean }>('/memory/git/push', {});
|
|
106
|
+
if (r.ok) toast.success('Push successful.');
|
|
107
|
+
else toast.error('Push failed.');
|
|
108
|
+
} else if (action === 'commit') {
|
|
109
|
+
const r = await api.post<{ ok: boolean; message?: string }>('/memory/git/commit', {});
|
|
110
|
+
if (r.ok) toast.success(`Committed: ${r.message}`);
|
|
111
|
+
else toast.error('Commit failed.');
|
|
112
|
+
}
|
|
113
|
+
await loadSyncStatus();
|
|
114
|
+
} catch (err) {
|
|
115
|
+
toast.error(`${action} failed: ${(err as Error).message}`);
|
|
116
|
+
} finally {
|
|
117
|
+
setActionLoading(null);
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
|
|
70
121
|
const onTest = async () => {
|
|
71
122
|
setTesting(true);
|
|
72
123
|
try {
|
|
@@ -94,144 +145,75 @@ export function ConfigPanel({ refreshKey }: Props) {
|
|
|
94
145
|
|
|
95
146
|
return (
|
|
96
147
|
<div className="memory-panel-content">
|
|
97
|
-
{/* ──
|
|
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 ───────────────────────────────────────────────────── */}
|
|
148
|
+
{/* ── Git sync ───────────────────────────────────────────────────── */}
|
|
170
149
|
<Card>
|
|
171
150
|
<CardTitle><GitBranch size={14} /> Git sync</CardTitle>
|
|
172
|
-
<CardMeta>
|
|
151
|
+
<CardMeta>Configure the git remote and sync your memory vault.</CardMeta>
|
|
173
152
|
<div className="memory-config-form">
|
|
174
|
-
<Row label="
|
|
175
|
-
<
|
|
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
|
-
/>
|
|
153
|
+
<Row label="Vault path" inline>
|
|
154
|
+
<span className="memory-vault-path mono muted">{vaultPath || '(loading…)'}</span>
|
|
182
155
|
</Row>
|
|
183
156
|
<Row label="Remote URL">
|
|
184
157
|
<input
|
|
185
158
|
type="text"
|
|
186
159
|
className="input mono"
|
|
187
|
-
value={
|
|
188
|
-
onChange={(e) =>
|
|
160
|
+
value={remoteUrl}
|
|
161
|
+
onChange={(e) => setRemoteUrl(e.target.value)}
|
|
189
162
|
placeholder="git@github.com:org/repo.git"
|
|
190
163
|
/>
|
|
191
164
|
</Row>
|
|
192
|
-
|
|
193
|
-
<
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
</label>
|
|
210
|
-
</Row>
|
|
165
|
+
{syncStatus && (
|
|
166
|
+
<Row label="Status" inline>
|
|
167
|
+
<span className={cn('memory-pill', syncStatus.clean ? 'memory-pill-ok' : 'memory-pill-warn')}>
|
|
168
|
+
{syncStatus.clean ? 'clean' : 'dirty'}
|
|
169
|
+
</span>
|
|
170
|
+
{syncStatus.branch && (
|
|
171
|
+
<span className="muted" style={{ marginLeft: 8 }}>
|
|
172
|
+
<GitBranch size={12} style={{ display: 'inline' }} /> {syncStatus.branch}
|
|
173
|
+
</span>
|
|
174
|
+
)}
|
|
175
|
+
{!syncStatus.clean && syncStatus.modified !== undefined && (
|
|
176
|
+
<span className="muted" style={{ marginLeft: 8 }}>
|
|
177
|
+
{syncStatus.modified} modified, {syncStatus.untracked} untracked
|
|
178
|
+
</span>
|
|
179
|
+
)}
|
|
180
|
+
</Row>
|
|
181
|
+
)}
|
|
211
182
|
</div>
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
183
|
+
<div className="memory-action-row" style={{ marginTop: 12 }}>
|
|
184
|
+
<Button
|
|
185
|
+
variant="secondary"
|
|
186
|
+
onClick={() => runAction('pull')}
|
|
187
|
+
disabled={actionLoading !== null}
|
|
188
|
+
>
|
|
189
|
+
{actionLoading === 'pull' ? <Loader2 size={12} className="memory-spin" /> : <Download size={12} />}
|
|
190
|
+
Pull
|
|
191
|
+
</Button>
|
|
192
|
+
<Button
|
|
193
|
+
variant="secondary"
|
|
194
|
+
onClick={() => runAction('commit')}
|
|
195
|
+
disabled={actionLoading !== null}
|
|
196
|
+
>
|
|
197
|
+
{actionLoading === 'commit' ? <Loader2 size={12} className="memory-spin" /> : <GitCommitHorizontal size={12} />}
|
|
198
|
+
Commit
|
|
199
|
+
</Button>
|
|
200
|
+
<Button
|
|
201
|
+
variant="secondary"
|
|
202
|
+
onClick={() => runAction('push')}
|
|
203
|
+
disabled={actionLoading !== null}
|
|
204
|
+
>
|
|
205
|
+
{actionLoading === 'push' ? <Loader2 size={12} className="memory-spin" /> : <Upload size={12} />}
|
|
206
|
+
Push
|
|
207
|
+
</Button>
|
|
226
208
|
</div>
|
|
227
209
|
</Card>
|
|
228
210
|
|
|
229
|
-
{/* ──
|
|
211
|
+
{/* ── Connection tests ─────────────────────────────────────────── */}
|
|
230
212
|
<Card>
|
|
231
213
|
<CardTitle>
|
|
232
214
|
<Plug size={14} /> Connection tests
|
|
233
215
|
</CardTitle>
|
|
234
|
-
<CardMeta>Verify the configured git
|
|
216
|
+
<CardMeta>Verify the configured git remote is reachable.</CardMeta>
|
|
235
217
|
<div className="memory-action-row">
|
|
236
218
|
<Button variant="secondary" onClick={onTest} disabled={testing}>
|
|
237
219
|
{testing ? <Loader2 size={12} className="memory-spin" /> : <Plug size={12} />}
|
|
@@ -292,4 +274,3 @@ function Row({
|
|
|
292
274
|
</label>
|
|
293
275
|
);
|
|
294
276
|
}
|
|
295
|
-
|
|
@@ -3,6 +3,7 @@ import React, { useEffect, useState } from 'react';
|
|
|
3
3
|
import { Server as ServerIcon, Globe, Save } from 'lucide-react';
|
|
4
4
|
import { Card, CardTitle, CardMeta } from '../../components/Card';
|
|
5
5
|
import { Button } from '../../components/Button';
|
|
6
|
+
import { AutosaveField } from '../../components/AutosaveField';
|
|
6
7
|
import { useToast } from '../../components/Toast';
|
|
7
8
|
import { api } from '../../lib/api';
|
|
8
9
|
import type { Settings } from '../../lib/types';
|
|
@@ -11,9 +12,11 @@ type Props = {
|
|
|
11
12
|
settings: Settings;
|
|
12
13
|
patchAgents: (patch: Partial<Settings['agents']>) => void;
|
|
13
14
|
patchDashboard: (patch: Partial<Settings['dashboard']>) => void;
|
|
15
|
+
/** Called after state is patched; parent debounces the API save */
|
|
16
|
+
autoSave?: (key: keyof Settings, value: Settings[keyof Settings]) => void;
|
|
14
17
|
};
|
|
15
18
|
|
|
16
|
-
export function AgentSection({ settings, patchAgents, patchDashboard }: Props) {
|
|
19
|
+
export function AgentSection({ settings, patchAgents, patchDashboard, autoSave }: Props) {
|
|
17
20
|
const toast = useToast();
|
|
18
21
|
const [pluginOptions, setPluginOptions] = useState<Record<string, number>>({});
|
|
19
22
|
|
|
@@ -139,19 +142,27 @@ export function AgentSection({ settings, patchAgents, patchDashboard }: Props) {
|
|
|
139
142
|
<label className="field-label" htmlFor="set-allowed-roots">
|
|
140
143
|
Additional allowed roots <span className="muted">(advanced)</span>
|
|
141
144
|
</label>
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
const lines = e.target.value
|
|
150
|
-
.split('\n')
|
|
151
|
-
.map((l) => l.trim())
|
|
152
|
-
.filter(Boolean);
|
|
145
|
+
{/* key= forces re-mount when allowedRoots changes externally (e.g. reset/reload) */}
|
|
146
|
+
<AutosaveField
|
|
147
|
+
key={settings.dashboard.allowedRoots?.join('\n')}
|
|
148
|
+
initialValue={(settings.dashboard.allowedRoots ?? []).join('\n')}
|
|
149
|
+
delay={1500}
|
|
150
|
+
saveFn={async (v) => {
|
|
151
|
+
const lines = v.split('\n').map((l) => l.trim()).filter(Boolean);
|
|
153
152
|
patchDashboard({ allowedRoots: lines });
|
|
153
|
+
autoSave?.('dashboard', { allowedRoots: lines } as Settings['dashboard']);
|
|
154
154
|
}}
|
|
155
|
+
render={({ value, onChange, onBlur }) => (
|
|
156
|
+
<textarea
|
|
157
|
+
id="set-allowed-roots"
|
|
158
|
+
className="textarea"
|
|
159
|
+
rows={4}
|
|
160
|
+
placeholder="/workspace /srv/projects"
|
|
161
|
+
value={value}
|
|
162
|
+
onChange={e => onChange(e.target.value)}
|
|
163
|
+
onBlur={onBlur}
|
|
164
|
+
/>
|
|
165
|
+
)}
|
|
155
166
|
/>
|
|
156
167
|
<p className="field-help">
|
|
157
168
|
Optional. Add filesystem roots beyond your home directory that the file
|
|
@@ -1,16 +1,15 @@
|
|
|
1
1
|
// src/web/views/settings/EnvVarsSection.tsx
|
|
2
|
-
// Placeholder — EnvVarManager integration to be added in a future PR.
|
|
3
2
|
import React from 'react';
|
|
3
|
+
import { KeyRound } from 'lucide-react';
|
|
4
4
|
import { Card, CardTitle, CardMeta } from '../../components/Card';
|
|
5
|
+
import { EnvVarManager } from '../../components/EnvVarManager';
|
|
5
6
|
|
|
6
7
|
export function EnvVarsSection() {
|
|
7
8
|
return (
|
|
8
9
|
<Card id="settings-env-vars" data-section="env-vars">
|
|
9
|
-
<CardTitle
|
|
10
|
-
<CardMeta>
|
|
11
|
-
<
|
|
12
|
-
Environment variable management is coming soon.
|
|
13
|
-
</p>
|
|
10
|
+
<CardTitle><KeyRound size={14} /> Environment Variables</CardTitle>
|
|
11
|
+
<CardMeta>Manage BIZAR_* env vars (API keys, secrets). Values are stored in <code>~/.config/bizar/env.json</code> (mode 0600).</CardMeta>
|
|
12
|
+
<EnvVarManager />
|
|
14
13
|
</Card>
|
|
15
14
|
);
|
|
16
15
|
}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import React from 'react';
|
|
3
3
|
import { Layout as LayoutIcon } from 'lucide-react';
|
|
4
4
|
import { Card, CardTitle, CardMeta } from '../../components/Card';
|
|
5
|
+
import { AutosaveField } from '../../components/AutosaveField';
|
|
5
6
|
import { cn } from '../../lib/utils';
|
|
6
7
|
import type { Settings } from '../../lib/types';
|
|
7
8
|
|
|
@@ -9,6 +10,8 @@ type Props = {
|
|
|
9
10
|
settings: Settings;
|
|
10
11
|
patchUi: (patch: Partial<Settings['ui']>) => void;
|
|
11
12
|
patchTop: <K extends keyof Settings>(key: K, value: Settings[K]) => void;
|
|
13
|
+
/** Called by AutosaveField after state is patched; the parent debounces the API save */
|
|
14
|
+
autoSave?: (key: keyof Settings, value: Settings[keyof Settings]) => void;
|
|
12
15
|
};
|
|
13
16
|
|
|
14
17
|
const LAYOUTS = [
|
|
@@ -17,7 +20,7 @@ const LAYOUTS = [
|
|
|
17
20
|
{ id: 'both', label: 'Both' },
|
|
18
21
|
] as const;
|
|
19
22
|
|
|
20
|
-
export function GeneralSection({ settings, patchUi, patchTop }: Props) {
|
|
23
|
+
export function GeneralSection({ settings, patchUi, patchTop, autoSave }: Props) {
|
|
21
24
|
return (
|
|
22
25
|
<>
|
|
23
26
|
{/* UI Layout */}
|
|
@@ -79,24 +82,44 @@ export function GeneralSection({ settings, patchUi, patchTop }: Props) {
|
|
|
79
82
|
<CardMeta>Default agent + model override.</CardMeta>
|
|
80
83
|
<div className="field" data-setting-id="defaultAgent">
|
|
81
84
|
<label className="field-label" htmlFor="set-default-agent">Default agent</label>
|
|
82
|
-
<
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
85
|
+
<AutosaveField
|
|
86
|
+
initialValue={settings.defaultAgent || ''}
|
|
87
|
+
saveFn={async (v) => {
|
|
88
|
+
patchTop('defaultAgent', v);
|
|
89
|
+
autoSave?.('defaultAgent', v);
|
|
90
|
+
}}
|
|
91
|
+
render={({ value, onChange, onBlur }) => (
|
|
92
|
+
<input
|
|
93
|
+
id="set-default-agent"
|
|
94
|
+
className="input"
|
|
95
|
+
type="text"
|
|
96
|
+
placeholder="e.g. odin"
|
|
97
|
+
value={value}
|
|
98
|
+
onChange={e => onChange(e.target.value)}
|
|
99
|
+
onBlur={onBlur}
|
|
100
|
+
/>
|
|
101
|
+
)}
|
|
89
102
|
/>
|
|
90
103
|
</div>
|
|
91
104
|
<div className="field" data-setting-id="defaultModel">
|
|
92
105
|
<label className="field-label" htmlFor="set-default-model">Model override</label>
|
|
93
|
-
<
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
106
|
+
<AutosaveField
|
|
107
|
+
initialValue={settings.defaultModel || ''}
|
|
108
|
+
saveFn={async (v) => {
|
|
109
|
+
patchTop('defaultModel', v);
|
|
110
|
+
autoSave?.('defaultModel', v);
|
|
111
|
+
}}
|
|
112
|
+
render={({ value, onChange, onBlur }) => (
|
|
113
|
+
<input
|
|
114
|
+
id="set-default-model"
|
|
115
|
+
className="input"
|
|
116
|
+
type="text"
|
|
117
|
+
placeholder="(leave empty for provider default)"
|
|
118
|
+
value={value}
|
|
119
|
+
onChange={e => onChange(e.target.value)}
|
|
120
|
+
onBlur={onBlur}
|
|
121
|
+
/>
|
|
122
|
+
)}
|
|
100
123
|
/>
|
|
101
124
|
</div>
|
|
102
125
|
</Card>
|
|
@@ -1,16 +1,100 @@
|
|
|
1
|
-
// src/web/views/settings/MemorySection.tsx
|
|
2
|
-
|
|
3
|
-
import
|
|
1
|
+
// src/web/views/settings/MemorySection.tsx — memory vault + git config for Settings.
|
|
2
|
+
import React, { useEffect, useState } from 'react';
|
|
3
|
+
import { Brain, GitBranch, Loader2, Save } from 'lucide-react';
|
|
4
|
+
import { Button } from '../../components/Button';
|
|
4
5
|
import { Card, CardTitle, CardMeta } from '../../components/Card';
|
|
6
|
+
import { Spinner } from '../../components/Spinner';
|
|
7
|
+
import { useToast } from '../../components/Toast';
|
|
8
|
+
import { api } from '../../lib/api';
|
|
9
|
+
|
|
10
|
+
type GitConfig = {
|
|
11
|
+
remoteUrl?: string;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
type GlobalConfig = {
|
|
15
|
+
git?: GitConfig;
|
|
16
|
+
};
|
|
5
17
|
|
|
6
18
|
export function MemorySection() {
|
|
19
|
+
const toast = useToast();
|
|
20
|
+
const [loading, setLoading] = useState(true);
|
|
21
|
+
const [saving, setSaving] = useState(false);
|
|
22
|
+
const [remoteUrl, setRemoteUrl] = useState('');
|
|
23
|
+
const [vaultPath] = useState<string>(''); // server-side default, not user-editable
|
|
24
|
+
const [refreshKey, setRefreshKey] = useState(0);
|
|
25
|
+
|
|
26
|
+
useEffect(() => {
|
|
27
|
+
api.get<{ config: GlobalConfig; path?: string }>('/memory/config/global')
|
|
28
|
+
.then((r) => {
|
|
29
|
+
setRemoteUrl(r.config?.git?.remoteUrl || '');
|
|
30
|
+
setLoading(false);
|
|
31
|
+
})
|
|
32
|
+
.catch(() => setLoading(false));
|
|
33
|
+
}, [refreshKey]);
|
|
34
|
+
|
|
35
|
+
const onSave = async () => {
|
|
36
|
+
setSaving(true);
|
|
37
|
+
try {
|
|
38
|
+
await api.put('/memory/config/global', { git: { remoteUrl } });
|
|
39
|
+
toast.success('Memory config saved.');
|
|
40
|
+
setRefreshKey((k) => k + 1);
|
|
41
|
+
} catch (err) {
|
|
42
|
+
toast.error(`Save failed: ${(err as Error).message}`);
|
|
43
|
+
} finally {
|
|
44
|
+
setSaving(false);
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
|
|
7
48
|
return (
|
|
8
49
|
<Card id="settings-memory" data-section="memory">
|
|
9
|
-
<CardTitle
|
|
10
|
-
<CardMeta>
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
</
|
|
50
|
+
<CardTitle><Brain size={14} /> Memory vault</CardTitle>
|
|
51
|
+
<CardMeta>
|
|
52
|
+
The vault lives at a default location on disk. Only the git remote URL
|
|
53
|
+
needs to be configured to enable sync across machines.
|
|
54
|
+
</CardMeta>
|
|
55
|
+
|
|
56
|
+
{loading ? (
|
|
57
|
+
<div className="flex-center" style={{ padding: 24 }}>
|
|
58
|
+
<Spinner size="sm" />
|
|
59
|
+
</div>
|
|
60
|
+
) : (
|
|
61
|
+
<div className="settings-fields">
|
|
62
|
+
<div className="field">
|
|
63
|
+
<label className="field-label">Vault path</label>
|
|
64
|
+
<div className="field-readonly mono muted" style={{ fontSize: 12, padding: '6px 0' }}>
|
|
65
|
+
{vaultPath || '~/.local/share/bizar/memory'}
|
|
66
|
+
</div>
|
|
67
|
+
</div>
|
|
68
|
+
|
|
69
|
+
<div className="field">
|
|
70
|
+
<label className="field-label" htmlFor="set-memory-git-remote">
|
|
71
|
+
<GitBranch size={12} style={{ display: 'inline', marginRight: 4 }} />
|
|
72
|
+
Git remote URL
|
|
73
|
+
</label>
|
|
74
|
+
<input
|
|
75
|
+
id="set-memory-git-remote"
|
|
76
|
+
className="input mono"
|
|
77
|
+
type="text"
|
|
78
|
+
value={remoteUrl}
|
|
79
|
+
onChange={(e) => setRemoteUrl(e.target.value)}
|
|
80
|
+
placeholder="git@github.com:org/repo.git"
|
|
81
|
+
/>
|
|
82
|
+
<span className="field-hint">Leave empty to use local-only mode (no git sync).</span>
|
|
83
|
+
</div>
|
|
84
|
+
|
|
85
|
+
<div style={{ marginTop: 12 }}>
|
|
86
|
+
<Button
|
|
87
|
+
variant="primary"
|
|
88
|
+
size="sm"
|
|
89
|
+
onClick={onSave}
|
|
90
|
+
disabled={saving}
|
|
91
|
+
>
|
|
92
|
+
{saving ? <Loader2 size={12} className="memory-spin" /> : <Save size={12} />}
|
|
93
|
+
Save memory config
|
|
94
|
+
</Button>
|
|
95
|
+
</div>
|
|
96
|
+
</div>
|
|
97
|
+
)}
|
|
14
98
|
</Card>
|
|
15
99
|
);
|
|
16
100
|
}
|