@sampleapp.ai/sdk 1.0.30 → 1.0.32
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/sandbox/Sandbox.js +3 -2
- package/dist/components/sandbox/api.js +4 -28
- package/dist/components/sandbox/guardian/app-layout-no-sidebar.js +6 -3
- package/dist/components/sandbox/guardian/demo/guardian-demo.js +2 -2
- package/dist/components/sandbox/guardian/demo/left-view.js +17 -5
- package/dist/components/sandbox/guardian/guardian-component.js +7 -5
- package/dist/components/sandbox/guardian/guardian-playground.js +2 -2
- package/dist/components/sandbox/guardian/guardian-style-wrapper.js +17 -3
- package/dist/components/sandbox/guardian/hooks/use-sandbox-url-loader.js +0 -20
- package/dist/components/sandbox/guardian/right-view/pill-file-selector.js +13 -0
- package/dist/components/sandbox/guardian/right-view/right-panel-view.js +17 -4
- package/dist/components/sandbox/guardian/right-view/right-top-down-view.js +42 -50
- package/dist/components/sandbox/guardian/ui/ai-loader.js +56 -13
- package/dist/components/sandbox/guardian/ui/download-and-open-buttons.js +117 -0
- package/dist/components/sandbox/guardian/ui/markdown/code-group/code-block.js +33 -66
- package/dist/components/sandbox/guardian/ui/markdown.js +15 -10
- package/dist/components/sandbox/guardian/ui/theme-color-context.d.ts +6 -0
- package/dist/components/sandbox/guardian/utils.js +1 -0
- package/dist/components/sandbox/sandbox-home/SandboxCard.js +14 -8
- package/dist/components/sandbox/sandbox-home/SandboxHome.js +22 -81
- package/dist/components/sandbox/sandbox-home/SearchBar.js +1 -1
- package/dist/components/ui/skeleton.js +18 -0
- package/dist/index.d.ts +8 -2
- package/dist/index.es.js +23944 -23548
- package/dist/index.standalone.umd.js +13 -13
- package/dist/lib/api-client.js +47 -5
- package/dist/lib/generated-css.js +1 -1
- package/dist/sdk.css +1 -1
- package/dist/tailwind.css +1 -1
- package/package.json +12 -7
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import React from "react";
|
|
3
|
+
import { DownloadIcon, Check, Copy } from "lucide-react";
|
|
4
|
+
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, } from "./dialog";
|
|
5
|
+
import vscodeLogo from "../../../../assets/vscode.png";
|
|
6
|
+
import { extractContainerIdFromUrl } from "../../../../lib/api-client";
|
|
7
|
+
// VSCode logo component using imported asset
|
|
8
|
+
function VSCodeLogo({ className }) {
|
|
9
|
+
return (React.createElement("img", { src: vscodeLogo, alt: "VSCode", width: 20, height: 20, className: className }));
|
|
10
|
+
}
|
|
11
|
+
export const DownloadAndOpenButtons = ({ downloadUrl, themeColor, gitUrl, browserUrl, }) => {
|
|
12
|
+
const [isModalOpen, setIsModalOpen] = React.useState(false);
|
|
13
|
+
const [hasCopied, setHasCopied] = React.useState(false);
|
|
14
|
+
// Extract container ID from browserUrl and build download URL (same as Download Code button)
|
|
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
|
|
38
|
+
// Use default MAIN_APP_URL for SDK (can be overridden via env)
|
|
39
|
+
const MAIN_APP_URL = typeof window !== "undefined" && window.SAMPLEAPP_MAIN_URL
|
|
40
|
+
? window.SAMPLEAPP_MAIN_URL
|
|
41
|
+
: "https://sampleapp.ai";
|
|
42
|
+
const npxCommand = React.useMemo(() => {
|
|
43
|
+
// If using container-based download, we can't generate a static npx command
|
|
44
|
+
// Use the container download URL instead
|
|
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);
|
|
55
|
+
}
|
|
56
|
+
return `npx sampleappai add "${MAIN_APP_URL}/api/code/static/${apiPath}"`;
|
|
57
|
+
}, [containerDownloadUrl, normalizedDownloadUrl, MAIN_APP_URL]);
|
|
58
|
+
const handleDownload = () => {
|
|
59
|
+
window.open(finalDownloadUrl, "_blank");
|
|
60
|
+
};
|
|
61
|
+
const handleOpenInVSCode = () => {
|
|
62
|
+
if (gitUrl) {
|
|
63
|
+
try {
|
|
64
|
+
window.location.href = `vscode://vscode.git/clone?url=${gitUrl}`;
|
|
65
|
+
}
|
|
66
|
+
catch (_a) {
|
|
67
|
+
// Silently fail if vscode:// protocol isn't registered
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
setIsModalOpen(true);
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
const handleCopyCommand = async () => {
|
|
75
|
+
await navigator.clipboard.writeText(npxCommand);
|
|
76
|
+
setHasCopied(true);
|
|
77
|
+
setTimeout(() => setHasCopied(false), 2000);
|
|
78
|
+
};
|
|
79
|
+
const handleOpenVSCode = () => {
|
|
80
|
+
try {
|
|
81
|
+
window.location.href = "vscode://";
|
|
82
|
+
}
|
|
83
|
+
catch (_a) {
|
|
84
|
+
// Silently fail if vscode:// protocol isn't registered
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
return (React.createElement(React.Fragment, null,
|
|
88
|
+
React.createElement("div", { className: "flex items-center gap-2" },
|
|
89
|
+
React.createElement("button", { type: "button", className: "inline-flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium rounded-md !text-white transition-all hover:opacity-90 active:scale-[0.98]", style: { backgroundColor: themeColor }, onClick: handleDownload },
|
|
90
|
+
React.createElement(DownloadIcon, { className: "w-3.5 h-3.5 !text-white" }),
|
|
91
|
+
"Download example"),
|
|
92
|
+
React.createElement("button", { type: "button", className: "inline-flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium rounded-md border transition-all hover:opacity-90 active:scale-[0.98] bg-zinc-900 dark:bg-zinc-800 border-zinc-700 dark:border-zinc-700 !text-white", onClick: handleOpenInVSCode },
|
|
93
|
+
React.createElement(VSCodeLogo, { className: "w-3.5 h-3.5" }),
|
|
94
|
+
"Integrate in VS Code")),
|
|
95
|
+
React.createElement(Dialog, { open: isModalOpen, onOpenChange: setIsModalOpen },
|
|
96
|
+
React.createElement(DialogContent, { className: "max-w-[90vw] sm:max-w-md w-full" },
|
|
97
|
+
React.createElement(DialogHeader, null,
|
|
98
|
+
React.createElement(DialogTitle, { className: "flex items-center gap-2" },
|
|
99
|
+
React.createElement(VSCodeLogo, { className: "w-5 h-5" }),
|
|
100
|
+
"Add to VS Code"),
|
|
101
|
+
React.createElement(DialogDescription, null, "Copy the command and paste it in VSCode terminal")),
|
|
102
|
+
React.createElement("div", { className: "flex flex-col gap-4 py-2 overflow-hidden" },
|
|
103
|
+
React.createElement("div", { className: "flex flex-col gap-2 overflow-hidden" },
|
|
104
|
+
React.createElement("div", { className: "flex items-center gap-2" },
|
|
105
|
+
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
|
+
React.createElement("p", { className: "text-sm font-medium" }, "Copy this command")),
|
|
107
|
+
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.replace("http://localhost:3000", "https://sampleapp.ai")),
|
|
109
|
+
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
|
+
React.createElement("div", { className: "flex flex-col gap-2" },
|
|
111
|
+
React.createElement("div", { className: "flex items-center gap-2" },
|
|
112
|
+
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 } }, "2"),
|
|
113
|
+
React.createElement("p", { className: "text-sm font-medium" }, "Open VSCode and paste in terminal")),
|
|
114
|
+
React.createElement("button", { type: "button", onClick: handleOpenVSCode, className: "w-full inline-flex items-center justify-center gap-2 px-4 py-2.5 text-sm font-medium rounded-md text-white transition-all hover:opacity-90 active:scale-[0.99]", style: { backgroundColor: themeColor } },
|
|
115
|
+
React.createElement(VSCodeLogo, { className: "w-4 h-4" }),
|
|
116
|
+
"Open VSCode")))))));
|
|
117
|
+
};
|
|
@@ -1,48 +1,10 @@
|
|
|
1
|
-
import React from "react";
|
|
1
|
+
import React, { memo, useState } from "react";
|
|
2
2
|
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
|
|
3
|
+
import { vscDarkPlus } from "react-syntax-highlighter/dist/esm/styles/prism";
|
|
3
4
|
import { cn } from "../../../../../../lib/utils";
|
|
4
|
-
import { Copy } from "lucide-react";
|
|
5
|
-
export const CodeBlock = ({ language, value, title, className, children, isInsideCodeGroup = false, }) => {
|
|
6
|
-
|
|
7
|
-
// const isDark = theme === "dark";
|
|
8
|
-
const isDark = true;
|
|
9
|
-
const [copied, setCopied] = React.useState(false);
|
|
10
|
-
const lightTheme = {
|
|
11
|
-
'code[class*="language-"]': {
|
|
12
|
-
color: "#1F2937",
|
|
13
|
-
background: "transparent",
|
|
14
|
-
},
|
|
15
|
-
'pre[class*="language-"]': {
|
|
16
|
-
background: "transparent",
|
|
17
|
-
},
|
|
18
|
-
keyword: {
|
|
19
|
-
color: "#D92D20",
|
|
20
|
-
},
|
|
21
|
-
string: {
|
|
22
|
-
color: "#027A48",
|
|
23
|
-
},
|
|
24
|
-
constant: {
|
|
25
|
-
color: "#1F2937",
|
|
26
|
-
},
|
|
27
|
-
};
|
|
28
|
-
const darkTheme = {
|
|
29
|
-
'code[class*="language-"]': {
|
|
30
|
-
color: "#E5E7EB",
|
|
31
|
-
background: "transparent",
|
|
32
|
-
},
|
|
33
|
-
'pre[class*="language-"]': {
|
|
34
|
-
background: "transparent",
|
|
35
|
-
},
|
|
36
|
-
keyword: {
|
|
37
|
-
color: "#FF6B6B",
|
|
38
|
-
},
|
|
39
|
-
string: {
|
|
40
|
-
color: "#34D399",
|
|
41
|
-
},
|
|
42
|
-
constant: {
|
|
43
|
-
color: "#E5E7EB",
|
|
44
|
-
},
|
|
45
|
-
};
|
|
5
|
+
import { Copy, CheckCircle2 } from "lucide-react";
|
|
6
|
+
export const CodeBlock = memo(({ language, value, title, className, children, isInsideCodeGroup = false, maxH, }) => {
|
|
7
|
+
const [copied, setCopied] = useState(false);
|
|
46
8
|
let code = value;
|
|
47
9
|
if (typeof children === "string") {
|
|
48
10
|
code = children;
|
|
@@ -60,28 +22,33 @@ export const CodeBlock = ({ language, value, title, className, children, isInsid
|
|
|
60
22
|
// If inside CodeGroup, render just the code content
|
|
61
23
|
if (isInsideCodeGroup) {
|
|
62
24
|
return (React.createElement("div", { className: "group relative overflow-x-auto" },
|
|
63
|
-
React.createElement(
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
25
|
+
React.createElement("div", { className: `${maxH || "max-h-[40vh]"} rounded-b-md bg-zinc-900 px-5 py-3 overflow-y-auto [&>pre]:overflow-x-auto` },
|
|
26
|
+
React.createElement("div", { className: "min-w-0 text-[13px]" },
|
|
27
|
+
React.createElement(SyntaxHighlighter, { language: language || "javascript", style: vscDarkPlus, PreTag: "div", customStyle: {
|
|
28
|
+
margin: 0,
|
|
29
|
+
padding: 0,
|
|
30
|
+
background: "transparent",
|
|
31
|
+
fontSize: "13px",
|
|
32
|
+
width: "100%",
|
|
33
|
+
minWidth: 0,
|
|
34
|
+
maxWidth: "100%",
|
|
35
|
+
}, wrapLongLines: false }, code || "")))));
|
|
70
36
|
}
|
|
71
37
|
// Standalone code block with title
|
|
72
|
-
return (React.createElement("div", { className: cn("relative
|
|
73
|
-
|
|
74
|
-
React.createElement("span", { className: "text-
|
|
75
|
-
React.createElement("button", { onClick: handleCopy, className:
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
React.createElement(
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
};
|
|
38
|
+
return (React.createElement("div", { className: cn("relative w-full border rounded-lg mb-4", className) },
|
|
39
|
+
React.createElement("div", { className: "flex items-center justify-between px-4 py-2 bg-zinc-900 text-primary border-b rounded-t-md" },
|
|
40
|
+
React.createElement("span", { className: "text-xs text-primary font-mono" }, language || title || ""),
|
|
41
|
+
React.createElement("button", { type: "button", className: "h-5 w-5 flex items-center justify-center hover:bg-transparent text-zinc-200 hover:text-zinc-300 border-none", onClick: handleCopy }, copied ? (React.createElement(CheckCircle2, { className: "h-3 w-3 text-green-500" })) : (React.createElement(Copy, { className: "h-3 w-3" })))),
|
|
42
|
+
React.createElement("div", { className: `${maxH || "max-h-[40vh]"} rounded-b-md bg-zinc-900 px-5 py-3 overflow-y-auto [&>pre]:overflow-x-auto` },
|
|
43
|
+
React.createElement("div", { className: "min-w-0 text-[13px]" },
|
|
44
|
+
React.createElement(SyntaxHighlighter, { language: language || "javascript", style: vscDarkPlus, PreTag: "div", customStyle: {
|
|
45
|
+
margin: 0,
|
|
46
|
+
padding: 0,
|
|
47
|
+
background: "transparent",
|
|
48
|
+
fontSize: "13px",
|
|
49
|
+
width: "100%",
|
|
50
|
+
minWidth: 0,
|
|
51
|
+
maxWidth: "100%",
|
|
52
|
+
}, wrapLongLines: false }, code || "")))));
|
|
53
|
+
});
|
|
54
|
+
CodeBlock.displayName = "CodeBlock";
|
|
@@ -32,6 +32,7 @@ import { CalloutWarning } from "./markdown/callout/callout-warning";
|
|
|
32
32
|
import { CardGroup } from "./markdown/card-group";
|
|
33
33
|
import { Card } from "./markdown/card-group/card";
|
|
34
34
|
import CodeFocusSection from "@/components/sandbox/guardian/code-focus-section";
|
|
35
|
+
import { DownloadAndOpenButtons } from "./download-and-open-buttons";
|
|
35
36
|
// Stub components for components that don't exist in SDK
|
|
36
37
|
const ButtonFileServer = ({ title, description, filePath, feature, fileContent, color, }) => {
|
|
37
38
|
return null; // Stub - not implemented in SDK
|
|
@@ -42,9 +43,6 @@ const ToggleFileServer = ({ title, description, filePath, feature, fileContent,
|
|
|
42
43
|
const ToggleIframeUrl = ({ title, description, newUrl, feature, color, useVm, groupId, preloadedVmUrl, }) => {
|
|
43
44
|
return null; // Stub - not implemented in SDK
|
|
44
45
|
};
|
|
45
|
-
const DownloadAndOpenButtons = ({ downloadUrl, themeColor, gitUrl }) => {
|
|
46
|
-
return null; // Stub - not implemented in SDK
|
|
47
|
-
};
|
|
48
46
|
const createId = (text) => {
|
|
49
47
|
return text
|
|
50
48
|
.toLowerCase()
|
|
@@ -53,7 +51,7 @@ const createId = (text) => {
|
|
|
53
51
|
};
|
|
54
52
|
export const MemoizedReactMarkdown = memo(ReactMarkdown, (prevProps, nextProps) => prevProps.children === nextProps.children &&
|
|
55
53
|
prevProps.className === nextProps.className);
|
|
56
|
-
export const Markdown = ({ children, className, isRestricted, showToc, themeColor }) => {
|
|
54
|
+
export const Markdown = ({ children, className, isRestricted, showToc, themeColor, browserUrl, gitUrl }) => {
|
|
57
55
|
const headingRenderer = (level) => {
|
|
58
56
|
return (_a) => {
|
|
59
57
|
var _b, _c;
|
|
@@ -74,7 +72,7 @@ export const Markdown = ({ children, className, isRestricted, showToc, themeColo
|
|
|
74
72
|
};
|
|
75
73
|
// Wrapper for CodeFocusSection to handle HTML attributes from react-markdown
|
|
76
74
|
const CodeFocusSectionWrapper = (_a) => {
|
|
77
|
-
var { children, filepath, filerange, title, description,
|
|
75
|
+
var { children, filepath, filerange, title, description, className } = _a, props = __rest(_a, ["children", "filepath", "filerange", "title", "description", "className"]);
|
|
78
76
|
// Parse lineRange from string format like "10-30" or "10"
|
|
79
77
|
let lineRange;
|
|
80
78
|
if (filerange) {
|
|
@@ -94,8 +92,9 @@ export const Markdown = ({ children, className, isRestricted, showToc, themeColo
|
|
|
94
92
|
if (!filepath || !title) {
|
|
95
93
|
return React.createElement("div", { className: className }, children);
|
|
96
94
|
}
|
|
97
|
-
// Use
|
|
98
|
-
|
|
95
|
+
// Use themeColor from Markdown component props (from guardian-playground)
|
|
96
|
+
// Don't accept themecolor from HTML attributes
|
|
97
|
+
const color = themeColor || "#3b82f6";
|
|
99
98
|
return (React.createElement("div", { className: className },
|
|
100
99
|
React.createElement(CodeFocusSection, { filePath: filepath, lineRange: lineRange, title: title, description: description, themeColor: color }, children)));
|
|
101
100
|
};
|
|
@@ -143,12 +142,18 @@ export const Markdown = ({ children, className, isRestricted, showToc, themeColo
|
|
|
143
142
|
};
|
|
144
143
|
// Wrapper for DownloadAndOpenButtons to handle HTML attributes from react-markdown
|
|
145
144
|
const DownloadAndOpenButtonsWrapper = (_a) => {
|
|
146
|
-
var { children, downloadurl,
|
|
147
|
-
|
|
145
|
+
var { children, downloadurl, giturl, className } = _a, props = __rest(_a, ["children", "downloadurl", "giturl", "className"]);
|
|
146
|
+
// Use themeColor from Markdown component props (from guardian-playground)
|
|
147
|
+
// Don't accept themecolor from HTML attributes
|
|
148
|
+
const effectiveThemeColor = themeColor || "#3b82f6";
|
|
149
|
+
// Use gitUrl from Markdown component props (from guardian-playground) as priority,
|
|
150
|
+
// fall back to HTML attribute if not provided
|
|
151
|
+
const effectiveGitUrl = gitUrl || giturl;
|
|
152
|
+
if (!downloadurl || !effectiveThemeColor) {
|
|
148
153
|
return React.createElement("div", { className: className }, children);
|
|
149
154
|
}
|
|
150
155
|
return (React.createElement("div", { className: cn("my-6", className) },
|
|
151
|
-
React.createElement(DownloadAndOpenButtons, { downloadUrl: downloadurl, themeColor:
|
|
156
|
+
React.createElement(DownloadAndOpenButtons, { downloadUrl: downloadurl, themeColor: effectiveThemeColor, gitUrl: effectiveGitUrl, browserUrl: browserUrl }),
|
|
152
157
|
children));
|
|
153
158
|
};
|
|
154
159
|
const sharedComponents = {
|
|
@@ -33,14 +33,21 @@ function getGradientColors(title) {
|
|
|
33
33
|
/**
|
|
34
34
|
* SandboxCard component - displays an individual template card
|
|
35
35
|
*/
|
|
36
|
-
export const SandboxCard = ({ title, description, sandboxId, }) => {
|
|
36
|
+
export const SandboxCard = ({ title, description, sandboxId, basePath = "/sandbox", imageUrl, }) => {
|
|
37
37
|
// Ensure title is always a string
|
|
38
38
|
const safeTitle = title || "";
|
|
39
39
|
const safeDescription = description || "";
|
|
40
40
|
const gradient = getGradientColors(safeTitle);
|
|
41
41
|
const handleClick = () => {
|
|
42
42
|
if (sandboxId) {
|
|
43
|
-
|
|
43
|
+
// Ensure basePath starts with / and doesn't end with /
|
|
44
|
+
const normalizedBasePath = basePath.startsWith("/")
|
|
45
|
+
? basePath
|
|
46
|
+
: `/${basePath}`;
|
|
47
|
+
const cleanBasePath = normalizedBasePath.endsWith("/")
|
|
48
|
+
? normalizedBasePath.slice(0, -1)
|
|
49
|
+
: normalizedBasePath;
|
|
50
|
+
window.location.href = `${cleanBasePath}/${sandboxId}`;
|
|
44
51
|
}
|
|
45
52
|
};
|
|
46
53
|
return (React.createElement("div", { onClick: handleClick, className: "group relative block min-w-[240px] w-full h-[200px] border border-[#333] bg-[#0a0a0a] rounded-lg overflow-hidden transition-all duration-100 ease-out hover:border-[#444] focus-visible:outline-none cursor-pointer" },
|
|
@@ -50,10 +57,9 @@ export const SandboxCard = ({ title, description, sandboxId, }) => {
|
|
|
50
57
|
React.createElement("svg", { className: "rotate-[-45deg] translate-x-[-2px] opacity-0 transition-all duration-100 ease-out origin-center group-hover:opacity-100 group-hover:translate-x-[4px] ml-1", height: "16", strokeLinejoin: "round", style: { width: 12, height: 12, color: "currentColor" }, viewBox: "0 0 16 16", width: "16" },
|
|
51
58
|
React.createElement("path", { fillRule: "evenodd", clipRule: "evenodd", d: "M9.53033 2.21968L9 1.68935L7.93934 2.75001L8.46967 3.28034L12.4393 7.25001H1.75H1V8.75001H1.75H12.4393L8.46967 12.7197L7.93934 13.25L9 14.3107L9.53033 13.7803L14.6036 8.70711C14.9941 8.31659 14.9941 7.68342 14.6036 7.2929L9.53033 2.21968Z", fill: "currentColor" }))),
|
|
52
59
|
React.createElement("p", { className: "text-[14px] text-gray-400 m-0 line-clamp-2" }, safeDescription)),
|
|
53
|
-
React.createElement("div", { className: "absolute top-[110px] -right-10 w-[300px] h-[100px] rounded-lg border border-[#333] overflow-hidden shadow-lg transition-transform duration-100 ease-out rotate-[-5deg] group-hover:rotate-[-3deg] group-hover:-translate-y-1 group-hover:-translate-x-0.5" },
|
|
54
|
-
React.createElement("div", { className:
|
|
55
|
-
React.createElement("div", { className: "
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
React.createElement("div", { className: "h-1.5 bg-white/10 rounded w-2/3 mx-auto" }))))));
|
|
60
|
+
React.createElement("div", { className: "absolute top-[110px] -right-10 w-[300px] h-[100px] rounded-lg border border-[#333] overflow-hidden shadow-lg transition-transform duration-100 ease-out rotate-[-5deg] group-hover:rotate-[-3deg] group-hover:-translate-y-1 group-hover:-translate-x-0.5" }, imageUrl ? (React.createElement("img", { src: imageUrl, alt: safeTitle, className: "w-full h-full object-cover", style: { transform: "rotate(0deg)" } })) : (React.createElement("div", { className: `w-full h-full bg-gradient-to-br ${gradient} flex items-center justify-center` },
|
|
61
|
+
React.createElement("div", { className: "w-full px-6 space-y-2" },
|
|
62
|
+
React.createElement("div", { className: "h-2 bg-white/20 rounded w-1/2 mx-auto" }),
|
|
63
|
+
React.createElement("div", { className: "h-1.5 bg-white/10 rounded w-3/4 mx-auto" }),
|
|
64
|
+
React.createElement("div", { className: "h-1.5 bg-white/10 rounded w-2/3 mx-auto" })))))));
|
|
59
65
|
};
|
|
@@ -3,74 +3,9 @@ import React, { useState, useMemo, useEffect } from "react";
|
|
|
3
3
|
import { SandboxCard } from "./SandboxCard";
|
|
4
4
|
import { SearchBar } from "./SearchBar";
|
|
5
5
|
import { createApiClient } from "../../../lib/api-client";
|
|
6
|
-
|
|
7
|
-
const DEFAULT_SANDBOXES = [
|
|
8
|
-
{
|
|
9
|
-
id: "1",
|
|
10
|
-
title: "Next.js AI Chatbot",
|
|
11
|
-
description: "A full-featured, hackable Next.js AI chatbot built by Vercel",
|
|
12
|
-
sandboxId: "nextjs-ai-chatbot",
|
|
13
|
-
},
|
|
14
|
-
{
|
|
15
|
-
id: "2",
|
|
16
|
-
title: "Hume AI - Empathic Voice Interface",
|
|
17
|
-
description: "This template creates a voice chat using Hume AI's Empathic Voice Interface.",
|
|
18
|
-
sandboxId: "hume-ai-voice",
|
|
19
|
-
},
|
|
20
|
-
{
|
|
21
|
-
id: "3",
|
|
22
|
-
title: "Lead Agent",
|
|
23
|
-
description: "An inbound lead qualification and research agent built with Next.js, AI SDK, Workflow...",
|
|
24
|
-
sandboxId: "lead-agent",
|
|
25
|
-
},
|
|
26
|
-
{
|
|
27
|
-
id: "4",
|
|
28
|
-
title: "Slack Agent Template",
|
|
29
|
-
description: "This is a Slack Agent template built with Bolt for JavaScript (TypeScript) and the Nitro server...",
|
|
30
|
-
sandboxId: "slack-agent",
|
|
31
|
-
},
|
|
32
|
-
{
|
|
33
|
-
id: "5",
|
|
34
|
-
title: "Customer Reviews AI Summary",
|
|
35
|
-
description: "Use a Large Language Model to summarize customer feedback.",
|
|
36
|
-
sandboxId: "customer-reviews-ai",
|
|
37
|
-
},
|
|
38
|
-
{
|
|
39
|
-
id: "6",
|
|
40
|
-
title: "Nuxt AI Chatbot",
|
|
41
|
-
description: "An AI chatbot template to build your own chatbot powered by Nuxt MDC and Vercel AI...",
|
|
42
|
-
sandboxId: "nuxt-ai-chatbot",
|
|
43
|
-
},
|
|
44
|
-
{
|
|
45
|
-
id: "7",
|
|
46
|
-
title: "Morphic: AI-powered answer engine",
|
|
47
|
-
description: "AI answer engine with Generative UI.",
|
|
48
|
-
sandboxId: "morphic-ai",
|
|
49
|
-
},
|
|
50
|
-
{
|
|
51
|
-
id: "8",
|
|
52
|
-
title: "Pinecone - Vercel AI SDK Starter",
|
|
53
|
-
description: "A Next.js starter chatbot using Vercel's AI SDK and implements the Retrieval-Augmented...",
|
|
54
|
-
sandboxId: "pinecone-ai-sdk",
|
|
55
|
-
},
|
|
56
|
-
{
|
|
57
|
-
id: "9",
|
|
58
|
-
title: "qrGPT - AI QR Code Generator",
|
|
59
|
-
description: "QrGPT is an AI tool for you to generate beautiful QR codes using AI with one click. Powered by...",
|
|
60
|
-
sandboxId: "qrgpt-ai",
|
|
61
|
-
},
|
|
62
|
-
];
|
|
6
|
+
import { Skeleton } from "../../ui/skeleton";
|
|
63
7
|
/**
|
|
64
|
-
*
|
|
65
|
-
*/
|
|
66
|
-
function getBaseUrl(customBaseUrl) {
|
|
67
|
-
if (customBaseUrl) {
|
|
68
|
-
return customBaseUrl;
|
|
69
|
-
}
|
|
70
|
-
return "http://127.0.0.1:8000";
|
|
71
|
-
}
|
|
72
|
-
/**
|
|
73
|
-
* Transform SandboxContent to SandboxItem
|
|
8
|
+
* Transform SandboxContentPublic to SandboxItem
|
|
74
9
|
*/
|
|
75
10
|
function transformSandboxContent(content) {
|
|
76
11
|
// Extract title from markdown (first heading) or use uid as fallback
|
|
@@ -96,14 +31,15 @@ function transformSandboxContent(content) {
|
|
|
96
31
|
}
|
|
97
32
|
}
|
|
98
33
|
return {
|
|
99
|
-
id: content.uid
|
|
34
|
+
id: content.uid,
|
|
100
35
|
title,
|
|
101
36
|
description,
|
|
102
37
|
sandboxId: content.uid,
|
|
103
38
|
};
|
|
104
39
|
}
|
|
105
|
-
export const SandboxHome = ({ apiKey, orgid,
|
|
106
|
-
|
|
40
|
+
export const SandboxHome = ({ apiKey, orgid, sandboxes, basePath = "/sandbox", }) => {
|
|
41
|
+
// Start loading as true if we need to fetch (no sandboxes prop provided)
|
|
42
|
+
const [loading, setLoading] = useState(!sandboxes);
|
|
107
43
|
const [error, setError] = useState(null);
|
|
108
44
|
const [searchQuery, setSearchQuery] = useState("");
|
|
109
45
|
const [fetchedSandboxes, setFetchedSandboxes] = useState([]);
|
|
@@ -112,6 +48,7 @@ export const SandboxHome = ({ apiKey, orgid, baseUrl, sandboxes, }) => {
|
|
|
112
48
|
if (sandboxes) {
|
|
113
49
|
// If sandboxes are provided as prop, use them
|
|
114
50
|
setFetchedSandboxes([]);
|
|
51
|
+
setLoading(false);
|
|
115
52
|
return;
|
|
116
53
|
}
|
|
117
54
|
const fetchSandboxes = async () => {
|
|
@@ -119,7 +56,6 @@ export const SandboxHome = ({ apiKey, orgid, baseUrl, sandboxes, }) => {
|
|
|
119
56
|
setError(null);
|
|
120
57
|
try {
|
|
121
58
|
const client = createApiClient({
|
|
122
|
-
baseUrl: getBaseUrl(baseUrl),
|
|
123
59
|
apiKey,
|
|
124
60
|
});
|
|
125
61
|
const sandboxContents = await client.sandboxContent.getByPlayground(orgid);
|
|
@@ -130,18 +66,17 @@ export const SandboxHome = ({ apiKey, orgid, baseUrl, sandboxes, }) => {
|
|
|
130
66
|
const errorMessage = err instanceof Error ? err.message : "Failed to fetch sandboxes";
|
|
131
67
|
setError(errorMessage);
|
|
132
68
|
console.error("Error fetching sandboxes:", err);
|
|
133
|
-
//
|
|
134
|
-
setFetchedSandboxes(
|
|
69
|
+
// Don't fall back to default sandboxes - show error state instead
|
|
70
|
+
setFetchedSandboxes([]);
|
|
135
71
|
}
|
|
136
72
|
finally {
|
|
137
73
|
setLoading(false);
|
|
138
74
|
}
|
|
139
75
|
};
|
|
140
76
|
fetchSandboxes();
|
|
141
|
-
}, [apiKey, orgid,
|
|
142
|
-
// Use provided sandboxes
|
|
143
|
-
const allSandboxes = sandboxes ||
|
|
144
|
-
(fetchedSandboxes.length > 0 ? fetchedSandboxes : DEFAULT_SANDBOXES);
|
|
77
|
+
}, [apiKey, orgid, sandboxes]);
|
|
78
|
+
// Use provided sandboxes or fetched sandboxes (no default fallback to prevent flickering)
|
|
79
|
+
const allSandboxes = sandboxes || fetchedSandboxes;
|
|
145
80
|
// Filter sandboxes based on search query
|
|
146
81
|
const filteredSandboxes = useMemo(() => {
|
|
147
82
|
if (!searchQuery.trim()) {
|
|
@@ -160,14 +95,20 @@ export const SandboxHome = ({ apiKey, orgid, baseUrl, sandboxes, }) => {
|
|
|
160
95
|
React.createElement("div", { className: "max-w-[1400px] mx-auto" },
|
|
161
96
|
React.createElement(SearchBar, { value: searchQuery, onChange: setSearchQuery }))),
|
|
162
97
|
React.createElement("div", { className: "px-4 md:px-6 pb-16" },
|
|
163
|
-
React.createElement("div", { className: "max-w-[1400px] mx-auto" }, loading ? (React.createElement("div", { className: "
|
|
164
|
-
React.createElement("
|
|
98
|
+
React.createElement("div", { className: "max-w-[1400px] mx-auto" }, loading ? (React.createElement("div", { className: "grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-4" }, [...Array(6)].map((_, i) => (React.createElement("div", { key: i, className: "relative block min-w-[240px] w-full h-[200px] border border-[#333] bg-[#0a0a0a] rounded-lg overflow-hidden" },
|
|
99
|
+
React.createElement("div", { className: "flex flex-col gap-1 px-6 py-5" },
|
|
100
|
+
React.createElement("div", { className: "flex items-center max-w-max pr-2.5" },
|
|
101
|
+
React.createElement(Skeleton, { className: "h-5 w-32 bg-zinc-800" })),
|
|
102
|
+
React.createElement(Skeleton, { className: "h-4 w-full bg-zinc-800 mt-1" }),
|
|
103
|
+
React.createElement(Skeleton, { className: "h-4 w-3/4 bg-zinc-800 mt-1" })),
|
|
104
|
+
React.createElement("div", { className: "absolute top-[110px] -right-10 w-[300px] h-[100px] rounded-lg border border-[#333] overflow-hidden shadow-lg rotate-[-5deg]" },
|
|
105
|
+
React.createElement(Skeleton, { className: "w-full h-full bg-zinc-800" }))))))) : error && !sandboxes ? (React.createElement("div", { className: "col-span-full text-center py-16" },
|
|
165
106
|
React.createElement("p", { className: "text-red-500 text-lg" },
|
|
166
107
|
"Error: ",
|
|
167
108
|
error),
|
|
168
|
-
React.createElement("p", { className: "text-gray-500 text-sm mt-2" }, "
|
|
109
|
+
React.createElement("p", { className: "text-gray-500 text-sm mt-2" }, "Please check your API key and try again."))) : (React.createElement("div", { className: "grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-4" }, filteredSandboxes && filteredSandboxes.length > 0 ? (filteredSandboxes
|
|
169
110
|
.filter((sandbox) => sandbox && sandbox.title && sandbox.description)
|
|
170
|
-
.map((sandbox) => (React.createElement(SandboxCard, { key: sandbox.id, title: sandbox.title || "", description: sandbox.description || "", sandboxId: sandbox.sandboxId })))) : (React.createElement("div", { className: "col-span-full text-center py-16" },
|
|
111
|
+
.map((sandbox) => (React.createElement(SandboxCard, { key: sandbox.id, title: sandbox.title || "", description: sandbox.description || "", sandboxId: sandbox.sandboxId, basePath: basePath, imageUrl: `https://sampleappassets.blob.core.windows.net/assets/${orgid}/${sandbox.id}.png` })))) : (React.createElement("div", { className: "col-span-full text-center py-16" },
|
|
171
112
|
React.createElement("p", { className: "text-gray-500 text-lg" }, searchQuery
|
|
172
113
|
? `No sandboxes found matching "${searchQuery}".`
|
|
173
114
|
: "No sandboxes available. Provide sandboxes prop to display cards.")))))))));
|
|
@@ -3,7 +3,7 @@ import React from "react";
|
|
|
3
3
|
/**
|
|
4
4
|
* SearchBar component - provides search input for filtering sandboxes
|
|
5
5
|
*/
|
|
6
|
-
export const SearchBar = ({ value, onChange, placeholder = "Search
|
|
6
|
+
export const SearchBar = ({ value, onChange, placeholder = "Search sample apps...", }) => {
|
|
7
7
|
return (React.createElement("div", { className: "relative max-w-md mx-auto" },
|
|
8
8
|
React.createElement("div", { className: "absolute inset-y-0 left-4 flex items-center pointer-events-none" },
|
|
9
9
|
React.createElement("svg", { className: "w-4 h-4 text-gray-500", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", xmlns: "http://www.w3.org/2000/svg" },
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
var __rest = (this && this.__rest) || function (s, e) {
|
|
2
|
+
var t = {};
|
|
3
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
4
|
+
t[p] = s[p];
|
|
5
|
+
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
|
6
|
+
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
|
7
|
+
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
|
8
|
+
t[p[i]] = s[p[i]];
|
|
9
|
+
}
|
|
10
|
+
return t;
|
|
11
|
+
};
|
|
12
|
+
import React from "react";
|
|
13
|
+
import { cn } from "@/lib/utils";
|
|
14
|
+
function Skeleton(_a) {
|
|
15
|
+
var { className } = _a, props = __rest(_a, ["className"]);
|
|
16
|
+
return (React.createElement("div", Object.assign({ className: cn("animate-pulse rounded-md bg-zinc-100 dark:bg-zinc-900", className) }, props)));
|
|
17
|
+
}
|
|
18
|
+
export { Skeleton };
|
package/dist/index.d.ts
CHANGED
|
@@ -138,7 +138,8 @@ export declare enum Framework {
|
|
|
138
138
|
|
|
139
139
|
export declare const getTheme: (themeName?: ThemeName) => ThemeColors;
|
|
140
140
|
|
|
141
|
-
export declare function GuardianComponent({ demoOptions, frameworkOptions, firstFrameworkByUseCase, currentFramework, currentUseCase, CustomConsole, GuideView, playgroundLogo, playgroundUid, browserUrl, useVm, sandboxUid, codeZipFile, consoleUrlConfigs, completeCodeZipFile, variant, themeColor, hasPreview, isFrame, apiKey, env, chatUid,
|
|
141
|
+
export declare function GuardianComponent({ demoOptions, frameworkOptions, firstFrameworkByUseCase, currentFramework, currentUseCase, CustomConsole, GuideView, playgroundLogo, playgroundUid, browserUrl, useVm, sandboxUid, codeZipFile, consoleUrlConfigs, completeCodeZipFile, variant, themeColor, hasPreview, isFrame, apiKey, env, chatUid, hideHeader, // Hardcoded to true by default, not exposed in Sandbox.tsx
|
|
142
|
+
gitUrl, }: GuardianComponentProps): default_2.JSX.Element;
|
|
142
143
|
|
|
143
144
|
export declare interface GuardianComponentProps {
|
|
144
145
|
name: string;
|
|
@@ -194,6 +195,9 @@ export declare interface GuardianComponentProps {
|
|
|
194
195
|
env?: Record<string, string>;
|
|
195
196
|
/** Chat UID for starting sandbox */
|
|
196
197
|
chatUid?: string;
|
|
198
|
+
/* Excluded from this release type: hideHeader */
|
|
199
|
+
/** Optional Git URL for opening the project in VSCode */
|
|
200
|
+
gitUrl?: string;
|
|
197
201
|
}
|
|
198
202
|
|
|
199
203
|
/**
|
|
@@ -221,6 +225,8 @@ export declare interface GuardianFramework {
|
|
|
221
225
|
themeColor?: string;
|
|
222
226
|
/** Optional URLs to preload for this specific framework */
|
|
223
227
|
urlsToPreload?: UrlToPreload[];
|
|
228
|
+
/** Optional Git URL for opening the project in VSCode */
|
|
229
|
+
gitUrl?: string;
|
|
224
230
|
}
|
|
225
231
|
|
|
226
232
|
/**
|
|
@@ -340,8 +346,8 @@ export declare const SandboxHome: React_2.FC<SandboxHomeProps>;
|
|
|
340
346
|
export declare interface SandboxHomeProps {
|
|
341
347
|
apiKey: string;
|
|
342
348
|
orgid: string;
|
|
343
|
-
baseUrl?: string;
|
|
344
349
|
sandboxes?: SandboxItem[];
|
|
350
|
+
basePath?: string;
|
|
345
351
|
}
|
|
346
352
|
|
|
347
353
|
export declare interface SandboxItem {
|