CodeIDE / src /App.js
FrederickSundeep's picture
commit initial 09-12-2025 002
25fd1e9
raw
history blame
14 kB
import { useState, useEffect, useRef } from "react";
import Editor from "@monaco-editor/react";
import { askAgent } from "./agent/assistant";
import { runCode } from "./agent/runner";
// 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"];
// 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 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);
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 (
<div className={`ide-root ${theme === "vs-dark" ? "ide-dark" : "ide-light"}`}>
{/* Top Menu Bar */}
<div className="ide-menubar">
<div className="ide-menubar-left">
<span className="ide-logo">⚙️ DevMate IDE</span>
{/* File menu */}
<div className="ide-menu-wrapper">
<button
className="ide-menu-item"
onClick={() => toggleMenu("file")}
>
File ▾
</button>
{openMenu === "file" && (
<div className="ide-menu-dropdown">
<button onClick={handleNewFile}>New File</button>
<button onClick={handleSaveLocal}>Save (Local)</button>
<button onClick={handleDownloadFile}>Download File</button>
</div>
)}
</div>
{/* Run menu */}
<div className="ide-menu-wrapper">
<button
className="ide-menu-item"
onClick={() => toggleMenu("run")}
>
Run ▾
</button>
{openMenu === "run" && (
<div className="ide-menu-dropdown">
<button onClick={handleRun}>Run Current File</button>
</div>
)}
</div>
{/* AI menu */}
<div className="ide-menu-wrapper">
<button
className="ide-menu-item"
onClick={() => toggleMenu("ai")}
>
AI ▾
</button>
{openMenu === "ai" && (
<div className="ide-menu-dropdown">
<button onClick={handleAskFix}>Fix Current File</button>
<button onClick={handleExplainSelection}>Explain Selection</button>
</div>
)}
</div>
<button className="ide-menu-item" onClick={() => setOutput("DevMate IDE - simple VS Code style IDE with AI agent and runner.")}>
Help
</button>
</div>
<div className="ide-menubar-right">
<span className="ide-menu-item" onClick={toggleTheme}>
{theme === "vs-dark" ? "☀️ Light" : "🌙 Dark"}
</span>
</div>
</div>
{/* Main body: sidebar + editor + panel */}
<div className="ide-body">
{/* Explorer Sidebar */}
<div className="ide-sidebar">
<div className="ide-sidebar-header">
<span>EXPLORER</span>
<button className="ide-icon-button" onClick={handleNewFile} title="New File">
</button>
</div>
<div className="ide-file-list">
{Object.keys(files).map((fname) => {
const fLang = files[fname].language;
const fMeta =
LANGUAGE_OPTIONS.find((l) => l.id === fLang) ||
LANGUAGE_OPTIONS[0];
return (
<div
key={fname}
className={
"ide-file-item" +
(fname === activeFile ? " ide-file-item-active" : "")
}
onClick={() => setActiveFile(fname)}
>
<span className="ide-file-icon">{fMeta.icon}</span>
<span className="ide-file-name">{fname}</span>
</div>
);
})}
</div>
</div>
{/* Editor & bottom panel */}
<div className="ide-main">
{/* Tabs + quick actions */}
<div className="ide-tabs">
<div className="ide-tab-list">
{Object.keys(files).map((fname) => (
<div
key={fname}
className={
"ide-tab" + (fname === activeFile ? " ide-tab-active" : "")
}
onClick={() => setActiveFile(fname)}
>
<span>{fname}</span>
{fname === activeFile && (
<button
className="ide-tab-close"
onClick={(e) => {
e.stopPropagation();
handleDeleteFile();
}}
>
×
</button>
)}
</div>
))}
</div>
<div className="ide-tab-actions">
<select
value={currentLangId}
onChange={(e) => handleChangeLanguage(e.target.value)}
className="ide-select"
>
{LANGUAGE_OPTIONS.map((lang) => (
<option key={lang.id} value={lang.id}>
{lang.label}
</option>
))}
</select>
<button className="ide-button" onClick={handleRun}>
▶ Run
</button>
<button className="ide-button" onClick={handleAskFix}>
🤖 Fix with AI
</button>
<button className="ide-button" onClick={handleExplainSelection}>
📖 Explain Selection
</button>
</div>
</div>
{/* Editor */}
<div className="ide-editor-wrapper">
<Editor
height="100%"
theme={theme}
language={langMeta.monaco}
value={currentFile?.content}
onChange={updateActiveFileContent}
onMount={(editor) => {
editorRef.current = editor;
}}
options={{
minimap: { enabled: true },
fontSize: 14,
scrollBeyondLastLine: false,
}}
/>
</div>
{/* Bottom Panel: Output + Agent Prompt + Explanation */}
<div className="ide-bottom-panel">
<div className="ide-output-panel">
<div className="ide-panel-header">TERMINAL / OUTPUT</div>
<pre className="ide-output-content">{output}</pre>
</div>
<div className="ide-agent-panel">
<div className="ide-panel-header">AI PROMPT</div>
<textarea
className="ide-agent-textarea"
value={prompt}
onChange={(e) => setPrompt(e.target.value)}
placeholder="Ask AI to refactor, explain, or add features..."
/>
{explanation && (
<div className="ide-explanation">
<div className="ide-panel-header">AI EXPLANATION</div>
<pre className="ide-output-content">{explanation}</pre>
</div>
)}
</div>
</div>
</div>
</div>
{/* Status Bar */}
<div className="ide-statusbar">
<span>{activeFile}</span>
<span>{langMeta.label}</span>
<span>Theme: {theme === "vs-dark" ? "Dark" : "Light"}</span>
</div>
</div>
);
}
export default App;