import { useState, useEffect, useRef } from "react"; import Editor from "@monaco-editor/react"; import { askAgent } from "./agent/assistant"; import { runCode } from "./agent/runner"; import "./App.css"; // Supported languages in the IDE const LANGUAGE_OPTIONS = [ { id: "python", label: "Python", ext: ".py", icon: "๐", monaco: "python" }, { id: "javascript", label: "JavaScript", ext: ".js", icon: "๐จ", monaco: "javascript" }, { id: "typescript", label: "TypeScript", ext: ".ts", icon: "๐ฆ", monaco: "typescript" }, { id: "cpp", label: "C++", ext: ".cpp", icon: "๐ ", monaco: "cpp" }, { id: "c", label: "C", ext: ".c", icon: "๐ท", monaco: "c" }, { id: "java", label: "Java", ext: ".java", icon: "โ", monaco: "java" }, { id: "html", label: "HTML", ext: ".html", icon: "๐", monaco: "html" }, { id: "css", label: "CSS", ext: ".css", icon: "๐จ", monaco: "css" }, { id: "json", label: "JSON", ext: ".json", icon: "๐งพ", monaco: "json" }, ]; // Languages we actually can run on the backend right now const RUNNABLE_LANGS = ["python", "javascript","java"]; // LocalStorage key const STORAGE_KEY = "devmate_ide_files"; function App() { const [files, setFiles] = useState({ "main.py": { language: "python", content: "# Python\nprint('Hello from IDE')", }, "script.js": { language: "javascript", content: "console.log('Hello from JS');", }, }); const [activeFile, setActiveFile] = useState("main.py"); const [output, setOutput] = useState(""); const [prompt, setPrompt] = useState(""); const [explanation, setExplanation] = useState(""); const [theme, setTheme] = useState("vs-dark"); // "vs-dark" | "light" const [openMenu, setOpenMenu] = useState(null); // "file" | "run" | "ai" | null const [stdin, setStdin] = useState(""); const editorRef = useRef(null); const currentFile = files[activeFile]; const currentLangId = currentFile?.language || "python"; const langMeta = LANGUAGE_OPTIONS.find((l) => l.id === currentLangId) || LANGUAGE_OPTIONS[0]; // ----- LocalStorage: load on mount ----- useEffect(() => { try { const saved = localStorage.getItem(STORAGE_KEY); if (saved) { const parsed = JSON.parse(saved); if (parsed && typeof parsed === "object") { setFiles(parsed.files || files); setActiveFile(parsed.activeFile || Object.keys(parsed.files || files)[0]); } } } catch (e) { console.warn("Failed to load saved files:", e); } // eslint-disable-next-line react-hooks/exhaustive-deps }, []); // ----- LocalStorage: save whenever files/activeFile change ----- useEffect(() => { try { localStorage.setItem( STORAGE_KEY, JSON.stringify({ files, activeFile }) ); } catch (e) { console.warn("Failed to save files:", e); } }, [files, activeFile]); const updateActiveFileContent = (value) => { if (!activeFile) return; setFiles((prev) => ({ ...prev, [activeFile]: { ...prev[activeFile], content: value ?? "", }, })); }; const handleRun = async () => { if (!currentFile) return; if (!RUNNABLE_LANGS.includes(currentLangId)) { setOutput( `โ ๏ธ Run is only supported for: ${RUNNABLE_LANGS.join( ", " )}.\nSelected language: ${currentLangId}` ); return; } try { const res = await runCode(currentFile.content, currentLangId, stdin); setOutput(res.output ?? ""); } catch (err) { setOutput(`Error running code: ${String(err)}`); } setOpenMenu(null); }; const handleAskFix = async () => { if (!currentFile) return; const fullPrompt = ` You are an AI coding assistant inside an online IDE. Improve, debug, or refactor the following ${currentLangId} code. Return ONLY the updated code, with no explanation. User request/hint (optional): ${prompt || "(no extra hint)"} Code: ${currentFile.content} `.trim(); const reply = await askAgent(fullPrompt, [ { role: "user", content: currentFile.content }, ]); updateActiveFileContent(reply); setOpenMenu(null); }; const handleExplainSelection = async () => { if (!currentFile) return; const editor = editorRef.current; let selectedCode = ""; if (editor) { const model = editor.getModel(); const selection = editor.getSelection(); if (model && selection) { selectedCode = model.getValueInRange(selection); } } if (!selectedCode.trim()) { selectedCode = currentFile.content; } const explainPrompt = ` You are an AI code explainer inside an IDE. Explain clearly and concisely what the following ${currentLangId} code does, including edge cases and any potential issues. Code: ${selectedCode} `.trim(); const reply = await askAgent(explainPrompt, [ { role: "user", content: selectedCode }, ]); setExplanation(reply); setOpenMenu(null); }; const handleNewFile = () => { const base = "untitled"; let idx = 1; let name = `${base}${idx}${langMeta.ext}`; while (files[name]) { idx += 1; name = `${base}${idx}${langMeta.ext}`; } setFiles((prev) => ({ ...prev, [name]: { language: currentLangId, content: `// ${langMeta.label} file\n`, }, })); setActiveFile(name); setOpenMenu(null); }; const handleDeleteFile = () => { if (!activeFile) return; if (Object.keys(files).length === 1) return; // keep at least one const newFiles = { ...files }; delete newFiles[activeFile]; const remaining = Object.keys(newFiles); setFiles(newFiles); setActiveFile(remaining[0] || ""); }; const handleChangeLanguage = (langId) => { setFiles((prev) => ({ ...prev, [activeFile]: { ...prev[activeFile], language: langId, }, })); }; const toggleTheme = () => { setTheme((prev) => (prev === "vs-dark" ? "light" : "vs-dark")); }; const toggleMenu = (menuName) => { setOpenMenu((prev) => (prev === menuName ? null : menuName)); }; const handleSaveLocal = () => { // Already persisted automatically; this is more like a user-triggered "save" action try { localStorage.setItem( STORAGE_KEY, JSON.stringify({ files, activeFile }) ); setOutput("๐พ Saved project to browser storage."); } catch (e) { setOutput(`Failed to save: ${String(e)}`); } setOpenMenu(null); }; const handleDownloadFile = () => { if (!currentFile || !activeFile) return; const blob = new Blob([currentFile.content], { type: "text/plain;charset=utf-8" }); const url = window.URL.createObjectURL(blob); const a = document.createElement("a"); a.href = url; a.download = activeFile; a.click(); window.URL.revokeObjectURL(url); setOpenMenu(null); }; return (
{output}
setStdin(e.target.value)}
className="ide-input-box"
/>