ltcai 4.4.0 → 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/README.md +46 -18
- package/docs/CHANGELOG.md +85 -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_DIGITAL_BRAIN_RECOVERY.md +16 -16
- package/docs/architecture.md +8 -4
- package/frontend/src/App.tsx +152 -91
- package/frontend/src/api/client.ts +83 -1
- package/frontend/src/components/FirstRunGuide.tsx +99 -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 +51 -19
- package/frontend/src/pages/Brain.tsx +60 -42
- 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 +15 -13
- package/frontend/src/store/appStore.ts +8 -1
- package/frontend/src/styles.css +666 -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 +1 -1
- 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-3G8qcrIS.js +336 -0
- package/static/app/assets/index-3G8qcrIS.js.map +1 -0
- package/static/app/assets/index-C0wYZp7k.css +2 -0
- package/static/app/index.html +2 -2
- 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
|
@@ -3,12 +3,13 @@ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
|
|
3
3
|
import cytoscape, { Core, ElementDefinition } from "cytoscape";
|
|
4
4
|
import { BrainCircuit, DatabaseBackup, Filter, Focus, Layers3, LocateFixed, Search, Sparkles } from "lucide-react";
|
|
5
5
|
import { latticeApi } from "@/api/client";
|
|
6
|
-
import { ActionButton, DataPanel, EmptyState, EntityList, LoadingPanel, OperationResult, StatGrid, StructuredView, Tabs } from "@/components/primitives";
|
|
6
|
+
import { ActionButton, DataPanel, EmptyState, EntityList, KeyValueList, LoadingPanel, OperationResult, StatGrid, StructuredView, Tabs } from "@/components/primitives";
|
|
7
7
|
import { Badge } from "@/components/ui/badge";
|
|
8
8
|
import { Button } from "@/components/ui/button";
|
|
9
9
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
|
10
10
|
import { Input } from "@/components/ui/input";
|
|
11
11
|
import { Textarea } from "@/components/ui/textarea";
|
|
12
|
+
import { useAppStore } from "@/store/appStore";
|
|
12
13
|
import { asArray, fmtNumber, pct, shortId, titleize } from "@/lib/utils";
|
|
13
14
|
|
|
14
15
|
type BrainTab = "overview" | "graph" | "search" | "memory" | "provenance" | "portability";
|
|
@@ -60,11 +61,11 @@ type ExplorerModel = ParsedGraph & {
|
|
|
60
61
|
};
|
|
61
62
|
|
|
62
63
|
const tabs: Array<{ id: BrainTab; label: string }> = [
|
|
63
|
-
{ id: "overview", label: "
|
|
64
|
-
{ id: "graph", label: "
|
|
64
|
+
{ id: "overview", label: "Today" },
|
|
65
|
+
{ id: "graph", label: "Map" },
|
|
65
66
|
{ id: "search", label: "Search" },
|
|
66
67
|
{ id: "memory", label: "Memory" },
|
|
67
|
-
{ id: "provenance", label: "
|
|
68
|
+
{ id: "provenance", label: "Sources" },
|
|
68
69
|
{ id: "portability", label: "Portability" },
|
|
69
70
|
];
|
|
70
71
|
|
|
@@ -405,13 +406,14 @@ function CytoscapeGraph({
|
|
|
405
406
|
<div
|
|
406
407
|
ref={hostRef}
|
|
407
408
|
data-testid="brain-cytoscape"
|
|
408
|
-
className="h-[620px] min-h-[32rem] w-full overflow-hidden rounded-
|
|
409
|
+
className="brain-grid h-[620px] min-h-[32rem] w-full overflow-hidden rounded-lg border border-border bg-background/80"
|
|
409
410
|
/>
|
|
410
411
|
);
|
|
411
412
|
}
|
|
412
413
|
|
|
413
414
|
export function BrainPage({ initialTab }: { initialTab?: string }) {
|
|
414
|
-
const
|
|
415
|
+
const mode = useAppStore((state) => state.mode);
|
|
416
|
+
const [tab, setTab] = React.useState<BrainTab>((initialTab as BrainTab) || "overview");
|
|
415
417
|
React.useEffect(() => {
|
|
416
418
|
if (initialTab && tabs.some((item) => item.id === initialTab)) setTab(initialTab as BrainTab);
|
|
417
419
|
}, [initialTab]);
|
|
@@ -423,19 +425,19 @@ export function BrainPage({ initialTab }: { initialTab?: string }) {
|
|
|
423
425
|
const memory = useQuery({ queryKey: ["memoryManager"], queryFn: latticeApi.memoryManager });
|
|
424
426
|
|
|
425
427
|
return (
|
|
426
|
-
<div className="space-y-
|
|
427
|
-
<header className="grid gap-4 xl:grid-cols-[1.4fr_0.6fr]">
|
|
428
|
+
<div className="space-y-5">
|
|
429
|
+
<header className="page-hero grid gap-4 xl:grid-cols-[1.4fr_0.6fr]">
|
|
428
430
|
<div>
|
|
429
|
-
<div className="
|
|
430
|
-
<h1 className="
|
|
431
|
-
<p className="
|
|
432
|
-
|
|
431
|
+
<div className="page-kicker"><BrainCircuit className="h-4 w-4" /> Home</div>
|
|
432
|
+
<h1 className="page-title">Start from the shape of your work.</h1>
|
|
433
|
+
<p className="page-copy">
|
|
434
|
+
Lattice turns what you add into a living memory map, then keeps every answer tied back to its source.
|
|
433
435
|
</p>
|
|
434
436
|
</div>
|
|
435
|
-
<div className="rounded-
|
|
436
|
-
<div className="text-xs uppercase text-muted-foreground">
|
|
437
|
+
<div className="rounded-lg border border-border bg-background/58 p-4">
|
|
438
|
+
<div className="text-xs uppercase text-muted-foreground">Source coverage</div>
|
|
437
439
|
<div className="mt-2 text-3xl font-semibold">{pct((coverage.data?.data as Record<string, unknown>)?.coverage_ratio)}</div>
|
|
438
|
-
<div className="mt-2 text-sm text-muted-foreground">
|
|
440
|
+
<div className="mt-2 text-sm text-muted-foreground">{coverage.data?.ok ? "Sources are linked to graph records." : "Checking source coverage."}</div>
|
|
439
441
|
</div>
|
|
440
442
|
</header>
|
|
441
443
|
<Tabs tabs={tabs} value={tab} onChange={(id) => setTab(id as BrainTab)} />
|
|
@@ -451,7 +453,7 @@ export function BrainPage({ initialTab }: { initialTab?: string }) {
|
|
|
451
453
|
<DataPanel title="Memory tiers" result={memory.data}>
|
|
452
454
|
{(data) => <MemoryStatus data={data as Record<string, unknown>} />}
|
|
453
455
|
</DataPanel>
|
|
454
|
-
<DataPanel title="Recent
|
|
456
|
+
<DataPanel title="Recent sources" result={provenance.data}>
|
|
455
457
|
{(data) => <EntityList items={(data as Record<string, unknown>).items || data} titleKey="source" metaKey="source_type" />}
|
|
456
458
|
</DataPanel>
|
|
457
459
|
</div>
|
|
@@ -459,7 +461,7 @@ export function BrainPage({ initialTab }: { initialTab?: string }) {
|
|
|
459
461
|
|
|
460
462
|
{tab === "graph" ? (
|
|
461
463
|
graph.isLoading ? <LoadingPanel title="Knowledge graph" /> : (
|
|
462
|
-
<DataPanel title="
|
|
464
|
+
<DataPanel title="Brain map" description={mode === "basic" ? "Search, focus, and filter the ideas Lattice has learned from your workspace." : "Explore relationships, sources, and graph structure."} result={graph.data}>
|
|
463
465
|
{(data) => <DigitalBrainExplorer data={data} />}
|
|
464
466
|
</DataPanel>
|
|
465
467
|
)
|
|
@@ -474,6 +476,7 @@ export function BrainPage({ initialTab }: { initialTab?: string }) {
|
|
|
474
476
|
}
|
|
475
477
|
|
|
476
478
|
function GraphStatus({ data }: { data: Record<string, unknown> }) {
|
|
479
|
+
const mode = useAppStore((state) => state.mode);
|
|
477
480
|
const nodeTypes = Object.keys((data.nodes as Record<string, unknown>) || {});
|
|
478
481
|
const edgeTypes = Object.keys((data.edges as Record<string, unknown>) || {});
|
|
479
482
|
return (
|
|
@@ -484,7 +487,11 @@ function GraphStatus({ data }: { data: Record<string, unknown> }) {
|
|
|
484
487
|
{ label: "Node types", value: nodeTypes.length },
|
|
485
488
|
{ label: "Edge types", value: edgeTypes.length },
|
|
486
489
|
]} />
|
|
487
|
-
|
|
490
|
+
{mode === "basic" ? (
|
|
491
|
+
<div className="flex flex-wrap gap-1">
|
|
492
|
+
{[...nodeTypes, ...edgeTypes].slice(0, 10).map((item) => <Badge key={item} variant="muted">{titleize(item)}</Badge>)}
|
|
493
|
+
</div>
|
|
494
|
+
) : <StructuredView value={{ node_types: nodeTypes, edge_types: edgeTypes }} />}
|
|
488
495
|
</div>
|
|
489
496
|
);
|
|
490
497
|
}
|
|
@@ -515,10 +522,11 @@ function MemoryStatus({ data }: { data: Record<string, unknown> }) {
|
|
|
515
522
|
}
|
|
516
523
|
|
|
517
524
|
function DigitalBrainExplorer({ data }: { data: unknown }) {
|
|
525
|
+
const mode = useAppStore((state) => state.mode);
|
|
518
526
|
const parsed = React.useMemo(() => parseGraph(data), [data]);
|
|
519
527
|
const [search, setSearch] = React.useState("");
|
|
520
528
|
const [groupFilter, setGroupFilter] = React.useState("all");
|
|
521
|
-
const [minImportance, setMinImportance] = React.useState(0);
|
|
529
|
+
const [minImportance, setMinImportance] = React.useState(mode === "basic" ? 0.1 : 0);
|
|
522
530
|
const [labelMode, setLabelMode] = React.useState<LabelMode>("important");
|
|
523
531
|
const [collapsedGroups, setCollapsedGroups] = React.useState<Set<string>>(new Set());
|
|
524
532
|
const [selectedId, setSelectedId] = React.useState<string | null>(null);
|
|
@@ -544,11 +552,14 @@ function DigitalBrainExplorer({ data }: { data: unknown }) {
|
|
|
544
552
|
return next;
|
|
545
553
|
});
|
|
546
554
|
};
|
|
555
|
+
React.useEffect(() => {
|
|
556
|
+
if (mode === "basic" && minImportance < 0.1) setMinImportance(0.1);
|
|
557
|
+
}, [mode, minImportance]);
|
|
547
558
|
if (!parsed.nodes.length) {
|
|
548
559
|
return (
|
|
549
560
|
<EmptyState
|
|
550
561
|
title="No graph records yet"
|
|
551
|
-
detail="Capture a document, note, or local folder to create
|
|
562
|
+
detail="Capture a document, note, or local folder to create connected ideas with sources."
|
|
552
563
|
/>
|
|
553
564
|
);
|
|
554
565
|
}
|
|
@@ -557,7 +568,7 @@ function DigitalBrainExplorer({ data }: { data: unknown }) {
|
|
|
557
568
|
<div className="grid gap-3 xl:grid-cols-[1fr_220px_180px_170px]">
|
|
558
569
|
<div className="relative">
|
|
559
570
|
<Search className="pointer-events-none absolute left-3 top-2.5 h-4 w-4 text-muted-foreground" />
|
|
560
|
-
<Input className="pl-9" value={search} onChange={(event) => setSearch(event.target.value)} placeholder="Search graph labels, types, provenance..." />
|
|
571
|
+
<Input className="pl-9" value={search} onChange={(event) => setSearch(event.target.value)} placeholder={mode === "basic" ? "Search ideas, files, people, and notes..." : "Search graph labels, types, provenance..."} />
|
|
561
572
|
</div>
|
|
562
573
|
<select className="h-9 rounded-md border border-border bg-background px-3 text-sm" value={groupFilter} onChange={(event) => setGroupFilter(event.target.value)}>
|
|
563
574
|
<option value="all">All semantic groups</option>
|
|
@@ -574,9 +585,9 @@ function DigitalBrainExplorer({ data }: { data: unknown }) {
|
|
|
574
585
|
<Card>
|
|
575
586
|
<CardHeader className="flex-row items-start justify-between gap-3">
|
|
576
587
|
<div>
|
|
577
|
-
<CardTitle className="flex items-center gap-2"><Layers3 className="h-4 w-4" />
|
|
588
|
+
<CardTitle className="flex items-center gap-2"><Layers3 className="h-4 w-4" /> Knowledge map</CardTitle>
|
|
578
589
|
<CardDescription>
|
|
579
|
-
Showing {fmtNumber(model.visibleNodes.length)}
|
|
590
|
+
Showing {fmtNumber(model.visibleNodes.length)} ideas and {fmtNumber(model.visibleEdges.length)} connections from {fmtNumber(model.totalNodes)} saved items.
|
|
580
591
|
</CardDescription>
|
|
581
592
|
</div>
|
|
582
593
|
<Badge variant={model.hiddenByFilters ? "warning" : "success"}>{model.hiddenByFilters ? `${fmtNumber(model.hiddenByFilters)} filtered` : "all in view"}</Badge>
|
|
@@ -598,7 +609,7 @@ function DigitalBrainExplorer({ data }: { data: unknown }) {
|
|
|
598
609
|
/>
|
|
599
610
|
<Badge variant="muted">{Math.round(minImportance * 100)}%+</Badge>
|
|
600
611
|
{selectedId ? <Button variant="outline" size="sm" onClick={() => setSelectedId(null)}>Clear focus</Button> : null}
|
|
601
|
-
{search.trim() ? <Button variant="outline" size="sm" onClick={() => backendSearch.mutate()} disabled={backendSearch.isPending}>Search
|
|
612
|
+
{search.trim() ? <Button variant="outline" size="sm" onClick={() => backendSearch.mutate()} disabled={backendSearch.isPending}>Search all memories</Button> : null}
|
|
602
613
|
</div>
|
|
603
614
|
<div className="flex flex-wrap gap-2">
|
|
604
615
|
{model.groups.map((group) => (
|
|
@@ -620,7 +631,7 @@ function DigitalBrainExplorer({ data }: { data: unknown }) {
|
|
|
620
631
|
<Card>
|
|
621
632
|
<CardHeader>
|
|
622
633
|
<CardTitle className="flex items-center gap-2"><Focus className="h-4 w-4" /> Focus</CardTitle>
|
|
623
|
-
<CardDescription>Click
|
|
634
|
+
<CardDescription>Click any idea to see why it matters.</CardDescription>
|
|
624
635
|
</CardHeader>
|
|
625
636
|
<CardContent className="space-y-3">
|
|
626
637
|
{selected ? (
|
|
@@ -634,11 +645,18 @@ function DigitalBrainExplorer({ data }: { data: unknown }) {
|
|
|
634
645
|
</div>
|
|
635
646
|
</div>
|
|
636
647
|
{selected.summary ? <p className="text-sm text-muted-foreground">{selected.summary}</p> : null}
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
648
|
+
{mode === "basic" ? (
|
|
649
|
+
<KeyValueList data={{
|
|
650
|
+
connections: selected.degree,
|
|
651
|
+
source: selected.source || "not reported",
|
|
652
|
+
}} />
|
|
653
|
+
) : (
|
|
654
|
+
<StructuredView value={{
|
|
655
|
+
id: selected.id,
|
|
656
|
+
degree: selected.degree,
|
|
657
|
+
source: selected.source || "not reported",
|
|
658
|
+
}} />
|
|
659
|
+
)}
|
|
642
660
|
</>
|
|
643
661
|
) : selectedGroup ? (
|
|
644
662
|
<div className="space-y-2">
|
|
@@ -651,8 +669,8 @@ function DigitalBrainExplorer({ data }: { data: unknown }) {
|
|
|
651
669
|
</Card>
|
|
652
670
|
<Card>
|
|
653
671
|
<CardHeader>
|
|
654
|
-
<CardTitle>Important
|
|
655
|
-
<CardDescription>
|
|
672
|
+
<CardTitle>Important ideas</CardTitle>
|
|
673
|
+
<CardDescription>What Lattice thinks is most connected right now.</CardDescription>
|
|
656
674
|
</CardHeader>
|
|
657
675
|
<CardContent className="space-y-2">
|
|
658
676
|
{model.visibleNodes.slice(0, 8).map((node) => (
|
|
@@ -687,8 +705,8 @@ function HybridSearch() {
|
|
|
687
705
|
return (
|
|
688
706
|
<Card>
|
|
689
707
|
<CardHeader>
|
|
690
|
-
<CardTitle className="flex items-center gap-2"><Search className="h-4 w-4" />
|
|
691
|
-
<CardDescription>
|
|
708
|
+
<CardTitle className="flex items-center gap-2"><Search className="h-4 w-4" /> Brain search</CardTitle>
|
|
709
|
+
<CardDescription>Find ideas across memories, documents, and connections.</CardDescription>
|
|
692
710
|
</CardHeader>
|
|
693
711
|
<CardContent className="space-y-3">
|
|
694
712
|
<div className="flex flex-col gap-2 sm:flex-row">
|
|
@@ -717,7 +735,7 @@ function MemoryPanel() {
|
|
|
717
735
|
<Card>
|
|
718
736
|
<CardHeader>
|
|
719
737
|
<CardTitle className="flex items-center gap-2"><Sparkles className="h-4 w-4" /> Recall</CardTitle>
|
|
720
|
-
<CardDescription>
|
|
738
|
+
<CardDescription>Bring back related memories from your workspace.</CardDescription>
|
|
721
739
|
</CardHeader>
|
|
722
740
|
<CardContent className="space-y-3">
|
|
723
741
|
<Input value={query} onChange={(e) => setQuery(e.target.value)} placeholder="Recall memories about..." />
|
|
@@ -741,7 +759,7 @@ function ProvenancePanel() {
|
|
|
741
759
|
<DataPanel title="Coverage" result={coverage.data}>
|
|
742
760
|
{(data) => <StructuredView value={data} />}
|
|
743
761
|
</DataPanel>
|
|
744
|
-
<DataPanel title="Recent
|
|
762
|
+
<DataPanel title="Recent sources" result={provenance.data}>
|
|
745
763
|
{(data) => <EntityList items={(data as Record<string, unknown>).items || data} titleKey="source" metaKey="source_type" limit={14} />}
|
|
746
764
|
</DataPanel>
|
|
747
765
|
</div>
|
|
@@ -770,22 +788,22 @@ function PortabilityPanel() {
|
|
|
770
788
|
<Card>
|
|
771
789
|
<CardHeader>
|
|
772
790
|
<CardTitle className="flex items-center gap-2"><DatabaseBackup className="h-4 w-4" /> Export, backup, import</CardTitle>
|
|
773
|
-
<CardDescription>
|
|
791
|
+
<CardDescription>Export, back up, or preview an import before changing your brain.</CardDescription>
|
|
774
792
|
</CardHeader>
|
|
775
793
|
<CardContent className="space-y-3">
|
|
776
794
|
<div className="flex flex-wrap gap-2">
|
|
777
|
-
<ActionButton label="Export
|
|
795
|
+
<ActionButton label="Export brain map" action={() => latticeApi.graphExport()} />
|
|
778
796
|
<ActionButton label="Create backup" action={() => latticeApi.graphBackup()} />
|
|
779
797
|
</div>
|
|
780
|
-
<Textarea value={artifact} onChange={(e) => setArtifact(e.target.value)} placeholder="Paste an exported
|
|
798
|
+
<Textarea value={artifact} onChange={(e) => setArtifact(e.target.value)} placeholder="Paste an exported brain map to preview import" />
|
|
781
799
|
<Button
|
|
782
800
|
variant="outline"
|
|
783
801
|
disabled={!artifact.trim() || importMutation.isPending}
|
|
784
802
|
onClick={() => importMutation.mutate()}
|
|
785
803
|
>
|
|
786
|
-
|
|
804
|
+
Preview import
|
|
787
805
|
</Button>
|
|
788
|
-
{importMutation.data ? <OperationResult result={importMutation.data} successLabel="
|
|
806
|
+
{importMutation.data ? <OperationResult result={importMutation.data} successLabel="Import preview completed" /> : null}
|
|
789
807
|
</CardContent>
|
|
790
808
|
</Card>
|
|
791
809
|
</div>
|
|
@@ -798,7 +816,7 @@ function PortabilityStatus({ data }: { data: Record<string, unknown> }) {
|
|
|
798
816
|
return (
|
|
799
817
|
<div className="space-y-3">
|
|
800
818
|
<StatGrid stats={[
|
|
801
|
-
{ label: "
|
|
819
|
+
{ label: "Graph version", value: data.graph_schema_version || data.schema_version || "reported" },
|
|
802
820
|
{ label: "Nodes", value: (stats.total_nodes as number) || Object.values((stats.nodes as Record<string, unknown>) || {}).reduce((sum: number, value) => sum + Number(value || 0), 0) },
|
|
803
821
|
{ label: "Edges", value: (stats.total_edges as number) || Object.values((stats.edges as Record<string, unknown>) || {}).reduce((sum: number, value) => sum + Number(value || 0), 0) },
|
|
804
822
|
{ label: "Storage", value: storage.engine || "reported" },
|
|
@@ -12,9 +12,9 @@ type CaptureTab = "files" | "local" | "browser" | "pipeline";
|
|
|
12
12
|
|
|
13
13
|
const tabs: Array<{ id: CaptureTab; label: string }> = [
|
|
14
14
|
{ id: "files", label: "Files" },
|
|
15
|
-
{ id: "local", label: "
|
|
16
|
-
{ id: "browser", label: "Web
|
|
17
|
-
{ id: "pipeline", label: "
|
|
15
|
+
{ id: "local", label: "Folders" },
|
|
16
|
+
{ id: "browser", label: "Web" },
|
|
17
|
+
{ id: "pipeline", label: "Flow" },
|
|
18
18
|
];
|
|
19
19
|
|
|
20
20
|
export function CapturePage({ initialTab }: { initialTab?: string }) {
|
|
@@ -23,11 +23,11 @@ export function CapturePage({ initialTab }: { initialTab?: string }) {
|
|
|
23
23
|
if (initialTab === "pipeline" || initialTab === "local" || initialTab === "files") setTab(initialTab);
|
|
24
24
|
}, [initialTab]);
|
|
25
25
|
return (
|
|
26
|
-
<div className="space-y-
|
|
27
|
-
<header>
|
|
28
|
-
<div className="
|
|
29
|
-
<h1 className="
|
|
30
|
-
<p className="
|
|
26
|
+
<div className="space-y-5">
|
|
27
|
+
<header className="page-hero">
|
|
28
|
+
<div className="page-kicker"><Upload className="h-4 w-4" /> Add</div>
|
|
29
|
+
<h1 className="page-title">Feed the brain what matters.</h1>
|
|
30
|
+
<p className="page-copy">Drop in files, connect folders, or save a page. Lattice remembers the origin of every idea it learns.</p>
|
|
31
31
|
</header>
|
|
32
32
|
<Tabs tabs={tabs} value={tab} onChange={(id) => setTab(id as CaptureTab)} />
|
|
33
33
|
{tab === "files" ? <FilesPanel /> : null}
|
|
@@ -49,14 +49,14 @@ function FilesPanel() {
|
|
|
49
49
|
<div className="grid gap-4 xl:grid-cols-[0.75fr_1.25fr]">
|
|
50
50
|
<Card>
|
|
51
51
|
<CardHeader>
|
|
52
|
-
<CardTitle className="flex items-center gap-2"><Upload className="h-4 w-4" />
|
|
53
|
-
<CardDescription>
|
|
52
|
+
<CardTitle className="flex items-center gap-2"><Upload className="h-4 w-4" /> Add documents</CardTitle>
|
|
53
|
+
<CardDescription>Choose files and Lattice will prepare them for search and memory.</CardDescription>
|
|
54
54
|
</CardHeader>
|
|
55
55
|
<CardContent>
|
|
56
|
-
<label className="flex min-h-
|
|
56
|
+
<label className="flex min-h-56 cursor-pointer flex-col items-center justify-center gap-3 rounded-lg border border-dashed border-border bg-muted/30 p-6 text-center transition hover:bg-muted/50">
|
|
57
57
|
<Upload className="h-7 w-7 text-primary" />
|
|
58
|
-
<span className="font-
|
|
59
|
-
<span className="text-sm text-muted-foreground">PDF,
|
|
58
|
+
<span className="text-lg font-semibold">Choose files</span>
|
|
59
|
+
<span className="max-w-sm text-sm leading-6 text-muted-foreground">PDF, Office files, notes, markdown, text, and spreadsheets are all welcome.</span>
|
|
60
60
|
<input type="file" multiple className="sr-only" onChange={(e) => e.target.files && upload.mutate(e.target.files)} />
|
|
61
61
|
</label>
|
|
62
62
|
{upload.data ? (
|
|
@@ -86,12 +86,12 @@ function LocalPanel() {
|
|
|
86
86
|
<div className="grid gap-4 xl:grid-cols-[0.9fr_1.1fr]">
|
|
87
87
|
<Card>
|
|
88
88
|
<CardHeader>
|
|
89
|
-
<CardTitle className="flex items-center gap-2"><FolderPlus className="h-4 w-4" /> Connect folder</CardTitle>
|
|
90
|
-
<CardDescription>
|
|
89
|
+
<CardTitle className="flex items-center gap-2"><FolderPlus className="h-4 w-4" /> Connect a folder</CardTitle>
|
|
90
|
+
<CardDescription>Point Lattice at a folder you want it to remember.</CardDescription>
|
|
91
91
|
</CardHeader>
|
|
92
92
|
<CardContent className="space-y-3">
|
|
93
|
-
<Input value={path} onChange={(e) => setPath(e.target.value)} placeholder="
|
|
94
|
-
<Button disabled={!path.trim() || connect.isPending} onClick={() => connect.mutate()}>Connect
|
|
93
|
+
<Input value={path} onChange={(e) => setPath(e.target.value)} placeholder="Folder path on this Mac" />
|
|
94
|
+
<Button disabled={!path.trim() || connect.isPending} onClick={() => connect.mutate()}>Connect Folder</Button>
|
|
95
95
|
{connect.data ? <OperationResult result={connect.data} successLabel="Folder connection requested" /> : null}
|
|
96
96
|
</CardContent>
|
|
97
97
|
</Card>
|
|
@@ -110,7 +110,7 @@ function LocalPanel() {
|
|
|
110
110
|
</div>
|
|
111
111
|
)}
|
|
112
112
|
</DataPanel>
|
|
113
|
-
<DataPanel title="
|
|
113
|
+
<DataPanel title="Folder access" result={agent.data} className="xl:col-span-2">
|
|
114
114
|
{(data) => <StructuredView value={data} />}
|
|
115
115
|
</DataPanel>
|
|
116
116
|
</div>
|
|
@@ -123,8 +123,8 @@ function BrowserPanel() {
|
|
|
123
123
|
return (
|
|
124
124
|
<Card>
|
|
125
125
|
<CardHeader>
|
|
126
|
-
<CardTitle className="flex items-center gap-2"><Globe2 className="h-4 w-4" />
|
|
127
|
-
<CardDescription>
|
|
126
|
+
<CardTitle className="flex items-center gap-2"><Globe2 className="h-4 w-4" /> Save a web page</CardTitle>
|
|
127
|
+
<CardDescription>Capture a page so Lattice can remember the useful parts.</CardDescription>
|
|
128
128
|
</CardHeader>
|
|
129
129
|
<CardContent className="space-y-3">
|
|
130
130
|
<div className="flex flex-col gap-2 sm:flex-row">
|
|
@@ -142,16 +142,16 @@ function PipelinePanel() {
|
|
|
142
142
|
const stats = useQuery({ queryKey: ["graphStats"], queryFn: latticeApi.graphStats });
|
|
143
143
|
return (
|
|
144
144
|
<div className="grid gap-4 xl:grid-cols-2">
|
|
145
|
-
<DataPanel title="
|
|
145
|
+
<DataPanel title="Processing status" result={index.data}>
|
|
146
146
|
{(data) => <StructuredView value={data} />}
|
|
147
147
|
</DataPanel>
|
|
148
|
-
<DataPanel title="
|
|
148
|
+
<DataPanel title="Brain growth" result={stats.data}>
|
|
149
149
|
{(data) => <StructuredView value={data} />}
|
|
150
150
|
</DataPanel>
|
|
151
151
|
<Card className="xl:col-span-2">
|
|
152
152
|
<CardHeader>
|
|
153
|
-
<CardTitle className="flex items-center gap-2"><HardDrive className="h-4 w-4" />
|
|
154
|
-
<CardDescription>
|
|
153
|
+
<CardTitle className="flex items-center gap-2"><HardDrive className="h-4 w-4" /> Refresh memory</CardTitle>
|
|
154
|
+
<CardDescription>Refresh search when you want Lattice to re-check captured material.</CardDescription>
|
|
155
155
|
</CardHeader>
|
|
156
156
|
<CardContent>
|
|
157
157
|
<ActionButton label="Rebuild retrieval index" action={() => latticeApi.rebuildIndex()} invalidate={["index"]} />
|