ltcai 6.2.0 → 6.3.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 +18 -15
- package/docs/CHANGELOG.md +42 -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 +3 -1
- package/frontend/src/features/review/ReviewInbox.tsx +11 -1
- package/frontend/src/i18n.ts +92 -0
- package/frontend/src/pages/Brain.tsx +63 -5
- package/frontend/src/pages/Capture.tsx +102 -13
- 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 +31 -39
- package/latticeai/core/marketplace.py +1 -1
- package/latticeai/core/workspace_os.py +1 -1
- 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-D91Rz5--.js → index-D76dWuQk.js} +2 -2
- package/static/app/assets/index-D76dWuQk.js.map +1 -0
- package/static/app/assets/index-Div5vMlq.css +2 -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,6 +1,6 @@
|
|
|
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";
|
|
@@ -41,10 +41,20 @@ export function CapturePage({ initialTab }: { initialTab?: string }) {
|
|
|
41
41
|
function FilesPanel() {
|
|
42
42
|
const qc = useQueryClient();
|
|
43
43
|
const docs = useQuery({ queryKey: ["documents"], queryFn: () => latticeApi.documents(200) });
|
|
44
|
+
const [queue, setQueue] = React.useState<UploadQueueItem[]>([]);
|
|
44
45
|
const upload = useMutation({
|
|
45
|
-
mutationFn: (files:
|
|
46
|
-
onSuccess: () =>
|
|
46
|
+
mutationFn: (files: File[]) => uploadFiles(files, setQueue),
|
|
47
|
+
onSuccess: () => {
|
|
48
|
+
void qc.invalidateQueries({ queryKey: ["documents"] });
|
|
49
|
+
void qc.invalidateQueries({ queryKey: ["graphStats"] });
|
|
50
|
+
void qc.invalidateQueries({ queryKey: ["memoryManager"] });
|
|
51
|
+
},
|
|
47
52
|
});
|
|
53
|
+
const beginUpload = React.useCallback((files: FileList | File[]) => {
|
|
54
|
+
const nextFiles = Array.from(files);
|
|
55
|
+
if (!nextFiles.length) return;
|
|
56
|
+
upload.mutate(nextFiles);
|
|
57
|
+
}, [upload]);
|
|
48
58
|
return (
|
|
49
59
|
<div className="grid gap-4 xl:grid-cols-[0.75fr_1.25fr]">
|
|
50
60
|
<Card>
|
|
@@ -53,26 +63,105 @@ function FilesPanel() {
|
|
|
53
63
|
<CardDescription>Choose files and Lattice will prepare them for search and memory.</CardDescription>
|
|
54
64
|
</CardHeader>
|
|
55
65
|
<CardContent>
|
|
56
|
-
<label
|
|
66
|
+
<label
|
|
67
|
+
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"
|
|
68
|
+
onDragOver={(event) => event.preventDefault()}
|
|
69
|
+
onDrop={(event) => {
|
|
70
|
+
event.preventDefault();
|
|
71
|
+
beginUpload(event.dataTransfer.files);
|
|
72
|
+
}}
|
|
73
|
+
>
|
|
57
74
|
<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 &&
|
|
75
|
+
<span className="text-lg font-semibold">Drop files or choose documents</span>
|
|
76
|
+
<span className="max-w-sm text-sm leading-6 text-muted-foreground">Each file is queued, parsed, written to Memory, and linked into the Brain graph with source metadata.</span>
|
|
77
|
+
<input type="file" multiple className="sr-only" onChange={(e) => e.target.files && beginUpload(e.target.files)} />
|
|
61
78
|
</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}
|
|
79
|
+
<DocumentUploadQueue queue={queue} onRetry={(file) => beginUpload([file])} />
|
|
67
80
|
</CardContent>
|
|
68
81
|
</Card>
|
|
69
82
|
<DataPanel title="Uploaded documents" result={docs.data}>
|
|
70
|
-
{(data) =>
|
|
83
|
+
{(data) => (
|
|
84
|
+
<div className="space-y-3">
|
|
85
|
+
<EntityList items={(data as Record<string, unknown>).documents || data} titleKey="filename" metaKey="ingest_state" limit={12} />
|
|
86
|
+
<div className="rounded-md border border-border bg-background/55 p-3 text-sm text-muted-foreground">
|
|
87
|
+
Completed uploads appear here after they enter Memory. Graph links may continue building briefly after parsing finishes.
|
|
88
|
+
</div>
|
|
89
|
+
</div>
|
|
90
|
+
)}
|
|
71
91
|
</DataPanel>
|
|
72
92
|
</div>
|
|
73
93
|
);
|
|
74
94
|
}
|
|
75
95
|
|
|
96
|
+
type UploadQueueItem = {
|
|
97
|
+
id: string;
|
|
98
|
+
file: File;
|
|
99
|
+
name: string;
|
|
100
|
+
size: number;
|
|
101
|
+
status: "queued" | "uploading" | "done" | "failed";
|
|
102
|
+
result?: Awaited<ReturnType<typeof latticeApi.uploadDocument>>;
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
async function uploadFiles(files: File[], setQueue: React.Dispatch<React.SetStateAction<UploadQueueItem[]>>) {
|
|
106
|
+
const rows = files.map((file) => ({
|
|
107
|
+
id: `${file.name}-${file.size}-${file.lastModified}-${Date.now()}`,
|
|
108
|
+
file,
|
|
109
|
+
name: file.name,
|
|
110
|
+
size: file.size,
|
|
111
|
+
status: "queued" as const,
|
|
112
|
+
}));
|
|
113
|
+
setQueue((items) => [...rows, ...items].slice(0, 12));
|
|
114
|
+
const results = [];
|
|
115
|
+
for (const row of rows) {
|
|
116
|
+
setQueue((items) => items.map((item) => item.id === row.id ? { ...item, status: "uploading" } : item));
|
|
117
|
+
const result = await latticeApi.uploadDocument(row.file);
|
|
118
|
+
results.push(result);
|
|
119
|
+
setQueue((items) => items.map((item) => item.id === row.id ? { ...item, status: result.ok ? "done" : "failed", result } : item));
|
|
120
|
+
}
|
|
121
|
+
return results;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function DocumentUploadQueue({ queue, onRetry }: { queue: UploadQueueItem[]; onRetry: (file: File) => void }) {
|
|
125
|
+
if (!queue.length) return null;
|
|
126
|
+
return (
|
|
127
|
+
<div className="mt-4 space-y-2">
|
|
128
|
+
{queue.map((item) => {
|
|
129
|
+
const detail = uploadResultDetail(item);
|
|
130
|
+
return (
|
|
131
|
+
<div key={item.id} className="rounded-md border border-border bg-background/55 p-3 text-sm">
|
|
132
|
+
<div className="flex flex-wrap items-start justify-between gap-3">
|
|
133
|
+
<div>
|
|
134
|
+
<div className="flex items-center gap-2 font-medium">
|
|
135
|
+
{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" />}
|
|
136
|
+
{item.name}
|
|
137
|
+
</div>
|
|
138
|
+
<div className="mt-1 text-xs text-muted-foreground">
|
|
139
|
+
{Math.max(1, Math.round(item.size / 1024))} KB · {detail}
|
|
140
|
+
</div>
|
|
141
|
+
</div>
|
|
142
|
+
{item.status === "failed" ? (
|
|
143
|
+
<Button size="sm" variant="outline" onClick={() => onRetry(item.file)}>
|
|
144
|
+
<RotateCcw className="h-3.5 w-3.5" /> Retry
|
|
145
|
+
</Button>
|
|
146
|
+
) : null}
|
|
147
|
+
</div>
|
|
148
|
+
{item.result ? <OperationResult result={item.result} successLabel="Entered Brain and graph queue" /> : null}
|
|
149
|
+
</div>
|
|
150
|
+
);
|
|
151
|
+
})}
|
|
152
|
+
</div>
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function uploadResultDetail(item: UploadQueueItem) {
|
|
157
|
+
if (item.status === "queued") return "Waiting in the ingest queue";
|
|
158
|
+
if (item.status === "uploading") return "Parsing and sending to Memory";
|
|
159
|
+
if (!item.result?.ok) return item.result?.error || "Ingest failed before it entered the Brain";
|
|
160
|
+
const data = item.result.data || {};
|
|
161
|
+
const node = String(data.node_id || data.graph_node || data.provenance_id || "");
|
|
162
|
+
return node ? `Captured with source metadata · ${node}` : "Captured with source metadata";
|
|
163
|
+
}
|
|
164
|
+
|
|
76
165
|
function LocalPanel() {
|
|
77
166
|
const qc = useQueryClient();
|
|
78
167
|
const [path, setPath] = React.useState("");
|
|
@@ -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.0"
|
|
23
23
|
|
|
24
24
|
AGENT_ROLES = ("researcher", "planner", "executor", "reviewer", "release")
|
|
25
25
|
CORE_PIPELINE = ("planner", "executor", "reviewer")
|
package/latticeai/__init__.py
CHANGED
package/latticeai/app_factory.py
CHANGED
|
@@ -20,6 +20,11 @@ from typing import TYPE_CHECKING, Any, Dict, List, Optional
|
|
|
20
20
|
from latticeai.runtime.app_context_runtime import build_app_context
|
|
21
21
|
from latticeai.runtime.bootstrap import build_session_runtime
|
|
22
22
|
from latticeai.runtime.brain_runtime import build_brain_runtime
|
|
23
|
+
from latticeai.runtime.chat_wiring import (
|
|
24
|
+
build_chat_agent_runtime_from_context,
|
|
25
|
+
build_interaction_contexts,
|
|
26
|
+
maybe_build_telegram_chat_mirror,
|
|
27
|
+
)
|
|
23
28
|
from latticeai.runtime.config_runtime import build_config_runtime
|
|
24
29
|
from latticeai.runtime.context_runtime import build_context_runtime
|
|
25
30
|
from latticeai.runtime.hooks_runtime import (
|
|
@@ -28,12 +33,16 @@ from latticeai.runtime.hooks_runtime import (
|
|
|
28
33
|
build_hooks_runtime,
|
|
29
34
|
)
|
|
30
35
|
from latticeai.runtime.lifespan_runtime import build_lifespan_runtime
|
|
36
|
+
from latticeai.runtime.model_wiring import (
|
|
37
|
+
configure_model_runtime_from_context,
|
|
38
|
+
register_model_runtime_routers,
|
|
39
|
+
)
|
|
31
40
|
from latticeai.runtime.platform_services_runtime import (
|
|
32
41
|
build_brain_network,
|
|
33
|
-
build_model_service,
|
|
34
42
|
)
|
|
35
43
|
from latticeai.runtime.platform_runtime_wiring import build_platform_automation_runtime
|
|
36
44
|
from latticeai.runtime.persistence_runtime import build_persistence_runtime
|
|
45
|
+
from latticeai.runtime.review_wiring import build_review_run_now_runner
|
|
37
46
|
from latticeai.runtime.router_registration import (
|
|
38
47
|
build_auth_admin_security_router_bundle,
|
|
39
48
|
build_static_routes_bundle,
|
|
@@ -44,8 +53,8 @@ from latticeai.runtime.router_registration import (
|
|
|
44
53
|
register_review_and_brain_tail_routers,
|
|
45
54
|
)
|
|
46
55
|
from latticeai.runtime.security_runtime import build_security_runtime
|
|
56
|
+
from latticeai.runtime.tail_wiring import register_tail_runtime_routers
|
|
47
57
|
from latticeai.runtime.web_runtime import build_web_runtime
|
|
48
|
-
from latticeai.services.router_context import InteractionRouterContext, ToolRouterContext
|
|
49
58
|
|
|
50
59
|
if TYPE_CHECKING: # imports for annotations only — keep module import light
|
|
51
60
|
from fastapi import FastAPI
|
|
@@ -1089,7 +1098,8 @@ def _build(config: "Optional[Config]" = None) -> Dict[str, Any]:
|
|
|
1089
1098
|
OPEN_REGISTRATION = CONFIG.open_registration
|
|
1090
1099
|
INVITE_CODE = CONFIG.invite_code
|
|
1091
1100
|
INVITE_GATE_ENABLED = CONFIG.invite_gate_enabled
|
|
1092
|
-
|
|
1101
|
+
configure_model_runtime_from_context(
|
|
1102
|
+
configure_model_runtime=configure_model_runtime,
|
|
1093
1103
|
router=router,
|
|
1094
1104
|
APP_MODE=APP_MODE,
|
|
1095
1105
|
DEFAULT_HOST=DEFAULT_HOST,
|
|
@@ -1236,12 +1246,10 @@ def _build(config: "Optional[Config]" = None) -> Dict[str, Any]:
|
|
|
1236
1246
|
# ── Telegram chat mirror: registered only when ENABLE_TELEGRAM is truthy.
|
|
1237
1247
|
# latticeai.api.chat no longer imports telegram_bot (a 45KB module that
|
|
1238
1248
|
# mutates os.environ at import); it calls this injected callback instead.
|
|
1239
|
-
on_chat_message =
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
_spawn(broadcast_web_chat(role, text), name="telegram_broadcast")
|
|
1244
|
-
on_chat_message = _telegram_chat_mirror
|
|
1249
|
+
on_chat_message = maybe_build_telegram_chat_mirror(
|
|
1250
|
+
enable_telegram=ENABLE_TELEGRAM,
|
|
1251
|
+
spawn=_spawn,
|
|
1252
|
+
)
|
|
1245
1253
|
|
|
1246
1254
|
def _recent_chat_context(
|
|
1247
1255
|
limit: int = 10,
|
|
@@ -1257,7 +1265,8 @@ def _build(config: "Optional[Config]" = None) -> Dict[str, Any]:
|
|
|
1257
1265
|
conversation_id=conversation_id,
|
|
1258
1266
|
)
|
|
1259
1267
|
|
|
1260
|
-
CHAT_AGENT_RUNTIME =
|
|
1268
|
+
CHAT_AGENT_RUNTIME = build_chat_agent_runtime_from_context(
|
|
1269
|
+
build_agent_runtime=build_agent_runtime,
|
|
1261
1270
|
model_router=router,
|
|
1262
1271
|
execute_tool=execute_tool,
|
|
1263
1272
|
recent_chat_context=_recent_chat_context,
|
|
@@ -1404,22 +1413,19 @@ def _build(config: "Optional[Config]" = None) -> Dict[str, Any]:
|
|
|
1404
1413
|
# ── Health / status / engine-summary router (latticeai.api.health, v1.2.0) ───
|
|
1405
1414
|
# /health, /mode, /runtime_features, /engines(GET) now live in the health router.
|
|
1406
1415
|
# Heavier engine mutation endpoints remain below in server_app.
|
|
1407
|
-
MODEL_SERVICE =
|
|
1416
|
+
MODEL_SERVICE = register_model_runtime_routers(
|
|
1417
|
+
app=app,
|
|
1418
|
+
create_health_router=create_health_router,
|
|
1419
|
+
create_models_router=create_models_router,
|
|
1420
|
+
register_health_and_model_routers=register_health_and_model_routers,
|
|
1408
1421
|
model_router=router,
|
|
1409
1422
|
runtime_features=runtime_features,
|
|
1410
|
-
|
|
1411
|
-
)
|
|
1412
|
-
register_health_and_model_routers(
|
|
1413
|
-
app,
|
|
1414
|
-
create_health_router=create_health_router,
|
|
1415
|
-
model_service=MODEL_SERVICE,
|
|
1423
|
+
is_public_mode=IS_PUBLIC_MODE,
|
|
1416
1424
|
engine_status=engine_status,
|
|
1417
1425
|
get_current_user=get_current_user,
|
|
1418
1426
|
require_auth=REQUIRE_AUTH,
|
|
1419
1427
|
app_version=APP_VERSION,
|
|
1420
1428
|
app_mode=APP_MODE,
|
|
1421
|
-
create_models_router=create_models_router,
|
|
1422
|
-
model_router=router,
|
|
1423
1429
|
require_user=require_user,
|
|
1424
1430
|
load_users=load_users,
|
|
1425
1431
|
get_user_role=get_user_role,
|
|
@@ -1438,7 +1444,6 @@ def _build(config: "Optional[Config]" = None) -> Dict[str, Any]:
|
|
|
1438
1444
|
engine_model_catalog=ENGINE_MODEL_CATALOG,
|
|
1439
1445
|
model_engine_aliases=MODEL_ENGINE_ALIASES,
|
|
1440
1446
|
cloud_verify_ttl_seconds=CLOUD_VERIFY_TTL_SECONDS,
|
|
1441
|
-
is_public_mode=IS_PUBLIC_MODE,
|
|
1442
1447
|
allow_local_models=ALLOW_LOCAL_MODELS,
|
|
1443
1448
|
)
|
|
1444
1449
|
|
|
@@ -1461,7 +1466,7 @@ def _build(config: "Optional[Config]" = None) -> Dict[str, Any]:
|
|
|
1461
1466
|
return None
|
|
1462
1467
|
return PLATFORM.allowed_scopes(user)
|
|
1463
1468
|
|
|
1464
|
-
tool_router_context =
|
|
1469
|
+
tool_router_context, interaction_router_context = build_interaction_contexts(
|
|
1465
1470
|
config=CONFIG,
|
|
1466
1471
|
ingestion_pipeline=INGESTION_PIPELINE,
|
|
1467
1472
|
data_dir=DATA_DIR,
|
|
@@ -1485,15 +1490,10 @@ def _build(config: "Optional[Config]" = None) -> Dict[str, Any]:
|
|
|
1485
1490
|
install_mcp=install_mcp,
|
|
1486
1491
|
mcp_public_item=mcp_public_item,
|
|
1487
1492
|
hooks=HOOKS_REGISTRY,
|
|
1488
|
-
)
|
|
1489
|
-
interaction_router_context = InteractionRouterContext(
|
|
1490
1493
|
chat_context=context,
|
|
1491
1494
|
search_service=SEARCH_SERVICE,
|
|
1492
1495
|
allowed_workspaces_for=_allowed_workspaces_for,
|
|
1493
|
-
require_user=require_user,
|
|
1494
1496
|
embedding_info=_embedding_info,
|
|
1495
|
-
tool_context=tool_router_context,
|
|
1496
|
-
hooks=HOOKS_REGISTRY,
|
|
1497
1497
|
agent_registry=AGENT_REGISTRY,
|
|
1498
1498
|
memory_service=MEMORY_SERVICE,
|
|
1499
1499
|
platform=PLATFORM,
|
|
@@ -1510,25 +1510,17 @@ def _build(config: "Optional[Config]" = None) -> Dict[str, Any]:
|
|
|
1510
1510
|
)
|
|
1511
1511
|
|
|
1512
1512
|
from latticeai.api.review_queue import create_review_queue_router
|
|
1513
|
+
run_review_item = build_review_run_now_runner(PLATFORM, HTTPException)
|
|
1513
1514
|
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
wf_id = (item.get("payload") or {}).get("workflow_id") or (item.get("provenance") or {}).get("workflow_id")
|
|
1517
|
-
if not wf_id:
|
|
1518
|
-
raise HTTPException(status_code=409, detail="review item has no workflow to run")
|
|
1519
|
-
return PLATFORM.run_workflow_by_id(
|
|
1520
|
-
wf_id, user_email, scope, with_agent=False,
|
|
1521
|
-
inputs={"__review_item__": item.get("id")},
|
|
1522
|
-
)
|
|
1523
|
-
|
|
1524
|
-
BRAIN_NETWORK = register_review_and_brain_tail_routers(
|
|
1525
|
-
app,
|
|
1515
|
+
BRAIN_NETWORK = register_tail_runtime_routers(
|
|
1516
|
+
app=app,
|
|
1526
1517
|
create_review_queue_router=create_review_queue_router,
|
|
1518
|
+
register_review_and_brain_tail_routers=register_review_and_brain_tail_routers,
|
|
1527
1519
|
review_queue=REVIEW_QUEUE,
|
|
1528
1520
|
require_user=require_user,
|
|
1529
1521
|
gate_read=PLATFORM.gate_read,
|
|
1530
1522
|
gate_write=PLATFORM.gate_write,
|
|
1531
|
-
run_review_item=
|
|
1523
|
+
run_review_item=run_review_item,
|
|
1532
1524
|
append_audit_event=append_audit_event,
|
|
1533
1525
|
create_browser_router=create_browser_router,
|
|
1534
1526
|
ingestion_pipeline=INGESTION_PIPELINE,
|
|
@@ -19,7 +19,7 @@ from pathlib import Path
|
|
|
19
19
|
from typing import Any, Callable, Dict, Iterable, List, Optional
|
|
20
20
|
|
|
21
21
|
|
|
22
|
-
WORKSPACE_OS_VERSION = "6.
|
|
22
|
+
WORKSPACE_OS_VERSION = "6.3.0"
|
|
23
23
|
|
|
24
24
|
# Workspace types separate single-user Personal workspaces from shared
|
|
25
25
|
# Organization workspaces. Both keep the same local-first JSON store; the type
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
"""Chat and interaction wiring seam for ``latticeai.app_factory``."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any, Optional
|
|
6
|
+
|
|
7
|
+
from latticeai.services.router_context import InteractionRouterContext, ToolRouterContext
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def build_chat_agent_runtime_from_context(
|
|
11
|
+
*,
|
|
12
|
+
build_agent_runtime: Any,
|
|
13
|
+
model_router: Any,
|
|
14
|
+
execute_tool: Any,
|
|
15
|
+
recent_chat_context: Any,
|
|
16
|
+
clear_history: Any,
|
|
17
|
+
knowledge_save: Any,
|
|
18
|
+
audit: Any,
|
|
19
|
+
hooks: Any,
|
|
20
|
+
brain_memory: Any,
|
|
21
|
+
) -> Any:
|
|
22
|
+
return build_agent_runtime(
|
|
23
|
+
model_router=model_router,
|
|
24
|
+
execute_tool=execute_tool,
|
|
25
|
+
recent_chat_context=recent_chat_context,
|
|
26
|
+
clear_history=clear_history,
|
|
27
|
+
knowledge_save=knowledge_save,
|
|
28
|
+
audit=audit,
|
|
29
|
+
hooks=hooks,
|
|
30
|
+
brain_memory=brain_memory,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def build_interaction_contexts(
|
|
35
|
+
*,
|
|
36
|
+
config: Any,
|
|
37
|
+
ingestion_pipeline: Any,
|
|
38
|
+
data_dir: Any,
|
|
39
|
+
static_dir: Any,
|
|
40
|
+
model_router: Any,
|
|
41
|
+
require_user: Any,
|
|
42
|
+
require_admin: Any,
|
|
43
|
+
get_current_user: Any,
|
|
44
|
+
clear_history: Any,
|
|
45
|
+
append_audit_event: Any,
|
|
46
|
+
enforce_rate_limit: Any,
|
|
47
|
+
bytes_match_extension: Any,
|
|
48
|
+
classify_sensitive_message: Any,
|
|
49
|
+
save_to_history: Any,
|
|
50
|
+
enable_graph: bool,
|
|
51
|
+
knowledge_graph: Any,
|
|
52
|
+
require_graph: Any,
|
|
53
|
+
local_kg_watcher: Any,
|
|
54
|
+
load_mcp_installs: Any,
|
|
55
|
+
recommend_mcps: Any,
|
|
56
|
+
install_mcp: Any,
|
|
57
|
+
mcp_public_item: Any,
|
|
58
|
+
hooks: Any,
|
|
59
|
+
chat_context: Any,
|
|
60
|
+
search_service: Any,
|
|
61
|
+
allowed_workspaces_for: Any,
|
|
62
|
+
embedding_info: Any,
|
|
63
|
+
agent_registry: Any,
|
|
64
|
+
memory_service: Any,
|
|
65
|
+
platform: Any,
|
|
66
|
+
) -> tuple[ToolRouterContext, InteractionRouterContext]:
|
|
67
|
+
tool_router_context = ToolRouterContext(
|
|
68
|
+
config=config,
|
|
69
|
+
ingestion_pipeline=ingestion_pipeline,
|
|
70
|
+
data_dir=data_dir,
|
|
71
|
+
static_dir=static_dir,
|
|
72
|
+
model_router=model_router,
|
|
73
|
+
require_user=require_user,
|
|
74
|
+
require_admin=require_admin,
|
|
75
|
+
get_current_user=get_current_user,
|
|
76
|
+
clear_history=clear_history,
|
|
77
|
+
append_audit_event=append_audit_event,
|
|
78
|
+
enforce_rate_limit=enforce_rate_limit,
|
|
79
|
+
bytes_match_extension=bytes_match_extension,
|
|
80
|
+
classify_sensitive_message=classify_sensitive_message,
|
|
81
|
+
save_to_history=save_to_history,
|
|
82
|
+
enable_graph=enable_graph,
|
|
83
|
+
knowledge_graph=knowledge_graph,
|
|
84
|
+
require_graph=require_graph,
|
|
85
|
+
local_kg_watcher=local_kg_watcher,
|
|
86
|
+
load_mcp_installs=load_mcp_installs,
|
|
87
|
+
recommend_mcps=recommend_mcps,
|
|
88
|
+
install_mcp=install_mcp,
|
|
89
|
+
mcp_public_item=mcp_public_item,
|
|
90
|
+
hooks=hooks,
|
|
91
|
+
)
|
|
92
|
+
interaction_router_context = InteractionRouterContext(
|
|
93
|
+
chat_context=chat_context,
|
|
94
|
+
search_service=search_service,
|
|
95
|
+
allowed_workspaces_for=allowed_workspaces_for,
|
|
96
|
+
require_user=require_user,
|
|
97
|
+
embedding_info=embedding_info,
|
|
98
|
+
tool_context=tool_router_context,
|
|
99
|
+
hooks=hooks,
|
|
100
|
+
agent_registry=agent_registry,
|
|
101
|
+
memory_service=memory_service,
|
|
102
|
+
platform=platform,
|
|
103
|
+
)
|
|
104
|
+
return tool_router_context, interaction_router_context
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def maybe_build_telegram_chat_mirror(
|
|
108
|
+
*,
|
|
109
|
+
enable_telegram: bool,
|
|
110
|
+
spawn: Any,
|
|
111
|
+
) -> Optional[Any]:
|
|
112
|
+
if not enable_telegram:
|
|
113
|
+
return None
|
|
114
|
+
|
|
115
|
+
def telegram_chat_mirror(role: str, text: str, source: Optional[str] = None) -> None:
|
|
116
|
+
from latticeai.integrations.telegram_bot import broadcast_web_chat
|
|
117
|
+
spawn(broadcast_web_chat(role, text), name="telegram_broadcast")
|
|
118
|
+
|
|
119
|
+
return telegram_chat_mirror
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"""Model/runtime wiring seam for ``latticeai.app_factory``."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from latticeai.runtime.platform_services_runtime import build_model_service
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def configure_model_runtime_from_context(**kwargs: Any) -> None:
|
|
11
|
+
configure_model_runtime = kwargs.pop("configure_model_runtime")
|
|
12
|
+
configure_model_runtime(**kwargs)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def register_model_runtime_routers(
|
|
16
|
+
*,
|
|
17
|
+
app: Any,
|
|
18
|
+
create_health_router: Any,
|
|
19
|
+
create_models_router: Any,
|
|
20
|
+
register_health_and_model_routers: Any,
|
|
21
|
+
model_router: Any,
|
|
22
|
+
runtime_features: Any,
|
|
23
|
+
is_public_mode: bool,
|
|
24
|
+
**kwargs: Any,
|
|
25
|
+
) -> Any:
|
|
26
|
+
model_service = build_model_service(
|
|
27
|
+
model_router=model_router,
|
|
28
|
+
runtime_features=runtime_features,
|
|
29
|
+
is_public=is_public_mode,
|
|
30
|
+
)
|
|
31
|
+
register_health_and_model_routers(
|
|
32
|
+
app,
|
|
33
|
+
create_health_router=create_health_router,
|
|
34
|
+
model_service=model_service,
|
|
35
|
+
create_models_router=create_models_router,
|
|
36
|
+
model_router=model_router,
|
|
37
|
+
is_public_mode=is_public_mode,
|
|
38
|
+
**kwargs,
|
|
39
|
+
)
|
|
40
|
+
return model_service
|
|
@@ -9,9 +9,6 @@ from __future__ import annotations
|
|
|
9
9
|
|
|
10
10
|
from typing import Any, Callable, Dict
|
|
11
11
|
|
|
12
|
-
from latticeai.runtime.automation_runtime import build_automation_runtime
|
|
13
|
-
from latticeai.services.platform_runtime import PlatformRuntime
|
|
14
|
-
|
|
15
12
|
|
|
16
13
|
def build_platform_automation_runtime(
|
|
17
14
|
*,
|
|
@@ -34,6 +31,8 @@ def build_platform_automation_runtime(
|
|
|
34
31
|
``app_factory._build`` so ``dict(locals())`` continues to expose the legacy
|
|
35
32
|
``server_app`` compatibility surface.
|
|
36
33
|
"""
|
|
34
|
+
from latticeai.runtime.automation_runtime import build_automation_runtime
|
|
35
|
+
from latticeai.services.platform_runtime import PlatformRuntime
|
|
37
36
|
|
|
38
37
|
def _llm_generate_sync(
|
|
39
38
|
message: str,
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"""Review Center runtime wiring helpers."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any, Callable, Dict, Optional
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def build_review_run_now_runner(platform: Any, http_exception: type[Exception]) -> Callable[..., Any]:
|
|
9
|
+
"""Build the Review Center run-now runner used by the API router.
|
|
10
|
+
|
|
11
|
+
The runner preserves the public contract: "Run now" previews/regenerates the
|
|
12
|
+
source workflow, records a fresh run id, and leaves approval status unchanged.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
def run_review_item(
|
|
16
|
+
item: Dict[str, Any],
|
|
17
|
+
*,
|
|
18
|
+
user_email: Optional[str],
|
|
19
|
+
scope: Optional[str],
|
|
20
|
+
) -> Any:
|
|
21
|
+
payload = item.get("payload") or {}
|
|
22
|
+
provenance = item.get("provenance") or {}
|
|
23
|
+
workflow_id = payload.get("workflow_id") or provenance.get("workflow_id")
|
|
24
|
+
if not workflow_id:
|
|
25
|
+
raise http_exception(status_code=409, detail="review item has no workflow to run")
|
|
26
|
+
return platform.run_workflow_by_id(
|
|
27
|
+
workflow_id,
|
|
28
|
+
user_email,
|
|
29
|
+
scope,
|
|
30
|
+
with_agent=False,
|
|
31
|
+
inputs={"__review_item__": item.get("id")},
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
return run_review_item
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
__all__ = ["build_review_run_now_runner"]
|