ltcai 7.9.0 → 8.1.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 +35 -49
- package/docs/CHANGELOG.md +35 -0
- package/docs/V3_FRONTEND.md +11 -5
- package/lattice_brain/__init__.py +1 -1
- package/lattice_brain/embeddings.py +9 -1
- package/lattice_brain/graph/projection.py +19 -5
- package/lattice_brain/graph/provenance.py +26 -4
- package/lattice_brain/graph/write_master.py +30 -3
- package/lattice_brain/runtime/multi_agent.py +1 -1
- package/latticeai/__init__.py +1 -1
- package/latticeai/app_factory.py +13 -134
- package/latticeai/core/local_embeddings.py +8 -0
- package/latticeai/core/marketplace.py +1 -1
- package/latticeai/core/mcp_registry.py +140 -1
- package/latticeai/core/tool_registry.py +7 -0
- package/latticeai/core/workspace_os.py +1 -1
- package/latticeai/services/architecture_readiness.py +50 -6
- package/latticeai/services/model_runtime.py +36 -2
- package/latticeai/services/product_readiness.py +19 -17
- package/package.json +2 -2
- package/scripts/build_frontend_assets.mjs +11 -2
- 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 +11 -102
- package/static/app/assets/Act-BOO66G-c.js +1 -0
- package/static/app/assets/Brain-C6lEYiD7.js +321 -0
- package/static/app/assets/{Capture-B9Tlhzqr.js → Capture-TATXBRDw.js} +1 -2
- package/static/app/assets/Library-DK4FIp8a.js +1 -0
- package/static/app/assets/System-Bgs6Ql7x.js +1 -0
- package/static/app/assets/core-CwxXejkd.js +1 -2
- package/static/app/assets/index-Bvv79nre.js +16 -0
- package/static/app/assets/index-Dslqglia.css +2 -0
- package/static/app/assets/primitives-B70WOra0.js +1 -0
- package/static/app/assets/{textarea-BZk6ybp5.js → textarea-Czrd9gwM.js} +1 -2
- package/static/app/index.html +3 -10
- package/static/app/theme-boot.js +8 -0
- package/static/sw.js +1 -1
- package/frontend/index.html +0 -24
- package/frontend/openapi.json +0 -15425
- package/frontend/src/App.tsx +0 -243
- package/frontend/src/api/client.ts +0 -580
- package/frontend/src/api/openapi.ts +0 -17892
- package/frontend/src/components/AdminAccessGate.tsx +0 -70
- package/frontend/src/components/FeedbackState.tsx +0 -45
- package/frontend/src/components/LanguageSwitcher.tsx +0 -23
- package/frontend/src/components/LivingBrain.tsx +0 -220
- package/frontend/src/components/ProductFlow.tsx +0 -171
- package/frontend/src/components/WorkspaceProfileSwitcher.tsx +0 -176
- package/frontend/src/components/onboarding/AnalysisScreen.tsx +0 -135
- package/frontend/src/components/onboarding/DownloadConsentPanel.tsx +0 -29
- package/frontend/src/components/onboarding/InstallScreen.tsx +0 -263
- package/frontend/src/components/onboarding/LanguageChooser.tsx +0 -23
- package/frontend/src/components/onboarding/LoginScreen.tsx +0 -131
- package/frontend/src/components/onboarding/ProductFlowScreens.tsx +0 -13
- package/frontend/src/components/onboarding/RecommendationScreen.tsx +0 -116
- package/frontend/src/components/onboarding/recommendationModel.ts +0 -189
- package/frontend/src/components/primitives.tsx +0 -392
- package/frontend/src/components/ui/badge.tsx +0 -27
- package/frontend/src/components/ui/button.tsx +0 -37
- package/frontend/src/components/ui/card.tsx +0 -22
- package/frontend/src/components/ui/input.tsx +0 -16
- package/frontend/src/components/ui/textarea.tsx +0 -16
- package/frontend/src/features/admin/AdminConsole.tsx +0 -334
- package/frontend/src/features/brain/BrainCarePanel.tsx +0 -254
- package/frontend/src/features/brain/BrainComposer.tsx +0 -85
- package/frontend/src/features/brain/BrainConversation.tsx +0 -688
- package/frontend/src/features/brain/BrainGraphLayer.tsx +0 -365
- package/frontend/src/features/brain/BrainHome.tsx +0 -624
- package/frontend/src/features/brain/BrainMemoryLayer.tsx +0 -45
- package/frontend/src/features/brain/BrainOverviewPanel.tsx +0 -149
- package/frontend/src/features/brain/BrainRelationshipLayer.tsx +0 -43
- package/frontend/src/features/brain/DepthEmergence.tsx +0 -53
- package/frontend/src/features/brain/brainData.ts +0 -246
- package/frontend/src/features/brain/graphLayout.ts +0 -37
- package/frontend/src/features/brain/types.ts +0 -150
- package/frontend/src/features/review/ReviewCard.tsx +0 -100
- package/frontend/src/features/review/ReviewInbox.tsx +0 -127
- package/frontend/src/features/review/reviewHelpers.ts +0 -69
- package/frontend/src/i18n.ts +0 -1303
- package/frontend/src/lib/utils.ts +0 -33
- package/frontend/src/main.tsx +0 -23
- package/frontend/src/pages/Act.tsx +0 -458
- package/frontend/src/pages/Ask.tsx +0 -14
- package/frontend/src/pages/Brain.tsx +0 -914
- package/frontend/src/pages/Capture.tsx +0 -258
- package/frontend/src/pages/Library.tsx +0 -486
- package/frontend/src/pages/System.tsx +0 -621
- package/frontend/src/routes.ts +0 -92
- package/frontend/src/store/appStore.ts +0 -94
- package/frontend/src/styles.css +0 -6579
- package/static/app/assets/Act-DOvf59ru.js +0 -2
- package/static/app/assets/Act-DOvf59ru.js.map +0 -1
- package/static/app/assets/Brain-C7_0mEiI.js +0 -322
- package/static/app/assets/Brain-C7_0mEiI.js.map +0 -1
- package/static/app/assets/Capture-B9Tlhzqr.js.map +0 -1
- package/static/app/assets/Library-BJPEEm5O.js +0 -2
- package/static/app/assets/Library-BJPEEm5O.js.map +0 -1
- package/static/app/assets/System-D6t9jo9V.js +0 -2
- package/static/app/assets/System-D6t9jo9V.js.map +0 -1
- package/static/app/assets/core-CwxXejkd.js.map +0 -1
- package/static/app/assets/index-C7g26IF6.css +0 -2
- package/static/app/assets/index-DbcEYJQ2.js +0 -17
- package/static/app/assets/index-DbcEYJQ2.js.map +0 -1
- package/static/app/assets/primitives-CD38lt4n.js +0 -2
- package/static/app/assets/primitives-CD38lt4n.js.map +0 -1
- package/static/app/assets/textarea-BZk6ybp5.js.map +0 -1
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import { clsx, type ClassValue } from "clsx";
|
|
2
|
-
import { twMerge } from "tailwind-merge";
|
|
3
|
-
|
|
4
|
-
export function cn(...inputs: ClassValue[]) {
|
|
5
|
-
return twMerge(clsx(inputs));
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export function fmtNumber(value: unknown, fallback = "0") {
|
|
9
|
-
const n = Number(value);
|
|
10
|
-
if (!Number.isFinite(n)) return fallback;
|
|
11
|
-
return new Intl.NumberFormat().format(n);
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export function pct(value: unknown) {
|
|
15
|
-
const n = Number(value);
|
|
16
|
-
if (!Number.isFinite(n)) return "0%";
|
|
17
|
-
return `${Math.round(n * 100)}%`;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export function shortId(value: unknown, length = 10) {
|
|
21
|
-
const text = String(value || "");
|
|
22
|
-
return text.length > length ? `${text.slice(0, length)}...` : text;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export function asArray<T = Record<string, unknown>>(value: unknown): T[] {
|
|
26
|
-
return Array.isArray(value) ? (value as T[]) : [];
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export function titleize(value: unknown) {
|
|
30
|
-
return String(value || "")
|
|
31
|
-
.replace(/[_-]+/g, " ")
|
|
32
|
-
.replace(/\b\w/g, (m) => m.toUpperCase());
|
|
33
|
-
}
|
package/frontend/src/main.tsx
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import React from "react";
|
|
2
|
-
import ReactDOM from "react-dom/client";
|
|
3
|
-
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
|
4
|
-
import App from "./App";
|
|
5
|
-
import "./styles.css";
|
|
6
|
-
|
|
7
|
-
const queryClient = new QueryClient({
|
|
8
|
-
defaultOptions: {
|
|
9
|
-
queries: {
|
|
10
|
-
staleTime: 15_000,
|
|
11
|
-
refetchOnWindowFocus: false,
|
|
12
|
-
retry: 1,
|
|
13
|
-
},
|
|
14
|
-
},
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
ReactDOM.createRoot(document.getElementById("root")!).render(
|
|
18
|
-
<React.StrictMode>
|
|
19
|
-
<QueryClientProvider client={queryClient}>
|
|
20
|
-
<App />
|
|
21
|
-
</QueryClientProvider>
|
|
22
|
-
</React.StrictMode>,
|
|
23
|
-
);
|
|
@@ -1,458 +0,0 @@
|
|
|
1
|
-
import * as React from "react";
|
|
2
|
-
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
|
3
|
-
import ReactFlow, { Background, Controls, Edge, Node } from "reactflow";
|
|
4
|
-
import { Bot, CalendarClock, GitBranch, PauseCircle, Play, ShieldCheck, Workflow } from "lucide-react";
|
|
5
|
-
import { latticeApi } from "@/api/client";
|
|
6
|
-
import { ActionButton, DataPanel, EntityList, KeyValueList, ModeGate, OperationResult, StructuredView, Tabs } from "@/components/primitives";
|
|
7
|
-
import { Badge } from "@/components/ui/badge";
|
|
8
|
-
import { Button } from "@/components/ui/button";
|
|
9
|
-
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
|
10
|
-
import { Input } from "@/components/ui/input";
|
|
11
|
-
import { Textarea } from "@/components/ui/textarea";
|
|
12
|
-
import { ReviewInbox } from "@/features/review/ReviewInbox";
|
|
13
|
-
import { useAppStore } from "@/store/appStore";
|
|
14
|
-
import { asArray, shortId } from "@/lib/utils";
|
|
15
|
-
import { t } from "@/i18n";
|
|
16
|
-
|
|
17
|
-
type ActTab = "agents" | "runs" | "workflows" | "hooks" | "tools";
|
|
18
|
-
type RunsSubTab = "runs" | "review";
|
|
19
|
-
|
|
20
|
-
const runsSubTabs: Array<{ id: RunsSubTab; labelKey: string }> = [
|
|
21
|
-
{ id: "runs", labelKey: "act.tab.runs" },
|
|
22
|
-
{ id: "review", labelKey: "act.tab.review" },
|
|
23
|
-
];
|
|
24
|
-
|
|
25
|
-
const tabs: Array<{ id: ActTab; labelKey: string; advancedLabelKey?: string }> = [
|
|
26
|
-
{ id: "agents", labelKey: "act.tab.goals" },
|
|
27
|
-
{ id: "runs", labelKey: "act.tab.runs" },
|
|
28
|
-
{ id: "workflows", labelKey: "act.tab.recipes" },
|
|
29
|
-
{ id: "hooks", labelKey: "act.tab.safeguards", advancedLabelKey: "act.tab.hooks" },
|
|
30
|
-
{ id: "tools", labelKey: "act.tab.permissions", advancedLabelKey: "act.tab.tools" },
|
|
31
|
-
];
|
|
32
|
-
|
|
33
|
-
export function ActPage({ initialTab }: { initialTab?: string }) {
|
|
34
|
-
const mode = useAppStore((state) => state.mode);
|
|
35
|
-
const language = useAppStore((state) => state.language);
|
|
36
|
-
const [tab, setTab] = React.useState<ActTab>(() => {
|
|
37
|
-
if (initialTab === "review") return "runs";
|
|
38
|
-
return (initialTab as ActTab) || "agents";
|
|
39
|
-
});
|
|
40
|
-
const [runsSubTab, setRunsSubTab] = React.useState<RunsSubTab>(initialTab === "review" ? "review" : "runs");
|
|
41
|
-
React.useEffect(() => {
|
|
42
|
-
if (initialTab === "review") {
|
|
43
|
-
setTab("runs");
|
|
44
|
-
setRunsSubTab("review");
|
|
45
|
-
return;
|
|
46
|
-
}
|
|
47
|
-
if (tabs.some((item) => item.id === initialTab)) setTab(initialTab as ActTab);
|
|
48
|
-
}, [initialTab]);
|
|
49
|
-
return (
|
|
50
|
-
<div className="space-y-5">
|
|
51
|
-
<header className="page-hero">
|
|
52
|
-
<div className="page-kicker"><Workflow className="h-4 w-4" /> {t(language, "act.kicker")}</div>
|
|
53
|
-
<h1 className="page-title">{t(language, "act.title")}</h1>
|
|
54
|
-
<p className="page-copy">{t(language, "act.copy")}</p>
|
|
55
|
-
</header>
|
|
56
|
-
<Tabs
|
|
57
|
-
tabs={tabs.map((item) => ({
|
|
58
|
-
id: item.id,
|
|
59
|
-
label: t(language, mode === "basic" || !item.advancedLabelKey ? item.labelKey : item.advancedLabelKey),
|
|
60
|
-
}))}
|
|
61
|
-
value={tab}
|
|
62
|
-
onChange={(id) => setTab(id as ActTab)}
|
|
63
|
-
/>
|
|
64
|
-
{tab === "agents" ? <AgentsPanel /> : null}
|
|
65
|
-
{tab === "runs" ? <RunsPanel subTab={runsSubTab} onSubTabChange={setRunsSubTab} /> : null}
|
|
66
|
-
{tab === "workflows" ? <WorkflowsPanel /> : null}
|
|
67
|
-
{tab === "hooks" ? <HooksPanel /> : null}
|
|
68
|
-
{tab === "tools" ? <ToolsPanel /> : null}
|
|
69
|
-
</div>
|
|
70
|
-
);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
function AgentsPanel() {
|
|
74
|
-
const qc = useQueryClient();
|
|
75
|
-
const mode = useAppStore((state) => state.mode);
|
|
76
|
-
const [goal, setGoal] = React.useState("");
|
|
77
|
-
const runtime = useQuery({ queryKey: ["agentRuntime"], queryFn: latticeApi.agentRuntime });
|
|
78
|
-
const registry = useQuery({ queryKey: ["agentRegistry"], queryFn: latticeApi.agentRegistry });
|
|
79
|
-
const caps = useQuery({ queryKey: ["agentCapabilities"], queryFn: latticeApi.agentCapabilities });
|
|
80
|
-
const run = useMutation({
|
|
81
|
-
mutationFn: () => latticeApi.runAgent(goal, ["planner", "executor", "reviewer"]),
|
|
82
|
-
onSuccess: () => qc.invalidateQueries({ queryKey: ["agentRuntime"] }),
|
|
83
|
-
});
|
|
84
|
-
const [agentName, setAgentName] = React.useState("");
|
|
85
|
-
const register = useMutation({
|
|
86
|
-
mutationFn: () => latticeApi.registerAgent({ name: agentName, type: "custom", capabilities: [] }),
|
|
87
|
-
onSuccess: () => qc.invalidateQueries({ queryKey: ["agentRegistry"] }),
|
|
88
|
-
});
|
|
89
|
-
const runtimeData = (runtime.data?.data || {}) as Record<string, unknown>;
|
|
90
|
-
const runtimeMeta = (runtimeData.runtime || {}) as Record<string, unknown>;
|
|
91
|
-
const runtimeReady = Boolean(runtimeMeta.ready);
|
|
92
|
-
const runtimeReason = mode === "basic" ? "Load a local model before running agents." : String(runtimeMeta.unavailable_reason || "Load an LLM-backed model before running agents.");
|
|
93
|
-
const canRunAgent = Boolean(goal.trim()) && runtimeReady && !run.isPending;
|
|
94
|
-
return (
|
|
95
|
-
<div className="grid gap-4 xl:grid-cols-[0.9fr_1.1fr]">
|
|
96
|
-
<Card>
|
|
97
|
-
<CardHeader>
|
|
98
|
-
<CardTitle className="flex items-center gap-2"><Bot className="h-4 w-4" /> Start with a goal</CardTitle>
|
|
99
|
-
<CardDescription>Lattice will plan, execute, and review only when the local model is ready.</CardDescription>
|
|
100
|
-
</CardHeader>
|
|
101
|
-
<CardContent className="space-y-3">
|
|
102
|
-
<Textarea value={goal} onChange={(e) => setGoal(e.target.value)} placeholder="What should Lattice help you accomplish?" />
|
|
103
|
-
{!runtimeReady ? <Badge variant="warning">{runtimeReason}</Badge> : null}
|
|
104
|
-
<Button
|
|
105
|
-
className="w-full"
|
|
106
|
-
variant={runtimeReady ? "default" : "outline"}
|
|
107
|
-
disabled={!canRunAgent}
|
|
108
|
-
onClick={() => run.mutate()}
|
|
109
|
-
>
|
|
110
|
-
<Play className="h-4 w-4" /> {runtimeReady ? "Start Run" : "Load a model first"}
|
|
111
|
-
</Button>
|
|
112
|
-
{run.data ? <OperationResult result={run.data} successLabel="Agent run request completed" /> : null}
|
|
113
|
-
</CardContent>
|
|
114
|
-
</Card>
|
|
115
|
-
<DataPanel title="Readiness" result={runtime.data}>
|
|
116
|
-
{(data) => mode === "basic" ? (
|
|
117
|
-
<div className="grid gap-3 sm:grid-cols-3">
|
|
118
|
-
<div className="rounded-lg border border-border bg-background/55 p-3">
|
|
119
|
-
<div className="text-sm font-medium">Model</div>
|
|
120
|
-
<Badge variant={runtimeReady ? "success" : "warning"}>{runtimeReady ? "ready" : "needed"}</Badge>
|
|
121
|
-
</div>
|
|
122
|
-
<div className="rounded-lg border border-border bg-background/55 p-3">
|
|
123
|
-
<div className="text-sm font-medium">Planner</div>
|
|
124
|
-
<Badge variant="muted">{runtimeReady ? "available" : "waiting"}</Badge>
|
|
125
|
-
</div>
|
|
126
|
-
<div className="rounded-lg border border-border bg-background/55 p-3">
|
|
127
|
-
<div className="text-sm font-medium">Review</div>
|
|
128
|
-
<Badge variant="success">approval required</Badge>
|
|
129
|
-
</div>
|
|
130
|
-
</div>
|
|
131
|
-
) : <StructuredView value={data} />}
|
|
132
|
-
</DataPanel>
|
|
133
|
-
<DataPanel title="Agent team" result={registry.data}>
|
|
134
|
-
{(data) => (
|
|
135
|
-
<div className="space-y-3">
|
|
136
|
-
<EntityList items={(data as Record<string, unknown>).agents} titleKey="name" metaKey="type" />
|
|
137
|
-
<div className="flex gap-2">
|
|
138
|
-
<Input value={agentName} onChange={(e) => setAgentName(e.target.value)} placeholder="New custom agent name" />
|
|
139
|
-
<Button disabled={!agentName.trim() || register.isPending} onClick={() => register.mutate()}>Register</Button>
|
|
140
|
-
</div>
|
|
141
|
-
</div>
|
|
142
|
-
)}
|
|
143
|
-
</DataPanel>
|
|
144
|
-
<DataPanel title={mode === "basic" ? "What Lattice can do" : "What agents can do"} result={caps.data}>
|
|
145
|
-
{(data) => <StructuredView value={data} />}
|
|
146
|
-
</DataPanel>
|
|
147
|
-
</div>
|
|
148
|
-
);
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
function RunsPanel({ subTab, onSubTabChange }: { subTab: RunsSubTab; onSubTabChange: (tab: RunsSubTab) => void }) {
|
|
152
|
-
const language = useAppStore((state) => state.language);
|
|
153
|
-
return (
|
|
154
|
-
<div className="space-y-4">
|
|
155
|
-
<Tabs
|
|
156
|
-
tabs={runsSubTabs.map((item) => ({ id: item.id, label: t(language, item.labelKey) }))}
|
|
157
|
-
value={subTab}
|
|
158
|
-
onChange={(id) => onSubTabChange(id as RunsSubTab)}
|
|
159
|
-
/>
|
|
160
|
-
{subTab === "runs" ? <RunsListPanel /> : <ReviewInbox />}
|
|
161
|
-
</div>
|
|
162
|
-
);
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
function RunsListPanel() {
|
|
166
|
-
const mode = useAppStore((state) => state.mode);
|
|
167
|
-
const runtime = useQuery({ queryKey: ["agentRuntime"], queryFn: latticeApi.agentRuntime });
|
|
168
|
-
const workflows = useQuery({ queryKey: ["workflowRuns"], queryFn: latticeApi.workflowRuns });
|
|
169
|
-
const pending = useQuery({ queryKey: ["permissions"], queryFn: latticeApi.permissionsPending });
|
|
170
|
-
const agentRuns = asArray<Record<string, unknown>>((runtime.data?.data as Record<string, unknown>)?.runs);
|
|
171
|
-
const workflowRuns = asArray<Record<string, unknown>>((workflows.data?.data as Record<string, unknown>)?.runs);
|
|
172
|
-
return (
|
|
173
|
-
<div className="grid gap-4 xl:grid-cols-2">
|
|
174
|
-
<DataPanel title="Agent runs" result={runtime.data}>
|
|
175
|
-
{() => <RunList runs={agentRuns} kind="agent" />}
|
|
176
|
-
</DataPanel>
|
|
177
|
-
<DataPanel title="Workflow runs" result={workflows.data}>
|
|
178
|
-
{() => <RunList runs={workflowRuns} kind="workflow" />}
|
|
179
|
-
</DataPanel>
|
|
180
|
-
<DataPanel title="Approval inbox" result={pending.data} className="xl:col-span-2">
|
|
181
|
-
{(data) => {
|
|
182
|
-
const pendingMap = ((data as Record<string, unknown>).pending || {}) as Record<string, unknown>;
|
|
183
|
-
const rows = Object.entries(pendingMap);
|
|
184
|
-
return rows.length ? (
|
|
185
|
-
<div className="grid gap-2">
|
|
186
|
-
{rows.map(([token, value], index) => (
|
|
187
|
-
<div key={token} className="flex flex-wrap items-center justify-between gap-3 rounded-md border border-border p-3">
|
|
188
|
-
<div>
|
|
189
|
-
<div className="font-medium">{mode === "basic" ? `Approval request ${index + 1}` : shortId(token, 16)}</div>
|
|
190
|
-
<div className="mt-2">
|
|
191
|
-
<KeyValueList data={(value || {}) as Record<string, unknown>} limit={5} />
|
|
192
|
-
</div>
|
|
193
|
-
</div>
|
|
194
|
-
<div className="flex gap-2">
|
|
195
|
-
<ActionButton label="Approve" action={() => latticeApi.approvePermission(token)} invalidate={["permissions"]} />
|
|
196
|
-
<ActionButton label="Deny" action={() => latticeApi.denyPermission(token)} invalidate={["permissions"]} variant="destructive" />
|
|
197
|
-
</div>
|
|
198
|
-
</div>
|
|
199
|
-
))}
|
|
200
|
-
</div>
|
|
201
|
-
) : <EntityList items={[]} />;
|
|
202
|
-
}}
|
|
203
|
-
</DataPanel>
|
|
204
|
-
</div>
|
|
205
|
-
);
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
function RunList({ runs, kind }: { runs: Array<Record<string, unknown>>; kind: "agent" | "workflow" }) {
|
|
209
|
-
if (!runs.length) return <EntityList items={[]} />;
|
|
210
|
-
return (
|
|
211
|
-
<div className="grid gap-2">
|
|
212
|
-
{runs.slice(0, 10).map((run) => {
|
|
213
|
-
const id = String(run.run_id || run.id);
|
|
214
|
-
const status = String(run.status || "unknown");
|
|
215
|
-
return (
|
|
216
|
-
<div key={id} className="rounded-md border border-border bg-background p-3">
|
|
217
|
-
<div className="flex flex-wrap items-center justify-between gap-2">
|
|
218
|
-
<div className="font-medium">{shortId(id, 18)}</div>
|
|
219
|
-
<Badge variant={status === "succeeded" ? "success" : status === "awaiting_approval" ? "warning" : "muted"}>{status}</Badge>
|
|
220
|
-
</div>
|
|
221
|
-
<div className="mt-2 flex flex-wrap gap-2">
|
|
222
|
-
<ActionButton label="Stop" action={() => kind === "agent" ? latticeApi.stopAgentRun(id) : latticeApi.stopWorkflowRun(id)} />
|
|
223
|
-
{status === "awaiting_approval" && kind === "workflow" ? (
|
|
224
|
-
<>
|
|
225
|
-
<ActionButton label="Resume approved" action={() => latticeApi.resumeWorkflowRun(id, true)} />
|
|
226
|
-
<ActionButton label="Resume denied" action={() => latticeApi.resumeWorkflowRun(id, false)} variant="destructive" />
|
|
227
|
-
</>
|
|
228
|
-
) : null}
|
|
229
|
-
</div>
|
|
230
|
-
</div>
|
|
231
|
-
);
|
|
232
|
-
})}
|
|
233
|
-
</div>
|
|
234
|
-
);
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
function WorkflowsPanel() {
|
|
238
|
-
const qc = useQueryClient();
|
|
239
|
-
const defs = useQuery({ queryKey: ["workflowDefinitions"], queryFn: latticeApi.workflowDefinitions });
|
|
240
|
-
const triggers = useQuery({ queryKey: ["workflowTriggers"], queryFn: latticeApi.workflowTriggers });
|
|
241
|
-
const recipes = useQuery({ queryKey: ["automationRecipes"], queryFn: latticeApi.automationRecipes });
|
|
242
|
-
const [name, setName] = React.useState("Manual workflow");
|
|
243
|
-
const [importText, setImportText] = React.useState("");
|
|
244
|
-
const create = useMutation({
|
|
245
|
-
mutationFn: () => latticeApi.createWorkflow({
|
|
246
|
-
name: name.trim() || "Manual workflow",
|
|
247
|
-
nodes: manualWorkflowNodes(),
|
|
248
|
-
metadata: { created_from: "desktop-act-ui" },
|
|
249
|
-
}),
|
|
250
|
-
onSuccess: () => qc.invalidateQueries({ queryKey: ["workflowDefinitions"] }),
|
|
251
|
-
});
|
|
252
|
-
const importWorkflow = useMutation({
|
|
253
|
-
mutationFn: () => latticeApi.importWorkflow(JSON.parse(importText) as Record<string, unknown>),
|
|
254
|
-
onSuccess: () => {
|
|
255
|
-
setImportText("");
|
|
256
|
-
qc.invalidateQueries({ queryKey: ["workflowDefinitions"] });
|
|
257
|
-
},
|
|
258
|
-
});
|
|
259
|
-
const installRecipe = useMutation({
|
|
260
|
-
mutationFn: (recipeId: string) => latticeApi.installAutomationRecipe(recipeId, false),
|
|
261
|
-
onSuccess: () => {
|
|
262
|
-
qc.invalidateQueries({ queryKey: ["workflowDefinitions"] });
|
|
263
|
-
qc.invalidateQueries({ queryKey: ["workflowTriggers"] });
|
|
264
|
-
},
|
|
265
|
-
});
|
|
266
|
-
const workflows = asArray<Record<string, unknown>>((defs.data?.data as Record<string, unknown>)?.workflows);
|
|
267
|
-
const installedRecipeIds = new Set(
|
|
268
|
-
workflows
|
|
269
|
-
.filter((w: any) => w && w.metadata && w.metadata.created_from === "brain_automation_recipe" && w.metadata.recipe_id)
|
|
270
|
-
.map((w: any) => String(w.metadata.recipe_id))
|
|
271
|
-
);
|
|
272
|
-
const nodes: Node[] = workflows.slice(0, 12).map((workflow, index) => ({
|
|
273
|
-
id: String(workflow.id || workflow.workflow_id || index),
|
|
274
|
-
position: { x: (index % 4) * 190, y: Math.floor(index / 4) * 120 },
|
|
275
|
-
data: { label: String(workflow.name || workflow.id || `Workflow ${index + 1}`) },
|
|
276
|
-
}));
|
|
277
|
-
const edges: Edge[] = nodes.slice(1).map((node, index) => ({ id: `e-${index}`, source: nodes[index].id, target: node.id }));
|
|
278
|
-
return (
|
|
279
|
-
<div className="grid gap-4 xl:grid-cols-[1.2fr_0.8fr]">
|
|
280
|
-
<DataPanel title="Brain automations" result={recipes.data} className="xl:col-span-2">
|
|
281
|
-
{(data) => {
|
|
282
|
-
const items = asArray<Record<string, unknown>>((data as Record<string, unknown>).recipes);
|
|
283
|
-
return (
|
|
284
|
-
<div className="grid gap-3 lg:grid-cols-3">
|
|
285
|
-
{items.map((recipe) => {
|
|
286
|
-
const id = String(recipe.id || "");
|
|
287
|
-
const consent = (recipe.consent || {}) as Record<string, unknown>;
|
|
288
|
-
const creates = asArray<string>(recipe.creates);
|
|
289
|
-
return (
|
|
290
|
-
<div key={id} className="rounded-lg border border-border bg-background/70 p-4">
|
|
291
|
-
<div className="flex items-start justify-between gap-2">
|
|
292
|
-
<div>
|
|
293
|
-
<div className="flex items-center gap-2 font-medium">
|
|
294
|
-
<CalendarClock className="h-4 w-4" /> {String(recipe.name || id)}
|
|
295
|
-
</div>
|
|
296
|
-
<p className="mt-2 text-sm text-muted-foreground">{String(recipe.summary || "")}</p>
|
|
297
|
-
</div>
|
|
298
|
-
<Badge variant="muted">{String(recipe.cadence || "draft")}</Badge>
|
|
299
|
-
</div>
|
|
300
|
-
<p className="mt-3 text-sm">{String(recipe.user_value || "")}</p>
|
|
301
|
-
<div className="mt-3 flex flex-wrap gap-2">
|
|
302
|
-
<Badge variant="success"><ShieldCheck className="h-3 w-3" /> local only</Badge>
|
|
303
|
-
{consent.requires_user_enable ? <Badge variant="warning">draft first</Badge> : null}
|
|
304
|
-
{creates.slice(0, 2).map((item) => <Badge key={item} variant="muted">{item}</Badge>)}
|
|
305
|
-
</div>
|
|
306
|
-
{(() => {
|
|
307
|
-
const isInstalling = installRecipe.isPending && installRecipe.variables === id;
|
|
308
|
-
const last = installRecipe.data as any;
|
|
309
|
-
const lastRid = last && last.recipe && last.recipe.recipe_id ? String(last.recipe.recipe_id) : "";
|
|
310
|
-
const justSucceeded = !installRecipe.isPending && lastRid === id;
|
|
311
|
-
const installed = installedRecipeIds.has(id);
|
|
312
|
-
const btnLabel = isInstalling
|
|
313
|
-
? "Creating..."
|
|
314
|
-
: justSucceeded
|
|
315
|
-
? "✓ Draft created"
|
|
316
|
-
: installed
|
|
317
|
-
? "Draft already created"
|
|
318
|
-
: "Create reviewable draft";
|
|
319
|
-
return (
|
|
320
|
-
<>
|
|
321
|
-
<Button
|
|
322
|
-
className="mt-4 w-full"
|
|
323
|
-
variant={installed || justSucceeded ? "secondary" : "outline"}
|
|
324
|
-
disabled={!id || isInstalling || installed}
|
|
325
|
-
onClick={() => {
|
|
326
|
-
if (installed || isInstalling) return;
|
|
327
|
-
installRecipe.mutate(id);
|
|
328
|
-
}}
|
|
329
|
-
>
|
|
330
|
-
{btnLabel}
|
|
331
|
-
</Button>
|
|
332
|
-
{justSucceeded ? (
|
|
333
|
-
<p className="mt-1 text-[10px] text-green-600">Reviewable draft ready. Check Definitions below.</p>
|
|
334
|
-
) : null}
|
|
335
|
-
{installed && !justSucceeded ? (
|
|
336
|
-
<p className="mt-1 text-[10px] text-muted-foreground">Reviewable draft exists — see Definitions.</p>
|
|
337
|
-
) : null}
|
|
338
|
-
</>
|
|
339
|
-
);
|
|
340
|
-
})()}
|
|
341
|
-
</div>
|
|
342
|
-
);
|
|
343
|
-
})}
|
|
344
|
-
</div>
|
|
345
|
-
);
|
|
346
|
-
}}
|
|
347
|
-
</DataPanel>
|
|
348
|
-
<Card>
|
|
349
|
-
<CardHeader>
|
|
350
|
-
<CardTitle className="flex items-center gap-2"><GitBranch className="h-4 w-4" /> Workflow graph</CardTitle>
|
|
351
|
-
<CardDescription>See your saved workflows as a simple map.</CardDescription>
|
|
352
|
-
</CardHeader>
|
|
353
|
-
<CardContent>
|
|
354
|
-
<div className="h-[440px] rounded-lg border border-border">
|
|
355
|
-
<ReactFlow nodes={nodes} edges={edges} fitView>
|
|
356
|
-
<Background />
|
|
357
|
-
<Controls />
|
|
358
|
-
</ReactFlow>
|
|
359
|
-
</div>
|
|
360
|
-
</CardContent>
|
|
361
|
-
</Card>
|
|
362
|
-
<DataPanel title="Definitions" result={defs.data}>
|
|
363
|
-
{() => (
|
|
364
|
-
<div className="space-y-3">
|
|
365
|
-
<div className="grid gap-2 rounded-md border border-border p-3">
|
|
366
|
-
<div className="flex flex-wrap gap-2">
|
|
367
|
-
<Input value={name} onChange={(event) => setName(event.target.value)} placeholder="Workflow name" />
|
|
368
|
-
<Button disabled={create.isPending} onClick={() => create.mutate()}>Create</Button>
|
|
369
|
-
</div>
|
|
370
|
-
<Textarea value={importText} onChange={(event) => setImportText(event.target.value)} placeholder="Paste a workflow export" />
|
|
371
|
-
<Button variant="outline" disabled={!importText.trim() || importWorkflow.isPending} onClick={() => importWorkflow.mutate()}>Import</Button>
|
|
372
|
-
{create.data ? <OperationResult result={create.data} successLabel="Workflow created" /> : null}
|
|
373
|
-
{importWorkflow.data ? <OperationResult result={importWorkflow.data} successLabel="Workflow imported" /> : null}
|
|
374
|
-
</div>
|
|
375
|
-
{workflows.length ? workflows.map((workflow) => {
|
|
376
|
-
const id = String(workflow.id || workflow.workflow_id);
|
|
377
|
-
return (
|
|
378
|
-
<div key={id} className="rounded-md border border-border p-3">
|
|
379
|
-
<div className="font-medium">{String(workflow.name || id)}</div>
|
|
380
|
-
<div className="mt-2 flex gap-2">
|
|
381
|
-
<ActionButton label="Run" action={() => latticeApi.runWorkflow(id)} invalidate={["workflowRuns"]} />
|
|
382
|
-
<ActionButton label="Export" action={() => latticeApi.exportWorkflow(id)} />
|
|
383
|
-
</div>
|
|
384
|
-
</div>
|
|
385
|
-
);
|
|
386
|
-
}) : <EntityList items={[]} />}
|
|
387
|
-
</div>
|
|
388
|
-
)}
|
|
389
|
-
</DataPanel>
|
|
390
|
-
<DataPanel title="Automation triggers" result={triggers.data} className="xl:col-span-2">
|
|
391
|
-
{(data) => <StructuredView value={data} />}
|
|
392
|
-
</DataPanel>
|
|
393
|
-
</div>
|
|
394
|
-
);
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
function manualWorkflowNodes(): Array<Record<string, unknown>> {
|
|
398
|
-
return [
|
|
399
|
-
{
|
|
400
|
-
id: "trigger",
|
|
401
|
-
type: "trigger",
|
|
402
|
-
name: "Manual start",
|
|
403
|
-
config: { trigger: "manual" },
|
|
404
|
-
next: "output",
|
|
405
|
-
},
|
|
406
|
-
{
|
|
407
|
-
id: "output",
|
|
408
|
-
type: "output",
|
|
409
|
-
name: "Output",
|
|
410
|
-
config: { value: "Workflow completed" },
|
|
411
|
-
next: null,
|
|
412
|
-
},
|
|
413
|
-
];
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
function HooksPanel() {
|
|
417
|
-
const mode = useAppStore((state) => state.mode);
|
|
418
|
-
const hooks = useQuery({ queryKey: ["hooks"], queryFn: latticeApi.hooks });
|
|
419
|
-
const runs = useQuery({ queryKey: ["hookRuns"], queryFn: latticeApi.hookRuns });
|
|
420
|
-
if (mode === "basic") {
|
|
421
|
-
return (
|
|
422
|
-
<div className="grid gap-4 xl:grid-cols-2">
|
|
423
|
-
<DataPanel title="Safeguards" result={hooks.data}>
|
|
424
|
-
{(data) => <EntityList items={(data as Record<string, unknown>).hooks} titleKey="name" metaKey="kind" />}
|
|
425
|
-
</DataPanel>
|
|
426
|
-
<ModeGate title="Detailed hook logs" detail="Switch to Advanced when you need hook run logs and manual diagnostic controls." />
|
|
427
|
-
</div>
|
|
428
|
-
);
|
|
429
|
-
}
|
|
430
|
-
return (
|
|
431
|
-
<div className="grid gap-4 xl:grid-cols-2">
|
|
432
|
-
<DataPanel title="Hooks" result={hooks.data}>
|
|
433
|
-
{(data) => <EntityList items={(data as Record<string, unknown>).hooks} titleKey="name" metaKey="kind" />}
|
|
434
|
-
</DataPanel>
|
|
435
|
-
<DataPanel title="Hook run log" result={runs.data}>
|
|
436
|
-
{(data) => <EntityList items={(data as Record<string, unknown>).runs} titleKey="hook_id" metaKey="status" />}
|
|
437
|
-
</DataPanel>
|
|
438
|
-
<Card className="xl:col-span-2">
|
|
439
|
-
<CardHeader>
|
|
440
|
-
<CardTitle className="flex items-center gap-2"><PauseCircle className="h-4 w-4" /> Run manual hooks</CardTitle>
|
|
441
|
-
<CardDescription>Trigger hooks deliberately and review the recorded result.</CardDescription>
|
|
442
|
-
</CardHeader>
|
|
443
|
-
<CardContent>
|
|
444
|
-
<ActionButton label="Run all manual hooks" action={() => latticeApi.hookRun({ event: "manual" })} invalidate={["hookRuns"]} />
|
|
445
|
-
</CardContent>
|
|
446
|
-
</Card>
|
|
447
|
-
</div>
|
|
448
|
-
);
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
function ToolsPanel() {
|
|
452
|
-
const tools = useQuery({ queryKey: ["toolPermissions"], queryFn: latticeApi.toolPermissions });
|
|
453
|
-
return (
|
|
454
|
-
<DataPanel title="Action permissions" result={tools.data}>
|
|
455
|
-
{(data) => <EntityList items={(data as Record<string, unknown>).permissions || data} titleKey="tool" metaKey="risk" />}
|
|
456
|
-
</DataPanel>
|
|
457
|
-
);
|
|
458
|
-
}
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import * as React from "react";
|
|
2
|
-
import { type BrainState } from "@/components/LivingBrain";
|
|
3
|
-
import { BrainHome } from "@/features/brain/BrainHome";
|
|
4
|
-
|
|
5
|
-
export function AskPage() {
|
|
6
|
-
const [brainPresence, setBrainPresence] = React.useState<{ state: BrainState; intensity: number }>({
|
|
7
|
-
state: "idle",
|
|
8
|
-
intensity: 0.58,
|
|
9
|
-
});
|
|
10
|
-
const setBrain = React.useCallback((state: BrainState, intensity = 0.58) => {
|
|
11
|
-
setBrainPresence({ state, intensity });
|
|
12
|
-
}, []);
|
|
13
|
-
return <BrainHome brainState={brainPresence.state} intensity={brainPresence.intensity} onBrainChange={setBrain} />;
|
|
14
|
-
}
|