@sampleapp.ai/sdk 1.0.32 → 1.0.34

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.
@@ -148,7 +148,7 @@ export const ModalSearchAndChat = ({ settings, onClose, }) => {
148
148
  }, onMouseLeave: (e) => {
149
149
  e.currentTarget.style.opacity = "1";
150
150
  }, onClick: () => {
151
- console.log("Search for:", searchQuery);
151
+ // console.log("Search for:", searchQuery);
152
152
  // Handle search logic here
153
153
  } }, "Search")),
154
154
  ((_b = settings.aiChatSettings) === null || _b === void 0 ? void 0 : _b.exampleQuestions) &&
@@ -18,6 +18,12 @@ import { Skeleton } from "../ui/skeleton";
18
18
  * LAUNCHDARKLY_SDK_KEY: "sdk-xxx",
19
19
  * }}
20
20
  * />
21
+ *
22
+ * // env is optional if published_url is available
23
+ * <Sandbox
24
+ * apiKey={process.env.NEXT_PUBLIC_SAMPLEAPP_API_KEY!}
25
+ * sandboxId="launchdarkly-feature-flags"
26
+ * />
21
27
  * ```
22
28
  */
23
29
  export default function Sandbox({ apiKey, sandboxId, env, themeColor, }) {
@@ -43,17 +43,19 @@ export async function fetchSandboxConfig(apiKey, sandboxId) {
43
43
  // link.click();
44
44
  // document.body.removeChild(link);
45
45
  const containerId = ""; // Will be populated when sandbox is started via sdk.startSandbox()
46
- const completeCodeZipFile = containerId
47
- ? `${"hello there"}/fileshare?container_id=${containerId}`
48
- : `${"hello there"}/fileshare?container_id=placeholder`; // Placeholder until sandbox is started
46
+ // Set to empty string since this endpoint is not needed
47
+ const completeCodeZipFile = "";
49
48
  // Transform SandboxContent to SandboxConfig
49
+ // NOTE: published_url takes precedence over chat_uid:
50
+ // - If published_url exists, it will be used directly as browserUrl (no API call needed)
51
+ // - If published_url is not available, chat_uid will be used to call startSandbox API
50
52
  const config = {
51
53
  id: sandboxContent.uid,
52
54
  name: sandboxContent.uid, // Use UID as name if no name field exists
53
55
  description: sandboxContent.markdown || "", // Use markdown as description
54
56
  themeColor: "#3b82f6", // Default theme color
55
57
  hasPreview: true,
56
- chatUid: sandboxContent.chat_uid || "", // Include chatUid for startSandbox
58
+ chatUid: sandboxContent.chat_uid || "", // Fallback: only used if published_url is not available
57
59
  useCases: [
58
60
  {
59
61
  id: sandboxContent.uid,
@@ -64,12 +66,14 @@ export async function fetchSandboxConfig(apiKey, sandboxId) {
64
66
  frameworks: [
65
67
  {
66
68
  key: frameworkKey,
67
- browserUrl: "", // Will be set when sandbox is started
69
+ // published_url takes precedence: if available, use it directly (no startSandbox API call needed)
70
+ // If not available, chat_uid will be used to call startSandbox API as fallback
71
+ browserUrl: sandboxContent.published_url || "",
68
72
  sandboxUid: sandboxContent.uid,
69
73
  // Use generated_code if available, otherwise fallback to empty object
70
74
  codeZipFile: Object.keys(generatedCode).length > 0 ? generatedCode : "", // Backward compatible: empty string if no generated_code
71
75
  completeCodeZipFile,
72
- useVm: false,
76
+ useVm: sandboxContent.is_vm_enabled || false, // Use is_vm_enabled from sandbox content
73
77
  // CustomConsole is now sandboxContent.markdown
74
78
  CustomConsole: sandboxContent.markdown || "",
75
79
  GuideView: "Default Guide",
@@ -3,6 +3,7 @@ import React, { createContext, useContext, useState } from "react";
3
3
  const VmContext = createContext(undefined);
4
4
  export const VmProvider = ({ children }) => {
5
5
  const [vmUrlMap, setVmUrlMap] = useState({});
6
+ const [vmResolution, setVmResolution] = useState(undefined);
6
7
  const setVmUrl = (uid, url) => {
7
8
  setVmUrlMap((prev) => (Object.assign(Object.assign({}, prev), { [uid]: url })));
8
9
  };
@@ -17,7 +18,15 @@ export const VmProvider = ({ children }) => {
17
18
  const hasError = (uid) => {
18
19
  return vmUrlMap[uid] === "error";
19
20
  };
20
- return (React.createElement(VmContext.Provider, { value: { vmUrlMap, setVmUrl, getVmUrl, setVmError, hasError } }, children));
21
+ return (React.createElement(VmContext.Provider, { value: {
22
+ vmUrlMap,
23
+ setVmUrl,
24
+ getVmUrl,
25
+ setVmError,
26
+ hasError,
27
+ vmResolution,
28
+ setVmResolution,
29
+ } }, children));
21
30
  };
22
31
  export const useVmContext = () => {
23
32
  const context = useContext(VmContext);
@@ -4,7 +4,7 @@ import ConsoleWithApp from "../ui/console-with-app";
4
4
  import { useGuardianContext } from "../context/guardian-context";
5
5
  import ConsoleWithGuide from "./left-view";
6
6
  import RightView from "../right-view/right-view";
7
- export default function GuardianDemo({ CustomConsole, GuideView, browserUrl, playgroundUid, useVm, codeZipFile, completeCodeZipFile, variant, themeColor, hasPreview, isFrame = false, isGuardian = false, gitUrl, }) {
7
+ export default function GuardianDemo({ CustomConsole, GuideView, browserUrl, playgroundUid, useVm, codeZipFile, completeCodeZipFile, variant, themeColor, hasPreview, isFrame = false, isGuardian = false, gitUrl, isBrowserUrlReady = false, sandboxId, apiKey, sandboxError, }) {
8
8
  const { isBrowserMaximized } = useGuardianContext();
9
9
  const { setCurrentView } = useGuardianContext();
10
10
  const [reloadCounter, setReloadCounter] = useState(0);
@@ -24,12 +24,12 @@ export default function GuardianDemo({ CustomConsole, GuideView, browserUrl, pla
24
24
  }, [setCurrentView]);
25
25
  // If isFrame, just return RightView directly
26
26
  if (isFrame) {
27
- return (React.createElement(RightView, { reloadCounter: reloadCounter, overlayStage: overlayStage, browserUrl: browserUrl, useVm: useVm, codeZipFile: codeZipFile, hasPreview: hasPreview, variant: variant, themeColor: themeColor, completeCodeZipFile: completeCodeZipFile, isGuardian: isGuardian }));
27
+ return (React.createElement(RightView, { reloadCounter: reloadCounter, overlayStage: overlayStage, browserUrl: browserUrl, useVm: useVm, codeZipFile: codeZipFile, hasPreview: hasPreview, variant: variant, themeColor: themeColor, completeCodeZipFile: completeCodeZipFile, isGuardian: isGuardian, isBrowserMaximized: isBrowserMaximized, isBrowserUrlReady: isBrowserUrlReady, sandboxId: sandboxId, apiKey: apiKey, sandboxError: sandboxError }));
28
28
  }
29
29
  // If browser is maximized, render RightView at full size without the console
30
30
  if (isBrowserMaximized && isGuardian) {
31
31
  return (React.createElement("div", { className: "w-full h-full rounded-2xl border border-border overflow-hidden" },
32
- React.createElement(RightView, { reloadCounter: reloadCounter, overlayStage: overlayStage, browserUrl: browserUrl, useVm: useVm, codeZipFile: codeZipFile, hasPreview: hasPreview, variant: variant, themeColor: themeColor, completeCodeZipFile: completeCodeZipFile, isGuardian: isGuardian, isBrowserMaximized: true })));
32
+ React.createElement(RightView, { reloadCounter: reloadCounter, overlayStage: overlayStage, browserUrl: browserUrl, useVm: useVm, codeZipFile: codeZipFile, hasPreview: hasPreview, variant: variant, themeColor: themeColor, completeCodeZipFile: completeCodeZipFile, isGuardian: isGuardian, isBrowserMaximized: true, isBrowserUrlReady: isBrowserUrlReady, sandboxId: sandboxId, apiKey: apiKey, sandboxError: sandboxError })));
33
33
  }
34
- return (React.createElement(ConsoleWithApp, { containerClassName: "rounded-2xl border border-border", console: React.createElement(ConsoleWithGuide, { CustomConsole: CustomConsole, GuideView: GuideView, playgroundUid: playgroundUid, onReloadPreview: handleReloadPreview, onStageChange: handleStageChange, codeZipFile: codeZipFile, themeColor: themeColor, browserUrl: browserUrl, gitUrl: gitUrl }), app: React.createElement(RightView, { reloadCounter: reloadCounter, overlayStage: overlayStage, browserUrl: browserUrl, useVm: useVm, codeZipFile: codeZipFile, hasPreview: hasPreview, variant: variant, themeColor: themeColor, completeCodeZipFile: completeCodeZipFile, isGuardian: isGuardian }) }));
34
+ return (React.createElement(ConsoleWithApp, { containerClassName: "rounded-2xl border border-border", console: React.createElement(ConsoleWithGuide, { CustomConsole: CustomConsole, GuideView: GuideView, playgroundUid: playgroundUid, onReloadPreview: handleReloadPreview, onStageChange: handleStageChange, codeZipFile: codeZipFile, themeColor: themeColor, browserUrl: browserUrl, gitUrl: gitUrl, sandboxId: sandboxId, apiKey: apiKey }), app: React.createElement(RightView, { reloadCounter: reloadCounter, overlayStage: overlayStage, browserUrl: browserUrl, useVm: useVm, codeZipFile: codeZipFile, hasPreview: hasPreview, variant: variant, themeColor: themeColor, completeCodeZipFile: completeCodeZipFile, isGuardian: isGuardian, isBrowserMaximized: isBrowserMaximized, isBrowserUrlReady: isBrowserUrlReady, sandboxId: sandboxId, apiKey: apiKey, sandboxError: sandboxError }) }));
35
35
  }
@@ -11,7 +11,7 @@ function Logo({ href, width }) {
11
11
  React.createElement("div", { className: "invert dark:invert-0" },
12
12
  React.createElement("img", { src: sampleappLogo, alt: "SampleApp Logo", width: width || 160, height: 40 })))));
13
13
  }
14
- export default function ConsoleWithGuide({ CustomConsole, GuideView, playgroundUid, onReloadPreview, onStageChange, codeZipFile, themeColor, browserUrl, gitUrl, }) {
14
+ export default function ConsoleWithGuide({ CustomConsole, GuideView, playgroundUid, onReloadPreview, onStageChange, codeZipFile, themeColor, browserUrl, gitUrl, sandboxId, apiKey, }) {
15
15
  const [currentView, setCurrentView] = useState("console");
16
16
  const isConsoleView = useMemo(() => currentView === "console", [currentView]);
17
17
  // const isGuideView = useMemo(() => currentView === "guide", [currentView]);
@@ -20,7 +20,7 @@ export default function ConsoleWithGuide({ CustomConsole, GuideView, playgroundU
20
20
  if (typeof CustomConsole === "string") {
21
21
  return (React.createElement("div", { className: "h-full flex flex-col" },
22
22
  React.createElement("div", { className: "flex-1 px-8 py-6" },
23
- React.createElement(Markdown, { className: "text-gray-100", themeColor: themeColor, browserUrl: browserUrl, gitUrl: gitUrl }, CustomConsole))));
23
+ React.createElement(Markdown, { className: "text-gray-100", themeColor: themeColor, browserUrl: browserUrl, gitUrl: gitUrl, sandboxId: sandboxId, apiKey: apiKey }, CustomConsole))));
24
24
  }
25
25
  const CustomConsoleComponent = CustomConsole;
26
26
  return (React.createElement(CustomConsoleComponent, { onReloadPreview: onReloadPreview !== null && onReloadPreview !== void 0 ? onReloadPreview : (() => { }), onStageChange: onStageChange !== null && onStageChange !== void 0 ? onStageChange : (() => { }), themeColor: themeColor }));
@@ -6,28 +6,26 @@ import React, { useEffect, useRef } from "react";
6
6
  import { useGuardianContext } from "./context/guardian-context";
7
7
  import { useSandboxUrlLoader, } from "./hooks/use-sandbox-url-loader";
8
8
  import { useFrameMessages } from "./hooks/use-frame-messages";
9
+ import { useVmContext } from "./context/vm-context";
9
10
  import { cn } from "../../../lib/utils";
10
11
  export default function GuardianComponent({ demoOptions, frameworkOptions, firstFrameworkByUseCase, currentFramework, currentUseCase, CustomConsole, GuideView, playgroundLogo, playgroundUid, browserUrl, useVm, sandboxUid, codeZipFile, consoleUrlConfigs, completeCodeZipFile, variant, themeColor, hasPreview = true, isFrame = false, apiKey, env, chatUid, hideHeader = true, // Hardcoded to true by default, not exposed in Sandbox.tsx
11
12
  gitUrl, }) {
12
13
  const { previewUrl, setPreviewUrl } = useGuardianContext();
13
- // Debug: Log the props received for sandbox configuration
14
- console.log("[GuardianComponent] API config props:", {
15
- apiKey: apiKey ? `${apiKey.substring(0, 8)}...` : undefined,
16
- env,
17
- chatUid,
18
- hasApiKey: !!apiKey,
19
- hasEnv: !!env,
20
- hasChatUid: !!chatUid,
21
- });
22
- // Build startSandboxConfig only if all required fields are present and non-empty
23
- const startSandboxConfig = apiKey && env && chatUid
24
- ? {
25
- apiKey,
26
- env,
27
- chatUid,
28
- }
29
- : undefined;
30
- console.log("[GuardianComponent] startSandboxConfig:", startSandboxConfig ? "configured" : "undefined (missing required fields)");
14
+ const { vmResolution } = useVmContext();
15
+ const [sandboxError, setSandboxError] = React.useState(null);
16
+ // Build startSandboxConfig:
17
+ // - Always include apiKey when available (needed for VM calls even when browserUrl exists)
18
+ // - Include chatUid when browserUrl is missing (needed for startSandbox API call)
19
+ // - Include env if provided (will be validated later if required)
20
+ // PRECEDENCE: published_url takes precedence over chat_uid
21
+ // - If browserUrl (published_url) exists, it will be used directly (no startSandbox API call needed)
22
+ // - If browserUrl is not available, we need startSandboxConfig with chatUid to call startSandbox API
23
+ // This ensures backward compatibility while prioritizing published_url when available
24
+ const startSandboxConfig = apiKey
25
+ ? Object.assign({ apiKey }, (!browserUrl && chatUid ? { env, chatUid } : {})) : undefined;
26
+ // Determine if browserUrl is ready to use immediately (from published_url)
27
+ // If browserUrl exists, it's ready (startSandboxConfig may still exist with just apiKey for VM calls)
28
+ const isBrowserUrlReady = !!browserUrl;
31
29
  const { getSandboxUrl, loadSandboxUrl, preloadSandboxUrls } = useSandboxUrlLoader(startSandboxConfig);
32
30
  // Initialize postMessage listener for iframe communication
33
31
  // This sends IFRAME_READY and listens for UPDATE_VIEW messages
@@ -38,20 +36,22 @@ gitUrl, }) {
38
36
  const loadedSandboxRef = useRef("");
39
37
  // Preload console URLs when component mounts
40
38
  useEffect(() => {
41
- if (consoleUrlConfigs && consoleUrlConfigs.length > 0) {
39
+ if (consoleUrlConfigs && consoleUrlConfigs.length > 0 && apiKey) {
42
40
  // Create a key from the configs to detect changes
43
41
  const configKey = JSON.stringify(consoleUrlConfigs.map((c) => c.sandboxUid));
44
42
  // Only preload if we haven't already preloaded these configs
45
43
  if (preloadedConfigsRef.current !== configKey) {
46
44
  preloadedConfigsRef.current = configKey;
47
- preloadSandboxUrls(consoleUrlConfigs).catch((error) => {
45
+ // Ensure apiKey is passed to all configs
46
+ const configsWithApiKey = consoleUrlConfigs.map((config) => (Object.assign(Object.assign({}, config), { apiKey: config.apiKey || apiKey })));
47
+ preloadSandboxUrls(configsWithApiKey).catch((error) => {
48
48
  console.error("Failed to preload console URLs:", error);
49
49
  });
50
50
  }
51
51
  }
52
- // Only re-run if consoleUrlConfigs changes (not when preloadSandboxUrls changes)
52
+ // Only re-run if consoleUrlConfigs or apiKey changes
53
53
  // eslint-disable-next-line react-hooks/exhaustive-deps
54
- }, [consoleUrlConfigs]);
54
+ }, [consoleUrlConfigs, apiKey]);
55
55
  useEffect(() => {
56
56
  // Create a key for this sandbox load
57
57
  const sandboxKey = `${sandboxUid}-${browserUrl}-${useVm}`;
@@ -67,6 +67,18 @@ gitUrl, }) {
67
67
  loadedSandboxRef.current = sandboxKey;
68
68
  return;
69
69
  }
70
+ // If useVm is true, wait for resolution from context before loading
71
+ // The Browser component will measure and set resolution in context after it mounts
72
+ if (useVm && !vmResolution) {
73
+ // Don't proceed until resolution is available
74
+ // The resolution will be set by Browser component via context
75
+ return;
76
+ }
77
+ // If useVm is true, apiKey is required
78
+ if (useVm && !apiKey) {
79
+ console.error("apiKey is required when useVm is true");
80
+ return;
81
+ }
70
82
  // Mark as loading to prevent duplicate requests
71
83
  loadedSandboxRef.current = sandboxKey;
72
84
  // Load the sandbox URL (will be cached by sandboxUid + browserUrl)
@@ -77,23 +89,29 @@ gitUrl, }) {
77
89
  sandboxUid,
78
90
  browserUrl,
79
91
  useVm,
92
+ resolution: vmResolution,
93
+ apiKey,
80
94
  })
81
95
  .then((url) => {
82
96
  setPreviewUrl(url);
97
+ setSandboxError(null); // Clear error on success
83
98
  })
84
99
  .catch((error) => {
85
100
  console.error("Failed to load sandbox URL:", error);
101
+ // Store error message for display
102
+ const errorMessage = error instanceof Error ? error.message : String(error);
103
+ setSandboxError(errorMessage);
86
104
  // Keep preview URL empty on error
87
105
  });
88
- // Only re-run if the sandbox parameters change (not when functions change)
106
+ // Re-run when sandbox parameters change OR when resolution becomes available
89
107
  // eslint-disable-next-line react-hooks/exhaustive-deps
90
- }, [sandboxUid, browserUrl, useVm]);
108
+ }, [sandboxUid, browserUrl, useVm, vmResolution, apiKey]);
91
109
  // If isFrame, just render GuardianDemo without the header/layout wrapper
92
110
  if (isFrame) {
93
111
  return (React.createElement("div", { className: "h-[100vh]" },
94
- React.createElement(GuardianDemo, { CustomConsole: CustomConsole, GuideView: GuideView, browserUrl: previewUrl, playgroundUid: playgroundUid, useVm: useVm, codeZipFile: codeZipFile, completeCodeZipFile: completeCodeZipFile, variant: variant, themeColor: themeColor, hasPreview: hasPreview, isFrame: isFrame, isGuardian: true, gitUrl: gitUrl })));
112
+ React.createElement(GuardianDemo, { CustomConsole: CustomConsole, GuideView: GuideView, browserUrl: previewUrl, playgroundUid: playgroundUid, useVm: useVm, codeZipFile: codeZipFile, completeCodeZipFile: completeCodeZipFile, variant: variant, themeColor: themeColor, hasPreview: hasPreview, isFrame: isFrame, isGuardian: true, gitUrl: gitUrl, isBrowserUrlReady: isBrowserUrlReady, sandboxId: sandboxUid, apiKey: apiKey, sandboxError: sandboxError })));
95
113
  }
96
114
  return (React.createElement(AppLayoutNoSidebar, { header: !hideHeader ? (React.createElement(Header, { demoOptions: demoOptions, frameworkOptions: frameworkOptions, currentFramework: currentFramework, currentUseCase: currentUseCase, playgroundLogo: playgroundLogo, firstFrameworkByUseCase: firstFrameworkByUseCase })) : undefined, hasBodyPadding: false },
97
115
  React.createElement("div", { className: cn("flex-1 min-h-0 flex flex-col px-4 pb-4", hideHeader && "pt-4") },
98
- React.createElement(GuardianDemo, { CustomConsole: CustomConsole, GuideView: GuideView, browserUrl: previewUrl, playgroundUid: playgroundUid, useVm: useVm, codeZipFile: codeZipFile, completeCodeZipFile: completeCodeZipFile, variant: variant, themeColor: themeColor, hasPreview: hasPreview, isFrame: isFrame, isGuardian: true, gitUrl: gitUrl }))));
116
+ React.createElement(GuardianDemo, { CustomConsole: CustomConsole, GuideView: GuideView, browserUrl: previewUrl, playgroundUid: playgroundUid, useVm: useVm, codeZipFile: codeZipFile, completeCodeZipFile: completeCodeZipFile, variant: variant, themeColor: themeColor, hasPreview: hasPreview, isFrame: isFrame, isGuardian: true, gitUrl: gitUrl, isBrowserUrlReady: isBrowserUrlReady, sandboxId: sandboxUid, apiKey: apiKey, sandboxError: sandboxError }))));
99
117
  }
@@ -36,20 +36,41 @@ export function useSandboxUrlLoader(startSandboxConfig) {
36
36
  }
37
37
  if (useVm) {
38
38
  try {
39
- // Fetch VM URL from API
40
- // Note: This assumes the API endpoint is available at /api/auto-sandbox
41
- // You may need to configure this based on your deployment
42
- const apiBaseUrl = typeof window !== "undefined" ? window.location.origin : "";
43
- const response = await fetch(`${apiBaseUrl}/api/auto-sandbox?url=${encodeURIComponent(browserUrl)}&mode=json`);
44
- // Check if response is successful
45
- if (!response.ok) {
46
- const errorText = await response.text();
47
- console.error(`Failed to fetch sandbox URL for ${sandboxUid}:`, errorText);
48
- // Mark as error to prevent infinite retries
49
- setVmError(cacheKey);
50
- throw new Error(`Failed to create sandbox: ${response.status} ${response.statusText}`);
39
+ // API key is required - use from config or fallback to startSandboxConfig
40
+ const apiKey = config.apiKey || (startSandboxConfig === null || startSandboxConfig === void 0 ? void 0 : startSandboxConfig.apiKey);
41
+ if (!apiKey) {
42
+ throw new Error("API key is required for VM sandbox creation");
43
+ }
44
+ // Fetch VM URL from API using the SDK API client with apiKey in config
45
+ const client = createApiClient({ apiKey });
46
+ if (config === null || config === void 0 ? void 0 : config.resolution) {
47
+ config.resolution[0] += 20;
48
+ config.resolution[1] += 60;
49
+ }
50
+ // Edge case: If browserUrl is empty but we have chatUid, call startSandbox first
51
+ let targetUrl = browserUrl;
52
+ if (!targetUrl || targetUrl.trim() === "") {
53
+ if (startSandboxConfig === null || startSandboxConfig === void 0 ? void 0 : startSandboxConfig.chatUid) {
54
+ // When browserUrl (published_url) is empty, env is required to start sandbox
55
+ if (!startSandboxConfig.env) {
56
+ throw new Error("env is required when published_url is not available (needed to start sandbox)");
57
+ }
58
+ // First, start the sandbox to get the container_url
59
+ const sandboxResponse = await client.sdk.startSandbox({
60
+ env: startSandboxConfig.env,
61
+ chatUid: startSandboxConfig.chatUid,
62
+ });
63
+ targetUrl = sandboxResponse.container_url;
64
+ }
65
+ else {
66
+ throw new Error("browserUrl is required for VM, or chatUid must be provided to start sandbox first");
67
+ }
51
68
  }
52
- const data = await response.json();
69
+ const data = await client.sdk.startVm({
70
+ url: targetUrl,
71
+ mode: "json",
72
+ resolution: config.resolution,
73
+ });
53
74
  if (!data.vncUrl) {
54
75
  console.error(`No vncUrl in response for ${sandboxUid}:`, data);
55
76
  setVmError(cacheKey);
@@ -62,19 +83,36 @@ export function useSandboxUrlLoader(startSandboxConfig) {
62
83
  catch (error) {
63
84
  // Mark as error to prevent infinite retries
64
85
  setVmError(cacheKey);
86
+ console.error(`Failed to fetch sandbox URL for ${sandboxUid}:`, error);
65
87
  throw error;
66
88
  }
67
89
  }
68
90
  else {
69
- // Non-VM mode: call startSandbox API to get container URL
70
- if (startSandboxConfig) {
91
+ // Non-VM mode:
92
+ // PRECEDENCE: published_url takes precedence over chat_uid
93
+ // 1. If browserUrl (published_url) is provided, use it directly - no API call needed
94
+ // 2. Otherwise, fall back to calling startSandbox API with chat_uid
95
+ if (browserUrl && browserUrl.trim() !== "") {
96
+ // published_url is available - use it directly (chat_uid not needed)
97
+ setVmUrl(cacheKey, browserUrl);
98
+ return browserUrl;
99
+ }
100
+ else if (startSandboxConfig) {
101
+ // No published_url available - fall back to calling startSandbox API with chat_uid
71
102
  try {
103
+ if (!startSandboxConfig.chatUid) {
104
+ throw new Error("chatUid is required when published_url is not available");
105
+ }
106
+ // When published_url is not available, env is required to start sandbox
107
+ if (!startSandboxConfig.env) {
108
+ throw new Error("env is required when published_url is not available (needed to start sandbox)");
109
+ }
110
+ // Create client with apiKey in config (will be sent as Bearer token)
72
111
  const client = createApiClient({
73
112
  apiKey: startSandboxConfig.apiKey,
74
113
  });
75
114
  const response = await client.sdk.startSandbox({
76
115
  env: startSandboxConfig.env,
77
- apiKey: startSandboxConfig.apiKey,
78
116
  chatUid: startSandboxConfig.chatUid,
79
117
  });
80
118
  // Use the container_url from the response as the browserUrl
@@ -115,8 +153,9 @@ export function useSandboxUrlLoader(startSandboxConfig) {
115
153
  return getVmUrl(cacheKey) === undefined && !hasError(cacheKey);
116
154
  });
117
155
  // Load all URLs in parallel, but catch errors individually
118
- await Promise.allSettled(configsToLoad.map((config) => loadSandboxUrl(config)));
119
- }, [getVmUrl, loadSandboxUrl, hasError]);
156
+ // Use API key from startSandboxConfig as fallback if not provided in config
157
+ await Promise.allSettled(configsToLoad.map((config) => loadSandboxUrl(Object.assign(Object.assign({}, config), { apiKey: config.apiKey || (startSandboxConfig === null || startSandboxConfig === void 0 ? void 0 : startSandboxConfig.apiKey) }))));
158
+ }, [getVmUrl, loadSandboxUrl, hasError, startSandboxConfig]);
120
159
  return {
121
160
  loadSandboxUrl,
122
161
  getSandboxUrl,
@@ -3,9 +3,10 @@ import { useGuardianContext } from "../context/guardian-context";
3
3
  import { ChevronLeft, ChevronRight, RefreshCw, Lock, AlertTriangle, Maximize2, Minimize2, ExternalLink, MoreVertical, } from "lucide-react";
4
4
  import { cn } from "../../../../lib/utils";
5
5
  import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from "../ui/dropdown-menu";
6
- export default function Browser({ previewUrl, setPreviewUrl, containerEndpoint, outerContainerClassName, children, reloadSignal, useVm, isGuardian = false, }) {
6
+ export default function Browser({ previewUrl, setPreviewUrl, containerEndpoint, outerContainerClassName, children, reloadSignal, useVm, isGuardian = false, sandboxError, }) {
7
7
  const { isBrowserMaximized, setIsBrowserMaximized } = useGuardianContext();
8
8
  const iframeRef = useRef(null);
9
+ const containerRef = useRef(null);
9
10
  const [isLoading, setIsLoading] = useState(false);
10
11
  const [error, setError] = useState(null);
11
12
  const historyRef = useRef([]);
@@ -521,18 +522,41 @@ export default function Browser({ previewUrl, setPreviewUrl, containerEndpoint,
521
522
  React.createElement(DropdownMenuContent, { align: "end", className: "bg-white dark:bg-zinc-900 border border-zinc-200 dark:border-zinc-800" },
522
523
  React.createElement(DropdownMenuItem, { onClick: () => {
523
524
  // Restart functionality - can be implemented if needed
524
- console.log("Restart app");
525
525
  }, className: "cursor-pointer text-zinc-700 dark:text-zinc-300 hover:bg-zinc-100 dark:hover:bg-zinc-800" }, "Restart App"))))))),
526
526
  error && (React.createElement("div", { className: "p-4 bg-red-50 border-b border-red-100 text-red-700 text-sm dark:bg-red-950 dark:border-red-800 dark:text-red-200" },
527
527
  React.createElement("div", { className: "flex items-center gap-2" },
528
528
  React.createElement(AlertTriangle, { size: 16 }),
529
529
  React.createElement("span", null, error)))),
530
- React.createElement("div", { className: "flex-1 min-h-0 relative" },
530
+ React.createElement("div", { ref: containerRef, className: "flex-1 min-h-0 relative" }, sandboxError && sandboxError.includes("env is required") ? (React.createElement("div", { className: "h-full w-full flex items-center justify-center bg-zinc-50 dark:bg-zinc-900" },
531
+ React.createElement("div", { className: "max-w-md mx-auto p-8 text-center" },
532
+ React.createElement("div", { className: "mb-4 flex justify-center" },
533
+ React.createElement(AlertTriangle, { size: 48, className: "text-amber-500 dark:text-amber-400" })),
534
+ React.createElement("h2", { className: "text-xl font-semibold text-zinc-900 dark:text-zinc-100 mb-2" }, "Environment Variables Required"),
535
+ React.createElement("p", { className: "text-sm text-zinc-600 dark:text-zinc-400 mb-4" },
536
+ "This sandbox doesn't have a published URL and needs to be started dynamically. Please provide the",
537
+ " ",
538
+ React.createElement("code", { className: "px-1.5 py-0.5 bg-zinc-200 dark:bg-zinc-800 rounded text-xs font-mono" }, "env"),
539
+ " ",
540
+ "prop to the",
541
+ " ",
542
+ React.createElement("code", { className: "px-1.5 py-0.5 bg-zinc-200 dark:bg-zinc-800 rounded text-xs font-mono" }, "<Sandbox>"),
543
+ " ",
544
+ "component."),
545
+ React.createElement("div", { className: "mt-6 p-4 bg-zinc-100 dark:bg-zinc-800 rounded-lg text-left" },
546
+ React.createElement("p", { className: "text-xs font-mono text-zinc-700 dark:text-zinc-300 mb-2" }, "Example:"),
547
+ React.createElement("pre", { className: "text-xs text-zinc-600 dark:text-zinc-400 overflow-x-auto" }, `<Sandbox
548
+ apiKey="your-api-key"
549
+ sandboxId="sandbox-id"
550
+ env={{
551
+ YOUR_API_KEY: "value",
552
+ YOUR_SECRET: "secret"
553
+ }}
554
+ />`))))) : (React.createElement(React.Fragment, null,
531
555
  renderIframe(),
532
556
  isLoading && (React.createElement("div", { className: "absolute inset-0 bg-zinc-100 dark:bg-zinc-800 bg-opacity-50 flex items-center justify-center z-10" },
533
557
  React.createElement("div", { className: "text-sm text-zinc-600 bg-white dark:bg-black p-3 rounded-lg shadow-md" },
534
558
  "Loading ",
535
559
  urlInput,
536
560
  "..."))),
537
- children)));
561
+ children)))));
538
562
  }
@@ -5,12 +5,17 @@ import Browser from "../ide/browser";
5
5
  import CodeView from "./code-view";
6
6
  import { useGuardianContext } from "../context/guardian-context";
7
7
  import { Component as AiLoader } from "../ui/ai-loader";
8
- export default function RightPanelView({ reloadCounter, overlayStage, browserUrl, useVm, codeZipFile, themeColor = "#3b82f6", }) {
8
+ export default function RightPanelView({ reloadCounter, overlayStage, browserUrl, useVm, codeZipFile, themeColor = "#3b82f6", sandboxError, }) {
9
9
  const { setCurrentView, currentView } = useGuardianContext();
10
10
  const [showLoader, setShowLoader] = useState(!browserUrl);
11
11
  // Delay hiding the loader for 5 seconds after browserUrl becomes available
12
+ // If sandboxError exists, hide loader immediately to show error UI
12
13
  useEffect(() => {
13
- if (browserUrl) {
14
+ if (sandboxError) {
15
+ // Show error UI immediately instead of loader
16
+ setShowLoader(false);
17
+ }
18
+ else if (browserUrl) {
14
19
  const timer = setTimeout(() => {
15
20
  setShowLoader(false);
16
21
  }, 7000);
@@ -19,7 +24,7 @@ export default function RightPanelView({ reloadCounter, overlayStage, browserUrl
19
24
  else {
20
25
  setShowLoader(true);
21
26
  }
22
- }, [browserUrl]);
27
+ }, [browserUrl, sandboxError]);
23
28
  return (React.createElement("div", null,
24
29
  React.createElement("div", { className: "flex justify-center border-b" },
25
30
  React.createElement(SlidingEditPreviewToggle, { playgroundUid: "guardian", currentView: currentView, setCurrentView: (view) => setCurrentView(view), variant: "pill", buttonClassName: "text-sm px-4 py-1 h-auto", tabs: [
@@ -31,7 +36,7 @@ export default function RightPanelView({ reloadCounter, overlayStage, browserUrl
31
36
  } }, showLoader ? (React.createElement("div", { className: "h-[calc(100vh-12.2rem)] w-full relative overflow-hidden rounded-b-none" },
32
37
  React.createElement(AiLoader, { text: "Booting up...", fullScreen: false, themeColor: themeColor }))) : (React.createElement(Browser, { previewUrl: browserUrl, setPreviewUrl: () => { }, containerEndpoint: browserUrl, outerContainerClassName: useVm
33
38
  ? "h-[calc(100vh-9.6rem)] w-full border-none rounded-b-none"
34
- : "h-[calc(100vh-12.2rem)] w-full border-none rounded-b-none", reloadSignal: reloadCounter, useVm: useVm }, overlayStage !== "hidden" && (React.createElement("div", { className: "absolute inset-0 z-20 flex items-center justify-center" },
39
+ : "h-[calc(100vh-12.2rem)] w-full border-none rounded-b-none", reloadSignal: reloadCounter, useVm: useVm, sandboxError: sandboxError }, overlayStage !== "hidden" && (React.createElement("div", { className: "absolute inset-0 z-20 flex items-center justify-center" },
35
40
  overlayStage === "error" && (React.createElement("div", { className: "w-full h-full bg-red-950 text-red-200 flex items-center justify-center" },
36
41
  React.createElement("div", { className: "text-center" },
37
42
  React.createElement("div", { className: "text-2xl font-semibold" }, "Service Unavailable"),