@adea-ai/ui 0.10.8
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 +5 -0
- package/components.json +19 -0
- package/package.json +56 -0
- package/src/components/account-drawer.tsx +133 -0
- package/src/components/on-screen-controls.tsx +156 -0
- package/src/components/prop-catalog.tsx +583 -0
- package/src/components/scene-settings.tsx +202 -0
- package/src/components/theme-provider.tsx +30 -0
- package/src/components/theme-toggle.tsx +73 -0
- package/src/components/ui/badge.tsx +49 -0
- package/src/components/ui/button.tsx +60 -0
- package/src/components/ui/card.tsx +18 -0
- package/src/components/ui/dialog.tsx +129 -0
- package/src/components/ui/drawer.tsx +209 -0
- package/src/components/ui/dropdown-menu.tsx +258 -0
- package/src/components/ui/empty.tsx +94 -0
- package/src/components/ui/field.tsx +224 -0
- package/src/components/ui/input.tsx +20 -0
- package/src/components/ui/label.tsx +20 -0
- package/src/components/ui/radio-group.tsx +38 -0
- package/src/components/ui/separator.tsx +21 -0
- package/src/components/ui/skeleton.tsx +13 -0
- package/src/components/ui/spinner.tsx +16 -0
- package/src/components/ui/switch.tsx +32 -0
- package/src/components/ui/tabs.tsx +75 -0
- package/src/components/ui/toggle-group.tsx +87 -0
- package/src/components/ui/toggle.tsx +45 -0
- package/src/components/ui/tooltip.tsx +56 -0
- package/src/components/version-dialog.tsx +366 -0
- package/src/components/workspace-brand.tsx +18 -0
- package/src/components/workspace-logo.tsx +17 -0
- package/src/index.tsx +55 -0
- package/src/lib/utils.ts +6 -0
- package/src/lib/version-notes.ts +30 -0
- package/src/styles/auth-shell.css +67 -0
- package/src/styles/conventional-workspace.css +2846 -0
- package/src/styles/globals.css +93 -0
- package/src/styles/theme.css +94 -0
- package/src/styles/workspace-shell.css +145 -0
- package/tsconfig.json +10 -0
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
4
|
+
import {
|
|
5
|
+
Check,
|
|
6
|
+
Download,
|
|
7
|
+
ExternalLink,
|
|
8
|
+
FileText,
|
|
9
|
+
LoaderCircle,
|
|
10
|
+
RefreshCw,
|
|
11
|
+
Sparkles,
|
|
12
|
+
} from "lucide-react";
|
|
13
|
+
|
|
14
|
+
import { formatReleaseDate, plainTextFromMarkdown } from "#lib/version-notes";
|
|
15
|
+
import { Button } from "#components/ui/button";
|
|
16
|
+
import {
|
|
17
|
+
Dialog,
|
|
18
|
+
DialogClose,
|
|
19
|
+
DialogContent,
|
|
20
|
+
DialogDescription,
|
|
21
|
+
DialogFooter,
|
|
22
|
+
DialogHeader,
|
|
23
|
+
DialogTitle,
|
|
24
|
+
DialogTrigger,
|
|
25
|
+
} from "#components/ui/dialog";
|
|
26
|
+
|
|
27
|
+
export type SharedDesktopUpdate = Readonly<{
|
|
28
|
+
available_version: string | null;
|
|
29
|
+
changelog: string;
|
|
30
|
+
current_version: string;
|
|
31
|
+
downloaded_bytes: number;
|
|
32
|
+
error: string | null;
|
|
33
|
+
github_url: string;
|
|
34
|
+
phase:
|
|
35
|
+
| "idle"
|
|
36
|
+
| "checking"
|
|
37
|
+
| "current"
|
|
38
|
+
| "available"
|
|
39
|
+
| "downloading"
|
|
40
|
+
| "installing"
|
|
41
|
+
| "installed"
|
|
42
|
+
| "failed";
|
|
43
|
+
release_date: string | null;
|
|
44
|
+
release_notes: string | null;
|
|
45
|
+
restart_required: boolean;
|
|
46
|
+
total_bytes: number | null;
|
|
47
|
+
}>;
|
|
48
|
+
|
|
49
|
+
export type VersionDialogAdapter = Readonly<{
|
|
50
|
+
check(): Promise<SharedDesktopUpdate>;
|
|
51
|
+
getStatus(): Promise<SharedDesktopUpdate>;
|
|
52
|
+
install(expectedVersion: string): Promise<SharedDesktopUpdate>;
|
|
53
|
+
isDesktopRuntime(): boolean;
|
|
54
|
+
}>;
|
|
55
|
+
|
|
56
|
+
function errorMessage(caught: unknown, fallback: string): string {
|
|
57
|
+
if (caught instanceof Error && caught.message) return caught.message;
|
|
58
|
+
if (typeof caught === "string" && caught) return caught;
|
|
59
|
+
return fallback;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function phaseLabel(update: SharedDesktopUpdate | null, fallbackVersion: string): string {
|
|
63
|
+
if (!update) return `Agent HQ v${fallbackVersion}`;
|
|
64
|
+
if (update.phase === "checking") return "Checking for updates…";
|
|
65
|
+
if (update.phase === "available" && update.available_version) {
|
|
66
|
+
return `Update v${update.available_version} available`;
|
|
67
|
+
}
|
|
68
|
+
if (update.phase === "downloading" || update.phase === "installing") {
|
|
69
|
+
return "Installing update…";
|
|
70
|
+
}
|
|
71
|
+
if (update.phase === "failed") return `Version ${update.current_version} · Retry`;
|
|
72
|
+
return `Agent HQ v${update.current_version || fallbackVersion}`;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function isUpdateBusy(update: SharedDesktopUpdate | null): boolean {
|
|
76
|
+
return (
|
|
77
|
+
update?.phase === "checking" ||
|
|
78
|
+
update?.phase === "downloading" ||
|
|
79
|
+
update?.phase === "installing"
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function VersionDialog({
|
|
84
|
+
adapter,
|
|
85
|
+
fallbackVersion = "0.1.0",
|
|
86
|
+
onOpenChange,
|
|
87
|
+
open: controlledOpen,
|
|
88
|
+
}: Readonly<{
|
|
89
|
+
adapter: VersionDialogAdapter;
|
|
90
|
+
fallbackVersion?: string;
|
|
91
|
+
onOpenChange?: (open: boolean) => void;
|
|
92
|
+
open?: boolean;
|
|
93
|
+
}>) {
|
|
94
|
+
const [uncontrolledOpen, setUncontrolledOpen] = useState(false);
|
|
95
|
+
const open = controlledOpen ?? uncontrolledOpen;
|
|
96
|
+
const setOpen = useCallback(
|
|
97
|
+
(nextOpen: boolean) => {
|
|
98
|
+
if (controlledOpen === undefined) setUncontrolledOpen(nextOpen);
|
|
99
|
+
onOpenChange?.(nextOpen);
|
|
100
|
+
},
|
|
101
|
+
[controlledOpen, onOpenChange]
|
|
102
|
+
);
|
|
103
|
+
const [desktopRuntime, setDesktopRuntime] = useState(false);
|
|
104
|
+
const [update, setUpdate] = useState<SharedDesktopUpdate | null>(null);
|
|
105
|
+
const [busy, setBusy] = useState(false);
|
|
106
|
+
const [error, setError] = useState("");
|
|
107
|
+
|
|
108
|
+
useEffect(() => setDesktopRuntime(adapter.isDesktopRuntime()), [adapter]);
|
|
109
|
+
|
|
110
|
+
const loadCurrentStatus = useCallback(async () => {
|
|
111
|
+
if (!desktopRuntime) return;
|
|
112
|
+
try {
|
|
113
|
+
setUpdate(await adapter.getStatus());
|
|
114
|
+
} catch (caught) {
|
|
115
|
+
setError(errorMessage(caught, "Version status is unavailable"));
|
|
116
|
+
}
|
|
117
|
+
}, [adapter, desktopRuntime]);
|
|
118
|
+
|
|
119
|
+
useEffect(() => {
|
|
120
|
+
if (desktopRuntime) void loadCurrentStatus();
|
|
121
|
+
}, [desktopRuntime, loadCurrentStatus]);
|
|
122
|
+
|
|
123
|
+
const checkForUpdates = useCallback(async () => {
|
|
124
|
+
if (!desktopRuntime) {
|
|
125
|
+
setError("Update checks are available from the desktop app.");
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
setBusy(true);
|
|
129
|
+
setError("");
|
|
130
|
+
try {
|
|
131
|
+
setUpdate(await adapter.check());
|
|
132
|
+
} catch (caught) {
|
|
133
|
+
setError(errorMessage(caught, "Could not check for updates"));
|
|
134
|
+
await loadCurrentStatus();
|
|
135
|
+
} finally {
|
|
136
|
+
setBusy(false);
|
|
137
|
+
}
|
|
138
|
+
}, [adapter, desktopRuntime, loadCurrentStatus]);
|
|
139
|
+
|
|
140
|
+
useEffect(() => {
|
|
141
|
+
if (!open || !desktopRuntime) return;
|
|
142
|
+
let active = true;
|
|
143
|
+
setError("");
|
|
144
|
+
setBusy(true);
|
|
145
|
+
void (async () => {
|
|
146
|
+
try {
|
|
147
|
+
const current = await adapter.getStatus();
|
|
148
|
+
if (!active) return;
|
|
149
|
+
setUpdate(current);
|
|
150
|
+
const checked = await adapter.check();
|
|
151
|
+
if (active) setUpdate(checked);
|
|
152
|
+
} catch (caught) {
|
|
153
|
+
if (active) setError(errorMessage(caught, "Could not check for updates"));
|
|
154
|
+
} finally {
|
|
155
|
+
if (active) setBusy(false);
|
|
156
|
+
}
|
|
157
|
+
})();
|
|
158
|
+
return () => {
|
|
159
|
+
active = false;
|
|
160
|
+
};
|
|
161
|
+
}, [adapter, desktopRuntime, open]);
|
|
162
|
+
|
|
163
|
+
const install = async () => {
|
|
164
|
+
const version = update?.available_version;
|
|
165
|
+
if (!version) return;
|
|
166
|
+
setBusy(true);
|
|
167
|
+
setError("");
|
|
168
|
+
try {
|
|
169
|
+
setUpdate(await adapter.install(version));
|
|
170
|
+
} catch (caught) {
|
|
171
|
+
setError(errorMessage(caught, "Update installation failed"));
|
|
172
|
+
await loadCurrentStatus();
|
|
173
|
+
} finally {
|
|
174
|
+
setBusy(false);
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
const currentChangelog = useMemo(
|
|
179
|
+
() => plainTextFromMarkdown(update?.changelog || "Changelog is loading…"),
|
|
180
|
+
[update?.changelog]
|
|
181
|
+
);
|
|
182
|
+
const releaseNotes = update?.release_notes ? plainTextFromMarkdown(update.release_notes) : "";
|
|
183
|
+
const busyFromSnapshot = isUpdateBusy(update);
|
|
184
|
+
|
|
185
|
+
return (
|
|
186
|
+
<Dialog open={open} onOpenChange={setOpen}>
|
|
187
|
+
{controlledOpen === undefined ? (
|
|
188
|
+
<DialogTrigger
|
|
189
|
+
render={
|
|
190
|
+
<Button
|
|
191
|
+
type="button"
|
|
192
|
+
variant="ghost"
|
|
193
|
+
size="sm"
|
|
194
|
+
aria-label="Open version and updates dialog"
|
|
195
|
+
aria-haspopup="dialog"
|
|
196
|
+
onClick={() => setOpen(true)}
|
|
197
|
+
/>
|
|
198
|
+
}
|
|
199
|
+
>
|
|
200
|
+
{update?.phase === "available" ? (
|
|
201
|
+
<Sparkles aria-hidden="true" />
|
|
202
|
+
) : (
|
|
203
|
+
<FileText aria-hidden="true" />
|
|
204
|
+
)}
|
|
205
|
+
{phaseLabel(update, fallbackVersion)}
|
|
206
|
+
</DialogTrigger>
|
|
207
|
+
) : null}
|
|
208
|
+
|
|
209
|
+
<DialogContent className="max-w-3xl">
|
|
210
|
+
<DialogHeader>
|
|
211
|
+
<div className="flex items-center gap-3">
|
|
212
|
+
<span className="flex size-10 items-center justify-center rounded-xl bg-primary text-primary-foreground">
|
|
213
|
+
<Sparkles className="size-5" aria-hidden="true" />
|
|
214
|
+
</span>
|
|
215
|
+
<div>
|
|
216
|
+
<DialogTitle>Version & updates</DialogTitle>
|
|
217
|
+
<DialogDescription>
|
|
218
|
+
Keep Agent HQ current and review what changed in each release.
|
|
219
|
+
</DialogDescription>
|
|
220
|
+
</div>
|
|
221
|
+
</div>
|
|
222
|
+
</DialogHeader>
|
|
223
|
+
|
|
224
|
+
<div className="min-h-0 space-y-5 overflow-y-auto p-6">
|
|
225
|
+
<section className="rounded-xl border bg-background/45 p-4" aria-label="Version status">
|
|
226
|
+
<div className="flex flex-wrap items-start justify-between gap-4">
|
|
227
|
+
<div className="space-y-1">
|
|
228
|
+
<p className="text-xs font-semibold uppercase tracking-[0.14em] text-muted-foreground">
|
|
229
|
+
Installed version
|
|
230
|
+
</p>
|
|
231
|
+
<p className="text-xl font-semibold tracking-tight">
|
|
232
|
+
v{update?.current_version || fallbackVersion}
|
|
233
|
+
</p>
|
|
234
|
+
<p className="text-sm text-muted-foreground">
|
|
235
|
+
{update?.phase === "current"
|
|
236
|
+
? "You are running the latest desktop release."
|
|
237
|
+
: update?.phase === "available" && update.available_version
|
|
238
|
+
? `A newer desktop release, v${update.available_version}, is ready.`
|
|
239
|
+
: desktopRuntime
|
|
240
|
+
? "Check the release channel for the latest signed build."
|
|
241
|
+
: "Open this dialog inside the desktop app to check for updates."}
|
|
242
|
+
</p>
|
|
243
|
+
</div>
|
|
244
|
+
<Button
|
|
245
|
+
type="button"
|
|
246
|
+
variant="outline"
|
|
247
|
+
size="sm"
|
|
248
|
+
disabled={!desktopRuntime || busy || busyFromSnapshot}
|
|
249
|
+
onClick={() => void checkForUpdates()}
|
|
250
|
+
>
|
|
251
|
+
{busy || busyFromSnapshot ? (
|
|
252
|
+
<LoaderCircle className="animate-spin" aria-hidden="true" />
|
|
253
|
+
) : (
|
|
254
|
+
<RefreshCw aria-hidden="true" />
|
|
255
|
+
)}
|
|
256
|
+
Check latest version
|
|
257
|
+
</Button>
|
|
258
|
+
</div>
|
|
259
|
+
</section>
|
|
260
|
+
|
|
261
|
+
{update?.phase === "available" && update.available_version ? (
|
|
262
|
+
<section
|
|
263
|
+
className="rounded-xl border border-primary/35 bg-primary/8 p-4"
|
|
264
|
+
aria-label="Available update"
|
|
265
|
+
>
|
|
266
|
+
<div className="flex flex-wrap items-start justify-between gap-4">
|
|
267
|
+
<div className="space-y-1">
|
|
268
|
+
<p className="flex items-center gap-2 text-sm font-semibold">
|
|
269
|
+
<Sparkles className="size-4 text-primary" aria-hidden="true" />
|
|
270
|
+
Version {update.available_version} is ready
|
|
271
|
+
</p>
|
|
272
|
+
<p className="text-sm text-muted-foreground">
|
|
273
|
+
The signed installer will be verified before Agent HQ restarts.
|
|
274
|
+
</p>
|
|
275
|
+
{formatReleaseDate(update.release_date) ? (
|
|
276
|
+
<p className="text-xs text-muted-foreground">
|
|
277
|
+
Released {formatReleaseDate(update.release_date)}
|
|
278
|
+
</p>
|
|
279
|
+
) : null}
|
|
280
|
+
</div>
|
|
281
|
+
<Button
|
|
282
|
+
type="button"
|
|
283
|
+
size="sm"
|
|
284
|
+
disabled={busy || busyFromSnapshot}
|
|
285
|
+
onClick={() => void install()}
|
|
286
|
+
>
|
|
287
|
+
{busy || busyFromSnapshot ? (
|
|
288
|
+
<LoaderCircle className="animate-spin" aria-hidden="true" />
|
|
289
|
+
) : (
|
|
290
|
+
<Download aria-hidden="true" />
|
|
291
|
+
)}
|
|
292
|
+
Install and restart
|
|
293
|
+
</Button>
|
|
294
|
+
</div>
|
|
295
|
+
</section>
|
|
296
|
+
) : null}
|
|
297
|
+
|
|
298
|
+
{update?.phase === "current" ? (
|
|
299
|
+
<p
|
|
300
|
+
className="flex items-center gap-2 text-sm text-emerald-700 dark:text-emerald-300"
|
|
301
|
+
role="status"
|
|
302
|
+
>
|
|
303
|
+
<Check className="size-4" aria-hidden="true" />
|
|
304
|
+
Agent HQ is up to date.
|
|
305
|
+
</p>
|
|
306
|
+
) : null}
|
|
307
|
+
{error ? (
|
|
308
|
+
<p
|
|
309
|
+
className="rounded-lg border border-destructive/35 bg-destructive/8 px-3 py-2 text-sm text-destructive"
|
|
310
|
+
role="alert"
|
|
311
|
+
>
|
|
312
|
+
{error}
|
|
313
|
+
</p>
|
|
314
|
+
) : null}
|
|
315
|
+
|
|
316
|
+
{releaseNotes ? (
|
|
317
|
+
<section className="space-y-2" aria-labelledby="agent-hq-release-notes">
|
|
318
|
+
<div>
|
|
319
|
+
<h2 id="agent-hq-release-notes" className="text-sm font-semibold">
|
|
320
|
+
What changed in this release
|
|
321
|
+
</h2>
|
|
322
|
+
<p className="text-xs text-muted-foreground">
|
|
323
|
+
Release notes are shown as readable text.
|
|
324
|
+
</p>
|
|
325
|
+
</div>
|
|
326
|
+
<div className="max-h-52 overflow-y-auto whitespace-pre-wrap rounded-xl border bg-background/45 p-4 font-mono text-xs leading-5 text-muted-foreground">
|
|
327
|
+
{releaseNotes}
|
|
328
|
+
</div>
|
|
329
|
+
</section>
|
|
330
|
+
) : null}
|
|
331
|
+
|
|
332
|
+
<section className="space-y-2" aria-labelledby="agent-hq-changelog">
|
|
333
|
+
<div>
|
|
334
|
+
<h2 id="agent-hq-changelog" className="text-sm font-semibold">
|
|
335
|
+
Installed changelog
|
|
336
|
+
</h2>
|
|
337
|
+
<p className="text-xs text-muted-foreground">
|
|
338
|
+
A plain-text history of the installed channel.
|
|
339
|
+
</p>
|
|
340
|
+
</div>
|
|
341
|
+
<div className="max-h-72 overflow-y-auto whitespace-pre-wrap rounded-xl border bg-background/45 p-4 font-mono text-xs leading-5 text-muted-foreground">
|
|
342
|
+
{currentChangelog}
|
|
343
|
+
</div>
|
|
344
|
+
</section>
|
|
345
|
+
</div>
|
|
346
|
+
|
|
347
|
+
<DialogFooter>
|
|
348
|
+
{update?.github_url ? (
|
|
349
|
+
<Button
|
|
350
|
+
type="button"
|
|
351
|
+
variant="ghost"
|
|
352
|
+
size="sm"
|
|
353
|
+
onClick={() => window.open(update.github_url, "_blank", "noopener,noreferrer")}
|
|
354
|
+
>
|
|
355
|
+
<ExternalLink aria-hidden="true" />
|
|
356
|
+
View releases
|
|
357
|
+
</Button>
|
|
358
|
+
) : null}
|
|
359
|
+
<DialogClose render={<Button type="button" variant="outline" size="sm" />}>
|
|
360
|
+
Close
|
|
361
|
+
</DialogClose>
|
|
362
|
+
</DialogFooter>
|
|
363
|
+
</DialogContent>
|
|
364
|
+
</Dialog>
|
|
365
|
+
);
|
|
366
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export interface WorkspaceBrandProps {
|
|
2
|
+
title: string;
|
|
3
|
+
eyebrow?: string;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export function WorkspaceBrand({ title, eyebrow = "AGENT OPERATIONS" }: WorkspaceBrandProps) {
|
|
7
|
+
return (
|
|
8
|
+
<div className="workspace-brand">
|
|
9
|
+
<div className="workspace-brand__mark" aria-hidden="true">
|
|
10
|
+
<span>HQ</span>
|
|
11
|
+
</div>
|
|
12
|
+
<div>
|
|
13
|
+
<p className="workspace-eyebrow">{eyebrow}</p>
|
|
14
|
+
<h1>{title}</h1>
|
|
15
|
+
</div>
|
|
16
|
+
</div>
|
|
17
|
+
);
|
|
18
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { SVGProps } from "react";
|
|
2
|
+
|
|
3
|
+
/** The canonical Agent HQ mark used by web, desktop, and dialogs. */
|
|
4
|
+
export function WorkspaceLogo(props: SVGProps<SVGSVGElement>) {
|
|
5
|
+
return (
|
|
6
|
+
<svg viewBox="0 0 64 64" role="img" aria-label="Agent HQ" {...props}>
|
|
7
|
+
<rect width="64" height="64" rx="16" fill="#111827" />
|
|
8
|
+
<path d="M10 16h7v13h9V16h7v32h-7V36h-9v12h-7z" fill="#f8fafc" />
|
|
9
|
+
<path
|
|
10
|
+
d="M45 16c-7.18 0-13 6-13 16s5.82 16 13 16c7.18 0 13-6 13-16s-5.82-16-13-16Zm0 7c3.42 0 6 3.46 6 9s-2.58 9-6 9-6-3.46-6-9 2.58-9 6-9Z"
|
|
11
|
+
fill="#f8fafc"
|
|
12
|
+
fillRule="evenodd"
|
|
13
|
+
/>
|
|
14
|
+
<path d="m45 35 11 11-4 4-11-11z" fill="#f8fafc" />
|
|
15
|
+
</svg>
|
|
16
|
+
);
|
|
17
|
+
}
|
package/src/index.tsx
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
export { Button, buttonVariants } from "./components/ui/button";
|
|
2
|
+
export { Switch } from "./components/ui/switch";
|
|
3
|
+
export { Toggle } from "./components/ui/toggle";
|
|
4
|
+
export { ToggleGroup, ToggleGroupItem } from "./components/ui/toggle-group";
|
|
5
|
+
export { RadioGroup, RadioGroupItem } from "./components/ui/radio-group";
|
|
6
|
+
export { ThemeToggle } from "./components/theme-toggle";
|
|
7
|
+
export { ThemeProvider } from "./components/theme-provider";
|
|
8
|
+
export {
|
|
9
|
+
Drawer,
|
|
10
|
+
DrawerClose,
|
|
11
|
+
DrawerContent,
|
|
12
|
+
DrawerDescription,
|
|
13
|
+
DrawerFooter,
|
|
14
|
+
DrawerHeader,
|
|
15
|
+
DrawerOverlay,
|
|
16
|
+
DrawerPortal,
|
|
17
|
+
DrawerSwipeHandle,
|
|
18
|
+
DrawerTitle,
|
|
19
|
+
DrawerTrigger,
|
|
20
|
+
} from "./components/ui/drawer";
|
|
21
|
+
export { AccountDrawer, type AccountDrawerProps } from "./components/account-drawer";
|
|
22
|
+
export { WorkspaceBrand, type WorkspaceBrandProps } from "./components/workspace-brand";
|
|
23
|
+
export { WorkspaceLogo } from "./components/workspace-logo";
|
|
24
|
+
export { OnScreenControls } from "./components/on-screen-controls";
|
|
25
|
+
export { SceneSettings, type SceneSettingsProps } from "./components/scene-settings";
|
|
26
|
+
export {
|
|
27
|
+
VersionDialog,
|
|
28
|
+
type SharedDesktopUpdate,
|
|
29
|
+
type VersionDialogAdapter,
|
|
30
|
+
} from "./components/version-dialog";
|
|
31
|
+
export { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "./components/ui/tooltip";
|
|
32
|
+
export { Card, CardContent } from "./components/ui/card";
|
|
33
|
+
export { Spinner } from "./components/ui/spinner";
|
|
34
|
+
export {
|
|
35
|
+
Dialog,
|
|
36
|
+
DialogClose,
|
|
37
|
+
DialogContent,
|
|
38
|
+
DialogDescription,
|
|
39
|
+
DialogFooter,
|
|
40
|
+
DialogHeader,
|
|
41
|
+
DialogOverlay,
|
|
42
|
+
DialogPortal,
|
|
43
|
+
DialogTitle,
|
|
44
|
+
DialogTrigger,
|
|
45
|
+
} from "./components/ui/dialog";
|
|
46
|
+
export {
|
|
47
|
+
ModelThumbnail,
|
|
48
|
+
PropCatalog,
|
|
49
|
+
defaultPropCatalogCategories,
|
|
50
|
+
getSharedLoader,
|
|
51
|
+
type PropCatalogCategory,
|
|
52
|
+
type PropCatalogItem,
|
|
53
|
+
type PropCatalogProps,
|
|
54
|
+
} from "./components/prop-catalog";
|
|
55
|
+
export { cn } from "./lib/utils";
|
package/src/lib/utils.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export function plainTextFromMarkdown(markdown: string): string {
|
|
2
|
+
return markdown
|
|
3
|
+
.replace(/\r\n/g, "\n")
|
|
4
|
+
.replace(/!\[([^\]]*)\]\([^)]*\)/g, "$1")
|
|
5
|
+
.replace(/\[([^\]]+)\]\([^)]*\)/g, "$1")
|
|
6
|
+
.replace(/^[ \t]*#{1,6}[ \t]*/gm, "")
|
|
7
|
+
.replace(/^[ \t]*>[ \t]?/gm, "")
|
|
8
|
+
.replace(/^[ \t]*---+[ \t]*$/gm, "")
|
|
9
|
+
.replace(/^[ \t]*[-*+][ \t]+/gm, "• ")
|
|
10
|
+
.replace(/^[ \t]*\d+\.[ \t]+/gm, "")
|
|
11
|
+
.replace(/```[^\n]*\n?/g, "")
|
|
12
|
+
.replace(/`([^`]+)`/g, "$1")
|
|
13
|
+
.replace(/\*\*([^*]+)\*\*/g, "$1")
|
|
14
|
+
.replace(/__([^_]+)__/g, "$1")
|
|
15
|
+
.replace(/\*([^*]+)\*/g, "$1")
|
|
16
|
+
.replace(/_([^_]+)_/g, "$1")
|
|
17
|
+
.replace(/\n{3,}/g, "\n\n")
|
|
18
|
+
.trim();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function formatReleaseDate(value: string | null): string | null {
|
|
22
|
+
if (!value) return null;
|
|
23
|
+
const date = new Date(value);
|
|
24
|
+
if (Number.isNaN(date.getTime())) return value;
|
|
25
|
+
return new Intl.DateTimeFormat(undefined, {
|
|
26
|
+
day: "numeric",
|
|
27
|
+
month: "short",
|
|
28
|
+
year: "numeric",
|
|
29
|
+
}).format(date);
|
|
30
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
.auth-shell {
|
|
2
|
+
--auth-background: var(--hq-shell-background);
|
|
3
|
+
--auth-surface: var(--hq-shell-surface);
|
|
4
|
+
--auth-surface-strong: var(--hq-shell-surface-strong);
|
|
5
|
+
--auth-border: var(--hq-shell-border);
|
|
6
|
+
--auth-border-subtle: var(--hq-shell-border-subtle);
|
|
7
|
+
--auth-foreground: var(--hq-shell-foreground);
|
|
8
|
+
--auth-muted: var(--hq-shell-muted);
|
|
9
|
+
--auth-muted-strong: var(--hq-shell-muted);
|
|
10
|
+
--auth-accent: var(--hq-shell-accent);
|
|
11
|
+
--auth-accent-hover: var(--hq-shell-accent-strong);
|
|
12
|
+
--auth-accent-foreground: var(--hq-shell-accent-foreground);
|
|
13
|
+
|
|
14
|
+
display: grid;
|
|
15
|
+
min-height: 100svh;
|
|
16
|
+
place-items: center;
|
|
17
|
+
padding: 1.5rem;
|
|
18
|
+
background:
|
|
19
|
+
linear-gradient(90deg, rgb(255 255 255 / 4%) 1px, transparent 1px) 0 0 / 4rem 4rem,
|
|
20
|
+
var(--auth-background);
|
|
21
|
+
color: var(--auth-foreground);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.auth-panel {
|
|
25
|
+
width: min(100%, 40rem);
|
|
26
|
+
padding: clamp(1.5rem, 5vw, 3.5rem);
|
|
27
|
+
border: 1px solid var(--auth-border);
|
|
28
|
+
border-radius: 0.75rem;
|
|
29
|
+
background: var(--auth-surface);
|
|
30
|
+
box-shadow: 0 1.5rem 4rem rgb(0 0 0 / 24%);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.auth-eyebrow {
|
|
34
|
+
margin: 0 0 0.75rem;
|
|
35
|
+
color: var(--auth-accent);
|
|
36
|
+
font-size: 0.75rem;
|
|
37
|
+
font-weight: 800;
|
|
38
|
+
letter-spacing: 0.14em;
|
|
39
|
+
text-transform: uppercase;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.auth-title {
|
|
43
|
+
max-width: 13ch;
|
|
44
|
+
margin: 0;
|
|
45
|
+
font-size: clamp(2rem, 7vw, 3.5rem);
|
|
46
|
+
line-height: 1;
|
|
47
|
+
letter-spacing: -0.04em;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.auth-introduction {
|
|
51
|
+
max-width: 52ch;
|
|
52
|
+
margin: 1.5rem 0;
|
|
53
|
+
color: var(--auth-muted);
|
|
54
|
+
line-height: 1.6;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
@media (max-width: 30rem) {
|
|
58
|
+
.auth-shell {
|
|
59
|
+
padding: 0;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.auth-panel {
|
|
63
|
+
min-height: 100svh;
|
|
64
|
+
border: 0;
|
|
65
|
+
border-radius: 0;
|
|
66
|
+
}
|
|
67
|
+
}
|