@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/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 { a as PricingPage, i as UsageChart, r as TemplateCard, s as BillingDashboard, t as DashboardPageHeader } from "./page-layout-DJV7cbxx.js";
4
- import { Badge, Button, Card, Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, EmptyState, Input, Skeleton, SkeletonCard, Tabs, TabsContent, TabsList, TabsTrigger } from "@tangle-network/ui/primitives";
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, ArrowLeft, Check, CheckCircle, ChevronDown, ChevronRight, ChevronUp, Clock, Copy, Cpu, Edit2, Eye, EyeOff, GripVertical, Info, Key, Layers, Loader2, Lock, MemoryStick, Pencil, Plus, Power, PowerOff, Search, Settings, Settings2, Terminal, Trash2, Upload } from "lucide-react";
10
- import { focusField, focusRing } from "@tangle-network/ui/utils";
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: "#f7f7f8",
14
- card: "#ffffff",
15
- border: "rgba(15, 23, 42, 0.10)",
16
- text: "#0f172a",
17
- muted: "#71717a",
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 = "#0f172a", accentHover = "#1e293b", logoSize = "lg", className, style, children }) {
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: accent,
162
- border: "1px solid rgba(255,255,255,0.12)",
163
- color: "#fff",
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 = accentHover,
170
- onMouseLeave: (e) => e.currentTarget.style.background = accent,
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 = "rgba(0,0,0,0.02)",
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: "#b91c1c"
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, ProfilesPage, ProvisioningWizard, SecretsPage, StandalonePricingPage, StartupScriptsPage, TemplatesPage, getPresetForTemplate, resolveEnvironment };
2474
+ export { AuthPage, BillingPage, SecretsPage, StartupScriptsPage, getPresetForTemplate };