Trudy's picture
did styling for mobile
a5fe725
/**
* Copyright 2025 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React, { useState } from 'react';
import { Code2, Play, Copy, Check, MessageCircle } from 'lucide-react';
import Editor from '@monaco-editor/react';
import ReactMarkdown from 'react-markdown';
import remarkGfm from 'remark-gfm';
import ToggleButton from './ToggleButton';
const CodePreview = ({ output, onCodeChange, fullResponse }) => {
const [showCode, setShowCode] = useState(false);
const [showReasoning, setShowReasoning] = useState(false);
const [isCopied, setIsCopied] = useState(false);
const handleCopy = async () => {
try {
await navigator.clipboard.writeText(output.code);
setIsCopied(true);
setTimeout(() => setIsCopied(false), 2000);
} catch (err) {
console.error('Failed to copy code:', err);
}
};
const renderSketch = (code) => {
// Make sure we're working with a string
const codeString = typeof code === 'string' ? code : code.toString();
const wrappedCode = codeString.includes('function setup()') ? codeString : `
function setup() {
createCanvas(500, 500);
${codeString}
}
function draw() {
// Add default draw function if not present
if (typeof window.draw !== 'function') {
window.draw = function() {};
}
}
`;
const formattedCodeResponse = `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/p5.js"></script>
<title>p5.js Sketch</title>
<style>
body {
padding: 0;
margin: 0;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
overflow: hidden;
}
canvas {
max-width: 100% !important;
height: auto !important;
}
</style>
</head>
<body>
<script>
try {
${wrappedCode}
// Immediately call setup if it hasn't been called
if (typeof window.setup === 'function') {
new p5();
}
} catch (error) {
console.error('Sketch error:', error);
document.body.innerHTML = '<div style="color: red; padding: 20px;"><h3>🔴 Error:</h3><pre>' + error.message + '</pre></div>';
}
</script>
</body>
</html>
`;
return (
<div className="relative w-full h-[500px] bg-gray-50 rounded-lg overflow-hidden">
<iframe
srcDoc={formattedCodeResponse}
title="p5.js Sketch"
width="100%"
height="100%"
style={{ border: "none" }}
className="absolute inset-0"
/>
</div>
);
};
// Make sure we're passing the actual code string to renderSketch
const sketchCode = output?.code || '';
return (
<div className="mb-4 p-6 rounded-3xl bg-gray-100">
<div className="mb-4">
{showCode ? (
<div className="w-full h-[500px] rounded-lg overflow-hidden border">
<Editor
height="500px"
defaultLanguage="javascript"
value={sketchCode}
onChange={(value) => onCodeChange(output.id, value)}
theme="light"
options={{
minimap: { enabled: false },
fontSize: 12,
lineNumbers: 'off',
scrollBeyondLastLine: false,
automaticLayout: true,
tabSize: 2,
wordWrap: 'on',
padding: { top: 8, bottom: 8 }
}}
/>
</div>
) : showReasoning ? (
<div className="w-full h-[500px] rounded-lg overflow-y-auto border p-4 prose prose-xs max-w-none bg-white">
<ReactMarkdown
remarkPlugins={[remarkGfm]}
className="text-xs text-gray-700"
components={{
code: ({node, inline, className, children, ...props}) => (
<code className={`${className} ${inline ? 'text-[0.7rem] bg-slate-50 text-slate-900 px-1.5 py-0.5 rounded border border-slate-200' : ''}`} {...props}>
{children}
</code>
),
pre: ({node, children, ...props}) => (
<pre className="text-[0.7rem] bg-slate-50 text-slate-900 p-3 rounded-md overflow-x-auto border border-slate-200" {...props}>
{children}
</pre>
),
p: ({node, children}) => (
<p className="text-xs mb-2 text-gray-700">{children}</p>
),
h1: ({node, children}) => (
<h1 className="text-sm font-bold mb-2 text-gray-900">{children}</h1>
),
h2: ({node, children}) => (
<h2 className="text-xs font-bold mb-2 text-gray-900">{children}</h2>
),
h3: ({node, children}) => (
<h3 className="text-xs font-semibold mb-1 text-gray-900">{children}</h3>
),
ul: ({node, children}) => (
<ul className="text-xs list-disc pl-4 mb-2 text-gray-700">{children}</ul>
),
ol: ({node, children}) => (
<ol className="text-xs list-decimal pl-4 mb-2 text-gray-700">{children}</ol>
),
li: ({node, children}) => (
<li className="text-xs mb-1 text-gray-700">{children}</li>
)
}}
>
{fullResponse}
</ReactMarkdown>
</div>
) : (
renderSketch(sketchCode)
)}
</div>
<div className="flex flex-col sm:flex-row justify-between items-center gap-3 mt-2">
<div className="inline-flex rounded-full bg-gray-200 gap-1 w-full sm:w-auto justify-center">
<ToggleButton
icon={Play}
label="Preview"
isSelected={!showCode && !showReasoning}
onClick={() => {
setShowCode(false);
setShowReasoning(false);
}}
/>
<ToggleButton
icon={MessageCircle}
label="Reasoning"
isSelected={showReasoning}
onClick={() => {
setShowCode(false);
setShowReasoning(true);
}}
/>
<ToggleButton
icon={Code2}
label="Code"
isSelected={showCode}
onClick={() => {
setShowCode(true);
setShowReasoning(false);
}}
/>
</div>
<button
type="button"
onClick={handleCopy}
className={`px-3.5 py-2.5 rounded-full transition-colors inline-flex text-sm border border-gray-300
items-center gap-1 w-full sm:w-auto justify-center ${
isCopied
? "bg-gray-500 text-white"
: "bg-transparent text-gray-700 hover:bg-gray-100"
}`}
>
{isCopied ? (
<>
<Check size={14} />
Copied!
</>
) : (
<>
<Copy size={14} />
Copy Code
</>
)}
</button>
</div>
</div>
);
};
export default CodePreview;