@tangle-network/sandbox-ui 0.125.2 → 0.126.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 +1 -2
- package/dist/assets.d.ts +1 -27
- package/dist/assets.js +2 -76
- package/dist/{dashboard-Ce1Qy0Yv.js → dashboard-TI-fPoih.js} +43 -1024
- package/dist/dashboard.d.ts +3 -3
- package/dist/dashboard.js +3 -3
- package/dist/globals.css +0 -253
- package/dist/{index-B_Ae4rdp.d.ts → index-Bww3rzEG.d.ts} +1 -41
- package/dist/{index-CV-BKrDF.d.ts → index-DPTVn2nS.d.ts} +1 -1
- package/dist/{index-Qm_yQvZh.d.ts → index-DXxi5EtM.d.ts} +26 -208
- package/dist/{index-DloDwgbc.d.ts → index-Du0ovoHu.d.ts} +2 -2
- package/dist/index.d.ts +6 -6
- package/dist/index.js +6 -6
- package/dist/{page-layout-DJV7cbxx.js → page-layout-2IRtfx51.js} +2 -38
- package/dist/pages.d.ts +20 -139
- package/dist/pages.js +28 -2073
- package/dist/{primitives-DLE_MCwZ.js → primitives-WP3v1yf0.js} +2 -2
- package/dist/primitives.d.ts +1 -1
- package/dist/primitives.js +1 -1
- package/dist/styles.css +0 -253
- package/dist/{template-card-L-4gmjx2.d.ts → usage-chart-BjM8ShvX.d.ts} +1 -16
- package/dist/{workbench-DLQ07SoM.js → workbench-BJlDJ8Cj.js} +16 -16
- package/dist/workbench.d.ts +1 -1
- package/dist/workbench.js +1 -1
- package/dist/{workspace-CjeZTLPQ.js → workspace-C68fk9qa.js} +3 -194
- package/dist/workspace.d.ts +2 -2
- package/dist/workspace.js +2 -2
- package/package.json +1 -1
package/dist/pages.js
CHANGED
|
@@ -1,21 +1,25 @@
|
|
|
1
|
-
import { J as Input$1, Rt as Textarea$1, S as DialogHeader$1, T as DialogTitle$1, _ as Dialog$1, a as Badge$1, at as SelectItem$1, b as DialogDescription$1, dt as SelectValue$1, nt as Select$1, o as Button$1, rt as SelectContent$1, ut as SelectTrigger$1, vt as Switch, x as DialogFooter$1, y as DialogContent$1 } from "./primitives-DLE_MCwZ.js";
|
|
2
1
|
import { t as cn$1 } from "./utils-DzWbktWG.js";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
2
|
+
import { i as PricingPage, o as BillingDashboard, r as UsageChart, t as DashboardPageHeader } from "./page-layout-2IRtfx51.js";
|
|
3
|
+
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, Skeleton, SkeletonCard, Tabs, TabsContent, TabsList, TabsTrigger } from "@tangle-network/ui/primitives";
|
|
5
4
|
import { Logo, TangleKnot } from "@tangle-network/brand";
|
|
6
5
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
7
6
|
import * as React$1 from "react";
|
|
8
7
|
import { useState } from "react";
|
|
9
|
-
import { AlertCircle,
|
|
10
|
-
import { focusField
|
|
8
|
+
import { AlertCircle, CheckCircle, ChevronDown, ChevronUp, Clock, Cpu, Eye, EyeOff, GripVertical, Key, Layers, Lock, MemoryStick, Pencil, Plus, Power, PowerOff, Terminal, Trash2, Upload } from "lucide-react";
|
|
9
|
+
import { focusField } from "@tangle-network/ui/utils";
|
|
11
10
|
//#region src/pages/auth-page.tsx
|
|
12
11
|
const C = {
|
|
13
|
-
pageBg: "
|
|
14
|
-
card: "
|
|
15
|
-
border: "
|
|
16
|
-
text: "
|
|
17
|
-
muted: "
|
|
18
|
-
inputBg: "#f5f5f4"
|
|
12
|
+
pageBg: "hsl(var(--background, 240 5% 97%))",
|
|
13
|
+
card: "hsl(var(--card, 0 0% 100%))",
|
|
14
|
+
border: "hsl(var(--border, 214 32% 91%))",
|
|
15
|
+
text: "hsl(var(--card-foreground, 222 47% 11%))",
|
|
16
|
+
muted: "hsl(var(--muted-foreground, 240 4% 46%))",
|
|
17
|
+
inputBg: "var(--bg-input, #f5f5f4)",
|
|
18
|
+
hover: "hsl(var(--muted, 240 5% 96%))",
|
|
19
|
+
danger: "hsl(var(--destructive, 0 72% 42%))",
|
|
20
|
+
ink: "hsl(var(--foreground, 222 47% 11%))",
|
|
21
|
+
inkHover: "hsl(var(--foreground, 222 47% 11%) / 0.88)",
|
|
22
|
+
onInk: "hsl(var(--background, 0 0% 100%))"
|
|
19
23
|
};
|
|
20
24
|
function GithubIcon() {
|
|
21
25
|
return /* @__PURE__ */ jsx("svg", {
|
|
@@ -61,12 +65,15 @@ const SOCIAL_ICON = {
|
|
|
61
65
|
github: /* @__PURE__ */ jsx(GithubIcon, {}),
|
|
62
66
|
google: /* @__PURE__ */ jsx(GoogleIcon, {})
|
|
63
67
|
};
|
|
64
|
-
function AuthPage({ product, tagline, mode = "signin", tangleAuthUrl = "/auth/tangle/start", providers = ["github", "google"], socialHref = (p) => `/api/auth/sign-in/social?provider=${p}&callbackURL=/app`, onEmailSubmit, collectName, altHref, accent
|
|
68
|
+
function AuthPage({ product, tagline, mode = "signin", tangleAuthUrl = "/auth/tangle/start", providers = ["github", "google"], socialHref = (p) => `/api/auth/sign-in/social?provider=${p}&callbackURL=/app`, onEmailSubmit, collectName, altHref, accent, accentHover, accentForeground, logoSize = "lg", className, style, children }) {
|
|
65
69
|
const [name, setName] = useState("");
|
|
66
70
|
const [email, setEmail] = useState("");
|
|
67
71
|
const [password, setPassword] = useState("");
|
|
68
72
|
const [error, setError] = useState("");
|
|
69
73
|
const [loading, setLoading] = useState(false);
|
|
74
|
+
const buttonBg = accent ?? C.ink;
|
|
75
|
+
const buttonHoverBg = accentHover ?? (accent ? `color-mix(in oklch, ${accent} 88%, transparent)` : C.inkHover);
|
|
76
|
+
const buttonFg = accentForeground ?? (accent ? "#fff" : C.onInk);
|
|
70
77
|
const isSignup = mode === "signup";
|
|
71
78
|
const showName = collectName ?? isSignup;
|
|
72
79
|
const tangleLabel = isSignup ? "Sign up with Tangle" : "Continue with Tangle";
|
|
@@ -126,6 +133,7 @@ function AuthPage({ product, tagline, mode = "signin", tangleAuthUrl = "/auth/ta
|
|
|
126
133
|
width: "100%",
|
|
127
134
|
maxWidth: 384,
|
|
128
135
|
background: C.card,
|
|
136
|
+
color: C.text,
|
|
129
137
|
border: `1px solid ${C.border}`,
|
|
130
138
|
borderRadius: 16,
|
|
131
139
|
padding: 32,
|
|
@@ -158,16 +166,16 @@ function AuthPage({ product, tagline, mode = "signin", tangleAuthUrl = "/auth/ta
|
|
|
158
166
|
width: "100%",
|
|
159
167
|
padding: "10px 12px",
|
|
160
168
|
borderRadius: 8,
|
|
161
|
-
background:
|
|
162
|
-
border: "1px solid
|
|
163
|
-
color:
|
|
169
|
+
background: buttonBg,
|
|
170
|
+
border: "1px solid transparent",
|
|
171
|
+
color: buttonFg,
|
|
164
172
|
fontSize: 14,
|
|
165
173
|
fontWeight: 600,
|
|
166
174
|
textDecoration: "none",
|
|
167
175
|
transition: "background 120ms"
|
|
168
176
|
},
|
|
169
|
-
onMouseEnter: (e) => e.currentTarget.style.background =
|
|
170
|
-
onMouseLeave: (e) => e.currentTarget.style.background =
|
|
177
|
+
onMouseEnter: (e) => e.currentTarget.style.background = buttonHoverBg,
|
|
178
|
+
onMouseLeave: (e) => e.currentTarget.style.background = buttonBg,
|
|
171
179
|
children: [/* @__PURE__ */ jsx(TangleKnot, { size: 16 }), tangleLabel]
|
|
172
180
|
}),
|
|
173
181
|
providers.length > 0 && /* @__PURE__ */ jsx("div", {
|
|
@@ -183,7 +191,7 @@ function AuthPage({ product, tagline, mode = "signin", tangleAuthUrl = "/auth/ta
|
|
|
183
191
|
onClick: () => {
|
|
184
192
|
window.location.href = socialHref(p);
|
|
185
193
|
},
|
|
186
|
-
onMouseEnter: (e) => e.currentTarget.style.background =
|
|
194
|
+
onMouseEnter: (e) => e.currentTarget.style.background = C.hover,
|
|
187
195
|
onMouseLeave: (e) => e.currentTarget.style.background = "transparent",
|
|
188
196
|
children: [
|
|
189
197
|
SOCIAL_ICON[p],
|
|
@@ -258,7 +266,7 @@ function AuthPage({ product, tagline, mode = "signin", tangleAuthUrl = "/auth/ta
|
|
|
258
266
|
role: "alert",
|
|
259
267
|
style: {
|
|
260
268
|
fontSize: 14,
|
|
261
|
-
color:
|
|
269
|
+
color: C.danger
|
|
262
270
|
},
|
|
263
271
|
children: error
|
|
264
272
|
}),
|
|
@@ -579,2023 +587,6 @@ function BillingPage({ variant = "sandbox", initialData, apiBasePath = "", onMan
|
|
|
579
587
|
});
|
|
580
588
|
}
|
|
581
589
|
//#endregion
|
|
582
|
-
//#region src/pages/add-ssh-key-dialog.tsx
|
|
583
|
-
const NAME_INPUT_ID = "add-ssh-key-name";
|
|
584
|
-
const KEY_INPUT_ID = "add-ssh-key-public-key";
|
|
585
|
-
const SSH_PUBLIC_KEY_PATTERN = /^(ssh-rsa|ssh-dss|ssh-ed25519|ecdsa-sha2-nistp256|ecdsa-sha2-nistp384|ecdsa-sha2-nistp521|sk-ecdsa-sha2-nistp256@openssh\.com|sk-ssh-ed25519@openssh\.com)\s+[A-Za-z0-9+/]+={0,3}(\s+.+)?$/;
|
|
586
|
-
/** Validates an SSH public key string; returns an error message or null. */
|
|
587
|
-
function validateSshPublicKey(value) {
|
|
588
|
-
const trimmed = value.trim();
|
|
589
|
-
if (!trimmed) return "Public key is required";
|
|
590
|
-
if (!SSH_PUBLIC_KEY_PATTERN.test(trimmed)) return "Enter a valid public key, e.g. ssh-ed25519 AAAAC3NzaC1lZDI1NTE5...";
|
|
591
|
-
return null;
|
|
592
|
-
}
|
|
593
|
-
function AddSshKeyDialog({ open, onOpenChange, onCreateKey, onRefreshKeys, onCreatedKeyId }) {
|
|
594
|
-
const [name, setName] = React$1.useState("");
|
|
595
|
-
const [publicKey, setPublicKey] = React$1.useState("");
|
|
596
|
-
const [errors, setErrors] = React$1.useState({
|
|
597
|
-
name: null,
|
|
598
|
-
publicKey: null
|
|
599
|
-
});
|
|
600
|
-
const [submitError, setSubmitError] = React$1.useState(null);
|
|
601
|
-
const [isSaving, setIsSaving] = React$1.useState(false);
|
|
602
|
-
const resetForm = React$1.useCallback(() => {
|
|
603
|
-
setName("");
|
|
604
|
-
setPublicKey("");
|
|
605
|
-
setErrors({
|
|
606
|
-
name: null,
|
|
607
|
-
publicKey: null
|
|
608
|
-
});
|
|
609
|
-
setSubmitError(null);
|
|
610
|
-
}, []);
|
|
611
|
-
const handleSave = async () => {
|
|
612
|
-
const nameError = name.trim() ? null : "Name is required";
|
|
613
|
-
const keyError = validateSshPublicKey(publicKey);
|
|
614
|
-
if (nameError || keyError) {
|
|
615
|
-
setErrors({
|
|
616
|
-
name: nameError,
|
|
617
|
-
publicKey: keyError
|
|
618
|
-
});
|
|
619
|
-
return;
|
|
620
|
-
}
|
|
621
|
-
setIsSaving(true);
|
|
622
|
-
setSubmitError(null);
|
|
623
|
-
try {
|
|
624
|
-
const created = await onCreateKey({
|
|
625
|
-
name: name.trim(),
|
|
626
|
-
publicKey: publicKey.trim()
|
|
627
|
-
});
|
|
628
|
-
if (onRefreshKeys) await onRefreshKeys();
|
|
629
|
-
if (created && created.id) onCreatedKeyId?.(created.id);
|
|
630
|
-
resetForm();
|
|
631
|
-
onOpenChange(false);
|
|
632
|
-
} catch (err) {
|
|
633
|
-
setSubmitError(err instanceof Error ? err.message : "Failed to add SSH key");
|
|
634
|
-
} finally {
|
|
635
|
-
setIsSaving(false);
|
|
636
|
-
}
|
|
637
|
-
};
|
|
638
|
-
const handleSubmit = (e) => {
|
|
639
|
-
e.preventDefault();
|
|
640
|
-
if (!isSaving) handleSave();
|
|
641
|
-
};
|
|
642
|
-
return /* @__PURE__ */ jsx(Dialog$1, {
|
|
643
|
-
open,
|
|
644
|
-
onOpenChange,
|
|
645
|
-
children: /* @__PURE__ */ jsxs(DialogContent$1, {
|
|
646
|
-
className: "max-w-md",
|
|
647
|
-
children: [
|
|
648
|
-
/* @__PURE__ */ jsxs(DialogHeader$1, { children: [/* @__PURE__ */ jsx(DialogTitle$1, { children: "Add SSH key" }), /* @__PURE__ */ jsx(DialogDescription$1, { children: "Save a public key to your account. It will be available to select for this and future sandboxes." })] }),
|
|
649
|
-
/* @__PURE__ */ jsxs("form", {
|
|
650
|
-
onSubmit: handleSubmit,
|
|
651
|
-
className: "space-y-4",
|
|
652
|
-
children: [
|
|
653
|
-
/* @__PURE__ */ jsxs("div", {
|
|
654
|
-
className: "space-y-1.5",
|
|
655
|
-
children: [
|
|
656
|
-
/* @__PURE__ */ jsx("label", {
|
|
657
|
-
htmlFor: NAME_INPUT_ID,
|
|
658
|
-
className: "block text-xs font-medium uppercase tracking-[0.06em] text-muted-foreground",
|
|
659
|
-
children: "Name"
|
|
660
|
-
}),
|
|
661
|
-
/* @__PURE__ */ jsx("input", {
|
|
662
|
-
id: NAME_INPUT_ID,
|
|
663
|
-
name: "name",
|
|
664
|
-
type: "text",
|
|
665
|
-
value: name,
|
|
666
|
-
onChange: (e) => setName(e.target.value),
|
|
667
|
-
placeholder: "My laptop",
|
|
668
|
-
autoComplete: "off",
|
|
669
|
-
"aria-invalid": errors.name ? true : void 0,
|
|
670
|
-
className: `w-full rounded-md border bg-surface-container-low px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground ${focusField}`
|
|
671
|
-
}),
|
|
672
|
-
errors.name && /* @__PURE__ */ jsx("p", {
|
|
673
|
-
className: "text-xs text-destructive",
|
|
674
|
-
children: errors.name
|
|
675
|
-
})
|
|
676
|
-
]
|
|
677
|
-
}),
|
|
678
|
-
/* @__PURE__ */ jsxs("div", {
|
|
679
|
-
className: "space-y-1.5",
|
|
680
|
-
children: [
|
|
681
|
-
/* @__PURE__ */ jsx("label", {
|
|
682
|
-
htmlFor: KEY_INPUT_ID,
|
|
683
|
-
className: "block text-xs font-medium uppercase tracking-[0.06em] text-muted-foreground",
|
|
684
|
-
children: "Public key"
|
|
685
|
-
}),
|
|
686
|
-
/* @__PURE__ */ jsx("textarea", {
|
|
687
|
-
id: KEY_INPUT_ID,
|
|
688
|
-
name: "public-key",
|
|
689
|
-
value: publicKey,
|
|
690
|
-
onChange: (e) => setPublicKey(e.target.value),
|
|
691
|
-
placeholder: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI...",
|
|
692
|
-
autoComplete: "off",
|
|
693
|
-
spellCheck: false,
|
|
694
|
-
"aria-invalid": errors.publicKey ? true : void 0,
|
|
695
|
-
className: `min-h-24 w-full resize-y rounded-md border bg-surface-container-low px-3 py-2 font-mono text-xs text-foreground placeholder:text-muted-foreground ${focusField}`
|
|
696
|
-
}),
|
|
697
|
-
errors.publicKey && /* @__PURE__ */ jsx("p", {
|
|
698
|
-
className: "text-xs text-destructive",
|
|
699
|
-
children: errors.publicKey
|
|
700
|
-
})
|
|
701
|
-
]
|
|
702
|
-
}),
|
|
703
|
-
submitError && /* @__PURE__ */ jsx("p", {
|
|
704
|
-
role: "alert",
|
|
705
|
-
className: "text-sm text-destructive",
|
|
706
|
-
children: submitError
|
|
707
|
-
}),
|
|
708
|
-
/* @__PURE__ */ jsx("button", {
|
|
709
|
-
type: "submit",
|
|
710
|
-
className: "hidden",
|
|
711
|
-
tabIndex: -1,
|
|
712
|
-
"aria-hidden": "true",
|
|
713
|
-
children: "Submit"
|
|
714
|
-
})
|
|
715
|
-
]
|
|
716
|
-
}),
|
|
717
|
-
/* @__PURE__ */ jsxs(DialogFooter$1, { children: [/* @__PURE__ */ jsx("button", {
|
|
718
|
-
type: "button",
|
|
719
|
-
onClick: () => onOpenChange(false),
|
|
720
|
-
disabled: isSaving,
|
|
721
|
-
className: "rounded-md border border-[var(--md3-outline-variant)] bg-surface-container px-4 py-2 text-sm font-medium text-foreground hover:bg-surface-container-high transition-colors disabled:opacity-50",
|
|
722
|
-
children: "Cancel"
|
|
723
|
-
}), /* @__PURE__ */ jsxs("button", {
|
|
724
|
-
type: "button",
|
|
725
|
-
onClick: handleSave,
|
|
726
|
-
disabled: isSaving,
|
|
727
|
-
className: "inline-flex items-center gap-2 rounded-md bg-primary px-4 py-2 text-sm font-bold text-primary-foreground hover:bg-primary/90 transition-colors disabled:opacity-50 active:scale-[0.97]",
|
|
728
|
-
children: [isSaving && /* @__PURE__ */ jsx(Loader2, {
|
|
729
|
-
className: "h-4 w-4 animate-spin",
|
|
730
|
-
"data-motion": "essential"
|
|
731
|
-
}), isSaving ? "Saving..." : "Add key"]
|
|
732
|
-
})] })
|
|
733
|
-
]
|
|
734
|
-
})
|
|
735
|
-
});
|
|
736
|
-
}
|
|
737
|
-
//#endregion
|
|
738
|
-
//#region src/pages/provisioning-wizard.tsx
|
|
739
|
-
const VALID_DRIVERS = /* @__PURE__ */ new Set([
|
|
740
|
-
"docker",
|
|
741
|
-
"firecracker",
|
|
742
|
-
"tangle"
|
|
743
|
-
]);
|
|
744
|
-
const SECTION_CARD_CLASS = "rounded-lg border border-[var(--md3-outline-variant)] bg-surface-container p-5 shadow-sm";
|
|
745
|
-
const FIELD_LABEL_CLASS = "block text-xs font-medium text-muted-foreground uppercase tracking-[0.06em]";
|
|
746
|
-
const STACK_DISPLAY = {
|
|
747
|
-
universal: {
|
|
748
|
-
name: "Default",
|
|
749
|
-
abbr: "D",
|
|
750
|
-
color: "violet",
|
|
751
|
-
textClass: "text-[var(--surface-violet-text)]"
|
|
752
|
-
},
|
|
753
|
-
ethereum: {
|
|
754
|
-
name: "Ethereum",
|
|
755
|
-
abbr: "Ξ",
|
|
756
|
-
color: "blue",
|
|
757
|
-
textClass: "text-[var(--surface-info-text)]"
|
|
758
|
-
},
|
|
759
|
-
solana: {
|
|
760
|
-
name: "Solana",
|
|
761
|
-
abbr: "S",
|
|
762
|
-
color: "green",
|
|
763
|
-
textClass: "text-[var(--surface-success-text)]"
|
|
764
|
-
},
|
|
765
|
-
tangle: {
|
|
766
|
-
name: "Tangle",
|
|
767
|
-
abbr: "T",
|
|
768
|
-
color: "purple",
|
|
769
|
-
textClass: "text-[var(--surface-violet-text)]"
|
|
770
|
-
},
|
|
771
|
-
"ai-sdk": {
|
|
772
|
-
name: "AI SDK",
|
|
773
|
-
abbr: "AI",
|
|
774
|
-
color: "teal",
|
|
775
|
-
textClass: "text-[var(--surface-teal-text)]"
|
|
776
|
-
},
|
|
777
|
-
rust: {
|
|
778
|
-
name: "Rust",
|
|
779
|
-
abbr: "Rs",
|
|
780
|
-
color: "orange",
|
|
781
|
-
textClass: "text-[var(--surface-orange-text)]"
|
|
782
|
-
}
|
|
783
|
-
};
|
|
784
|
-
function resolveEnvironment(env) {
|
|
785
|
-
if (env.id.startsWith("template:")) {
|
|
786
|
-
const templateName = env.description?.replace(/^Template:\s*/, "") ?? "Custom Template";
|
|
787
|
-
return {
|
|
788
|
-
id: env.id,
|
|
789
|
-
name: templateName,
|
|
790
|
-
description: env.description ?? "User template from snapshot",
|
|
791
|
-
icon: /* @__PURE__ */ jsx("span", {
|
|
792
|
-
className: "text-[var(--surface-success-text)] text-2xl font-bold",
|
|
793
|
-
children: "T"
|
|
794
|
-
}),
|
|
795
|
-
color: "green"
|
|
796
|
-
};
|
|
797
|
-
}
|
|
798
|
-
const display = STACK_DISPLAY[env.id];
|
|
799
|
-
const name = display?.name ?? (env.id.length > 0 ? env.id.charAt(0).toUpperCase() + env.id.slice(1).replace(/-/g, " ") : "Unknown");
|
|
800
|
-
const abbr = display?.abbr ?? (env.id.length > 0 ? env.id[0].toUpperCase() : "?");
|
|
801
|
-
const color = display?.color ?? "slate";
|
|
802
|
-
const textClass = display?.textClass ?? "text-muted-foreground";
|
|
803
|
-
return {
|
|
804
|
-
id: env.id,
|
|
805
|
-
name,
|
|
806
|
-
description: env.description ?? `${name} development environment`,
|
|
807
|
-
icon: /* @__PURE__ */ jsx("span", {
|
|
808
|
-
className: `${textClass} text-2xl font-bold`,
|
|
809
|
-
children: abbr
|
|
810
|
-
}),
|
|
811
|
-
color
|
|
812
|
-
};
|
|
813
|
-
}
|
|
814
|
-
const defaultEnvironments = [
|
|
815
|
-
{
|
|
816
|
-
id: "node",
|
|
817
|
-
name: "Node.js",
|
|
818
|
-
description: "v20.x LTS with optimized runtime for asynchronous event-driven agents.",
|
|
819
|
-
icon: /* @__PURE__ */ jsx("span", {
|
|
820
|
-
className: "text-[var(--code-success)] text-2xl font-bold",
|
|
821
|
-
children: "N"
|
|
822
|
-
}),
|
|
823
|
-
color: "green"
|
|
824
|
-
},
|
|
825
|
-
{
|
|
826
|
-
id: "python",
|
|
827
|
-
name: "Python",
|
|
828
|
-
description: "v3.11 pre-installed with PyTorch and common data science libraries.",
|
|
829
|
-
icon: /* @__PURE__ */ jsx("span", {
|
|
830
|
-
className: "text-sky-400 text-2xl font-bold",
|
|
831
|
-
children: "Py"
|
|
832
|
-
}),
|
|
833
|
-
color: "blue"
|
|
834
|
-
},
|
|
835
|
-
{
|
|
836
|
-
id: "ubuntu",
|
|
837
|
-
name: "Ubuntu",
|
|
838
|
-
description: "Full 22.04 LTS terminal access for custom containerized workloads.",
|
|
839
|
-
icon: /* @__PURE__ */ jsx("span", {
|
|
840
|
-
className: "text-orange-400 text-2xl font-bold",
|
|
841
|
-
children: "U"
|
|
842
|
-
}),
|
|
843
|
-
color: "orange"
|
|
844
|
-
}
|
|
845
|
-
];
|
|
846
|
-
const CPU_MIN = .5;
|
|
847
|
-
const CPU_MAX = 8;
|
|
848
|
-
const RAM_MIN = 2;
|
|
849
|
-
const RAM_MAX = 32;
|
|
850
|
-
const STORAGE_MIN = 20;
|
|
851
|
-
const STORAGE_MAX = 512;
|
|
852
|
-
const CPU_STEP = .5;
|
|
853
|
-
const RAM_STEP = 1;
|
|
854
|
-
const STORAGE_STEP = 8;
|
|
855
|
-
/**
|
|
856
|
-
* HTML range inputs snap the thumb to the largest step-aligned value
|
|
857
|
-
* ≤ max. When `(max - min)` is not a multiple of `step` the thumb can
|
|
858
|
-
* never reach the right edge of the track, producing a visibly
|
|
859
|
-
* "short" slider whose label claims a bound the user cannot actually
|
|
860
|
-
* select (e.g. STORAGE_MIN=20, plan max=50, step=8 → thumb caps at 44
|
|
861
|
-
* while the right-hand label still reads "50GB").
|
|
862
|
-
*
|
|
863
|
-
* Adjusting the step down to the largest value ≤ desired that divides
|
|
864
|
-
* `(max - min)` exactly keeps the thumb's travel coincident with the
|
|
865
|
-
* labelled range without secretly capping the user's plan allowance.
|
|
866
|
-
*
|
|
867
|
-
* Precision guarantees: integer `desiredStep` → integer result; 0.5
|
|
868
|
-
* `desiredStep` with a max expressible to one decimal → one-decimal
|
|
869
|
-
* result. Callers that pass an integer step with a non-integer range
|
|
870
|
-
* (e.g. `max=50.5`) get `desiredStep` back unchanged — the thumb will
|
|
871
|
-
* still cap short of `max` in that case, by design (see inline note).
|
|
872
|
-
*/
|
|
873
|
-
function alignSliderStep(min, max, desiredStep) {
|
|
874
|
-
const range = max - min;
|
|
875
|
-
if (range <= 0 || desiredStep <= 0) return desiredStep;
|
|
876
|
-
if (Number.isInteger(desiredStep)) {
|
|
877
|
-
if (!Number.isInteger(range)) return desiredStep;
|
|
878
|
-
for (let c = Math.floor(desiredStep); c >= 1; c--) if (range % c === 0) return c;
|
|
879
|
-
return desiredStep;
|
|
880
|
-
}
|
|
881
|
-
const scale = 10;
|
|
882
|
-
const scaledRange = Math.round(range * scale);
|
|
883
|
-
const scaledStep = Math.round(desiredStep * scale);
|
|
884
|
-
for (let c = scaledStep; c >= 1; c--) if (scaledRange % c === 0) return c / scale;
|
|
885
|
-
return desiredStep;
|
|
886
|
-
}
|
|
887
|
-
/**
|
|
888
|
-
* Snap a value onto the step grid anchored at `min` and bounded by `max`.
|
|
889
|
-
*
|
|
890
|
-
* Range inputs clamp to the grid visually on interaction, but a value
|
|
891
|
-
* seeded from `defaultConfig` (or a preset evaluated under different
|
|
892
|
-
* limits) can sit between stops — e.g. `storageGB=28` on a {20, 26, 32}
|
|
893
|
-
* grid. The browser paints the thumb at the nearest stop while React
|
|
894
|
-
* state still holds the off-grid number, so the reading and the visual
|
|
895
|
-
* disagree until the user drags. Running seed values through this
|
|
896
|
-
* helper keeps state and paint in lock-step.
|
|
897
|
-
*/
|
|
898
|
-
function snapSliderValue(value, min, max, step) {
|
|
899
|
-
if (!Number.isFinite(value)) return min;
|
|
900
|
-
const clamped = Math.max(min, Math.min(value, max));
|
|
901
|
-
if (step <= 0) return clamped;
|
|
902
|
-
const snapped = min + Math.round((clamped - min) / step) * step;
|
|
903
|
-
return Math.max(min, Math.min(snapped, max));
|
|
904
|
-
}
|
|
905
|
-
const DEFAULT_PRICING_RATES = {
|
|
906
|
-
cpuPerHr: .045,
|
|
907
|
-
ramPerGbHr: .005,
|
|
908
|
-
diskPerGbHr: .0011,
|
|
909
|
-
minChargePerHr: void 0
|
|
910
|
-
};
|
|
911
|
-
function formatPerSecondValue(hourlyValue) {
|
|
912
|
-
return (hourlyValue / 3600).toFixed(8);
|
|
913
|
-
}
|
|
914
|
-
/**
|
|
915
|
-
* Real (unclamped) resource preset values. The wizard locks any preset
|
|
916
|
-
* that exceeds the current plan's limits rather than rewriting its
|
|
917
|
-
* values, so every user sees the same three rows but the locked ones
|
|
918
|
-
* carry an upsell badge. Hoisted out of the component body so the
|
|
919
|
-
* array identity is stable across renders.
|
|
920
|
-
*/
|
|
921
|
-
const RAW_PRESETS = [
|
|
922
|
-
{
|
|
923
|
-
name: "Lightweight",
|
|
924
|
-
cpu: 2,
|
|
925
|
-
ram: 4,
|
|
926
|
-
storage: 50
|
|
927
|
-
},
|
|
928
|
-
{
|
|
929
|
-
name: "Standard",
|
|
930
|
-
cpu: 4,
|
|
931
|
-
ram: 16,
|
|
932
|
-
storage: 128
|
|
933
|
-
},
|
|
934
|
-
{
|
|
935
|
-
name: "Performance",
|
|
936
|
-
cpu: 8,
|
|
937
|
-
ram: 32,
|
|
938
|
-
storage: 256
|
|
939
|
-
}
|
|
940
|
-
];
|
|
941
|
-
function computeHourlyCost(cpu, ram, storage, rates) {
|
|
942
|
-
const compute = cpu * rates.cpuPerHr;
|
|
943
|
-
const memory = ram * rates.ramPerGbHr;
|
|
944
|
-
const storageCost = storage * rates.diskPerGbHr;
|
|
945
|
-
const lineSum = compute + memory + storageCost;
|
|
946
|
-
const floor = rates.minChargePerHr ?? 0;
|
|
947
|
-
return {
|
|
948
|
-
compute,
|
|
949
|
-
memory,
|
|
950
|
-
storage: storageCost,
|
|
951
|
-
lineSum,
|
|
952
|
-
floor,
|
|
953
|
-
floorApplies: floor > lineSum,
|
|
954
|
-
total: Math.max(floor, lineSum)
|
|
955
|
-
};
|
|
956
|
-
}
|
|
957
|
-
function SshAccessStep({ config }) {
|
|
958
|
-
const keys = config.keys ?? [];
|
|
959
|
-
const canAddKey = typeof config.onCreateKey === "function";
|
|
960
|
-
const [isAddKeyOpen, setIsAddKeyOpen] = React$1.useState(false);
|
|
961
|
-
const inlineKeyCount = config.inlinePublicKeys.split(/\r?\n/).map((key) => key.trim()).filter(Boolean).length;
|
|
962
|
-
const totalKeyCount = config.selectedKeyIds.length + inlineKeyCount;
|
|
963
|
-
return /* @__PURE__ */ jsxs("div", {
|
|
964
|
-
className: "space-y-4",
|
|
965
|
-
children: [
|
|
966
|
-
/* @__PURE__ */ jsxs("div", {
|
|
967
|
-
className: "flex items-start justify-between gap-4",
|
|
968
|
-
children: [/* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("p", {
|
|
969
|
-
className: "font-medium text-foreground text-sm",
|
|
970
|
-
children: "SSH Access"
|
|
971
|
-
}), /* @__PURE__ */ jsx("p", {
|
|
972
|
-
className: "mt-1 text-muted-foreground text-xs",
|
|
973
|
-
children: "Select stored keys or paste public keys for authorized_keys."
|
|
974
|
-
})] }), /* @__PURE__ */ jsxs("div", {
|
|
975
|
-
className: "flex items-center gap-2",
|
|
976
|
-
children: [canAddKey && /* @__PURE__ */ jsxs(Button$1, {
|
|
977
|
-
type: "button",
|
|
978
|
-
variant: "outline",
|
|
979
|
-
size: "sm",
|
|
980
|
-
onClick: () => setIsAddKeyOpen(true),
|
|
981
|
-
children: [/* @__PURE__ */ jsx(Plus, { className: "h-3.5 w-3.5" }), "Add SSH key"]
|
|
982
|
-
}), /* @__PURE__ */ jsxs(Badge$1, {
|
|
983
|
-
variant: "outline",
|
|
984
|
-
children: [
|
|
985
|
-
totalKeyCount,
|
|
986
|
-
" key",
|
|
987
|
-
totalKeyCount === 1 ? "" : "s"
|
|
988
|
-
]
|
|
989
|
-
})]
|
|
990
|
-
})]
|
|
991
|
-
}),
|
|
992
|
-
keys.length > 0 && /* @__PURE__ */ jsx("div", {
|
|
993
|
-
className: "grid gap-2 sm:grid-cols-2",
|
|
994
|
-
children: keys.map((key) => {
|
|
995
|
-
const selected = config.selectedKeyIds.includes(key.id);
|
|
996
|
-
return /* @__PURE__ */ jsx("button", {
|
|
997
|
-
type: "button",
|
|
998
|
-
"aria-pressed": selected,
|
|
999
|
-
onClick: () => {
|
|
1000
|
-
config.onSelectedKeyIdsChange(selected ? config.selectedKeyIds.filter((id) => id !== key.id) : [...config.selectedKeyIds, key.id]);
|
|
1001
|
-
},
|
|
1002
|
-
className: cn$1("group p-3 text-left rounded-lg border transition-colors duration-200", selected ? "bg-primary/5 border-primary ring-1 ring-primary/20" : "bg-surface-container border-[var(--md3-outline-variant)] hover:border-primary/30 active:scale-[0.99]"),
|
|
1003
|
-
children: /* @__PURE__ */ jsxs("div", {
|
|
1004
|
-
className: "flex items-start justify-between gap-2",
|
|
1005
|
-
children: [/* @__PURE__ */ jsxs("span", {
|
|
1006
|
-
className: "min-w-0",
|
|
1007
|
-
children: [/* @__PURE__ */ jsx("span", {
|
|
1008
|
-
className: "block font-medium text-sm text-foreground",
|
|
1009
|
-
children: key.name
|
|
1010
|
-
}), /* @__PURE__ */ jsxs("span", {
|
|
1011
|
-
className: "block truncate font-mono text-muted-foreground text-xs",
|
|
1012
|
-
children: [
|
|
1013
|
-
key.keyType,
|
|
1014
|
-
" · ",
|
|
1015
|
-
key.fingerprint
|
|
1016
|
-
]
|
|
1017
|
-
})]
|
|
1018
|
-
}), /* @__PURE__ */ jsx("span", {
|
|
1019
|
-
"aria-hidden": "true",
|
|
1020
|
-
className: cn$1("flex h-4 w-4 shrink-0 items-center justify-center rounded-[4px] border-2 transition-colors duration-200", selected ? "border-primary bg-primary" : "border-[var(--md3-outline-variant)] group-hover:border-primary/40"),
|
|
1021
|
-
children: selected && /* @__PURE__ */ jsx(Check, { className: "h-2.5 w-2.5 text-primary-foreground" })
|
|
1022
|
-
})]
|
|
1023
|
-
})
|
|
1024
|
-
}, key.id);
|
|
1025
|
-
})
|
|
1026
|
-
}),
|
|
1027
|
-
/* @__PURE__ */ jsx(Textarea$1, {
|
|
1028
|
-
className: "min-h-24 font-mono text-xs",
|
|
1029
|
-
placeholder: "Paste one public key per line",
|
|
1030
|
-
value: config.inlinePublicKeys,
|
|
1031
|
-
onChange: (event) => config.onInlinePublicKeysChange(event.target.value)
|
|
1032
|
-
}),
|
|
1033
|
-
canAddKey && /* @__PURE__ */ jsx(AddSshKeyDialog, {
|
|
1034
|
-
open: isAddKeyOpen,
|
|
1035
|
-
onOpenChange: setIsAddKeyOpen,
|
|
1036
|
-
onCreateKey: config.onCreateKey,
|
|
1037
|
-
onRefreshKeys: config.onRefreshKeys,
|
|
1038
|
-
onCreatedKeyId: (id) => config.onSelectedKeyIdsChange(config.selectedKeyIds.includes(id) ? config.selectedKeyIds : [...config.selectedKeyIds, id])
|
|
1039
|
-
})
|
|
1040
|
-
]
|
|
1041
|
-
});
|
|
1042
|
-
}
|
|
1043
|
-
/**
|
|
1044
|
-
* Environment-variable value field with a reveal toggle. Values are masked by
|
|
1045
|
-
* default (they commonly hold secrets); the eye button flips to plaintext so
|
|
1046
|
-
* the user can verify what they typed without re-entering it.
|
|
1047
|
-
*/
|
|
1048
|
-
function EnvVarValueInput({ value, onChange, placeholder }) {
|
|
1049
|
-
const [revealed, setRevealed] = React$1.useState(false);
|
|
1050
|
-
return /* @__PURE__ */ jsxs("div", {
|
|
1051
|
-
className: "relative flex-[2]",
|
|
1052
|
-
children: [/* @__PURE__ */ jsx(Input$1, {
|
|
1053
|
-
type: revealed ? "text" : "password",
|
|
1054
|
-
"aria-label": "Environment variable value",
|
|
1055
|
-
value,
|
|
1056
|
-
onChange: (e) => onChange(e.target.value),
|
|
1057
|
-
autoComplete: "off",
|
|
1058
|
-
className: "h-9 px-3 pr-10 font-mono text-sm",
|
|
1059
|
-
placeholder
|
|
1060
|
-
}), /* @__PURE__ */ jsx("button", {
|
|
1061
|
-
type: "button",
|
|
1062
|
-
onClick: () => setRevealed((s) => !s),
|
|
1063
|
-
"aria-label": revealed ? "Hide value" : "Show value",
|
|
1064
|
-
"aria-pressed": revealed,
|
|
1065
|
-
className: `absolute right-2 top-1/2 -translate-y-1/2 rounded p-1 text-muted-foreground hover:text-foreground ${focusRing}`,
|
|
1066
|
-
children: revealed ? /* @__PURE__ */ jsx(EyeOff, { className: "h-4 w-4" }) : /* @__PURE__ */ jsx(Eye, { className: "h-4 w-4" })
|
|
1067
|
-
})]
|
|
1068
|
-
});
|
|
1069
|
-
}
|
|
1070
|
-
function ProvisioningWizard({ environments: environmentsProp, onLoadEnvironments, onSubmit, onBack, className, defaultEnvironment, defaultConfig, onLoadStartupScripts, resourceLimits, sshAccess, pricingRates, planTiers }) {
|
|
1071
|
-
const fieldIdPrefix = React$1.useId();
|
|
1072
|
-
const cpuMax = Math.max(CPU_MIN, resourceLimits?.cpuMax ?? CPU_MAX);
|
|
1073
|
-
const ramMax = Math.max(RAM_MIN, resourceLimits?.ramMaxGB ?? RAM_MAX);
|
|
1074
|
-
const storageMax = Math.max(STORAGE_MIN, resourceLimits?.storageMaxGB ?? STORAGE_MAX);
|
|
1075
|
-
const cpuStep = alignSliderStep(CPU_MIN, cpuMax, CPU_STEP);
|
|
1076
|
-
const ramStep = alignSliderStep(RAM_MIN, ramMax, RAM_STEP);
|
|
1077
|
-
const storageStep = alignSliderStep(STORAGE_MIN, storageMax, STORAGE_STEP);
|
|
1078
|
-
const dc = defaultConfig;
|
|
1079
|
-
const [envList, setEnvList] = React$1.useState(() => {
|
|
1080
|
-
if (environmentsProp) return environmentsProp;
|
|
1081
|
-
if (onLoadEnvironments) return [];
|
|
1082
|
-
return defaultEnvironments;
|
|
1083
|
-
});
|
|
1084
|
-
const [isLoadingEnvironments, setIsLoadingEnvironments] = React$1.useState(() => !environmentsProp && !!onLoadEnvironments);
|
|
1085
|
-
const onLoadEnvironmentsRef = React$1.useRef(onLoadEnvironments);
|
|
1086
|
-
onLoadEnvironmentsRef.current = onLoadEnvironments;
|
|
1087
|
-
React$1.useEffect(() => {
|
|
1088
|
-
let cancelled = false;
|
|
1089
|
-
if (onLoadEnvironmentsRef.current) {
|
|
1090
|
-
setIsLoadingEnvironments(true);
|
|
1091
|
-
onLoadEnvironmentsRef.current().then((entries) => {
|
|
1092
|
-
if (!cancelled) {
|
|
1093
|
-
setEnvList(entries.map(resolveEnvironment));
|
|
1094
|
-
setIsLoadingEnvironments(false);
|
|
1095
|
-
}
|
|
1096
|
-
}).catch((err) => {
|
|
1097
|
-
if (!cancelled) {
|
|
1098
|
-
setLoadError(err instanceof Error ? err.message : "Failed to load environments");
|
|
1099
|
-
setIsLoadingEnvironments(false);
|
|
1100
|
-
}
|
|
1101
|
-
});
|
|
1102
|
-
} else if (environmentsProp) setEnvList(environmentsProp);
|
|
1103
|
-
return () => {
|
|
1104
|
-
cancelled = true;
|
|
1105
|
-
};
|
|
1106
|
-
}, [environmentsProp]);
|
|
1107
|
-
const environments = envList;
|
|
1108
|
-
const showEnvironmentSection = isLoadingEnvironments || environments.length !== 1;
|
|
1109
|
-
const effectiveDefault = dc?.environment ?? defaultEnvironment;
|
|
1110
|
-
const [selectedEnv, setSelectedEnv] = React$1.useState(effectiveDefault ?? environments[0]?.id ?? "");
|
|
1111
|
-
React$1.useEffect(() => {
|
|
1112
|
-
if (envList.length === 0) return;
|
|
1113
|
-
if (effectiveDefault && envList.some((e) => e.id === effectiveDefault)) {
|
|
1114
|
-
setSelectedEnv(effectiveDefault);
|
|
1115
|
-
return;
|
|
1116
|
-
}
|
|
1117
|
-
setSelectedEnv((prev) => {
|
|
1118
|
-
if (prev && envList.some((e) => e.id === prev)) return prev;
|
|
1119
|
-
return envList[0]?.id ?? "";
|
|
1120
|
-
});
|
|
1121
|
-
}, [envList, effectiveDefault]);
|
|
1122
|
-
const [cpuCores, setCpuCores] = React$1.useState(snapSliderValue(dc?.cpuCores ?? 4, CPU_MIN, cpuMax, cpuStep));
|
|
1123
|
-
const [ramGB, setRamGB] = React$1.useState(snapSliderValue(dc?.ramGB ?? 16, RAM_MIN, ramMax, ramStep));
|
|
1124
|
-
const [storageGB, setStorageGB] = React$1.useState(snapSliderValue(dc?.storageGB ?? 128, STORAGE_MIN, storageMax, storageStep));
|
|
1125
|
-
React$1.useEffect(() => {
|
|
1126
|
-
setCpuCores((prev) => snapSliderValue(prev, CPU_MIN, cpuMax, cpuStep));
|
|
1127
|
-
setRamGB((prev) => snapSliderValue(prev, RAM_MIN, ramMax, ramStep));
|
|
1128
|
-
setStorageGB((prev) => snapSliderValue(prev, STORAGE_MIN, storageMax, storageStep));
|
|
1129
|
-
}, [
|
|
1130
|
-
cpuMax,
|
|
1131
|
-
ramMax,
|
|
1132
|
-
storageMax,
|
|
1133
|
-
cpuStep,
|
|
1134
|
-
ramStep,
|
|
1135
|
-
storageStep
|
|
1136
|
-
]);
|
|
1137
|
-
const [name, setName] = React$1.useState(dc?.name ?? "");
|
|
1138
|
-
const [gitUrl, setGitUrl] = React$1.useState(dc?.gitUrl ?? "");
|
|
1139
|
-
const envVarUidRef = React$1.useRef(0);
|
|
1140
|
-
const makeEnvVarRow = (key = "", value = "") => ({
|
|
1141
|
-
uid: `env-${envVarUidRef.current++}`,
|
|
1142
|
-
key,
|
|
1143
|
-
value
|
|
1144
|
-
});
|
|
1145
|
-
const [envVars, setEnvVars] = React$1.useState(() => (dc?.envVars ?? [{
|
|
1146
|
-
key: "",
|
|
1147
|
-
value: ""
|
|
1148
|
-
}]).map((e) => makeEnvVarRow(e.key, e.value)));
|
|
1149
|
-
const [driver, setDriver] = React$1.useState(dc?.driver ?? "docker");
|
|
1150
|
-
const [bare, setBare] = React$1.useState(dc?.bare ?? false);
|
|
1151
|
-
const [startupScriptIds, setStartupScriptIds] = React$1.useState(dc?.startupScriptIds ?? []);
|
|
1152
|
-
const [availableScripts, setAvailableScripts] = React$1.useState([]);
|
|
1153
|
-
const [activePreset, setActivePreset] = React$1.useState(null);
|
|
1154
|
-
const [pricingView, setPricingView] = React$1.useState("hourly");
|
|
1155
|
-
const [showAdvanced, setShowAdvanced] = React$1.useState(false);
|
|
1156
|
-
const [loadError, setLoadError] = React$1.useState(null);
|
|
1157
|
-
const onLoadStartupScriptsRef = React$1.useRef(onLoadStartupScripts);
|
|
1158
|
-
onLoadStartupScriptsRef.current = onLoadStartupScripts;
|
|
1159
|
-
React$1.useEffect(() => {
|
|
1160
|
-
let cancelled = false;
|
|
1161
|
-
if (onLoadStartupScriptsRef.current) onLoadStartupScriptsRef.current().then((scripts) => {
|
|
1162
|
-
if (!cancelled) setAvailableScripts(scripts);
|
|
1163
|
-
}).catch((err) => {
|
|
1164
|
-
if (!cancelled) setLoadError(err instanceof Error ? err.message : "Failed to load startup scripts");
|
|
1165
|
-
});
|
|
1166
|
-
return () => {
|
|
1167
|
-
cancelled = true;
|
|
1168
|
-
};
|
|
1169
|
-
}, []);
|
|
1170
|
-
const [isDeploying, setIsDeploying] = React$1.useState(false);
|
|
1171
|
-
const [deployError, setDeployError] = React$1.useState(null);
|
|
1172
|
-
const handleDeploy = async () => {
|
|
1173
|
-
if (!onSubmit) return;
|
|
1174
|
-
setIsDeploying(true);
|
|
1175
|
-
setDeployError(null);
|
|
1176
|
-
try {
|
|
1177
|
-
const validScriptIds = new Set(availableScripts.filter((s) => s.enabled).map((s) => s.id));
|
|
1178
|
-
await onSubmit({
|
|
1179
|
-
environment: selectedEnv,
|
|
1180
|
-
cpuCores,
|
|
1181
|
-
ramGB,
|
|
1182
|
-
storageGB,
|
|
1183
|
-
name,
|
|
1184
|
-
gitUrl,
|
|
1185
|
-
envVars: envVars.filter((e) => e.key.trim() !== "").map(({ key, value }) => ({
|
|
1186
|
-
key,
|
|
1187
|
-
value
|
|
1188
|
-
})),
|
|
1189
|
-
driver,
|
|
1190
|
-
bare,
|
|
1191
|
-
startupScriptIds: startupScriptIds.filter((id) => validScriptIds.has(id))
|
|
1192
|
-
});
|
|
1193
|
-
} catch (err) {
|
|
1194
|
-
setDeployError(err instanceof Error ? err.message : "Deployment failed");
|
|
1195
|
-
} finally {
|
|
1196
|
-
setIsDeploying(false);
|
|
1197
|
-
}
|
|
1198
|
-
};
|
|
1199
|
-
const applyPreset = (name, cpu, ram, storage) => {
|
|
1200
|
-
setCpuCores(snapSliderValue(cpu, CPU_MIN, cpuMax, cpuStep));
|
|
1201
|
-
setRamGB(snapSliderValue(ram, RAM_MIN, ramMax, ramStep));
|
|
1202
|
-
setStorageGB(snapSliderValue(storage, STORAGE_MIN, storageMax, storageStep));
|
|
1203
|
-
setActivePreset(name);
|
|
1204
|
-
};
|
|
1205
|
-
const presets = RAW_PRESETS.map((p) => {
|
|
1206
|
-
const locked = p.cpu > cpuMax || p.ram > ramMax || p.storage > storageMax;
|
|
1207
|
-
let unlockLabel;
|
|
1208
|
-
if (locked && planTiers && planTiers.length > 0) unlockLabel = planTiers.find((t) => p.cpu <= t.cpuMax && p.ram <= t.ramMaxGB && p.storage <= t.storageMaxGB)?.label;
|
|
1209
|
-
return {
|
|
1210
|
-
...p,
|
|
1211
|
-
fits: !locked,
|
|
1212
|
-
locked,
|
|
1213
|
-
unlockLabel: unlockLabel ?? "Pro"
|
|
1214
|
-
};
|
|
1215
|
-
});
|
|
1216
|
-
const didInitPresetFromDcRef = React$1.useRef(false);
|
|
1217
|
-
const lastLimitsRef = React$1.useRef(null);
|
|
1218
|
-
React$1.useEffect(() => {
|
|
1219
|
-
if (lastLimitsRef.current !== null && lastLimitsRef.current.cpu === cpuMax && lastLimitsRef.current.ram === ramMax && lastLimitsRef.current.storage === storageMax) return;
|
|
1220
|
-
lastLimitsRef.current = {
|
|
1221
|
-
cpu: cpuMax,
|
|
1222
|
-
ram: ramMax,
|
|
1223
|
-
storage: storageMax
|
|
1224
|
-
};
|
|
1225
|
-
if (dc && !didInitPresetFromDcRef.current) {
|
|
1226
|
-
didInitPresetFromDcRef.current = true;
|
|
1227
|
-
const matching = RAW_PRESETS.find((p) => p.cpu === dc.cpuCores && p.ram === dc.ramGB && p.storage === dc.storageGB);
|
|
1228
|
-
if (matching) setActivePreset(matching.name);
|
|
1229
|
-
return;
|
|
1230
|
-
}
|
|
1231
|
-
const largestFitting = [...RAW_PRESETS].reverse().find((p) => p.cpu <= cpuMax && p.ram <= ramMax && p.storage <= storageMax);
|
|
1232
|
-
if (largestFitting) applyPreset(largestFitting.name, largestFitting.cpu, largestFitting.ram, largestFitting.storage);
|
|
1233
|
-
else setActivePreset(null);
|
|
1234
|
-
}, [
|
|
1235
|
-
cpuMax,
|
|
1236
|
-
ramMax,
|
|
1237
|
-
storageMax,
|
|
1238
|
-
dc
|
|
1239
|
-
]);
|
|
1240
|
-
const hourlyCostBreakdown = computeHourlyCost(cpuCores, ramGB, storageGB, pricingRates ?? DEFAULT_PRICING_RATES);
|
|
1241
|
-
const hourCost = hourlyCostBreakdown.total.toFixed(2);
|
|
1242
|
-
const displayValue = pricingView === "hourly" ? hourCost : formatPerSecondValue(hourlyCostBreakdown.total);
|
|
1243
|
-
const pricingSuffix = pricingView === "hourly" ? "/ hour" : "/ sec";
|
|
1244
|
-
const rateSuffix = pricingView === "hourly" ? "/h" : "/s";
|
|
1245
|
-
const fmtRate = (v) => pricingView === "hourly" ? v.toFixed(2) : formatPerSecondValue(v);
|
|
1246
|
-
return /* @__PURE__ */ jsxs("div", {
|
|
1247
|
-
className: cn$1("max-w-6xl mx-auto flex flex-col", className),
|
|
1248
|
-
children: [/* @__PURE__ */ jsxs("div", {
|
|
1249
|
-
className: "mb-4 flex items-center gap-3 shrink-0",
|
|
1250
|
-
children: [onBack && /* @__PURE__ */ jsx(Button$1, {
|
|
1251
|
-
type: "button",
|
|
1252
|
-
variant: "outline",
|
|
1253
|
-
size: "icon",
|
|
1254
|
-
onClick: onBack,
|
|
1255
|
-
className: "h-9 w-9 shrink-0",
|
|
1256
|
-
children: /* @__PURE__ */ jsx(ArrowLeft, { className: "h-4 w-4" })
|
|
1257
|
-
}), /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("h1", {
|
|
1258
|
-
className: "text-2xl font-semibold tracking-tight text-foreground",
|
|
1259
|
-
children: "Sandbox Provisioning"
|
|
1260
|
-
}), /* @__PURE__ */ jsx("p", {
|
|
1261
|
-
className: "text-muted-foreground text-sm",
|
|
1262
|
-
children: "Select your stack, allocate resources, and deploy."
|
|
1263
|
-
})] })]
|
|
1264
|
-
}), /* @__PURE__ */ jsxs("div", {
|
|
1265
|
-
className: "grid grid-cols-12 gap-5 flex-1 min-h-0",
|
|
1266
|
-
children: [/* @__PURE__ */ jsxs("div", {
|
|
1267
|
-
className: "col-span-12 xl:col-span-8 flex flex-col min-h-0",
|
|
1268
|
-
children: [loadError && /* @__PURE__ */ jsxs("div", {
|
|
1269
|
-
className: "rounded-lg border border-destructive/30 bg-destructive/10 p-3 flex items-center gap-2 shrink-0 mb-4",
|
|
1270
|
-
children: [/* @__PURE__ */ jsx(Info, { className: "h-4 w-4 text-destructive shrink-0" }), /* @__PURE__ */ jsx("p", {
|
|
1271
|
-
className: "text-sm font-medium text-destructive",
|
|
1272
|
-
children: loadError
|
|
1273
|
-
})]
|
|
1274
|
-
}), /* @__PURE__ */ jsxs("div", {
|
|
1275
|
-
className: "flex-1 overflow-y-auto min-h-0 space-y-4",
|
|
1276
|
-
children: [
|
|
1277
|
-
showEnvironmentSection && /* @__PURE__ */ jsxs("section", {
|
|
1278
|
-
className: SECTION_CARD_CLASS,
|
|
1279
|
-
children: [
|
|
1280
|
-
/* @__PURE__ */ jsxs("div", {
|
|
1281
|
-
className: "flex items-center gap-2 mb-4",
|
|
1282
|
-
children: [/* @__PURE__ */ jsx(Layers, { className: "h-4 w-4 text-primary shrink-0" }), /* @__PURE__ */ jsx("h2", {
|
|
1283
|
-
className: "text-sm font-semibold uppercase tracking-[0.06em] text-muted-foreground",
|
|
1284
|
-
children: "Environment Selection"
|
|
1285
|
-
})]
|
|
1286
|
-
}),
|
|
1287
|
-
!isLoadingEnvironments && environments.length === 0 && /* @__PURE__ */ jsx("p", {
|
|
1288
|
-
className: "text-sm text-muted-foreground",
|
|
1289
|
-
children: "No environments are available to select right now. Refresh to try again."
|
|
1290
|
-
}),
|
|
1291
|
-
/* @__PURE__ */ jsx("div", {
|
|
1292
|
-
className: "grid grid-cols-1 md:grid-cols-3 gap-3",
|
|
1293
|
-
children: isLoadingEnvironments && environments.length === 0 ? Array.from({ length: 3 }).map((_, i) => /* @__PURE__ */ jsxs("div", {
|
|
1294
|
-
className: "p-3.5 rounded-lg border border-border bg-card/50 animate-pulse",
|
|
1295
|
-
"aria-hidden": "true",
|
|
1296
|
-
children: [
|
|
1297
|
-
/* @__PURE__ */ jsxs("div", {
|
|
1298
|
-
className: "flex justify-between items-start mb-2.5",
|
|
1299
|
-
children: [/* @__PURE__ */ jsx("div", { className: "w-10 h-10 rounded-full bg-muted/60 border border-border" }), /* @__PURE__ */ jsx("div", { className: "w-4 h-4 rounded-full border-2 border-border" })]
|
|
1300
|
-
}),
|
|
1301
|
-
/* @__PURE__ */ jsx("div", { className: "h-3 w-1/3 rounded bg-muted/60 mb-2" }),
|
|
1302
|
-
/* @__PURE__ */ jsx("div", { className: "h-2.5 w-5/6 rounded bg-muted/50 mb-1.5" }),
|
|
1303
|
-
/* @__PURE__ */ jsx("div", { className: "h-2.5 w-2/3 rounded bg-muted/50" })
|
|
1304
|
-
]
|
|
1305
|
-
}, `env-skeleton-${i}`)) : environments.map((env) => /* @__PURE__ */ jsxs("button", {
|
|
1306
|
-
type: "button",
|
|
1307
|
-
onClick: () => setSelectedEnv(env.id),
|
|
1308
|
-
className: cn$1("group p-3.5 rounded-lg text-left border transition-colors duration-200", selectedEnv === env.id ? "bg-primary/5 border-primary ring-1 ring-primary/20" : "bg-surface-container border-[var(--md3-outline-variant)] hover:border-primary/30 active:scale-[0.99]"),
|
|
1309
|
-
children: [
|
|
1310
|
-
/* @__PURE__ */ jsxs("div", {
|
|
1311
|
-
className: "flex justify-between items-start mb-2.5",
|
|
1312
|
-
children: [/* @__PURE__ */ jsx("div", {
|
|
1313
|
-
className: "w-10 h-10 rounded-full flex items-center justify-center bg-surface-container-high border border-[var(--md3-outline-variant)]",
|
|
1314
|
-
children: env.icon
|
|
1315
|
-
}), /* @__PURE__ */ jsx("div", {
|
|
1316
|
-
className: cn$1("w-4 h-4 rounded-full border-2 flex items-center justify-center transition-colors duration-200", selectedEnv === env.id ? "border-primary bg-primary" : "border-[var(--md3-outline-variant)] group-hover:border-primary/40"),
|
|
1317
|
-
children: selectedEnv === env.id && /* @__PURE__ */ jsx(Check, { className: "h-2.5 w-2.5 text-primary-foreground" })
|
|
1318
|
-
})]
|
|
1319
|
-
}),
|
|
1320
|
-
/* @__PURE__ */ jsx("h3", {
|
|
1321
|
-
className: "font-semibold text-sm mb-0.5 text-foreground",
|
|
1322
|
-
children: env.name
|
|
1323
|
-
}),
|
|
1324
|
-
/* @__PURE__ */ jsx("p", {
|
|
1325
|
-
className: "text-xs text-muted-foreground leading-relaxed",
|
|
1326
|
-
children: env.description
|
|
1327
|
-
})
|
|
1328
|
-
]
|
|
1329
|
-
}, env.id))
|
|
1330
|
-
})
|
|
1331
|
-
]
|
|
1332
|
-
}),
|
|
1333
|
-
/* @__PURE__ */ jsxs("section", {
|
|
1334
|
-
className: SECTION_CARD_CLASS,
|
|
1335
|
-
children: [
|
|
1336
|
-
/* @__PURE__ */ jsxs("div", {
|
|
1337
|
-
className: "flex items-center gap-2 mb-4",
|
|
1338
|
-
children: [/* @__PURE__ */ jsx(Cpu, { className: "h-4 w-4 text-primary shrink-0" }), /* @__PURE__ */ jsx("h2", {
|
|
1339
|
-
className: "text-sm font-semibold uppercase tracking-[0.06em] text-muted-foreground",
|
|
1340
|
-
children: "Resource Allocation"
|
|
1341
|
-
})]
|
|
1342
|
-
}),
|
|
1343
|
-
/* @__PURE__ */ jsxs("div", {
|
|
1344
|
-
className: "mb-4",
|
|
1345
|
-
children: [/* @__PURE__ */ jsx("label", {
|
|
1346
|
-
className: cn$1(FIELD_LABEL_CLASS, "mb-2"),
|
|
1347
|
-
children: "Compute Presets"
|
|
1348
|
-
}), /* @__PURE__ */ jsx("div", {
|
|
1349
|
-
className: "grid grid-cols-3 gap-2",
|
|
1350
|
-
children: presets.map((p) => {
|
|
1351
|
-
const active = activePreset === p.name && !p.locked;
|
|
1352
|
-
return /* @__PURE__ */ jsxs("button", {
|
|
1353
|
-
type: "button",
|
|
1354
|
-
onClick: () => !p.locked && applyPreset(p.name, p.cpu, p.ram, p.storage),
|
|
1355
|
-
disabled: p.locked,
|
|
1356
|
-
className: cn$1("p-2.5 rounded-lg transition-colors duration-200 text-center group border relative", active ? "bg-primary/5 border-primary ring-1 ring-primary/20" : p.locked ? "bg-muted/30 border-border opacity-60 cursor-not-allowed" : "bg-surface-container border-[var(--md3-outline-variant)] hover:border-primary/30 active:scale-[0.99]"),
|
|
1357
|
-
children: [
|
|
1358
|
-
p.locked && /* @__PURE__ */ jsx("div", {
|
|
1359
|
-
className: "absolute -top-1.5 -right-1.5 bg-primary text-primary-foreground text-[9px] font-semibold px-1.5 py-0.5 rounded-full uppercase tracking-wider",
|
|
1360
|
-
children: p.unlockLabel
|
|
1361
|
-
}),
|
|
1362
|
-
/* @__PURE__ */ jsx("div", {
|
|
1363
|
-
className: cn$1("font-semibold text-sm transition-colors duration-200", active ? "text-primary" : p.locked ? "text-muted-foreground" : "text-foreground"),
|
|
1364
|
-
children: p.name
|
|
1365
|
-
}),
|
|
1366
|
-
/* @__PURE__ */ jsxs("div", {
|
|
1367
|
-
className: "text-xs text-muted-foreground mt-0.5 font-mono",
|
|
1368
|
-
children: [
|
|
1369
|
-
p.cpu,
|
|
1370
|
-
" vCPU",
|
|
1371
|
-
p.cpu === 1 ? "" : "s",
|
|
1372
|
-
" / ",
|
|
1373
|
-
p.ram,
|
|
1374
|
-
"GB /",
|
|
1375
|
-
" ",
|
|
1376
|
-
p.storage,
|
|
1377
|
-
"GB"
|
|
1378
|
-
]
|
|
1379
|
-
})
|
|
1380
|
-
]
|
|
1381
|
-
}, p.name);
|
|
1382
|
-
})
|
|
1383
|
-
})]
|
|
1384
|
-
}),
|
|
1385
|
-
/* @__PURE__ */ jsx("div", {
|
|
1386
|
-
className: "space-y-4",
|
|
1387
|
-
children: [
|
|
1388
|
-
{
|
|
1389
|
-
id: "cpu",
|
|
1390
|
-
label: "Compute Cores (CPU)",
|
|
1391
|
-
value: cpuCores,
|
|
1392
|
-
setter: setCpuCores,
|
|
1393
|
-
min: CPU_MIN,
|
|
1394
|
-
max: cpuMax,
|
|
1395
|
-
step: cpuStep,
|
|
1396
|
-
unit: "vCPU"
|
|
1397
|
-
},
|
|
1398
|
-
{
|
|
1399
|
-
id: "ram",
|
|
1400
|
-
label: "Memory (RAM)",
|
|
1401
|
-
value: ramGB,
|
|
1402
|
-
setter: setRamGB,
|
|
1403
|
-
min: RAM_MIN,
|
|
1404
|
-
max: ramMax,
|
|
1405
|
-
step: ramStep,
|
|
1406
|
-
unit: "GB"
|
|
1407
|
-
},
|
|
1408
|
-
{
|
|
1409
|
-
id: "storage",
|
|
1410
|
-
label: "Ephemeral Storage",
|
|
1411
|
-
value: storageGB,
|
|
1412
|
-
setter: setStorageGB,
|
|
1413
|
-
min: STORAGE_MIN,
|
|
1414
|
-
max: storageMax,
|
|
1415
|
-
step: storageStep,
|
|
1416
|
-
unit: "GB"
|
|
1417
|
-
}
|
|
1418
|
-
].map(({ id, label, value, setter, min, max, step: s, unit }) => {
|
|
1419
|
-
const displayUnit = unit === "vCPU" ? `${value} vCPU${value === 1 ? "" : "s"}` : `${value}${unit}`;
|
|
1420
|
-
return /* @__PURE__ */ jsxs("div", { children: [
|
|
1421
|
-
/* @__PURE__ */ jsxs("div", {
|
|
1422
|
-
className: "flex justify-between items-end pb-1 mb-1.5",
|
|
1423
|
-
children: [/* @__PURE__ */ jsx("label", {
|
|
1424
|
-
htmlFor: `${fieldIdPrefix}-${id}`,
|
|
1425
|
-
className: FIELD_LABEL_CLASS,
|
|
1426
|
-
children: label
|
|
1427
|
-
}), /* @__PURE__ */ jsx("span", {
|
|
1428
|
-
className: "text-sm font-semibold text-foreground tabular-nums",
|
|
1429
|
-
children: displayUnit
|
|
1430
|
-
})]
|
|
1431
|
-
}),
|
|
1432
|
-
/* @__PURE__ */ jsx("input", {
|
|
1433
|
-
id: `${fieldIdPrefix}-${id}`,
|
|
1434
|
-
type: "range",
|
|
1435
|
-
min,
|
|
1436
|
-
max,
|
|
1437
|
-
step: s,
|
|
1438
|
-
value,
|
|
1439
|
-
onChange: (e) => {
|
|
1440
|
-
setter(+e.target.value);
|
|
1441
|
-
setActivePreset(null);
|
|
1442
|
-
},
|
|
1443
|
-
className: "w-full h-1.5 rounded-full appearance-none cursor-pointer accent-primary [&::-webkit-slider-runnable-track]:bg-border [&::-webkit-slider-runnable-track]:rounded-full [&::-webkit-slider-runnable-track]:h-1.5 [&::-moz-range-track]:bg-border [&::-moz-range-track]:rounded-full [&::-moz-range-track]:h-1.5 [&::-webkit-slider-thumb]:bg-primary [&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:h-4 [&::-webkit-slider-thumb]:w-4 [&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:-mt-[5px] [&::-webkit-slider-thumb]:shadow-sm [&::-webkit-slider-thumb]:border-2 [&::-webkit-slider-thumb]:border-primary-foreground"
|
|
1444
|
-
}),
|
|
1445
|
-
/* @__PURE__ */ jsxs("div", {
|
|
1446
|
-
className: "flex justify-between text-[10px] font-mono text-muted-foreground/60 mt-1",
|
|
1447
|
-
children: [/* @__PURE__ */ jsxs("span", { children: [min, unit === "vCPU" ? min === 1 ? " vCPU" : " vCPUs" : unit] }), /* @__PURE__ */ jsxs("span", { children: [max, unit === "vCPU" ? max === 1 ? " vCPU" : " vCPUs" : unit] })]
|
|
1448
|
-
})
|
|
1449
|
-
] }, label);
|
|
1450
|
-
})
|
|
1451
|
-
})
|
|
1452
|
-
]
|
|
1453
|
-
}),
|
|
1454
|
-
/* @__PURE__ */ jsx("section", {
|
|
1455
|
-
className: SECTION_CARD_CLASS,
|
|
1456
|
-
children: /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsxs("button", {
|
|
1457
|
-
type: "button",
|
|
1458
|
-
onClick: () => setShowAdvanced(!showAdvanced),
|
|
1459
|
-
"aria-expanded": showAdvanced,
|
|
1460
|
-
"aria-controls": `${fieldIdPrefix}-advanced`,
|
|
1461
|
-
className: `flex items-center gap-2 text-muted-foreground hover:text-foreground transition-colors text-sm font-medium ${focusRing}`,
|
|
1462
|
-
children: [/* @__PURE__ */ jsx(Settings, { className: "w-4 h-4" }), showAdvanced ? "Hide Advanced Options" : "Show Advanced Options"]
|
|
1463
|
-
}), /* @__PURE__ */ jsxs("div", {
|
|
1464
|
-
id: `${fieldIdPrefix}-advanced`,
|
|
1465
|
-
hidden: !showAdvanced,
|
|
1466
|
-
className: "mt-4 space-y-4",
|
|
1467
|
-
children: [
|
|
1468
|
-
/* @__PURE__ */ jsxs("div", {
|
|
1469
|
-
className: "grid grid-cols-1 md:grid-cols-2 gap-4",
|
|
1470
|
-
children: [/* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("label", {
|
|
1471
|
-
htmlFor: `${fieldIdPrefix}-name`,
|
|
1472
|
-
className: cn$1(FIELD_LABEL_CLASS, "mb-1.5"),
|
|
1473
|
-
children: "Workspace Name"
|
|
1474
|
-
}), /* @__PURE__ */ jsx(Input$1, {
|
|
1475
|
-
id: `${fieldIdPrefix}-name`,
|
|
1476
|
-
type: "text",
|
|
1477
|
-
value: name,
|
|
1478
|
-
onChange: (e) => setName(e.target.value),
|
|
1479
|
-
maxLength: 128,
|
|
1480
|
-
className: "h-9 px-3 text-sm",
|
|
1481
|
-
placeholder: "my-cool-sandbox"
|
|
1482
|
-
})] }), /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("label", {
|
|
1483
|
-
htmlFor: `${fieldIdPrefix}-driver`,
|
|
1484
|
-
className: cn$1(FIELD_LABEL_CLASS, "mb-1.5"),
|
|
1485
|
-
children: "Virtualization Driver"
|
|
1486
|
-
}), /* @__PURE__ */ jsxs(Select$1, {
|
|
1487
|
-
value: driver,
|
|
1488
|
-
onValueChange: (value) => {
|
|
1489
|
-
if (VALID_DRIVERS.has(value)) setDriver(value);
|
|
1490
|
-
},
|
|
1491
|
-
children: [/* @__PURE__ */ jsx(SelectTrigger$1, {
|
|
1492
|
-
id: `${fieldIdPrefix}-driver`,
|
|
1493
|
-
className: "h-9",
|
|
1494
|
-
children: /* @__PURE__ */ jsx(SelectValue$1, {})
|
|
1495
|
-
}), /* @__PURE__ */ jsxs(SelectContent$1, { children: [
|
|
1496
|
-
/* @__PURE__ */ jsx(SelectItem$1, {
|
|
1497
|
-
value: "docker",
|
|
1498
|
-
children: "Docker container (Default)"
|
|
1499
|
-
}),
|
|
1500
|
-
/* @__PURE__ */ jsx(SelectItem$1, {
|
|
1501
|
-
value: "firecracker",
|
|
1502
|
-
children: "Firecracker microVM (Secure)"
|
|
1503
|
-
}),
|
|
1504
|
-
/* @__PURE__ */ jsx(SelectItem$1, {
|
|
1505
|
-
value: "tangle",
|
|
1506
|
-
children: "Tangle Distributed Node"
|
|
1507
|
-
})
|
|
1508
|
-
] })]
|
|
1509
|
-
})] })]
|
|
1510
|
-
}),
|
|
1511
|
-
/* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("label", {
|
|
1512
|
-
htmlFor: `${fieldIdPrefix}-git-url`,
|
|
1513
|
-
className: cn$1(FIELD_LABEL_CLASS, "mb-1.5"),
|
|
1514
|
-
children: "Git Repository URL"
|
|
1515
|
-
}), /* @__PURE__ */ jsx(Input$1, {
|
|
1516
|
-
id: `${fieldIdPrefix}-git-url`,
|
|
1517
|
-
type: "text",
|
|
1518
|
-
value: gitUrl,
|
|
1519
|
-
onChange: (e) => setGitUrl(e.target.value),
|
|
1520
|
-
className: "h-9 px-3 text-sm",
|
|
1521
|
-
placeholder: "https://github.com/my-org/my-repo.git"
|
|
1522
|
-
})] }),
|
|
1523
|
-
/* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsxs("div", {
|
|
1524
|
-
className: "flex justify-between items-center mb-1.5",
|
|
1525
|
-
children: [/* @__PURE__ */ jsx("label", {
|
|
1526
|
-
className: FIELD_LABEL_CLASS,
|
|
1527
|
-
children: "Environment Variables"
|
|
1528
|
-
}), /* @__PURE__ */ jsxs("button", {
|
|
1529
|
-
type: "button",
|
|
1530
|
-
onClick: () => setEnvVars([...envVars, makeEnvVarRow()]),
|
|
1531
|
-
className: "flex items-center gap-1 text-xs text-primary hover:text-primary/70 transition-colors font-medium",
|
|
1532
|
-
children: [/* @__PURE__ */ jsx(Plus, { className: "h-3 w-3" }), " Add Var"]
|
|
1533
|
-
})]
|
|
1534
|
-
}), /* @__PURE__ */ jsxs("div", {
|
|
1535
|
-
className: "space-y-2",
|
|
1536
|
-
children: [envVars.map((env) => /* @__PURE__ */ jsxs("div", {
|
|
1537
|
-
className: "flex gap-2",
|
|
1538
|
-
children: [
|
|
1539
|
-
/* @__PURE__ */ jsx(Input$1, {
|
|
1540
|
-
type: "text",
|
|
1541
|
-
"aria-label": "Environment variable name",
|
|
1542
|
-
value: env.key,
|
|
1543
|
-
onChange: (e) => setEnvVars(envVars.map((v) => v.uid === env.uid ? {
|
|
1544
|
-
...v,
|
|
1545
|
-
key: e.target.value
|
|
1546
|
-
} : v)),
|
|
1547
|
-
className: "flex-1 h-9 px-3 font-mono text-sm",
|
|
1548
|
-
placeholder: "API_KEY"
|
|
1549
|
-
}),
|
|
1550
|
-
/* @__PURE__ */ jsx(EnvVarValueInput, {
|
|
1551
|
-
value: env.value,
|
|
1552
|
-
onChange: (value) => setEnvVars(envVars.map((v) => v.uid === env.uid ? {
|
|
1553
|
-
...v,
|
|
1554
|
-
value
|
|
1555
|
-
} : v)),
|
|
1556
|
-
placeholder: "sk-xxxxxxxxxxx"
|
|
1557
|
-
}),
|
|
1558
|
-
/* @__PURE__ */ jsx(Button$1, {
|
|
1559
|
-
type: "button",
|
|
1560
|
-
variant: "outline",
|
|
1561
|
-
size: "icon",
|
|
1562
|
-
"aria-label": "Remove variable",
|
|
1563
|
-
onClick: () => setEnvVars(envVars.filter((v) => v.uid !== env.uid)),
|
|
1564
|
-
className: "h-9 w-9 shrink-0 text-destructive hover:bg-destructive/10 hover:border-destructive/30",
|
|
1565
|
-
children: /* @__PURE__ */ jsx(Trash2, { className: "h-4 w-4" })
|
|
1566
|
-
})
|
|
1567
|
-
]
|
|
1568
|
-
}, env.uid)), envVars.length === 0 && /* @__PURE__ */ jsx("div", {
|
|
1569
|
-
className: "text-center p-3 border border-dashed border-[var(--md3-outline-variant)] rounded-lg text-muted-foreground/60 text-xs italic",
|
|
1570
|
-
children: "No environment variables set"
|
|
1571
|
-
})]
|
|
1572
|
-
})] }),
|
|
1573
|
-
availableScripts.length > 0 && /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("div", {
|
|
1574
|
-
className: cn$1(FIELD_LABEL_CLASS, "mb-1.5"),
|
|
1575
|
-
children: "Startup Scripts"
|
|
1576
|
-
}), /* @__PURE__ */ jsx("div", {
|
|
1577
|
-
className: "space-y-2",
|
|
1578
|
-
children: availableScripts.filter((s) => s.enabled).map((script) => {
|
|
1579
|
-
const selected = startupScriptIds.includes(script.id);
|
|
1580
|
-
return /* @__PURE__ */ jsxs("label", {
|
|
1581
|
-
className: "flex items-start gap-3 cursor-pointer group rounded-lg border border-[var(--md3-outline-variant)] p-3 transition-colors hover:border-primary/30",
|
|
1582
|
-
children: [/* @__PURE__ */ jsx("input", {
|
|
1583
|
-
type: "checkbox",
|
|
1584
|
-
checked: selected,
|
|
1585
|
-
onChange: () => setStartupScriptIds((prev) => selected ? prev.filter((id) => id !== script.id) : [...prev, script.id]),
|
|
1586
|
-
className: `mt-0.5 h-4 w-4 rounded border-[var(--md3-outline-variant)] text-primary ${focusRing}`
|
|
1587
|
-
}), /* @__PURE__ */ jsxs("div", {
|
|
1588
|
-
className: "flex-1 min-w-0",
|
|
1589
|
-
children: [
|
|
1590
|
-
/* @__PURE__ */ jsx("div", {
|
|
1591
|
-
className: "text-sm font-medium text-foreground group-hover:text-primary transition-colors",
|
|
1592
|
-
children: script.name
|
|
1593
|
-
}),
|
|
1594
|
-
script.description && /* @__PURE__ */ jsx("div", {
|
|
1595
|
-
className: "text-xs text-muted-foreground mt-0.5",
|
|
1596
|
-
children: script.description
|
|
1597
|
-
}),
|
|
1598
|
-
script.injectSecrets.length > 0 && /* @__PURE__ */ jsx("div", {
|
|
1599
|
-
className: "flex flex-wrap gap-1 mt-1.5",
|
|
1600
|
-
children: script.injectSecrets.map((s) => /* @__PURE__ */ jsxs("span", {
|
|
1601
|
-
className: "inline-flex items-center gap-0.5 rounded-full bg-surface-container-high px-2 py-0.5 text-[10px] text-muted-foreground",
|
|
1602
|
-
children: [/* @__PURE__ */ jsxs("svg", {
|
|
1603
|
-
className: "h-2.5 w-2.5",
|
|
1604
|
-
viewBox: "0 0 24 24",
|
|
1605
|
-
fill: "none",
|
|
1606
|
-
stroke: "currentColor",
|
|
1607
|
-
strokeWidth: "2",
|
|
1608
|
-
children: [/* @__PURE__ */ jsx("rect", {
|
|
1609
|
-
x: "3",
|
|
1610
|
-
y: "11",
|
|
1611
|
-
width: "18",
|
|
1612
|
-
height: "11",
|
|
1613
|
-
rx: "2",
|
|
1614
|
-
ry: "2"
|
|
1615
|
-
}), /* @__PURE__ */ jsx("path", { d: "M7 11V7a5 5 0 0 1 10 0v4" })]
|
|
1616
|
-
}), s]
|
|
1617
|
-
}, s))
|
|
1618
|
-
})
|
|
1619
|
-
]
|
|
1620
|
-
})]
|
|
1621
|
-
}, script.id);
|
|
1622
|
-
})
|
|
1623
|
-
})] }),
|
|
1624
|
-
/* @__PURE__ */ jsxs("div", {
|
|
1625
|
-
className: "pt-3 border-t border-[var(--md3-outline-variant)] flex items-start justify-between gap-3",
|
|
1626
|
-
children: [/* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("label", {
|
|
1627
|
-
htmlFor: "wizard-bare-mode",
|
|
1628
|
-
className: "block text-sm font-medium text-foreground cursor-pointer",
|
|
1629
|
-
children: "Bare Mode"
|
|
1630
|
-
}), /* @__PURE__ */ jsx("p", {
|
|
1631
|
-
className: "text-xs text-muted-foreground mt-0.5",
|
|
1632
|
-
children: "Start as a raw container without an embedded AI Agent backend."
|
|
1633
|
-
})] }), /* @__PURE__ */ jsx(Switch, {
|
|
1634
|
-
id: "wizard-bare-mode",
|
|
1635
|
-
checked: bare,
|
|
1636
|
-
onCheckedChange: setBare,
|
|
1637
|
-
className: "mt-0.5"
|
|
1638
|
-
})]
|
|
1639
|
-
}),
|
|
1640
|
-
sshAccess && /* @__PURE__ */ jsx("div", {
|
|
1641
|
-
className: "pt-3 border-t border-[var(--md3-outline-variant)]",
|
|
1642
|
-
children: /* @__PURE__ */ jsx(SshAccessStep, { config: sshAccess })
|
|
1643
|
-
})
|
|
1644
|
-
]
|
|
1645
|
-
})] })
|
|
1646
|
-
})
|
|
1647
|
-
]
|
|
1648
|
-
})]
|
|
1649
|
-
}), /* @__PURE__ */ jsxs("div", {
|
|
1650
|
-
className: "col-span-12 xl:col-span-4 sticky top-4 space-y-4",
|
|
1651
|
-
children: [
|
|
1652
|
-
/* @__PURE__ */ jsxs("div", {
|
|
1653
|
-
className: "rounded-lg border border-[var(--md3-outline-variant)] bg-surface-container p-5 shadow-sm",
|
|
1654
|
-
children: [
|
|
1655
|
-
/* @__PURE__ */ jsxs("div", {
|
|
1656
|
-
className: "flex justify-between items-center mb-3",
|
|
1657
|
-
children: [/* @__PURE__ */ jsx("span", {
|
|
1658
|
-
className: FIELD_LABEL_CLASS,
|
|
1659
|
-
children: "Run Cost"
|
|
1660
|
-
}), /* @__PURE__ */ jsxs("div", {
|
|
1661
|
-
role: "group",
|
|
1662
|
-
"aria-label": "Pricing view",
|
|
1663
|
-
className: "inline-flex items-center rounded-md border border-[var(--md3-outline-variant)] bg-surface-container-low p-0.5",
|
|
1664
|
-
children: [/* @__PURE__ */ jsx("button", {
|
|
1665
|
-
type: "button",
|
|
1666
|
-
"aria-pressed": pricingView === "hourly",
|
|
1667
|
-
onClick: () => setPricingView("hourly"),
|
|
1668
|
-
className: cn$1("rounded px-2.5 py-0.5 text-[10px] font-medium transition-all", pricingView === "hourly" ? "bg-surface-container-high text-foreground shadow-sm" : "text-muted-foreground hover:text-foreground"),
|
|
1669
|
-
children: "Per Hour"
|
|
1670
|
-
}), /* @__PURE__ */ jsx("button", {
|
|
1671
|
-
type: "button",
|
|
1672
|
-
"aria-pressed": pricingView === "perSecond",
|
|
1673
|
-
onClick: () => setPricingView("perSecond"),
|
|
1674
|
-
className: cn$1("rounded px-2.5 py-0.5 text-[10px] font-medium transition-all", pricingView === "perSecond" ? "bg-surface-container-high text-foreground shadow-sm" : "text-muted-foreground hover:text-foreground"),
|
|
1675
|
-
children: "Per Second"
|
|
1676
|
-
})]
|
|
1677
|
-
})]
|
|
1678
|
-
}),
|
|
1679
|
-
/* @__PURE__ */ jsxs("div", {
|
|
1680
|
-
className: "flex items-baseline gap-2 mb-4",
|
|
1681
|
-
children: [/* @__PURE__ */ jsxs("span", {
|
|
1682
|
-
className: cn$1("font-semibold text-foreground tracking-tight tabular-nums animate-in fade-in duration-200", pricingView === "hourly" ? "text-3xl" : "text-xl"),
|
|
1683
|
-
children: ["$", displayValue]
|
|
1684
|
-
}, pricingView), /* @__PURE__ */ jsx("span", {
|
|
1685
|
-
className: "text-muted-foreground text-sm",
|
|
1686
|
-
children: pricingSuffix
|
|
1687
|
-
})]
|
|
1688
|
-
}),
|
|
1689
|
-
/* @__PURE__ */ jsxs("div", {
|
|
1690
|
-
className: "space-y-2 rounded-md border border-[var(--md3-outline-variant)] bg-surface-container-high p-3",
|
|
1691
|
-
children: [
|
|
1692
|
-
/* @__PURE__ */ jsxs("div", {
|
|
1693
|
-
className: "flex justify-between text-xs font-mono tracking-wide text-muted-foreground",
|
|
1694
|
-
children: [/* @__PURE__ */ jsx("span", { children: "COMPUTE" }), /* @__PURE__ */ jsxs("span", {
|
|
1695
|
-
className: "text-foreground",
|
|
1696
|
-
children: [
|
|
1697
|
-
"$",
|
|
1698
|
-
fmtRate(hourlyCostBreakdown.compute),
|
|
1699
|
-
rateSuffix
|
|
1700
|
-
]
|
|
1701
|
-
})]
|
|
1702
|
-
}),
|
|
1703
|
-
/* @__PURE__ */ jsxs("div", {
|
|
1704
|
-
className: "flex justify-between text-xs font-mono tracking-wide text-muted-foreground",
|
|
1705
|
-
children: [/* @__PURE__ */ jsx("span", { children: "MEMORY" }), /* @__PURE__ */ jsxs("span", {
|
|
1706
|
-
className: "text-foreground/80",
|
|
1707
|
-
children: [
|
|
1708
|
-
"$",
|
|
1709
|
-
fmtRate(hourlyCostBreakdown.memory),
|
|
1710
|
-
rateSuffix
|
|
1711
|
-
]
|
|
1712
|
-
})]
|
|
1713
|
-
}),
|
|
1714
|
-
/* @__PURE__ */ jsxs("div", {
|
|
1715
|
-
className: "flex justify-between text-xs font-mono tracking-wide text-muted-foreground",
|
|
1716
|
-
children: [/* @__PURE__ */ jsx("span", { children: "STORAGE" }), /* @__PURE__ */ jsxs("span", {
|
|
1717
|
-
className: "text-foreground/80",
|
|
1718
|
-
children: [
|
|
1719
|
-
"$",
|
|
1720
|
-
fmtRate(hourlyCostBreakdown.storage),
|
|
1721
|
-
rateSuffix
|
|
1722
|
-
]
|
|
1723
|
-
})]
|
|
1724
|
-
}),
|
|
1725
|
-
hourlyCostBreakdown.floorApplies && /* @__PURE__ */ jsxs("div", {
|
|
1726
|
-
className: "flex justify-between text-xs font-mono tracking-wide text-primary border-t border-[var(--md3-outline-variant)] pt-2",
|
|
1727
|
-
children: [/* @__PURE__ */ jsx("span", { children: "MIN CHARGE" }), /* @__PURE__ */ jsxs("span", { children: [
|
|
1728
|
-
"$",
|
|
1729
|
-
fmtRate(hourlyCostBreakdown.floor - hourlyCostBreakdown.lineSum),
|
|
1730
|
-
rateSuffix
|
|
1731
|
-
] })]
|
|
1732
|
-
})
|
|
1733
|
-
]
|
|
1734
|
-
})
|
|
1735
|
-
]
|
|
1736
|
-
}),
|
|
1737
|
-
deployError && /* @__PURE__ */ jsxs("div", {
|
|
1738
|
-
className: "rounded-lg border border-destructive/30 bg-destructive/10 p-3 flex items-center gap-2",
|
|
1739
|
-
children: [/* @__PURE__ */ jsx(Info, { className: "h-4 w-4 text-destructive shrink-0" }), /* @__PURE__ */ jsx("p", {
|
|
1740
|
-
className: "text-sm font-medium text-destructive",
|
|
1741
|
-
children: deployError
|
|
1742
|
-
})]
|
|
1743
|
-
}),
|
|
1744
|
-
/* @__PURE__ */ jsx("div", {
|
|
1745
|
-
className: "space-y-2",
|
|
1746
|
-
children: /* @__PURE__ */ jsx(Button$1, {
|
|
1747
|
-
type: "button",
|
|
1748
|
-
onClick: handleDeploy,
|
|
1749
|
-
disabled: isDeploying || !selectedEnv,
|
|
1750
|
-
className: "w-full",
|
|
1751
|
-
children: isDeploying ? /* @__PURE__ */ jsxs("span", {
|
|
1752
|
-
className: "flex items-center justify-center gap-2",
|
|
1753
|
-
children: [/* @__PURE__ */ jsx(Loader2, {
|
|
1754
|
-
className: "h-4 w-4 animate-spin",
|
|
1755
|
-
"data-motion": "essential"
|
|
1756
|
-
}), "Spinning up environment..."]
|
|
1757
|
-
}) : "Deploy Workspace"
|
|
1758
|
-
})
|
|
1759
|
-
})
|
|
1760
|
-
]
|
|
1761
|
-
})]
|
|
1762
|
-
})]
|
|
1763
|
-
});
|
|
1764
|
-
}
|
|
1765
|
-
//#endregion
|
|
1766
|
-
//#region src/pages/standalone-pricing-page.tsx
|
|
1767
|
-
async function fetchTiersFromApi(apiBasePath) {
|
|
1768
|
-
const res = await fetch(`${apiBasePath}/v1/billing/tiers`);
|
|
1769
|
-
if (!res.ok) throw new Error("Failed to fetch pricing tiers");
|
|
1770
|
-
const data = await res.json();
|
|
1771
|
-
return data.tiers || data;
|
|
1772
|
-
}
|
|
1773
|
-
const FAQ = [
|
|
1774
|
-
{
|
|
1775
|
-
q: "What is included usage?",
|
|
1776
|
-
a: "Each plan includes a monthly USD usage balance. Compute and AI model usage are billed per second from this balance. If you exceed your included amount, overage is billed at the same rates with no penalty."
|
|
1777
|
-
},
|
|
1778
|
-
{
|
|
1779
|
-
q: "Can I change plans later?",
|
|
1780
|
-
a: "Yes. You can upgrade or downgrade your plan at any time. When you upgrade, you are charged the prorated difference. When you downgrade, the change takes effect at the end of your billing cycle."
|
|
1781
|
-
},
|
|
1782
|
-
{
|
|
1783
|
-
q: "Does unused balance roll over?",
|
|
1784
|
-
a: "Monthly included usage does not roll over to the next month. Your balance resets at the start of each billing cycle."
|
|
1785
|
-
}
|
|
1786
|
-
];
|
|
1787
|
-
function StandalonePricingPage({ variant = "sandbox", initialTiers, apiBasePath = "", onSelectTier, fetchTiers, title = "Simple, transparent pricing", subtitle = "Choose the plan that fits your needs. Upgrade or downgrade at any time.", eyebrow, className }) {
|
|
1788
|
-
const [state, setState] = React$1.useState({
|
|
1789
|
-
tiers: initialTiers || [],
|
|
1790
|
-
loading: !initialTiers,
|
|
1791
|
-
error: null,
|
|
1792
|
-
billingPeriod: "monthly",
|
|
1793
|
-
selectingTier: false
|
|
1794
|
-
});
|
|
1795
|
-
const loadTiers = React$1.useCallback(async () => {
|
|
1796
|
-
setState((prev) => ({
|
|
1797
|
-
...prev,
|
|
1798
|
-
loading: true,
|
|
1799
|
-
error: null
|
|
1800
|
-
}));
|
|
1801
|
-
try {
|
|
1802
|
-
const tiers = fetchTiers ? await fetchTiers() : await fetchTiersFromApi(apiBasePath);
|
|
1803
|
-
setState((prev) => ({
|
|
1804
|
-
...prev,
|
|
1805
|
-
tiers,
|
|
1806
|
-
loading: false
|
|
1807
|
-
}));
|
|
1808
|
-
} catch (err) {
|
|
1809
|
-
setState((prev) => ({
|
|
1810
|
-
...prev,
|
|
1811
|
-
loading: false,
|
|
1812
|
-
error: err instanceof Error ? err.message : "An unknown error occurred"
|
|
1813
|
-
}));
|
|
1814
|
-
}
|
|
1815
|
-
}, [apiBasePath, fetchTiers]);
|
|
1816
|
-
React$1.useEffect(() => {
|
|
1817
|
-
if (!initialTiers) loadTiers();
|
|
1818
|
-
}, [initialTiers, loadTiers]);
|
|
1819
|
-
const handleSelectTier = React$1.useCallback(async (tierId) => {
|
|
1820
|
-
if (onSelectTier) {
|
|
1821
|
-
onSelectTier(tierId, state.billingPeriod);
|
|
1822
|
-
return;
|
|
1823
|
-
}
|
|
1824
|
-
setState((prev) => ({
|
|
1825
|
-
...prev,
|
|
1826
|
-
selectingTier: true
|
|
1827
|
-
}));
|
|
1828
|
-
try {
|
|
1829
|
-
const res = await fetch(`${apiBasePath}/v1/billing/subscribe`, {
|
|
1830
|
-
method: "POST",
|
|
1831
|
-
headers: { "Content-Type": "application/json" },
|
|
1832
|
-
body: JSON.stringify({
|
|
1833
|
-
tierId,
|
|
1834
|
-
billingPeriod: state.billingPeriod
|
|
1835
|
-
})
|
|
1836
|
-
});
|
|
1837
|
-
if (!res.ok) throw new Error("Failed to initiate subscription");
|
|
1838
|
-
const { checkoutUrl, loginUrl } = await res.json();
|
|
1839
|
-
if (loginUrl) window.location.href = loginUrl;
|
|
1840
|
-
else if (checkoutUrl) window.location.href = checkoutUrl;
|
|
1841
|
-
} catch (err) {
|
|
1842
|
-
setState((prev) => ({
|
|
1843
|
-
...prev,
|
|
1844
|
-
selectingTier: false,
|
|
1845
|
-
error: err instanceof Error ? err.message : "Failed to select tier"
|
|
1846
|
-
}));
|
|
1847
|
-
}
|
|
1848
|
-
}, [
|
|
1849
|
-
apiBasePath,
|
|
1850
|
-
state.billingPeriod,
|
|
1851
|
-
onSelectTier
|
|
1852
|
-
]);
|
|
1853
|
-
return /* @__PURE__ */ jsxs("div", {
|
|
1854
|
-
className: cn$1("mx-auto max-w-6xl px-6 py-16 space-y-16", className),
|
|
1855
|
-
children: [
|
|
1856
|
-
/* @__PURE__ */ jsxs("div", {
|
|
1857
|
-
className: "space-y-4 text-center",
|
|
1858
|
-
children: [
|
|
1859
|
-
eyebrow && /* @__PURE__ */ jsx("span", {
|
|
1860
|
-
className: "text-xs font-bold uppercase tracking-widest text-[var(--brand-emerald,#10B981)]",
|
|
1861
|
-
children: eyebrow
|
|
1862
|
-
}),
|
|
1863
|
-
/* @__PURE__ */ jsx("h1", {
|
|
1864
|
-
className: "text-4xl font-extrabold tracking-tight text-foreground sm:text-5xl font-display",
|
|
1865
|
-
children: title
|
|
1866
|
-
}),
|
|
1867
|
-
/* @__PURE__ */ jsx("p", {
|
|
1868
|
-
className: "mx-auto max-w-2xl text-lg text-muted-foreground",
|
|
1869
|
-
children: subtitle
|
|
1870
|
-
})
|
|
1871
|
-
]
|
|
1872
|
-
}),
|
|
1873
|
-
state.loading ? /* @__PURE__ */ jsxs("div", {
|
|
1874
|
-
className: "space-y-8",
|
|
1875
|
-
children: [/* @__PURE__ */ jsxs("div", {
|
|
1876
|
-
className: "flex items-center justify-center gap-4",
|
|
1877
|
-
children: [/* @__PURE__ */ jsx(Skeleton, { className: "h-10 w-24" }), /* @__PURE__ */ jsx(Skeleton, { className: "h-10 w-24" })]
|
|
1878
|
-
}), /* @__PURE__ */ jsxs("div", {
|
|
1879
|
-
className: "grid grid-cols-1 gap-6 lg:grid-cols-3",
|
|
1880
|
-
children: [
|
|
1881
|
-
/* @__PURE__ */ jsx(SkeletonCard, { className: "h-[500px]" }),
|
|
1882
|
-
/* @__PURE__ */ jsx(SkeletonCard, { className: "h-[500px]" }),
|
|
1883
|
-
/* @__PURE__ */ jsx(SkeletonCard, { className: "h-[500px]" })
|
|
1884
|
-
]
|
|
1885
|
-
})]
|
|
1886
|
-
}) : state.error ? /* @__PURE__ */ jsxs("div", {
|
|
1887
|
-
className: "flex flex-col items-center justify-center space-y-4 rounded-xl border border-destructive/20 bg-destructive/5 p-8 text-center",
|
|
1888
|
-
children: [/* @__PURE__ */ jsx("p", {
|
|
1889
|
-
className: "text-destructive text-sm font-medium",
|
|
1890
|
-
children: state.error
|
|
1891
|
-
}), /* @__PURE__ */ jsx("button", {
|
|
1892
|
-
type: "button",
|
|
1893
|
-
onClick: loadTiers,
|
|
1894
|
-
className: "rounded-lg bg-primary px-4 py-2 font-medium text-primary-foreground text-sm hover:bg-primary/90 transition-colors",
|
|
1895
|
-
children: "Try Again"
|
|
1896
|
-
})]
|
|
1897
|
-
}) : /* @__PURE__ */ jsx(PricingPage, {
|
|
1898
|
-
tiers: state.tiers,
|
|
1899
|
-
billingPeriod: state.billingPeriod,
|
|
1900
|
-
onBillingPeriodChange: (p) => setState((prev) => ({
|
|
1901
|
-
...prev,
|
|
1902
|
-
billingPeriod: p
|
|
1903
|
-
})),
|
|
1904
|
-
onSelectTier: handleSelectTier,
|
|
1905
|
-
variant,
|
|
1906
|
-
loading: state.selectingTier
|
|
1907
|
-
}),
|
|
1908
|
-
/* @__PURE__ */ jsxs("div", {
|
|
1909
|
-
className: "mx-auto max-w-2xl space-y-4 border-t border-[var(--md3-outline-variant)] pt-12",
|
|
1910
|
-
children: [/* @__PURE__ */ jsx("h2", {
|
|
1911
|
-
className: "text-center text-xl font-bold text-foreground mb-6",
|
|
1912
|
-
children: "Frequently Asked Questions"
|
|
1913
|
-
}), FAQ.map(({ q, a }) => /* @__PURE__ */ jsxs("details", {
|
|
1914
|
-
className: "group rounded-xl border border-[var(--md3-outline-variant)] bg-surface-container overflow-hidden",
|
|
1915
|
-
children: [/* @__PURE__ */ jsxs("summary", {
|
|
1916
|
-
className: "flex cursor-pointer items-center justify-between px-6 py-4 font-medium text-foreground text-sm",
|
|
1917
|
-
children: [q, /* @__PURE__ */ jsx(ChevronDown, { className: "h-4 w-4 text-muted-foreground transition-transform group-open:rotate-180" })]
|
|
1918
|
-
}), /* @__PURE__ */ jsx("div", {
|
|
1919
|
-
className: "px-6 pb-4",
|
|
1920
|
-
children: /* @__PURE__ */ jsx("p", {
|
|
1921
|
-
className: "text-sm text-muted-foreground leading-relaxed",
|
|
1922
|
-
children: a
|
|
1923
|
-
})
|
|
1924
|
-
})]
|
|
1925
|
-
}, q))]
|
|
1926
|
-
})
|
|
1927
|
-
]
|
|
1928
|
-
});
|
|
1929
|
-
}
|
|
1930
|
-
//#endregion
|
|
1931
|
-
//#region src/pages/profiles-page.tsx
|
|
1932
|
-
const TIER_LIMITS = {
|
|
1933
|
-
free: 3,
|
|
1934
|
-
starter: 10,
|
|
1935
|
-
pro: 50,
|
|
1936
|
-
enterprise: Number.POSITIVE_INFINITY
|
|
1937
|
-
};
|
|
1938
|
-
function ProfilesPage({ apiClient, tier = "free", maxProfiles, onCompareClick, title = "Profiles" }) {
|
|
1939
|
-
const [builtinProfiles, setBuiltinProfiles] = React$1.useState([]);
|
|
1940
|
-
const [customProfiles, setCustomProfiles] = React$1.useState([]);
|
|
1941
|
-
const [loading, setLoading] = React$1.useState(true);
|
|
1942
|
-
const [error, setError] = React$1.useState(null);
|
|
1943
|
-
const [searchQuery, setSearchQuery] = React$1.useState("");
|
|
1944
|
-
const [createDialogOpen, setCreateDialogOpen] = React$1.useState(false);
|
|
1945
|
-
const [editingProfile, setEditingProfile] = React$1.useState(null);
|
|
1946
|
-
const [deletingProfile, setDeletingProfile] = React$1.useState(null);
|
|
1947
|
-
const [detailProfile, setDetailProfile] = React$1.useState(null);
|
|
1948
|
-
const profileLimit = maxProfiles ?? TIER_LIMITS[tier] ?? 3;
|
|
1949
|
-
const canCreateMore = customProfiles.length < profileLimit;
|
|
1950
|
-
const loadProfiles = React$1.useCallback(async () => {
|
|
1951
|
-
try {
|
|
1952
|
-
setLoading(true);
|
|
1953
|
-
setError(null);
|
|
1954
|
-
const data = await apiClient.listProfiles();
|
|
1955
|
-
setBuiltinProfiles(data.builtin);
|
|
1956
|
-
setCustomProfiles(data.custom);
|
|
1957
|
-
} catch (err) {
|
|
1958
|
-
setError(err instanceof Error ? err.message : "Failed to load profiles");
|
|
1959
|
-
} finally {
|
|
1960
|
-
setLoading(false);
|
|
1961
|
-
}
|
|
1962
|
-
}, [apiClient]);
|
|
1963
|
-
React$1.useEffect(() => {
|
|
1964
|
-
loadProfiles();
|
|
1965
|
-
}, [loadProfiles]);
|
|
1966
|
-
const filteredBuiltin = builtinProfiles.filter((p) => p.name.toLowerCase().includes(searchQuery.toLowerCase()) || p.description?.toLowerCase().includes(searchQuery.toLowerCase()));
|
|
1967
|
-
const filteredCustom = customProfiles.filter((p) => p.name.toLowerCase().includes(searchQuery.toLowerCase()) || p.description?.toLowerCase().includes(searchQuery.toLowerCase()));
|
|
1968
|
-
return /* @__PURE__ */ jsxs("div", {
|
|
1969
|
-
className: "space-y-6",
|
|
1970
|
-
children: [
|
|
1971
|
-
/* @__PURE__ */ jsxs("div", {
|
|
1972
|
-
className: "flex items-center justify-between",
|
|
1973
|
-
children: [/* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("h1", {
|
|
1974
|
-
className: "font-semibold text-2xl",
|
|
1975
|
-
children: title
|
|
1976
|
-
}), /* @__PURE__ */ jsx("p", {
|
|
1977
|
-
className: "text-muted-foreground",
|
|
1978
|
-
children: "Customize agent behavior with system prompts, models, and instructions"
|
|
1979
|
-
})] }), /* @__PURE__ */ jsxs("div", {
|
|
1980
|
-
className: "flex items-center gap-3",
|
|
1981
|
-
children: [onCompareClick && customProfiles.length >= 2 && /* @__PURE__ */ jsx(Button, {
|
|
1982
|
-
variant: "outline",
|
|
1983
|
-
onClick: () => onCompareClick(customProfiles),
|
|
1984
|
-
children: "Compare Profiles"
|
|
1985
|
-
}), /* @__PURE__ */ jsxs(Button, {
|
|
1986
|
-
onClick: () => setCreateDialogOpen(true),
|
|
1987
|
-
disabled: !canCreateMore,
|
|
1988
|
-
children: [/* @__PURE__ */ jsx(Plus, { className: "mr-2 h-4 w-4" }), "Create Profile"]
|
|
1989
|
-
})]
|
|
1990
|
-
})]
|
|
1991
|
-
}),
|
|
1992
|
-
!canCreateMore && /* @__PURE__ */ jsxs("div", {
|
|
1993
|
-
className: "flex items-center gap-2 rounded-lg border border-[var(--surface-warning-border)] bg-[var(--surface-warning-bg)] p-3 text-sm text-[var(--surface-warning-text)]",
|
|
1994
|
-
children: [/* @__PURE__ */ jsx(AlertCircle, { className: "h-4 w-4" }), /* @__PURE__ */ jsxs("span", { children: [
|
|
1995
|
-
"You've reached your profile limit (",
|
|
1996
|
-
profileLimit,
|
|
1997
|
-
" profiles). Upgrade your plan to create more."
|
|
1998
|
-
] })]
|
|
1999
|
-
}),
|
|
2000
|
-
/* @__PURE__ */ jsxs("div", {
|
|
2001
|
-
className: "relative max-w-md",
|
|
2002
|
-
children: [/* @__PURE__ */ jsx(Search, { className: "absolute top-1/2 left-3 h-4 w-4 -translate-y-1/2 text-muted-foreground" }), /* @__PURE__ */ jsx(Input, {
|
|
2003
|
-
placeholder: "Search profiles...",
|
|
2004
|
-
value: searchQuery,
|
|
2005
|
-
onChange: (e) => setSearchQuery(e.target.value),
|
|
2006
|
-
className: "pl-9"
|
|
2007
|
-
})]
|
|
2008
|
-
}),
|
|
2009
|
-
error && /* @__PURE__ */ jsxs("div", {
|
|
2010
|
-
className: "flex items-center gap-2 rounded-lg border border-[var(--surface-danger-border)] bg-[var(--surface-danger-bg)] p-3 text-[var(--surface-danger-text)] text-sm",
|
|
2011
|
-
children: [
|
|
2012
|
-
/* @__PURE__ */ jsx(AlertCircle, { className: "h-4 w-4" }),
|
|
2013
|
-
/* @__PURE__ */ jsx("span", { children: error }),
|
|
2014
|
-
/* @__PURE__ */ jsx(Button, {
|
|
2015
|
-
variant: "ghost",
|
|
2016
|
-
size: "sm",
|
|
2017
|
-
onClick: loadProfiles,
|
|
2018
|
-
children: "Retry"
|
|
2019
|
-
})
|
|
2020
|
-
]
|
|
2021
|
-
}),
|
|
2022
|
-
loading && /* @__PURE__ */ jsx("div", {
|
|
2023
|
-
className: "flex items-center justify-center py-12",
|
|
2024
|
-
children: /* @__PURE__ */ jsx(Loader2, {
|
|
2025
|
-
className: "h-8 w-8 animate-spin text-muted-foreground",
|
|
2026
|
-
"data-motion": "essential"
|
|
2027
|
-
})
|
|
2028
|
-
}),
|
|
2029
|
-
!loading && /* @__PURE__ */ jsxs("div", {
|
|
2030
|
-
className: "space-y-8",
|
|
2031
|
-
children: [/* @__PURE__ */ jsxs("section", { children: [/* @__PURE__ */ jsx("div", {
|
|
2032
|
-
className: "mb-3 flex items-center justify-between",
|
|
2033
|
-
children: /* @__PURE__ */ jsxs("h2", {
|
|
2034
|
-
className: "font-medium text-lg",
|
|
2035
|
-
children: [
|
|
2036
|
-
"Your Profiles (",
|
|
2037
|
-
customProfiles.length,
|
|
2038
|
-
"/",
|
|
2039
|
-
profileLimit === Number.POSITIVE_INFINITY ? "inf" : profileLimit,
|
|
2040
|
-
")"
|
|
2041
|
-
]
|
|
2042
|
-
})
|
|
2043
|
-
}), filteredCustom.length === 0 ? /* @__PURE__ */ jsx(EmptyState, {
|
|
2044
|
-
icon: /* @__PURE__ */ jsx(Settings2, { className: "h-8 w-8" }),
|
|
2045
|
-
title: "No custom profiles yet",
|
|
2046
|
-
description: "Create a profile to customize agent behavior",
|
|
2047
|
-
action: canCreateMore ? /* @__PURE__ */ jsxs(Button, {
|
|
2048
|
-
onClick: () => setCreateDialogOpen(true),
|
|
2049
|
-
children: [/* @__PURE__ */ jsx(Plus, { className: "mr-2 h-4 w-4" }), "Create Profile"]
|
|
2050
|
-
}) : void 0
|
|
2051
|
-
}) : /* @__PURE__ */ jsx("div", {
|
|
2052
|
-
className: "grid gap-4 md:grid-cols-2 lg:grid-cols-3",
|
|
2053
|
-
children: filteredCustom.map((profile) => /* @__PURE__ */ jsx(ProfileCard, {
|
|
2054
|
-
profile,
|
|
2055
|
-
onView: () => setDetailProfile(profile),
|
|
2056
|
-
onEdit: () => setEditingProfile(profile),
|
|
2057
|
-
onDelete: () => setDeletingProfile(profile)
|
|
2058
|
-
}, profile.id))
|
|
2059
|
-
})] }), /* @__PURE__ */ jsxs("section", { children: [/* @__PURE__ */ jsx("div", {
|
|
2060
|
-
className: "mb-3 flex items-center justify-between",
|
|
2061
|
-
children: /* @__PURE__ */ jsxs("h2", {
|
|
2062
|
-
className: "font-medium text-lg",
|
|
2063
|
-
children: [
|
|
2064
|
-
"Built-in Profiles (",
|
|
2065
|
-
builtinProfiles.length,
|
|
2066
|
-
")"
|
|
2067
|
-
]
|
|
2068
|
-
})
|
|
2069
|
-
}), filteredBuiltin.length === 0 ? /* @__PURE__ */ jsx("p", {
|
|
2070
|
-
className: "text-muted-foreground text-sm",
|
|
2071
|
-
children: "No matching built-in profiles"
|
|
2072
|
-
}) : /* @__PURE__ */ jsx("div", {
|
|
2073
|
-
className: "grid gap-4 md:grid-cols-2 lg:grid-cols-3",
|
|
2074
|
-
children: filteredBuiltin.map((profile) => /* @__PURE__ */ jsx(ProfileCard, {
|
|
2075
|
-
profile,
|
|
2076
|
-
onView: () => setDetailProfile(profile),
|
|
2077
|
-
isBuiltin: true
|
|
2078
|
-
}, profile.id))
|
|
2079
|
-
})] })]
|
|
2080
|
-
}),
|
|
2081
|
-
/* @__PURE__ */ jsx(ProfileFormDialog, {
|
|
2082
|
-
open: createDialogOpen || !!editingProfile,
|
|
2083
|
-
onClose: () => {
|
|
2084
|
-
setCreateDialogOpen(false);
|
|
2085
|
-
setEditingProfile(null);
|
|
2086
|
-
},
|
|
2087
|
-
profile: editingProfile,
|
|
2088
|
-
builtinProfiles,
|
|
2089
|
-
apiClient,
|
|
2090
|
-
onSuccess: () => {
|
|
2091
|
-
setCreateDialogOpen(false);
|
|
2092
|
-
setEditingProfile(null);
|
|
2093
|
-
loadProfiles();
|
|
2094
|
-
}
|
|
2095
|
-
}),
|
|
2096
|
-
/* @__PURE__ */ jsx(DeleteProfileDialog, {
|
|
2097
|
-
profile: deletingProfile,
|
|
2098
|
-
onClose: () => setDeletingProfile(null),
|
|
2099
|
-
apiClient,
|
|
2100
|
-
onSuccess: () => {
|
|
2101
|
-
setDeletingProfile(null);
|
|
2102
|
-
loadProfiles();
|
|
2103
|
-
}
|
|
2104
|
-
}),
|
|
2105
|
-
/* @__PURE__ */ jsx(ProfileDetailDialog, {
|
|
2106
|
-
profile: detailProfile,
|
|
2107
|
-
onClose: () => setDetailProfile(null),
|
|
2108
|
-
onEdit: detailProfile && !detailProfile.is_builtin ? () => {
|
|
2109
|
-
setDetailProfile(null);
|
|
2110
|
-
setEditingProfile(detailProfile);
|
|
2111
|
-
} : void 0,
|
|
2112
|
-
onDuplicate: canCreateMore ? () => {
|
|
2113
|
-
setDetailProfile(null);
|
|
2114
|
-
setEditingProfile({
|
|
2115
|
-
...detailProfile,
|
|
2116
|
-
id: "",
|
|
2117
|
-
name: `${detailProfile?.name}-copy`,
|
|
2118
|
-
is_builtin: false
|
|
2119
|
-
});
|
|
2120
|
-
} : void 0
|
|
2121
|
-
})
|
|
2122
|
-
]
|
|
2123
|
-
});
|
|
2124
|
-
}
|
|
2125
|
-
function ProfileCard({ profile, onView, onEdit, onDelete, isBuiltin }) {
|
|
2126
|
-
return /* @__PURE__ */ jsxs(Card, {
|
|
2127
|
-
className: "cursor-pointer p-4 transition-colors hover:border-[var(--md3-outline)]",
|
|
2128
|
-
onClick: onView,
|
|
2129
|
-
children: [
|
|
2130
|
-
/* @__PURE__ */ jsxs("div", {
|
|
2131
|
-
className: "flex items-start justify-between",
|
|
2132
|
-
children: [/* @__PURE__ */ jsxs("div", {
|
|
2133
|
-
className: "flex-1",
|
|
2134
|
-
children: [/* @__PURE__ */ jsxs("div", {
|
|
2135
|
-
className: "flex items-center gap-2",
|
|
2136
|
-
children: [
|
|
2137
|
-
/* @__PURE__ */ jsx("h3", {
|
|
2138
|
-
className: "font-medium",
|
|
2139
|
-
children: profile.name
|
|
2140
|
-
}),
|
|
2141
|
-
isBuiltin && /* @__PURE__ */ jsx(Badge, {
|
|
2142
|
-
variant: "secondary",
|
|
2143
|
-
className: "border-0 text-xs",
|
|
2144
|
-
children: "Built-in"
|
|
2145
|
-
}),
|
|
2146
|
-
profile.is_public && !isBuiltin && /* @__PURE__ */ jsx(Badge, {
|
|
2147
|
-
className: "border border-[var(--surface-info-border)] bg-[var(--surface-info-bg)] text-[var(--surface-info-text)] text-xs",
|
|
2148
|
-
children: "Public"
|
|
2149
|
-
})
|
|
2150
|
-
]
|
|
2151
|
-
}), profile.description && /* @__PURE__ */ jsx("p", {
|
|
2152
|
-
className: "mt-1 line-clamp-2 text-muted-foreground text-sm",
|
|
2153
|
-
children: profile.description
|
|
2154
|
-
})]
|
|
2155
|
-
}), !isBuiltin && /* @__PURE__ */ jsxs("div", {
|
|
2156
|
-
className: "flex gap-1",
|
|
2157
|
-
onClick: (e) => e.stopPropagation(),
|
|
2158
|
-
children: [onEdit && /* @__PURE__ */ jsx(Button, {
|
|
2159
|
-
variant: "ghost",
|
|
2160
|
-
size: "icon",
|
|
2161
|
-
onClick: onEdit,
|
|
2162
|
-
"aria-label": "Edit profile",
|
|
2163
|
-
children: /* @__PURE__ */ jsx(Edit2, { className: "h-4 w-4" })
|
|
2164
|
-
}), onDelete && /* @__PURE__ */ jsx(Button, {
|
|
2165
|
-
variant: "ghost",
|
|
2166
|
-
size: "icon",
|
|
2167
|
-
onClick: onDelete,
|
|
2168
|
-
"aria-label": "Delete profile",
|
|
2169
|
-
children: /* @__PURE__ */ jsx(Trash2, { className: "h-4 w-4 text-[var(--surface-danger-text)]" })
|
|
2170
|
-
})]
|
|
2171
|
-
})]
|
|
2172
|
-
}),
|
|
2173
|
-
/* @__PURE__ */ jsxs("div", {
|
|
2174
|
-
className: "mt-3 flex flex-wrap gap-2",
|
|
2175
|
-
children: [profile.extends && /* @__PURE__ */ jsxs(Badge, {
|
|
2176
|
-
variant: "outline",
|
|
2177
|
-
className: "text-xs",
|
|
2178
|
-
children: ["extends ", profile.extends]
|
|
2179
|
-
}), profile.model && /* @__PURE__ */ jsx(Badge, {
|
|
2180
|
-
variant: "outline",
|
|
2181
|
-
className: "text-xs",
|
|
2182
|
-
children: profile.model.split("/").pop()
|
|
2183
|
-
})]
|
|
2184
|
-
}),
|
|
2185
|
-
profile.metrics && profile.metrics.total_runs > 0 && /* @__PURE__ */ jsxs("div", {
|
|
2186
|
-
className: "mt-3 flex gap-4 border-[var(--md3-outline-variant)] border-t pt-3 text-muted-foreground text-xs",
|
|
2187
|
-
children: [
|
|
2188
|
-
/* @__PURE__ */ jsxs("span", { children: [profile.metrics.total_runs, " runs"] }),
|
|
2189
|
-
/* @__PURE__ */ jsxs("span", { children: [profile.metrics.success_rate.toFixed(0), "% success"] }),
|
|
2190
|
-
/* @__PURE__ */ jsxs("span", { children: [
|
|
2191
|
-
"~",
|
|
2192
|
-
(profile.metrics.avg_duration_ms / 1e3).toFixed(1),
|
|
2193
|
-
"s avg"
|
|
2194
|
-
] })
|
|
2195
|
-
]
|
|
2196
|
-
})
|
|
2197
|
-
]
|
|
2198
|
-
});
|
|
2199
|
-
}
|
|
2200
|
-
function ProfileFormDialog({ open, onClose, profile, builtinProfiles, apiClient, onSuccess }) {
|
|
2201
|
-
const isEditing = !!profile?.id;
|
|
2202
|
-
const [formData, setFormData] = React$1.useState({
|
|
2203
|
-
name: "",
|
|
2204
|
-
description: "",
|
|
2205
|
-
extends: "",
|
|
2206
|
-
model: "",
|
|
2207
|
-
system_prompt: "",
|
|
2208
|
-
instructions: [],
|
|
2209
|
-
tags: [],
|
|
2210
|
-
is_public: false
|
|
2211
|
-
});
|
|
2212
|
-
const [saving, setSaving] = React$1.useState(false);
|
|
2213
|
-
const [error, setError] = React$1.useState(null);
|
|
2214
|
-
React$1.useEffect(() => {
|
|
2215
|
-
if (profile) setFormData({
|
|
2216
|
-
name: profile.name,
|
|
2217
|
-
description: profile.description ?? "",
|
|
2218
|
-
extends: profile.extends ?? "",
|
|
2219
|
-
model: profile.model ?? "",
|
|
2220
|
-
system_prompt: profile.system_prompt ?? "",
|
|
2221
|
-
instructions: profile.instructions ?? [],
|
|
2222
|
-
tags: profile.tags ?? [],
|
|
2223
|
-
is_public: profile.is_public ?? false
|
|
2224
|
-
});
|
|
2225
|
-
else setFormData({
|
|
2226
|
-
name: "",
|
|
2227
|
-
description: "",
|
|
2228
|
-
extends: "",
|
|
2229
|
-
model: "",
|
|
2230
|
-
system_prompt: "",
|
|
2231
|
-
instructions: [],
|
|
2232
|
-
tags: [],
|
|
2233
|
-
is_public: false
|
|
2234
|
-
});
|
|
2235
|
-
}, [profile]);
|
|
2236
|
-
const handleSubmit = async (e) => {
|
|
2237
|
-
e.preventDefault();
|
|
2238
|
-
setError(null);
|
|
2239
|
-
setSaving(true);
|
|
2240
|
-
try {
|
|
2241
|
-
const cleanData = {
|
|
2242
|
-
name: formData.name,
|
|
2243
|
-
...formData.description && { description: formData.description },
|
|
2244
|
-
...formData.extends && { extends: formData.extends },
|
|
2245
|
-
...formData.model && { model: formData.model },
|
|
2246
|
-
...formData.system_prompt && { system_prompt: formData.system_prompt },
|
|
2247
|
-
...formData.instructions?.length && { instructions: formData.instructions },
|
|
2248
|
-
...formData.tags?.length && { tags: formData.tags },
|
|
2249
|
-
is_public: formData.is_public
|
|
2250
|
-
};
|
|
2251
|
-
if (isEditing) await apiClient.updateProfile(profile.id, cleanData);
|
|
2252
|
-
else await apiClient.createProfile(cleanData);
|
|
2253
|
-
onSuccess();
|
|
2254
|
-
} catch (err) {
|
|
2255
|
-
setError(err instanceof Error ? err.message : "Failed to save profile");
|
|
2256
|
-
} finally {
|
|
2257
|
-
setSaving(false);
|
|
2258
|
-
}
|
|
2259
|
-
};
|
|
2260
|
-
return /* @__PURE__ */ jsx(Dialog, {
|
|
2261
|
-
open,
|
|
2262
|
-
onOpenChange: (o) => !o && onClose(),
|
|
2263
|
-
children: /* @__PURE__ */ jsxs(DialogContent, {
|
|
2264
|
-
className: "max-w-2xl",
|
|
2265
|
-
children: [/* @__PURE__ */ jsxs(DialogHeader, { children: [/* @__PURE__ */ jsx(DialogTitle, { children: isEditing ? "Edit Profile" : "Create Profile" }), /* @__PURE__ */ jsx(DialogDescription, { children: isEditing ? "Update your custom profile configuration" : "Create a new profile to customize agent behavior" })] }), /* @__PURE__ */ jsxs("form", {
|
|
2266
|
-
onSubmit: handleSubmit,
|
|
2267
|
-
className: "space-y-4",
|
|
2268
|
-
children: [
|
|
2269
|
-
error && /* @__PURE__ */ jsxs("div", {
|
|
2270
|
-
className: "flex items-center gap-2 rounded-lg border border-[var(--surface-danger-border)] bg-[var(--surface-danger-bg)] p-3 text-[var(--surface-danger-text)] text-sm",
|
|
2271
|
-
children: [/* @__PURE__ */ jsx(AlertCircle, { className: "h-4 w-4" }), /* @__PURE__ */ jsx("span", { children: error })]
|
|
2272
|
-
}),
|
|
2273
|
-
/* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("label", {
|
|
2274
|
-
className: "mb-1 block font-medium text-sm",
|
|
2275
|
-
children: "Name *"
|
|
2276
|
-
}), /* @__PURE__ */ jsx(Input, {
|
|
2277
|
-
value: formData.name,
|
|
2278
|
-
onChange: (e) => setFormData((d) => ({
|
|
2279
|
-
...d,
|
|
2280
|
-
name: e.target.value
|
|
2281
|
-
})),
|
|
2282
|
-
placeholder: "my-custom-profile",
|
|
2283
|
-
required: true,
|
|
2284
|
-
pattern: "^[a-zA-Z0-9_-]+$",
|
|
2285
|
-
title: "Only letters, numbers, hyphens, and underscores"
|
|
2286
|
-
})] }),
|
|
2287
|
-
/* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("label", {
|
|
2288
|
-
className: "mb-1 block font-medium text-sm",
|
|
2289
|
-
children: "Description"
|
|
2290
|
-
}), /* @__PURE__ */ jsx(Input, {
|
|
2291
|
-
value: formData.description,
|
|
2292
|
-
onChange: (e) => setFormData((d) => ({
|
|
2293
|
-
...d,
|
|
2294
|
-
description: e.target.value
|
|
2295
|
-
})),
|
|
2296
|
-
placeholder: "A brief description of this profile"
|
|
2297
|
-
})] }),
|
|
2298
|
-
/* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("label", {
|
|
2299
|
-
className: "mb-1 block font-medium text-sm",
|
|
2300
|
-
children: "Extends (base profile)"
|
|
2301
|
-
}), /* @__PURE__ */ jsxs("select", {
|
|
2302
|
-
value: formData.extends,
|
|
2303
|
-
onChange: (e) => setFormData((d) => ({
|
|
2304
|
-
...d,
|
|
2305
|
-
extends: e.target.value
|
|
2306
|
-
})),
|
|
2307
|
-
className: "w-full rounded-md border border-[var(--md3-outline-variant)] bg-surface-container-low px-3 py-2 text-sm",
|
|
2308
|
-
children: [/* @__PURE__ */ jsx("option", {
|
|
2309
|
-
value: "",
|
|
2310
|
-
children: "None (start from scratch)"
|
|
2311
|
-
}), builtinProfiles.map((p) => /* @__PURE__ */ jsxs("option", {
|
|
2312
|
-
value: p.name,
|
|
2313
|
-
children: [
|
|
2314
|
-
p.name,
|
|
2315
|
-
" - ",
|
|
2316
|
-
p.description ?? "Built-in profile"
|
|
2317
|
-
]
|
|
2318
|
-
}, p.id))]
|
|
2319
|
-
})] }),
|
|
2320
|
-
/* @__PURE__ */ jsxs("div", { children: [
|
|
2321
|
-
/* @__PURE__ */ jsx("label", {
|
|
2322
|
-
className: "mb-1 block font-medium text-sm",
|
|
2323
|
-
children: "Model"
|
|
2324
|
-
}),
|
|
2325
|
-
/* @__PURE__ */ jsx(Input, {
|
|
2326
|
-
value: formData.model,
|
|
2327
|
-
onChange: (e) => setFormData((d) => ({
|
|
2328
|
-
...d,
|
|
2329
|
-
model: e.target.value
|
|
2330
|
-
})),
|
|
2331
|
-
placeholder: "anthropic/claude-sonnet-4"
|
|
2332
|
-
}),
|
|
2333
|
-
/* @__PURE__ */ jsx("p", {
|
|
2334
|
-
className: "mt-1 text-muted-foreground text-xs",
|
|
2335
|
-
children: "Format: provider/model-id (e.g., anthropic/claude-sonnet-4)"
|
|
2336
|
-
})
|
|
2337
|
-
] }),
|
|
2338
|
-
/* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("label", {
|
|
2339
|
-
className: "mb-1 block font-medium text-sm",
|
|
2340
|
-
children: "System Prompt"
|
|
2341
|
-
}), /* @__PURE__ */ jsx("textarea", {
|
|
2342
|
-
value: formData.system_prompt,
|
|
2343
|
-
onChange: (e) => setFormData((d) => ({
|
|
2344
|
-
...d,
|
|
2345
|
-
system_prompt: e.target.value
|
|
2346
|
-
})),
|
|
2347
|
-
placeholder: "Custom system prompt for the agent...",
|
|
2348
|
-
rows: 4,
|
|
2349
|
-
className: "w-full rounded-md border border-[var(--md3-outline-variant)] bg-surface-container-low px-3 py-2 font-mono text-sm"
|
|
2350
|
-
})] }),
|
|
2351
|
-
/* @__PURE__ */ jsxs("div", { children: [
|
|
2352
|
-
/* @__PURE__ */ jsx("label", {
|
|
2353
|
-
className: "mb-1 block font-medium text-sm",
|
|
2354
|
-
children: "Tags"
|
|
2355
|
-
}),
|
|
2356
|
-
/* @__PURE__ */ jsx(Input, {
|
|
2357
|
-
value: formData.tags?.join(", "),
|
|
2358
|
-
onChange: (e) => setFormData((d) => ({
|
|
2359
|
-
...d,
|
|
2360
|
-
tags: e.target.value.split(",").map((t) => t.trim()).filter(Boolean)
|
|
2361
|
-
})),
|
|
2362
|
-
placeholder: "trading, aggressive, experimental"
|
|
2363
|
-
}),
|
|
2364
|
-
/* @__PURE__ */ jsx("p", {
|
|
2365
|
-
className: "mt-1 text-muted-foreground text-xs",
|
|
2366
|
-
children: "Comma-separated tags for organization"
|
|
2367
|
-
})
|
|
2368
|
-
] }),
|
|
2369
|
-
/* @__PURE__ */ jsxs("div", {
|
|
2370
|
-
className: "flex items-center gap-2",
|
|
2371
|
-
children: [/* @__PURE__ */ jsx("input", {
|
|
2372
|
-
type: "checkbox",
|
|
2373
|
-
id: "is_public",
|
|
2374
|
-
checked: formData.is_public,
|
|
2375
|
-
onChange: (e) => setFormData((d) => ({
|
|
2376
|
-
...d,
|
|
2377
|
-
is_public: e.target.checked
|
|
2378
|
-
})),
|
|
2379
|
-
className: "rounded border-[var(--md3-outline-variant)]"
|
|
2380
|
-
}), /* @__PURE__ */ jsx("label", {
|
|
2381
|
-
htmlFor: "is_public",
|
|
2382
|
-
className: "text-sm",
|
|
2383
|
-
children: "Make this profile public (visible to other users)"
|
|
2384
|
-
})]
|
|
2385
|
-
}),
|
|
2386
|
-
/* @__PURE__ */ jsxs(DialogFooter, { children: [/* @__PURE__ */ jsx(Button, {
|
|
2387
|
-
type: "button",
|
|
2388
|
-
variant: "outline",
|
|
2389
|
-
onClick: onClose,
|
|
2390
|
-
children: "Cancel"
|
|
2391
|
-
}), /* @__PURE__ */ jsxs(Button, {
|
|
2392
|
-
type: "submit",
|
|
2393
|
-
disabled: saving || !formData.name,
|
|
2394
|
-
children: [saving && /* @__PURE__ */ jsx(Loader2, {
|
|
2395
|
-
className: "mr-2 h-4 w-4 animate-spin",
|
|
2396
|
-
"data-motion": "essential"
|
|
2397
|
-
}), isEditing ? "Save Changes" : "Create Profile"]
|
|
2398
|
-
})] })
|
|
2399
|
-
]
|
|
2400
|
-
})]
|
|
2401
|
-
})
|
|
2402
|
-
});
|
|
2403
|
-
}
|
|
2404
|
-
function DeleteProfileDialog({ profile, onClose, apiClient, onSuccess }) {
|
|
2405
|
-
const [deleting, setDeleting] = React$1.useState(false);
|
|
2406
|
-
const [error, setError] = React$1.useState(null);
|
|
2407
|
-
const handleDelete = async () => {
|
|
2408
|
-
if (!profile) return;
|
|
2409
|
-
setError(null);
|
|
2410
|
-
setDeleting(true);
|
|
2411
|
-
try {
|
|
2412
|
-
await apiClient.deleteProfile(profile.id);
|
|
2413
|
-
onSuccess();
|
|
2414
|
-
} catch (err) {
|
|
2415
|
-
setError(err instanceof Error ? err.message : "Failed to delete profile");
|
|
2416
|
-
} finally {
|
|
2417
|
-
setDeleting(false);
|
|
2418
|
-
}
|
|
2419
|
-
};
|
|
2420
|
-
return /* @__PURE__ */ jsx(Dialog, {
|
|
2421
|
-
open: !!profile,
|
|
2422
|
-
onOpenChange: (o) => !o && onClose(),
|
|
2423
|
-
children: /* @__PURE__ */ jsxs(DialogContent, { children: [
|
|
2424
|
-
/* @__PURE__ */ jsxs(DialogHeader, { children: [/* @__PURE__ */ jsx(DialogTitle, { children: "Delete Profile" }), /* @__PURE__ */ jsxs(DialogDescription, { children: [
|
|
2425
|
-
"Are you sure you want to delete \"",
|
|
2426
|
-
profile?.name,
|
|
2427
|
-
"\"? This action cannot be undone."
|
|
2428
|
-
] })] }),
|
|
2429
|
-
error && /* @__PURE__ */ jsxs("div", {
|
|
2430
|
-
className: "flex items-center gap-2 rounded-lg border border-[var(--surface-danger-border)] bg-[var(--surface-danger-bg)] p-3 text-[var(--surface-danger-text)] text-sm",
|
|
2431
|
-
children: [/* @__PURE__ */ jsx(AlertCircle, { className: "h-4 w-4" }), /* @__PURE__ */ jsx("span", { children: error })]
|
|
2432
|
-
}),
|
|
2433
|
-
profile?.metrics && profile.metrics.total_runs > 0 && /* @__PURE__ */ jsxs("div", {
|
|
2434
|
-
className: "rounded-lg border border-[var(--surface-warning-border)] bg-[var(--surface-warning-bg)] p-3 text-sm text-[var(--surface-warning-text)]",
|
|
2435
|
-
children: [
|
|
2436
|
-
"This profile has ",
|
|
2437
|
-
profile.metrics.total_runs,
|
|
2438
|
-
" recorded runs. Deleting it will lose all performance metrics."
|
|
2439
|
-
]
|
|
2440
|
-
}),
|
|
2441
|
-
/* @__PURE__ */ jsxs(DialogFooter, { children: [/* @__PURE__ */ jsx(Button, {
|
|
2442
|
-
variant: "outline",
|
|
2443
|
-
onClick: onClose,
|
|
2444
|
-
children: "Cancel"
|
|
2445
|
-
}), /* @__PURE__ */ jsxs(Button, {
|
|
2446
|
-
variant: "destructive",
|
|
2447
|
-
onClick: handleDelete,
|
|
2448
|
-
disabled: deleting,
|
|
2449
|
-
children: [deleting && /* @__PURE__ */ jsx(Loader2, {
|
|
2450
|
-
className: "mr-2 h-4 w-4 animate-spin",
|
|
2451
|
-
"data-motion": "essential"
|
|
2452
|
-
}), "Delete Profile"]
|
|
2453
|
-
})] })
|
|
2454
|
-
] })
|
|
2455
|
-
});
|
|
2456
|
-
}
|
|
2457
|
-
function ProfileDetailDialog({ profile, onClose, onEdit, onDuplicate }) {
|
|
2458
|
-
if (!profile) return null;
|
|
2459
|
-
return /* @__PURE__ */ jsx(Dialog, {
|
|
2460
|
-
open: !!profile,
|
|
2461
|
-
onOpenChange: (o) => !o && onClose(),
|
|
2462
|
-
children: /* @__PURE__ */ jsxs(DialogContent, {
|
|
2463
|
-
className: "max-w-2xl",
|
|
2464
|
-
children: [
|
|
2465
|
-
/* @__PURE__ */ jsxs(DialogHeader, { children: [/* @__PURE__ */ jsxs("div", {
|
|
2466
|
-
className: "flex items-center gap-2",
|
|
2467
|
-
children: [
|
|
2468
|
-
/* @__PURE__ */ jsx(DialogTitle, { children: profile.name }),
|
|
2469
|
-
profile.is_builtin && /* @__PURE__ */ jsx(Badge, {
|
|
2470
|
-
variant: "secondary",
|
|
2471
|
-
className: "border-0",
|
|
2472
|
-
children: "Built-in"
|
|
2473
|
-
}),
|
|
2474
|
-
profile.is_public && !profile.is_builtin && /* @__PURE__ */ jsx(Badge, {
|
|
2475
|
-
className: "border border-[var(--surface-info-border)] bg-[var(--surface-info-bg)] text-[var(--surface-info-text)]",
|
|
2476
|
-
children: "Public"
|
|
2477
|
-
})
|
|
2478
|
-
]
|
|
2479
|
-
}), profile.description && /* @__PURE__ */ jsx(DialogDescription, { children: profile.description })] }),
|
|
2480
|
-
/* @__PURE__ */ jsxs("div", {
|
|
2481
|
-
className: "space-y-4",
|
|
2482
|
-
children: [
|
|
2483
|
-
/* @__PURE__ */ jsxs("div", {
|
|
2484
|
-
className: "grid gap-4 sm:grid-cols-2",
|
|
2485
|
-
children: [profile.extends && /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("label", {
|
|
2486
|
-
className: "font-medium text-muted-foreground text-xs",
|
|
2487
|
-
children: "Extends"
|
|
2488
|
-
}), /* @__PURE__ */ jsx("p", {
|
|
2489
|
-
className: "text-sm",
|
|
2490
|
-
children: profile.extends
|
|
2491
|
-
})] }), profile.model && /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("label", {
|
|
2492
|
-
className: "font-medium text-muted-foreground text-xs",
|
|
2493
|
-
children: "Model"
|
|
2494
|
-
}), /* @__PURE__ */ jsx("p", {
|
|
2495
|
-
className: "text-sm",
|
|
2496
|
-
children: profile.model
|
|
2497
|
-
})] })]
|
|
2498
|
-
}),
|
|
2499
|
-
profile.tags && profile.tags.length > 0 && /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("label", {
|
|
2500
|
-
className: "font-medium text-muted-foreground text-xs",
|
|
2501
|
-
children: "Tags"
|
|
2502
|
-
}), /* @__PURE__ */ jsx("div", {
|
|
2503
|
-
className: "mt-1 flex flex-wrap gap-1",
|
|
2504
|
-
children: profile.tags.map((tag) => /* @__PURE__ */ jsx(Badge, {
|
|
2505
|
-
variant: "outline",
|
|
2506
|
-
className: "text-xs",
|
|
2507
|
-
children: tag
|
|
2508
|
-
}, tag))
|
|
2509
|
-
})] }),
|
|
2510
|
-
profile.system_prompt && /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("label", {
|
|
2511
|
-
className: "font-medium text-muted-foreground text-xs",
|
|
2512
|
-
children: "System Prompt"
|
|
2513
|
-
}), /* @__PURE__ */ jsxs("div", {
|
|
2514
|
-
className: "relative mt-1",
|
|
2515
|
-
children: [/* @__PURE__ */ jsx("pre", {
|
|
2516
|
-
className: "max-h-48 overflow-auto rounded-lg bg-surface-container-low p-3 font-mono text-sm",
|
|
2517
|
-
children: profile.system_prompt
|
|
2518
|
-
}), /* @__PURE__ */ jsx(Button, {
|
|
2519
|
-
variant: "ghost",
|
|
2520
|
-
size: "icon",
|
|
2521
|
-
className: "absolute top-2 right-2",
|
|
2522
|
-
onClick: () => navigator.clipboard.writeText(profile.system_prompt),
|
|
2523
|
-
"aria-label": "Copy system prompt",
|
|
2524
|
-
children: /* @__PURE__ */ jsx(Copy, { className: "h-4 w-4" })
|
|
2525
|
-
})]
|
|
2526
|
-
})] }),
|
|
2527
|
-
profile.instructions && profile.instructions.length > 0 && /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("label", {
|
|
2528
|
-
className: "font-medium text-muted-foreground text-xs",
|
|
2529
|
-
children: "Instructions"
|
|
2530
|
-
}), /* @__PURE__ */ jsx("ul", {
|
|
2531
|
-
className: "mt-1 space-y-1",
|
|
2532
|
-
children: profile.instructions.map((inst, i) => /* @__PURE__ */ jsxs("li", {
|
|
2533
|
-
className: "flex items-start gap-2 text-sm",
|
|
2534
|
-
children: [/* @__PURE__ */ jsx(ChevronRight, { className: "mt-0.5 h-4 w-4 text-muted-foreground" }), inst]
|
|
2535
|
-
}, i))
|
|
2536
|
-
})] }),
|
|
2537
|
-
profile.metrics && profile.metrics.total_runs > 0 && /* @__PURE__ */ jsxs("div", {
|
|
2538
|
-
className: "rounded-lg border border-[var(--md3-outline-variant)] bg-surface-container-high p-4",
|
|
2539
|
-
children: [/* @__PURE__ */ jsx("label", {
|
|
2540
|
-
className: "font-medium text-muted-foreground text-xs",
|
|
2541
|
-
children: "Performance Metrics"
|
|
2542
|
-
}), /* @__PURE__ */ jsxs("div", {
|
|
2543
|
-
className: "mt-2 grid grid-cols-2 gap-4 sm:grid-cols-4",
|
|
2544
|
-
children: [
|
|
2545
|
-
/* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("p", {
|
|
2546
|
-
className: "font-medium text-2xl",
|
|
2547
|
-
children: profile.metrics.total_runs
|
|
2548
|
-
}), /* @__PURE__ */ jsx("p", {
|
|
2549
|
-
className: "text-muted-foreground text-xs",
|
|
2550
|
-
children: "Total Runs"
|
|
2551
|
-
})] }),
|
|
2552
|
-
/* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsxs("p", {
|
|
2553
|
-
className: "font-medium text-2xl",
|
|
2554
|
-
children: [profile.metrics.success_rate.toFixed(0), "%"]
|
|
2555
|
-
}), /* @__PURE__ */ jsx("p", {
|
|
2556
|
-
className: "text-muted-foreground text-xs",
|
|
2557
|
-
children: "Success Rate"
|
|
2558
|
-
})] }),
|
|
2559
|
-
/* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsxs("p", {
|
|
2560
|
-
className: "font-medium text-2xl",
|
|
2561
|
-
children: [(profile.metrics.avg_duration_ms / 1e3).toFixed(1), "s"]
|
|
2562
|
-
}), /* @__PURE__ */ jsx("p", {
|
|
2563
|
-
className: "text-muted-foreground text-xs",
|
|
2564
|
-
children: "Avg Duration"
|
|
2565
|
-
})] }),
|
|
2566
|
-
profile.metrics.avg_tokens_used && /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("p", {
|
|
2567
|
-
className: "font-medium text-2xl",
|
|
2568
|
-
children: profile.metrics.avg_tokens_used.toLocaleString()
|
|
2569
|
-
}), /* @__PURE__ */ jsx("p", {
|
|
2570
|
-
className: "text-muted-foreground text-xs",
|
|
2571
|
-
children: "Avg Tokens"
|
|
2572
|
-
})] })
|
|
2573
|
-
]
|
|
2574
|
-
})]
|
|
2575
|
-
})
|
|
2576
|
-
]
|
|
2577
|
-
}),
|
|
2578
|
-
/* @__PURE__ */ jsxs(DialogFooter, { children: [
|
|
2579
|
-
/* @__PURE__ */ jsx(Button, {
|
|
2580
|
-
variant: "outline",
|
|
2581
|
-
onClick: onClose,
|
|
2582
|
-
children: "Close"
|
|
2583
|
-
}),
|
|
2584
|
-
onDuplicate && /* @__PURE__ */ jsxs(Button, {
|
|
2585
|
-
variant: "outline",
|
|
2586
|
-
onClick: onDuplicate,
|
|
2587
|
-
children: [/* @__PURE__ */ jsx(Copy, { className: "mr-2 h-4 w-4" }), "Duplicate"]
|
|
2588
|
-
}),
|
|
2589
|
-
onEdit && /* @__PURE__ */ jsxs(Button, {
|
|
2590
|
-
onClick: onEdit,
|
|
2591
|
-
children: [/* @__PURE__ */ jsx(Edit2, { className: "mr-2 h-4 w-4" }), "Edit"]
|
|
2592
|
-
})
|
|
2593
|
-
] })
|
|
2594
|
-
]
|
|
2595
|
-
})
|
|
2596
|
-
});
|
|
2597
|
-
}
|
|
2598
|
-
//#endregion
|
|
2599
590
|
//#region src/pages/env-importer.ts
|
|
2600
591
|
const NORMALIZE_RE = /[^A-Z0-9_]/g;
|
|
2601
592
|
/** Mirrors the on-keystroke normalization used by the single-secret input. */
|
|
@@ -3484,42 +1475,6 @@ function SecretsPage({ apiClient, className, teamSecretsHint, title = "Environme
|
|
|
3484
1475
|
});
|
|
3485
1476
|
}
|
|
3486
1477
|
//#endregion
|
|
3487
|
-
//#region src/pages/templates-page.tsx
|
|
3488
|
-
function TemplatesPage({ templates, loading = false, onUseTemplate, className }) {
|
|
3489
|
-
return /* @__PURE__ */ jsxs("div", {
|
|
3490
|
-
className: cn$1("space-y-8", className),
|
|
3491
|
-
children: [/* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("h1", {
|
|
3492
|
-
className: "font-bold text-3xl text-foreground",
|
|
3493
|
-
children: "Templates"
|
|
3494
|
-
}), /* @__PURE__ */ jsx("p", {
|
|
3495
|
-
className: "mt-1 text-muted-foreground",
|
|
3496
|
-
children: "Pre-configured environments to get started quickly"
|
|
3497
|
-
})] }), loading || !templates ? /* @__PURE__ */ jsx("div", {
|
|
3498
|
-
className: "grid grid-cols-1 gap-5 sm:grid-cols-2 lg:grid-cols-3",
|
|
3499
|
-
children: Array.from({ length: 6 }).map((_, i) => /* @__PURE__ */ jsx(Skeleton, { className: "h-56 rounded-2xl" }, i))
|
|
3500
|
-
}) : templates.length === 0 ? /* @__PURE__ */ jsxs("div", {
|
|
3501
|
-
className: "rounded-2xl border border-[var(--md3-outline-variant)] bg-surface-container p-16 text-center",
|
|
3502
|
-
children: [
|
|
3503
|
-
/* @__PURE__ */ jsx(Layers, { className: "mx-auto mb-3 h-10 w-10 text-muted-foreground" }),
|
|
3504
|
-
/* @__PURE__ */ jsx("p", {
|
|
3505
|
-
className: "text-sm font-medium text-foreground mb-1",
|
|
3506
|
-
children: "No templates available"
|
|
3507
|
-
}),
|
|
3508
|
-
/* @__PURE__ */ jsx("p", {
|
|
3509
|
-
className: "text-sm text-muted-foreground",
|
|
3510
|
-
children: "Check back later for pre-configured environments."
|
|
3511
|
-
})
|
|
3512
|
-
]
|
|
3513
|
-
}) : /* @__PURE__ */ jsx("div", {
|
|
3514
|
-
className: "grid grid-cols-1 gap-5 sm:grid-cols-2 lg:grid-cols-3",
|
|
3515
|
-
children: templates.map((template) => /* @__PURE__ */ jsx(TemplateCard, {
|
|
3516
|
-
template,
|
|
3517
|
-
onUseTemplate
|
|
3518
|
-
}, template.id))
|
|
3519
|
-
})]
|
|
3520
|
-
});
|
|
3521
|
-
}
|
|
3522
|
-
//#endregion
|
|
3523
1478
|
//#region src/pages/startup-scripts-page.tsx
|
|
3524
1479
|
const SCRIPT_TYPE_META = {
|
|
3525
1480
|
bash: {
|
|
@@ -4516,4 +2471,4 @@ function getPresetForTemplate(id) {
|
|
|
4516
2471
|
};
|
|
4517
2472
|
}
|
|
4518
2473
|
//#endregion
|
|
4519
|
-
export { AuthPage, BillingPage,
|
|
2474
|
+
export { AuthPage, BillingPage, SecretsPage, StartupScriptsPage, getPresetForTemplate };
|