@tangle-network/sandbox-ui 0.10.1 → 0.10.3

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.d.ts CHANGED
@@ -39,6 +39,32 @@ interface ResourceLimits {
39
39
  ramMaxGB?: number;
40
40
  storageMaxGB?: number;
41
41
  }
42
+ interface ModelOption {
43
+ value: string;
44
+ label: string;
45
+ disabled?: boolean;
46
+ }
47
+ interface PricingRates {
48
+ cpuPerHr: number;
49
+ ramPerGbHr: number;
50
+ diskPerGbHr: number;
51
+ minChargePerHr?: number;
52
+ }
53
+ /**
54
+ * Describes one selectable plan tier for the purpose of badging locked
55
+ * presets with the *correct* upgrade target. Without this, every locked
56
+ * preset shows "Pro" — wrong for a user who is already on Pro and whose
57
+ * next step up is Enterprise.
58
+ */
59
+ interface PlanTierInfo {
60
+ /** Stable id (e.g. "free" | "pro" | "enterprise") */
61
+ id: string;
62
+ /** Short badge label shown on locked presets (e.g. "Pro", "Enterprise") */
63
+ label: string;
64
+ cpuMax: number;
65
+ ramMaxGB: number;
66
+ storageMaxGB: number;
67
+ }
42
68
  interface ProvisioningWizardProps {
43
69
  environments?: EnvironmentOption[];
44
70
  onLoadEnvironments?: () => Promise<EnvironmentEntry[]>;
@@ -56,6 +82,16 @@ interface ProvisioningWizardProps {
56
82
  onLoadStartupScripts?: () => Promise<StartupScriptEntry[]>;
57
83
  /** Plan-based resource limits — caps the slider maximums */
58
84
  resourceLimits?: ResourceLimits;
85
+ /** Override the list of model engines shown in step 3 */
86
+ modelOptions?: ModelOption[];
87
+ /** Real pricing rates from the API for accurate cost calculation */
88
+ pricingRates?: PricingRates;
89
+ /**
90
+ * Ordered list of plan tiers (smallest to largest). When provided,
91
+ * locked presets are badged with the label of the smallest tier that
92
+ * would unlock them. Falls back to a generic "Pro" badge when omitted.
93
+ */
94
+ planTiers?: PlanTierInfo[];
59
95
  }
60
96
  interface StartupScriptEntry {
61
97
  id: string;
@@ -82,7 +118,7 @@ interface ProvisioningConfig {
82
118
  startupScriptIds?: string[];
83
119
  }
84
120
  declare function resolveEnvironment(env: EnvironmentEntry): EnvironmentOption;
85
- declare function ProvisioningWizard({ environments: environmentsProp, onLoadEnvironments, onSubmit, onBack, className, variant, defaultEnvironment, defaultConfig, skipToReview, onLoadStartupScripts, resourceLimits, }: ProvisioningWizardProps): react_jsx_runtime.JSX.Element;
121
+ declare function ProvisioningWizard({ environments: environmentsProp, onLoadEnvironments, onSubmit, onBack, className, variant, defaultEnvironment, defaultConfig, skipToReview, onLoadStartupScripts, resourceLimits, modelOptions, pricingRates, planTiers, }: ProvisioningWizardProps): react_jsx_runtime.JSX.Element;
86
122
 
87
123
  type ProductVariant = "sandbox";
88
124
  interface StandalonePricingPageProps {
@@ -234,4 +270,4 @@ type TemplateCategory = "blockchain" | "ai-ml" | "frontend" | "infrastructure" |
234
270
  type TemplatePreset = Omit<ProvisioningConfig, "name" | "gitUrl" | "envVars" | "driver" | "startupScriptIds">;
235
271
  declare function getPresetForTemplate(id: string): TemplatePreset;
236
272
 
237
- export { BillingPage, type BillingPageData, type BillingPageProps, type EnvironmentEntry, type EnvironmentOption, PricingTier, type ProductVariant$1 as ProductVariant, type Profile, type ProfileFormData, type ProfileMetrics, ProfilesPage, type ProfilesPageProps, type ProvisioningConfig, ProvisioningWizard, type ProvisioningWizardProps, type ResourceLimits, type ScriptType, type Secret, type SecretsApiClient, SecretsPage, type SecretsPageProps, StandalonePricingPage, type StandalonePricingPageProps, type StartupScript, type StartupScriptEntry, type StartupScriptFormData, type StartupScriptsApiClient, StartupScriptsPage, type StartupScriptsPageProps, type TemplateCategory, type TemplatePreset, TemplatesPage, type TemplatesPageProps, getPresetForTemplate, resolveEnvironment };
273
+ export { BillingPage, type BillingPageData, type BillingPageProps, type EnvironmentEntry, type EnvironmentOption, type ModelOption, type PlanTierInfo, type PricingRates, PricingTier, type ProductVariant$1 as ProductVariant, type Profile, type ProfileFormData, type ProfileMetrics, ProfilesPage, type ProfilesPageProps, type ProvisioningConfig, ProvisioningWizard, type ProvisioningWizardProps, type ResourceLimits, type ScriptType, type Secret, type SecretsApiClient, SecretsPage, type SecretsPageProps, StandalonePricingPage, type StandalonePricingPageProps, type StartupScript, type StartupScriptEntry, type StartupScriptFormData, type StartupScriptsApiClient, StartupScriptsPage, type StartupScriptsPageProps, type TemplateCategory, type TemplatePreset, TemplatesPage, type TemplatesPageProps, getPresetForTemplate, resolveEnvironment };
package/dist/pages.js CHANGED
@@ -301,6 +301,10 @@ import * as React2 from "react";
301
301
  import { ArrowLeft, Layers, Cpu, Bot, Info, Loader2, Settings, Plus, Trash2, Check } from "lucide-react";
302
302
  import { Fragment as Fragment2, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
303
303
  var VALID_DRIVERS = /* @__PURE__ */ new Set(["docker", "firecracker", "tangle"]);
304
+ var DEFAULT_MODEL_OPTIONS = [
305
+ { value: "claude-sonnet", label: "Claude Sonnet 4.6 (Highly Capable)" }
306
+ ];
307
+ var DEFAULT_MODEL_TIER = DEFAULT_MODEL_OPTIONS[0]?.value ?? "claude-sonnet";
304
308
  var STACK_DISPLAY = {
305
309
  universal: { name: "Default", abbr: "D", color: "violet", textClass: "text-[var(--surface-violet-text)]" },
306
310
  ethereum: { name: "Ethereum", abbr: "\u039E", color: "blue", textClass: "text-[var(--surface-info-text)]" },
@@ -344,8 +348,22 @@ var RAM_MIN = 2;
344
348
  var RAM_MAX = 32;
345
349
  var STORAGE_MIN = 20;
346
350
  var STORAGE_MAX = 512;
347
- function calcCost(cpu, ram, storage) {
348
- const cost = cpu * 0.045 + ram * 5e-3 + storage * 11e-4;
351
+ var DEFAULT_PRICING_RATES = {
352
+ cpuPerHr: 0.045,
353
+ ramPerGbHr: 5e-3,
354
+ diskPerGbHr: 11e-4,
355
+ minChargePerHr: void 0
356
+ };
357
+ var RAW_PRESETS = [
358
+ { name: "Lightweight", cpu: 2, ram: 4, storage: 50 },
359
+ { name: "Standard", cpu: 4, ram: 16, storage: 128 },
360
+ { name: "Performance", cpu: 8, ram: 32, storage: 256 }
361
+ ];
362
+ function calcCost(cpu, ram, storage, rates) {
363
+ const cost = Math.max(
364
+ rates.minChargePerHr ?? 0,
365
+ cpu * rates.cpuPerHr + ram * rates.ramPerGbHr + storage * rates.diskPerGbHr
366
+ );
349
367
  return cost.toFixed(2);
350
368
  }
351
369
  function ProvisioningWizard({
@@ -359,7 +377,10 @@ function ProvisioningWizard({
359
377
  defaultConfig,
360
378
  skipToReview,
361
379
  onLoadStartupScripts,
362
- resourceLimits
380
+ resourceLimits,
381
+ modelOptions,
382
+ pricingRates,
383
+ planTiers
363
384
  }) {
364
385
  const cpuMax = Math.max(CPU_MIN, Math.min(resourceLimits?.cpuMax ?? CPU_MAX, CPU_MAX));
365
386
  const ramMax = Math.max(RAM_MIN, Math.min(resourceLimits?.ramMaxGB ?? RAM_MAX, RAM_MAX));
@@ -399,8 +420,19 @@ function ProvisioningWizard({
399
420
  setRamGB((prev) => Math.min(prev, ramMax));
400
421
  setStorageGB((prev) => Math.min(prev, storageMax));
401
422
  }, [cpuMax, ramMax, storageMax]);
402
- const [modelTier, setModelTier] = React2.useState(dc?.modelTier ?? "claude-sonnet");
423
+ const [modelTier, setModelTier] = React2.useState(dc?.modelTier ?? DEFAULT_MODEL_TIER);
403
424
  const [systemPrompt, setSystemPrompt] = React2.useState(dc?.systemPrompt ?? "");
425
+ React2.useEffect(() => {
426
+ const options = modelOptions ?? DEFAULT_MODEL_OPTIONS;
427
+ if (options.length === 0) return;
428
+ const currentOption = options.find((o) => o.value === modelTier);
429
+ if (!currentOption || currentOption.disabled) {
430
+ const firstAvailable = options.find((o) => !o.disabled);
431
+ if (firstAvailable && firstAvailable.value !== modelTier) {
432
+ setModelTier(firstAvailable.value);
433
+ }
434
+ }
435
+ }, [modelOptions, modelTier]);
404
436
  const [name, setName] = React2.useState(dc?.name ?? "");
405
437
  const [gitUrl, setGitUrl] = React2.useState(dc?.gitUrl ?? "");
406
438
  const [envVars, setEnvVars] = React2.useState(dc?.envVars ?? [{ key: "", value: "" }]);
@@ -449,12 +481,45 @@ function ProvisioningWizard({
449
481
  setStorageGB(Math.min(storage, storageMax));
450
482
  setActivePreset(name2);
451
483
  };
452
- const presets = [
453
- { name: "Lightweight", cpu: Math.min(2, cpuMax), ram: Math.min(4, ramMax), storage: Math.min(50, storageMax) },
454
- { name: "Standard", cpu: Math.min(4, cpuMax), ram: Math.min(16, ramMax), storage: Math.min(128, storageMax) },
455
- { name: "Performance", cpu: Math.min(8, cpuMax), ram: Math.min(32, ramMax), storage: Math.min(256, storageMax) }
456
- ];
457
- const hourCost = calcCost(cpuCores, ramGB, storageGB);
484
+ const presets = RAW_PRESETS.map((p) => {
485
+ const locked = p.cpu > cpuMax || p.ram > ramMax || p.storage > storageMax;
486
+ let unlockLabel;
487
+ if (locked && planTiers && planTiers.length > 0) {
488
+ const unlocking = planTiers.find(
489
+ (t) => p.cpu <= t.cpuMax && p.ram <= t.ramMaxGB && p.storage <= t.storageMaxGB
490
+ );
491
+ unlockLabel = unlocking?.label;
492
+ }
493
+ return {
494
+ ...p,
495
+ fits: !locked,
496
+ locked,
497
+ unlockLabel: unlockLabel ?? "Pro"
498
+ };
499
+ });
500
+ const didInitPresetFromDcRef = React2.useRef(false);
501
+ const lastLimitsRef = React2.useRef(null);
502
+ React2.useEffect(() => {
503
+ const limitsUnchanged = lastLimitsRef.current !== null && lastLimitsRef.current.cpu === cpuMax && lastLimitsRef.current.ram === ramMax && lastLimitsRef.current.storage === storageMax;
504
+ if (limitsUnchanged) return;
505
+ lastLimitsRef.current = { cpu: cpuMax, ram: ramMax, storage: storageMax };
506
+ if (dc && !didInitPresetFromDcRef.current) {
507
+ didInitPresetFromDcRef.current = true;
508
+ const matching = RAW_PRESETS.find(
509
+ (p) => p.cpu === dc.cpuCores && p.ram === dc.ramGB && p.storage === dc.storageGB
510
+ );
511
+ if (matching) setActivePreset(matching.name);
512
+ return;
513
+ }
514
+ const largestFitting = [...RAW_PRESETS].reverse().find((p) => p.cpu <= cpuMax && p.ram <= ramMax && p.storage <= storageMax);
515
+ if (largestFitting) {
516
+ applyPreset(largestFitting.name, largestFitting.cpu, largestFitting.ram, largestFitting.storage);
517
+ } else {
518
+ setActivePreset(null);
519
+ }
520
+ }, [cpuMax, ramMax, storageMax, dc]);
521
+ const effectivePricingRates = pricingRates ?? DEFAULT_PRICING_RATES;
522
+ const hourCost = calcCost(cpuCores, ramGB, storageGB, effectivePricingRates);
458
523
  return /* @__PURE__ */ jsxs2("div", { className: cn("max-w-6xl mx-auto flex flex-col", className), children: [
459
524
  /* @__PURE__ */ jsxs2("div", { className: "mb-6 flex items-center gap-4 shrink-0", children: [
460
525
  onBack && /* @__PURE__ */ jsx2("button", { type: "button", onClick: onBack, className: "flex h-10 w-10 shrink-0 items-center justify-center rounded-xl border border-border hover:bg-muted/50 transition-colors text-foreground", children: /* @__PURE__ */ jsx2(ArrowLeft, { className: "h-5 w-5" }) }),
@@ -492,7 +557,11 @@ function ProvisioningWizard({
492
557
  setCpuCores(Math.min(4, cpuMax));
493
558
  setRamGB(Math.min(16, ramMax));
494
559
  setStorageGB(Math.min(128, storageMax));
495
- setModelTier("claude-sonnet");
560
+ {
561
+ const resetOptions = modelOptions ?? DEFAULT_MODEL_OPTIONS;
562
+ const firstAvailable = resetOptions.find((o) => !o.disabled);
563
+ setModelTier(firstAvailable?.value ?? DEFAULT_MODEL_TIER);
564
+ }
496
565
  setSystemPrompt("");
497
566
  setName("");
498
567
  setGitUrl("");
@@ -550,59 +619,74 @@ function ProvisioningWizard({
550
619
  /* @__PURE__ */ jsxs2("div", { className: "mb-6", children: [
551
620
  /* @__PURE__ */ jsx2("label", { className: "block font-label text-xs font-bold uppercase tracking-widest text-muted-foreground mb-3", children: "Compute Presets" }),
552
621
  /* @__PURE__ */ jsx2("div", { className: "grid grid-cols-3 gap-3", children: presets.map((p) => {
553
- const active = activePreset === p.name;
554
- return /* @__PURE__ */ jsxs2("button", { type: "button", onClick: () => applyPreset(p.name, p.cpu, p.ram, p.storage), className: cn("p-3 rounded-[14px] transition-all duration-200 text-center group border", active ? "bg-primary/5 border-primary ring-1 ring-primary/20 shadow-sm" : "bg-card border-border hover:border-primary/30 hover:shadow-sm active:scale-[0.97]"), children: [
555
- /* @__PURE__ */ jsx2("div", { className: cn("font-bold text-sm transition-colors duration-200", active ? "text-primary" : "text-foreground"), children: p.name }),
556
- /* @__PURE__ */ jsxs2("div", { className: "text-xs text-muted-foreground mt-0.5 font-mono", children: [
557
- p.cpu,
558
- "C / ",
559
- p.ram,
560
- "G / ",
561
- p.storage,
562
- "G"
563
- ] })
564
- ] }, p.name);
622
+ const active = activePreset === p.name && !p.locked;
623
+ return /* @__PURE__ */ jsxs2(
624
+ "button",
625
+ {
626
+ type: "button",
627
+ onClick: () => !p.locked && applyPreset(p.name, p.cpu, p.ram, p.storage),
628
+ disabled: p.locked,
629
+ className: cn(
630
+ "p-3 rounded-[14px] transition-all duration-200 text-center group border relative",
631
+ active ? "bg-primary/5 border-primary ring-1 ring-primary/20 shadow-sm" : p.locked ? "bg-muted/30 border-border opacity-60 cursor-not-allowed" : "bg-card border-border hover:border-primary/30 hover:shadow-sm active:scale-[0.97]"
632
+ ),
633
+ children: [
634
+ p.locked && /* @__PURE__ */ jsx2("div", { className: "absolute -top-1.5 -right-1.5 bg-primary text-primary-foreground text-[9px] font-bold px-1.5 py-0.5 rounded-full uppercase tracking-wider", children: p.unlockLabel }),
635
+ /* @__PURE__ */ jsx2("div", { className: cn("font-bold text-sm transition-colors duration-200", active ? "text-primary" : p.locked ? "text-muted-foreground" : "text-foreground"), children: p.name }),
636
+ /* @__PURE__ */ jsxs2("div", { className: "text-xs text-muted-foreground mt-0.5 font-mono", children: [
637
+ p.cpu,
638
+ " vCPU",
639
+ p.cpu === 1 ? "" : "s",
640
+ " / ",
641
+ p.ram,
642
+ "GB / ",
643
+ p.storage,
644
+ "GB"
645
+ ] })
646
+ ]
647
+ },
648
+ p.name
649
+ );
565
650
  }) })
566
651
  ] }),
567
652
  /* @__PURE__ */ jsx2("div", { className: "space-y-6", children: [
568
- { label: "Compute Cores (CPU)", value: cpuCores, setter: setCpuCores, min: CPU_MIN, max: cpuMax, step: 0.5, unit: "vCPUs" },
653
+ { label: "Compute Cores (CPU)", value: cpuCores, setter: setCpuCores, min: CPU_MIN, max: cpuMax, step: 0.5, unit: "vCPU" },
569
654
  { label: "Memory (RAM)", value: ramGB, setter: setRamGB, min: RAM_MIN, max: ramMax, step: 1, unit: "GB" },
570
655
  { label: "Ephemeral Storage", value: storageGB, setter: setStorageGB, min: STORAGE_MIN, max: storageMax, step: 8, unit: "GB" }
571
- ].map(({ label, value, setter, min, max, step: s, unit }) => /* @__PURE__ */ jsxs2("div", { children: [
572
- /* @__PURE__ */ jsxs2("div", { className: "flex justify-between items-end border-b border-border pb-1.5 mb-2", children: [
573
- /* @__PURE__ */ jsx2("label", { className: "font-label text-xs font-bold uppercase tracking-widest text-muted-foreground", children: label }),
574
- /* @__PURE__ */ jsxs2("span", { className: "text-xl font-bold text-foreground tracking-tight", children: [
575
- value,
576
- " ",
577
- /* @__PURE__ */ jsx2("span", { className: "text-xs text-primary ml-1", children: unit })
578
- ] })
579
- ] }),
580
- /* @__PURE__ */ jsx2(
581
- "input",
582
- {
583
- type: "range",
584
- min,
585
- max,
586
- step: s,
587
- value,
588
- onChange: (e) => {
589
- setter(+e.target.value);
590
- setActivePreset(null);
591
- },
592
- className: "w-full h-2 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-2 [&::-moz-range-track]:bg-border [&::-moz-range-track]:rounded-full [&::-moz-range-track]:h-2 [&::-webkit-slider-thumb]:bg-primary [&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:h-5 [&::-webkit-slider-thumb]:w-5 [&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:-mt-[6px] [&::-webkit-slider-thumb]:shadow-md [&::-webkit-slider-thumb]:border-2 [&::-webkit-slider-thumb]:border-primary-foreground [&::-webkit-slider-thumb]:transition-transform [&::-webkit-slider-thumb]:hover:scale-110"
593
- }
594
- ),
595
- /* @__PURE__ */ jsxs2("div", { className: "flex justify-between text-[10px] font-mono text-muted-foreground/60 mt-1", children: [
596
- /* @__PURE__ */ jsxs2("span", { children: [
597
- min,
598
- unit
656
+ ].map(({ label, value, setter, min, max, step: s, unit }) => {
657
+ const displayUnit = unit === "vCPU" ? `${value} vCPU${value === 1 ? "" : "s"}` : `${value}${unit}`;
658
+ return /* @__PURE__ */ jsxs2("div", { children: [
659
+ /* @__PURE__ */ jsxs2("div", { className: "flex justify-between items-end border-b border-border pb-1.5 mb-2", children: [
660
+ /* @__PURE__ */ jsx2("label", { className: "font-label text-xs font-bold uppercase tracking-widest text-muted-foreground", children: label }),
661
+ /* @__PURE__ */ jsx2("span", { className: "text-xl font-bold text-foreground tracking-tight", children: displayUnit })
599
662
  ] }),
600
- /* @__PURE__ */ jsxs2("span", { children: [
601
- max,
602
- unit
663
+ /* @__PURE__ */ jsx2(
664
+ "input",
665
+ {
666
+ type: "range",
667
+ min,
668
+ max,
669
+ step: s,
670
+ value,
671
+ onChange: (e) => {
672
+ setter(+e.target.value);
673
+ setActivePreset(null);
674
+ },
675
+ className: "w-full h-2 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-2 [&::-moz-range-track]:bg-border [&::-moz-range-track]:rounded-full [&::-moz-range-track]:h-2 [&::-webkit-slider-thumb]:bg-primary [&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:h-5 [&::-webkit-slider-thumb]:w-5 [&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:-mt-[6px] [&::-webkit-slider-thumb]:shadow-md [&::-webkit-slider-thumb]:border-2 [&::-webkit-slider-thumb]:border-primary-foreground [&::-webkit-slider-thumb]:transition-transform [&::-webkit-slider-thumb]:hover:scale-110"
676
+ }
677
+ ),
678
+ /* @__PURE__ */ jsxs2("div", { className: "flex justify-between text-[10px] font-mono text-muted-foreground/60 mt-1", children: [
679
+ /* @__PURE__ */ jsxs2("span", { children: [
680
+ min,
681
+ unit === "vCPU" ? min === 1 ? " vCPU" : " vCPUs" : unit
682
+ ] }),
683
+ /* @__PURE__ */ jsxs2("span", { children: [
684
+ max,
685
+ unit === "vCPU" ? max === 1 ? " vCPU" : " vCPUs" : unit
686
+ ] })
603
687
  ] })
604
- ] })
605
- ] }, label)) })
688
+ ] }, label);
689
+ }) })
606
690
  ] }) }),
607
691
  (!isMultistep || currentStep === 3) && /* @__PURE__ */ jsx2(React2.Fragment, { children: /* @__PURE__ */ jsxs2("section", { className: "bg-card border border-border rounded-[24px] p-6 shadow-2xl relative overflow-hidden animate-in fade-in slide-in-from-bottom-4 duration-300", children: [
608
692
  /* @__PURE__ */ jsxs2("div", { className: "flex items-center gap-3 mb-5", children: [
@@ -612,19 +696,26 @@ function ProvisioningWizard({
612
696
  /* @__PURE__ */ jsxs2("div", { className: "space-y-5", children: [
613
697
  /* @__PURE__ */ jsxs2("div", { children: [
614
698
  /* @__PURE__ */ jsx2("label", { className: "block font-label text-xs font-bold uppercase tracking-widest text-muted-foreground mb-2", children: "Model Engine" }),
615
- /* @__PURE__ */ jsxs2(
699
+ /* @__PURE__ */ jsx2(
616
700
  "select",
617
701
  {
618
702
  value: modelTier,
619
703
  onChange: (e) => setModelTier(e.target.value),
620
- className: "w-full bg-card border border-border rounded-xl h-12 px-4 font-bold text-sm text-foreground focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent appearance-none",
621
- children: [
622
- /* @__PURE__ */ jsx2("option", { value: "llama-3-8b", className: "bg-gray-900", children: "Llama-3-8B-Instruct (Lightweight)" }),
623
- /* @__PURE__ */ jsx2("option", { value: "mistral-7b", className: "bg-gray-900", children: "Mistral-7B-v0.2 (Efficient)" }),
624
- /* @__PURE__ */ jsx2("option", { value: "claude-sonnet", className: "bg-gray-900", children: "Claude Sonnet 4.6 (Highly Capable)" })
625
- ]
704
+ disabled: modelOptions && modelOptions.filter((o) => !o.disabled).length === 0,
705
+ className: "w-full bg-card border border-border rounded-xl h-12 px-4 font-bold text-sm text-foreground focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent appearance-none disabled:opacity-50 disabled:cursor-not-allowed",
706
+ children: (modelOptions ?? DEFAULT_MODEL_OPTIONS).map((option) => /* @__PURE__ */ jsx2(
707
+ "option",
708
+ {
709
+ value: option.value,
710
+ disabled: option.disabled,
711
+ className: "bg-gray-900",
712
+ children: option.label
713
+ },
714
+ option.value
715
+ ))
626
716
  }
627
- )
717
+ ),
718
+ modelOptions && modelOptions.length > 0 && modelOptions.every((o) => o.disabled) && /* @__PURE__ */ jsx2("p", { className: "text-xs text-muted-foreground mt-2", children: "All model options are currently disabled. Please upgrade your plan or contact support." })
628
719
  ] }),
629
720
  /* @__PURE__ */ jsxs2("div", { children: [
630
721
  /* @__PURE__ */ jsx2("label", { className: "block font-label text-xs font-bold uppercase tracking-widest text-muted-foreground mb-2", children: "Core Directives (System Prompt)" }),
@@ -782,57 +873,6 @@ function ProvisioningWizard({
782
873
  ] })
783
874
  ] }),
784
875
  /* @__PURE__ */ jsxs2("div", { className: "col-span-12 xl:col-span-4 sticky top-4 space-y-4", children: [
785
- /* @__PURE__ */ jsxs2("div", { className: "bg-card border border-primary/15 rounded-[24px] overflow-hidden shadow-2xl relative", children: [
786
- /* @__PURE__ */ jsx2("div", { className: "absolute inset-0 bg-[radial-gradient(circle_at_top,rgba(173,163,255,0.05)_0,transparent_100%)] pointer-events-none" }),
787
- /* @__PURE__ */ jsxs2("div", { className: "bg-muted/50 border-b border-border px-4 py-3 flex items-center gap-3", children: [
788
- /* @__PURE__ */ jsxs2("div", { className: "flex gap-2", children: [
789
- /* @__PURE__ */ jsx2("div", { className: "h-3 w-3 rounded-full bg-[#ff5f56]/80" }),
790
- /* @__PURE__ */ jsx2("div", { className: "h-3 w-3 rounded-full bg-[#ffbd2e]/80" }),
791
- /* @__PURE__ */ jsx2("div", { className: "h-3 w-3 rounded-full bg-[#27c93f]/80" })
792
- ] }),
793
- /* @__PURE__ */ jsx2("div", { className: "font-mono text-[10px] text-muted-foreground uppercase tracking-widest ml-2 border-l border-border pl-3", children: "deploy_sequence.sh" })
794
- ] }),
795
- /* @__PURE__ */ jsxs2("div", { className: "p-5 font-mono text-xs space-y-3 min-h-[240px] relative z-10 text-[13px]", children: [
796
- /* @__PURE__ */ jsxs2("div", { className: "text-green-400", children: [
797
- "root@tangle:~# ",
798
- /* @__PURE__ */ jsx2("span", { className: "text-foreground/80", children: "tangle-cli provision --new" })
799
- ] }),
800
- /* @__PURE__ */ jsx2("div", { className: "text-muted-foreground/70", children: "Initializing deployment handshake..." }),
801
- /* @__PURE__ */ jsxs2("div", { className: "text-foreground/70", children: [
802
- /* @__PURE__ */ jsx2("span", { className: "text-primary mr-2", children: "\u2713" }),
803
- " Bound Platform: ",
804
- /* @__PURE__ */ jsx2("span", { className: "text-foreground font-bold animate-in fade-in duration-300", children: environments.find((e) => e.id === selectedEnv)?.name ?? "Node.js" }, `env-${selectedEnv}`)
805
- ] }),
806
- /* @__PURE__ */ jsxs2("div", { className: "text-foreground/70", children: [
807
- /* @__PURE__ */ jsx2("span", { className: "text-primary mr-2", children: "\u2713" }),
808
- " Allocation CPU: ",
809
- /* @__PURE__ */ jsxs2("span", { className: "text-foreground font-bold animate-in fade-in duration-300", children: [
810
- cpuCores,
811
- " Cores"
812
- ] }, `cpu-${cpuCores}`)
813
- ] }),
814
- /* @__PURE__ */ jsxs2("div", { className: "text-foreground/70", children: [
815
- /* @__PURE__ */ jsx2("span", { className: "text-primary mr-2", children: "\u2713" }),
816
- " Allocation RAM: ",
817
- /* @__PURE__ */ jsxs2("span", { className: "text-foreground font-bold animate-in fade-in duration-300", children: [
818
- ramGB,
819
- "GB"
820
- ] }, `ram-${ramGB}`)
821
- ] }),
822
- /* @__PURE__ */ jsxs2("div", { className: "text-foreground/70", children: [
823
- /* @__PURE__ */ jsx2("span", { className: "text-primary mr-2", children: "\u2713" }),
824
- " Mounted Storage: ",
825
- /* @__PURE__ */ jsxs2("span", { className: "text-foreground font-bold animate-in fade-in duration-300", children: [
826
- storageGB,
827
- "GB NVMe"
828
- ] }, `disk-${storageGB}`)
829
- ] }),
830
- /* @__PURE__ */ jsxs2("div", { className: "pt-3 flex items-center gap-3", children: [
831
- /* @__PURE__ */ jsx2("div", { className: "w-2 h-4 bg-primary animate-pulse" }),
832
- /* @__PURE__ */ jsx2("span", { className: "text-muted-foreground", children: "Awaiting user confirmation..." })
833
- ] })
834
- ] })
835
- ] }),
836
876
  /* @__PURE__ */ jsxs2("div", { className: "p-6 rounded-[24px] bg-card border border-primary/15 relative overflow-hidden", children: [
837
877
  /* @__PURE__ */ jsx2("div", { className: "hidden" }),
838
878
  /* @__PURE__ */ jsxs2("div", { className: "flex justify-between items-center mb-4 relative z-10", children: [
@@ -846,32 +886,48 @@ function ProvisioningWizard({
846
886
  ] }, hourCost),
847
887
  /* @__PURE__ */ jsx2("span", { className: "text-muted-foreground text-sm font-bold", children: "/ hour" })
848
888
  ] }),
849
- /* @__PURE__ */ jsxs2("div", { className: "space-y-2 relative z-10 bg-card border border-border rounded-xl p-3", children: [
850
- /* @__PURE__ */ jsxs2("div", { className: "flex justify-between text-xs font-mono tracking-widest text-muted-foreground", children: [
851
- /* @__PURE__ */ jsx2("span", { children: "COMPUTE" }),
852
- /* @__PURE__ */ jsxs2("span", { className: "text-foreground", children: [
853
- "$",
854
- (cpuCores * 0.045).toFixed(2),
855
- "/h"
856
- ] })
857
- ] }),
858
- /* @__PURE__ */ jsxs2("div", { className: "flex justify-between text-xs font-mono tracking-widest text-muted-foreground", children: [
859
- /* @__PURE__ */ jsx2("span", { children: "MEMORY" }),
860
- /* @__PURE__ */ jsxs2("span", { className: "text-foreground/80", children: [
861
- "$",
862
- (ramGB * 5e-3).toFixed(2),
863
- "/h"
864
- ] })
865
- ] }),
866
- /* @__PURE__ */ jsxs2("div", { className: "flex justify-between text-xs font-mono tracking-widest text-muted-foreground", children: [
867
- /* @__PURE__ */ jsx2("span", { children: "STORAGE" }),
868
- /* @__PURE__ */ jsxs2("span", { className: "text-foreground/80", children: [
869
- "$",
870
- (storageGB * 11e-4).toFixed(2),
871
- "/h"
889
+ (() => {
890
+ const computeCost = cpuCores * effectivePricingRates.cpuPerHr;
891
+ const memoryCost = ramGB * effectivePricingRates.ramPerGbHr;
892
+ const storageCost = storageGB * effectivePricingRates.diskPerGbHr;
893
+ const lineSum = computeCost + memoryCost + storageCost;
894
+ const floor = effectivePricingRates.minChargePerHr ?? 0;
895
+ const floorApplies = floor > lineSum;
896
+ return /* @__PURE__ */ jsxs2("div", { className: "space-y-2 relative z-10 bg-card border border-border rounded-xl p-3", children: [
897
+ /* @__PURE__ */ jsxs2("div", { className: "flex justify-between text-xs font-mono tracking-widest text-muted-foreground", children: [
898
+ /* @__PURE__ */ jsx2("span", { children: "COMPUTE" }),
899
+ /* @__PURE__ */ jsxs2("span", { className: "text-foreground", children: [
900
+ "$",
901
+ computeCost.toFixed(2),
902
+ "/h"
903
+ ] })
904
+ ] }),
905
+ /* @__PURE__ */ jsxs2("div", { className: "flex justify-between text-xs font-mono tracking-widest text-muted-foreground", children: [
906
+ /* @__PURE__ */ jsx2("span", { children: "MEMORY" }),
907
+ /* @__PURE__ */ jsxs2("span", { className: "text-foreground/80", children: [
908
+ "$",
909
+ memoryCost.toFixed(2),
910
+ "/h"
911
+ ] })
912
+ ] }),
913
+ /* @__PURE__ */ jsxs2("div", { className: "flex justify-between text-xs font-mono tracking-widest text-muted-foreground", children: [
914
+ /* @__PURE__ */ jsx2("span", { children: "STORAGE" }),
915
+ /* @__PURE__ */ jsxs2("span", { className: "text-foreground/80", children: [
916
+ "$",
917
+ storageCost.toFixed(2),
918
+ "/h"
919
+ ] })
920
+ ] }),
921
+ floorApplies && /* @__PURE__ */ jsxs2("div", { className: "flex justify-between text-xs font-mono tracking-widest text-primary border-t border-border pt-2", children: [
922
+ /* @__PURE__ */ jsx2("span", { children: "MIN CHARGE" }),
923
+ /* @__PURE__ */ jsxs2("span", { children: [
924
+ "$",
925
+ (floor - lineSum).toFixed(2),
926
+ "/h"
927
+ ] })
872
928
  ] })
873
- ] })
874
- ] })
929
+ ] });
930
+ })()
875
931
  ] }),
876
932
  deployError && /* @__PURE__ */ jsxs2("div", { className: "rounded-xl border border-destructive/30 bg-destructive/10 p-3 flex items-center gap-2", children: [
877
933
  /* @__PURE__ */ jsx2(Info, { className: "h-4 w-4 text-destructive shrink-0" }),
@@ -1839,46 +1895,64 @@ function SecretsPage({ apiClient, className }) {
1839
1895
  /* @__PURE__ */ jsx5(DialogTitle, { children: "Create Secret" }),
1840
1896
  /* @__PURE__ */ jsx5(DialogDescription, { children: "Secrets are automatically exposed as environment variables across all your new sandboxes." })
1841
1897
  ] }),
1842
- /* @__PURE__ */ jsxs5("div", { className: "space-y-4", children: [
1843
- /* @__PURE__ */ jsxs5("div", { children: [
1844
- /* @__PURE__ */ jsx5("label", { className: "block text-xs font-bold uppercase tracking-widest text-muted-foreground mb-2", children: "Name" }),
1845
- /* @__PURE__ */ jsx5(
1846
- "input",
1847
- {
1848
- type: "text",
1849
- value: newName,
1850
- onChange: (e) => setNewName(e.target.value.toUpperCase().replace(/[^A-Z0-9_]/g, "_")),
1851
- placeholder: "MY_SECRET_KEY",
1852
- className: "w-full rounded-md border border-border bg-card px-3 py-2.5 text-sm font-mono text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring"
1853
- }
1854
- )
1855
- ] }),
1856
- /* @__PURE__ */ jsxs5("div", { children: [
1857
- /* @__PURE__ */ jsx5("label", { className: "block text-xs font-bold uppercase tracking-widest text-muted-foreground mb-2", children: "Value" }),
1858
- /* @__PURE__ */ jsxs5("div", { className: "relative", children: [
1859
- /* @__PURE__ */ jsx5(
1860
- "input",
1861
- {
1862
- type: showValue ? "text" : "password",
1863
- value: newValue,
1864
- onChange: (e) => setNewValue(e.target.value),
1865
- placeholder: "Enter secret value...",
1866
- className: "w-full rounded-md border border-border bg-card px-3 py-2.5 pr-10 text-sm font-mono text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring"
1867
- }
1868
- ),
1869
- /* @__PURE__ */ jsx5(
1870
- "button",
1871
- {
1872
- type: "button",
1873
- onClick: () => setShowValue(!showValue),
1874
- className: "absolute right-2 top-1/2 -translate-y-1/2 p-1 text-muted-foreground hover:text-foreground",
1875
- children: showValue ? /* @__PURE__ */ jsx5(EyeOff, { className: "h-4 w-4" }) : /* @__PURE__ */ jsx5(Eye, { className: "h-4 w-4" })
1876
- }
1877
- )
1878
- ] }),
1879
- /* @__PURE__ */ jsx5("p", { className: "mt-1.5 text-xs text-muted-foreground", children: "This value cannot be retrieved after creation." })
1880
- ] })
1881
- ] }),
1898
+ /* @__PURE__ */ jsxs5(
1899
+ "form",
1900
+ {
1901
+ onSubmit: (e) => {
1902
+ e.preventDefault();
1903
+ if (newName.trim() && newValue.trim() && !isCreating) handleCreate();
1904
+ },
1905
+ className: "space-y-4",
1906
+ children: [
1907
+ /* @__PURE__ */ jsxs5("div", { children: [
1908
+ /* @__PURE__ */ jsx5("label", { htmlFor: "secret-name", className: "block text-xs font-bold uppercase tracking-widest text-muted-foreground mb-2", children: "Name" }),
1909
+ /* @__PURE__ */ jsx5(
1910
+ "input",
1911
+ {
1912
+ id: "secret-name",
1913
+ name: "secret-name",
1914
+ type: "text",
1915
+ value: newName,
1916
+ onChange: (e) => setNewName(e.target.value.toUpperCase().replace(/[^A-Z0-9_]/g, "_")),
1917
+ placeholder: "MY_SECRET_KEY",
1918
+ autoComplete: "off",
1919
+ className: "w-full rounded-md border border-border bg-card px-3 py-2.5 text-sm font-mono text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring"
1920
+ }
1921
+ )
1922
+ ] }),
1923
+ /* @__PURE__ */ jsxs5("div", { children: [
1924
+ /* @__PURE__ */ jsx5("label", { htmlFor: "secret-value", className: "block text-xs font-bold uppercase tracking-widest text-muted-foreground mb-2", children: "Value" }),
1925
+ /* @__PURE__ */ jsxs5("div", { className: "relative", children: [
1926
+ /* @__PURE__ */ jsx5(
1927
+ "input",
1928
+ {
1929
+ id: "secret-value",
1930
+ name: "secret-value",
1931
+ type: showValue ? "text" : "password",
1932
+ value: newValue,
1933
+ onChange: (e) => setNewValue(e.target.value),
1934
+ placeholder: "Enter secret value...",
1935
+ autoComplete: "new-password",
1936
+ className: "w-full rounded-md border border-border bg-card px-3 py-2.5 pr-10 text-sm font-mono text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring"
1937
+ }
1938
+ ),
1939
+ /* @__PURE__ */ jsx5(
1940
+ "button",
1941
+ {
1942
+ type: "button",
1943
+ onClick: () => setShowValue(!showValue),
1944
+ className: "absolute right-2 top-1/2 -translate-y-1/2 p-1 text-muted-foreground hover:text-foreground",
1945
+ "aria-label": showValue ? "Hide value" : "Show value",
1946
+ children: showValue ? /* @__PURE__ */ jsx5(EyeOff, { className: "h-4 w-4" }) : /* @__PURE__ */ jsx5(Eye, { className: "h-4 w-4" })
1947
+ }
1948
+ )
1949
+ ] }),
1950
+ /* @__PURE__ */ jsx5("p", { className: "mt-1.5 text-xs text-muted-foreground", children: "This value cannot be retrieved after creation." })
1951
+ ] }),
1952
+ /* @__PURE__ */ jsx5("button", { type: "submit", className: "hidden", tabIndex: -1, "aria-hidden": "true", children: "Submit" })
1953
+ ]
1954
+ }
1955
+ ),
1882
1956
  createError && /* @__PURE__ */ jsx5("p", { className: "mt-3 text-sm text-destructive", children: createError }),
1883
1957
  /* @__PURE__ */ jsxs5(DialogFooter, { children: [
1884
1958
  /* @__PURE__ */ jsx5(
@@ -1958,10 +2032,11 @@ function SecretsPage({ apiClient, className }) {
1958
2032
  {
1959
2033
  type: "button",
1960
2034
  onClick: () => setIsCreateOpen(true),
2035
+ "aria-label": "Create your first secret",
1961
2036
  className: "mt-6 inline-flex items-center gap-2 rounded-md bg-[var(--btn-primary-bg)] px-4 py-2 text-sm font-semibold text-[var(--btn-primary-text)] hover:bg-[var(--btn-primary-hover)] transition-colors active:scale-[0.97]",
1962
2037
  children: [
1963
2038
  /* @__PURE__ */ jsx5(Plus3, { className: "h-4 w-4" }),
1964
- "Create Secret"
2039
+ "New Secret"
1965
2040
  ]
1966
2041
  }
1967
2042
  )
@@ -2858,10 +2933,11 @@ function StartupScriptsPage({ apiClient, className }) {
2858
2933
  {
2859
2934
  type: "button",
2860
2935
  onClick: openCreate,
2936
+ "aria-label": "Create your first startup script",
2861
2937
  className: "mt-4 inline-flex items-center gap-2 rounded-lg bg-[var(--btn-primary-bg)] px-4 py-2.5 text-sm font-bold text-[var(--btn-primary-text)] shadow-sm transition-colors hover:bg-[var(--btn-primary-hover)]",
2862
2938
  children: [
2863
2939
  /* @__PURE__ */ jsx7(Plus4, { className: "h-4 w-4" }),
2864
- "Create Script"
2940
+ "New Script"
2865
2941
  ]
2866
2942
  }
2867
2943
  )