@sampleapp.ai/sdk 1.0.42 → 1.0.43

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.
@@ -1,11 +1,49 @@
1
1
  "use client";
2
- import React, { useEffect, useState } from "react";
3
- import { fetchSandboxConfig } from "./api";
2
+ import React, { useEffect, useState, useCallback } from "react";
3
+ import { fetchSandboxConfigWithContent, buildConfigFromContent, } from "./api";
4
4
  import GuardianPlayground from "./guardian/guardian-playground";
5
5
  import { GuardianProvider } from "./guardian/context/guardian-context";
6
6
  import { VmProvider } from "./guardian/context/vm-context";
7
- import { buildGuardianConfig } from "./guardian/utils";
8
7
  import { Skeleton } from "../ui/skeleton";
8
+ import { useTreeSelector } from "../../hooks/use-tree-selector";
9
+ /**
10
+ * Inner component that uses the tree selector hook
11
+ * Separated to ensure hooks are called after data is loaded
12
+ */
13
+ function SandboxInner({ configWithContent, apiKey, env, themeColor, theme, config, }) {
14
+ var _a;
15
+ const [currentConfig, setCurrentConfig] = useState(configWithContent.legacyConfig);
16
+ // Handle selection changes - update the config when user selects different options
17
+ const handleSelectionChange = useCallback((selection, content) => {
18
+ if (content) {
19
+ const newConfig = buildConfigFromContent(content);
20
+ // Apply theme color override if provided
21
+ if (themeColor) {
22
+ newConfig.themeColor = themeColor;
23
+ newConfig.useCases = newConfig.useCases.map((uc) => (Object.assign(Object.assign({}, uc), { themeColor: themeColor, frameworks: uc.frameworks.map((fw) => (Object.assign(Object.assign({}, fw), { themeColor: themeColor }))) })));
24
+ }
25
+ setCurrentConfig(newConfig);
26
+ }
27
+ }, [themeColor]);
28
+ // Use the tree selector hook for managing selection state
29
+ const { sections, selectOption, currentContent } = useTreeSelector({
30
+ config: configWithContent.techStackConfig,
31
+ allContent: configWithContent.allContent,
32
+ initialPath: configWithContent.initialSelectionPath,
33
+ onSelectionChange: handleSelectionChange,
34
+ });
35
+ // Get the first use case and framework from current config
36
+ const firstUseCase = currentConfig.useCases[0];
37
+ const firstFramework = (_a = firstUseCase === null || firstUseCase === void 0 ? void 0 : firstUseCase.frameworks[0]) === null || _a === void 0 ? void 0 : _a.key;
38
+ if (!firstUseCase || !firstFramework) {
39
+ return (React.createElement("div", { className: "flex items-center justify-center h-screen bg-white dark:bg-black" },
40
+ React.createElement("div", { className: "text-red-500" }, "No use cases or frameworks found")));
41
+ }
42
+ return (React.createElement(GuardianProvider, null,
43
+ React.createElement(VmProvider, null,
44
+ React.createElement("div", { className: "h-screen w-screen bg-white dark:bg-black" },
45
+ React.createElement(GuardianPlayground, { sandboxConfig: currentConfig, sections: sections, onSelect: selectOption, useCase: firstUseCase.id, framework: firstFramework, isFrame: config === null || config === void 0 ? void 0 : config.isFrame, apiKey: apiKey, env: env, chatUid: currentConfig.chatUid, theme: theme })))));
46
+ }
9
47
  /**
10
48
  * Sandbox component - simplified API for embedding sandboxes
11
49
  *
@@ -33,14 +71,13 @@ import { Skeleton } from "../ui/skeleton";
33
71
  * />
34
72
  * ```
35
73
  */
36
- export default function Sandbox({ apiKey, sandboxId, env, themeColor, theme = "dark", }) {
37
- var _a;
74
+ export default function Sandbox({ apiKey, sandboxId, env, themeColor, theme = "dark", config, }) {
38
75
  // Validate apiKey immediately
39
76
  if (!apiKey || apiKey.trim() === "") {
40
77
  return (React.createElement("div", { className: "flex items-center justify-center h-screen bg-white dark:bg-black" },
41
78
  React.createElement("div", { className: "text-red-500" }, "Error: apiKey is required. Please provide a valid API key.")));
42
79
  }
43
- const [config, setConfig] = useState(null);
80
+ const [configWithContent, setConfigWithContent] = useState(null);
44
81
  const [loading, setLoading] = useState(true);
45
82
  const [error, setError] = useState(null);
46
83
  useEffect(() => {
@@ -49,14 +86,15 @@ export default function Sandbox({ apiKey, sandboxId, env, themeColor, theme = "d
49
86
  try {
50
87
  setLoading(true);
51
88
  setError(null);
52
- const fetchedConfig = await fetchSandboxConfig(apiKey, sandboxId);
89
+ const fetchedConfig = await fetchSandboxConfigWithContent(apiKey, sandboxId);
53
90
  if (!cancelled) {
54
91
  // Override theme color if provided
55
92
  if (themeColor) {
56
- fetchedConfig.themeColor = themeColor;
57
- fetchedConfig.useCases = fetchedConfig.useCases.map((uc) => (Object.assign(Object.assign({}, uc), { themeColor: themeColor, frameworks: uc.frameworks.map((fw) => (Object.assign(Object.assign({}, fw), { themeColor: themeColor }))) })));
93
+ fetchedConfig.legacyConfig.themeColor = themeColor;
94
+ fetchedConfig.legacyConfig.useCases =
95
+ fetchedConfig.legacyConfig.useCases.map((uc) => (Object.assign(Object.assign({}, uc), { themeColor: themeColor, frameworks: uc.frameworks.map((fw) => (Object.assign(Object.assign({}, fw), { themeColor: themeColor }))) })));
58
96
  }
59
- setConfig(fetchedConfig);
97
+ setConfigWithContent(fetchedConfig);
60
98
  setLoading(false);
61
99
  }
62
100
  }
@@ -82,25 +120,8 @@ export default function Sandbox({ apiKey, sandboxId, env, themeColor, theme = "d
82
120
  "Error: ",
83
121
  error)));
84
122
  }
85
- if (!config) {
123
+ if (!configWithContent) {
86
124
  return null;
87
125
  }
88
- // Build the nested config from use cases
89
- const nestedConfig = buildGuardianConfig(config.useCases, {
90
- playgroundUid: sandboxId,
91
- playgroundLogo: config.playgroundLogo || (React.createElement("div", { className: "text-zinc-900 dark:text-white font-bold text-xl" }, config.name)),
92
- });
93
- // Get the first use case and framework as defaults
94
- const firstUseCase = config.useCases[0];
95
- const firstFramework = (_a = firstUseCase === null || firstUseCase === void 0 ? void 0 : firstUseCase.frameworks[0]) === null || _a === void 0 ? void 0 : _a.key;
96
- if (!firstUseCase || !firstFramework) {
97
- return (React.createElement("div", { className: "flex items-center justify-center h-screen bg-white dark:bg-black" },
98
- React.createElement("div", { className: "text-red-500" }, "No use cases or frameworks found")));
99
- }
100
- // TODO: Pass env variables to the container runtime
101
- // This will be implemented when integrating with the container technology
102
- return (React.createElement(GuardianProvider, null,
103
- React.createElement(VmProvider, null,
104
- React.createElement("div", { className: "h-screen w-screen bg-white dark:bg-black" },
105
- React.createElement(GuardianPlayground, { nestedConfig: nestedConfig, useCase: firstUseCase.id, framework: firstFramework, isFrame: false, apiKey: apiKey, env: env, chatUid: config.chatUid, theme: theme })))));
126
+ return (React.createElement(SandboxInner, { configWithContent: configWithContent, apiKey: apiKey, env: env, themeColor: themeColor, theme: theme, config: config }));
106
127
  }
@@ -15,10 +15,82 @@ function createDownloadLink(downloadEndpoint) {
15
15
  document.body.removeChild(link);
16
16
  }
17
17
  /**
18
- * Fetches sandbox configuration from the API
18
+ * Fetches sandbox configuration with all content for upfront loading
19
+ * This enables instant switching between tech stack options without API calls
20
+ *
21
+ * @param apiKey - API key from SandboxProps
22
+ * @param sandboxId - Sandbox content UID (maps to sandbox_content_uid)
23
+ * @returns Extended sandbox configuration with all content
24
+ */
25
+ export async function fetchSandboxConfigWithContent(apiKey, sandboxId) {
26
+ const client = createApiClient({ apiKey });
27
+ // 1. Fetch the initially requested sandbox content
28
+ const initialContent = await client.sandboxContent.getPublic(sandboxId);
29
+ // 2. Fetch the use case to get tech_stack_config
30
+ const useCase = await client.sandboxUseCase.getById(initialContent.use_case_id);
31
+ // 3. Fetch ALL sandbox content for this use case (for local filtering)
32
+ const allContent = await client.sandboxContent.getByUseCaseId(initialContent.use_case_id);
33
+ // 4. Build legacy config for backward compatibility
34
+ const legacyConfig = buildLegacyConfig(initialContent);
35
+ return {
36
+ useCase,
37
+ techStackConfig: useCase.tech_stack_config,
38
+ allContent,
39
+ initialContent,
40
+ initialSelectionPath: initialContent.selection_path || [],
41
+ legacyConfig,
42
+ };
43
+ }
44
+ /**
45
+ * Build legacy SandboxConfig from SandboxContent for backward compatibility
46
+ */
47
+ function buildLegacyConfig(sandboxContent) {
48
+ const frameworkKey = sandboxContent.framework || Framework.NEXTJS;
49
+ const generatedCode = sandboxContent.generated_code || {};
50
+ const completeCodeZipFile = "";
51
+ return {
52
+ id: sandboxContent.uid,
53
+ name: sandboxContent.uid,
54
+ description: sandboxContent.markdown || "",
55
+ themeColor: "#3b82f6",
56
+ hasPreview: sandboxContent.has_preview,
57
+ chatUid: sandboxContent.chat_uid || "",
58
+ useCases: [
59
+ {
60
+ id: sandboxContent.uid,
61
+ name: sandboxContent.uid,
62
+ description: sandboxContent.markdown || "",
63
+ themeColor: "#3b82f6",
64
+ hasPreview: sandboxContent.has_preview,
65
+ frameworks: [
66
+ {
67
+ key: frameworkKey,
68
+ browserUrl: sandboxContent.published_url || "",
69
+ sandboxUid: sandboxContent.uid,
70
+ codeZipFile: Object.keys(generatedCode).length > 0 ? generatedCode : "",
71
+ completeCodeZipFile,
72
+ useVm: sandboxContent.is_vm_enabled || false,
73
+ CustomConsole: sandboxContent.markdown || "",
74
+ GuideView: "Default Guide",
75
+ gitUrl: sandboxContent.github_url || undefined,
76
+ },
77
+ ],
78
+ },
79
+ ],
80
+ };
81
+ }
82
+ /**
83
+ * Build SandboxConfig from any SandboxContent (for switching between selections)
84
+ */
85
+ export function buildConfigFromContent(content) {
86
+ return buildLegacyConfig(content);
87
+ }
88
+ /**
89
+ * Fetches sandbox configuration from the API (legacy method)
19
90
  * @param apiKey - API key from SandboxProps
20
91
  * @param sandboxId - Sandbox content UID (maps to sandbox_content_uid)
21
92
  * @returns Sandbox configuration
93
+ * @deprecated Use fetchSandboxConfigWithContent for tree-based selection
22
94
  */
23
95
  export async function fetchSandboxConfig(apiKey, sandboxId) {
24
96
  const client = createApiClient({
@@ -24,12 +24,12 @@ export default function GuardianDemo({ CustomConsole, GuideView, browserUrl, pla
24
24
  }, [setCurrentView]);
25
25
  // If isFrame, just return RightView directly
26
26
  if (isFrame) {
27
- return (React.createElement(RightView, { reloadCounter: reloadCounter, overlayStage: overlayStage, browserUrl: browserUrl, useVm: useVm, codeZipFile: codeZipFile, hasPreview: hasPreview, variant: variant, themeColor: themeColor, completeCodeZipFile: completeCodeZipFile, isGuardian: isGuardian, isBrowserMaximized: isBrowserMaximized, isBrowserUrlReady: isBrowserUrlReady, sandboxId: sandboxId, apiKey: apiKey, sandboxError: sandboxError }));
27
+ return (React.createElement(RightView, { reloadCounter: reloadCounter, overlayStage: overlayStage, browserUrl: browserUrl, useVm: useVm, codeZipFile: codeZipFile, hasPreview: hasPreview, variant: variant, themeColor: themeColor, completeCodeZipFile: completeCodeZipFile, isGuardian: isGuardian, isBrowserMaximized: isBrowserMaximized, isBrowserUrlReady: isBrowserUrlReady, isFrame: isFrame, sandboxId: sandboxId, apiKey: apiKey, sandboxError: sandboxError }));
28
28
  }
29
29
  // If browser is maximized, render RightView at full size without the console
30
30
  if (isBrowserMaximized && isGuardian) {
31
31
  return (React.createElement("div", { className: "w-full h-full rounded-2xl overflow-hidden" },
32
- React.createElement(RightView, { reloadCounter: reloadCounter, overlayStage: overlayStage, browserUrl: browserUrl, useVm: useVm, codeZipFile: codeZipFile, hasPreview: hasPreview, variant: variant, themeColor: themeColor, completeCodeZipFile: completeCodeZipFile, isGuardian: isGuardian, isBrowserMaximized: true, isBrowserUrlReady: isBrowserUrlReady, sandboxId: sandboxId, apiKey: apiKey, sandboxError: sandboxError })));
32
+ React.createElement(RightView, { reloadCounter: reloadCounter, overlayStage: overlayStage, browserUrl: browserUrl, useVm: useVm, codeZipFile: codeZipFile, hasPreview: hasPreview, variant: variant, themeColor: themeColor, completeCodeZipFile: completeCodeZipFile, isGuardian: isGuardian, isBrowserMaximized: true, isBrowserUrlReady: isBrowserUrlReady, isFrame: isFrame, sandboxId: sandboxId, apiKey: apiKey, sandboxError: sandboxError })));
33
33
  }
34
- return (React.createElement(ConsoleWithApp, { containerClassName: "", console: React.createElement(ConsoleWithGuide, { CustomConsole: CustomConsole, GuideView: GuideView, playgroundUid: playgroundUid, onReloadPreview: handleReloadPreview, onStageChange: handleStageChange, codeZipFile: codeZipFile, themeColor: themeColor, browserUrl: browserUrl, gitUrl: gitUrl, sandboxId: sandboxId, apiKey: apiKey }), app: React.createElement(RightView, { reloadCounter: reloadCounter, overlayStage: overlayStage, browserUrl: browserUrl, useVm: useVm, codeZipFile: codeZipFile, hasPreview: hasPreview, variant: variant, themeColor: themeColor, completeCodeZipFile: completeCodeZipFile, isGuardian: isGuardian, isBrowserMaximized: isBrowserMaximized, isBrowserUrlReady: isBrowserUrlReady, sandboxId: sandboxId, apiKey: apiKey, sandboxError: sandboxError }) }));
34
+ return (React.createElement(ConsoleWithApp, { containerClassName: "", console: React.createElement(ConsoleWithGuide, { CustomConsole: CustomConsole, GuideView: GuideView, playgroundUid: playgroundUid, onReloadPreview: handleReloadPreview, onStageChange: handleStageChange, codeZipFile: codeZipFile, themeColor: themeColor, browserUrl: browserUrl, gitUrl: gitUrl, sandboxId: sandboxId, apiKey: apiKey }), app: React.createElement(RightView, { reloadCounter: reloadCounter, overlayStage: overlayStage, browserUrl: browserUrl, useVm: useVm, codeZipFile: codeZipFile, hasPreview: hasPreview, variant: variant, themeColor: themeColor, completeCodeZipFile: completeCodeZipFile, isGuardian: isGuardian, isBrowserMaximized: isBrowserMaximized, isBrowserUrlReady: isBrowserUrlReady, isFrame: isFrame, sandboxId: sandboxId, apiKey: apiKey, sandboxError: sandboxError }) }));
35
35
  }
@@ -8,7 +8,7 @@ import { useSandboxUrlLoader } from "./hooks/use-sandbox-url-loader";
8
8
  import { useFrameMessages } from "./hooks/use-frame-messages";
9
9
  import { useVmContext } from "./context/vm-context";
10
10
  import { cn } from "../../../lib/utils";
11
- export default function GuardianComponent({ demoOptions, frameworkOptions, firstFrameworkByUseCase, currentFramework, currentUseCase, CustomConsole, GuideView, playgroundLogo, playgroundUid, browserUrl, useVm, sandboxUid, codeZipFile, completeCodeZipFile, variant, themeColor, hasPreview = true, isFrame = false, apiKey, env, chatUid, hideHeader = false, // Hardcoded to true by default, not exposed in Sandbox.tsx
11
+ export default function GuardianComponent({ sections, onSelect, currentFramework, currentUseCase, CustomConsole, GuideView, playgroundLogo, playgroundUid, browserUrl, useVm, sandboxUid, codeZipFile, completeCodeZipFile, variant, themeColor, hasPreview = true, isFrame = false, apiKey, env, chatUid, hideHeader = false, // Hardcoded to true by default, not exposed in Sandbox.tsx
12
12
  gitUrl, }) {
13
13
  const { previewUrl, setPreviewUrl } = useGuardianContext();
14
14
  const { vmResolution } = useVmContext();
@@ -16,7 +16,9 @@ gitUrl, }) {
16
16
  // Build startSandboxConfig for use-sandbox-url-loader
17
17
  // apiKey is always included (guaranteed by parent validation)
18
18
  // Include chatUid and env when browserUrl is missing (needed for startSandbox API call)
19
- const startSandboxConfig = Object.assign({ apiKey }, (!browserUrl && chatUid ? { env, chatUid } : {}));
19
+ // Include hasPreview to skip env/chatUid validation when no preview is needed
20
+ const startSandboxConfig = Object.assign({ apiKey,
21
+ hasPreview }, (!browserUrl && chatUid ? { env, chatUid } : {}));
20
22
  // Determine if browserUrl is ready to use immediately (from published_url)
21
23
  // If browserUrl exists, it's ready (startSandboxConfig may still exist with just apiKey for VM calls)
22
24
  const isBrowserUrlReady = !!browserUrl;
@@ -80,7 +82,7 @@ gitUrl, }) {
80
82
  return (React.createElement("div", { className: "h-[100vh]" },
81
83
  React.createElement(GuardianDemo, { CustomConsole: CustomConsole, GuideView: GuideView, browserUrl: previewUrl, playgroundUid: playgroundUid, useVm: useVm, codeZipFile: codeZipFile, completeCodeZipFile: completeCodeZipFile, variant: variant, themeColor: themeColor, hasPreview: hasPreview, isFrame: isFrame, isGuardian: true, gitUrl: gitUrl, isBrowserUrlReady: isBrowserUrlReady, sandboxId: sandboxUid, apiKey: apiKey, sandboxError: sandboxError })));
82
84
  }
83
- return (React.createElement(AppLayoutNoSidebar, { header: !hideHeader ? (React.createElement(Header, { demoOptions: demoOptions, frameworkOptions: frameworkOptions, currentFramework: currentFramework, currentUseCase: currentUseCase, playgroundLogo: playgroundLogo, firstFrameworkByUseCase: firstFrameworkByUseCase, themecolor: themeColor })) : undefined, hasBodyPadding: false },
85
+ return (React.createElement(AppLayoutNoSidebar, { header: !hideHeader ? (React.createElement(Header, { sections: sections, onSelect: onSelect, playgroundLogo: playgroundLogo, themecolor: themeColor })) : undefined, hasBodyPadding: false },
84
86
  React.createElement("div", { className: cn("flex-1 min-h-0 flex flex-col", hideHeader && "pt-4") },
85
87
  React.createElement(GuardianDemo, { CustomConsole: CustomConsole, GuideView: GuideView, browserUrl: previewUrl, playgroundUid: playgroundUid, useVm: useVm, codeZipFile: codeZipFile, completeCodeZipFile: completeCodeZipFile, variant: variant, themeColor: themeColor, hasPreview: hasPreview, isFrame: isFrame, isGuardian: true, gitUrl: gitUrl, isBrowserUrlReady: isBrowserUrlReady, sandboxId: sandboxUid, apiKey: apiKey, sandboxError: sandboxError }))));
86
88
  }
@@ -3,17 +3,21 @@ import React from "react";
3
3
  import GuardianComponent from "./guardian-component";
4
4
  import { useFrameParams } from "./hooks/use-frame-params";
5
5
  import { GuardianStyleWrapper } from "./guardian-style-wrapper";
6
+ import { buildGuardianConfig } from "./utils";
6
7
  /**
7
- * Guardian Playground component with nested config structure.
8
- * Expects config[useCaseId].frameworks[frameworkKey] structure.
9
- *
10
- * When isFrame=true, reads framework from URL params client-side.
8
+ * Guardian Playground component with tree-based tech stack selection.
9
+ * Uses sections from useTreeSelector for dynamic header rendering.
11
10
  */
12
- export default function GuardianPlayground({ nestedConfig, useCase, framework: serverFramework, isFrame = false, apiKey, env, chatUid, theme = "dark", }) {
11
+ export default function GuardianPlayground({ sandboxConfig, sections, onSelect, useCase, framework: serverFramework, isFrame = false, apiKey, env, chatUid, theme = "dark", }) {
13
12
  var _a;
14
13
  // When in frame mode, allow URL params to override framework
15
14
  const frameParams = useFrameParams();
16
15
  const framework = isFrame && frameParams.framework ? frameParams.framework : serverFramework;
16
+ // Build the nested config from use cases
17
+ const nestedConfig = buildGuardianConfig(sandboxConfig.useCases, {
18
+ playgroundUid: sandboxConfig.id,
19
+ playgroundLogo: sandboxConfig.playgroundLogo || (React.createElement("div", { className: "text-zinc-900 dark:text-white font-bold text-xl" }, sandboxConfig.name)),
20
+ });
17
21
  const useCaseConfig = nestedConfig[useCase];
18
22
  if (!useCaseConfig) {
19
23
  return (React.createElement("div", { className: "flex items-center justify-center h-screen" },
@@ -32,14 +36,6 @@ export default function GuardianPlayground({ nestedConfig, useCase, framework: s
32
36
  useCase,
33
37
  "\"")));
34
38
  }
35
- // Build a map of use case ID to first available framework key
36
- const firstFrameworkByUseCase = {};
37
- Object.entries(nestedConfig).forEach(([useCaseId, uc]) => {
38
- const firstFrameworkKey = Object.keys(uc.frameworks)[0];
39
- if (firstFrameworkKey) {
40
- firstFrameworkByUseCase[useCaseId] = firstFrameworkKey;
41
- }
42
- });
43
39
  return (React.createElement(GuardianStyleWrapper, { themeColor: frameworkConfig.themeColor, theme: theme },
44
- React.createElement(GuardianComponent, { demoOptions: frameworkConfig.demoOptions, frameworkOptions: frameworkConfig.frameworkOptions, firstFrameworkByUseCase: firstFrameworkByUseCase, currentFramework: frameworkConfig.currentFramework, CustomConsole: frameworkConfig.CustomConsole, GuideView: frameworkConfig.GuideView, playgroundUid: frameworkConfig.playgroundUid, browserUrl: (_a = frameParams.iframeUrl) !== null && _a !== void 0 ? _a : frameworkConfig.browserUrl, useVm: frameworkConfig.useVm, playgroundLogo: frameworkConfig.playgroundLogo, sandboxUid: frameworkConfig.sandboxUid, name: frameworkConfig.name, description: frameworkConfig.description, codeZipFile: frameworkConfig.codeZipFile, completeCodeZipFile: frameworkConfig.completeCodeZipFile, variant: frameworkConfig.variant, themeColor: frameworkConfig.themeColor, hasPreview: frameworkConfig.hasPreview, currentUseCase: useCase, isFrame: isFrame, apiKey: apiKey, env: env, chatUid: chatUid, gitUrl: frameworkConfig.gitUrl, hideHeader: false })));
40
+ React.createElement(GuardianComponent, { sections: sections, onSelect: onSelect, currentFramework: frameworkConfig.currentFramework, CustomConsole: frameworkConfig.CustomConsole, GuideView: frameworkConfig.GuideView, playgroundUid: frameworkConfig.playgroundUid, browserUrl: (_a = frameParams.iframeUrl) !== null && _a !== void 0 ? _a : frameworkConfig.browserUrl, useVm: frameworkConfig.useVm, playgroundLogo: frameworkConfig.playgroundLogo, sandboxUid: frameworkConfig.sandboxUid, name: frameworkConfig.name, description: frameworkConfig.description, codeZipFile: frameworkConfig.codeZipFile, completeCodeZipFile: frameworkConfig.completeCodeZipFile, variant: frameworkConfig.variant, themeColor: frameworkConfig.themeColor, hasPreview: frameworkConfig.hasPreview, currentUseCase: useCase, isFrame: isFrame, apiKey: apiKey, env: env, chatUid: chatUid, gitUrl: frameworkConfig.gitUrl, hideHeader: false })));
45
41
  }
@@ -1,53 +1,40 @@
1
1
  "use client";
2
2
  import React from "react";
3
- export default function Header({ demoOptions, frameworkOptions, firstFrameworkByUseCase, currentFramework, playgroundLogo, currentUseCase, themecolor, }) {
4
- var _a, _b;
5
- // Derive values directly from props (which come from the URL)
6
- const selectedUseCase = currentUseCase || ((_a = demoOptions[0]) === null || _a === void 0 ? void 0 : _a.value);
7
- const selectedFramework = currentFramework || ((_b = frameworkOptions[0]) === null || _b === void 0 ? void 0 : _b.value);
8
- const sections = [
9
- // {
10
- // id: "useCase",
11
- // label: "Use Case:",
12
- // options: demoOptions.map((opt) => ({
13
- // ...opt,
14
- // href: `/${opt.value}?framework=${firstFrameworkByUseCase?.[opt.value] || frameworkOptions[0]?.value
15
- // }`,
16
- // })),
17
- // },
18
- {
19
- id: "framework",
20
- label: "Framework:",
21
- options: frameworkOptions.map((opt) => (Object.assign(Object.assign({}, opt), { href: `/${selectedUseCase}?framework=${opt.value}` }))),
22
- },
23
- ];
3
+ /**
4
+ * Header component for dynamic tech stack selection
5
+ *
6
+ * Uses sections from useTreeSelector to render selector buttons
7
+ * based on the tech_stack_config tree structure.
8
+ */
9
+ export default function Header({ sections, onSelect, playgroundLogo, themecolor }) {
10
+ console.log("playgroundLogo", playgroundLogo);
24
11
  return (React.createElement("div", { className: "w-full sticky top-0 z-50 px-4 lg:px-8 backdrop-blur-md" },
25
- React.createElement("div", { className: "flex flex-wrap items-center justify-between" },
12
+ React.createElement("div", { className: "flex flex-wrap items-center justify-between py-2" },
26
13
  React.createElement("div", { className: "flex flex-wrap items-center gap-6" }, sections.map((section) => {
27
- const currentValue = section.id === "useCase" ? selectedUseCase : selectedFramework;
28
- return (React.createElement("div", { key: section.id, className: "flex items-center flex-wrap gap-3" },
14
+ var _a;
15
+ return (React.createElement("div", { key: section.nodeType, className: "flex items-center flex-wrap gap-3" },
29
16
  React.createElement("span", { className: "text-sm font-semibold text-gray-600 dark:text-gray-400 whitespace-nowrap" }, section.label),
30
17
  React.createElement("div", { className: "hidden md:block" },
31
18
  React.createElement("div", { className: "flex items-center gap-2" }, section.options.map((option) => {
32
- const isSelected = currentValue === option.value;
33
- return (React.createElement("button", { key: option.value, onClick: () => {
34
- if (typeof window !== "undefined") {
35
- window.location.href = option.href;
19
+ const isSelected = option.isSelected;
20
+ const isDisabled = option.hasContent === false;
21
+ return (React.createElement("button", { key: option.key, onClick: () => {
22
+ if (!isDisabled) {
23
+ onSelect(section.nodeType, option.key);
36
24
  }
37
- }, className: `inline-flex items-center px-3 py-1.5 text-xs font-medium rounded-md border transition-all cursor-pointer active:scale-[0.98] ${isSelected
25
+ }, disabled: isDisabled, className: `inline-flex items-center px-3 py-1.5 text-xs font-medium rounded-md border transition-all cursor-pointer active:scale-[0.98] ${isSelected
38
26
  ? "bg-white dark:bg-zinc-900 hover:bg-zinc-100 dark:hover:bg-zinc-800"
39
- : "bg-white dark:bg-zinc-900 border-zinc-300 dark:border-zinc-700 text-zinc-900 dark:text-zinc-100 hover:bg-zinc-100 dark:hover:bg-zinc-800"}`, style: isSelected && themecolor ? {
40
- borderColor: themecolor,
41
- color: themecolor,
42
- } : undefined }, option.label));
27
+ : "bg-white dark:bg-zinc-900 border-zinc-300 dark:border-zinc-700 text-zinc-900 dark:text-zinc-100 hover:bg-zinc-100 dark:hover:bg-zinc-800"} ${isDisabled ? "opacity-50 cursor-not-allowed" : ""}`, style: isSelected && themecolor
28
+ ? {
29
+ borderColor: themecolor,
30
+ color: themecolor
31
+ }
32
+ : undefined }, option.label));
43
33
  }))),
44
34
  React.createElement("div", { className: "block md:hidden relative" },
45
- React.createElement("select", { value: currentValue, onChange: (e) => {
46
- const selectedOption = section.options.find((opt) => opt.value === e.target.value);
47
- if (selectedOption === null || selectedOption === void 0 ? void 0 : selectedOption.href) {
48
- window.location.href = selectedOption.href;
49
- }
50
- }, className: "appearance-none bg-white dark:bg-zinc-900 border border-zinc-300 dark:border-zinc-700 rounded-md px-3 py-1.5 pr-8 text-xs font-medium text-zinc-900 dark:text-zinc-100 focus:outline-none focus:ring-2 focus:ring-primary dark:focus:ring-primary-light focus:border-transparent transition-all hover:bg-zinc-100 dark:hover:bg-zinc-800" }, section.options.map((option) => (React.createElement("option", { key: option.value, value: option.value }, option.label)))),
35
+ React.createElement("select", { value: ((_a = section.options.find((o) => o.isSelected)) === null || _a === void 0 ? void 0 : _a.key) || "", onChange: (e) => {
36
+ onSelect(section.nodeType, e.target.value);
37
+ }, className: "appearance-none bg-white dark:bg-zinc-900 border border-zinc-300 dark:border-zinc-700 rounded-md px-3 py-1.5 pr-8 text-xs font-medium text-zinc-900 dark:text-zinc-100 focus:outline-none focus:ring-2 focus:ring-primary dark:focus:ring-primary-light focus:border-transparent transition-all hover:bg-zinc-100 dark:hover:bg-zinc-800" }, section.options.map((option) => (React.createElement("option", { key: option.key, value: option.key, disabled: option.hasContent === false }, option.label)))),
51
38
  React.createElement("svg", { "aria-hidden": "true", width: "16", height: "16", viewBox: "0 0 16 16", xmlns: "http://www.w3.org/2000/svg", className: "absolute right-2 top-1/2 -translate-y-1/2 pointer-events-none text-zinc-400" },
52
39
  React.createElement("path", { fillRule: "evenodd", clipRule: "evenodd", d: "M2.35 9.915a.875.875 0 0 1 1.235-.065L8 13.823l4.415-3.973a.875.875 0 0 1 1.17 1.3l-5 4.5a.873.873 0 0 1-1.17 0l-5-4.5a.875.875 0 0 1-.065-1.235ZM7.415.35a.873.873 0 0 1 1.17 0l5 4.5a.875.875 0 1 1-1.17 1.3L8 2.177 3.585 6.15a.875.875 0 0 1-1.17-1.3l5-4.5Z", fill: "currentColor" })))));
53
40
  })))));
@@ -50,6 +50,12 @@ export function useSandboxUrlLoader(startSandboxConfig) {
50
50
  // Edge case: If browserUrl is empty but we have chatUid, call startSandbox first
51
51
  let targetUrl = browserUrl;
52
52
  if (!targetUrl || targetUrl.trim() === "") {
53
+ // If hasPreview is false, we don't need to start a container
54
+ // Just return empty string - no preview will be shown
55
+ if ((startSandboxConfig === null || startSandboxConfig === void 0 ? void 0 : startSandboxConfig.hasPreview) === false) {
56
+ setVmUrl(cacheKey, "");
57
+ return "";
58
+ }
53
59
  if (startSandboxConfig === null || startSandboxConfig === void 0 ? void 0 : startSandboxConfig.chatUid) {
54
60
  // When browserUrl (published_url) is empty, env is required to start sandbox
55
61
  if (!startSandboxConfig.env) {
@@ -58,7 +64,7 @@ export function useSandboxUrlLoader(startSandboxConfig) {
58
64
  // First, start the sandbox to get the container_url
59
65
  const sandboxResponse = await client.sdk.startSandbox({
60
66
  env: startSandboxConfig.env,
61
- chatUid: startSandboxConfig.chatUid,
67
+ chatUid: startSandboxConfig.chatUid
62
68
  });
63
69
  targetUrl = sandboxResponse.container_url;
64
70
  }
@@ -69,7 +75,7 @@ export function useSandboxUrlLoader(startSandboxConfig) {
69
75
  const data = await client.sdk.startVm({
70
76
  url: targetUrl,
71
77
  mode: "json",
72
- resolution: config.resolution,
78
+ resolution: config.resolution
73
79
  });
74
80
  if (!data.vncUrl) {
75
81
  console.error(`No vncUrl in response for ${sandboxUid}:`, data);
@@ -98,6 +104,12 @@ export function useSandboxUrlLoader(startSandboxConfig) {
98
104
  return browserUrl;
99
105
  }
100
106
  else if (startSandboxConfig) {
107
+ // If hasPreview is false, we don't need to start a container
108
+ // Just return empty string - no preview will be shown
109
+ if (startSandboxConfig.hasPreview === false) {
110
+ setVmUrl(cacheKey, "");
111
+ return "";
112
+ }
101
113
  // No published_url available - fall back to calling startSandbox API with chat_uid
102
114
  try {
103
115
  if (!startSandboxConfig.chatUid) {
@@ -116,7 +128,7 @@ export function useSandboxUrlLoader(startSandboxConfig) {
116
128
  const client = createApiClient({ apiKey });
117
129
  const response = await client.sdk.startSandbox({
118
130
  env: startSandboxConfig.env,
119
- chatUid: startSandboxConfig.chatUid,
131
+ chatUid: startSandboxConfig.chatUid
120
132
  });
121
133
  // Use the container_url from the response as the browserUrl
122
134
  const containerUrl = response.container_url;
@@ -161,6 +173,6 @@ export function useSandboxUrlLoader(startSandboxConfig) {
161
173
  return {
162
174
  loadSandboxUrl,
163
175
  getSandboxUrl,
164
- preloadSandboxUrls,
176
+ preloadSandboxUrls
165
177
  };
166
178
  }
@@ -1,5 +1,6 @@
1
1
  export { default as GuardianPlayground } from "./guardian-playground";
2
2
  export { default as GuardianComponent } from "./guardian-component";
3
+ export { default as Header } from "./header";
3
4
  export { VmProvider, useVmContext } from "./context/vm-context";
4
5
  export { GuardianProvider, useGuardianContext } from "./context/guardian-context";
5
6
  export * from "./types";
@@ -3,8 +3,8 @@ import React from "react";
3
3
  import { useGuardianContext } from "../context/guardian-context";
4
4
  import { cn } from "../../../../lib/utils";
5
5
  import { useFrameParams } from "../hooks/use-frame-params";
6
- import { FileCode, FileJson, FileText, Settings, Layout, Database, Image, Terminal, } from "lucide-react";
7
- import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, } from "../ui/tooltip";
6
+ import { FileCode, FileJson, FileText, Settings, Layout, Database, Image, Terminal } from "lucide-react";
7
+ import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "../ui/tooltip";
8
8
  const FILE_ICONS = {
9
9
  // JavaScript/React
10
10
  js: { icon: FileCode, color: "text-yellow-600" },
@@ -42,13 +42,13 @@ const FILE_ICONS = {
42
42
  svg: { icon: Image, color: "text-green-500" },
43
43
  ico: { icon: Image, color: "text-green-500" },
44
44
  // Default
45
- default: { icon: FileText, color: "text-zinc-500" },
45
+ default: { icon: FileText, color: "text-zinc-500" }
46
46
  };
47
47
  export default function PillFileSelector({ themeColor }) {
48
48
  const containerRef = React.useRef(null);
49
49
  const fileRefs = React.useRef({});
50
50
  const lastInitializedFilePath = React.useRef(null);
51
- const { generatedCode, updateCode, setLanguage, activeFilePath: contextActiveFilePath, setActiveFileName, setActiveFilePath, setActiveDependenciesToInstall, setActiveLineRange, filesEdited, } = useGuardianContext();
51
+ const { generatedCode, updateCode, setLanguage, activeFilePath: contextActiveFilePath, setActiveFileName, setActiveFilePath, setActiveDependenciesToInstall, setActiveLineRange, filesEdited } = useGuardianContext();
52
52
  // Read frame params - these override context when isFrame=true
53
53
  const frameParams = useFrameParams();
54
54
  // Use frame param overrides when in frame mode
@@ -94,7 +94,7 @@ export default function PillFileSelector({ themeColor }) {
94
94
  "vite-plugin-react-instrumentation.ts",
95
95
  "instrumentation-client.ts",
96
96
  // Global styles (often not needed in code editor)
97
- "globals.css",
97
+ "globals.css"
98
98
  ];
99
99
  const filteredFiles = fileEntries.filter(([filePath]) => {
100
100
  const fileName = filePath.split("/").pop() || filePath;
@@ -128,7 +128,7 @@ export default function PillFileSelector({ themeColor }) {
128
128
  activeButton.scrollIntoView({
129
129
  behavior: "instant",
130
130
  block: "nearest",
131
- inline: "center",
131
+ inline: "center"
132
132
  });
133
133
  }
134
134
  }, 50);
@@ -156,38 +156,88 @@ export default function PillFileSelector({ themeColor }) {
156
156
  window.addEventListener("frameParamsUpdated", handleFrameParamsUpdate);
157
157
  return () => window.removeEventListener("frameParamsUpdated", handleFrameParamsUpdate);
158
158
  }, [frameParams.isFrame, scrollActivePillIntoView]);
159
- // Load file from frame params when they change
159
+ // Load first file on mount when NOT in frame mode
160
160
  React.useEffect(() => {
161
- // Skip if not in frame mode or no file path specified
162
- if (!frameParams.isFrame || !frameParams.activeFilePath) {
161
+ // Skip if in frame mode (handled by separate effect)
162
+ if (frameParams.isFrame) {
163
163
  return;
164
164
  }
165
- // Skip if we've already initialized this exact file path
166
- if (lastInitializedFilePath.current === frameParams.activeFilePath) {
165
+ // Skip if generatedCode isn't loaded yet
166
+ if (!generatedCode || Object.keys(generatedCode).length === 0) {
167
+ return;
168
+ }
169
+ // Skip if already initialized
170
+ if (lastInitializedFilePath.current) {
167
171
  return;
168
172
  }
173
+ // Get filtered files (excluding certain files)
174
+ const fileEntries = Object.entries(generatedCode)
175
+ .filter(([filePath]) => {
176
+ const fileName = filePath.split("/").pop() || filePath;
177
+ return !excludedFiles.includes(fileName);
178
+ })
179
+ .sort(([a], [b]) => {
180
+ const aName = a.split("/").pop() || a;
181
+ const bName = b.split("/").pop() || b;
182
+ return aName.localeCompare(bName);
183
+ });
184
+ if (fileEntries.length === 0) {
185
+ return;
186
+ }
187
+ // Load the first file
188
+ const [firstFilePath, firstFileData] = fileEntries[0];
189
+ handleFileClick(firstFilePath, firstFileData);
190
+ lastInitializedFilePath.current = firstFilePath;
191
+ // eslint-disable-next-line react-hooks/exhaustive-deps
192
+ }, [frameParams.isFrame, generatedCode]);
193
+ // Load file from frame params when they change, or load first file if in frame mode
194
+ React.useEffect(() => {
169
195
  // Skip if generatedCode isn't loaded yet
170
196
  if (!generatedCode || Object.keys(generatedCode).length === 0) {
171
197
  return;
172
198
  }
199
+ // Get filtered files (excluding certain files)
200
+ const fileEntries = Object.entries(generatedCode).filter(([filePath]) => {
201
+ const fileName = filePath.split("/").pop() || filePath;
202
+ return !excludedFiles.includes(fileName);
203
+ });
204
+ // If not in frame mode, skip this effect
205
+ if (!frameParams.isFrame) {
206
+ return;
207
+ }
208
+ // Determine which file to load
209
+ let targetFilePath;
210
+ if (frameParams.activeFilePath) {
211
+ // Frame params specify a file - use it
212
+ targetFilePath = frameParams.activeFilePath;
213
+ }
214
+ else if (fileEntries.length > 0) {
215
+ // No file specified - load the first available file
216
+ const [firstFilePath] = fileEntries[0];
217
+ targetFilePath = firstFilePath;
218
+ }
219
+ // Skip if we've already initialized this exact file path
220
+ if (!targetFilePath || lastInitializedFilePath.current === targetFilePath) {
221
+ return;
222
+ }
173
223
  // Search by file_path property first (source of truth), then fallback to key lookup
174
224
  let fileData;
175
225
  let resolvedFilePath;
176
226
  // First, search through all code outputs to find one matching file_path
177
- const matchingCodeOutput = Object.values(generatedCode).find((output) => output.file_path === frameParams.activeFilePath);
227
+ const matchingCodeOutput = Object.values(generatedCode).find((output) => output.file_path === targetFilePath);
178
228
  if (matchingCodeOutput) {
179
229
  fileData = matchingCodeOutput;
180
230
  resolvedFilePath = matchingCodeOutput.file_path;
181
231
  }
182
232
  else {
183
233
  // Fallback: try to get from generatedCode by key
184
- if (generatedCode[frameParams.activeFilePath]) {
185
- fileData = generatedCode[frameParams.activeFilePath];
186
- resolvedFilePath = fileData.file_path || frameParams.activeFilePath;
234
+ if (generatedCode[targetFilePath]) {
235
+ fileData = generatedCode[targetFilePath];
236
+ resolvedFilePath = fileData.file_path || targetFilePath;
187
237
  }
188
238
  }
189
239
  if (fileData && resolvedFilePath) {
190
- lastInitializedFilePath.current = frameParams.activeFilePath; // Track initialized path
240
+ lastInitializedFilePath.current = targetFilePath; // Track initialized path
191
241
  const fileName = fileData.code_file_name ||
192
242
  resolvedFilePath.split("/").pop() ||
193
243
  resolvedFilePath;
@@ -202,7 +252,7 @@ export default function PillFileSelector({ themeColor }) {
202
252
  if (frameParams.linesStart !== undefined) {
203
253
  setActiveLineRange({
204
254
  start: frameParams.linesStart,
205
- end: frameParams.linesEnd,
255
+ end: frameParams.linesEnd
206
256
  });
207
257
  }
208
258
  else {
@@ -235,7 +285,7 @@ export default function PillFileSelector({ themeColor }) {
235
285
  : "text-zinc-400 hover:text-zinc-200", isEdited && "ring-1 ring-yellow-500/50"), style: isActive
236
286
  ? {
237
287
  backgroundColor: `${effectiveThemeColor}33`, // ~20% opacity
238
- color: effectiveThemeColor,
288
+ color: effectiveThemeColor
239
289
  }
240
290
  : undefined },
241
291
  React.createElement(IconComponent, { size: 12, className: cn("flex-shrink-0", !isActive && "text-zinc-500"), style: isActive ? { color: effectiveThemeColor } : undefined }),