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,392 +0,0 @@
|
|
|
1
|
-
import * as React from "react";
|
|
2
|
-
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
|
3
|
-
import { AlertCircle, CheckCircle2, Loader2, LockKeyhole, Sparkles } from "lucide-react";
|
|
4
|
-
import type { ApiResult } from "@/api/client";
|
|
5
|
-
import { Badge } from "@/components/ui/badge";
|
|
6
|
-
import { Button } from "@/components/ui/button";
|
|
7
|
-
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
|
8
|
-
import { useAppStore } from "@/store/appStore";
|
|
9
|
-
import { cn, asArray, fmtNumber, shortId, titleize } from "@/lib/utils";
|
|
10
|
-
|
|
11
|
-
export function SourceBadge({ result }: { result?: Pick<ApiResult, "source" | "ok" | "status"> }) {
|
|
12
|
-
const mode = useAppStore((state) => state.mode);
|
|
13
|
-
if (!result) return <Badge variant="muted">not loaded</Badge>;
|
|
14
|
-
if (result.source === "live" && result.ok) return <Badge variant="success">{mode === "basic" ? "ready" : "connected"}</Badge>;
|
|
15
|
-
return <Badge variant="warning">{mode === "basic" ? "needs setup" : "unavailable"}</Badge>;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export function EmptyState({ title = "Unavailable", detail }: { title?: string; detail?: React.ReactNode }) {
|
|
19
|
-
return (
|
|
20
|
-
<div className="flex min-h-36 flex-col items-center justify-center gap-2 rounded-lg border border-dashed border-border bg-muted/24 p-6 text-center text-sm text-muted-foreground">
|
|
21
|
-
<div className="grid h-10 w-10 place-items-center rounded-md border border-border bg-card">
|
|
22
|
-
<Sparkles className="h-5 w-5 text-primary" />
|
|
23
|
-
</div>
|
|
24
|
-
<div className="text-base font-semibold text-foreground">{title}</div>
|
|
25
|
-
{detail ? <div className="max-w-md leading-6">{detail}</div> : null}
|
|
26
|
-
</div>
|
|
27
|
-
);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export function DataPanel<T>({
|
|
31
|
-
title,
|
|
32
|
-
description,
|
|
33
|
-
result,
|
|
34
|
-
children,
|
|
35
|
-
className,
|
|
36
|
-
}: {
|
|
37
|
-
title: string;
|
|
38
|
-
description?: string;
|
|
39
|
-
result?: ApiResult<T>;
|
|
40
|
-
children: (data: T) => React.ReactNode;
|
|
41
|
-
className?: string;
|
|
42
|
-
}) {
|
|
43
|
-
const mode = useAppStore((state) => state.mode);
|
|
44
|
-
return (
|
|
45
|
-
<Card className={cn("overflow-hidden", className)}>
|
|
46
|
-
<CardHeader className="flex-row items-start justify-between gap-3">
|
|
47
|
-
<div>
|
|
48
|
-
<CardTitle>{title}</CardTitle>
|
|
49
|
-
{description ? <CardDescription>{description}</CardDescription> : null}
|
|
50
|
-
</div>
|
|
51
|
-
<SourceBadge result={result} />
|
|
52
|
-
</CardHeader>
|
|
53
|
-
<CardContent>
|
|
54
|
-
{result?.ok ? children(result.data) : (
|
|
55
|
-
<EmptyState detail={mode === "basic" ? "This area needs setup or is not available yet." : result?.error || "This capability is not reporting right now."} />
|
|
56
|
-
)}
|
|
57
|
-
</CardContent>
|
|
58
|
-
</Card>
|
|
59
|
-
);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
export function LoadingPanel({ title }: { title: string }) {
|
|
63
|
-
return (
|
|
64
|
-
<Card>
|
|
65
|
-
<CardHeader>
|
|
66
|
-
<CardTitle>{title}</CardTitle>
|
|
67
|
-
</CardHeader>
|
|
68
|
-
<CardContent>
|
|
69
|
-
<div className="flex items-center gap-2 text-sm text-muted-foreground">
|
|
70
|
-
<Loader2 className="h-4 w-4 animate-spin" /> Loading
|
|
71
|
-
</div>
|
|
72
|
-
</CardContent>
|
|
73
|
-
</Card>
|
|
74
|
-
);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
export function StatGrid({ stats }: { stats: Array<{ label: string; value: unknown; hint?: string }> }) {
|
|
78
|
-
return (
|
|
79
|
-
<div className="grid gap-3 sm:grid-cols-2 xl:grid-cols-4">
|
|
80
|
-
{stats.map((stat) => (
|
|
81
|
-
<div key={stat.label} className="rounded-lg border border-border bg-background/55 p-4">
|
|
82
|
-
<div className="text-xs uppercase text-muted-foreground">{stat.label}</div>
|
|
83
|
-
<div className="mt-2 text-2xl font-semibold leading-tight">{typeof stat.value === "number" ? fmtNumber(stat.value) : String(stat.value ?? "-")}</div>
|
|
84
|
-
{stat.hint ? <div className="mt-2 text-xs leading-5 text-muted-foreground">{stat.hint}</div> : null}
|
|
85
|
-
</div>
|
|
86
|
-
))}
|
|
87
|
-
</div>
|
|
88
|
-
);
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
92
|
-
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
function scalarText(value: unknown) {
|
|
96
|
-
if (value === null || value === undefined || value === "") return "-";
|
|
97
|
-
if (typeof value === "number") return Number.isFinite(value) ? fmtNumber(value) : "-";
|
|
98
|
-
if (typeof value === "boolean") return value ? "Enabled" : "Disabled";
|
|
99
|
-
return String(value);
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
const BASIC_HIDDEN_KEY = /(^id$|_id$|token|secret|passphrase|fingerprint|public_key|private_key|dsn|schema|endpoint|base_url|localhost|127\.0\.0\.1|stack|trace|raw|runtime|engine|module|port|host|api|internal)/i;
|
|
103
|
-
|
|
104
|
-
function hideInBasic(key: string) {
|
|
105
|
-
return BASIC_HIDDEN_KEY.test(key);
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
function humanText(value: unknown) {
|
|
109
|
-
const text = scalarText(value);
|
|
110
|
-
if (text === "-") return text;
|
|
111
|
-
if (text.includes("/") || text.includes("@") || /\.[a-z0-9]{2,5}$/i.test(text)) return text;
|
|
112
|
-
return titleize(text.replace(/^agent:/i, "").replace(/^tool:/i, ""));
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
function firstRecordList(value: Record<string, unknown>) {
|
|
116
|
-
const preferred = [
|
|
117
|
-
"documents", "sources", "items", "agents", "workflows", "runs", "events",
|
|
118
|
-
"permissions", "models", "peers", "invitations", "roles", "policies",
|
|
119
|
-
"hooks", "tools", "templates", "plugins", "recent_events",
|
|
120
|
-
];
|
|
121
|
-
for (const key of preferred) {
|
|
122
|
-
const rows = asArray<Record<string, unknown>>(value[key]);
|
|
123
|
-
if (rows.length) return rows;
|
|
124
|
-
}
|
|
125
|
-
return [];
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
export function ValuePreview({ value }: { value: unknown }) {
|
|
129
|
-
if (typeof value === "boolean") {
|
|
130
|
-
return <Badge variant={value ? "success" : "muted"}>{value ? "enabled" : "disabled"}</Badge>;
|
|
131
|
-
}
|
|
132
|
-
if (Array.isArray(value)) {
|
|
133
|
-
if (!value.length) return <span className="text-muted-foreground">None</span>;
|
|
134
|
-
const primitive = value.every((item) => item === null || ["string", "number", "boolean"].includes(typeof item));
|
|
135
|
-
if (primitive) {
|
|
136
|
-
return (
|
|
137
|
-
<span className="flex flex-wrap gap-1">
|
|
138
|
-
{value.slice(0, 5).map((item, index) => <Badge key={`${String(item)}-${index}`} variant="muted">{scalarText(item)}</Badge>)}
|
|
139
|
-
{value.length > 5 ? <Badge variant="muted">+{value.length - 5}</Badge> : null}
|
|
140
|
-
</span>
|
|
141
|
-
);
|
|
142
|
-
}
|
|
143
|
-
return <span className="text-muted-foreground">{fmtNumber(value.length)} records</span>;
|
|
144
|
-
}
|
|
145
|
-
if (isRecord(value)) {
|
|
146
|
-
const keys = Object.keys(value);
|
|
147
|
-
if (!keys.length) return <span className="text-muted-foreground">No fields</span>;
|
|
148
|
-
return <span className="text-muted-foreground">{keys.slice(0, 4).map(titleize).join(", ")}{keys.length > 4 ? ` +${keys.length - 4}` : ""}</span>;
|
|
149
|
-
}
|
|
150
|
-
const text = scalarText(value);
|
|
151
|
-
return <span className="break-words">{text.length > 96 ? shortId(text, 96) : text}</span>;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
export function KeyValueList({ data, limit = 8 }: { data: Record<string, unknown>; limit?: number }) {
|
|
155
|
-
const mode = useAppStore((state) => state.mode);
|
|
156
|
-
const rows = Object.entries(data || {})
|
|
157
|
-
.filter(([key]) => mode !== "basic" || !hideInBasic(key))
|
|
158
|
-
.slice(0, limit);
|
|
159
|
-
if (!rows.length) return <EmptyState title="No values" />;
|
|
160
|
-
return (
|
|
161
|
-
<div className="divide-y divide-border rounded-md border border-border">
|
|
162
|
-
{rows.map(([key, value]) => (
|
|
163
|
-
<div key={key} className="grid grid-cols-[minmax(9rem,0.5fr)_1fr] gap-3 p-3 text-sm">
|
|
164
|
-
<span className="font-medium text-muted-foreground">{titleize(key)}</span>
|
|
165
|
-
<span className="min-w-0 break-words"><ValuePreview value={value} /></span>
|
|
166
|
-
</div>
|
|
167
|
-
))}
|
|
168
|
-
</div>
|
|
169
|
-
);
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
export function StructuredView({
|
|
173
|
-
value,
|
|
174
|
-
titleKey = "title",
|
|
175
|
-
metaKey = "status",
|
|
176
|
-
limit = 8,
|
|
177
|
-
}: {
|
|
178
|
-
value: unknown;
|
|
179
|
-
titleKey?: string;
|
|
180
|
-
metaKey?: string;
|
|
181
|
-
limit?: number;
|
|
182
|
-
}) {
|
|
183
|
-
const mode = useAppStore((state) => state.mode);
|
|
184
|
-
if (mode === "basic") return <FriendlySummary value={value} titleKey={titleKey} metaKey={metaKey} limit={limit} />;
|
|
185
|
-
if (Array.isArray(value)) {
|
|
186
|
-
if (!value.length) return <EmptyState title="Nothing here yet" detail="New items will appear here when Lattice has something to show." />;
|
|
187
|
-
if (value.every((item) => isRecord(item))) {
|
|
188
|
-
return <EntityList items={value} titleKey={titleKey} metaKey={metaKey} limit={limit} />;
|
|
189
|
-
}
|
|
190
|
-
return (
|
|
191
|
-
<div className="flex flex-wrap gap-1 rounded-md border border-border bg-background p-3">
|
|
192
|
-
{value.slice(0, limit).map((item, index) => <Badge key={`${String(item)}-${index}`} variant="muted">{scalarText(item)}</Badge>)}
|
|
193
|
-
{value.length > limit ? <Badge variant="muted">+{value.length - limit}</Badge> : null}
|
|
194
|
-
</div>
|
|
195
|
-
);
|
|
196
|
-
}
|
|
197
|
-
if (isRecord(value)) return <KeyValueList data={value} limit={limit} />;
|
|
198
|
-
return (
|
|
199
|
-
<div className="rounded-md border border-border bg-background p-3 text-sm">
|
|
200
|
-
<ValuePreview value={value} />
|
|
201
|
-
</div>
|
|
202
|
-
);
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
export function FriendlySummary({
|
|
206
|
-
value,
|
|
207
|
-
titleKey = "title",
|
|
208
|
-
metaKey = "status",
|
|
209
|
-
limit = 6,
|
|
210
|
-
}: {
|
|
211
|
-
value: unknown;
|
|
212
|
-
titleKey?: string;
|
|
213
|
-
metaKey?: string;
|
|
214
|
-
limit?: number;
|
|
215
|
-
}) {
|
|
216
|
-
if (Array.isArray(value)) {
|
|
217
|
-
if (!value.length) return <EmptyState title="Nothing here yet" detail="New items will appear here when Lattice has something to show." />;
|
|
218
|
-
if (value.every((item) => isRecord(item))) {
|
|
219
|
-
return <EntityList items={value} titleKey={titleKey} metaKey={metaKey} limit={limit} />;
|
|
220
|
-
}
|
|
221
|
-
return (
|
|
222
|
-
<div className="flex flex-wrap gap-1 rounded-md border border-border bg-background/55 p-3">
|
|
223
|
-
{value.slice(0, limit).map((item, index) => <Badge key={`${String(item)}-${index}`} variant="muted">{humanText(item)}</Badge>)}
|
|
224
|
-
{value.length > limit ? <Badge variant="muted">+{value.length - limit}</Badge> : null}
|
|
225
|
-
</div>
|
|
226
|
-
);
|
|
227
|
-
}
|
|
228
|
-
if (isRecord(value)) {
|
|
229
|
-
const list = firstRecordList(value);
|
|
230
|
-
if (list.length) return <EntityList items={list} titleKey={titleKey} metaKey={metaKey} limit={limit} />;
|
|
231
|
-
const friendly = Object.fromEntries(
|
|
232
|
-
Object.entries(value)
|
|
233
|
-
.filter(([key]) => !hideInBasic(key))
|
|
234
|
-
.map(([key, item]) => [key, Array.isArray(item) ? `${fmtNumber(item.length)} items` : isRecord(item) ? "available" : item]),
|
|
235
|
-
);
|
|
236
|
-
return <KeyValueList data={friendly} limit={limit} />;
|
|
237
|
-
}
|
|
238
|
-
return (
|
|
239
|
-
<div className="rounded-md border border-border bg-background/55 p-3 text-sm">
|
|
240
|
-
{humanText(value)}
|
|
241
|
-
</div>
|
|
242
|
-
);
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
export function OperationResult({
|
|
246
|
-
result,
|
|
247
|
-
successLabel = "Request completed",
|
|
248
|
-
}: {
|
|
249
|
-
result?: ApiResult<unknown> | null;
|
|
250
|
-
successLabel?: string;
|
|
251
|
-
}) {
|
|
252
|
-
const mode = useAppStore((state) => state.mode);
|
|
253
|
-
if (!result) return null;
|
|
254
|
-
if (!result.ok) {
|
|
255
|
-
return <EmptyState title="Request unavailable" detail={result.error || <ValuePreview value={result.data} />} />;
|
|
256
|
-
}
|
|
257
|
-
return (
|
|
258
|
-
<div className="space-y-2 rounded-md border border-border bg-background p-3">
|
|
259
|
-
<Badge variant="success">{successLabel}</Badge>
|
|
260
|
-
{mode === "basic" ? <FriendlySummary value={result.data} /> : <StructuredView value={result.data} />}
|
|
261
|
-
</div>
|
|
262
|
-
);
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
export function EntityList({
|
|
266
|
-
items,
|
|
267
|
-
titleKey = "title",
|
|
268
|
-
metaKey = "type",
|
|
269
|
-
limit = 8,
|
|
270
|
-
}: {
|
|
271
|
-
items: unknown;
|
|
272
|
-
titleKey?: string;
|
|
273
|
-
metaKey?: string;
|
|
274
|
-
limit?: number;
|
|
275
|
-
}) {
|
|
276
|
-
const mode = useAppStore((state) => state.mode);
|
|
277
|
-
const rows = asArray<Record<string, unknown>>(items).slice(0, limit);
|
|
278
|
-
if (!rows.length) return <EmptyState title="Nothing here yet" detail="New items will appear here when Lattice has something to show." />;
|
|
279
|
-
return (
|
|
280
|
-
<div className="grid gap-2">
|
|
281
|
-
{rows.map((item, index) => (
|
|
282
|
-
<div key={String(item.id || item.name || index)} className="rounded-lg border border-border bg-background/55 p-3">
|
|
283
|
-
<div className="flex flex-wrap items-center justify-between gap-2">
|
|
284
|
-
<div className="font-medium">{mode === "basic" ? humanText(item[titleKey] || item.name || item.label || `Item ${index + 1}`) : String(item[titleKey] || item.name || item.id || `Record ${index + 1}`)}</div>
|
|
285
|
-
<Badge variant="muted">{mode === "basic" ? humanText(item[metaKey] || item.status || item.state || "ready") : String(item[metaKey] || item.status || item.state || "record")}</Badge>
|
|
286
|
-
</div>
|
|
287
|
-
{item.summary || item.description || item.path || (item.id && item[titleKey] !== item.id) ? (
|
|
288
|
-
<p className="mt-1 text-sm text-muted-foreground">{String(item.summary || item.description || item.path || item.id)}</p>
|
|
289
|
-
) : null}
|
|
290
|
-
{mode !== "basic" && item.id && item[titleKey] !== item.id ? (
|
|
291
|
-
<div className="mt-1 text-xs text-muted-foreground">{shortId(item.id, 48)}</div>
|
|
292
|
-
) : null}
|
|
293
|
-
</div>
|
|
294
|
-
))}
|
|
295
|
-
</div>
|
|
296
|
-
);
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
export function ModeGate({
|
|
300
|
-
title = "Advanced controls",
|
|
301
|
-
detail = "Switch modes when you want diagnostics or administrative controls. Basic mode keeps the product focused on everyday use.",
|
|
302
|
-
target = "advanced",
|
|
303
|
-
}: {
|
|
304
|
-
title?: string;
|
|
305
|
-
detail?: string;
|
|
306
|
-
target?: "advanced" | "admin";
|
|
307
|
-
}) {
|
|
308
|
-
const setMode = useAppStore((state) => state.setMode);
|
|
309
|
-
return (
|
|
310
|
-
<Card>
|
|
311
|
-
<CardContent className="flex flex-col items-start gap-3 p-6">
|
|
312
|
-
<div className="grid h-10 w-10 place-items-center rounded-md border border-border bg-background/70">
|
|
313
|
-
<LockKeyhole className="h-5 w-5 text-primary" />
|
|
314
|
-
</div>
|
|
315
|
-
<div>
|
|
316
|
-
<div className="text-lg font-semibold">{title}</div>
|
|
317
|
-
<p className="mt-1 max-w-2xl text-sm leading-6 text-muted-foreground">{detail}</p>
|
|
318
|
-
</div>
|
|
319
|
-
<Button onClick={() => setMode(target)}>{target === "admin" ? "Switch to Admin Console" : "Switch to Advanced"}</Button>
|
|
320
|
-
</CardContent>
|
|
321
|
-
</Card>
|
|
322
|
-
);
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
export function ActionButton({
|
|
326
|
-
label,
|
|
327
|
-
successLabel = "Done",
|
|
328
|
-
action,
|
|
329
|
-
invalidate,
|
|
330
|
-
variant = "outline",
|
|
331
|
-
disabled,
|
|
332
|
-
}: {
|
|
333
|
-
label: string;
|
|
334
|
-
successLabel?: string;
|
|
335
|
-
action: () => Promise<ApiResult<unknown>>;
|
|
336
|
-
invalidate?: string[];
|
|
337
|
-
variant?: React.ComponentProps<typeof Button>["variant"];
|
|
338
|
-
disabled?: boolean;
|
|
339
|
-
}) {
|
|
340
|
-
const qc = useQueryClient();
|
|
341
|
-
const [result, setResult] = React.useState<string | null>(null);
|
|
342
|
-
const mut = useMutation({
|
|
343
|
-
mutationFn: action,
|
|
344
|
-
onSuccess: async (res) => {
|
|
345
|
-
setResult(res.ok ? successLabel : res.error || "Unavailable");
|
|
346
|
-
if (invalidate) {
|
|
347
|
-
await Promise.all(invalidate.map((key) => qc.invalidateQueries({ queryKey: [key] })));
|
|
348
|
-
}
|
|
349
|
-
},
|
|
350
|
-
});
|
|
351
|
-
return (
|
|
352
|
-
<div className="flex flex-wrap items-center gap-2">
|
|
353
|
-
<Button variant={variant} disabled={disabled || mut.isPending} onClick={() => mut.mutate()}>
|
|
354
|
-
{mut.isPending ? <Loader2 className="h-4 w-4 animate-spin" /> : null}
|
|
355
|
-
{label}
|
|
356
|
-
</Button>
|
|
357
|
-
{result ? (
|
|
358
|
-
<span className={cn("inline-flex items-center gap-1 text-xs", result === successLabel ? "text-emerald-300" : "text-amber-300")}>
|
|
359
|
-
{result === successLabel ? <CheckCircle2 className="h-3.5 w-3.5" /> : <AlertCircle className="h-3.5 w-3.5" />}
|
|
360
|
-
{result}
|
|
361
|
-
</span>
|
|
362
|
-
) : null}
|
|
363
|
-
</div>
|
|
364
|
-
);
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
export function Tabs({
|
|
368
|
-
tabs,
|
|
369
|
-
value,
|
|
370
|
-
onChange,
|
|
371
|
-
}: {
|
|
372
|
-
tabs: Array<{ id: string; label: string }>;
|
|
373
|
-
value: string;
|
|
374
|
-
onChange: (id: string) => void;
|
|
375
|
-
}) {
|
|
376
|
-
return (
|
|
377
|
-
<div className="inline-flex max-w-full flex-wrap gap-1 rounded-lg border border-border bg-muted/28 p-1">
|
|
378
|
-
{tabs.map((tab) => (
|
|
379
|
-
<button
|
|
380
|
-
key={tab.id}
|
|
381
|
-
onClick={() => onChange(tab.id)}
|
|
382
|
-
className={cn(
|
|
383
|
-
"h-9 rounded-md px-3.5 text-sm font-semibold transition",
|
|
384
|
-
value === tab.id ? "bg-card text-foreground shadow-sm" : "text-muted-foreground hover:bg-card/60 hover:text-foreground",
|
|
385
|
-
)}
|
|
386
|
-
>
|
|
387
|
-
{tab.label}
|
|
388
|
-
</button>
|
|
389
|
-
))}
|
|
390
|
-
</div>
|
|
391
|
-
);
|
|
392
|
-
}
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import * as React from "react";
|
|
2
|
-
import { cn } from "@/lib/utils";
|
|
3
|
-
|
|
4
|
-
type BadgeProps = React.HTMLAttributes<HTMLSpanElement> & {
|
|
5
|
-
variant?: "default" | "success" | "warning" | "muted" | "danger";
|
|
6
|
-
};
|
|
7
|
-
|
|
8
|
-
const variants = {
|
|
9
|
-
default: "border-primary/25 bg-primary/12 text-primary",
|
|
10
|
-
success: "border-emerald-500/25 bg-emerald-500/12 text-emerald-300",
|
|
11
|
-
warning: "border-amber-500/25 bg-amber-500/12 text-amber-300",
|
|
12
|
-
muted: "border-border bg-muted/70 text-muted-foreground",
|
|
13
|
-
danger: "border-destructive/30 bg-destructive/12 text-destructive",
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
export function Badge({ className, variant = "default", ...props }: BadgeProps) {
|
|
17
|
-
return (
|
|
18
|
-
<span
|
|
19
|
-
className={cn(
|
|
20
|
-
"inline-flex min-h-6 max-w-full items-center rounded-md border px-2 py-0.5 text-xs font-semibold leading-none",
|
|
21
|
-
variants[variant],
|
|
22
|
-
className,
|
|
23
|
-
)}
|
|
24
|
-
{...props}
|
|
25
|
-
/>
|
|
26
|
-
);
|
|
27
|
-
}
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
import * as React from "react";
|
|
2
|
-
import { cva, type VariantProps } from "class-variance-authority";
|
|
3
|
-
import { cn } from "@/lib/utils";
|
|
4
|
-
|
|
5
|
-
const buttonVariants = cva(
|
|
6
|
-
"inline-flex h-10 max-w-full items-center justify-center gap-2 rounded-md px-3.5 text-sm font-semibold leading-none transition focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-45",
|
|
7
|
-
{
|
|
8
|
-
variants: {
|
|
9
|
-
variant: {
|
|
10
|
-
default: "bg-primary text-primary-foreground shadow-[0_10px_30px_hsl(var(--primary)/0.16)] hover:bg-primary/92",
|
|
11
|
-
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/82",
|
|
12
|
-
ghost: "text-foreground hover:bg-muted",
|
|
13
|
-
outline: "border border-border bg-card/70 hover:bg-muted/88",
|
|
14
|
-
destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
|
|
15
|
-
},
|
|
16
|
-
size: {
|
|
17
|
-
sm: "h-8 px-2.5 text-xs",
|
|
18
|
-
md: "h-10 px-3.5",
|
|
19
|
-
icon: "h-10 w-10 px-0",
|
|
20
|
-
},
|
|
21
|
-
},
|
|
22
|
-
defaultVariants: {
|
|
23
|
-
variant: "default",
|
|
24
|
-
size: "md",
|
|
25
|
-
},
|
|
26
|
-
},
|
|
27
|
-
);
|
|
28
|
-
|
|
29
|
-
export type ButtonProps = React.ButtonHTMLAttributes<HTMLButtonElement> &
|
|
30
|
-
VariantProps<typeof buttonVariants>;
|
|
31
|
-
|
|
32
|
-
export const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
|
33
|
-
({ className, variant, size, ...props }, ref) => (
|
|
34
|
-
<button ref={ref} className={cn(buttonVariants({ variant, size, className }))} {...props} />
|
|
35
|
-
),
|
|
36
|
-
);
|
|
37
|
-
Button.displayName = "Button";
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import * as React from "react";
|
|
2
|
-
import { cn } from "@/lib/utils";
|
|
3
|
-
|
|
4
|
-
export function Card({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
|
|
5
|
-
return <section className={cn("premium-surface rounded-lg text-card-foreground", className)} {...props} />;
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export function CardHeader({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
|
|
9
|
-
return <div className={cn("flex flex-col gap-1.5 p-5", className)} {...props} />;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export function CardTitle({ className, ...props }: React.HTMLAttributes<HTMLHeadingElement>) {
|
|
13
|
-
return <h2 className={cn("text-base font-semibold tracking-normal", className)} {...props} />;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export function CardDescription({ className, ...props }: React.HTMLAttributes<HTMLParagraphElement>) {
|
|
17
|
-
return <p className={cn("text-sm leading-6 text-muted-foreground", className)} {...props} />;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export function CardContent({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
|
|
21
|
-
return <div className={cn("p-5 pt-0", className)} {...props} />;
|
|
22
|
-
}
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import * as React from "react";
|
|
2
|
-
import { cn } from "@/lib/utils";
|
|
3
|
-
|
|
4
|
-
export const Input = React.forwardRef<HTMLInputElement, React.InputHTMLAttributes<HTMLInputElement>>(
|
|
5
|
-
({ className, ...props }, ref) => (
|
|
6
|
-
<input
|
|
7
|
-
ref={ref}
|
|
8
|
-
className={cn(
|
|
9
|
-
"h-10 w-full rounded-md border border-input bg-background/70 px-3 text-sm outline-none transition placeholder:text-muted-foreground focus:border-ring focus:ring-2 focus:ring-ring/35 disabled:cursor-not-allowed disabled:opacity-50",
|
|
10
|
-
className,
|
|
11
|
-
)}
|
|
12
|
-
{...props}
|
|
13
|
-
/>
|
|
14
|
-
),
|
|
15
|
-
);
|
|
16
|
-
Input.displayName = "Input";
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import * as React from "react";
|
|
2
|
-
import { cn } from "@/lib/utils";
|
|
3
|
-
|
|
4
|
-
export const Textarea = React.forwardRef<HTMLTextAreaElement, React.TextareaHTMLAttributes<HTMLTextAreaElement>>(
|
|
5
|
-
({ className, ...props }, ref) => (
|
|
6
|
-
<textarea
|
|
7
|
-
ref={ref}
|
|
8
|
-
className={cn(
|
|
9
|
-
"min-h-28 w-full resize-y rounded-md border border-input bg-background/70 px-3 py-3 text-sm leading-6 outline-none transition placeholder:text-muted-foreground focus:border-ring focus:ring-2 focus:ring-ring/35 disabled:cursor-not-allowed disabled:opacity-50",
|
|
10
|
-
className,
|
|
11
|
-
)}
|
|
12
|
-
{...props}
|
|
13
|
-
/>
|
|
14
|
-
),
|
|
15
|
-
);
|
|
16
|
-
Textarea.displayName = "Textarea";
|