@sampleapp.ai/sdk 1.0.30 → 1.0.31

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.
Files changed (29) hide show
  1. package/dist/components/sandbox/Sandbox.js +3 -2
  2. package/dist/components/sandbox/api.js +4 -28
  3. package/dist/components/sandbox/guardian/app-layout-no-sidebar.js +6 -3
  4. package/dist/components/sandbox/guardian/demo/guardian-demo.js +2 -2
  5. package/dist/components/sandbox/guardian/demo/left-view.js +17 -5
  6. package/dist/components/sandbox/guardian/guardian-component.js +7 -5
  7. package/dist/components/sandbox/guardian/guardian-playground.js +2 -2
  8. package/dist/components/sandbox/guardian/guardian-style-wrapper.js +17 -3
  9. package/dist/components/sandbox/guardian/hooks/use-sandbox-url-loader.js +0 -20
  10. package/dist/components/sandbox/guardian/right-view/pill-file-selector.js +13 -0
  11. package/dist/components/sandbox/guardian/right-view/right-panel-view.js +17 -4
  12. package/dist/components/sandbox/guardian/right-view/right-top-down-view.js +42 -50
  13. package/dist/components/sandbox/guardian/ui/ai-loader.js +56 -13
  14. package/dist/components/sandbox/guardian/ui/download-and-open-buttons.js +117 -0
  15. package/dist/components/sandbox/guardian/ui/markdown/code-group/code-block.js +33 -66
  16. package/dist/components/sandbox/guardian/ui/markdown.js +15 -10
  17. package/dist/components/sandbox/guardian/utils.js +1 -0
  18. package/dist/components/sandbox/sandbox-home/SandboxCard.js +14 -8
  19. package/dist/components/sandbox/sandbox-home/SandboxHome.js +22 -81
  20. package/dist/components/sandbox/sandbox-home/SearchBar.js +1 -1
  21. package/dist/components/ui/skeleton.js +18 -0
  22. package/dist/index.d.ts +32 -438
  23. package/dist/index.es.js +23944 -23548
  24. package/dist/index.standalone.umd.js +13 -13
  25. package/dist/lib/api-client.js +47 -5
  26. package/dist/lib/generated-css.js +1 -1
  27. package/dist/sdk.css +1 -1
  28. package/dist/tailwind.css +1 -1
  29. 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
- // const {theme} = useTheme();
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(SyntaxHighlighter, { language: language || "javascript", style: isDark ? darkTheme : lightTheme, customStyle: {
64
- margin: 0,
65
- padding: "1rem 1.25rem",
66
- background: "transparent",
67
- fontSize: "0.875rem",
68
- lineHeight: "1.5rem",
69
- }, showLineNumbers: false, wrapLines: true }, code || "")));
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 rounded-lg border border-zinc-200 bg-white shadow-sm dark:border-zinc-800 dark:bg-zinc-950", className) },
73
- title && (React.createElement("div", { className: "flex items-center justify-between px-4 py-2 border-b border-zinc-200 dark:border-zinc-800 bg-zinc-50/80 dark:bg-zinc-900/80" },
74
- React.createElement("span", { className: "text-sm text-[var(--colors-primary)] font-medium" }, language || title),
75
- React.createElement("button", { onClick: handleCopy, className: cn("p-1 transition-colors", copied
76
- ? "text-green-500 dark:text-green-400"
77
- : "text-zinc-500 hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-zinc-200"), "aria-label": "Copy code" },
78
- React.createElement(Copy, { className: "h-4 w-4" })))),
79
- React.createElement("div", { className: "px-4 py-3 overflow-x-auto" },
80
- React.createElement(SyntaxHighlighter, { language: language || "javascript", style: isDark ? darkTheme : lightTheme, customStyle: {
81
- margin: 0,
82
- padding: 0,
83
- background: "transparent",
84
- fontSize: "0.875rem",
85
- lineHeight: "1.5rem",
86
- }, showLineNumbers: false, wrapLines: true }, code || ""))));
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, themecolor, className } = _a, props = __rest(_a, ["children", "filepath", "filerange", "title", "description", "themecolor", "className"]);
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 provided themecolor from HTML attribute, or fall back to component's themeColor prop, or default to blue
98
- const color = themecolor || themeColor || "#3b82f6";
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, themecolor, giturl, className } = _a, props = __rest(_a, ["children", "downloadurl", "themecolor", "giturl", "className"]);
147
- if (!downloadurl || !themecolor) {
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: themecolor, gitUrl: giturl }),
156
+ React.createElement(DownloadAndOpenButtons, { downloadUrl: downloadurl, themeColor: effectiveThemeColor, gitUrl: effectiveGitUrl, browserUrl: browserUrl }),
152
157
  children));
153
158
  };
154
159
  const sharedComponents = {
@@ -69,6 +69,7 @@ export function buildGuardianConfig(useCases, options) {
69
69
  themeColor: (_a = fw.themeColor) !== null && _a !== void 0 ? _a : uc.themeColor,
70
70
  variant,
71
71
  hasPreview: uc.hasPreview,
72
+ gitUrl: fw.gitUrl,
72
73
  };
73
74
  }
74
75
  result[uc.id] = useCaseConfig;
@@ -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
- window.location.href = `/sample/${sandboxId}`;
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: `w-full h-full bg-gradient-to-br ${gradient} flex items-center justify-center` },
55
- React.createElement("div", { className: "w-full px-6 space-y-2" },
56
- React.createElement("div", { className: "h-2 bg-white/20 rounded w-1/2 mx-auto" }),
57
- React.createElement("div", { className: "h-1.5 bg-white/10 rounded w-3/4 mx-auto" }),
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
- // Default sandboxes array - will be replaced with API call in the future
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
- * Get the base URL for API requests
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 || content.id.toString(),
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, baseUrl, sandboxes, }) => {
106
- const [loading, setLoading] = useState(false);
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
- // Fall back to default sandboxes on error
134
- setFetchedSandboxes(DEFAULT_SANDBOXES);
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, baseUrl, sandboxes]);
142
- // Use provided sandboxes, fetched sandboxes, or default array
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: "col-span-full text-center py-16" },
164
- React.createElement("p", { className: "text-gray-500 text-lg" }, "Loading sandboxes..."))) : error && !sandboxes ? (React.createElement("div", { className: "col-span-full text-center py-16" },
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" }, "Falling back to default sandboxes."))) : (React.createElement("div", { className: "grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-4" }, filteredSandboxes && filteredSandboxes.length > 0 ? (filteredSandboxes
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 templates...", }) => {
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 };