@polderlabs/bizar 4.0.0 → 4.2.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/README.md +11 -14
- package/bizar-dash/CHANGELOG.md +1 -1
- package/bizar-dash/src/server/api.mjs +2 -2
- package/bizar-dash/src/server/artifacts-store.mjs +4 -4
- package/bizar-dash/src/server/memory-git.mjs +142 -0
- package/bizar-dash/src/server/memory-lightrag.mjs +767 -0
- package/bizar-dash/src/server/memory-store.mjs +129 -10
- package/bizar-dash/src/server/mod-security.mjs +2 -2
- package/bizar-dash/src/server/routes/memory.mjs +174 -15
- package/bizar-dash/src/server/server.mjs +1 -1
- package/bizar-dash/src/server/state.mjs +2 -2
- package/bizar-dash/src/web/views/Config.tsx +461 -1
- package/bizar-dash/tests/memory-cli-readlistdelete.test.mjs +405 -0
- package/bizar-dash/tests/memory-cli-setup.test.mjs +382 -0
- package/bizar-dash/tests/memory-cli.test.mjs +542 -0
- package/bizar-dash/tests/memory-config.test.mjs +422 -0
- package/bizar-dash/tests/memory-conflicts.test.mjs +229 -0
- package/bizar-dash/tests/memory-git.test.mjs +109 -1
- package/bizar-dash/tests/memory-lightrag.test.mjs +153 -0
- package/bizar-dash/tests/memory-namespace.test.mjs +404 -0
- package/bizar-dash/tests/memory-path-safety.test.mjs +427 -0
- package/bizar-dash/tests/memory-protocol-drift.test.mjs +45 -0
- package/bizar-dash/tests/memory-roundtrip.test.mjs +219 -0
- package/cli/banner.mjs +1 -1
- package/cli/bin.mjs +4 -4
- package/cli/bootstrap.mjs +1 -1
- package/cli/copy.mjs +22 -16
- package/cli/doctor.mjs +4 -4
- package/cli/doctor.test.mjs +2 -2
- package/cli/init.mjs +2 -2
- package/cli/install.mjs +21 -16
- package/cli/memory.mjs +952 -37
- package/cli/utils.mjs +6 -3
- package/config/AGENTS.md +7 -7
- package/config/agents/_shared/AGENT_BASELINE.md +59 -61
- package/config/opencode.json +13 -38
- package/config/skills/memory-protocol/SKILL.md +105 -0
- package/config/skills/obsidian/SKILL.md +58 -1
- package/install.sh +11 -1
- package/package.json +2 -2
- package/plugins/bizar/index.ts +7 -0
- package/plugins/bizar/src/commands.ts +42 -1
- package/plugins/bizar/src/tools/open-kb.ts +191 -0
- package/plugins/bizar/tests/commands.test.ts +36 -0
|
@@ -36,6 +36,19 @@ import type {
|
|
|
36
36
|
Snapshot,
|
|
37
37
|
} from '../lib/types';
|
|
38
38
|
|
|
39
|
+
type LightragStatus = {
|
|
40
|
+
running: boolean;
|
|
41
|
+
pid: number | null;
|
|
42
|
+
host: string;
|
|
43
|
+
port: number;
|
|
44
|
+
llmBinding: string;
|
|
45
|
+
embeddingBinding: string;
|
|
46
|
+
llmModel: string;
|
|
47
|
+
embeddingModel: string;
|
|
48
|
+
lastError: string | null;
|
|
49
|
+
logTail: string[];
|
|
50
|
+
};
|
|
51
|
+
|
|
39
52
|
type Props = {
|
|
40
53
|
snapshot: Snapshot;
|
|
41
54
|
settings: Settings;
|
|
@@ -44,13 +57,14 @@ type Props = {
|
|
|
44
57
|
refreshSnapshot: () => Promise<void>;
|
|
45
58
|
};
|
|
46
59
|
|
|
47
|
-
type NavId = 'opencode' | 'providers' | 'mcps' | 'diagnostics' | 'export';
|
|
60
|
+
type NavId = 'opencode' | 'providers' | 'mcps' | 'diagnostics' | 'memory' | 'export';
|
|
48
61
|
|
|
49
62
|
const NAV_ITEMS: { id: NavId; label: string; icon: typeof Settings2; desc: string }[] = [
|
|
50
63
|
{ id: 'opencode', label: 'OpenCode config', icon: FileCode2, desc: 'Edit opencode.json directly' },
|
|
51
64
|
{ id: 'providers', label: 'Providers', icon: ServerIcon, desc: 'AI providers + API keys' },
|
|
52
65
|
{ id: 'mcps', label: 'MCPs', icon: Plug, desc: 'Model Context Protocol servers' },
|
|
53
66
|
{ id: 'diagnostics', label: 'Diagnostics', icon: Stethoscope, desc: 'Service health + counts' },
|
|
67
|
+
{ id: 'memory', label: 'Memory & LightRAG', icon: Database, desc: 'Configure Bizar Memory Service and the LightRAG embedding server.' },
|
|
54
68
|
{ id: 'export', label: 'Export / Import', icon: Download, desc: 'Download diagnostics bundle' },
|
|
55
69
|
];
|
|
56
70
|
|
|
@@ -77,6 +91,10 @@ export function Config({ snapshot, refreshSnapshot }: Props) {
|
|
|
77
91
|
// Diagnostics state
|
|
78
92
|
const [diagnostics, setDiagnostics] = useState<Diagnostics | null>(null);
|
|
79
93
|
|
|
94
|
+
// Memory & LightRAG state
|
|
95
|
+
const [lightragStatus, setLightragStatus] = useState<LightragStatus | null>(null);
|
|
96
|
+
const [lightragLog, setLightragLog] = useState<string[]>([]);
|
|
97
|
+
|
|
80
98
|
// Providers + MCPs state — re-fetch when nav switches so we always have fresh data.
|
|
81
99
|
const [providers, setProviders] = useState<Provider[]>(snapshot.providers || []);
|
|
82
100
|
const [mcps, setMcps] = useState<McpServer[]>(snapshot.mcps || []);
|
|
@@ -125,6 +143,7 @@ export function Config({ snapshot, refreshSnapshot }: Props) {
|
|
|
125
143
|
if (activeNav === 'providers' && providers.length === 0) reloadProviders();
|
|
126
144
|
if (activeNav === 'mcps' && mcps.length === 0) reloadMcps();
|
|
127
145
|
if (activeNav === 'diagnostics' && !diagnostics) loadDiagnostics();
|
|
146
|
+
if (activeNav === 'memory') loadLightragStatus();
|
|
128
147
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
129
148
|
}, [activeNav]);
|
|
130
149
|
|
|
@@ -181,6 +200,24 @@ export function Config({ snapshot, refreshSnapshot }: Props) {
|
|
|
181
200
|
}
|
|
182
201
|
};
|
|
183
202
|
|
|
203
|
+
const loadLightragStatus = async () => {
|
|
204
|
+
try {
|
|
205
|
+
const d = await api.get<LightragStatus>('/memory/lightrag/status');
|
|
206
|
+
setLightragStatus(d);
|
|
207
|
+
} catch (err) {
|
|
208
|
+
toast.error(`LightRAG status failed: ${(err as Error).message}`);
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
const loadLightragLog = async () => {
|
|
213
|
+
try {
|
|
214
|
+
const d = await api.get<{ lines: string[] }>('/memory/lightrag/log');
|
|
215
|
+
setLightragLog(d.lines || []);
|
|
216
|
+
} catch {
|
|
217
|
+
setLightragLog([]);
|
|
218
|
+
}
|
|
219
|
+
};
|
|
220
|
+
|
|
184
221
|
const onDownloadDiagnostics = () => {
|
|
185
222
|
const bundle = {
|
|
186
223
|
generatedAt: new Date().toISOString(),
|
|
@@ -263,6 +300,15 @@ export function Config({ snapshot, refreshSnapshot }: Props) {
|
|
|
263
300
|
<DiagnosticsPanel diagnostics={diagnostics} loading={!diagnostics} onReload={loadDiagnostics} />
|
|
264
301
|
)}
|
|
265
302
|
|
|
303
|
+
{activeNav === 'memory' && (
|
|
304
|
+
<MemoryLightragPanel
|
|
305
|
+
status={lightragStatus}
|
|
306
|
+
onReload={loadLightragStatus}
|
|
307
|
+
logLines={lightragLog}
|
|
308
|
+
onReloadLog={loadLightragLog}
|
|
309
|
+
/>
|
|
310
|
+
)}
|
|
311
|
+
|
|
266
312
|
{activeNav === 'export' && (
|
|
267
313
|
<ExportPanel onDownload={onDownloadDiagnostics} />
|
|
268
314
|
)}
|
|
@@ -1468,6 +1514,420 @@ function ExportPanel({ onDownload }: { onDownload: () => void }) {
|
|
|
1468
1514
|
);
|
|
1469
1515
|
}
|
|
1470
1516
|
|
|
1517
|
+
/* ──────────────────────────────────────────────────────────────
|
|
1518
|
+
Memory & LightRAG Panel
|
|
1519
|
+
────────────────────────────────────────────────────────────── */
|
|
1520
|
+
|
|
1521
|
+
type LightragDraft = {
|
|
1522
|
+
enabled: boolean;
|
|
1523
|
+
host: string;
|
|
1524
|
+
port: number | '';
|
|
1525
|
+
workingDir: string;
|
|
1526
|
+
llmBinding: string;
|
|
1527
|
+
embeddingBinding: string;
|
|
1528
|
+
llmBindingHost: string;
|
|
1529
|
+
embeddingBindingHost: string;
|
|
1530
|
+
llmModel: string;
|
|
1531
|
+
embeddingModel: string;
|
|
1532
|
+
apiKeySource: 'env' | 'file';
|
|
1533
|
+
apiKey: string;
|
|
1534
|
+
};
|
|
1535
|
+
|
|
1536
|
+
const LLM_BINDING_OPTIONS = ['ollama', 'openai', 'lollms', 'azure_openai', 'bedrock', 'gemini'];
|
|
1537
|
+
const EMBEDDING_BINDING_OPTIONS = ['ollama', 'openai', 'azure_openai', 'bedrock', 'jina', 'gemini', 'voyageai'];
|
|
1538
|
+
|
|
1539
|
+
function MemoryLightragPanel({
|
|
1540
|
+
status,
|
|
1541
|
+
onReload,
|
|
1542
|
+
logLines,
|
|
1543
|
+
onReloadLog,
|
|
1544
|
+
}: {
|
|
1545
|
+
status: LightragStatus | null;
|
|
1546
|
+
onReload: () => void;
|
|
1547
|
+
logLines: string[];
|
|
1548
|
+
onReloadLog: () => void;
|
|
1549
|
+
}) {
|
|
1550
|
+
const toast = useToast();
|
|
1551
|
+
const [saving, setSaving] = useState(false);
|
|
1552
|
+
const [starting, setStarting] = useState(false);
|
|
1553
|
+
const [stopping, setStopping] = useState(false);
|
|
1554
|
+
const [loadingLog, setLoadingLog] = useState(false);
|
|
1555
|
+
const [showLog, setShowLog] = useState(false);
|
|
1556
|
+
|
|
1557
|
+
// Draft form state — initialise from status once loaded
|
|
1558
|
+
const [draft, setDraft] = useState<LightragDraft>({
|
|
1559
|
+
enabled: true,
|
|
1560
|
+
host: '127.0.0.1',
|
|
1561
|
+
port: 9621,
|
|
1562
|
+
workingDir: '',
|
|
1563
|
+
llmBinding: 'ollama',
|
|
1564
|
+
embeddingBinding: 'ollama',
|
|
1565
|
+
llmBindingHost: '',
|
|
1566
|
+
embeddingBindingHost: '',
|
|
1567
|
+
llmModel: 'minimax/MiniMax-M3',
|
|
1568
|
+
embeddingModel: 'text-embedding-3-small',
|
|
1569
|
+
apiKeySource: 'env',
|
|
1570
|
+
apiKey: '',
|
|
1571
|
+
});
|
|
1572
|
+
const [dirty, setDirty] = useState(false);
|
|
1573
|
+
|
|
1574
|
+
// Sync draft from status when status loads
|
|
1575
|
+
useEffect(() => {
|
|
1576
|
+
if (!status) return;
|
|
1577
|
+
setDraft((d) => ({
|
|
1578
|
+
...d,
|
|
1579
|
+
enabled: true,
|
|
1580
|
+
host: status.host,
|
|
1581
|
+
port: status.port,
|
|
1582
|
+
llmBinding: status.llmBinding,
|
|
1583
|
+
embeddingBinding: status.embeddingBinding,
|
|
1584
|
+
llmBindingHost: status.llmBindingHost || '',
|
|
1585
|
+
embeddingBindingHost: status.embeddingBindingHost || '',
|
|
1586
|
+
llmModel: status.llmModel,
|
|
1587
|
+
embeddingModel: status.embeddingModel,
|
|
1588
|
+
apiKeySource: 'env',
|
|
1589
|
+
apiKey: '',
|
|
1590
|
+
}));
|
|
1591
|
+
setDirty(false);
|
|
1592
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
1593
|
+
}, [status?.running]);
|
|
1594
|
+
|
|
1595
|
+
const set = <K extends keyof LightragDraft>(key: K, value: LightragDraft[K]) => {
|
|
1596
|
+
setDraft((d) => ({ ...d, [key]: value }));
|
|
1597
|
+
setDirty(true);
|
|
1598
|
+
};
|
|
1599
|
+
|
|
1600
|
+
const handleSave = async () => {
|
|
1601
|
+
if (saving) return;
|
|
1602
|
+
setSaving(true);
|
|
1603
|
+
try {
|
|
1604
|
+
// Build patch payload
|
|
1605
|
+
const patch: Record<string, unknown> = {
|
|
1606
|
+
enabled: draft.enabled,
|
|
1607
|
+
host: draft.host,
|
|
1608
|
+
port: draft.port === '' ? 9621 : Number(draft.port),
|
|
1609
|
+
workingDir: draft.workingDir,
|
|
1610
|
+
llmBinding: draft.llmBinding,
|
|
1611
|
+
embeddingBinding: draft.embeddingBinding,
|
|
1612
|
+
llmBindingHost: draft.llmBindingHost || null,
|
|
1613
|
+
embeddingBindingHost: draft.embeddingBindingHost || null,
|
|
1614
|
+
llmModel: draft.llmModel,
|
|
1615
|
+
embeddingModel: draft.embeddingModel,
|
|
1616
|
+
apiKeySource: draft.apiKeySource,
|
|
1617
|
+
};
|
|
1618
|
+
if (draft.apiKeySource === 'file' && draft.apiKey !== '') {
|
|
1619
|
+
patch.apiKey = draft.apiKey;
|
|
1620
|
+
}
|
|
1621
|
+
if (draft.apiKeySource === 'env') {
|
|
1622
|
+
patch.apiKey = '<empty>';
|
|
1623
|
+
}
|
|
1624
|
+
|
|
1625
|
+
await api.post('/memory/config', { patch: true, lightrag: patch });
|
|
1626
|
+
toast.success('LightRAG config saved.');
|
|
1627
|
+
setDirty(false);
|
|
1628
|
+
onReload();
|
|
1629
|
+
} catch (err) {
|
|
1630
|
+
toast.error(`Save failed: ${(err as Error).message}`);
|
|
1631
|
+
} finally {
|
|
1632
|
+
setSaving(false);
|
|
1633
|
+
}
|
|
1634
|
+
};
|
|
1635
|
+
|
|
1636
|
+
const handleStart = async () => {
|
|
1637
|
+
if (starting) return;
|
|
1638
|
+
setStarting(true);
|
|
1639
|
+
try {
|
|
1640
|
+
const r = await api.post<{ ok: boolean; error?: string; pid?: number }>('/memory/lightrag/start');
|
|
1641
|
+
if (r.ok) {
|
|
1642
|
+
toast.success(`LightRAG started (pid ${r.pid}).`);
|
|
1643
|
+
} else {
|
|
1644
|
+
toast.error(`Start failed: ${r.error}`);
|
|
1645
|
+
}
|
|
1646
|
+
onReload();
|
|
1647
|
+
} catch (err) {
|
|
1648
|
+
toast.error(`Start failed: ${(err as Error).message}`);
|
|
1649
|
+
} finally {
|
|
1650
|
+
setStarting(false);
|
|
1651
|
+
}
|
|
1652
|
+
};
|
|
1653
|
+
|
|
1654
|
+
const handleStop = async () => {
|
|
1655
|
+
if (stopping) return;
|
|
1656
|
+
setStopping(true);
|
|
1657
|
+
try {
|
|
1658
|
+
await api.post('/memory/lightrag/stop');
|
|
1659
|
+
toast.success('LightRAG stopped.');
|
|
1660
|
+
onReload();
|
|
1661
|
+
} catch (err) {
|
|
1662
|
+
toast.error(`Stop failed: ${(err as Error).message}`);
|
|
1663
|
+
} finally {
|
|
1664
|
+
setStopping(false);
|
|
1665
|
+
}
|
|
1666
|
+
};
|
|
1667
|
+
|
|
1668
|
+
const handleRestart = async () => {
|
|
1669
|
+
await handleStop();
|
|
1670
|
+
await handleStart();
|
|
1671
|
+
};
|
|
1672
|
+
|
|
1673
|
+
const handleShowLog = async () => {
|
|
1674
|
+
if (!showLog) {
|
|
1675
|
+
setLoadingLog(true);
|
|
1676
|
+
await onReloadLog();
|
|
1677
|
+
setLoadingLog(false);
|
|
1678
|
+
}
|
|
1679
|
+
setShowLog((v) => !v);
|
|
1680
|
+
};
|
|
1681
|
+
|
|
1682
|
+
const running = status?.running ?? false;
|
|
1683
|
+
|
|
1684
|
+
return (
|
|
1685
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
|
|
1686
|
+
{/* Status card */}
|
|
1687
|
+
<Card>
|
|
1688
|
+
<CardTitle>
|
|
1689
|
+
<Database size={14} /> LightRAG Server
|
|
1690
|
+
</CardTitle>
|
|
1691
|
+
<CardMeta>
|
|
1692
|
+
In-process embedding server for semantic memory search.{' '}
|
|
1693
|
+
<button type="button" className="link-btn" onClick={onReload}>Refresh</button>
|
|
1694
|
+
</CardMeta>
|
|
1695
|
+
<div style={{ display: 'flex', gap: 8, marginTop: 8, flexWrap: 'wrap', alignItems: 'center' }}>
|
|
1696
|
+
{running ? (
|
|
1697
|
+
<span className="tag tag-success">
|
|
1698
|
+
running {status?.pid ? `(pid ${status.pid})` : ''}
|
|
1699
|
+
</span>
|
|
1700
|
+
) : (
|
|
1701
|
+
<span className="tag tag-neutral">stopped</span>
|
|
1702
|
+
)}
|
|
1703
|
+
{status && (
|
|
1704
|
+
<span className="mono text-sm muted">
|
|
1705
|
+
{status.host}:{status.port} · {status.llmBinding} · {status.embeddingModel}
|
|
1706
|
+
</span>
|
|
1707
|
+
)}
|
|
1708
|
+
{status?.lastError && (
|
|
1709
|
+
<span className="text-error text-sm">⚠ {status.lastError}</span>
|
|
1710
|
+
)}
|
|
1711
|
+
</div>
|
|
1712
|
+
<div style={{ display: 'flex', gap: 8, marginTop: 12, flexWrap: 'wrap' }}>
|
|
1713
|
+
{!running ? (
|
|
1714
|
+
<Button variant="primary" size="sm" onClick={handleStart} disabled={starting}>
|
|
1715
|
+
{starting ? <Spinner size="sm" /> : <RefreshCw size={12} />}
|
|
1716
|
+
{starting ? 'Starting…' : 'Start'}
|
|
1717
|
+
</Button>
|
|
1718
|
+
) : (
|
|
1719
|
+
<>
|
|
1720
|
+
<Button variant="secondary" size="sm" onClick={handleRestart} disabled={stopping || starting}>
|
|
1721
|
+
<RefreshCw size={12} /> Restart
|
|
1722
|
+
</Button>
|
|
1723
|
+
<Button variant="ghost" size="sm" onClick={handleStop} disabled={stopping}>
|
|
1724
|
+
{stopping ? <Spinner size="sm" /> : null}
|
|
1725
|
+
{stopping ? 'Stopping…' : 'Stop'}
|
|
1726
|
+
</Button>
|
|
1727
|
+
</>
|
|
1728
|
+
)}
|
|
1729
|
+
<Button variant="ghost" size="sm" onClick={handleShowLog}>
|
|
1730
|
+
{showLog ? 'Hide' : 'Show'} log
|
|
1731
|
+
</Button>
|
|
1732
|
+
</div>
|
|
1733
|
+
{showLog && (
|
|
1734
|
+
<div style={{ marginTop: 12 }}>
|
|
1735
|
+
<div className="field-help" style={{ marginBottom: 6 }}>
|
|
1736
|
+
Last {logLines.length} line(s)
|
|
1737
|
+
<button type="button" className="link-btn" style={{ marginLeft: 8 }} onClick={onReloadLog}>
|
|
1738
|
+
Refresh
|
|
1739
|
+
</button>
|
|
1740
|
+
</div>
|
|
1741
|
+
{loadingLog ? (
|
|
1742
|
+
<p className="muted text-sm">Loading…</p>
|
|
1743
|
+
) : logLines.length === 0 ? (
|
|
1744
|
+
<p className="muted text-sm">No log output yet.</p>
|
|
1745
|
+
) : (
|
|
1746
|
+
<pre className="log-pre">{logLines.join('\n')}</pre>
|
|
1747
|
+
)}
|
|
1748
|
+
</div>
|
|
1749
|
+
)}
|
|
1750
|
+
</Card>
|
|
1751
|
+
|
|
1752
|
+
{/* Binding card */}
|
|
1753
|
+
<Card>
|
|
1754
|
+
<CardTitle>
|
|
1755
|
+
<ShieldCheck size={14} /> Bindings
|
|
1756
|
+
</CardTitle>
|
|
1757
|
+
<CardMeta>Choose the LLM and embedding provider bindings.</CardMeta>
|
|
1758
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: 12, marginTop: 12 }}>
|
|
1759
|
+
<div className="field-row">
|
|
1760
|
+
<label className="field-label">LLM Binding</label>
|
|
1761
|
+
<select
|
|
1762
|
+
className="input"
|
|
1763
|
+
value={draft.llmBinding}
|
|
1764
|
+
onChange={(e) => set('llmBinding', e.target.value)}
|
|
1765
|
+
>
|
|
1766
|
+
{LLM_BINDING_OPTIONS.map((b) => (
|
|
1767
|
+
<option key={b} value={b}>{b}</option>
|
|
1768
|
+
))}
|
|
1769
|
+
</select>
|
|
1770
|
+
</div>
|
|
1771
|
+
<div className="field-row">
|
|
1772
|
+
<label className="field-label">Embedding Binding</label>
|
|
1773
|
+
<select
|
|
1774
|
+
className="input"
|
|
1775
|
+
value={draft.embeddingBinding}
|
|
1776
|
+
onChange={(e) => set('embeddingBinding', e.target.value)}
|
|
1777
|
+
>
|
|
1778
|
+
{EMBEDDING_BINDING_OPTIONS.map((b) => (
|
|
1779
|
+
<option key={b} value={b}>{b}</option>
|
|
1780
|
+
))}
|
|
1781
|
+
</select>
|
|
1782
|
+
</div>
|
|
1783
|
+
{draft.llmBinding !== 'ollama' && (
|
|
1784
|
+
<div className="field-row">
|
|
1785
|
+
<label className="field-label">LLM Binding Host</label>
|
|
1786
|
+
<input
|
|
1787
|
+
className="input"
|
|
1788
|
+
type="text"
|
|
1789
|
+
value={draft.llmBindingHost}
|
|
1790
|
+
onChange={(e) => set('llmBindingHost', e.target.value)}
|
|
1791
|
+
placeholder="https://api.minimax.chat/v1"
|
|
1792
|
+
/>
|
|
1793
|
+
</div>
|
|
1794
|
+
)}
|
|
1795
|
+
{draft.embeddingBinding !== 'ollama' && (
|
|
1796
|
+
<div className="field-row">
|
|
1797
|
+
<label className="field-label">Embedding Binding Host</label>
|
|
1798
|
+
<input
|
|
1799
|
+
className="input"
|
|
1800
|
+
type="text"
|
|
1801
|
+
value={draft.embeddingBindingHost}
|
|
1802
|
+
onChange={(e) => set('embeddingBindingHost', e.target.value)}
|
|
1803
|
+
placeholder="https://api.minimax.chat/v1"
|
|
1804
|
+
/>
|
|
1805
|
+
</div>
|
|
1806
|
+
)}
|
|
1807
|
+
</div>
|
|
1808
|
+
</Card>
|
|
1809
|
+
|
|
1810
|
+
{/* Models card */}
|
|
1811
|
+
<Card>
|
|
1812
|
+
<CardTitle>
|
|
1813
|
+
<ServerIcon size={14} /> Models
|
|
1814
|
+
</CardTitle>
|
|
1815
|
+
<CardMeta>Model IDs for the LLM and embedding engine.</CardMeta>
|
|
1816
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: 12, marginTop: 12 }}>
|
|
1817
|
+
<div className="field-row">
|
|
1818
|
+
<label className="field-label">LLM Model</label>
|
|
1819
|
+
<input
|
|
1820
|
+
className="input"
|
|
1821
|
+
type="text"
|
|
1822
|
+
value={draft.llmModel}
|
|
1823
|
+
onChange={(e) => set('llmModel', e.target.value)}
|
|
1824
|
+
placeholder="minimax/MiniMax-M3"
|
|
1825
|
+
/>
|
|
1826
|
+
</div>
|
|
1827
|
+
<div className="field-row">
|
|
1828
|
+
<label className="field-label">Embedding Model</label>
|
|
1829
|
+
<input
|
|
1830
|
+
className="input"
|
|
1831
|
+
type="text"
|
|
1832
|
+
value={draft.embeddingModel}
|
|
1833
|
+
onChange={(e) => set('embeddingModel', e.target.value)}
|
|
1834
|
+
placeholder="text-embedding-3-small"
|
|
1835
|
+
/>
|
|
1836
|
+
</div>
|
|
1837
|
+
</div>
|
|
1838
|
+
</Card>
|
|
1839
|
+
|
|
1840
|
+
{/* Connection card */}
|
|
1841
|
+
<Card>
|
|
1842
|
+
<CardTitle>
|
|
1843
|
+
<Plug size={14} /> Connection
|
|
1844
|
+
</CardTitle>
|
|
1845
|
+
<CardMeta>Host, port, and working directory for the LightRAG server.</CardMeta>
|
|
1846
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: 12, marginTop: 12 }}>
|
|
1847
|
+
<div className="field-row">
|
|
1848
|
+
<label className="field-label">Host</label>
|
|
1849
|
+
<input
|
|
1850
|
+
className="input"
|
|
1851
|
+
type="text"
|
|
1852
|
+
value={draft.host}
|
|
1853
|
+
onChange={(e) => set('host', e.target.value)}
|
|
1854
|
+
placeholder="127.0.0.1"
|
|
1855
|
+
/>
|
|
1856
|
+
</div>
|
|
1857
|
+
<div className="field-row">
|
|
1858
|
+
<label className="field-label">Port</label>
|
|
1859
|
+
<input
|
|
1860
|
+
className="input"
|
|
1861
|
+
type="number"
|
|
1862
|
+
min={1}
|
|
1863
|
+
max={65535}
|
|
1864
|
+
value={draft.port}
|
|
1865
|
+
onChange={(e) => set('port', e.target.value === '' ? '' : Number(e.target.value))}
|
|
1866
|
+
placeholder="9621"
|
|
1867
|
+
/>
|
|
1868
|
+
</div>
|
|
1869
|
+
<div className="field-row">
|
|
1870
|
+
<label className="field-label">Working Dir</label>
|
|
1871
|
+
<input
|
|
1872
|
+
className="input"
|
|
1873
|
+
type="text"
|
|
1874
|
+
value={draft.workingDir}
|
|
1875
|
+
onChange={(e) => set('workingDir', e.target.value)}
|
|
1876
|
+
placeholder=".bizar/lightrag (default)"
|
|
1877
|
+
/>
|
|
1878
|
+
</div>
|
|
1879
|
+
</div>
|
|
1880
|
+
</Card>
|
|
1881
|
+
|
|
1882
|
+
{/* Credentials card */}
|
|
1883
|
+
<Card>
|
|
1884
|
+
<CardTitle>
|
|
1885
|
+
<ShieldCheck size={14} /> Credentials
|
|
1886
|
+
</CardTitle>
|
|
1887
|
+
<CardMeta>API key source — set in your shell env, or stored in .bizar/memory.json.</CardMeta>
|
|
1888
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: 12, marginTop: 12 }}>
|
|
1889
|
+
<div className="field-row">
|
|
1890
|
+
<label className="field-label">Source</label>
|
|
1891
|
+
<select
|
|
1892
|
+
className="input"
|
|
1893
|
+
value={draft.apiKeySource}
|
|
1894
|
+
onChange={(e) => set('apiKeySource', e.target.value as 'env' | 'file')}
|
|
1895
|
+
>
|
|
1896
|
+
<option value="env">Environment variable (recommended)</option>
|
|
1897
|
+
<option value="file">Stored in .bizar/memory.json</option>
|
|
1898
|
+
</select>
|
|
1899
|
+
</div>
|
|
1900
|
+
{draft.apiKeySource === 'env' ? (
|
|
1901
|
+
<p className="muted text-sm" style={{ padding: '8px 0' }}>
|
|
1902
|
+
Set <code>OPENAI_API_KEY</code> (or provider-specific env var) in your shell before
|
|
1903
|
+
running reindex or queries.
|
|
1904
|
+
</p>
|
|
1905
|
+
) : (
|
|
1906
|
+
<div className="field-row">
|
|
1907
|
+
<label className="field-label">API Key</label>
|
|
1908
|
+
<input
|
|
1909
|
+
className="input"
|
|
1910
|
+
type="password"
|
|
1911
|
+
value={draft.apiKey}
|
|
1912
|
+
onChange={(e) => set('apiKey', e.target.value)}
|
|
1913
|
+
placeholder={status?.running ? '(unchanged — leave blank)' : 'sk-…'}
|
|
1914
|
+
autoComplete="off"
|
|
1915
|
+
/>
|
|
1916
|
+
</div>
|
|
1917
|
+
)}
|
|
1918
|
+
</div>
|
|
1919
|
+
</Card>
|
|
1920
|
+
|
|
1921
|
+
<div className="view-actions">
|
|
1922
|
+
<Button variant="primary" disabled={!dirty || saving} onClick={handleSave}>
|
|
1923
|
+
{saving ? <Spinner size="sm" /> : <Save size={14} />}
|
|
1924
|
+
{saving ? 'Saving…' : 'Save config'}
|
|
1925
|
+
</Button>
|
|
1926
|
+
</div>
|
|
1927
|
+
</div>
|
|
1928
|
+
);
|
|
1929
|
+
}
|
|
1930
|
+
|
|
1471
1931
|
// ─── AutoDetectBanner (v3.16.0) ────────────────────────────────────
|
|
1472
1932
|
// Scans env vars + opencode.json for known provider API keys
|
|
1473
1933
|
// (Anthropic, OpenAI, Google, Mistral, Groq, Cohere, OpenRouter,
|