@sampleapp.ai/sdk 1.0.36 → 1.0.37

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 (82) hide show
  1. package/dist/components/sandbox/Sandbox.js +5 -0
  2. package/dist/components/sandbox/api.js +3 -2
  3. package/dist/components/sandbox/guardian/guardian-component.js +7 -38
  4. package/dist/components/sandbox/guardian/guardian-playground.js +1 -1
  5. package/dist/components/sandbox/guardian/hooks/use-sandbox-url-loader.js +12 -10
  6. package/dist/components/sandbox/guardian/index.js +1 -1
  7. package/dist/components/sandbox/guardian/right-view/right-top-down-view.js +1 -1
  8. package/dist/components/sandbox/guardian/ui/markdown.js +2 -2
  9. package/dist/components/sandbox/guardian/utils.js +0 -18
  10. package/dist/components/sandbox/index.js +1 -1
  11. package/dist/components/sandbox/sandbox-home/SandboxHome.js +5 -0
  12. package/dist/index.d.ts +8 -40
  13. package/dist/index.es.js +830 -867
  14. package/dist/index.js +1 -1
  15. package/dist/lib/api-client.js +9 -26
  16. package/package.json +1 -1
  17. package/dist/components/guardian/app-layout-no-sidebar.js +0 -8
  18. package/dist/components/guardian/ask-ai-view.js +0 -249
  19. package/dist/components/guardian/code-focus-section.d.ts +0 -41
  20. package/dist/components/guardian/code-focus-section.js +0 -174
  21. package/dist/components/guardian/context/guardian-context.js +0 -94
  22. package/dist/components/guardian/context/vm-context.js +0 -28
  23. package/dist/components/guardian/default-guide-view.js +0 -34
  24. package/dist/components/guardian/demo/guardian-demo.js +0 -35
  25. package/dist/components/guardian/demo/left-view/toggle.js +0 -28
  26. package/dist/components/guardian/demo/left-view.js +0 -49
  27. package/dist/components/guardian/guardian-component.js +0 -79
  28. package/dist/components/guardian/guardian-demo.js +0 -35
  29. package/dist/components/guardian/guardian-home.d.ts +0 -4
  30. package/dist/components/guardian/guardian-home.js +0 -61
  31. package/dist/components/guardian/guardian-playground.js +0 -45
  32. package/dist/components/guardian/guardian-style-wrapper.js +0 -29
  33. package/dist/components/guardian/guardian-upload-spec.d.ts +0 -14
  34. package/dist/components/guardian/guardian-upload-spec.js +0 -160
  35. package/dist/components/guardian/header/glassmorphic-combobox.d.ts +0 -15
  36. package/dist/components/guardian/header/glassmorphic-combobox.js +0 -30
  37. package/dist/components/guardian/header.js +0 -61
  38. package/dist/components/guardian/hooks/use-frame-messages.js +0 -65
  39. package/dist/components/guardian/hooks/use-frame-params.js +0 -44
  40. package/dist/components/guardian/hooks/use-sandbox-url-loader.js +0 -101
  41. package/dist/components/guardian/ide/browser.js +0 -538
  42. package/dist/components/guardian/index.js +0 -8
  43. package/dist/components/guardian/layout/app-layout-no-sidebar.js +0 -8
  44. package/dist/components/guardian/layout/header/glassmorphic-combobox.js +0 -48
  45. package/dist/components/guardian/layout/header.js +0 -63
  46. package/dist/components/guardian/right-view/code-view.js +0 -56
  47. package/dist/components/guardian/right-view/pill-file-selector.js +0 -233
  48. package/dist/components/guardian/right-view/preview-control-bar.js +0 -25
  49. package/dist/components/guardian/right-view/right-panel-view.js +0 -38
  50. package/dist/components/guardian/right-view/right-top-down-view.js +0 -289
  51. package/dist/components/guardian/right-view/right-view.js +0 -28
  52. package/dist/components/guardian/right-view/simplified-editor.js +0 -234
  53. package/dist/components/guardian/types/ide-types.js +0 -162
  54. package/dist/components/guardian/types.js +0 -3
  55. package/dist/components/guardian/ui/ai-loader.js +0 -48
  56. package/dist/components/guardian/ui/badge.js +0 -24
  57. package/dist/components/guardian/ui/button.js +0 -45
  58. package/dist/components/guardian/ui/command.js +0 -63
  59. package/dist/components/guardian/ui/console-with-app.js +0 -17
  60. package/dist/components/guardian/ui/dialog.js +0 -57
  61. package/dist/components/guardian/ui/dropdown-menu.js +0 -82
  62. package/dist/components/guardian/ui/markdown.js +0 -57
  63. package/dist/components/guardian/ui/popover.js +0 -25
  64. package/dist/components/guardian/ui/tooltip.js +0 -25
  65. package/dist/components/guardian/utils.js +0 -88
  66. package/dist/components/guardian/zip-to-codebase.js +0 -246
  67. package/dist/components/guardian/zip-to-filetree.js +0 -284
  68. package/dist/components/sandbox/SandboxHome.js +0 -141
  69. package/dist/components/sandbox/guardian/guardian-demo.js +0 -35
  70. package/dist/components/sandbox/guardian/guardian-home.d.ts +0 -4
  71. package/dist/components/sandbox/guardian/guardian-home.js +0 -61
  72. package/dist/components/sandbox/guardian/guardian-upload-spec.d.ts +0 -14
  73. package/dist/components/sandbox/guardian/guardian-upload-spec.js +0 -160
  74. package/dist/components/sandbox/guardian/ui/theme-color-context.d.ts +0 -6
  75. package/dist/components/sandbox/sandbox-control-bar.js +0 -91
  76. package/dist/components/sandbox/sandbox-header.js +0 -52
  77. package/dist/components/sandbox/sandbox-left-panel.js +0 -248
  78. package/dist/components/sandbox/sandbox-loading.js +0 -48
  79. package/dist/components/sandbox/sandbox-right-panel.js +0 -247
  80. package/dist/components/sandbox.js +0 -32
  81. package/dist/lib/api-client.example.js +0 -60
  82. package/dist/lib/ssr-safe-decode-entity.js +0 -16
@@ -1,160 +0,0 @@
1
- "use client";
2
- import React from "react";
3
- "use client";
4
- import { useState, useCallback } from "react";
5
- import { Upload, FileText, X, Check, AlertCircle } from "lucide-react";
6
- import { cn } from "../../lib/utils";
7
- export default function GuardianUploadSpec({ onBack, onContinue, isLoading = false, }) {
8
- const [uploadedFile, setUploadedFile] = useState(null);
9
- const [isDragging, setIsDragging] = useState(false);
10
- const [error, setError] = useState(null);
11
- const [isValidating, setIsValidating] = useState(false);
12
- const validateFile = (file) => {
13
- // Check file size (max 10MB)
14
- if (file.size > 10 * 1024 * 1024) {
15
- return "File size must be less than 10MB";
16
- }
17
- // Check file type
18
- const validTypes = [
19
- "application/json",
20
- "application/x-yaml",
21
- "application/yaml",
22
- "text/yaml",
23
- "text/x-yaml",
24
- "text/plain",
25
- ];
26
- const validExtensions = [".json", ".yaml", ".yml"];
27
- const hasValidType = validTypes.includes(file.type);
28
- const hasValidExtension = validExtensions.some((ext) => file.name.toLowerCase().endsWith(ext));
29
- if (!hasValidType && !hasValidExtension) {
30
- return "Please upload a JSON or YAML file";
31
- }
32
- return null;
33
- };
34
- const handleFile = useCallback((file) => {
35
- setError(null);
36
- setIsValidating(true);
37
- const validationError = validateFile(file);
38
- if (validationError) {
39
- setError(validationError);
40
- setIsValidating(false);
41
- return;
42
- }
43
- const reader = new FileReader();
44
- reader.onload = (e) => {
45
- var _a;
46
- const content = (_a = e.target) === null || _a === void 0 ? void 0 : _a.result;
47
- // Basic validation that it's valid JSON or YAML
48
- try {
49
- if (file.name.toLowerCase().endsWith(".json")) {
50
- JSON.parse(content);
51
- }
52
- }
53
- catch (_b) {
54
- setError("Invalid JSON file");
55
- setIsValidating(false);
56
- return;
57
- }
58
- setUploadedFile({
59
- name: file.name,
60
- size: file.size,
61
- type: file.type,
62
- content,
63
- });
64
- setIsValidating(false);
65
- };
66
- reader.onerror = () => {
67
- setError("Failed to read file");
68
- setIsValidating(false);
69
- };
70
- reader.readAsText(file);
71
- }, []);
72
- const handleDrop = useCallback((e) => {
73
- e.preventDefault();
74
- setIsDragging(false);
75
- const files = Array.from(e.dataTransfer.files);
76
- if (files.length > 0) {
77
- handleFile(files[0]);
78
- }
79
- }, [handleFile]);
80
- const handleDragOver = useCallback((e) => {
81
- e.preventDefault();
82
- setIsDragging(true);
83
- }, []);
84
- const handleDragLeave = useCallback((e) => {
85
- e.preventDefault();
86
- setIsDragging(false);
87
- }, []);
88
- const handleFileInput = useCallback((e) => {
89
- const files = e.target.files;
90
- if (files && files.length > 0) {
91
- handleFile(files[0]);
92
- }
93
- }, [handleFile]);
94
- const handleRemoveFile = () => {
95
- setUploadedFile(null);
96
- setError(null);
97
- };
98
- const formatFileSize = (bytes) => {
99
- if (bytes < 1024)
100
- return bytes + " B";
101
- if (bytes < 1024 * 1024)
102
- return (bytes / 1024).toFixed(1) + " KB";
103
- return (bytes / (1024 * 1024)).toFixed(1) + " MB";
104
- };
105
- return (React.createElement("div", { className: "min-h-screen bg-white flex flex-col items-center justify-center p-8" },
106
- React.createElement("div", { className: "w-full max-w-xl" },
107
- React.createElement("div", { className: "mb-8" },
108
- React.createElement("button", { type: "button", onClick: onBack, disabled: isLoading, className: "text-sm text-gray-500 hover:text-gray-700 font-medium mb-4 flex items-center gap-1 disabled:opacity-50 disabled:cursor-not-allowed" }, isLoading ? (React.createElement(React.Fragment, null,
109
- React.createElement("div", { className: "h-3 w-3 animate-spin rounded-full border-2 border-gray-300 border-t-gray-700" }),
110
- React.createElement("span", null, "Back"))) : (React.createElement(React.Fragment, null, "\u2190 Back"))),
111
- React.createElement("h1", { className: "text-3xl font-semibold text-black mb-3" }, "Upload OpenAPI spec"),
112
- React.createElement("p", { className: "text-sm text-gray-600" }, "Upload your OpenAPI specification file (JSON or YAML format) to generate sample apps.")),
113
- !uploadedFile ? (React.createElement("div", { onDrop: handleDrop, onDragOver: handleDragOver, onDragLeave: handleDragLeave, className: cn("relative rounded-xl border-2 border-dashed transition-all", isDragging
114
- ? "border-blue-500 bg-blue-50"
115
- : "border-gray-200 bg-gray-50", error && "border-red-300 bg-red-50") },
116
- React.createElement("label", { className: "flex flex-col items-center justify-center px-8 py-12 cursor-pointer" },
117
- React.createElement("input", { type: "file", className: "hidden", accept: ".json,.yaml,.yml", onChange: handleFileInput, disabled: isValidating }),
118
- isValidating ? (React.createElement(React.Fragment, null,
119
- React.createElement("div", { className: "h-12 w-12 mb-4 animate-spin rounded-full border-4 border-gray-200 border-t-blue-600" }),
120
- React.createElement("p", { className: "text-sm font-medium text-gray-900 mb-1" }, "Validating file..."))) : (React.createElement(React.Fragment, null,
121
- React.createElement("div", { className: cn("flex h-14 w-14 items-center justify-center rounded-full mb-4 transition-colors", isDragging ? "bg-blue-100" : "bg-white", error && "bg-red-100") }, error ? (React.createElement(AlertCircle, { className: "h-7 w-7 text-red-600" })) : (React.createElement(Upload, { className: cn("h-7 w-7", isDragging ? "text-blue-600" : "text-gray-400") }))),
122
- React.createElement("p", { className: "text-sm font-medium text-gray-900 mb-1" }, isDragging ? ("Drop your file here") : (React.createElement(React.Fragment, null,
123
- React.createElement("span", { className: "text-blue-600" }, "Click to upload"),
124
- " ",
125
- "or drag and drop"))),
126
- React.createElement("p", { className: "text-xs text-gray-500 mb-2" }, "JSON or YAML (max 10MB)"))),
127
- error && (React.createElement("div", { className: "mt-3 text-xs text-red-600 font-medium flex items-center gap-1.5" },
128
- React.createElement(AlertCircle, { className: "h-3.5 w-3.5" }),
129
- error))))) : (React.createElement("div", { className: "space-y-4" },
130
- React.createElement("div", { className: "rounded-xl border border-gray-200 bg-white p-4 transition hover:bg-gray-50" },
131
- React.createElement("div", { className: "flex items-start justify-between gap-3" },
132
- React.createElement("div", { className: "flex items-start gap-3 flex-1" },
133
- React.createElement("div", { className: "flex h-10 w-10 items-center justify-center rounded-full bg-green-50" },
134
- React.createElement(FileText, { className: "h-5 w-5 text-green-600" })),
135
- React.createElement("div", { className: "flex-1 min-w-0" },
136
- React.createElement("div", { className: "flex items-center gap-2 mb-1" },
137
- React.createElement("p", { className: "text-sm font-medium text-gray-900 truncate" }, uploadedFile.name),
138
- React.createElement("div", { className: "flex h-5 w-5 items-center justify-center rounded-full bg-green-500 flex-shrink-0" },
139
- React.createElement(Check, { className: "h-3 w-3 text-white" }))),
140
- React.createElement("p", { className: "text-xs text-gray-500" }, formatFileSize(uploadedFile.size)))),
141
- React.createElement("button", { type: "button", onClick: handleRemoveFile, className: "flex h-8 w-8 items-center justify-center rounded-full hover:bg-gray-100 transition-colors text-gray-400 hover:text-gray-600" },
142
- React.createElement(X, { className: "h-4 w-4" })))),
143
- React.createElement("div", { className: "rounded-xl bg-blue-50 border border-blue-100 p-4" },
144
- React.createElement("p", { className: "text-xs text-blue-900 font-medium mb-2" }, "What happens next?"),
145
- React.createElement("ul", { className: "space-y-1.5 text-xs text-blue-800" },
146
- React.createElement("li", { className: "flex items-start gap-2" },
147
- React.createElement("span", null, "\u2022"),
148
- React.createElement("span", null, "We'll parse your OpenAPI spec and extract all endpoints")),
149
- React.createElement("li", { className: "flex items-start gap-2" },
150
- React.createElement("span", null, "\u2022"),
151
- React.createElement("span", null, "You'll choose frameworks for which to generate sample apps")),
152
- React.createElement("li", { className: "flex items-start gap-2" },
153
- React.createElement("span", null, "\u2022"),
154
- React.createElement("span", null, "Each app will be pre-configured with auth and example calls")))),
155
- React.createElement("div", { className: "flex items-center justify-end gap-3 pt-2" },
156
- React.createElement("button", { type: "button", onClick: handleRemoveFile, disabled: isLoading, className: "rounded-full border border-gray-200 bg-white px-5 py-2.5 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors disabled:opacity-50 disabled:cursor-not-allowed" }, "Choose different file"),
157
- React.createElement("button", { type: "button", onClick: () => onContinue(uploadedFile), disabled: isLoading, className: "inline-flex items-center gap-2 rounded-full bg-[#2563EB] px-6 py-2.5 text-sm font-medium text-white hover:bg-[#1d4ed8] transition-colors disabled:opacity-50 disabled:cursor-not-allowed" }, isLoading ? (React.createElement(React.Fragment, null,
158
- React.createElement("div", { className: "h-4 w-4 animate-spin rounded-full border-2 border-white/30 border-t-white" }),
159
- React.createElement("span", null, "Processing..."))) : (React.createElement("span", null, "Continue")))))))));
160
- }
@@ -1,15 +0,0 @@
1
- import React from "react";
2
- type Option = {
3
- label: string;
4
- value: string;
5
- href: string;
6
- };
7
- interface GlassmorphicComboboxProps {
8
- options: Option[];
9
- value: string;
10
- placeholder?: string;
11
- className?: string;
12
- searchPlaceholder?: string;
13
- }
14
- export default function GlassmorphicCombobox({ options, value, placeholder, className, searchPlaceholder, }: GlassmorphicComboboxProps): React.JSX.Element;
15
- export {};
@@ -1,30 +0,0 @@
1
- "use client";
2
- import React, { useState } from "react";
3
- import { Button } from "../ui/button";
4
- import { Popover, PopoverContent, PopoverTrigger } from "../ui/popover";
5
- import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, } from "../ui/command";
6
- import { CheckIcon, ChevronsUpDown } from "lucide-react";
7
- import { cn } from "../../../lib/utils";
8
- export default function GlassmorphicCombobox({ options, value, placeholder, className, searchPlaceholder, }) {
9
- var _a;
10
- const [open, setOpen] = useState(false);
11
- const selectedLabel = (_a = options.find((o) => o.value === value)) === null || _a === void 0 ? void 0 : _a.label;
12
- return (React.createElement(Popover, { open: open, onOpenChange: setOpen },
13
- React.createElement(PopoverTrigger, { asChild: true },
14
- React.createElement(Button, { variant: "outline", role: "combobox", "aria-expanded": open, className: cn("w-64 justify-between rounded-xl border-white/20 bg-white/10 backdrop-blur-md", "text-white/90 shadow-[inset_0_1px_0_0_rgba(255,255,255,0.25)] hover:bg-white/15", "data-[state=open]:bg-white/15", className) },
15
- React.createElement("span", { className: "truncate min-w-0" }, selectedLabel !== null && selectedLabel !== void 0 ? selectedLabel : placeholder),
16
- React.createElement(ChevronsUpDown, { className: "ml-2 h-4 w-4 shrink-0 opacity-70" }))),
17
- React.createElement(PopoverContent, { className: cn("w-64 p-0 rounded-xl border border-white/20 bg-black/40 backdrop-blur-xl", "shadow-xl") },
18
- React.createElement(Command, { className: "bg-transparent" },
19
- React.createElement(CommandInput, { placeholder: searchPlaceholder, className: "bg-transparent placeholder:text-white/60" }),
20
- React.createElement(CommandList, { className: "bg-transparent" },
21
- React.createElement(CommandEmpty, { className: "text-white/80" }, "No results found."),
22
- React.createElement(CommandGroup, { className: "bg-transparent" }, options.map((option) => (React.createElement(CommandItem, { key: option.value, value: option.value, className: "cursor-pointer", onSelect: () => {
23
- setOpen(false);
24
- if (option.href && typeof window !== "undefined") {
25
- window.location.href = option.href;
26
- }
27
- } },
28
- React.createElement(CheckIcon, { className: cn("mr-2 h-4 w-4", value === option.value ? "opacity-100" : "opacity-0") }),
29
- option.label)))))))));
30
- }
@@ -1,61 +0,0 @@
1
- "use client";
2
- import React from "react";
3
- import { Badge } from "./ui/badge";
4
- import GlassmorphicCombobox from "./header/glassmorphic-combobox";
5
- export default function Header({ demoOptions, frameworkOptions, firstFrameworkByUseCase, currentFramework, playgroundLogo, currentUseCase, }) {
6
- var _a, _b;
7
- // Derive values directly from props (which come from the URL)
8
- // No internal state - the URL is the source of truth
9
- const selectedUseCase = currentUseCase || ((_a = demoOptions[0]) === null || _a === void 0 ? void 0 : _a.value);
10
- const selectedFramework = currentFramework || ((_b = frameworkOptions[0]) === null || _b === void 0 ? void 0 : _b.value);
11
- // Build options with hrefs
12
- const demoOptionsWithHrefs = demoOptions.map((opt) => {
13
- var _a;
14
- return (Object.assign(Object.assign({}, opt), {
15
- // Link to first available framework for this use case
16
- href: `/${opt.value}?framework=${(firstFrameworkByUseCase === null || firstFrameworkByUseCase === void 0 ? void 0 : firstFrameworkByUseCase[opt.value]) || ((_a = frameworkOptions[0]) === null || _a === void 0 ? void 0 : _a.value)}` }));
17
- });
18
- const frameworkOptionsWithHrefs = frameworkOptions.map((opt) => (Object.assign(Object.assign({}, opt), { href: `/${selectedUseCase}?framework=${opt.value}` })));
19
- // Derive environment metadata for badge styling and label
20
- const environment = (process.env.NEXT_PUBLIC_ENVIRONMENT ||
21
- process.env.NODE_ENV ||
22
- "development").toLowerCase();
23
- const envMeta = {
24
- production: {
25
- label: "Production",
26
- badgeClass: "border-emerald-500/30 bg-emerald-500/10 text-emerald-200",
27
- dotClass: "bg-emerald-400 shadow-[0_0_0_3px_rgba(16,185,129,0.25)]",
28
- },
29
- staging: {
30
- label: "Staging",
31
- badgeClass: "border-amber-500/30 bg-amber-500/10 text-amber-100",
32
- dotClass: "bg-amber-400 shadow-[0_0_0_3px_rgba(245,158,11,0.25)]",
33
- },
34
- test: {
35
- label: "Test",
36
- badgeClass: "border-cyan-500/30 bg-cyan-500/10 text-cyan-100",
37
- dotClass: "bg-cyan-400 shadow-[0_0_0_3px_rgba(34,211,238,0.25)]",
38
- },
39
- preview: {
40
- label: "Preview",
41
- badgeClass: "border-sky-500/30 bg-sky-500/10 text-sky-100",
42
- dotClass: "bg-sky-400 shadow-[0_0_0_3px_rgba(56,189,248,0.25)]",
43
- },
44
- development: {
45
- label: "Demo Environment",
46
- badgeClass: "border-violet-500/30 bg-violet-500/10 text-violet-100",
47
- dotClass: "bg-violet-400 shadow-[0_0_0_3px_rgba(139,92,246,0.25)]",
48
- },
49
- };
50
- const meta = envMeta[environment] || envMeta.development;
51
- return (React.createElement("div", { className: "grid grid-cols-[1fr_auto_1fr] items-center w-full ml-6" },
52
- React.createElement("div", { className: "justify-self-start" }, playgroundLogo),
53
- React.createElement("div", { className: "justify-self-center" },
54
- React.createElement("div", { className: "flex gap-2" },
55
- React.createElement(GlassmorphicCombobox, { options: demoOptionsWithHrefs, value: selectedUseCase, placeholder: "Select use case...", searchPlaceholder: "Search use case..." }),
56
- React.createElement(GlassmorphicCombobox, { options: frameworkOptionsWithHrefs, value: selectedFramework, placeholder: "Select framework...", searchPlaceholder: "Search framework...", className: "w-32" }))),
57
- React.createElement("div", { className: "justify-self-end mr-6" },
58
- React.createElement(Badge, { className: `rounded-full px-5 py-3 flex items-center gap-2 border ${meta.badgeClass} backdrop-blur-sm shadow-[inset_0_1px_0_rgba(255,255,255,0.04)] text-sm`, "aria-label": `${meta.label} environment` },
59
- React.createElement("span", { className: `inline-block h-2 w-2 rounded-full ${meta.dotClass}` }),
60
- meta.label))));
61
- }
@@ -1,65 +0,0 @@
1
- "use client";
2
- import { useEffect, useRef } from "react";
3
- /**
4
- * Hook to handle postMessage communication with parent window when in iframe mode.
5
- * - Sends IFRAME_READY when mounted
6
- * - Listens for UPDATE_VIEW messages and updates URL params accordingly
7
- * - Works with useFrameParams() which reads the updated URL params
8
- *
9
- * Supported UPDATE_VIEW message fields:
10
- * - framework: Switch between frameworks (e.g., "nextjs", "react")
11
- * - activeFilePath: Change the active file in the editor
12
- * - linesStart/linesEnd: Highlight specific line range in the editor
13
- * - theme: Override the theme color (hex color)
14
- */
15
- export function useFrameMessages() {
16
- const hasSignaledReady = useRef(false);
17
- useEffect(() => {
18
- if (typeof window === "undefined")
19
- return;
20
- // Check if we're in an iframe
21
- const isFrame = window.self !== window.top;
22
- if (!isFrame)
23
- return;
24
- // Signal to parent that we're ready to receive messages
25
- if (!hasSignaledReady.current && window.parent) {
26
- window.parent.postMessage({ type: "IFRAME_READY" }, "*");
27
- hasSignaledReady.current = true;
28
- }
29
- const handleMessage = (event) => {
30
- // Optional: Add origin validation for security in production
31
- // if (event.origin !== 'https://your-allowed-origin.com') return;
32
- const data = event.data;
33
- if (data.type === "UPDATE_VIEW") {
34
- const url = new URL(window.location.href);
35
- // Update URL params based on message
36
- if (data.framework !== undefined) {
37
- url.searchParams.set("framework", data.framework);
38
- }
39
- if (data.activeFilePath !== undefined) {
40
- url.searchParams.set("activeFilePath", data.activeFilePath);
41
- }
42
- if (data.linesStart !== undefined) {
43
- url.searchParams.set("linesStart", String(data.linesStart));
44
- }
45
- if (data.linesEnd !== undefined) {
46
- url.searchParams.set("linesEnd", String(data.linesEnd));
47
- }
48
- if (data.theme !== undefined) {
49
- url.searchParams.set("theme", data.theme);
50
- }
51
- if (data.iframeUrl !== undefined) {
52
- url.searchParams.set("iframeUrl", data.iframeUrl);
53
- }
54
- // Update URL without page reload
55
- window.history.replaceState({}, "", url.toString());
56
- // Dispatch a custom event so React components can react immediately
57
- // (useFrameParams may have a slight delay)
58
- window.dispatchEvent(new CustomEvent("frameParamsUpdated", { detail: data }));
59
- }
60
- };
61
- window.addEventListener("message", handleMessage);
62
- return () => window.removeEventListener("message", handleMessage);
63
- }, []);
64
- return {};
65
- }
@@ -1,44 +0,0 @@
1
- import { useMemo } from "react";
2
- /**
3
- * Hook to read frame-mode search parameters from URL.
4
- * These params override config/context when isFrame=true.
5
- *
6
- * Note: This is a simplified version that reads from window.location.search.
7
- * For Next.js apps, you can use the Next.js version that uses useSearchParams.
8
- */
9
- export function useFrameParams() {
10
- return useMemo(() => {
11
- if (typeof window === "undefined") {
12
- return {
13
- isFrame: false,
14
- framework: undefined,
15
- theme: undefined,
16
- activeFilePath: undefined,
17
- linesStart: undefined,
18
- linesEnd: undefined,
19
- iframeUrl: undefined,
20
- };
21
- }
22
- const searchParams = new URLSearchParams(window.location.search);
23
- const isFrame = searchParams.get("isFrame") === "true";
24
- const framework = searchParams.get("framework") || undefined;
25
- const theme = searchParams.get("theme") || undefined;
26
- const activeFilePath = searchParams.get("activeFilePath") || undefined;
27
- const linesStart = searchParams.get("linesStart")
28
- ? parseInt(searchParams.get("linesStart"), 10)
29
- : undefined;
30
- const linesEnd = searchParams.get("linesEnd")
31
- ? parseInt(searchParams.get("linesEnd"), 10)
32
- : undefined;
33
- const iframeUrl = searchParams.get("iframeUrl") || undefined;
34
- return {
35
- isFrame,
36
- framework,
37
- theme,
38
- activeFilePath,
39
- linesStart,
40
- linesEnd,
41
- iframeUrl,
42
- };
43
- }, [typeof window !== "undefined" ? window.location.search : ""]);
44
- }
@@ -1,101 +0,0 @@
1
- "use client";
2
- import { useCallback } from "react";
3
- import { useVmContext } from "../context/vm-context";
4
- /**
5
- * Creates a cache key from sandboxUid and browserUrl.
6
- * This allows caching different URLs for the same sandbox.
7
- */
8
- function getCacheKey(sandboxUid, browserUrl) {
9
- return `${sandboxUid}::${browserUrl}`;
10
- }
11
- /**
12
- * Hook that abstracts the logic for loading sandbox URLs.
13
- * Returns functions to load URLs and check if they're already loaded.
14
- */
15
- export function useSandboxUrlLoader() {
16
- const { setVmUrl, getVmUrl, setVmError, hasError } = useVmContext();
17
- /**
18
- * Loads a sandbox URL. If useVm is true, fetches from the API.
19
- * Otherwise, uses the browserUrl directly.
20
- * Returns a promise that resolves to the final URL, or throws if loading fails.
21
- *
22
- * Caches by sandboxUid + browserUrl to support multiple URLs per sandbox.
23
- */
24
- const loadSandboxUrl = useCallback(async (config) => {
25
- const { sandboxUid, browserUrl, useVm } = config;
26
- const cacheKey = getCacheKey(sandboxUid, browserUrl);
27
- // Check if URL is already loaded for this specific browserUrl
28
- const existingUrl = getVmUrl(cacheKey);
29
- if (existingUrl !== undefined) {
30
- return existingUrl;
31
- }
32
- // Check if we've already attempted and failed for this specific browserUrl
33
- if (hasError(cacheKey)) {
34
- throw new Error(`Previously failed to load sandbox URL for ${sandboxUid} with ${browserUrl}`);
35
- }
36
- if (useVm) {
37
- try {
38
- // Fetch VM URL from API
39
- // Note: This assumes the API endpoint is available at /api/auto-sandbox
40
- // You may need to configure this based on your deployment
41
- const apiBaseUrl = typeof window !== "undefined"
42
- ? window.location.origin
43
- : "";
44
- const response = await fetch(`${apiBaseUrl}/api/auto-sandbox?url=${encodeURIComponent(browserUrl)}&mode=json`);
45
- // Check if response is successful
46
- if (!response.ok) {
47
- const errorText = await response.text();
48
- console.error(`Failed to fetch sandbox URL for ${sandboxUid}:`, errorText);
49
- // Mark as error to prevent infinite retries
50
- setVmError(cacheKey);
51
- throw new Error(`Failed to create sandbox: ${response.status} ${response.statusText}`);
52
- }
53
- const data = await response.json();
54
- if (!data.vncUrl) {
55
- console.error(`No vncUrl in response for ${sandboxUid}:`, data);
56
- setVmError(cacheKey);
57
- throw new Error("No vncUrl in sandbox response");
58
- }
59
- const vncUrl = data.vncUrl;
60
- setVmUrl(cacheKey, vncUrl);
61
- return vncUrl;
62
- }
63
- catch (error) {
64
- // Mark as error to prevent infinite retries
65
- setVmError(cacheKey);
66
- throw error;
67
- }
68
- }
69
- else {
70
- // Use browserUrl directly (non-VM mode)
71
- setVmUrl(cacheKey, browserUrl);
72
- return browserUrl;
73
- }
74
- }, [getVmUrl, setVmUrl, hasError, setVmError]);
75
- /**
76
- * Gets the URL for a sandbox + browserUrl combination if it's already loaded.
77
- * Returns undefined if not loaded.
78
- */
79
- const getSandboxUrl = useCallback((sandboxUid, browserUrl) => {
80
- const cacheKey = getCacheKey(sandboxUid, browserUrl);
81
- return getVmUrl(cacheKey);
82
- }, [getVmUrl]);
83
- /**
84
- * Preloads URLs for multiple sandboxes in parallel.
85
- * Errors are logged but don't stop other URLs from loading.
86
- */
87
- const preloadSandboxUrls = useCallback(async (configs) => {
88
- // Filter out configs that are already loaded or have errors
89
- const configsToLoad = configs.filter((config) => {
90
- const cacheKey = getCacheKey(config.sandboxUid, config.browserUrl);
91
- return getVmUrl(cacheKey) === undefined && !hasError(cacheKey);
92
- });
93
- // Load all URLs in parallel, but catch errors individually
94
- await Promise.allSettled(configsToLoad.map((config) => loadSandboxUrl(config)));
95
- }, [getVmUrl, loadSandboxUrl, hasError]);
96
- return {
97
- loadSandboxUrl,
98
- getSandboxUrl,
99
- preloadSandboxUrls,
100
- };
101
- }