ltcai 6.2.0 → 6.3.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 +29 -17
- package/docs/CHANGELOG.md +66 -0
- package/frontend/src/components/onboarding/AnalysisScreen.tsx +127 -0
- package/frontend/src/components/onboarding/DownloadConsentPanel.tsx +29 -0
- package/frontend/src/components/onboarding/InstallScreen.tsx +208 -0
- package/frontend/src/components/onboarding/LanguageChooser.tsx +23 -0
- package/frontend/src/components/onboarding/LoginScreen.tsx +131 -0
- package/frontend/src/components/onboarding/ProductFlowScreens.tsx +13 -688
- package/frontend/src/components/onboarding/RecommendationScreen.tsx +59 -0
- package/frontend/src/components/onboarding/recommendationModel.ts +132 -0
- package/frontend/src/features/admin/AdminConsole.tsx +1 -1
- package/frontend/src/features/brain/BrainCarePanel.tsx +254 -0
- package/frontend/src/features/brain/BrainComposer.tsx +66 -0
- package/frontend/src/features/brain/BrainConversation.tsx +131 -0
- package/frontend/src/features/brain/BrainGraphLayer.tsx +132 -0
- package/frontend/src/features/brain/BrainHome.tsx +28 -795
- package/frontend/src/features/brain/BrainMemoryLayer.tsx +38 -0
- package/frontend/src/features/brain/BrainOverviewPanel.tsx +74 -0
- package/frontend/src/features/brain/BrainRelationshipLayer.tsx +43 -0
- package/frontend/src/features/brain/DepthEmergence.tsx +53 -0
- package/frontend/src/features/brain/types.ts +6 -6
- package/frontend/src/features/review/ReviewCard.tsx +14 -10
- package/frontend/src/features/review/ReviewInbox.tsx +24 -9
- package/frontend/src/i18n.ts +208 -0
- package/frontend/src/pages/Brain.tsx +63 -5
- package/frontend/src/pages/Capture.tsx +139 -43
- package/frontend/src/pages/Library.tsx +72 -6
- package/lattice_brain/__init__.py +1 -1
- package/lattice_brain/runtime/multi_agent.py +1 -1
- package/latticeai/__init__.py +1 -1
- package/latticeai/app_factory.py +48 -106
- package/latticeai/core/marketplace.py +1 -1
- package/latticeai/core/workspace_os.py +1 -1
- package/latticeai/runtime/access_runtime.py +97 -0
- package/latticeai/runtime/chat_wiring.py +119 -0
- package/latticeai/runtime/model_wiring.py +40 -0
- package/latticeai/runtime/platform_runtime_wiring.py +2 -3
- package/latticeai/runtime/review_wiring.py +37 -0
- package/latticeai/runtime/tail_wiring.py +21 -0
- package/package.json +2 -2
- package/scripts/check_i18n_literals.mjs +52 -0
- 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-Div5vMlq.css +2 -0
- package/static/app/assets/{index-D91Rz5--.js → index-t1jx1BR9.js} +2 -2
- package/static/app/assets/index-t1jx1BR9.js.map +1 -0
- package/static/app/index.html +2 -2
- package/static/app/assets/index-B2-1Gm0q.css +0 -2
- package/static/app/assets/index-D91Rz5--.js.map +0 -1
|
@@ -1,33 +1,35 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
2
|
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
|
3
|
-
import { FolderPlus, Globe2, HardDrive, Upload } from "lucide-react";
|
|
3
|
+
import { AlertCircle, CheckCircle2, FolderPlus, Globe2, HardDrive, Loader2, RotateCcw, Upload } from "lucide-react";
|
|
4
4
|
import { latticeApi } from "@/api/client";
|
|
5
5
|
import { ActionButton, DataPanel, EntityList, OperationResult, StructuredView, Tabs } from "@/components/primitives";
|
|
6
6
|
import { Button } from "@/components/ui/button";
|
|
7
7
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
|
8
8
|
import { Input } from "@/components/ui/input";
|
|
9
|
+
import { t, type Language } from "@/i18n";
|
|
9
10
|
import { asArray } from "@/lib/utils";
|
|
11
|
+
import { useAppStore } from "@/store/appStore";
|
|
10
12
|
|
|
11
13
|
type CaptureTab = "files" | "local" | "browser" | "pipeline";
|
|
12
14
|
|
|
13
|
-
const tabs: Array<{ id: CaptureTab; label: string }> = [
|
|
14
|
-
{ id: "files", label: "Files" },
|
|
15
|
-
{ id: "local", label: "Folders" },
|
|
16
|
-
{ id: "browser", label: "Web" },
|
|
17
|
-
{ id: "pipeline", label: "Flow" },
|
|
18
|
-
];
|
|
19
|
-
|
|
20
15
|
export function CapturePage({ initialTab }: { initialTab?: string }) {
|
|
16
|
+
const language = useAppStore((state) => state.language);
|
|
21
17
|
const [tab, setTab] = React.useState<CaptureTab>((initialTab as CaptureTab) || "files");
|
|
18
|
+
const tabs: Array<{ id: CaptureTab; label: string }> = [
|
|
19
|
+
{ id: "files", label: t(language, "capture.tab.files") },
|
|
20
|
+
{ id: "local", label: t(language, "capture.tab.local") },
|
|
21
|
+
{ id: "browser", label: t(language, "capture.tab.browser") },
|
|
22
|
+
{ id: "pipeline", label: t(language, "capture.tab.pipeline") },
|
|
23
|
+
];
|
|
22
24
|
React.useEffect(() => {
|
|
23
25
|
if (initialTab === "pipeline" || initialTab === "local" || initialTab === "files") setTab(initialTab);
|
|
24
26
|
}, [initialTab]);
|
|
25
27
|
return (
|
|
26
28
|
<div className="space-y-5">
|
|
27
29
|
<header className="page-hero">
|
|
28
|
-
<div className="page-kicker"><Upload className="h-4 w-4" />
|
|
29
|
-
<h1 className="page-title">
|
|
30
|
-
<p className="page-copy">
|
|
30
|
+
<div className="page-kicker"><Upload className="h-4 w-4" /> {t(language, "capture.kicker")}</div>
|
|
31
|
+
<h1 className="page-title">{t(language, "capture.title")}</h1>
|
|
32
|
+
<p className="page-copy">{t(language, "capture.body")}</p>
|
|
31
33
|
</header>
|
|
32
34
|
<Tabs tabs={tabs} value={tab} onChange={(id) => setTab(id as CaptureTab)} />
|
|
33
35
|
{tab === "files" ? <FilesPanel /> : null}
|
|
@@ -39,41 +41,133 @@ export function CapturePage({ initialTab }: { initialTab?: string }) {
|
|
|
39
41
|
}
|
|
40
42
|
|
|
41
43
|
function FilesPanel() {
|
|
44
|
+
const language = useAppStore((state) => state.language);
|
|
42
45
|
const qc = useQueryClient();
|
|
43
46
|
const docs = useQuery({ queryKey: ["documents"], queryFn: () => latticeApi.documents(200) });
|
|
47
|
+
const [queue, setQueue] = React.useState<UploadQueueItem[]>([]);
|
|
44
48
|
const upload = useMutation({
|
|
45
|
-
mutationFn: (files:
|
|
46
|
-
onSuccess: () =>
|
|
49
|
+
mutationFn: (files: File[]) => uploadFiles(files, setQueue),
|
|
50
|
+
onSuccess: () => {
|
|
51
|
+
void qc.invalidateQueries({ queryKey: ["documents"] });
|
|
52
|
+
void qc.invalidateQueries({ queryKey: ["graphStats"] });
|
|
53
|
+
void qc.invalidateQueries({ queryKey: ["memoryManager"] });
|
|
54
|
+
},
|
|
47
55
|
});
|
|
56
|
+
const beginUpload = React.useCallback((files: FileList | File[]) => {
|
|
57
|
+
const nextFiles = Array.from(files);
|
|
58
|
+
if (!nextFiles.length) return;
|
|
59
|
+
upload.mutate(nextFiles);
|
|
60
|
+
}, [upload]);
|
|
48
61
|
return (
|
|
49
62
|
<div className="grid gap-4 xl:grid-cols-[0.75fr_1.25fr]">
|
|
50
63
|
<Card>
|
|
51
64
|
<CardHeader>
|
|
52
|
-
<CardTitle className="flex items-center gap-2"><Upload className="h-4 w-4" />
|
|
53
|
-
<CardDescription>
|
|
65
|
+
<CardTitle className="flex items-center gap-2"><Upload className="h-4 w-4" /> {t(language, "capture.files.title")}</CardTitle>
|
|
66
|
+
<CardDescription>{t(language, "capture.files.description")}</CardDescription>
|
|
54
67
|
</CardHeader>
|
|
55
68
|
<CardContent>
|
|
56
|
-
<label
|
|
69
|
+
<label
|
|
70
|
+
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"
|
|
71
|
+
onDragOver={(event) => event.preventDefault()}
|
|
72
|
+
onDrop={(event) => {
|
|
73
|
+
event.preventDefault();
|
|
74
|
+
beginUpload(event.dataTransfer.files);
|
|
75
|
+
}}
|
|
76
|
+
>
|
|
57
77
|
<Upload className="h-7 w-7 text-primary" />
|
|
58
|
-
<span className="text-lg font-semibold">
|
|
59
|
-
<span className="max-w-sm text-sm leading-6 text-muted-foreground">
|
|
60
|
-
<input type="file" multiple className="sr-only" onChange={(e) => e.target.files &&
|
|
78
|
+
<span className="text-lg font-semibold">{t(language, "capture.files.drop")}</span>
|
|
79
|
+
<span className="max-w-sm text-sm leading-6 text-muted-foreground">{t(language, "capture.files.dropDetail")}</span>
|
|
80
|
+
<input type="file" multiple className="sr-only" onChange={(e) => e.target.files && beginUpload(e.target.files)} />
|
|
61
81
|
</label>
|
|
62
|
-
{
|
|
63
|
-
<div className="mt-3 space-y-2">
|
|
64
|
-
{upload.data.map((item, index) => <OperationResult key={index} result={item} successLabel="Upload completed" />)}
|
|
65
|
-
</div>
|
|
66
|
-
) : null}
|
|
82
|
+
<DocumentUploadQueue queue={queue} onRetry={(file) => beginUpload([file])} />
|
|
67
83
|
</CardContent>
|
|
68
84
|
</Card>
|
|
69
|
-
<DataPanel title=
|
|
70
|
-
{(data) =>
|
|
85
|
+
<DataPanel title={t(language, "capture.files.uploaded")} result={docs.data}>
|
|
86
|
+
{(data) => (
|
|
87
|
+
<div className="space-y-3">
|
|
88
|
+
<EntityList items={(data as Record<string, unknown>).documents || data} titleKey="filename" metaKey="ingest_state" limit={12} />
|
|
89
|
+
<div className="rounded-md border border-border bg-background/55 p-3 text-sm text-muted-foreground">
|
|
90
|
+
{t(language, "capture.files.completed")}
|
|
91
|
+
</div>
|
|
92
|
+
</div>
|
|
93
|
+
)}
|
|
71
94
|
</DataPanel>
|
|
72
95
|
</div>
|
|
73
96
|
);
|
|
74
97
|
}
|
|
75
98
|
|
|
99
|
+
type UploadQueueItem = {
|
|
100
|
+
id: string;
|
|
101
|
+
file: File;
|
|
102
|
+
name: string;
|
|
103
|
+
size: number;
|
|
104
|
+
status: "queued" | "uploading" | "done" | "failed";
|
|
105
|
+
result?: Awaited<ReturnType<typeof latticeApi.uploadDocument>>;
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
async function uploadFiles(files: File[], setQueue: React.Dispatch<React.SetStateAction<UploadQueueItem[]>>) {
|
|
109
|
+
const rows = files.map((file) => ({
|
|
110
|
+
id: `${file.name}-${file.size}-${file.lastModified}-${Date.now()}`,
|
|
111
|
+
file,
|
|
112
|
+
name: file.name,
|
|
113
|
+
size: file.size,
|
|
114
|
+
status: "queued" as const,
|
|
115
|
+
}));
|
|
116
|
+
setQueue((items) => [...rows, ...items].slice(0, 12));
|
|
117
|
+
const results = [];
|
|
118
|
+
for (const row of rows) {
|
|
119
|
+
setQueue((items) => items.map((item) => item.id === row.id ? { ...item, status: "uploading" } : item));
|
|
120
|
+
const result = await latticeApi.uploadDocument(row.file);
|
|
121
|
+
results.push(result);
|
|
122
|
+
setQueue((items) => items.map((item) => item.id === row.id ? { ...item, status: result.ok ? "done" : "failed", result } : item));
|
|
123
|
+
}
|
|
124
|
+
return results;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function DocumentUploadQueue({ queue, onRetry }: { queue: UploadQueueItem[]; onRetry: (file: File) => void }) {
|
|
128
|
+
const language = useAppStore((state) => state.language);
|
|
129
|
+
if (!queue.length) return null;
|
|
130
|
+
return (
|
|
131
|
+
<div className="mt-4 space-y-2">
|
|
132
|
+
{queue.map((item) => {
|
|
133
|
+
const detail = uploadResultDetail(item, language);
|
|
134
|
+
return (
|
|
135
|
+
<div key={item.id} className="rounded-md border border-border bg-background/55 p-3 text-sm">
|
|
136
|
+
<div className="flex flex-wrap items-start justify-between gap-3">
|
|
137
|
+
<div>
|
|
138
|
+
<div className="flex items-center gap-2 font-medium">
|
|
139
|
+
{item.status === "done" ? <CheckCircle2 className="h-4 w-4 text-emerald-400" /> : item.status === "failed" ? <AlertCircle className="h-4 w-4 text-amber-400" /> : <Loader2 className="h-4 w-4 animate-spin text-primary" />}
|
|
140
|
+
{item.name}
|
|
141
|
+
</div>
|
|
142
|
+
<div className="mt-1 text-xs text-muted-foreground">
|
|
143
|
+
{Math.max(1, Math.round(item.size / 1024))} KB · {detail}
|
|
144
|
+
</div>
|
|
145
|
+
</div>
|
|
146
|
+
{item.status === "failed" ? (
|
|
147
|
+
<Button size="sm" variant="outline" onClick={() => onRetry(item.file)}>
|
|
148
|
+
<RotateCcw className="h-3.5 w-3.5" /> {t(language, "capture.retry")}
|
|
149
|
+
</Button>
|
|
150
|
+
) : null}
|
|
151
|
+
</div>
|
|
152
|
+
{item.result ? <OperationResult result={item.result} successLabel={t(language, "capture.files.success")} /> : null}
|
|
153
|
+
</div>
|
|
154
|
+
);
|
|
155
|
+
})}
|
|
156
|
+
</div>
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function uploadResultDetail(item: UploadQueueItem, language: Language) {
|
|
161
|
+
if (item.status === "queued") return t(language, "capture.files.queued");
|
|
162
|
+
if (item.status === "uploading") return t(language, "capture.files.uploading");
|
|
163
|
+
if (!item.result?.ok) return item.result?.error || t(language, "capture.files.failed");
|
|
164
|
+
const data = item.result.data || {};
|
|
165
|
+
const node = String(data.node_id || data.graph_node || data.provenance_id || "");
|
|
166
|
+
return node ? t(language, "capture.files.capturedWithNode", { node }) : t(language, "capture.files.captured");
|
|
167
|
+
}
|
|
168
|
+
|
|
76
169
|
function LocalPanel() {
|
|
170
|
+
const language = useAppStore((state) => state.language);
|
|
77
171
|
const qc = useQueryClient();
|
|
78
172
|
const [path, setPath] = React.useState("");
|
|
79
173
|
const local = useQuery({ queryKey: ["localSources"], queryFn: latticeApi.localSources });
|
|
@@ -86,16 +180,16 @@ function LocalPanel() {
|
|
|
86
180
|
<div className="grid gap-4 xl:grid-cols-[0.9fr_1.1fr]">
|
|
87
181
|
<Card>
|
|
88
182
|
<CardHeader>
|
|
89
|
-
<CardTitle className="flex items-center gap-2"><FolderPlus className="h-4 w-4" />
|
|
90
|
-
<CardDescription>
|
|
183
|
+
<CardTitle className="flex items-center gap-2"><FolderPlus className="h-4 w-4" /> {t(language, "capture.local.title")}</CardTitle>
|
|
184
|
+
<CardDescription>{t(language, "capture.local.description")}</CardDescription>
|
|
91
185
|
</CardHeader>
|
|
92
186
|
<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()}>
|
|
95
|
-
{connect.data ? <OperationResult result={connect.data} successLabel=
|
|
187
|
+
<Input value={path} onChange={(e) => setPath(e.target.value)} placeholder={t(language, "capture.local.placeholder")} />
|
|
188
|
+
<Button disabled={!path.trim() || connect.isPending} onClick={() => connect.mutate()}>{t(language, "capture.local.connect")}</Button>
|
|
189
|
+
{connect.data ? <OperationResult result={connect.data} successLabel={t(language, "capture.local.success")} /> : null}
|
|
96
190
|
</CardContent>
|
|
97
191
|
</Card>
|
|
98
|
-
<DataPanel title="
|
|
192
|
+
<DataPanel title={t(language, "capture.local.sources")} result={local.data}>
|
|
99
193
|
{(data) => (
|
|
100
194
|
<div className="space-y-3">
|
|
101
195
|
<EntityList items={(data as Record<string, unknown>).sources} titleKey="path" metaKey="status" />
|
|
@@ -110,7 +204,7 @@ function LocalPanel() {
|
|
|
110
204
|
</div>
|
|
111
205
|
)}
|
|
112
206
|
</DataPanel>
|
|
113
|
-
<DataPanel title="
|
|
207
|
+
<DataPanel title={t(language, "capture.local.access")} result={agent.data} className="xl:col-span-2">
|
|
114
208
|
{(data) => <StructuredView value={data} />}
|
|
115
209
|
</DataPanel>
|
|
116
210
|
</div>
|
|
@@ -118,43 +212,45 @@ function LocalPanel() {
|
|
|
118
212
|
}
|
|
119
213
|
|
|
120
214
|
function BrowserPanel() {
|
|
215
|
+
const language = useAppStore((state) => state.language);
|
|
121
216
|
const [url, setUrl] = React.useState("");
|
|
122
217
|
const read = useMutation({ mutationFn: () => latticeApi.browserReadUrl(url) });
|
|
123
218
|
return (
|
|
124
219
|
<Card>
|
|
125
220
|
<CardHeader>
|
|
126
|
-
<CardTitle className="flex items-center gap-2"><Globe2 className="h-4 w-4" />
|
|
127
|
-
<CardDescription>
|
|
221
|
+
<CardTitle className="flex items-center gap-2"><Globe2 className="h-4 w-4" /> {t(language, "capture.browser.title")}</CardTitle>
|
|
222
|
+
<CardDescription>{t(language, "capture.browser.description")}</CardDescription>
|
|
128
223
|
</CardHeader>
|
|
129
224
|
<CardContent className="space-y-3">
|
|
130
225
|
<div className="flex flex-col gap-2 sm:flex-row">
|
|
131
|
-
<Input value={url} onChange={(e) => setUrl(e.target.value)} placeholder="
|
|
132
|
-
<Button disabled={!url.trim() || read.isPending} onClick={() => read.mutate()}>
|
|
226
|
+
<Input value={url} onChange={(e) => setUrl(e.target.value)} placeholder={t(language, "capture.browser.placeholder")} />
|
|
227
|
+
<Button disabled={!url.trim() || read.isPending} onClick={() => read.mutate()}>{t(language, "capture.browser.capture")}</Button>
|
|
133
228
|
</div>
|
|
134
|
-
{read.data ? <OperationResult result={read.data} successLabel="
|
|
229
|
+
{read.data ? <OperationResult result={read.data} successLabel={t(language, "capture.browser.success")} /> : null}
|
|
135
230
|
</CardContent>
|
|
136
231
|
</Card>
|
|
137
232
|
);
|
|
138
233
|
}
|
|
139
234
|
|
|
140
235
|
function PipelinePanel() {
|
|
236
|
+
const language = useAppStore((state) => state.language);
|
|
141
237
|
const index = useQuery({ queryKey: ["index"], queryFn: latticeApi.indexStatus });
|
|
142
238
|
const stats = useQuery({ queryKey: ["graphStats"], queryFn: latticeApi.graphStats });
|
|
143
239
|
return (
|
|
144
240
|
<div className="grid gap-4 xl:grid-cols-2">
|
|
145
|
-
<DataPanel title="
|
|
241
|
+
<DataPanel title={t(language, "capture.pipeline.status")} result={index.data}>
|
|
146
242
|
{(data) => <StructuredView value={data} />}
|
|
147
243
|
</DataPanel>
|
|
148
|
-
<DataPanel title="
|
|
244
|
+
<DataPanel title={t(language, "capture.pipeline.growth")} result={stats.data}>
|
|
149
245
|
{(data) => <StructuredView value={data} />}
|
|
150
246
|
</DataPanel>
|
|
151
247
|
<Card className="xl:col-span-2">
|
|
152
248
|
<CardHeader>
|
|
153
|
-
<CardTitle className="flex items-center gap-2"><HardDrive className="h-4 w-4" />
|
|
154
|
-
<CardDescription>
|
|
249
|
+
<CardTitle className="flex items-center gap-2"><HardDrive className="h-4 w-4" /> {t(language, "capture.pipeline.refresh")}</CardTitle>
|
|
250
|
+
<CardDescription>{t(language, "capture.pipeline.description")}</CardDescription>
|
|
155
251
|
</CardHeader>
|
|
156
252
|
<CardContent>
|
|
157
|
-
<ActionButton label=
|
|
253
|
+
<ActionButton label={t(language, "capture.pipeline.rebuild")} action={() => latticeApi.rebuildIndex()} invalidate={["index"]} />
|
|
158
254
|
</CardContent>
|
|
159
255
|
</Card>
|
|
160
256
|
</div>
|
|
@@ -109,12 +109,20 @@ function ModelsPanel() {
|
|
|
109
109
|
const profile = (data as Record<string, unknown>).profile as Record<string, unknown> | undefined;
|
|
110
110
|
return (
|
|
111
111
|
<div className="space-y-4">
|
|
112
|
-
<
|
|
113
|
-
|
|
114
|
-
{
|
|
115
|
-
{
|
|
116
|
-
{
|
|
117
|
-
|
|
112
|
+
<ModelRuntimeSummary
|
|
113
|
+
profile={profile || {}}
|
|
114
|
+
recommendation={recommendation || {}}
|
|
115
|
+
current={current}
|
|
116
|
+
currentId={currentId}
|
|
117
|
+
latestProgress={latestProgress}
|
|
118
|
+
lastResult={lastResult}
|
|
119
|
+
onReload={() => {
|
|
120
|
+
if (current) void prepareModel(String(current.recommended_load_id || current.id || currentId), String(current.recommended_engine || current.engine || "local_mlx"), consent);
|
|
121
|
+
}}
|
|
122
|
+
onUnload={() => {
|
|
123
|
+
if (currentId) void latticeApi.unloadModel(currentId).then(() => qc.invalidateQueries({ queryKey: ["models"] }));
|
|
124
|
+
}}
|
|
125
|
+
/>
|
|
118
126
|
<div className="grid gap-2 md:grid-cols-3 xl:grid-cols-6">
|
|
119
127
|
{[
|
|
120
128
|
["Environment Analysis", true, Cpu],
|
|
@@ -296,6 +304,64 @@ function AlternativeModels({ compatibility }: { compatibility: Record<string, un
|
|
|
296
304
|
);
|
|
297
305
|
}
|
|
298
306
|
|
|
307
|
+
function ModelRuntimeSummary({
|
|
308
|
+
profile,
|
|
309
|
+
recommendation,
|
|
310
|
+
current,
|
|
311
|
+
currentId,
|
|
312
|
+
latestProgress,
|
|
313
|
+
lastResult,
|
|
314
|
+
onReload,
|
|
315
|
+
onUnload,
|
|
316
|
+
}: {
|
|
317
|
+
profile: Record<string, unknown>;
|
|
318
|
+
recommendation: Record<string, unknown>;
|
|
319
|
+
current?: Record<string, unknown>;
|
|
320
|
+
currentId: string;
|
|
321
|
+
latestProgress: Record<string, unknown> | null;
|
|
322
|
+
lastResult: Record<string, unknown> | null;
|
|
323
|
+
onReload: () => void;
|
|
324
|
+
onUnload: () => void;
|
|
325
|
+
}) {
|
|
326
|
+
const loadedName = String(current?.name || current?.id || currentId || "");
|
|
327
|
+
const engine = String(current?.engine || current?.recommended_engine || latestProgress?.engine || "local_mlx");
|
|
328
|
+
const cachePath = String(current?.local_path || current?.storage_location || recommendation.cache_path || recommendation.storage_location || "~/.cache/huggingface / ~/.latticeai/models");
|
|
329
|
+
const progressStage = String(latestProgress?.stage || lastResult?.stage || (loadedName ? "ready" : "idle"));
|
|
330
|
+
return (
|
|
331
|
+
<div className="space-y-3">
|
|
332
|
+
<StatGrid stats={[
|
|
333
|
+
{ label: "Computer", value: profile?.os ? `${String(profile.os)} ${String(profile.arch || "")}` : "detected" },
|
|
334
|
+
{ label: "Engine", value: engine },
|
|
335
|
+
{ label: "Loaded model", value: loadedName || "No local model loaded" },
|
|
336
|
+
{ label: "Runtime state", value: progressStage },
|
|
337
|
+
]} />
|
|
338
|
+
<div className="rounded-lg border border-border bg-background/55 p-3 text-sm">
|
|
339
|
+
<div className="flex flex-wrap items-start justify-between gap-3">
|
|
340
|
+
<div>
|
|
341
|
+
<div className="font-medium">{loadedName ? "Local model runtime is available" : "No model is loaded yet"}</div>
|
|
342
|
+
<div className="mt-1 text-muted-foreground">Cache/storage: {cachePath}</div>
|
|
343
|
+
</div>
|
|
344
|
+
<div className="flex flex-wrap gap-2">
|
|
345
|
+
<Button variant="outline" size="sm" disabled={!loadedName} onClick={onReload}>Reload</Button>
|
|
346
|
+
<Button variant="outline" size="sm" disabled={!loadedName} onClick={onUnload}>Unload</Button>
|
|
347
|
+
</div>
|
|
348
|
+
</div>
|
|
349
|
+
{latestProgress ? (
|
|
350
|
+
<div className="mt-3">
|
|
351
|
+
<div className="flex justify-between text-xs text-muted-foreground">
|
|
352
|
+
<span>{String(latestProgress.message || "Preparing model")}</span>
|
|
353
|
+
<span>{Number(latestProgress.percent || 0)}%</span>
|
|
354
|
+
</div>
|
|
355
|
+
<div className="mt-1 h-2 overflow-hidden rounded-full bg-muted">
|
|
356
|
+
<div className="h-full bg-primary" style={{ width: `${Number(latestProgress.percent || 8)}%` }} />
|
|
357
|
+
</div>
|
|
358
|
+
</div>
|
|
359
|
+
) : null}
|
|
360
|
+
</div>
|
|
361
|
+
</div>
|
|
362
|
+
);
|
|
363
|
+
}
|
|
364
|
+
|
|
299
365
|
function ModelRecovery({ error }: { error: Record<string, unknown> }) {
|
|
300
366
|
const guidance = asArray<string>(error.recovery_guidance);
|
|
301
367
|
return (
|
|
@@ -19,7 +19,7 @@ from datetime import datetime
|
|
|
19
19
|
from typing import Any, Callable, Dict, List, Optional
|
|
20
20
|
|
|
21
21
|
|
|
22
|
-
MULTI_AGENT_VERSION = "6.
|
|
22
|
+
MULTI_AGENT_VERSION = "6.3.1"
|
|
23
23
|
|
|
24
24
|
AGENT_ROLES = ("researcher", "planner", "executor", "reviewer", "release")
|
|
25
25
|
CORE_PIPELINE = ("planner", "executor", "reviewer")
|
package/latticeai/__init__.py
CHANGED