'use client'; import { useRef, useState, useEffect, useCallback } from 'react'; import dynamic from 'next/dynamic'; import { javascript } from '@codemirror/lang-javascript'; import { vscodeDark } from '@uiw/codemirror-theme-vscode'; // Dynamically import CodeMirror to avoid SSR issues const CodeMirror = dynamic( () => import('@uiw/react-codemirror'), { ssr: false } ); const INITIAL_CODE = `function setup() { createCanvas(400, 400); } function draw() { background(220); ellipse(mouseX, mouseY, 50, 50); }`; const normalizeCode = (code) => { // Remove all comments (both single line and multi-line) const noComments = code.replace(/\/\*[\s\S]*?\*\/|\/\/.*/g, ''); // Remove all non-syntactic whitespace and newlines return noComments.replace(/\s+/g, ' ').trim(); }; const getIframeContent = (userCode) => `
`; export default function Editor() { const [code, setCode] = useState(INITIAL_CODE); const [normalizedCode, setNormalizedCode] = useState(normalizeCode(INITIAL_CODE)); const [prompt, setPrompt] = useState(""); const iframeRef = useRef(null); const [selectedModel, setSelectedModel] = useState("gemini-2.0-flash"); const [width, setWidth] = useState(400); const [height, setHeight] = useState(400); const [isGenerating, setIsGenerating] = useState(false); const [temperature, setTemperature] = useState(1); const [autoSave, setAutoSave] = useState(false); const [autoGenerate, setAutoGenerate] = useState(false); // Add effect to update temperature when model changes useEffect(() => { setTemperature(selectedModel.includes('thinking') ? 0.7 : 1); }, [selectedModel]); const getTimestamp = () => { const now = new Date(); return now.toISOString().replace(/[:.]/g, '-').slice(0, -5); // Format: YYYY-MM-DDTHH-mm }; const handleScreenshot = useCallback(() => { if (iframeRef.current) { iframeRef.current.contentWindow.postMessage({ type: 'takeScreenshot' }, '*'); } }, []); const handleSaveCode = useCallback(() => { const blob = new Blob([code], { type: 'text/javascript' }); const url = URL.createObjectURL(blob); const link = document.createElement('a'); link.download = `p5js-sketch-${getTimestamp()}.js`; link.href = url; link.click(); URL.revokeObjectURL(url); }, [code]); const updatePreview = useCallback(() => { const newNormalizedCode = normalizeCode(code); if (newNormalizedCode !== normalizedCode) { setNormalizedCode(newNormalizedCode); if (iframeRef.current) { const iframe = iframeRef.current; iframe.srcdoc = getIframeContent(code); } } }, [code, normalizedCode]); // Now generateCode can safely use the functions defined above const generateCode = useCallback(async () => { if (isGenerating) return; setIsGenerating(true); try { const response = await fetch('/api/generate', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ prompt, modelName: selectedModel, temperature: parseFloat(temperature) }), }); const data = await response.json(); if (data.error) { throw new Error(data.error); } setCode(data.code); if (autoGenerate) { // Wait a moment to let the preview update before generating again setTimeout(() => { generateCode(); }, 500); } } catch (error) { console.error('Error generating code:', error); } finally { setIsGenerating(false); } }, [isGenerating, selectedModel, temperature, prompt, autoGenerate]); // Update preview when code changes useEffect(() => { updatePreview(); if (autoSave) { // Add small delay to ensure the sketch is rendered const timer = setTimeout(() => { handleScreenshot(); handleSaveCode(); }, 500); return () => clearTimeout(timer); } }, [code, updatePreview, autoSave, handleScreenshot, handleSaveCode]); // Initial load effect useEffect(() => { if (iframeRef.current) { iframeRef.current.srcdoc = getIframeContent(code); } }, []); // Update the message handler to be more specific useEffect(() => { const handleMessage = (event) => { if (event.data.type === 'screenshot') { const link = document.createElement('a'); link.download = `p5js-sketch-${getTimestamp()}.png`; link.href = event.data.data; link.click(); } }; window.addEventListener('message', handleMessage); return () => window.removeEventListener('message', handleMessage); }, []); const handleKeyPress = (e) => { if (e.key === 'Enter' && !isGenerating) { generateCode(); } }; return (