@sampleapp.ai/sdk 1.0.47 → 1.0.49
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,23 +1,26 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import React, { useEffect, useState, useCallback, useRef } from "react";
|
|
3
|
-
import { fetchSandboxConfigWithContent, buildConfigFromContent } from "./api";
|
|
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
7
|
import { Skeleton } from "../ui/skeleton";
|
|
8
8
|
import { useTreeSelector } from "../../hooks/use-tree-selector";
|
|
9
|
-
import { SELECTABLE_NODE_TYPES } from "../../lib/types/tree-config";
|
|
10
|
-
import { useFrameParams } from "./guardian/hooks/use-frame-params";
|
|
9
|
+
import { SELECTABLE_NODE_TYPES, } from "../../lib/types/tree-config";
|
|
10
|
+
import { useFrameParams, } from "./guardian/hooks/use-frame-params";
|
|
11
11
|
/**
|
|
12
12
|
* Inner component that uses the tree selector hook
|
|
13
13
|
* Separated to ensure hooks are called after data is loaded
|
|
14
14
|
*/
|
|
15
|
-
function SandboxInner({ configWithContent, apiKey, env, themeColor, theme, config }) {
|
|
15
|
+
function SandboxInner({ configWithContent, apiKey, env, themeColor, theme, config, }) {
|
|
16
16
|
var _a;
|
|
17
17
|
const [currentConfig, setCurrentConfig] = useState(configWithContent.legacyConfig);
|
|
18
18
|
// Get frame params for syncing external selection changes
|
|
19
19
|
const frameParams = useFrameParams();
|
|
20
20
|
const isFrame = (config === null || config === void 0 ? void 0 : config.isFrame) || frameParams.isFrame;
|
|
21
|
+
const containerClass = isFrame
|
|
22
|
+
? "h-full w-full bg-white dark:bg-black"
|
|
23
|
+
: "h-screen w-full bg-white dark:bg-black";
|
|
21
24
|
// Track previous nodeTypes to detect changes
|
|
22
25
|
const prevNodeTypesRef = useRef({});
|
|
23
26
|
// Handle selection changes - update the config when user selects different options
|
|
@@ -37,7 +40,7 @@ function SandboxInner({ configWithContent, apiKey, env, themeColor, theme, confi
|
|
|
37
40
|
config: configWithContent.techStackConfig,
|
|
38
41
|
allContent: configWithContent.allContent,
|
|
39
42
|
initialPath: configWithContent.initialSelectionPath,
|
|
40
|
-
onSelectionChange: handleSelectionChange
|
|
43
|
+
onSelectionChange: handleSelectionChange,
|
|
41
44
|
});
|
|
42
45
|
// Sync frame params with tree selector when in frame mode
|
|
43
46
|
// This effect listens for changes in URL params (via postMessage or direct URL changes)
|
|
@@ -73,12 +76,12 @@ function SandboxInner({ configWithContent, apiKey, env, themeColor, theme, confi
|
|
|
73
76
|
const firstUseCase = currentConfig.useCases[0];
|
|
74
77
|
const firstFramework = (_a = firstUseCase === null || firstUseCase === void 0 ? void 0 : firstUseCase.frameworks[0]) === null || _a === void 0 ? void 0 : _a.key;
|
|
75
78
|
if (!firstUseCase || !firstFramework) {
|
|
76
|
-
return (React.createElement("div", { className:
|
|
79
|
+
return (React.createElement("div", { className: `flex items-center justify-center ${containerClass}` },
|
|
77
80
|
React.createElement("div", { className: "text-red-500" }, "No use cases or frameworks found")));
|
|
78
81
|
}
|
|
79
82
|
return (React.createElement(GuardianProvider, null,
|
|
80
83
|
React.createElement(VmProvider, null,
|
|
81
|
-
React.createElement("div", { className:
|
|
84
|
+
React.createElement("div", { className: containerClass },
|
|
82
85
|
React.createElement(GuardianPlayground, { sandboxConfig: currentConfig, sections: sections, onSelect: selectOption, useCase: firstUseCase.id, framework: firstFramework, isFrame: isFrame, apiKey: apiKey, env: env, chatUid: currentConfig.chatUid, theme: theme, hasNetworkView: config === null || config === void 0 ? void 0 : config.hasNetworkView })))));
|
|
83
86
|
}
|
|
84
87
|
/**
|
|
@@ -108,10 +111,14 @@ function SandboxInner({ configWithContent, apiKey, env, themeColor, theme, confi
|
|
|
108
111
|
* />
|
|
109
112
|
* ```
|
|
110
113
|
*/
|
|
111
|
-
export default function Sandbox({ apiKey, sandboxId, env, themeColor, theme = "dark", config }) {
|
|
114
|
+
export default function Sandbox({ apiKey, sandboxId, env, themeColor, theme = "dark", config, }) {
|
|
112
115
|
// Validate apiKey immediately
|
|
116
|
+
const isFrame = config === null || config === void 0 ? void 0 : config.isFrame;
|
|
117
|
+
const containerClass = isFrame
|
|
118
|
+
? "h-full w-full bg-white dark:bg-black"
|
|
119
|
+
: "h-screen w-full bg-white dark:bg-black";
|
|
113
120
|
if (!apiKey || apiKey.trim() === "") {
|
|
114
|
-
return (React.createElement("div", { className:
|
|
121
|
+
return (React.createElement("div", { className: `flex items-center justify-center ${containerClass}` },
|
|
115
122
|
React.createElement("div", { className: "text-red-500" }, "Error: apiKey is required. Please provide a valid API key.")));
|
|
116
123
|
}
|
|
117
124
|
const [configWithContent, setConfigWithContent] = useState(null);
|
|
@@ -148,11 +155,11 @@ export default function Sandbox({ apiKey, sandboxId, env, themeColor, theme = "d
|
|
|
148
155
|
};
|
|
149
156
|
}, [apiKey, sandboxId, themeColor]);
|
|
150
157
|
if (loading) {
|
|
151
|
-
return (React.createElement("div", { className:
|
|
158
|
+
return (React.createElement("div", { className: containerClass },
|
|
152
159
|
React.createElement(Skeleton, { className: "w-full h-full bg-zinc-100 dark:bg-zinc-900" })));
|
|
153
160
|
}
|
|
154
161
|
if (error) {
|
|
155
|
-
return (React.createElement("div", { className:
|
|
162
|
+
return (React.createElement("div", { className: `flex items-center justify-center ${containerClass}` },
|
|
156
163
|
React.createElement("div", { className: "text-red-500" },
|
|
157
164
|
"Error: ",
|
|
158
165
|
error)));
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import React from "react";
|
|
3
|
-
import { DownloadIcon, Check, Copy } from "lucide-react";
|
|
3
|
+
import { DownloadIcon, Check, Copy, Loader2 } from "lucide-react";
|
|
4
4
|
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, } from "./dialog";
|
|
5
5
|
import vscodeLogo from "../../../../assets/vscode.png";
|
|
6
6
|
import { handleSandboxDownload } from "../utils";
|
|
7
|
+
import { createApiClient } from "../../../../lib/api-client";
|
|
7
8
|
// VSCode logo component using imported asset
|
|
8
9
|
function VSCodeLogo({ className }) {
|
|
9
10
|
return (React.createElement("img", { src: vscodeLogo, alt: "VSCode", width: 20, height: 20, className: className }));
|
|
@@ -11,16 +12,19 @@ function VSCodeLogo({ className }) {
|
|
|
11
12
|
export const DownloadAndOpenButtons = ({ downloadUrl, themeColor, gitUrl, browserUrl, sandboxId, apiKey, }) => {
|
|
12
13
|
const [isModalOpen, setIsModalOpen] = React.useState(false);
|
|
13
14
|
const [hasCopied, setHasCopied] = React.useState(false);
|
|
15
|
+
const [isCloning, setIsCloning] = React.useState(false);
|
|
14
16
|
// Build the npx command using the SDK download endpoint
|
|
15
17
|
// Use default MAIN_APP_URL for SDK (can be overridden via env)
|
|
16
|
-
const MAIN_APP_URL = typeof window !== "undefined" &&
|
|
18
|
+
const MAIN_APP_URL = typeof window !== "undefined" &&
|
|
19
|
+
window.SAMPLEAPP_MAIN_URL
|
|
17
20
|
? window.SAMPLEAPP_MAIN_URL
|
|
18
21
|
: "https://sampleapp.ai";
|
|
19
22
|
const npxCommand = React.useMemo(() => {
|
|
20
23
|
if (!sandboxId) {
|
|
21
24
|
return "";
|
|
22
25
|
}
|
|
23
|
-
const apiBaseUrl = typeof window !== "undefined" &&
|
|
26
|
+
const apiBaseUrl = typeof window !== "undefined" &&
|
|
27
|
+
window.SAMPLEAPP_API_URL
|
|
24
28
|
? window.SAMPLEAPP_API_URL
|
|
25
29
|
: MAIN_APP_URL.replace("sampleapp.ai", "api.sampleapp.ai");
|
|
26
30
|
return `npx sampleappai add "${apiBaseUrl}/api/v1/sdk/download-code?sandbox_id=${sandboxId}"`;
|
|
@@ -28,10 +32,32 @@ export const DownloadAndOpenButtons = ({ downloadUrl, themeColor, gitUrl, browse
|
|
|
28
32
|
const handleDownload = async () => {
|
|
29
33
|
await handleSandboxDownload(sandboxId, apiKey, () => setIsModalOpen(true));
|
|
30
34
|
};
|
|
31
|
-
const handleOpenInVSCode = () => {
|
|
32
|
-
|
|
35
|
+
const handleOpenInVSCode = async () => {
|
|
36
|
+
// If we have a sandboxId and apiKey, fetch an authenticated clone URL from the backend
|
|
37
|
+
if (sandboxId && apiKey) {
|
|
38
|
+
setIsCloning(true);
|
|
33
39
|
try {
|
|
34
|
-
|
|
40
|
+
const client = createApiClient({ apiKey });
|
|
41
|
+
const { clone_url } = await client.sdk.getCloneUrl(sandboxId);
|
|
42
|
+
window.location.href = `vscode://vscode.git/clone?url=${encodeURIComponent(clone_url)}`;
|
|
43
|
+
}
|
|
44
|
+
catch (err) {
|
|
45
|
+
console.error("Failed to get clone URL:", err);
|
|
46
|
+
// Fall back to direct gitUrl or modal
|
|
47
|
+
if (gitUrl) {
|
|
48
|
+
window.location.href = `vscode://vscode.git/clone?url=${encodeURIComponent(gitUrl)}`;
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
setIsModalOpen(true);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
finally {
|
|
55
|
+
setIsCloning(false);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
else if (gitUrl) {
|
|
59
|
+
try {
|
|
60
|
+
window.location.href = `vscode://vscode.git/clone?url=${encodeURIComponent(gitUrl)}`;
|
|
35
61
|
}
|
|
36
62
|
catch (_a) {
|
|
37
63
|
// Silently fail if vscode:// protocol isn't registered
|
|
@@ -59,9 +85,9 @@ export const DownloadAndOpenButtons = ({ downloadUrl, themeColor, gitUrl, browse
|
|
|
59
85
|
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 },
|
|
60
86
|
React.createElement(DownloadIcon, { className: "w-3.5 h-3.5 !text-white" }),
|
|
61
87
|
"Download example"),
|
|
62
|
-
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-100 dark:bg-zinc-800 border-zinc-300 dark:border-zinc-700 text-zinc-900 dark:text-white", onClick: handleOpenInVSCode },
|
|
63
|
-
React.createElement(VSCodeLogo, { className: "w-3.5 h-3.5" }),
|
|
64
|
-
"Integrate in VS Code")),
|
|
88
|
+
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-100 dark:bg-zinc-800 border-zinc-300 dark:border-zinc-700 text-zinc-900 dark:text-white disabled:opacity-50", onClick: handleOpenInVSCode, disabled: isCloning },
|
|
89
|
+
isCloning ? (React.createElement(Loader2, { className: "w-3.5 h-3.5 animate-spin" })) : (React.createElement(VSCodeLogo, { className: "w-3.5 h-3.5" })),
|
|
90
|
+
isCloning ? "Opening..." : "Integrate in VS Code")),
|
|
65
91
|
React.createElement(Dialog, { open: isModalOpen, onOpenChange: setIsModalOpen },
|
|
66
92
|
React.createElement(DialogContent, { className: "max-w-[90vw] sm:max-w-md w-full" },
|
|
67
93
|
React.createElement(DialogHeader, null,
|
package/dist/index.d.ts
CHANGED
|
@@ -120,6 +120,15 @@ declare class ApiClient {
|
|
|
120
120
|
* @returns Promise that resolves when download is initiated
|
|
121
121
|
*/
|
|
122
122
|
downloadCode: (sandboxId: string) => Promise<void>;
|
|
123
|
+
/**
|
|
124
|
+
* Get an authenticated clone URL for a private GitHub repository.
|
|
125
|
+
* Returns an HTTPS URL with a short-lived token (expires in 1 hour)
|
|
126
|
+
* that can be used with vscode://vscode.git/clone or git clone directly.
|
|
127
|
+
*
|
|
128
|
+
* @param sandboxId - The sandbox content UID
|
|
129
|
+
* @returns Clone URL response with authenticated URL
|
|
130
|
+
*/
|
|
131
|
+
getCloneUrl: (sandboxId: string) => Promise<CloneUrlResponse>;
|
|
123
132
|
/**
|
|
124
133
|
* Start VM endpoint that starts a desktop sandbox, launches Chrome,
|
|
125
134
|
* navigates to the target URL, and returns a VNC viewer URL
|
|
@@ -216,6 +225,10 @@ declare interface ChatButtonProps {
|
|
|
216
225
|
onClick?: () => void;
|
|
217
226
|
}
|
|
218
227
|
|
|
228
|
+
declare interface CloneUrlResponse {
|
|
229
|
+
clone_url: string;
|
|
230
|
+
}
|
|
231
|
+
|
|
219
232
|
declare enum CodeLanguage {
|
|
220
233
|
JAVASCRIPT = "javascript",
|
|
221
234
|
TYPESCRIPT = "typescript",
|
|
@@ -670,7 +683,7 @@ declare class SampleAppSDK {
|
|
|
670
683
|
* />
|
|
671
684
|
* ```
|
|
672
685
|
*/
|
|
673
|
-
export declare function Sandbox({ apiKey, sandboxId, env, themeColor, theme, config }: SandboxProps): default_2.JSX.Element | null;
|
|
686
|
+
export declare function Sandbox({ apiKey, sandboxId, env, themeColor, theme, config, }: SandboxProps): default_2.JSX.Element | null;
|
|
674
687
|
|
|
675
688
|
/**
|
|
676
689
|
* Configuration fetched from the API for a sandbox
|