FrederickSundeep commited on
Commit
25fd1e9
·
1 Parent(s): 3589760

commit initial 09-12-2025 002

Browse files
Files changed (2) hide show
  1. src/App.css +39 -0
  2. src/App.js +430 -45
src/App.css CHANGED
@@ -36,3 +36,42 @@
36
  transform: rotate(360deg);
37
  }
38
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
36
  transform: rotate(360deg);
37
  }
38
  }
39
+
40
+ /* Menu dropdowns */
41
+ .ide-menu-wrapper {
42
+ position: relative;
43
+ }
44
+
45
+ .ide-menu-dropdown {
46
+ position: absolute;
47
+ top: 100%;
48
+ left: 0;
49
+ background: #252526;
50
+ border: 1px solid #3c3c3c;
51
+ min-width: 160px;
52
+ z-index: 50;
53
+ display: flex;
54
+ flex-direction: column;
55
+ }
56
+
57
+ .ide-menu-dropdown button {
58
+ border: none;
59
+ background: transparent;
60
+ color: #ddd;
61
+ text-align: left;
62
+ padding: 4px 10px;
63
+ font-size: 13px;
64
+ cursor: pointer;
65
+ }
66
+
67
+ .ide-menu-dropdown button:hover {
68
+ background: rgba(255, 255, 255, 0.08);
69
+ }
70
+
71
+ /* Explanation section inside AI panel */
72
+ .ide-explanation {
73
+ border-top: 1px solid #333;
74
+ max-height: 50%;
75
+ display: flex;
76
+ flex-direction: column;
77
+ }
src/App.js CHANGED
@@ -1,64 +1,449 @@
1
- import { useState } from "react";
2
  import Editor from "@monaco-editor/react";
3
  import { askAgent } from "./agent/assistant";
4
  import { runCode } from "./agent/runner";
5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6
  function App() {
7
- const [code, setCode] = useState("# Python\nprint('Hello from IDE')");
 
 
 
 
 
 
 
 
 
 
8
  const [output, setOutput] = useState("");
9
  const [prompt, setPrompt] = useState("");
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10
 
11
  const handleRun = async () => {
12
- const res = await runCode(code);
13
- setOutput(res.output);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
  };
15
 
16
- const handleAsk = async () => {
17
- const reply = await askAgent(
18
- `Improve or fix this code and return only the updated code.\n\n${prompt}`,
19
- [{ role: "user", content: code }]
20
- );
21
- setCode(reply);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
22
  };
23
 
24
  return (
25
- <div style={{ display: "flex", height: "100vh", background: "#111" }}>
26
- <div style={{ flex: 3 }}>
27
- <Editor
28
- height="100vh"
29
- theme="vs-dark"
30
- defaultLanguage="python"
31
- value={code}
32
- onChange={setCode}
33
- options={{ minimap: { enabled: true }, fontSize: 14 }}
34
- />
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
35
  </div>
36
 
37
- <div style={{ flex: 1, padding: 16, color: "#fff" }}>
38
- <h3>💻 Output</h3>
39
- <pre
40
- style={{
41
- background: "#000",
42
- padding: 8,
43
- height: "40vh",
44
- overflow: "auto",
45
- borderRadius: 4,
46
- }}
47
- >
48
- {output}
49
- </pre>
50
- <button onClick={handleRun}>▶ Run</button>
51
-
52
- <h3 style={{ marginTop: 24 }}>🤖 Agent Prompt</h3>
53
- <textarea
54
- style={{ width: "100%", height: "20vh" }}
55
- value={prompt}
56
- onChange={(e) => setPrompt(e.target.value)}
57
- placeholder="Explain, optimize, or add a feature..."
58
- />
59
- <button onClick={handleAsk} style={{ marginTop: 8 }}>
60
- Ask Agent
61
- </button>
62
  </div>
63
  </div>
64
  );
 
1
+ import { useState, useEffect, useRef } from "react";
2
  import Editor from "@monaco-editor/react";
3
  import { askAgent } from "./agent/assistant";
4
  import { runCode } from "./agent/runner";
5
 
6
+ // Supported languages in the IDE
7
+ const LANGUAGE_OPTIONS = [
8
+ { id: "python", label: "Python", ext: ".py", icon: "🐍", monaco: "python" },
9
+ { id: "javascript", label: "JavaScript", ext: ".js", icon: "🟨", monaco: "javascript" },
10
+ { id: "typescript", label: "TypeScript", ext: ".ts", icon: "🟦", monaco: "typescript" },
11
+ { id: "cpp", label: "C++", ext: ".cpp", icon: "💠", monaco: "cpp" },
12
+ { id: "c", label: "C", ext: ".c", icon: "🔷", monaco: "c" },
13
+ { id: "java", label: "Java", ext: ".java", icon: "☕", monaco: "java" },
14
+ { id: "html", label: "HTML", ext: ".html", icon: "🌐", monaco: "html" },
15
+ { id: "css", label: "CSS", ext: ".css", icon: "🎨", monaco: "css" },
16
+ { id: "json", label: "JSON", ext: ".json", icon: "🧾", monaco: "json" },
17
+ ];
18
+
19
+ // Languages we actually can run on the backend right now
20
+ const RUNNABLE_LANGS = ["python", "javascript"];
21
+
22
+ // LocalStorage key
23
+ const STORAGE_KEY = "devmate_ide_files";
24
+
25
  function App() {
26
+ const [files, setFiles] = useState({
27
+ "main.py": {
28
+ language: "python",
29
+ content: "# Python\nprint('Hello from IDE')",
30
+ },
31
+ "script.js": {
32
+ language: "javascript",
33
+ content: "console.log('Hello from JS');",
34
+ },
35
+ });
36
+ const [activeFile, setActiveFile] = useState("main.py");
37
  const [output, setOutput] = useState("");
38
  const [prompt, setPrompt] = useState("");
39
+ const [explanation, setExplanation] = useState("");
40
+ const [theme, setTheme] = useState("vs-dark"); // "vs-dark" | "light"
41
+ const [openMenu, setOpenMenu] = useState(null); // "file" | "run" | "ai" | null
42
+
43
+ const editorRef = useRef(null);
44
+
45
+ const currentFile = files[activeFile];
46
+ const currentLangId = currentFile?.language || "python";
47
+ const langMeta =
48
+ LANGUAGE_OPTIONS.find((l) => l.id === currentLangId) || LANGUAGE_OPTIONS[0];
49
+
50
+ // ----- LocalStorage: load on mount -----
51
+ useEffect(() => {
52
+ try {
53
+ const saved = localStorage.getItem(STORAGE_KEY);
54
+ if (saved) {
55
+ const parsed = JSON.parse(saved);
56
+ if (parsed && typeof parsed === "object") {
57
+ setFiles(parsed.files || files);
58
+ setActiveFile(parsed.activeFile || Object.keys(parsed.files || files)[0]);
59
+ }
60
+ }
61
+ } catch (e) {
62
+ console.warn("Failed to load saved files:", e);
63
+ }
64
+ // eslint-disable-next-line react-hooks/exhaustive-deps
65
+ }, []);
66
+
67
+ // ----- LocalStorage: save whenever files/activeFile change -----
68
+ useEffect(() => {
69
+ try {
70
+ localStorage.setItem(
71
+ STORAGE_KEY,
72
+ JSON.stringify({ files, activeFile })
73
+ );
74
+ } catch (e) {
75
+ console.warn("Failed to save files:", e);
76
+ }
77
+ }, [files, activeFile]);
78
+
79
+ const updateActiveFileContent = (value) => {
80
+ if (!activeFile) return;
81
+ setFiles((prev) => ({
82
+ ...prev,
83
+ [activeFile]: {
84
+ ...prev[activeFile],
85
+ content: value ?? "",
86
+ },
87
+ }));
88
+ };
89
 
90
  const handleRun = async () => {
91
+ if (!currentFile) return;
92
+
93
+ if (!RUNNABLE_LANGS.includes(currentLangId)) {
94
+ setOutput(
95
+ `⚠️ Run is only supported for: ${RUNNABLE_LANGS.join(
96
+ ", "
97
+ )}.\nSelected language: ${currentLangId}`
98
+ );
99
+ return;
100
+ }
101
+
102
+ try {
103
+ const res = await runCode(currentFile.content, currentLangId);
104
+ setOutput(res.output ?? "");
105
+ } catch (err) {
106
+ setOutput(`Error running code: ${String(err)}`);
107
+ }
108
+ setOpenMenu(null);
109
  };
110
 
111
+ const handleAskFix = async () => {
112
+ if (!currentFile) return;
113
+
114
+ const fullPrompt = `
115
+ You are an AI coding assistant inside an online IDE.
116
+ Improve, debug, or refactor the following ${currentLangId} code.
117
+ Return ONLY the updated code, with no explanation.
118
+
119
+ User request/hint (optional):
120
+ ${prompt || "(no extra hint)"}
121
+
122
+ Code:
123
+ ${currentFile.content}
124
+ `.trim();
125
+
126
+ const reply = await askAgent(fullPrompt, [
127
+ { role: "user", content: currentFile.content },
128
+ ]);
129
+
130
+ updateActiveFileContent(reply);
131
+ setOpenMenu(null);
132
+ };
133
+
134
+ const handleExplainSelection = async () => {
135
+ if (!currentFile) return;
136
+ const editor = editorRef.current;
137
+
138
+ let selectedCode = "";
139
+ if (editor) {
140
+ const model = editor.getModel();
141
+ const selection = editor.getSelection();
142
+ if (model && selection) {
143
+ selectedCode = model.getValueInRange(selection);
144
+ }
145
+ }
146
+
147
+ if (!selectedCode.trim()) {
148
+ selectedCode = currentFile.content;
149
+ }
150
+
151
+ const explainPrompt = `
152
+ You are an AI code explainer inside an IDE.
153
+ Explain clearly and concisely what the following ${currentLangId} code does,
154
+ including edge cases and any potential issues.
155
+
156
+ Code:
157
+ ${selectedCode}
158
+ `.trim();
159
+
160
+ const reply = await askAgent(explainPrompt, [
161
+ { role: "user", content: selectedCode },
162
+ ]);
163
+
164
+ setExplanation(reply);
165
+ setOpenMenu(null);
166
+ };
167
+
168
+ const handleNewFile = () => {
169
+ const base = "untitled";
170
+ let idx = 1;
171
+ let name = `${base}${idx}${langMeta.ext}`;
172
+ while (files[name]) {
173
+ idx += 1;
174
+ name = `${base}${idx}${langMeta.ext}`;
175
+ }
176
+
177
+ setFiles((prev) => ({
178
+ ...prev,
179
+ [name]: {
180
+ language: currentLangId,
181
+ content: `// ${langMeta.label} file\n`,
182
+ },
183
+ }));
184
+ setActiveFile(name);
185
+ setOpenMenu(null);
186
+ };
187
+
188
+ const handleDeleteFile = () => {
189
+ if (!activeFile) return;
190
+ if (Object.keys(files).length === 1) return; // keep at least one
191
+
192
+ const newFiles = { ...files };
193
+ delete newFiles[activeFile];
194
+
195
+ const remaining = Object.keys(newFiles);
196
+ setFiles(newFiles);
197
+ setActiveFile(remaining[0] || "");
198
+ };
199
+
200
+ const handleChangeLanguage = (langId) => {
201
+ setFiles((prev) => ({
202
+ ...prev,
203
+ [activeFile]: {
204
+ ...prev[activeFile],
205
+ language: langId,
206
+ },
207
+ }));
208
+ };
209
+
210
+ const toggleTheme = () => {
211
+ setTheme((prev) => (prev === "vs-dark" ? "light" : "vs-dark"));
212
+ };
213
+
214
+ const toggleMenu = (menuName) => {
215
+ setOpenMenu((prev) => (prev === menuName ? null : menuName));
216
+ };
217
+
218
+ const handleSaveLocal = () => {
219
+ // Already persisted automatically; this is more like a user-triggered "save" action
220
+ try {
221
+ localStorage.setItem(
222
+ STORAGE_KEY,
223
+ JSON.stringify({ files, activeFile })
224
+ );
225
+ setOutput("💾 Saved project to browser storage.");
226
+ } catch (e) {
227
+ setOutput(`Failed to save: ${String(e)}`);
228
+ }
229
+ setOpenMenu(null);
230
+ };
231
+
232
+ const handleDownloadFile = () => {
233
+ if (!currentFile || !activeFile) return;
234
+ const blob = new Blob([currentFile.content], { type: "text/plain;charset=utf-8" });
235
+ const url = window.URL.createObjectURL(blob);
236
+ const a = document.createElement("a");
237
+ a.href = url;
238
+ a.download = activeFile;
239
+ a.click();
240
+ window.URL.revokeObjectURL(url);
241
+ setOpenMenu(null);
242
  };
243
 
244
  return (
245
+ <div className={`ide-root ${theme === "vs-dark" ? "ide-dark" : "ide-light"}`}>
246
+ {/* Top Menu Bar */}
247
+ <div className="ide-menubar">
248
+ <div className="ide-menubar-left">
249
+ <span className="ide-logo">⚙️ DevMate IDE</span>
250
+
251
+ {/* File menu */}
252
+ <div className="ide-menu-wrapper">
253
+ <button
254
+ className="ide-menu-item"
255
+ onClick={() => toggleMenu("file")}
256
+ >
257
+ File ▾
258
+ </button>
259
+ {openMenu === "file" && (
260
+ <div className="ide-menu-dropdown">
261
+ <button onClick={handleNewFile}>New File</button>
262
+ <button onClick={handleSaveLocal}>Save (Local)</button>
263
+ <button onClick={handleDownloadFile}>Download File</button>
264
+ </div>
265
+ )}
266
+ </div>
267
+
268
+ {/* Run menu */}
269
+ <div className="ide-menu-wrapper">
270
+ <button
271
+ className="ide-menu-item"
272
+ onClick={() => toggleMenu("run")}
273
+ >
274
+ Run ▾
275
+ </button>
276
+ {openMenu === "run" && (
277
+ <div className="ide-menu-dropdown">
278
+ <button onClick={handleRun}>Run Current File</button>
279
+ </div>
280
+ )}
281
+ </div>
282
+
283
+ {/* AI menu */}
284
+ <div className="ide-menu-wrapper">
285
+ <button
286
+ className="ide-menu-item"
287
+ onClick={() => toggleMenu("ai")}
288
+ >
289
+ AI ▾
290
+ </button>
291
+ {openMenu === "ai" && (
292
+ <div className="ide-menu-dropdown">
293
+ <button onClick={handleAskFix}>Fix Current File</button>
294
+ <button onClick={handleExplainSelection}>Explain Selection</button>
295
+ </div>
296
+ )}
297
+ </div>
298
+
299
+ <button className="ide-menu-item" onClick={() => setOutput("DevMate IDE - simple VS Code style IDE with AI agent and runner.")}>
300
+ Help
301
+ </button>
302
+ </div>
303
+
304
+ <div className="ide-menubar-right">
305
+ <span className="ide-menu-item" onClick={toggleTheme}>
306
+ {theme === "vs-dark" ? "☀️ Light" : "🌙 Dark"}
307
+ </span>
308
+ </div>
309
+ </div>
310
+
311
+ {/* Main body: sidebar + editor + panel */}
312
+ <div className="ide-body">
313
+ {/* Explorer Sidebar */}
314
+ <div className="ide-sidebar">
315
+ <div className="ide-sidebar-header">
316
+ <span>EXPLORER</span>
317
+ <button className="ide-icon-button" onClick={handleNewFile} title="New File">
318
+
319
+ </button>
320
+ </div>
321
+ <div className="ide-file-list">
322
+ {Object.keys(files).map((fname) => {
323
+ const fLang = files[fname].language;
324
+ const fMeta =
325
+ LANGUAGE_OPTIONS.find((l) => l.id === fLang) ||
326
+ LANGUAGE_OPTIONS[0];
327
+
328
+ return (
329
+ <div
330
+ key={fname}
331
+ className={
332
+ "ide-file-item" +
333
+ (fname === activeFile ? " ide-file-item-active" : "")
334
+ }
335
+ onClick={() => setActiveFile(fname)}
336
+ >
337
+ <span className="ide-file-icon">{fMeta.icon}</span>
338
+ <span className="ide-file-name">{fname}</span>
339
+ </div>
340
+ );
341
+ })}
342
+ </div>
343
+ </div>
344
+
345
+ {/* Editor & bottom panel */}
346
+ <div className="ide-main">
347
+ {/* Tabs + quick actions */}
348
+ <div className="ide-tabs">
349
+ <div className="ide-tab-list">
350
+ {Object.keys(files).map((fname) => (
351
+ <div
352
+ key={fname}
353
+ className={
354
+ "ide-tab" + (fname === activeFile ? " ide-tab-active" : "")
355
+ }
356
+ onClick={() => setActiveFile(fname)}
357
+ >
358
+ <span>{fname}</span>
359
+ {fname === activeFile && (
360
+ <button
361
+ className="ide-tab-close"
362
+ onClick={(e) => {
363
+ e.stopPropagation();
364
+ handleDeleteFile();
365
+ }}
366
+ >
367
+ ×
368
+ </button>
369
+ )}
370
+ </div>
371
+ ))}
372
+ </div>
373
+
374
+ <div className="ide-tab-actions">
375
+ <select
376
+ value={currentLangId}
377
+ onChange={(e) => handleChangeLanguage(e.target.value)}
378
+ className="ide-select"
379
+ >
380
+ {LANGUAGE_OPTIONS.map((lang) => (
381
+ <option key={lang.id} value={lang.id}>
382
+ {lang.label}
383
+ </option>
384
+ ))}
385
+ </select>
386
+ <button className="ide-button" onClick={handleRun}>
387
+ ▶ Run
388
+ </button>
389
+ <button className="ide-button" onClick={handleAskFix}>
390
+ 🤖 Fix with AI
391
+ </button>
392
+ <button className="ide-button" onClick={handleExplainSelection}>
393
+ 📖 Explain Selection
394
+ </button>
395
+ </div>
396
+ </div>
397
+
398
+ {/* Editor */}
399
+ <div className="ide-editor-wrapper">
400
+ <Editor
401
+ height="100%"
402
+ theme={theme}
403
+ language={langMeta.monaco}
404
+ value={currentFile?.content}
405
+ onChange={updateActiveFileContent}
406
+ onMount={(editor) => {
407
+ editorRef.current = editor;
408
+ }}
409
+ options={{
410
+ minimap: { enabled: true },
411
+ fontSize: 14,
412
+ scrollBeyondLastLine: false,
413
+ }}
414
+ />
415
+ </div>
416
+
417
+ {/* Bottom Panel: Output + Agent Prompt + Explanation */}
418
+ <div className="ide-bottom-panel">
419
+ <div className="ide-output-panel">
420
+ <div className="ide-panel-header">TERMINAL / OUTPUT</div>
421
+ <pre className="ide-output-content">{output}</pre>
422
+ </div>
423
+ <div className="ide-agent-panel">
424
+ <div className="ide-panel-header">AI PROMPT</div>
425
+ <textarea
426
+ className="ide-agent-textarea"
427
+ value={prompt}
428
+ onChange={(e) => setPrompt(e.target.value)}
429
+ placeholder="Ask AI to refactor, explain, or add features..."
430
+ />
431
+ {explanation && (
432
+ <div className="ide-explanation">
433
+ <div className="ide-panel-header">AI EXPLANATION</div>
434
+ <pre className="ide-output-content">{explanation}</pre>
435
+ </div>
436
+ )}
437
+ </div>
438
+ </div>
439
+ </div>
440
  </div>
441
 
442
+ {/* Status Bar */}
443
+ <div className="ide-statusbar">
444
+ <span>{activeFile}</span>
445
+ <span>{langMeta.label}</span>
446
+ <span>Theme: {theme === "vs-dark" ? "Dark" : "Light"}</span>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
447
  </div>
448
  </div>
449
  );