@sampleapp.ai/sdk 1.0.32 → 1.0.33
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
|
@@ -6,44 +6,116 @@ import { Panel, PanelGroup, PanelResizeHandle } from "react-resizable-panels";
|
|
|
6
6
|
import SimplifiedEditor from "./simplified-editor";
|
|
7
7
|
import PillFileSelector from "./pill-file-selector";
|
|
8
8
|
import { useGuardianContext } from "../context/guardian-context";
|
|
9
|
-
import {
|
|
9
|
+
import { useVmContext } from "../context/vm-context";
|
|
10
|
+
import { useEffect, useMemo, useRef, useState } from "react";
|
|
10
11
|
import { zipPathToFileTree } from "../zip-to-filetree";
|
|
11
12
|
import { codeZipFileToCodebase } from "../zip-to-codebase";
|
|
12
13
|
import { DownloadIcon, Globe } from "lucide-react";
|
|
13
14
|
import { useFrameParams } from "../hooks/use-frame-params";
|
|
14
15
|
import { cn } from "../../../../lib/utils";
|
|
15
|
-
import {
|
|
16
|
-
export default function RightView({ reloadCounter, overlayStage, browserUrl, useVm, codeZipFile, themeColor, completeCodeZipFile, hasPreview, isPreviewMinimized = false, isGuardian = false, isBrowserMaximized = false, }) {
|
|
16
|
+
import { handleSandboxDownload } from "../utils";
|
|
17
|
+
export default function RightView({ reloadCounter, overlayStage, browserUrl, useVm, codeZipFile, themeColor, completeCodeZipFile, hasPreview, isPreviewMinimized = false, isGuardian = false, isBrowserMaximized = false, isBrowserUrlReady = false, sandboxId, apiKey, sandboxError, }) {
|
|
17
18
|
const { setFileTree, setGeneratedCode } = useGuardianContext();
|
|
19
|
+
const { setVmResolution } = useVmContext();
|
|
18
20
|
const frameParams = useFrameParams();
|
|
19
21
|
const [activeTab, setActiveTab] = useState("code");
|
|
20
22
|
const [networkRequests, setNetworkRequests] = useState([]);
|
|
21
23
|
const [showLoader, setShowLoader] = useState(!browserUrl);
|
|
22
|
-
//
|
|
24
|
+
// Ref to measure the preview container for VNC resolution
|
|
25
|
+
const previewContainerRef = useRef(null);
|
|
26
|
+
// Hide loader based on whether URL is ready or if there's an error
|
|
27
|
+
// - If sandboxError exists, hide loader immediately to show error UI
|
|
28
|
+
// - If isBrowserUrlReady (published_url), hide immediately when available
|
|
29
|
+
// - Otherwise, wait 5 seconds for sandbox to boot
|
|
23
30
|
useEffect(() => {
|
|
24
|
-
if (
|
|
31
|
+
if (sandboxError) {
|
|
32
|
+
// Show error UI immediately instead of loader
|
|
33
|
+
setShowLoader(false);
|
|
34
|
+
}
|
|
35
|
+
else if (browserUrl) {
|
|
36
|
+
const delay = isBrowserUrlReady ? 0 : 5000;
|
|
25
37
|
const timer = setTimeout(() => {
|
|
26
38
|
setShowLoader(false);
|
|
27
|
-
},
|
|
39
|
+
}, delay);
|
|
28
40
|
return () => clearTimeout(timer);
|
|
29
41
|
}
|
|
30
42
|
else {
|
|
31
43
|
setShowLoader(true);
|
|
32
44
|
}
|
|
33
|
-
}, [browserUrl]);
|
|
45
|
+
}, [browserUrl, isBrowserUrlReady, sandboxError]);
|
|
34
46
|
// Use frame param override for theme if in frame mode
|
|
35
47
|
const effectiveThemeColor = frameParams.isFrame && frameParams.theme ? frameParams.theme : themeColor;
|
|
36
|
-
//
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
|
|
48
|
+
// Measure the preview container for VNC resolution when useVm is true
|
|
49
|
+
// IMPORTANT: Re-run when isBrowserMaximized changes because the ref points to different elements
|
|
50
|
+
useEffect(() => {
|
|
51
|
+
if (!useVm)
|
|
52
|
+
return;
|
|
53
|
+
let mounted = true;
|
|
54
|
+
let resizeObserver = null;
|
|
55
|
+
let lastReportedResolution = null;
|
|
56
|
+
const updateResolution = () => {
|
|
57
|
+
if (!mounted)
|
|
58
|
+
return;
|
|
59
|
+
const container = previewContainerRef.current;
|
|
60
|
+
if (!container) {
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
// Get the exact inner dimensions - clientWidth/Height excludes borders and scrollbars
|
|
64
|
+
let widthPx = container.clientWidth;
|
|
65
|
+
let heightPx = container.clientHeight;
|
|
66
|
+
// Fallback to offsetWidth/Height if client dimensions are 0
|
|
67
|
+
if (widthPx === 0 || heightPx === 0) {
|
|
68
|
+
widthPx = container.offsetWidth;
|
|
69
|
+
heightPx = container.offsetHeight;
|
|
70
|
+
}
|
|
71
|
+
// Final fallback to getBoundingClientRect
|
|
72
|
+
if (widthPx === 0 || heightPx === 0) {
|
|
73
|
+
const rect = container.getBoundingClientRect();
|
|
74
|
+
widthPx = Math.floor(rect.width);
|
|
75
|
+
heightPx = Math.floor(rect.height);
|
|
76
|
+
}
|
|
77
|
+
widthPx = Math.floor(widthPx);
|
|
78
|
+
heightPx = Math.floor(heightPx);
|
|
79
|
+
if (widthPx > 0 && heightPx > 0) {
|
|
80
|
+
if (!lastReportedResolution ||
|
|
81
|
+
lastReportedResolution[0] !== widthPx ||
|
|
82
|
+
lastReportedResolution[1] !== heightPx) {
|
|
83
|
+
lastReportedResolution = [widthPx, heightPx];
|
|
84
|
+
setVmResolution([widthPx, heightPx]);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
const setupObserver = () => {
|
|
89
|
+
if (previewContainerRef.current) {
|
|
90
|
+
resizeObserver = new ResizeObserver(() => {
|
|
91
|
+
updateResolution();
|
|
92
|
+
});
|
|
93
|
+
resizeObserver.observe(previewContainerRef.current);
|
|
94
|
+
// Initial measurement
|
|
95
|
+
setTimeout(updateResolution, 10);
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
setTimeout(setupObserver, 50);
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
setupObserver();
|
|
102
|
+
// Additional delayed measurements as fallback
|
|
103
|
+
const t1 = setTimeout(updateResolution, 100);
|
|
104
|
+
const t2 = setTimeout(updateResolution, 300);
|
|
105
|
+
const t3 = setTimeout(updateResolution, 500);
|
|
106
|
+
return () => {
|
|
107
|
+
mounted = false;
|
|
108
|
+
clearTimeout(t1);
|
|
109
|
+
clearTimeout(t2);
|
|
110
|
+
clearTimeout(t3);
|
|
111
|
+
if (resizeObserver) {
|
|
112
|
+
resizeObserver.disconnect();
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
}, [useVm, setVmResolution, isBrowserMaximized]);
|
|
116
|
+
const handleDownloadAsZip = async () => {
|
|
117
|
+
await handleSandboxDownload(sandboxId, apiKey);
|
|
118
|
+
};
|
|
47
119
|
// Extract only the nested children (actual API calls to external services)
|
|
48
120
|
const childrenRequests = useMemo(() => {
|
|
49
121
|
const children = [];
|
|
@@ -56,15 +128,18 @@ export default function RightView({ reloadCounter, overlayStage, browserUrl, use
|
|
|
56
128
|
}, [networkRequests]);
|
|
57
129
|
useEffect(() => {
|
|
58
130
|
let isMounted = true;
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
131
|
+
// Only load zip file if completeCodeZipFile is provided
|
|
132
|
+
if (completeCodeZipFile) {
|
|
133
|
+
zipPathToFileTree(completeCodeZipFile)
|
|
134
|
+
.then((fileTree) => {
|
|
135
|
+
if (isMounted) {
|
|
136
|
+
setFileTree(fileTree);
|
|
137
|
+
}
|
|
138
|
+
})
|
|
139
|
+
.catch((error) => {
|
|
140
|
+
console.error("Failed to load zip file:", error);
|
|
141
|
+
});
|
|
142
|
+
}
|
|
68
143
|
codeZipFileToCodebase(codeZipFile)
|
|
69
144
|
.then((codebase) => {
|
|
70
145
|
if (isMounted) {
|
|
@@ -100,8 +175,8 @@ export default function RightView({ reloadCounter, overlayStage, browserUrl, use
|
|
|
100
175
|
}, []);
|
|
101
176
|
// When browser is maximized, render only the browser at full size
|
|
102
177
|
if (isBrowserMaximized) {
|
|
103
|
-
return (React.createElement("div", { className: "h-full w-full" }, showLoader ? (React.createElement("div", { className: "h-full w-full relative overflow-hidden" },
|
|
104
|
-
React.createElement(AiLoader, { text: "Booting up...", fullScreen: false, themeColor: effectiveThemeColor }))) : (React.createElement(Browser, { previewUrl: browserUrl, setPreviewUrl: () => { }, containerEndpoint: browserUrl, outerContainerClassName: "h-full w-full border-none", reloadSignal: reloadCounter, useVm: useVm, isGuardian: isGuardian }, overlayStage !== "hidden" && (React.createElement("div", { className: "absolute inset-0 z-20 flex items-center justify-center" },
|
|
178
|
+
return (React.createElement("div", { ref: previewContainerRef, className: "h-full w-full relative" }, showLoader ? (React.createElement("div", { className: "h-full w-full relative overflow-hidden" },
|
|
179
|
+
React.createElement(AiLoader, { text: "Booting up...", fullScreen: false, themeColor: effectiveThemeColor }))) : (React.createElement(Browser, { previewUrl: browserUrl || "", setPreviewUrl: () => { }, containerEndpoint: browserUrl, outerContainerClassName: "h-full w-full border-none", reloadSignal: reloadCounter, useVm: useVm, isGuardian: isGuardian, sandboxError: sandboxError }, overlayStage !== "hidden" && (React.createElement("div", { className: "absolute inset-0 z-20 flex items-center justify-center" },
|
|
105
180
|
overlayStage === "error" && (React.createElement("div", { className: "w-full h-full bg-red-950 text-red-200 flex items-center justify-center" },
|
|
106
181
|
React.createElement("div", { className: "text-center" },
|
|
107
182
|
React.createElement("div", { className: "text-2xl font-semibold" }, "Service Unavailable"),
|
|
@@ -118,9 +193,9 @@ export default function RightView({ reloadCounter, overlayStage, browserUrl, use
|
|
|
118
193
|
return (React.createElement("div", { className: "h-full w-full flex flex-col" },
|
|
119
194
|
React.createElement(PanelGroup, { direction: "vertical", className: "relative h-full" },
|
|
120
195
|
hasPreview && !isPreviewMinimized && (React.createElement(React.Fragment, null,
|
|
121
|
-
React.createElement(Panel, { defaultSize:
|
|
122
|
-
React.createElement("div", { className: "h-full w-full" }, showLoader ? (React.createElement("div", { className: "h-full w-full relative overflow-hidden" },
|
|
123
|
-
React.createElement(AiLoader, { text: "Booting up...", fullScreen: false, themeColor: effectiveThemeColor }))) : (React.createElement(Browser, { previewUrl: browserUrl, setPreviewUrl: () => { }, containerEndpoint: browserUrl, outerContainerClassName: "h-full w-full border-none", reloadSignal: reloadCounter, useVm: useVm, isGuardian: isGuardian }, overlayStage !== "hidden" && (React.createElement("div", { className: "absolute inset-0 z-20 flex items-center justify-center" },
|
|
196
|
+
React.createElement(Panel, { defaultSize: 68, minSize: 20, maxSize: 80, order: 1 },
|
|
197
|
+
React.createElement("div", { ref: previewContainerRef, className: "h-full w-full relative" }, showLoader ? (React.createElement("div", { className: "h-full w-full relative overflow-hidden" },
|
|
198
|
+
React.createElement(AiLoader, { text: "Booting up...", fullScreen: false, themeColor: effectiveThemeColor }))) : (React.createElement(Browser, { previewUrl: browserUrl || "", setPreviewUrl: () => { }, containerEndpoint: browserUrl, outerContainerClassName: "h-full w-full border-none", reloadSignal: reloadCounter, useVm: useVm, isGuardian: isGuardian, sandboxError: sandboxError }, overlayStage !== "hidden" && (React.createElement("div", { className: "absolute inset-0 z-20 flex items-center justify-center" },
|
|
124
199
|
overlayStage === "error" && (React.createElement("div", { className: "w-full h-full bg-red-950 text-red-200 flex items-center justify-center" },
|
|
125
200
|
React.createElement("div", { className: "text-center" },
|
|
126
201
|
React.createElement("div", { className: "text-2xl font-semibold" }, "Service Unavailable"),
|
|
@@ -138,18 +213,16 @@ export default function RightView({ reloadCounter, overlayStage, browserUrl, use
|
|
|
138
213
|
React.createElement("div", { className: "absolute inset-x-0 top-0 h-1/2 group-hover:bg-blue-400/20 transition-colors" }),
|
|
139
214
|
React.createElement("div", { className: "absolute inset-x-0 bottom-0 h-1/2 group-hover:bg-blue-400/20 transition-colors" }),
|
|
140
215
|
React.createElement("div", { className: "h-full w-px bg-border group-hover:bg-blue-400 transition-colors relative z-10" }))))),
|
|
141
|
-
React.createElement(Panel, { defaultSize:
|
|
216
|
+
React.createElement(Panel, { defaultSize: 32, minSize: 20, maxSize: 80, order: 2 },
|
|
142
217
|
React.createElement("div", { className: "h-full w-full flex flex-col" },
|
|
143
218
|
React.createElement("div", { className: "flex-1 min-h-0 flex flex-col" },
|
|
144
219
|
React.createElement(React.Fragment, null,
|
|
145
220
|
React.createElement("div", { className: "flex items-center justify-between border-b border-zinc-800 dark:border-zinc-700 bg-zinc-900 dark:bg-zinc-950" },
|
|
146
221
|
React.createElement(PillFileSelector, { themeColor: effectiveThemeColor }),
|
|
147
|
-
|
|
148
|
-
React.createElement("button", { type: "button", className: "inline-flex items-center gap-1 text-[10px] font-medium transition-colors hover:opacity-80", style: { color: effectiveThemeColor }, onClick:
|
|
149
|
-
window.open(downloadCodeUrl, "_blank");
|
|
150
|
-
} },
|
|
222
|
+
React.createElement("div", { className: "px-2 flex-shrink-0" },
|
|
223
|
+
React.createElement("button", { type: "button", className: "inline-flex items-center gap-1 text-[10px] font-medium transition-colors hover:opacity-80", style: { color: effectiveThemeColor }, onClick: handleDownloadAsZip },
|
|
151
224
|
React.createElement(DownloadIcon, { className: "w-4 h-4" }),
|
|
152
|
-
"Download Code")))
|
|
225
|
+
"Download Code"))),
|
|
153
226
|
React.createElement("div", { className: "flex-1 min-h-0" },
|
|
154
227
|
React.createElement(SimplifiedEditor, { themeColor: effectiveThemeColor })))))))));
|
|
155
228
|
}
|
|
@@ -3,7 +3,7 @@ import React, { useState, useEffect } from "react";
|
|
|
3
3
|
import RightPanelView from "./right-panel-view";
|
|
4
4
|
import RightTopDownView from "./right-top-down-view";
|
|
5
5
|
import PreviewControlBar from "./preview-control-bar";
|
|
6
|
-
export default function RightView({ reloadCounter, overlayStage, browserUrl, useVm, codeZipFile, completeCodeZipFile, variant = "top-down", themeColor, hasPreview, isGuardian = false, isBrowserMaximized = false, }) {
|
|
6
|
+
export default function RightView({ reloadCounter, overlayStage, browserUrl, useVm, codeZipFile, completeCodeZipFile, variant = "top-down", themeColor, hasPreview, isGuardian = false, isBrowserMaximized = false, isBrowserUrlReady = false, sandboxId, apiKey, sandboxError, }) {
|
|
7
7
|
const [isPreviewMinimized, setIsPreviewMinimized] = useState(false);
|
|
8
8
|
const [reloadCounterState, setReloadCounterState] = useState(reloadCounter);
|
|
9
9
|
// Sync with external reloadCounter changes
|
|
@@ -14,13 +14,13 @@ export default function RightView({ reloadCounter, overlayStage, browserUrl, use
|
|
|
14
14
|
setReloadCounterState((c) => c + 1);
|
|
15
15
|
};
|
|
16
16
|
if (variant === "panel") {
|
|
17
|
-
return (React.createElement(RightPanelView, { reloadCounter: reloadCounterState, overlayStage: overlayStage, browserUrl: browserUrl, useVm: useVm, codeZipFile: codeZipFile }));
|
|
17
|
+
return (React.createElement(RightPanelView, { reloadCounter: reloadCounterState, overlayStage: overlayStage, browserUrl: browserUrl, useVm: useVm, codeZipFile: codeZipFile, sandboxError: sandboxError }));
|
|
18
18
|
}
|
|
19
19
|
else if (variant === "top-down") {
|
|
20
20
|
return (React.createElement("div", { className: "h-full w-full flex flex-col" },
|
|
21
21
|
hasPreview && (React.createElement(PreviewControlBar, { isMinimized: isPreviewMinimized, onToggle: () => setIsPreviewMinimized(!isPreviewMinimized), onRefresh: handleRefresh, themeColor: themeColor, externalUrl: browserUrl })),
|
|
22
22
|
React.createElement("div", { className: "flex-1 min-h-0" },
|
|
23
|
-
React.createElement(RightTopDownView, { reloadCounter: reloadCounterState, overlayStage: overlayStage, browserUrl: browserUrl, useVm: useVm, codeZipFile: codeZipFile, themeColor: themeColor, completeCodeZipFile: completeCodeZipFile, hasPreview: hasPreview, isPreviewMinimized: isPreviewMinimized, isGuardian: isGuardian, isBrowserMaximized: isBrowserMaximized }))));
|
|
23
|
+
React.createElement(RightTopDownView, { reloadCounter: reloadCounterState, overlayStage: overlayStage, browserUrl: browserUrl, useVm: useVm, codeZipFile: codeZipFile, themeColor: themeColor, completeCodeZipFile: completeCodeZipFile, hasPreview: hasPreview, isPreviewMinimized: isPreviewMinimized, isGuardian: isGuardian, isBrowserMaximized: isBrowserMaximized, isBrowserUrlReady: isBrowserUrlReady, sandboxId: sandboxId, apiKey: apiKey, sandboxError: sandboxError }))));
|
|
24
24
|
}
|
|
25
25
|
else {
|
|
26
26
|
return null;
|
|
@@ -3,60 +3,30 @@ import React from "react";
|
|
|
3
3
|
import { DownloadIcon, Check, Copy } from "lucide-react";
|
|
4
4
|
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, } from "./dialog";
|
|
5
5
|
import vscodeLogo from "../../../../assets/vscode.png";
|
|
6
|
-
import {
|
|
6
|
+
import { handleSandboxDownload } from "../utils";
|
|
7
7
|
// VSCode logo component using imported asset
|
|
8
8
|
function VSCodeLogo({ className }) {
|
|
9
9
|
return (React.createElement("img", { src: vscodeLogo, alt: "VSCode", width: 20, height: 20, className: className }));
|
|
10
10
|
}
|
|
11
|
-
export const DownloadAndOpenButtons = ({ downloadUrl, themeColor, gitUrl, browserUrl, }) => {
|
|
11
|
+
export const DownloadAndOpenButtons = ({ downloadUrl, themeColor, gitUrl, browserUrl, sandboxId, apiKey, }) => {
|
|
12
12
|
const [isModalOpen, setIsModalOpen] = React.useState(false);
|
|
13
13
|
const [hasCopied, setHasCopied] = React.useState(false);
|
|
14
|
-
//
|
|
15
|
-
const containerDownloadUrl = React.useMemo(() => {
|
|
16
|
-
if (!browserUrl)
|
|
17
|
-
return null;
|
|
18
|
-
const containerId = extractContainerIdFromUrl(browserUrl);
|
|
19
|
-
if (!containerId)
|
|
20
|
-
return null;
|
|
21
|
-
const baseUrl = process.env.BASE_API_URL || "https://api.sampleapp.ai";
|
|
22
|
-
return `${baseUrl}/api/v1/sdk/download-code?container_id=${encodeURIComponent(containerId)}`;
|
|
23
|
-
}, [browserUrl]);
|
|
24
|
-
// Normalize the download path to work with public directory (fallback for static files)
|
|
25
|
-
const normalizedDownloadUrl = React.useMemo(() => {
|
|
26
|
-
let normalizedPath = downloadUrl;
|
|
27
|
-
if (normalizedPath.startsWith("/public/")) {
|
|
28
|
-
normalizedPath = normalizedPath.slice("/public".length);
|
|
29
|
-
}
|
|
30
|
-
if (!normalizedPath.startsWith("/")) {
|
|
31
|
-
normalizedPath = "/" + normalizedPath;
|
|
32
|
-
}
|
|
33
|
-
return normalizedPath;
|
|
34
|
-
}, [downloadUrl]);
|
|
35
|
-
// Use container-based download URL if available, otherwise fall back to static file
|
|
36
|
-
const finalDownloadUrl = containerDownloadUrl || normalizedDownloadUrl;
|
|
37
|
-
// Build the npx command using the static API route
|
|
14
|
+
// Build the npx command using the SDK download endpoint
|
|
38
15
|
// Use default MAIN_APP_URL for SDK (can be overridden via env)
|
|
39
16
|
const MAIN_APP_URL = typeof window !== "undefined" && window.SAMPLEAPP_MAIN_URL
|
|
40
17
|
? window.SAMPLEAPP_MAIN_URL
|
|
41
18
|
: "https://sampleapp.ai";
|
|
42
19
|
const npxCommand = React.useMemo(() => {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
if (containerDownloadUrl) {
|
|
46
|
-
return `npx sampleappai add "${containerDownloadUrl}"`;
|
|
47
|
-
}
|
|
48
|
-
// Remove .zip extension and leading slash for the API path
|
|
49
|
-
let apiPath = normalizedDownloadUrl;
|
|
50
|
-
if (apiPath.startsWith("/")) {
|
|
51
|
-
apiPath = apiPath.slice(1);
|
|
52
|
-
}
|
|
53
|
-
if (apiPath.endsWith(".zip")) {
|
|
54
|
-
apiPath = apiPath.slice(0, -4);
|
|
20
|
+
if (!sandboxId) {
|
|
21
|
+
return "";
|
|
55
22
|
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
23
|
+
const apiBaseUrl = typeof window !== "undefined" && window.SAMPLEAPP_API_URL
|
|
24
|
+
? window.SAMPLEAPP_API_URL
|
|
25
|
+
: MAIN_APP_URL.replace("sampleapp.ai", "api.sampleapp.ai");
|
|
26
|
+
return `npx sampleappai add "${apiBaseUrl}/api/v1/sdk/download-code?sandbox_id=${sandboxId}"`;
|
|
27
|
+
}, [sandboxId, MAIN_APP_URL]);
|
|
28
|
+
const handleDownload = async () => {
|
|
29
|
+
await handleSandboxDownload(sandboxId, apiKey, () => setIsModalOpen(true));
|
|
60
30
|
};
|
|
61
31
|
const handleOpenInVSCode = () => {
|
|
62
32
|
if (gitUrl) {
|
|
@@ -105,7 +75,7 @@ export const DownloadAndOpenButtons = ({ downloadUrl, themeColor, gitUrl, browse
|
|
|
105
75
|
React.createElement("div", { className: "flex-shrink-0 w-5 h-5 rounded-full flex items-center justify-center text-[10px] font-bold text-white", style: { backgroundColor: themeColor } }, "1"),
|
|
106
76
|
React.createElement("p", { className: "text-sm font-medium" }, "Copy this command")),
|
|
107
77
|
React.createElement("div", { className: "flex items-center gap-2 overflow-hidden" },
|
|
108
|
-
React.createElement("code", { className: "flex-1 bg-zinc-100 dark:bg-zinc-800 px-3 py-2.5 rounded-md text-xs font-mono truncate block max-w-[calc(100%-3rem)]" }, npxCommand
|
|
78
|
+
React.createElement("code", { className: "flex-1 bg-zinc-100 dark:bg-zinc-800 px-3 py-2.5 rounded-md text-xs font-mono truncate block max-w-[calc(100%-3rem)]" }, npxCommand || "npx sampleappai add <download-url>"),
|
|
109
79
|
React.createElement("button", { type: "button", onClick: handleCopyCommand, className: "flex-shrink-0 p-2 rounded-md hover:bg-zinc-100 dark:hover:bg-zinc-700 transition-colors border border-zinc-200 dark:border-zinc-700" }, hasCopied ? (React.createElement(Check, { className: "w-4 h-4 text-green-500" })) : (React.createElement(Copy, { className: "w-4 h-4 text-muted-foreground" }))))),
|
|
110
80
|
React.createElement("div", { className: "flex flex-col gap-2" },
|
|
111
81
|
React.createElement("div", { className: "flex items-center gap-2" },
|
|
@@ -51,7 +51,7 @@ const createId = (text) => {
|
|
|
51
51
|
};
|
|
52
52
|
export const MemoizedReactMarkdown = memo(ReactMarkdown, (prevProps, nextProps) => prevProps.children === nextProps.children &&
|
|
53
53
|
prevProps.className === nextProps.className);
|
|
54
|
-
export const Markdown = ({ children, className, isRestricted, showToc, themeColor, browserUrl, gitUrl }) => {
|
|
54
|
+
export const Markdown = ({ children, className, isRestricted, showToc, themeColor, browserUrl, gitUrl, sandboxId, apiKey }) => {
|
|
55
55
|
const headingRenderer = (level) => {
|
|
56
56
|
return (_a) => {
|
|
57
57
|
var _b, _c;
|
|
@@ -149,11 +149,13 @@ export const Markdown = ({ children, className, isRestricted, showToc, themeColo
|
|
|
149
149
|
// Use gitUrl from Markdown component props (from guardian-playground) as priority,
|
|
150
150
|
// fall back to HTML attribute if not provided
|
|
151
151
|
const effectiveGitUrl = gitUrl || giturl;
|
|
152
|
+
// Use sandboxId from Markdown component props
|
|
153
|
+
const effectiveSandboxId = sandboxId;
|
|
152
154
|
if (!downloadurl || !effectiveThemeColor) {
|
|
153
155
|
return React.createElement("div", { className: className }, children);
|
|
154
156
|
}
|
|
155
157
|
return (React.createElement("div", { className: cn("my-6", className) },
|
|
156
|
-
React.createElement(DownloadAndOpenButtons, { downloadUrl: downloadurl, themeColor: effectiveThemeColor, gitUrl: effectiveGitUrl, browserUrl: browserUrl }),
|
|
158
|
+
React.createElement(DownloadAndOpenButtons, { downloadUrl: downloadurl, themeColor: effectiveThemeColor, gitUrl: effectiveGitUrl, browserUrl: browserUrl, sandboxId: effectiveSandboxId, apiKey: apiKey }),
|
|
157
159
|
children));
|
|
158
160
|
};
|
|
159
161
|
const sharedComponents = {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { FrameworkLabel
|
|
1
|
+
import { FrameworkLabel } from "./types/ide-types";
|
|
2
|
+
import { createApiClient } from "../../../lib/api-client";
|
|
2
3
|
/**
|
|
3
4
|
* Creates sandbox URL configs for preloading from a framework's urlsToPreload.
|
|
4
5
|
* Only includes URLs that need VM preloading (useVm: true).
|
|
@@ -87,3 +88,31 @@ export function buildGuardianConfig(useCases, options) {
|
|
|
87
88
|
}
|
|
88
89
|
return result;
|
|
89
90
|
}
|
|
91
|
+
/**
|
|
92
|
+
* Shared download handler for sandbox code
|
|
93
|
+
* Uses the backend API endpoint to download code as zip file
|
|
94
|
+
* @param sandboxId - The sandbox content UID
|
|
95
|
+
* @param apiKey - API key for authentication (required)
|
|
96
|
+
* @param onError - Optional callback when download fails (e.g., to show modal)
|
|
97
|
+
* @returns Promise that resolves when download is initiated
|
|
98
|
+
*/
|
|
99
|
+
export async function handleSandboxDownload(sandboxId, apiKey, onError) {
|
|
100
|
+
// Always use the backend endpoint when sandboxId is available
|
|
101
|
+
if (!sandboxId) {
|
|
102
|
+
onError === null || onError === void 0 ? void 0 : onError();
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
if (!apiKey) {
|
|
106
|
+
console.error("API key is required for downloading code");
|
|
107
|
+
onError === null || onError === void 0 ? void 0 : onError();
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
try {
|
|
111
|
+
const apiClient = createApiClient({ apiKey });
|
|
112
|
+
await apiClient.sdk.downloadCode(sandboxId);
|
|
113
|
+
}
|
|
114
|
+
catch (error) {
|
|
115
|
+
console.error("Failed to download code:", error);
|
|
116
|
+
onError === null || onError === void 0 ? void 0 : onError();
|
|
117
|
+
}
|
|
118
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -337,6 +337,12 @@ declare class SampleAppSDK {
|
|
|
337
337
|
* LAUNCHDARKLY_SDK_KEY: "sdk-xxx",
|
|
338
338
|
* }}
|
|
339
339
|
* />
|
|
340
|
+
*
|
|
341
|
+
* // env is optional if published_url is available
|
|
342
|
+
* <Sandbox
|
|
343
|
+
* apiKey={process.env.NEXT_PUBLIC_SAMPLEAPP_API_KEY!}
|
|
344
|
+
* sandboxId="launchdarkly-feature-flags"
|
|
345
|
+
* />
|
|
340
346
|
* ```
|
|
341
347
|
*/
|
|
342
348
|
export declare function Sandbox({ apiKey, sandboxId, env, themeColor, }: SandboxProps): default_2.JSX.Element | null;
|
|
@@ -365,8 +371,12 @@ export declare interface SandboxProps {
|
|
|
365
371
|
apiKey: string;
|
|
366
372
|
/** Sandbox ID to fetch configuration - required */
|
|
367
373
|
sandboxId: string;
|
|
368
|
-
/**
|
|
369
|
-
|
|
374
|
+
/**
|
|
375
|
+
* Environment variables to pass to the sandbox.
|
|
376
|
+
* Optional - only required when published_url is not available (when starting a new sandbox).
|
|
377
|
+
* If published_url exists, env is not needed.
|
|
378
|
+
*/
|
|
379
|
+
env?: Record<string, string>;
|
|
370
380
|
/** Optional theme color override */
|
|
371
381
|
themeColor?: string;
|
|
372
382
|
}
|
|
@@ -375,6 +385,8 @@ declare interface SandboxUrlConfig {
|
|
|
375
385
|
sandboxUid: string;
|
|
376
386
|
browserUrl: string;
|
|
377
387
|
useVm: boolean;
|
|
388
|
+
resolution?: [number, number];
|
|
389
|
+
apiKey?: string;
|
|
378
390
|
}
|
|
379
391
|
|
|
380
392
|
declare const sdk: SampleAppSDK;
|