@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.
- package/dist/components/ModalSearchAndChat.js +1 -1
- package/dist/components/sandbox/Sandbox.js +6 -0
- package/dist/components/sandbox/api.js +10 -6
- package/dist/components/sandbox/guardian/context/vm-context.js +10 -1
- package/dist/components/sandbox/guardian/demo/guardian-demo.js +4 -4
- package/dist/components/sandbox/guardian/demo/left-view.js +2 -2
- package/dist/components/sandbox/guardian/guardian-component.js +44 -26
- package/dist/components/sandbox/guardian/hooks/use-sandbox-url-loader.js +57 -18
- package/dist/components/sandbox/guardian/ide/browser.js +28 -4
- package/dist/components/sandbox/guardian/right-view/right-panel-view.js +9 -4
- package/dist/components/sandbox/guardian/right-view/right-top-down-view.js +111 -38
- package/dist/components/sandbox/guardian/right-view/right-view.js +3 -3
- package/dist/components/sandbox/guardian/ui/download-and-open-buttons.js +13 -43
- package/dist/components/sandbox/guardian/ui/markdown.js +4 -2
- package/dist/components/sandbox/guardian/utils.js +30 -1
- package/dist/index.d.ts +14 -2
- package/dist/index.es.js +18814 -18583
- package/dist/lib/api-client.js +128 -14
- package/package.json +1 -1
|
@@ -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
|
-
|
|
47
|
-
|
|
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 || "", //
|
|
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
|
-
|
|
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: {
|
|
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
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
//
|
|
23
|
-
const startSandboxConfig = apiKey
|
|
24
|
-
? {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
//
|
|
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
|
-
//
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
//
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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
|
|
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:
|
|
70
|
-
|
|
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
|
-
|
|
119
|
-
|
|
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 (
|
|
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"),
|