import { THEME_CSS, SVG_CLASSES_CSS, FORM_STYLES_WITH_STAGGER_CSS as FORM_STYLES_CSS, IMPORTMAP_SCRIPT_TAG, } from "@repo/design-system"; const CHART_COLORS = [ "#3b82f6", "#8b5cf6", "#ec4899", "#f59e0b", "#10b981", "#06b6d4", "#f97316", ]; export interface StandaloneActivityContent { css?: string; html?: string[]; jsFunctions?: string; jsExpressions?: string[]; } const WEBSANDBOX_STUB = `window.Websandbox = { connection: { remote: { sendPrompt: async () => {}, openLink: async ({ url }) => { if (/^https:/.test(url)) window.open(url, "_blank", "noopener,noreferrer"); } } } };`; function escapeScriptClose(js: string): string { return js.replace(/<\/script/gi, "<\\/script"); } function escapeStyleClose(css: string): string { return css.replace(/<\/style/gi, "<\\/style"); } /** * Wrap an open-generative-ui activity payload in a standalone document that * works when opened in a browser: importmap first, then the same design-system * css composition the live renderer injects, then the generated css, a * Websandbox stub so exported bridge calls degrade gracefully, the joined html * chunks, and finally the generated js in ONE classic (non-module) script: * jsFunctions at top level — so function declarations become window globals, * matching the live websandbox rail where inline onclick="fn()" handlers * resolve them — followed by the jsExpressions inside an async IIFE so they * can use `await` (dynamic import() still resolves via the importmap in * classic scripts). */ export function assembleStandaloneHtmlFromActivity( content: StandaloneActivityContent, title = "generated-widget" ): string { const body = content.html?.join("") ?? ""; const expressions = content.jsExpressions ?? []; const scriptParts = [ ...(content.jsFunctions ? [escapeScriptClose(content.jsFunctions)] : []), ...(expressions.length > 0 ? [ `(async () => { ${expressions.map(escapeScriptClose).join("\n")} })();`, ] : []), ]; const generatedScript = scriptParts.length > 0 ? `` : ""; return `
${escapeHtml(data.description)}
`; } export function slugify(text: string): string { return text .toLowerCase() .replace(/[^a-z0-9]+/g, "-") .replace(/^-+|-+$/g, ""); } export function triggerDownload(htmlString: string, filename: string): void { const blob = new Blob([htmlString], { type: "text/html" }); const url = URL.createObjectURL(blob); const a = document.createElement("a"); a.href = url; a.download = filename; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); } function escapeHtml(text: string): string { return text .replace(/&/g, "&") .replace(//g, ">") .replace(/"/g, """); }