FrederickSundeep commited on
Commit
ac3107a
·
1 Parent(s): 6243bd8

commit initial 09-12-2025 009

Browse files
Files changed (7) hide show
  1. package-lock.json +80 -4
  2. package.json +1 -0
  3. src/App.css +159 -0
  4. src/App.js +205 -422
  5. src/fileStore.js +125 -0
  6. src/problemParser.js +32 -0
  7. src/zipExport.js +25 -0
package-lock.json CHANGED
@@ -14,6 +14,7 @@
14
  "@testing-library/react": "^16.3.0",
15
  "@testing-library/user-event": "^13.5.0",
16
  "axios": "^1.13.2",
 
17
  "react": "^19.1.0",
18
  "react-dom": "^19.1.0",
19
  "react-hot-toast": "^2.6.0",
@@ -9109,6 +9110,12 @@
9109
  "node": ">= 4"
9110
  }
9111
  },
 
 
 
 
 
 
9112
  "node_modules/immer": {
9113
  "version": "9.0.21",
9114
  "resolved": "https://registry.npmjs.org/immer/-/immer-9.0.21.tgz",
@@ -10968,6 +10975,54 @@
10968
  "node": ">=4.0"
10969
  }
10970
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10971
  "node_modules/keyv": {
10972
  "version": "4.5.4",
10973
  "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
@@ -11054,6 +11109,15 @@
11054
  "node": ">= 0.8.0"
11055
  }
11056
  },
 
 
 
 
 
 
 
 
 
11057
  "node_modules/lilconfig": {
11058
  "version": "2.1.0",
11059
  "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz",
@@ -11903,6 +11967,12 @@
11903
  "node": ">=6"
11904
  }
11905
  },
 
 
 
 
 
 
11906
  "node_modules/param-case": {
11907
  "version": "3.0.4",
11908
  "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz",
@@ -14848,6 +14918,12 @@
14848
  "node": ">= 0.4"
14849
  }
14850
  },
 
 
 
 
 
 
14851
  "node_modules/setprototypeof": {
14852
  "version": "1.2.0",
14853
  "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
@@ -16359,9 +16435,9 @@
16359
  }
16360
  },
16361
  "node_modules/typescript": {
16362
- "version": "5.9.3",
16363
- "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
16364
- "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
16365
  "license": "Apache-2.0",
16366
  "peer": true,
16367
  "bin": {
@@ -16369,7 +16445,7 @@
16369
  "tsserver": "bin/tsserver"
16370
  },
16371
  "engines": {
16372
- "node": ">=14.17"
16373
  }
16374
  },
16375
  "node_modules/unbox-primitive": {
 
14
  "@testing-library/react": "^16.3.0",
15
  "@testing-library/user-event": "^13.5.0",
16
  "axios": "^1.13.2",
17
+ "jszip": "^3.10.1",
18
  "react": "^19.1.0",
19
  "react-dom": "^19.1.0",
20
  "react-hot-toast": "^2.6.0",
 
9110
  "node": ">= 4"
9111
  }
9112
  },
9113
+ "node_modules/immediate": {
9114
+ "version": "3.0.6",
9115
+ "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz",
9116
+ "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==",
9117
+ "license": "MIT"
9118
+ },
9119
  "node_modules/immer": {
9120
  "version": "9.0.21",
9121
  "resolved": "https://registry.npmjs.org/immer/-/immer-9.0.21.tgz",
 
10975
  "node": ">=4.0"
10976
  }
10977
  },
10978
+ "node_modules/jszip": {
10979
+ "version": "3.10.1",
10980
+ "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz",
10981
+ "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==",
10982
+ "license": "(MIT OR GPL-3.0-or-later)",
10983
+ "dependencies": {
10984
+ "lie": "~3.3.0",
10985
+ "pako": "~1.0.2",
10986
+ "readable-stream": "~2.3.6",
10987
+ "setimmediate": "^1.0.5"
10988
+ }
10989
+ },
10990
+ "node_modules/jszip/node_modules/isarray": {
10991
+ "version": "1.0.0",
10992
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
10993
+ "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==",
10994
+ "license": "MIT"
10995
+ },
10996
+ "node_modules/jszip/node_modules/readable-stream": {
10997
+ "version": "2.3.8",
10998
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
10999
+ "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
11000
+ "license": "MIT",
11001
+ "dependencies": {
11002
+ "core-util-is": "~1.0.0",
11003
+ "inherits": "~2.0.3",
11004
+ "isarray": "~1.0.0",
11005
+ "process-nextick-args": "~2.0.0",
11006
+ "safe-buffer": "~5.1.1",
11007
+ "string_decoder": "~1.1.1",
11008
+ "util-deprecate": "~1.0.1"
11009
+ }
11010
+ },
11011
+ "node_modules/jszip/node_modules/safe-buffer": {
11012
+ "version": "5.1.2",
11013
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
11014
+ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
11015
+ "license": "MIT"
11016
+ },
11017
+ "node_modules/jszip/node_modules/string_decoder": {
11018
+ "version": "1.1.1",
11019
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
11020
+ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
11021
+ "license": "MIT",
11022
+ "dependencies": {
11023
+ "safe-buffer": "~5.1.0"
11024
+ }
11025
+ },
11026
  "node_modules/keyv": {
11027
  "version": "4.5.4",
11028
  "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
 
11109
  "node": ">= 0.8.0"
11110
  }
11111
  },
11112
+ "node_modules/lie": {
11113
+ "version": "3.3.0",
11114
+ "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz",
11115
+ "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==",
11116
+ "license": "MIT",
11117
+ "dependencies": {
11118
+ "immediate": "~3.0.5"
11119
+ }
11120
+ },
11121
  "node_modules/lilconfig": {
11122
  "version": "2.1.0",
11123
  "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz",
 
11967
  "node": ">=6"
11968
  }
11969
  },
11970
+ "node_modules/pako": {
11971
+ "version": "1.0.11",
11972
+ "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
11973
+ "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==",
11974
+ "license": "(MIT AND Zlib)"
11975
+ },
11976
  "node_modules/param-case": {
11977
  "version": "3.0.4",
11978
  "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz",
 
14918
  "node": ">= 0.4"
14919
  }
14920
  },
14921
+ "node_modules/setimmediate": {
14922
+ "version": "1.0.5",
14923
+ "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
14924
+ "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==",
14925
+ "license": "MIT"
14926
+ },
14927
  "node_modules/setprototypeof": {
14928
  "version": "1.2.0",
14929
  "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
 
16435
  }
16436
  },
16437
  "node_modules/typescript": {
16438
+ "version": "4.9.5",
16439
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz",
16440
+ "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==",
16441
  "license": "Apache-2.0",
16442
  "peer": true,
16443
  "bin": {
 
16445
  "tsserver": "bin/tsserver"
16446
  },
16447
  "engines": {
16448
+ "node": ">=4.2.0"
16449
  }
16450
  },
16451
  "node_modules/unbox-primitive": {
package.json CHANGED
@@ -9,6 +9,7 @@
9
  "@testing-library/react": "^16.3.0",
10
  "@testing-library/user-event": "^13.5.0",
11
  "axios": "^1.13.2",
 
12
  "react": "^19.1.0",
13
  "react-dom": "^19.1.0",
14
  "react-hot-toast": "^2.6.0",
 
9
  "@testing-library/react": "^16.3.0",
10
  "@testing-library/user-event": "^13.5.0",
11
  "axios": "^1.13.2",
12
+ "jszip": "^3.10.1",
13
  "react": "^19.1.0",
14
  "react-dom": "^19.1.0",
15
  "react-hot-toast": "^2.6.0",
src/App.css CHANGED
@@ -364,3 +364,162 @@
364
  gap: 16px;
365
  padding: 0 10px;
366
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
364
  gap: 16px;
365
  padding: 0 10px;
366
  }
367
+
368
+
369
+ /* Global layout */
370
+ .ide-root {
371
+ display: flex;
372
+ flex-direction: column;
373
+ height: 100vh;
374
+ font-family: "Consolas", monospace;
375
+ }
376
+
377
+ /* Dark & light themes */
378
+ .ide-dark { background: #1e1e1e; color: #eee; }
379
+ .ide-light { background: #fafafa; color: #111; }
380
+
381
+ /* Menubar */
382
+ .ide-menubar {
383
+ display: flex;
384
+ justify-content: space-between;
385
+ padding: 4px 10px;
386
+ background: #252526;
387
+ color: #eee;
388
+ font-size: 14px;
389
+ border-bottom: 1px solid #444;
390
+ }
391
+ .ide-menubar button {
392
+ margin-right: 6px;
393
+ background: transparent;
394
+ border: none;
395
+ color: inherit;
396
+ cursor: pointer;
397
+ }
398
+ .ide-logo {
399
+ font-weight: bold;
400
+ margin-right: 12px;
401
+ }
402
+
403
+ /* Body */
404
+ .ide-body {
405
+ flex: 1;
406
+ display: flex;
407
+ overflow: hidden;
408
+ }
409
+
410
+ /* Sidebar */
411
+ .ide-sidebar {
412
+ width: 210px;
413
+ background: #252526;
414
+ padding: 5px;
415
+ border-right: 1px solid #444;
416
+ overflow-y: auto;
417
+ font-size: 14px;
418
+ }
419
+ .tree-item {
420
+ padding: 4px 6px;
421
+ cursor: pointer;
422
+ user-select: none;
423
+ }
424
+ .tree-item:hover {
425
+ background: #3a3d41;
426
+ }
427
+
428
+ /* Editor */
429
+ .ide-main {
430
+ flex: 1;
431
+ display: flex;
432
+ flex-direction: column;
433
+ }
434
+
435
+ /* Bottom panels */
436
+ .ide-panels {
437
+ background: #1e1e1e;
438
+ border-top: 1px solid #444;
439
+ padding: 6px;
440
+ font-size: 13px;
441
+ height: 28%;
442
+ overflow-y: auto;
443
+ }
444
+
445
+ .ide-output {
446
+ background: #000;
447
+ color: #0f0;
448
+ padding: 6px;
449
+ min-height: 60px;
450
+ max-height: 120px;
451
+ overflow-y: auto;
452
+ border-radius: 4px;
453
+ }
454
+ .ide-input-box {
455
+ width: 100%;
456
+ margin-top: 4px;
457
+ padding: 4px;
458
+ }
459
+
460
+ /* Problems panel */
461
+ .ide-problems-panel {
462
+ margin-top: 6px;
463
+ padding: 4px;
464
+ background: #3c0000;
465
+ color: #fff;
466
+ border-left: 3px solid red;
467
+ font-size: 12px;
468
+ }
469
+
470
+ /* AI popup */
471
+ .ai-popup {
472
+ position: absolute;
473
+ bottom: 35%;
474
+ right: 5px;
475
+ width: 230px;
476
+ background: #333;
477
+ border: 1px solid #444;
478
+ border-radius: 6px;
479
+ padding: 4px;
480
+ animation: fadeIn 0.25s ease-in-out;
481
+ }
482
+ .ai-suggest {
483
+ padding: 4px;
484
+ cursor: pointer;
485
+ }
486
+ .ai-suggest:hover {
487
+ background: #444;
488
+ }
489
+
490
+ /* Search popup */
491
+ .search-dialog {
492
+ position: absolute;
493
+ right: 10px;
494
+ top: 50px;
495
+ background: #333;
496
+ padding: 10px;
497
+ border: 1px solid #444;
498
+ border-radius: 5px;
499
+ }
500
+ .search-dialog input {
501
+ width: 200px;
502
+ }
503
+
504
+ /* Context menu */
505
+ .ide-context-menu {
506
+ position: absolute;
507
+ background: #333;
508
+ color: #eee;
509
+ border: 1px solid #555;
510
+ border-radius: 5px;
511
+ padding: 4px;
512
+ }
513
+ .ide-context-menu div {
514
+ padding: 4px 8px;
515
+ cursor: pointer;
516
+ }
517
+ .ide-context-menu div:hover {
518
+ background: #555;
519
+ }
520
+
521
+ /* Animation */
522
+ @keyframes fadeIn {
523
+ from { opacity: 0; transform: translateY(5px); }
524
+ to { opacity: 1; transform: translateY(0); }
525
+ }
src/App.js CHANGED
@@ -2,502 +2,285 @@ 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
  import "./App.css";
6
 
7
- // Supported languages in the IDE
8
  const LANGUAGE_OPTIONS = [
9
- { id: "python", label: "Python", ext: ".py", icon: "🐍", monaco: "python" },
10
- { id: "javascript", label: "JavaScript", ext: ".js", icon: "🟨", monaco: "javascript" },
11
- { id: "typescript", label: "TypeScript", ext: ".ts", icon: "🟦", monaco: "typescript" },
12
- { id: "cpp", label: "C++", ext: ".cpp", icon: "💠", monaco: "cpp" },
13
- { id: "c", label: "C", ext: ".c", icon: "🔷", monaco: "c" },
14
- { id: "java", label: "Java", ext: ".java", icon: "☕", monaco: "java" },
15
- { id: "html", label: "HTML", ext: ".html", icon: "🌐", monaco: "html" },
16
- { id: "css", label: "CSS", ext: ".css", icon: "🎨", monaco: "css" },
17
- { id: "json", label: "JSON", ext: ".json", icon: "🧾", monaco: "json" },
18
  ];
19
 
20
- // Languages we actually can run on the backend right now
21
- const RUNNABLE_LANGS = ["python", "javascript","java"];
22
-
23
- // LocalStorage key
24
- const STORAGE_KEY = "devmate_ide_files";
25
 
 
26
  function App() {
27
- const [files, setFiles] = useState({
28
- "main.py": {
29
- language: "python",
30
- content: "# Python\nprint('Hello from IDE')",
31
- },
32
- "script.js": {
33
- language: "javascript",
34
- content: "console.log('Hello from JS');",
35
- },
36
- });
37
- const [activeFile, setActiveFile] = useState("main.py");
38
  const [output, setOutput] = useState("");
39
  const [prompt, setPrompt] = useState("");
40
  const [explanation, setExplanation] = useState("");
41
- const [theme, setTheme] = useState("vs-dark"); // "vs-dark" | "light"
42
- const [openMenu, setOpenMenu] = useState(null); // "file" | "run" | "ai" | null
43
  const [stdin, setStdin] = useState("");
 
 
 
 
 
 
44
  const editorRef = useRef(null);
45
 
46
- const currentFile = files[activeFile];
47
- const currentLangId = currentFile?.language || "python";
48
  const langMeta =
49
- LANGUAGE_OPTIONS.find((l) => l.id === currentLangId) || LANGUAGE_OPTIONS[0];
 
 
50
 
51
- // ----- LocalStorage: load on mount -----
52
  useEffect(() => {
53
- try {
54
- const saved = localStorage.getItem(STORAGE_KEY);
55
- if (saved) {
56
- const parsed = JSON.parse(saved);
57
- if (parsed && typeof parsed === "object") {
58
- setFiles(parsed.files || files);
59
- setActiveFile(parsed.activeFile || Object.keys(parsed.files || files)[0]);
60
- }
61
- }
62
- } catch (e) {
63
- console.warn("Failed to load saved files:", e);
64
- }
65
- // eslint-disable-next-line react-hooks/exhaustive-deps
66
- }, []);
67
 
68
- // ----- LocalStorage: save whenever files/activeFile change -----
69
- useEffect(() => {
70
- try {
71
- localStorage.setItem(
72
- STORAGE_KEY,
73
- JSON.stringify({ files, activeFile })
74
- );
75
- } catch (e) {
76
- console.warn("Failed to save files:", e);
77
- }
78
- }, [files, activeFile]);
79
-
80
- const updateActiveFileContent = (value) => {
81
- if (!activeFile) return;
82
- setFiles((prev) => ({
83
- ...prev,
84
- [activeFile]: {
85
- ...prev[activeFile],
86
- content: value ?? "",
87
- },
88
- }));
89
  };
90
 
91
- const handleRun = async () => {
92
- if (!currentFile) return;
93
-
94
- if (!RUNNABLE_LANGS.includes(currentLangId)) {
95
- setOutput(
96
- `⚠️ Run is only supported for: ${RUNNABLE_LANGS.join(
97
- ", "
98
- )}.\nSelected language: ${currentLangId}`
99
- );
100
- return;
101
- }
102
-
103
- try {
104
- const res = await runCode(currentFile.content, currentLangId, stdin);
105
- setOutput(res.output ?? "");
106
- } catch (err) {
107
- setOutput(`Error running code: ${String(err)}`);
108
- }
109
- setOpenMenu(null);
110
  };
111
 
112
- const handleAskFix = async () => {
113
- if (!currentFile) return;
114
-
115
- const fullPrompt = `
116
- You are an AI coding assistant inside an online IDE.
117
- Improve, debug, or refactor the following ${currentLangId} code.
118
- Return ONLY the updated code, with no explanation.
119
-
120
- User request/hint (optional):
121
- ${prompt || "(no extra hint)"}
122
-
123
- Code:
124
- ${currentFile.content}
125
- `.trim();
126
-
127
- const reply = await askAgent(fullPrompt, [
128
- { role: "user", content: currentFile.content },
129
- ]);
130
-
131
- updateActiveFileContent(reply);
132
- setOpenMenu(null);
133
  };
134
 
135
- const handleExplainSelection = async () => {
136
- if (!currentFile) return;
137
- const editor = editorRef.current;
138
-
139
- let selectedCode = "";
140
- if (editor) {
141
- const model = editor.getModel();
142
- const selection = editor.getSelection();
143
- if (model && selection) {
144
- selectedCode = model.getValueInRange(selection);
145
- }
146
- }
147
-
148
- if (!selectedCode.trim()) {
149
- selectedCode = currentFile.content;
150
- }
151
-
152
- const explainPrompt = `
153
- You are an AI code explainer inside an IDE.
154
- Explain clearly and concisely what the following ${currentLangId} code does,
155
- including edge cases and any potential issues.
156
-
157
- Code:
158
- ${selectedCode}
159
- `.trim();
160
-
161
- const reply = await askAgent(explainPrompt, [
162
- { role: "user", content: selectedCode },
163
- ]);
164
-
165
- setExplanation(reply);
166
- setOpenMenu(null);
167
- };
168
-
169
- const handleNewFile = () => {
170
- const base = "untitled";
171
- let idx = 1;
172
- let name = `${base}${idx}${langMeta.ext}`;
173
- while (files[name]) {
174
- idx += 1;
175
- name = `${base}${idx}${langMeta.ext}`;
176
- }
177
-
178
- setFiles((prev) => ({
179
- ...prev,
180
- [name]: {
181
- language: currentLangId,
182
- content: `// ${langMeta.label} file\n`,
183
- },
184
- }));
185
- setActiveFile(name);
186
- setOpenMenu(null);
187
  };
188
 
189
- const handleDeleteFile = () => {
190
- if (!activeFile) return;
191
- if (Object.keys(files).length === 1) return; // keep at least one
192
-
193
- const newFiles = { ...files };
194
- delete newFiles[activeFile];
195
-
196
- const remaining = Object.keys(newFiles);
197
- setFiles(newFiles);
198
- setActiveFile(remaining[0] || "");
199
  };
200
 
201
- // 🔁 NEW: Rename file
202
- const handleRenameFile = () => {
203
- if (!activeFile) return;
204
-
205
- const newName = window.prompt(
206
- "Enter new file name (with extension):",
207
- activeFile
208
- );
209
- if (!newName || newName === activeFile) return;
210
 
211
- // prevent overwriting other file
212
- if (files[newName]) {
213
- setOutput(` A file named "${newName}" already exists.`);
214
- setOpenMenu(null);
215
  return;
216
  }
217
 
218
- const oldFile = files[activeFile];
219
-
220
- // infer language from new extension if possible
221
- let newLangId = oldFile.language;
222
- const dotIndex = newName.lastIndexOf(".");
223
- if (dotIndex !== -1) {
224
- const newExt = newName.slice(dotIndex);
225
- const match = LANGUAGE_OPTIONS.find((l) => l.ext === newExt);
226
- if (match) {
227
- newLangId = match.id;
228
- }
229
- }
230
-
231
- setFiles((prev) => {
232
- const copy = { ...prev };
233
- delete copy[activeFile];
234
- copy[newName] = {
235
- ...oldFile,
236
- language: newLangId,
237
- };
238
- return copy;
239
- });
240
-
241
- setActiveFile(newName);
242
- setOpenMenu(null);
243
- setOutput(`✅ Renamed "${activeFile}" to "${newName}".`);
244
  };
245
 
246
- const handleChangeLanguage = (langId) => {
247
- setFiles((prev) => ({
248
- ...prev,
249
- [activeFile]: {
250
- ...prev[activeFile],
251
- language: langId,
252
- },
253
- }));
254
  };
255
 
256
- const toggleTheme = () => {
257
- setTheme((prev) => (prev === "vs-dark" ? "light" : "vs-dark"));
 
 
 
 
 
 
258
  };
259
 
260
- const toggleMenu = (menuName) => {
261
- setOpenMenu((prev) => (prev === menuName ? null : menuName));
 
262
  };
263
 
264
- const handleSaveLocal = () => {
265
- // Already persisted automatically; this is more like a user-triggered "save" action
266
- try {
267
- localStorage.setItem(
268
- STORAGE_KEY,
269
- JSON.stringify({ files, activeFile })
270
- );
271
- setOutput("💾 Saved project to browser storage.");
272
- } catch (e) {
273
- setOutput(`Failed to save: ${String(e)}`);
274
- }
275
- setOpenMenu(null);
276
  };
277
 
278
- const handleDownloadFile = () => {
279
- if (!currentFile || !activeFile) return;
280
- const blob = new Blob([currentFile.content], { type: "text/plain;charset=utf-8" });
281
- const url = window.URL.createObjectURL(blob);
282
- const a = document.createElement("a");
283
- a.href = url;
284
- a.download = activeFile;
285
- a.click();
286
- window.URL.revokeObjectURL(url);
287
- setOpenMenu(null);
 
 
288
  };
289
 
 
290
  return (
291
  <div className={`ide-root ${theme === "vs-dark" ? "ide-dark" : "ide-light"}`}>
292
- {/* Top Menu Bar */}
293
  <div className="ide-menubar">
294
  <div className="ide-menubar-left">
295
  <span className="ide-logo">⚙️ DevMate IDE</span>
296
 
297
- {/* File menu */}
298
- <div className="ide-menu-wrapper">
299
- <button
300
- className="ide-menu-item"
301
- onClick={() => toggleMenu("file")}
302
- >
303
- File ▾
304
- </button>
305
- {openMenu === "file" && (
306
- <div className="ide-menu-dropdown">
307
- <button onClick={handleNewFile}>New File</button>
308
- <button onClick={handleRenameFile}>Rename File</button>
309
- <button onClick={handleSaveLocal}>Save (Local)</button>
310
- <button onClick={handleDownloadFile}>Download File</button>
311
- </div>
312
- )}
313
- </div>
314
-
315
- {/* Run menu */}
316
- <div className="ide-menu-wrapper">
317
- <button
318
- className="ide-menu-item"
319
- onClick={() => toggleMenu("run")}
320
- >
321
- Run ▾
322
- </button>
323
- {openMenu === "run" && (
324
- <div className="ide-menu-dropdown">
325
- <button onClick={handleRun}>Run Current File</button>
326
- </div>
327
- )}
328
- </div>
329
-
330
- {/* AI menu */}
331
- <div className="ide-menu-wrapper">
332
- <button
333
- className="ide-menu-item"
334
- onClick={() => toggleMenu("ai")}
335
- >
336
- AI ▾
337
- </button>
338
- {openMenu === "ai" && (
339
- <div className="ide-menu-dropdown">
340
- <button onClick={handleAskFix}>Fix Current File</button>
341
- <button onClick={handleExplainSelection}>Explain Selection</button>
342
- </div>
343
- )}
344
- </div>
345
-
346
- <button className="ide-menu-item" onClick={() => setOutput("DevMate IDE - simple VS Code style IDE with AI agent and runner.")}>
347
- Help
348
- </button>
349
  </div>
350
 
351
  <div className="ide-menubar-right">
352
- <span className="ide-menu-item" onClick={toggleTheme}>
353
- {theme === "vs-dark" ? "☀️ Light" : "🌙 Dark"}
354
- </span>
 
 
 
 
355
  </div>
356
  </div>
357
 
358
- {/* Main body: sidebar + editor + panel */}
359
  <div className="ide-body">
360
- {/* Explorer Sidebar */}
 
361
  <div className="ide-sidebar">
362
- <div className="ide-sidebar-header">
363
- <span>EXPLORER</span>
364
- <button className="ide-icon-button" onClick={handleNewFile} title="New File">
365
-
366
- </button>
367
- </div>
368
- <div className="ide-file-list">
369
- {Object.keys(files).map((fname) => {
370
- const fLang = files[fname].language;
371
- const fMeta =
372
- LANGUAGE_OPTIONS.find((l) => l.id === fLang) ||
373
- LANGUAGE_OPTIONS[0];
374
-
375
- return (
376
- <div
377
- key={fname}
378
- className={
379
- "ide-file-item" +
380
- (fname === activeFile ? " ide-file-item-active" : "")
381
- }
382
- onClick={() => setActiveFile(fname)}
383
- >
384
- <span className="ide-file-icon">{fMeta.icon}</span>
385
- <span className="ide-file-name">{fname}</span>
386
- </div>
387
- );
388
- })}
389
- </div>
390
  </div>
391
 
392
- {/* Editor & bottom panel */}
393
  <div className="ide-main">
394
- {/* Tabs + quick actions */}
395
- <div className="ide-tabs">
396
- <div className="ide-tab-list">
397
- {Object.keys(files).map((fname) => (
 
 
 
 
 
 
 
 
 
 
398
  <div
399
- key={fname}
400
- className={
401
- "ide-tab" + (fname === activeFile ? " ide-tab-active" : "")
402
- }
403
- onClick={() => setActiveFile(fname)}
404
  >
405
- <span>{fname}</span>
406
- {fname === activeFile && (
407
- <button
408
- className="ide-tab-close"
409
- onClick={(e) => {
410
- e.stopPropagation();
411
- handleDeleteFile();
412
- }}
413
- >
414
- ×
415
- </button>
416
- )}
417
  </div>
418
  ))}
419
  </div>
 
420
 
421
- <div className="ide-tab-actions">
422
- <select
423
- value={currentLangId}
424
- onChange={(e) => handleChangeLanguage(e.target.value)}
425
- className="ide-select"
426
- >
427
- {LANGUAGE_OPTIONS.map((lang) => (
428
- <option key={lang.id} value={lang.id}>
429
- {lang.label}
430
- </option>
431
- ))}
432
- </select>
433
- <button className="ide-button" onClick={handleRun}>
434
- ▶ Run
435
- </button>
436
- <button className="ide-button" onClick={handleAskFix}>
437
- 🤖 Fix with AI
438
- </button>
439
- <button className="ide-button" onClick={handleExplainSelection}>
440
- 📖 Explain Selection
441
- </button>
442
- </div>
443
- </div>
444
 
445
- {/* Editor */}
446
- <div className="ide-editor-wrapper">
447
- <Editor
448
- height="100%"
449
- theme={theme}
450
- language={langMeta.monaco}
451
- value={currentFile?.content}
452
- onChange={updateActiveFileContent}
453
- onMount={(editor) => {
454
- editorRef.current = editor;
455
- }}
456
- options={{
457
- minimap: { enabled: true },
458
- fontSize: 14,
459
- scrollBeyondLastLine: false,
460
- }}
461
  />
462
- </div>
463
 
464
- {/* Bottom Panel: Output + Agent Prompt + Explanation */}
465
- <div className="ide-bottom-panel">
466
- <div className="ide-output-panel">
467
- <div className="ide-panel-header">TERMINAL / OUTPUT</div>
468
- <pre className="ide-output-content">{output}</pre>
469
- <input
470
- placeholder="Program input here..."
471
- value={stdin}
472
- onChange={(e) => setStdin(e.target.value)}
473
- className="ide-input-box"
474
- />
475
- </div>
476
- <div className="ide-agent-panel">
477
- <div className="ide-panel-header">AI PROMPT</div>
478
- <textarea
479
- className="ide-agent-textarea"
480
- value={prompt}
481
- onChange={(e) => setPrompt(e.target.value)}
482
- placeholder="Ask AI to refactor, explain, or add features..."
483
- />
484
- {explanation && (
485
- <div className="ide-explanation">
486
- <div className="ide-panel-header">AI EXPLANATION</div>
487
- <pre className="ide-output-content">{explanation}</pre>
488
- </div>
489
- )}
490
- </div>
491
  </div>
492
  </div>
493
  </div>
494
 
495
- {/* Status Bar */}
496
- <div className="ide-statusbar">
497
- <span>{activeFile}</span>
498
- <span>{langMeta.label}</span>
499
- <span>Theme: {theme === "vs-dark" ? "Dark" : "Light"}</span>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
500
  </div>
 
 
 
 
501
  </div>
502
  );
503
  }
 
2
  import Editor from "@monaco-editor/react";
3
  import { askAgent } from "./agent/assistant";
4
  import { runCode } from "./agent/runner";
5
+ import { loadTree, saveTree, addFile, addFolder, renameNode, deleteNode, getNodeByPath, updateFileContent } from "./fileStore";
6
+ import { downloadProjectZip } from "./zipExport";
7
+ import { parseProblems } from "./problemParser";
8
  import "./App.css";
9
 
10
+ // =================== SUPPORTED LANGUAGES ===================
11
  const LANGUAGE_OPTIONS = [
12
+ { id: "python", ext: ".py", icon: "🐍", monaco: "python" },
13
+ { id: "javascript", ext: ".js", icon: "🟨", monaco: "javascript" },
14
+ { id: "typescript", ext: ".ts", icon: "🟦", monaco: "typescript" },
15
+ { id: "cpp", ext: ".cpp", icon: "💠", monaco: "cpp" },
16
+ { id: "c", ext: ".c", icon: "🔷", monaco: "c" },
17
+ { id: "java", ext: ".java", icon: "☕", monaco: "java" },
18
+ { id: "html", ext: ".html", icon: "🌐", monaco: "html" },
19
+ { id: "css", ext: ".css", icon: "🎨", monaco: "css" },
20
+ { id: "json", ext: ".json", icon: "🧾", monaco: "json" },
21
  ];
22
 
23
+ const RUNNABLE_LANGS = ["python", "javascript", "java"];
 
 
 
 
24
 
25
+ // =================== APP ===================
26
  function App() {
27
+ const [tree, setTree] = useState(loadTree());
28
+ const [activePath, setActivePath] = useState("main.py");
 
 
 
 
 
 
 
 
 
29
  const [output, setOutput] = useState("");
30
  const [prompt, setPrompt] = useState("");
31
  const [explanation, setExplanation] = useState("");
 
 
32
  const [stdin, setStdin] = useState("");
33
+ const [problems, setProblems] = useState([]);
34
+ const [theme, setTheme] = useState("vs-dark");
35
+ const [searchOpen, setSearchOpen] = useState(false);
36
+ const [searchQuery, setSearchQuery] = useState("");
37
+ const [aiSuggestions, setAiSuggestions] = useState([]);
38
+ const [contextMenu, setContextMenu] = useState(null); // right-click menu
39
  const editorRef = useRef(null);
40
 
41
+ const currentFile = getNodeByPath(tree, activePath);
 
42
  const langMeta =
43
+ LANGUAGE_OPTIONS.find((l) =>
44
+ currentFile?.name.endsWith(l.ext)
45
+ ) || LANGUAGE_OPTIONS[0];
46
 
47
+ // Save after tree change
48
  useEffect(() => {
49
+ saveTree(tree);
50
+ }, [tree]);
 
 
 
 
 
 
 
 
 
 
 
 
51
 
52
+ // =================== FILE ACTIONS ===================
53
+ const handleNewFile = () => {
54
+ const name = window.prompt("Filename (with extension):");
55
+ if (!name) return;
56
+ setTree(addFile(tree, name));
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
57
  };
58
 
59
+ const handleNewFolder = () => {
60
+ const name = window.prompt("Folder name:");
61
+ if (!name) return;
62
+ setTree(addFolder(tree, name));
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
63
  };
64
 
65
+ const handleRename = () => {
66
+ if (!activePath) return;
67
+ const newName = window.prompt("New name:", currentFile.name);
68
+ if (!newName) return;
69
+ setTree(renameNode(tree, activePath, newName));
70
+ setActivePath(newName);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
71
  };
72
 
73
+ const handleDelete = () => {
74
+ if (!activePath) return;
75
+ setTree(deleteNode(tree, activePath));
76
+ setActivePath(""); // unselect
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
77
  };
78
 
79
+ const downloadFile = () => {
80
+ if (!currentFile?.content) return;
81
+ const blob = new Blob([currentFile.content], { type: "text/plain;charset=utf-8" });
82
+ const a = document.createElement("a");
83
+ a.href = URL.createObjectURL(blob);
84
+ a.download = currentFile.name;
85
+ a.click();
 
 
 
86
  };
87
 
88
+ // =================== RUN ===================
89
+ const handleRun = async () => {
90
+ if (!currentFile?.content) return;
 
 
 
 
 
 
91
 
92
+ const selectedLang = langMeta.id;
93
+ if (!RUNNABLE_LANGS.includes(selectedLang)) {
94
+ setOutput(`⚠️ Cannot run ${selectedLang}.`);
 
95
  return;
96
  }
97
 
98
+ const res = await runCode(currentFile.content, selectedLang, stdin);
99
+ setOutput(res.output || "");
100
+ setProblems(res.error ? parseProblems(res.output) : []);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
101
  };
102
 
103
+ // =================== AI ===================
104
+ const handleAskFix = async () => {
105
+ const reply = await askAgent(
106
+ `Fix or improve the following code and return only updated code.\n\n${currentFile.content}`
107
+ );
108
+ updateActiveFileContent(reply);
 
 
109
  };
110
 
111
+ const handleExplainSelection = async () => {
112
+ const editor = editorRef.current;
113
+ const selected = editor.getModel().getValueInRange(editor.getSelection());
114
+ const code = selected.trim() || currentFile.content;
115
+ const reply = await askAgent(
116
+ `Explain this ${langMeta.id} code:\n${code}`
117
+ );
118
+ setExplanation(reply);
119
  };
120
 
121
+ const updateActiveFileContent = (value) => {
122
+ const updated = updateFileContent(tree, activePath, value);
123
+ setTree(updated);
124
  };
125
 
126
+ // ===== AI Autocomplete Suggestions (Popup) =====
127
+ const fetchAiSuggestions = async (code) => {
128
+ if (!code.trim()) return;
129
+ const reply = await askAgent(
130
+ `Suggest possible next lines for continuation. Return 3 short snippets.\n${code}`
131
+ );
132
+ setAiSuggestions(reply.split("\n").filter((l) => l.trim()));
 
 
 
 
 
133
  };
134
 
135
+ // =================== SEARCH ===================
136
+ const handleSearchToggle = () => setSearchOpen(!searchOpen);
137
+
138
+ const handleSearchNow = () => {
139
+ if (!searchQuery) return;
140
+ alert(
141
+ `Found occurrences:\n${JSON.stringify(
142
+ searchTree(tree, searchQuery),
143
+ null,
144
+ 2
145
+ )}`
146
+ );
147
  };
148
 
149
+ // =================== UI ===================
150
  return (
151
  <div className={`ide-root ${theme === "vs-dark" ? "ide-dark" : "ide-light"}`}>
152
+ {/* ==== TOP BAR ==== */}
153
  <div className="ide-menubar">
154
  <div className="ide-menubar-left">
155
  <span className="ide-logo">⚙️ DevMate IDE</span>
156
 
157
+ <button onClick={handleNewFile}>📄 New File</button>
158
+ <button onClick={handleNewFolder}>📁 New Folder</button>
159
+ <button onClick={handleRename}>✏️ Rename</button>
160
+ <button onClick={downloadFile}>📥 Download</button>
161
+ <button onClick={downloadProjectZip}>📦 ZIP</button>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
162
  </div>
163
 
164
  <div className="ide-menubar-right">
165
+ <button onClick={handleSearchToggle}>🔍 Search</button>
166
+ <button onClick={handleRun}>▶ Run</button>
167
+ <button onClick={handleAskFix}>🤖 Fix</button>
168
+ <button onClick={handleExplainSelection}>📖 Explain</button>
169
+ <button onClick={() => setTheme(theme === "vs-dark" ? "light" : "vs-dark")}>
170
+ {theme === "vs-dark" ? "☀️" : "🌙"}
171
+ </button>
172
  </div>
173
  </div>
174
 
175
+ {/* ==== BODY ==== */}
176
  <div className="ide-body">
177
+
178
+ {/* ==== TREE VIEW ==== */}
179
  <div className="ide-sidebar">
180
+ {renderTree(tree, setActivePath, setContextMenu)}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
181
  </div>
182
 
183
+ {/* ==== EDITOR ==== */}
184
  <div className="ide-main">
185
+ <Editor
186
+ height="100%"
187
+ theme={theme}
188
+ language={langMeta.monaco}
189
+ value={currentFile?.content || ""}
190
+ onChange={updateActiveFileContent}
191
+ onMount={(editor) => (editorRef.current = editor)}
192
+ onBlur={() => fetchAiSuggestions(currentFile?.content)}
193
+ />
194
+
195
+ {/* AI Suggestions Tooltip */}
196
+ {aiSuggestions.length > 0 && (
197
+ <div className="ai-popup">
198
+ {aiSuggestions.map((s, i) => (
199
  <div
200
+ key={i}
201
+ className="ai-suggest"
202
+ onClick={() => updateActiveFileContent(currentFile.content + "\n" + s)}
 
 
203
  >
204
+ {s}
 
 
 
 
 
 
 
 
 
 
 
205
  </div>
206
  ))}
207
  </div>
208
+ )}
209
 
210
+ {/* Bottom Panels */}
211
+ <div className="ide-panels">
212
+ <pre className="ide-output">{output}</pre>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
213
 
214
+ <input
215
+ className="ide-input-box"
216
+ placeholder="Program input..."
217
+ value={stdin}
218
+ onChange={(e) => setStdin(e.target.value)}
 
 
 
 
 
 
 
 
 
 
 
219
  />
 
220
 
221
+ {explanation && (
222
+ <div className="ide-explain">{explanation}</div>
223
+ )}
224
+
225
+ {problems.length > 0 && (
226
+ <div className="ide-problems-panel">
227
+ <div>🚨 Problems ({problems.length})</div>
228
+ {problems.map((p, i) => (
229
+ <div key={i}>{p.file}:{p.line} — {p.message}</div>
230
+ ))}
231
+ </div>
232
+ )}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
233
  </div>
234
  </div>
235
  </div>
236
 
237
+ {/* ==== SEARCH PANEL ==== */}
238
+ {searchOpen && (
239
+ <div className="search-dialog">
240
+ <input
241
+ placeholder="Search text..."
242
+ onChange={(e) => setSearchQuery(e.target.value)}
243
+ />
244
+ <button onClick={handleSearchNow}>Search</button>
245
+ </div>
246
+ )}
247
+
248
+ {/* ==== RIGHT-CLICK CONTEXT MENU ==== */}
249
+ {contextMenu && (
250
+ <div
251
+ className="ide-context-menu"
252
+ style={{ top: contextMenu.y, left: contextMenu.x }}
253
+ onMouseLeave={() => setContextMenu(null)}
254
+ >
255
+ <div onClick={handleRename}>✏️ Rename</div>
256
+ <div onClick={handleDelete}>🗑 Delete</div>
257
+ <div onClick={downloadFile}>📥 Download</div>
258
+ </div>
259
+ )}
260
+ </div>
261
+ );
262
+ }
263
+
264
+ // =======================================================
265
+ // Tree Renderer UI
266
+ // =======================================================
267
+ function renderTree(node, setActivePath, setContextMenu, depth = 0) {
268
+ return (
269
+ <div key={node.name} style={{ paddingLeft: depth * 10 }}>
270
+ <div
271
+ className={`tree-item ${node.type}`}
272
+ onClick={() => node.type === "file" && setActivePath(node.path)}
273
+ onContextMenu={(e) => {
274
+ e.preventDefault();
275
+ setContextMenu({ x: e.pageX, y: e.pageY, file: node.path });
276
+ }}
277
+ >
278
+ {node.type === "folder" ? "📁" : "📄"} {node.name}
279
  </div>
280
+ {node.children &&
281
+ node.children.map((child) =>
282
+ renderTree(child, setActivePath, setContextMenu, depth + 1)
283
+ )}
284
  </div>
285
  );
286
  }
src/fileStore.js ADDED
@@ -0,0 +1,125 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // fileStore.js
2
+
3
+ const STORAGE_KEY = "devmate_ide_tree";
4
+
5
+ // Default starter tree
6
+ const defaultTree = {
7
+ type: "folder",
8
+ name: "root",
9
+ path: "",
10
+ children: [
11
+ {
12
+ type: "file",
13
+ name: "main.py",
14
+ path: "main.py",
15
+ content: "# Python\nprint('Hello from IDE')",
16
+ },
17
+ {
18
+ type: "file",
19
+ name: "script.js",
20
+ path: "script.js",
21
+ content: "console.log('Hello from JS');",
22
+ },
23
+ ],
24
+ };
25
+
26
+ // ============ HELPERS ============
27
+
28
+ export function loadTree() {
29
+ try {
30
+ const data = localStorage.getItem(STORAGE_KEY);
31
+ return data ? JSON.parse(data) : defaultTree;
32
+ } catch {
33
+ return defaultTree;
34
+ }
35
+ }
36
+
37
+ export function saveTree(tree) {
38
+ localStorage.setItem(STORAGE_KEY, JSON.stringify(tree));
39
+ }
40
+
41
+ export function getNodeByPath(node, path) {
42
+ if (!path) return null;
43
+ if (node.path === path) return node;
44
+ if (!node.children) return null;
45
+ for (let c of node.children) {
46
+ const result = getNodeByPath(c, path);
47
+ if (result) return result;
48
+ }
49
+ return null;
50
+ }
51
+
52
+ function buildPath(parent, name) {
53
+ return parent.path ? `${parent.path}/${name}` : name;
54
+ }
55
+
56
+ // ============ ADD ============
57
+
58
+ export function addFile(tree, name) {
59
+ const newTree = JSON.parse(JSON.stringify(tree));
60
+ newTree.children.push({
61
+ type: "file",
62
+ name,
63
+ path: name,
64
+ content: `// ${name}`,
65
+ });
66
+ return newTree;
67
+ }
68
+
69
+ export function addFolder(tree, name) {
70
+ const newTree = JSON.parse(JSON.stringify(tree));
71
+ newTree.children.push({
72
+ type: "folder",
73
+ name,
74
+ path: name,
75
+ children: [],
76
+ });
77
+ return newTree;
78
+ }
79
+
80
+ // ============ DELETE ============
81
+
82
+ export function deleteNode(tree, path) {
83
+ const clone = JSON.parse(JSON.stringify(tree));
84
+ clone.children = clone.children.filter((c) => c.path !== path);
85
+ return clone;
86
+ }
87
+
88
+ // ============ RENAME ============
89
+
90
+ export function renameNode(tree, path, newName) {
91
+ const clone = JSON.parse(JSON.stringify(tree));
92
+ function renameRec(node) {
93
+ if (node.path === path) {
94
+ node.name = newName;
95
+ node.path = newName;
96
+ }
97
+ node.children?.forEach(renameRec);
98
+ }
99
+ renameRec(clone);
100
+ return clone;
101
+ }
102
+
103
+ // ============ UPDATE CONTENT ============
104
+
105
+ export function updateFileContent(tree, path, content) {
106
+ const clone = JSON.parse(JSON.stringify(tree));
107
+ function update(node) {
108
+ if (node.path === path && node.type === "file") {
109
+ node.content = content;
110
+ }
111
+ node.children?.forEach(update);
112
+ }
113
+ update(clone);
114
+ return clone;
115
+ }
116
+
117
+ // ============ SEARCH ============
118
+
119
+ export function searchTree(node, term, result = []) {
120
+ if (node.type === "file" && node.content?.includes(term)) {
121
+ result.push({ file: node.name, path: node.path });
122
+ }
123
+ node.children?.forEach((c) => searchTree(c, term, result));
124
+ return result;
125
+ }
src/problemParser.js ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // problemParser.js
2
+
3
+ export function parseProblems(output) {
4
+ if (!output) return [];
5
+
6
+ const lines = output.split("\n");
7
+ const problems = [];
8
+
9
+ const regexes = [
10
+ // Python
11
+ { re: /(File "(.+)", line (\d+))/, lang: "python" },
12
+ // Java
13
+ { re: /(.*\.java):(\d+): (.+)/, lang: "java" },
14
+ // JS
15
+ { re: /(.*):(\d+):(\d+)/, lang: "js" },
16
+ ];
17
+
18
+ for (let line of lines) {
19
+ for (let { re, lang } of regexes) {
20
+ const m = line.match(re);
21
+ if (m) {
22
+ problems.push({
23
+ file: m[2] || m[1],
24
+ line: m[3] || m[2],
25
+ message: m[4] || m[0],
26
+ lang,
27
+ });
28
+ }
29
+ }
30
+ }
31
+ return problems;
32
+ }
src/zipExport.js ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // zipExport.js
2
+ import JSZip from "jszip";
3
+ import { loadTree } from "./fileStore";
4
+
5
+ export async function downloadProjectZip() {
6
+ const tree = loadTree();
7
+ const zip = new JSZip();
8
+
9
+ function addToZip(node, folder) {
10
+ if (node.type === "file") {
11
+ folder.file(node.name, node.content || "");
12
+ } else if (node.type === "folder") {
13
+ const newFolder = folder.folder(node.name);
14
+ node.children?.forEach((c) => addToZip(c, newFolder));
15
+ }
16
+ }
17
+
18
+ addToZip(tree, zip);
19
+ const blob = await zip.generateAsync({ type: "blob" });
20
+
21
+ const a = document.createElement("a");
22
+ a.href = URL.createObjectURL(blob);
23
+ a.download = "project.zip";
24
+ a.click();
25
+ }