feat: implement live React/JSX rendering in AI Assist Canvas
- Enhanced LiveCanvas with Babel Standalone and React/ReactDOM CDNs - Implemented automatic import resolution and root mounting for React components - Updated parseStreamingContent to identify React code as renderable 'app' artifacts - Updated UI to automatically switch to Preview mode for React artifacts - Updated all AI provider prompts to officially support and encourage React rendering
This commit is contained in:
@@ -55,23 +55,75 @@ const LiveCanvas = memo(({ data, type, isStreaming }: { data: string, type: stri
|
|||||||
const trimmed = normalized.trim();
|
const trimmed = normalized.trim();
|
||||||
const isFullDocument = /^<!DOCTYPE/i.test(trimmed) || /^<html/i.test(trimmed);
|
const isFullDocument = /^<!DOCTYPE/i.test(trimmed) || /^<html/i.test(trimmed);
|
||||||
const hasHeadTag = /<head[\s>]/i.test(normalized);
|
const hasHeadTag = /<head[\s>]/i.test(normalized);
|
||||||
|
const isReactLike = normalized.includes("import React") || normalized.includes("useState") || normalized.includes("useEffect") || /<[A-Z][\s\S]*>/.test(normalized);
|
||||||
|
|
||||||
let doc: string;
|
let doc: string;
|
||||||
if (isFullDocument) {
|
if (isFullDocument) {
|
||||||
// If it's a full document, inject Tailwind CSS but keep the structure
|
// ... same as before but add React support if needed ...
|
||||||
|
const reactScripts = isReactLike ? `
|
||||||
|
<script src="https://unpkg.com/react@18/umd/react.production.min.js"></script>
|
||||||
|
<script src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script>
|
||||||
|
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
|
||||||
|
` : "";
|
||||||
|
|
||||||
if (hasHeadTag) {
|
if (hasHeadTag) {
|
||||||
doc = normalized.replace(/<head>/i, `<head>
|
doc = normalized.replace(/<head>/i, `<head>
|
||||||
|
${reactScripts}
|
||||||
<script src="https://cdn.tailwindcss.com"></script>
|
<script src="https://cdn.tailwindcss.com"></script>
|
||||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;600;700;800&display=swap">
|
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;600;700;800&display=swap">
|
||||||
`);
|
`);
|
||||||
} else {
|
} else {
|
||||||
doc = normalized.replace(/<html[^>]*>/i, (match) => `${match}
|
doc = normalized.replace(/<html[^>]*>/i, (match) => `${match}
|
||||||
<head>
|
<head>
|
||||||
|
${reactScripts}
|
||||||
<script src="https://cdn.tailwindcss.com"></script>
|
<script src="https://cdn.tailwindcss.com"></script>
|
||||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;600;700;800&display=swap">
|
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;600;700;800&display=swap">
|
||||||
</head>
|
</head>
|
||||||
`);
|
`);
|
||||||
}
|
}
|
||||||
|
} else if (isReactLike) {
|
||||||
|
// Specialized React Runner for fragments/components
|
||||||
|
const cleanedCode = normalized
|
||||||
|
.replace(/import\s+(?:React\s*,\s*)?{?([\s\S]*?)}?\s+from\s+['"]react['"];?/g, "const { $1 } = React;")
|
||||||
|
.replace(/import\s+React\s+from\s+['"]react['"];?/g, "/* React already global */")
|
||||||
|
.replace(/import\s+[\s\S]*?from\s+['"]lucide-react['"];?/g, "const { ...lucide } = window.lucide || {};")
|
||||||
|
.replace(/export\s+default\s+/g, "const MainComponent = ");
|
||||||
|
|
||||||
|
// Try to find the component name to render
|
||||||
|
const componentMatch = cleanedCode.match(/const\s+([A-Z]\w+)\s*=\s*\(\)\s*=>/);
|
||||||
|
const mainComponent = componentMatch ? componentMatch[1] : (cleanedCode.includes("MainComponent") ? "MainComponent" : null);
|
||||||
|
|
||||||
|
doc = `
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html class="dark">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<script src="https://cdn.tailwindcss.com"></script>
|
||||||
|
<script src="https://unpkg.com/react@18/umd/react.production.min.js"></script>
|
||||||
|
<script src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script>
|
||||||
|
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
|
||||||
|
<script src="https://unpkg.com/lucide@latest"></script>
|
||||||
|
<style>
|
||||||
|
body { margin: 0; padding: 20px; font-family: sans-serif; background: #0b1414; color: white; }
|
||||||
|
#root { min-height: 100vh; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="root"></div>
|
||||||
|
<script type="text/babel">
|
||||||
|
${cleanedCode}
|
||||||
|
|
||||||
|
${mainComponent ? `
|
||||||
|
const root = ReactDOM.createRoot(document.getElementById('root'));
|
||||||
|
root.render(React.createElement(${mainComponent}));
|
||||||
|
` : `
|
||||||
|
// No clear component found to mount, executing raw code
|
||||||
|
`}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
`;
|
||||||
} else {
|
} else {
|
||||||
// Wrap fragments in a styled container
|
// Wrap fragments in a styled container
|
||||||
doc = `
|
doc = `
|
||||||
@@ -206,13 +258,16 @@ function parseStreamingContent(text: string, currentAgent: string) {
|
|||||||
.trim();
|
.trim();
|
||||||
|
|
||||||
if (!preview) {
|
if (!preview) {
|
||||||
const fenced = text.match(/```(html|css|javascript|tsx|jsx|md|markdown)\s*([\s\S]*?)```/i);
|
const fenced = text.match(/```(html|css|javascript|typescript|tsx|jsx|md|markdown)\s*([\s\S]*?)```/i);
|
||||||
if (fenced) {
|
if (fenced) {
|
||||||
const language = fenced[1].toLowerCase();
|
const language = fenced[1].toLowerCase();
|
||||||
|
const data = fenced[2].trim();
|
||||||
|
const isReactLike = data.includes("import React") || data.includes("useState") || /<[A-Z][\s\S]*>/.test(data);
|
||||||
|
|
||||||
preview = {
|
preview = {
|
||||||
type: language === "html" ? "web" : "code",
|
type: (language === "html" || isReactLike) ? "app" : "code",
|
||||||
language,
|
language,
|
||||||
data: fenced[2].trim(),
|
data,
|
||||||
isStreaming: false
|
isStreaming: false
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -391,6 +446,7 @@ export default function AIAssist() {
|
|||||||
setPreviewData(preview);
|
setPreviewData(preview);
|
||||||
lastParsedPreview = preview;
|
lastParsedPreview = preview;
|
||||||
setShowCanvas(true);
|
setShowCanvas(true);
|
||||||
|
if (isPreviewRenderable(preview)) setViewMode("preview");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (agent !== currentAgent) setCurrentAgent(agent);
|
if (agent !== currentAgent) setCurrentAgent(agent);
|
||||||
|
|||||||
@@ -751,8 +751,8 @@ AGENTS & CAPABILITIES:
|
|||||||
- pm: Project Manager. Create PRDs, timelines, and action plans.
|
- pm: Project Manager. Create PRDs, timelines, and action plans.
|
||||||
- code: Software Architect. Provide logic, algorithms, and backend snippets.
|
- code: Software Architect. Provide logic, algorithms, and backend snippets.
|
||||||
- design: UI/UX Designer. Create high-fidelity mockups and components.
|
- design: UI/UX Designer. Create high-fidelity mockups and components.
|
||||||
- web: Frontend Developer. Build responsive sites using HTML/Tailwind.
|
- web: Frontend Developer. Build responsive sites using HTML/Tailwind or React. Use [PREVIEW:web:html] or [PREVIEW:web:javascript].
|
||||||
- app: Mobile App Developer. Create mobile-first interfaces and dashboards.
|
- app: Mobile App Developer. Create mobile-first interfaces and dashboards. React components are supported and rendered live. Use [PREVIEW:app:javascript].
|
||||||
|
|
||||||
ITERATIVE MODIFICATIONS (CRITICAL):
|
ITERATIVE MODIFICATIONS (CRITICAL):
|
||||||
- When a user asks for a change, fix, or update to an existing design/preview, you MUST be SURGICAL.
|
- When a user asks for a change, fix, or update to an existing design/preview, you MUST be SURGICAL.
|
||||||
|
|||||||
@@ -1025,8 +1025,8 @@ AGENTS & CAPABILITIES:
|
|||||||
- pm: Project Manager. Create PRDs, timelines, and action plans.
|
- pm: Project Manager. Create PRDs, timelines, and action plans.
|
||||||
- code: Software Architect. Provide logic, algorithms, and backend snippets.
|
- code: Software Architect. Provide logic, algorithms, and backend snippets.
|
||||||
- design: UI/UX Designer. Create high-fidelity mockups and components.
|
- design: UI/UX Designer. Create high-fidelity mockups and components.
|
||||||
- web: Frontend Developer. Build responsive sites using HTML/Tailwind.
|
- web: Frontend Developer. Build responsive sites using HTML/Tailwind or React. Use [PREVIEW:web:html] or [PREVIEW:web:javascript].
|
||||||
- app: Mobile App Developer. Create mobile-first interfaces and dashboards.
|
- app: Mobile App Developer. Create mobile-first interfaces and dashboards. React components are supported and rendered live. Use [PREVIEW:app:javascript].
|
||||||
|
|
||||||
ITERATIVE MODIFICATIONS (CRITICAL):
|
ITERATIVE MODIFICATIONS (CRITICAL):
|
||||||
- When a user asks for a change, fix, or update to an existing design/preview, you MUST be SURGICAL.
|
- When a user asks for a change, fix, or update to an existing design/preview, you MUST be SURGICAL.
|
||||||
|
|||||||
@@ -824,8 +824,8 @@ AGENTS & CAPABILITIES:
|
|||||||
- pm: Project Manager. Create PRDs, timelines, and action plans.
|
- pm: Project Manager. Create PRDs, timelines, and action plans.
|
||||||
- code: Software Architect. Provide logic, algorithms, and backend snippets.
|
- code: Software Architect. Provide logic, algorithms, and backend snippets.
|
||||||
- design: UI/UX Designer. Create high-fidelity mockups and components.
|
- design: UI/UX Designer. Create high-fidelity mockups and components.
|
||||||
- web: Frontend Developer. Build responsive sites using HTML/Tailwind.
|
- web: Frontend Developer. Build responsive sites using HTML/Tailwind or React. Use [PREVIEW:web:html] or [PREVIEW:web:javascript].
|
||||||
- app: Mobile App Developer. Create mobile-first interfaces and dashboards.
|
- app: Mobile App Developer. Create mobile-first interfaces and dashboards. React components are supported and rendered live. Use [PREVIEW:app:javascript].
|
||||||
|
|
||||||
ITERATIVE MODIFICATIONS (CRITICAL):
|
ITERATIVE MODIFICATIONS (CRITICAL):
|
||||||
- When a user asks for a change, fix, or update to an existing design/preview, you MUST be SURGICAL.
|
- When a user asks for a change, fix, or update to an existing design/preview, you MUST be SURGICAL.
|
||||||
|
|||||||
Reference in New Issue
Block a user