@tangle-network/sandbox-ui 0.125.3 → 0.126.1

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