ltcai 4.4.0 → 4.6.0
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 +77 -33
- package/docs/CHANGELOG.md +128 -0
- package/docs/V4_5_0_GEMMA_RUNTIME_COMPATIBILITY_REPORT.md +49 -0
- package/docs/V4_5_0_GRAPH_UX_REPORT.md +34 -0
- package/docs/V4_5_0_MODEL_RUNTIME_UX_REPORT.md +40 -0
- package/docs/V4_5_0_ONBOARDING_REPORT.md +31 -0
- package/docs/V4_5_0_PRODUCT_EXPERIENCE_RECOVERY_REPORT.md +49 -0
- package/docs/V4_5_0_VALIDATION_REPORT.md +60 -0
- package/docs/V4_5_1_GRAPH_EXPERIENCE_REPORT.md +33 -0
- package/docs/V4_5_1_MODEL_EXPERIENCE_REPORT.md +37 -0
- package/docs/V4_5_1_NAVIGATION_REPORT.md +37 -0
- package/docs/V4_5_1_ONBOARDING_REPORT.md +29 -0
- package/docs/V4_5_1_PRODUCT_REIMAGINING_REPORT.md +61 -0
- package/docs/V4_5_1_RC_ARTIFACTS.md +44 -0
- package/docs/V4_5_1_UX_REPORT.md +45 -0
- package/docs/V4_5_1_VALIDATION_REPORT.md +54 -0
- package/docs/V4_5_1_VISUAL_DESIGN_REPORT.md +30 -0
- package/docs/V4_6_0_LIVING_BRAIN_EXPERIENCE_REPORT.md +58 -0
- package/docs/V4_DIGITAL_BRAIN_RECOVERY.md +18 -17
- package/docs/architecture.md +8 -4
- package/frontend/index.html +2 -2
- package/frontend/src/App.tsx +120 -98
- package/frontend/src/api/client.ts +84 -1
- package/frontend/src/components/BrainConversation.tsx +301 -0
- package/frontend/src/components/FirstRunGuide.tsx +99 -0
- package/frontend/src/components/LivingBrain.tsx +121 -0
- package/frontend/src/components/ProductFlow.tsx +596 -0
- package/frontend/src/components/primitives.tsx +131 -25
- package/frontend/src/components/ui/badge.tsx +2 -2
- package/frontend/src/components/ui/button.tsx +7 -7
- package/frontend/src/components/ui/card.tsx +5 -5
- package/frontend/src/components/ui/input.tsx +1 -1
- package/frontend/src/components/ui/textarea.tsx +1 -1
- package/frontend/src/pages/Act.tsx +58 -28
- package/frontend/src/pages/Ask.tsx +2 -197
- package/frontend/src/pages/Brain.tsx +108 -71
- package/frontend/src/pages/Capture.tsx +24 -24
- package/frontend/src/pages/Library.tsx +222 -32
- package/frontend/src/pages/System.tsx +56 -34
- package/frontend/src/routes.ts +16 -25
- package/frontend/src/store/appStore.ts +8 -1
- package/frontend/src/styles.css +1663 -36
- package/lattice_brain/__init__.py +1 -1
- package/lattice_brain/runtime/multi_agent.py +1 -1
- package/latticeai/__init__.py +1 -1
- package/latticeai/api/models.py +107 -18
- package/latticeai/core/marketplace.py +1 -1
- package/latticeai/core/model_compat.py +250 -0
- package/latticeai/core/workspace_os.py +1 -1
- package/latticeai/models/router.py +136 -32
- package/latticeai/services/model_catalog.py +2 -2
- package/latticeai/services/model_recommendation.py +8 -1
- package/latticeai/services/model_runtime.py +18 -3
- package/package.json +2 -2
- package/scripts/build_frontend_assets.mjs +12 -1
- package/src-tauri/Cargo.lock +1 -1
- package/src-tauri/Cargo.toml +1 -1
- package/src-tauri/tauri.conf.json +1 -1
- package/static/app/asset-manifest.json +5 -5
- package/static/app/assets/index-By-G-Kay.css +2 -0
- package/static/app/assets/index-CJx6WuQH.js +336 -0
- package/static/app/assets/index-CJx6WuQH.js.map +1 -0
- package/static/app/index.html +4 -4
- package/static/manifest.json +1 -1
- package/static/app/assets/index-CHHal8Zl.css +0 -2
- package/static/app/assets/index-pdzil9ac.js +0 -333
- package/static/app/assets/index-pdzil9ac.js.map +0 -1
|
@@ -2,7 +2,7 @@ import * as React from "react";
|
|
|
2
2
|
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
|
3
3
|
import { Network, ShieldCheck, UserCircle, Users } from "lucide-react";
|
|
4
4
|
import { latticeApi } from "@/api/client";
|
|
5
|
-
import { ActionButton, DataPanel, EmptyState, EntityList, KeyValueList, OperationResult, StatGrid, StructuredView, Tabs } from "@/components/primitives";
|
|
5
|
+
import { ActionButton, DataPanel, EmptyState, EntityList, KeyValueList, ModeGate, OperationResult, StatGrid, StructuredView, Tabs } from "@/components/primitives";
|
|
6
6
|
import { Badge } from "@/components/ui/badge";
|
|
7
7
|
import { Button } from "@/components/ui/button";
|
|
8
8
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
|
@@ -14,27 +14,28 @@ type SystemTab = "account" | "workspaces" | "snapshots" | "activity" | "network"
|
|
|
14
14
|
|
|
15
15
|
const tabs: Array<{ id: SystemTab; label: string }> = [
|
|
16
16
|
{ id: "account", label: "Account" },
|
|
17
|
-
{ id: "workspaces", label: "
|
|
17
|
+
{ id: "workspaces", label: "Spaces" },
|
|
18
18
|
{ id: "snapshots", label: "Snapshots" },
|
|
19
|
-
{ id: "activity", label: "
|
|
20
|
-
{ id: "network", label: "
|
|
21
|
-
{ id: "settings", label: "
|
|
19
|
+
{ id: "activity", label: "History" },
|
|
20
|
+
{ id: "network", label: "Devices" },
|
|
21
|
+
{ id: "settings", label: "Preferences" },
|
|
22
22
|
{ id: "admin", label: "Admin" },
|
|
23
23
|
];
|
|
24
24
|
|
|
25
25
|
export function SystemPage({ initialTab }: { initialTab?: string }) {
|
|
26
|
+
const mode = useAppStore((state) => state.mode);
|
|
26
27
|
const [tab, setTab] = React.useState<SystemTab>((initialTab as SystemTab) || "account");
|
|
27
28
|
React.useEffect(() => {
|
|
28
29
|
if (tabs.some((item) => item.id === initialTab)) setTab(initialTab as SystemTab);
|
|
29
30
|
}, [initialTab]);
|
|
30
31
|
return (
|
|
31
|
-
<div className="space-y-
|
|
32
|
-
<header>
|
|
33
|
-
<div className="
|
|
34
|
-
<h1 className="
|
|
35
|
-
<p className="
|
|
32
|
+
<div className="space-y-5">
|
|
33
|
+
<header className="page-hero">
|
|
34
|
+
<div className="page-kicker"><ShieldCheck className="h-4 w-4" /> Care</div>
|
|
35
|
+
<h1 className="page-title">Keep your brain safe and portable.</h1>
|
|
36
|
+
<p className="page-copy">Manage identity, spaces, backups, trusted devices, and safeguards from one calm place.</p>
|
|
36
37
|
</header>
|
|
37
|
-
<Tabs tabs={tabs} value={tab} onChange={(id) => setTab(id as SystemTab)} />
|
|
38
|
+
<Tabs tabs={mode === "basic" ? tabs.filter((item) => item.id !== "admin") : tabs} value={tab} onChange={(id) => setTab(id as SystemTab)} />
|
|
38
39
|
{tab === "account" ? <AccountPanel /> : null}
|
|
39
40
|
{tab === "workspaces" ? <WorkspacePanel /> : null}
|
|
40
41
|
{tab === "snapshots" ? <SnapshotsPanel /> : null}
|
|
@@ -66,8 +67,8 @@ function AccountPanel() {
|
|
|
66
67
|
</DataPanel>
|
|
67
68
|
<Card>
|
|
68
69
|
<CardHeader>
|
|
69
|
-
<CardTitle className="flex items-center gap-2"><UserCircle className="h-4 w-4" />
|
|
70
|
-
<CardDescription>
|
|
70
|
+
<CardTitle className="flex items-center gap-2"><UserCircle className="h-4 w-4" /> Account</CardTitle>
|
|
71
|
+
<CardDescription>Sign in, create a local account, and keep your profile current.</CardDescription>
|
|
71
72
|
</CardHeader>
|
|
72
73
|
<CardContent className="grid gap-3">
|
|
73
74
|
<Input value={email} onChange={(e) => setEmail(e.target.value)} placeholder="email" />
|
|
@@ -89,7 +90,7 @@ function AccountPanel() {
|
|
|
89
90
|
))}
|
|
90
91
|
</CardContent>
|
|
91
92
|
</Card>
|
|
92
|
-
<DataPanel title="
|
|
93
|
+
<DataPanel title="Sign-in options" result={sso.data} className="xl:col-span-2">
|
|
93
94
|
{(data) => <StructuredView value={data} />}
|
|
94
95
|
</DataPanel>
|
|
95
96
|
</div>
|
|
@@ -110,7 +111,7 @@ function WorkspacePanel() {
|
|
|
110
111
|
const workspaces = asArray<Record<string, unknown>>((registry.data?.data as Record<string, unknown>)?.workspaces);
|
|
111
112
|
return (
|
|
112
113
|
<div className="grid gap-4 xl:grid-cols-[1.1fr_0.9fr]">
|
|
113
|
-
<DataPanel title="
|
|
114
|
+
<DataPanel title="Your workspaces" result={registry.data}>
|
|
114
115
|
{() => (
|
|
115
116
|
<div className="grid gap-2">
|
|
116
117
|
{workspaces.map((workspace) => {
|
|
@@ -137,7 +138,7 @@ function WorkspacePanel() {
|
|
|
137
138
|
<Card>
|
|
138
139
|
<CardHeader>
|
|
139
140
|
<CardTitle className="flex items-center gap-2"><Users className="h-4 w-4" /> Organizations and invitations</CardTitle>
|
|
140
|
-
<CardDescription>
|
|
141
|
+
<CardDescription>Create or join a workspace before adding knowledge.</CardDescription>
|
|
141
142
|
</CardHeader>
|
|
142
143
|
<CardContent className="grid gap-3">
|
|
143
144
|
<Input value={orgName} onChange={(e) => setOrgName(e.target.value)} placeholder="New organization name" />
|
|
@@ -187,7 +188,7 @@ function SnapshotsPanel() {
|
|
|
187
188
|
<Card>
|
|
188
189
|
<CardHeader>
|
|
189
190
|
<CardTitle>Snapshot actions</CardTitle>
|
|
190
|
-
<CardDescription>Create and compare
|
|
191
|
+
<CardDescription>Create checkpoints and compare changes over time.</CardDescription>
|
|
191
192
|
</CardHeader>
|
|
192
193
|
<CardContent className="grid gap-3">
|
|
193
194
|
<Input value={name} onChange={(e) => setName(e.target.value)} placeholder="snapshot name" />
|
|
@@ -240,14 +241,14 @@ function NetworkPanel() {
|
|
|
240
241
|
</DataPanel>
|
|
241
242
|
<Card>
|
|
242
243
|
<CardHeader>
|
|
243
|
-
<CardTitle className="flex items-center gap-2"><Network className="h-4 w-4" /> Pair
|
|
244
|
-
|
|
244
|
+
<CardTitle className="flex items-center gap-2"><Network className="h-4 w-4" /> Pair device</CardTitle>
|
|
245
|
+
<CardDescription>Pair a trusted device for workspace exchange.</CardDescription>
|
|
245
246
|
</CardHeader>
|
|
246
247
|
<CardContent className="grid gap-3">
|
|
247
|
-
<Input value={name} onChange={(e) => setName(e.target.value)} placeholder="
|
|
248
|
-
<Input value={baseUrl} onChange={(e) => setBaseUrl(e.target.value)} placeholder="
|
|
248
|
+
<Input value={name} onChange={(e) => setName(e.target.value)} placeholder="device name" />
|
|
249
|
+
<Input value={baseUrl} onChange={(e) => setBaseUrl(e.target.value)} placeholder="trusted device address" />
|
|
249
250
|
<Input value={publicKey} onChange={(e) => setPublicKey(e.target.value)} placeholder="trusted public key" />
|
|
250
|
-
<Button disabled={!name || !baseUrl || !publicKey || pair.isPending} onClick={() => pair.mutate()}>Pair</Button>
|
|
251
|
+
<Button disabled={!name || !baseUrl || !publicKey || pair.isPending} onClick={() => pair.mutate()}>Pair device</Button>
|
|
251
252
|
{pair.data ? <OperationResult result={pair.data} successLabel="Peer pairing request completed" /> : null}
|
|
252
253
|
</CardContent>
|
|
253
254
|
</Card>
|
|
@@ -344,8 +345,15 @@ function SettingsPanel() {
|
|
|
344
345
|
<DataPanel title="Server health" result={health.data}>
|
|
345
346
|
{(data) => <HealthView data={data as Record<string, unknown>} />}
|
|
346
347
|
</DataPanel>
|
|
347
|
-
<DataPanel title="Host telemetry" result={sys.data}>
|
|
348
|
-
{(data) =>
|
|
348
|
+
<DataPanel title={mode === "basic" ? "Computer readiness" : "Host telemetry"} result={sys.data}>
|
|
349
|
+
{(data) => mode === "basic" ? (
|
|
350
|
+
<StatGrid stats={[
|
|
351
|
+
{ label: "CPU", value: `${String((data as Record<string, unknown>).cpu_pct || "0")}%` },
|
|
352
|
+
{ label: "Memory", value: `${String((data as Record<string, unknown>).ram_pct || "0")}%` },
|
|
353
|
+
{ label: "GPU memory", value: `${String((data as Record<string, unknown>).gpu_mem_pct || "0")}%` },
|
|
354
|
+
{ label: "Local status", value: "ready" },
|
|
355
|
+
]} />
|
|
356
|
+
) : <StructuredView value={data} />}
|
|
349
357
|
</DataPanel>
|
|
350
358
|
<DataPanel title="Brain storage" result={storage.data} className="xl:col-span-3">
|
|
351
359
|
{(data) => <StorageView data={data as Record<string, unknown>} />}
|
|
@@ -356,7 +364,7 @@ function SettingsPanel() {
|
|
|
356
364
|
<Card className="xl:col-span-3">
|
|
357
365
|
<CardHeader>
|
|
358
366
|
<CardTitle>.latticebrain portability</CardTitle>
|
|
359
|
-
<CardDescription>
|
|
367
|
+
<CardDescription>Create an encrypted portable brain file, verify one, or preview a restore before applying it.</CardDescription>
|
|
360
368
|
</CardHeader>
|
|
361
369
|
<CardContent className="grid gap-3">
|
|
362
370
|
<div className="grid gap-2 sm:grid-cols-[1fr_1fr]">
|
|
@@ -386,15 +394,15 @@ function SettingsPanel() {
|
|
|
386
394
|
))}
|
|
387
395
|
</CardContent>
|
|
388
396
|
</Card>
|
|
389
|
-
<Card className="xl:col-span-3">
|
|
397
|
+
{mode !== "basic" ? <Card className="xl:col-span-3">
|
|
390
398
|
<CardHeader>
|
|
391
|
-
<CardTitle>
|
|
392
|
-
<CardDescription>
|
|
399
|
+
<CardTitle>Scale mode</CardTitle>
|
|
400
|
+
<CardDescription>Optional advanced storage. Local SQLite remains the default.</CardDescription>
|
|
393
401
|
</CardHeader>
|
|
394
402
|
<CardContent className="grid gap-3">
|
|
395
403
|
<div className="grid gap-2 sm:grid-cols-[1fr_220px]">
|
|
396
|
-
<Input value={dsn} onChange={(e) => setDsn(e.target.value)} placeholder="
|
|
397
|
-
<Input value={schema} onChange={(e) => setSchema(e.target.value)} placeholder="schema" />
|
|
404
|
+
<Input value={dsn} onChange={(e) => setDsn(e.target.value)} placeholder="Postgres connection string" />
|
|
405
|
+
<Input value={schema} onChange={(e) => setSchema(e.target.value)} placeholder="database schema" />
|
|
398
406
|
</div>
|
|
399
407
|
<div className="flex flex-wrap gap-2">
|
|
400
408
|
<Button variant="outline" onClick={() => docker.mutate(false)} disabled={docker.isPending}>Docker plan</Button>
|
|
@@ -408,7 +416,7 @@ function SettingsPanel() {
|
|
|
408
416
|
{docker.data ? <OperationResult result={docker.data} successLabel="Docker setup request completed" /> : null}
|
|
409
417
|
{migration.data ? <OperationResult result={migration.data} successLabel="Migration plan completed" /> : null}
|
|
410
418
|
</CardContent>
|
|
411
|
-
</Card>
|
|
419
|
+
</Card> : null}
|
|
412
420
|
<DataPanel title="Computer memory" result={comp.data} className="xl:col-span-3">
|
|
413
421
|
{(data) => (
|
|
414
422
|
<div className="space-y-3">
|
|
@@ -441,7 +449,16 @@ function PresenceView({ data }: { data: Record<string, unknown> }) {
|
|
|
441
449
|
}
|
|
442
450
|
|
|
443
451
|
function DeviceIdentityView({ data }: { data: Record<string, unknown> }) {
|
|
452
|
+
const mode = useAppStore((state) => state.mode);
|
|
444
453
|
const publicKey = textValue(data.public_key, "");
|
|
454
|
+
if (mode === "basic") {
|
|
455
|
+
return (
|
|
456
|
+
<div className="space-y-3">
|
|
457
|
+
<StatusCard title="This Mac" status="trusted" detail="This device can participate in local workspace exchange when you pair another trusted device." />
|
|
458
|
+
<Badge variant="muted">{textValue(data.algorithm, "local identity")}</Badge>
|
|
459
|
+
</div>
|
|
460
|
+
);
|
|
461
|
+
}
|
|
445
462
|
return (
|
|
446
463
|
<div className="space-y-3">
|
|
447
464
|
<div className="flex flex-wrap items-center gap-2">
|
|
@@ -458,15 +475,16 @@ function DeviceIdentityView({ data }: { data: Record<string, unknown> }) {
|
|
|
458
475
|
}
|
|
459
476
|
|
|
460
477
|
function HealthView({ data }: { data: Record<string, unknown> }) {
|
|
478
|
+
const mode = useAppStore((state) => state.mode);
|
|
461
479
|
return (
|
|
462
480
|
<div className="space-y-3">
|
|
463
481
|
<StatGrid stats={[
|
|
464
482
|
{ label: "Status", value: data.status || data.ok || "reported" },
|
|
465
483
|
{ label: "Version", value: data.version || "not reported" },
|
|
466
484
|
{ label: "Mode", value: data.mode || data.environment || "local" },
|
|
467
|
-
{ label: "Port", value: data.port || data.backend_port || "configured" },
|
|
485
|
+
...(mode === "basic" ? [] : [{ label: "Port", value: data.port || data.backend_port || "configured" }]),
|
|
468
486
|
]} />
|
|
469
|
-
<StructuredView value={data} />
|
|
487
|
+
{mode === "basic" ? null : <StructuredView value={data} />}
|
|
470
488
|
</div>
|
|
471
489
|
);
|
|
472
490
|
}
|
|
@@ -543,7 +561,7 @@ function HardeningView({ data }: { data: Record<string, unknown> }) {
|
|
|
543
561
|
{ label: "Backups", value: backup.count || backup.available || "reported" },
|
|
544
562
|
]} />
|
|
545
563
|
<div className="grid gap-3 md:grid-cols-2">
|
|
546
|
-
<StatusCard title="Startup" status={startup.network_exposed ? "network exposed" : "local-only"} detail=
|
|
564
|
+
<StatusCard title="Startup" status={startup.network_exposed ? "network exposed" : "local-only"} detail="Lattice starts locally by default and reports when network access is enabled." />
|
|
547
565
|
<StatusCard title="Integrations" status={privacy.local_only_default === false ? "review required" : "opt-in"} detail="External integrations remain disabled until the user explicitly enables them." />
|
|
548
566
|
<StatusCard title="Device identity" status={textValue(identity.algorithm || identity.fingerprint, "reported")} detail={textValue(identity.storage, "Stored locally and used for signed bundle exchange.")} />
|
|
549
567
|
<StatusCard title="Permissions" status={permissions.destructive_restore_requires_confirmation === false ? "review required" : "guarded"} detail="Export, import, and destructive restore permissions are surfaced through admin status." />
|
|
@@ -569,6 +587,7 @@ function SecurityView({ data }: { data: Record<string, unknown> }) {
|
|
|
569
587
|
}
|
|
570
588
|
|
|
571
589
|
function AdminPanel() {
|
|
590
|
+
const mode = useAppStore((state) => state.mode);
|
|
572
591
|
const summary = useQuery({ queryKey: ["adminSummary"], queryFn: latticeApi.adminSummary });
|
|
573
592
|
const users = useQuery({ queryKey: ["adminUsers"], queryFn: latticeApi.adminUsers });
|
|
574
593
|
const audit = useQuery({ queryKey: ["adminAudit"], queryFn: latticeApi.adminAudit });
|
|
@@ -577,6 +596,9 @@ function AdminPanel() {
|
|
|
577
596
|
const hardening = useQuery({ queryKey: ["adminProductHardening"], queryFn: latticeApi.adminProductHardening });
|
|
578
597
|
const security = useQuery({ queryKey: ["adminSecurity"], queryFn: latticeApi.adminSecurity });
|
|
579
598
|
const vpc = useQuery({ queryKey: ["vpcStatus"], queryFn: latticeApi.vpcStatus });
|
|
599
|
+
if (mode !== "admin") {
|
|
600
|
+
return <ModeGate title="Admin controls" detail="Switch to Admin mode to review users, audit events, policies, security posture, and private networking diagnostics." target="admin" />;
|
|
601
|
+
}
|
|
580
602
|
return (
|
|
581
603
|
<div className="grid gap-4 xl:grid-cols-2">
|
|
582
604
|
<DataPanel title="Admin summary" result={summary.data}>{(data) => <KeyValueList data={data as Record<string, unknown>} />}</DataPanel>
|
package/frontend/src/routes.ts
CHANGED
|
@@ -1,34 +1,31 @@
|
|
|
1
1
|
import {
|
|
2
|
-
Activity,
|
|
3
2
|
Brain,
|
|
4
3
|
Database,
|
|
5
4
|
FolderInput,
|
|
6
5
|
Library,
|
|
7
|
-
MessageSquare,
|
|
8
|
-
Network,
|
|
9
6
|
Settings,
|
|
10
|
-
Shield,
|
|
11
7
|
Workflow,
|
|
12
|
-
Zap,
|
|
13
8
|
} from "lucide-react";
|
|
14
9
|
|
|
15
|
-
export type PrimaryRoute = "brain" | "
|
|
10
|
+
export type PrimaryRoute = "brain" | "memory" | "capture" | "act" | "library" | "system";
|
|
16
11
|
|
|
17
12
|
export const primaryRoutes = [
|
|
18
|
-
{ id: "brain", label: "Brain", icon: Brain, description: "
|
|
19
|
-
{ id: "
|
|
20
|
-
{ id: "capture", label: "
|
|
21
|
-
{ id: "act", label: "
|
|
22
|
-
{ id: "library", label: "
|
|
23
|
-
{ id: "system", label: "
|
|
13
|
+
{ id: "brain", label: "Brain", icon: Brain, description: "Talk with your living Brain" },
|
|
14
|
+
{ id: "memory", label: "Memory", icon: Database, description: "Recall what your Brain remembers" },
|
|
15
|
+
{ id: "capture", label: "Files", icon: FolderInput, description: "Bring in files, folders, and pages" },
|
|
16
|
+
{ id: "act", label: "Automations", icon: Workflow, description: "Turn goals into supervised runs" },
|
|
17
|
+
{ id: "library", label: "Models", icon: Library, description: "Choose the local model powering your Brain" },
|
|
18
|
+
{ id: "system", label: "Settings", icon: Settings, description: "Keep your Brain safe and portable" },
|
|
24
19
|
] as const;
|
|
25
20
|
|
|
26
21
|
export const routeAliases: Record<string, { primary: PrimaryRoute; tab?: string }> = {
|
|
27
|
-
home: { primary: "brain", tab: "
|
|
22
|
+
home: { primary: "brain", tab: "conversation" },
|
|
23
|
+
onboarding: { primary: "system", tab: "account" },
|
|
28
24
|
"knowledge-graph": { primary: "brain", tab: "graph" },
|
|
29
|
-
"hybrid-search": { primary: "brain", tab: "
|
|
30
|
-
memory: { primary: "
|
|
31
|
-
|
|
25
|
+
"hybrid-search": { primary: "brain", tab: "knowledge" },
|
|
26
|
+
memory: { primary: "memory", tab: "memory" },
|
|
27
|
+
ask: { primary: "brain", tab: "conversation" },
|
|
28
|
+
chat: { primary: "brain", tab: "conversation" },
|
|
32
29
|
files: { primary: "capture", tab: "files" },
|
|
33
30
|
pipeline: { primary: "capture", tab: "pipeline" },
|
|
34
31
|
"my-computer": { primary: "capture", tab: "local" },
|
|
@@ -58,17 +55,11 @@ export const routeAliases: Record<string, { primary: PrimaryRoute; tab?: string
|
|
|
58
55
|
|
|
59
56
|
export const commandRoutes = [
|
|
60
57
|
{ key: "brain", label: "Brain", icon: Brain },
|
|
61
|
-
{ key: "knowledge-graph", label: "Knowledge Graph", icon: Network },
|
|
62
|
-
{ key: "hybrid-search", label: "Hybrid Search", icon: Zap },
|
|
63
58
|
{ key: "memory", label: "Memory", icon: Database },
|
|
64
|
-
{ key: "
|
|
65
|
-
{ key: "
|
|
66
|
-
{ key: "agents", label: "Agents", icon: Workflow },
|
|
67
|
-
{ key: "workflows", label: "Workflows", icon: Workflow },
|
|
59
|
+
{ key: "files", label: "Files", icon: FolderInput },
|
|
60
|
+
{ key: "workflows", label: "Automations", icon: Workflow },
|
|
68
61
|
{ key: "models", label: "Models", icon: Library },
|
|
69
|
-
{ key: "
|
|
70
|
-
{ key: "activity", label: "Activity", icon: Activity },
|
|
71
|
-
{ key: "admin/security", label: "Security", icon: Shield },
|
|
62
|
+
{ key: "settings", label: "Settings", icon: Settings },
|
|
72
63
|
];
|
|
73
64
|
|
|
74
65
|
export function parseHash() {
|
|
@@ -30,10 +30,17 @@ function readMode(): WorkspaceMode {
|
|
|
30
30
|
return "basic";
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
function readWorkspaceId(): string | null {
|
|
34
|
+
try {
|
|
35
|
+
return localStorage.getItem("lattice.workspace") || null;
|
|
36
|
+
} catch {}
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
|
|
33
40
|
export const useAppStore = create<AppState>((set) => ({
|
|
34
41
|
theme: readTheme(),
|
|
35
42
|
mode: readMode(),
|
|
36
|
-
workspaceId:
|
|
43
|
+
workspaceId: readWorkspaceId(),
|
|
37
44
|
apiBase: null,
|
|
38
45
|
setTheme: (theme) => {
|
|
39
46
|
document.documentElement.dataset.theme = theme;
|