FrederickSundeep commited on
Commit
e3e0a75
·
1 Parent(s): 12db32d

commit initial 12-12-2025 0001

Browse files
README.md CHANGED
@@ -1,5 +1,5 @@
1
  ---
2
- title: CodeIDE Dev
3
  emoji: 🐠
4
  colorFrom: indigo
5
  colorTo: red
 
1
  ---
2
+ title: CodeIDE
3
  emoji: 🐠
4
  colorFrom: indigo
5
  colorTo: red
package-lock.json ADDED
The diff for this file is too large to render. See raw diff
 
package.json CHANGED
@@ -3,14 +3,20 @@
3
  "version": "0.1.0",
4
  "private": true,
5
  "dependencies": {
 
6
  "@testing-library/dom": "^10.4.0",
7
  "@testing-library/jest-dom": "^6.6.3",
8
  "@testing-library/react": "^16.3.0",
9
  "@testing-library/user-event": "^13.5.0",
 
 
10
  "react": "^19.1.0",
11
  "react-dom": "^19.1.0",
 
12
  "react-scripts": "5.0.1",
13
- "web-vitals": "^2.1.4"
 
 
14
  },
15
  "scripts": {
16
  "start": "react-scripts start",
 
3
  "version": "0.1.0",
4
  "private": true,
5
  "dependencies": {
6
+ "@monaco-editor/react": "^4.7.0",
7
  "@testing-library/dom": "^10.4.0",
8
  "@testing-library/jest-dom": "^6.6.3",
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",
16
  "react-scripts": "5.0.1",
17
+ "web-vitals": "^2.1.4",
18
+ "xterm": "^5.3.0",
19
+ "xterm-addon-fit": "^0.8.0"
20
  },
21
  "scripts": {
22
  "start": "react-scripts start",
public/index.html CHANGED
@@ -7,7 +7,8 @@
7
  <meta name="theme-color" content="#000000" />
8
  <meta
9
  name="description"
10
- content="Web site created using create-react-app"
 
11
  />
12
  <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
13
  <!--
@@ -24,7 +25,7 @@
24
  work correctly both with client-side routing and a non-root public URL.
25
  Learn how to configure a non-root public URL by running `npm run build`.
26
  -->
27
- <title>React App</title>
28
  </head>
29
  <body>
30
  <noscript>You need to enable JavaScript to run this app.</noscript>
 
7
  <meta name="theme-color" content="#000000" />
8
  <meta
9
  name="description"
10
+ content="DevMate AI
11
+ An AI-powered code editor and IDE that helps you write, debug, and optimize code faster."
12
  />
13
  <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
14
  <!--
 
25
  work correctly both with client-side routing and a non-root public URL.
26
  Learn how to configure a non-root public URL by running `npm run build`.
27
  -->
28
+ <title>DevMate IDE</title>
29
  </head>
30
  <body>
31
  <noscript>You need to enable JavaScript to run this app.</noscript>
src/App.css CHANGED
@@ -1,38 +1,502 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  .App {
2
  text-align: center;
3
  }
4
 
5
- .App-logo {
6
- height: 40vmin;
7
- pointer-events: none;
 
 
 
 
 
 
8
  }
9
 
10
- @media (prefers-reduced-motion: no-preference) {
11
- .App-logo {
12
- animation: App-logo-spin infinite 20s linear;
13
- }
14
  }
15
 
16
- .App-header {
17
- background-color: #282c34;
18
- min-height: 100vh;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
  display: flex;
20
- flex-direction: column;
21
  align-items: center;
22
- justify-content: center;
23
- font-size: calc(10px + 2vmin);
24
- color: white;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
25
  }
26
 
27
- .App-link {
28
- color: #61dafb;
29
  }
30
 
31
- @keyframes App-logo-spin {
32
- from {
33
- transform: rotate(0deg);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
34
  }
35
- to {
36
- transform: rotate(360deg);
 
 
 
 
 
 
 
 
 
 
 
37
  }
 
 
 
 
 
 
 
 
 
 
38
  }
 
1
+ /* ============================
2
+ Global & Base
3
+ ============================ */
4
+
5
+ html, body, #root {
6
+ margin: 0;
7
+ padding: 0;
8
+ overflow-x: hidden; /* prevent page-level horizontal scroll */
9
+ box-sizing: border-box;
10
+ }
11
+
12
+ *,
13
+ *::before,
14
+ *::after {
15
+ box-sizing: inherit;
16
+ }
17
+
18
  .App {
19
  text-align: center;
20
  }
21
 
22
+ /* Root layout */
23
+ .ide-root {
24
+ height: 100vh;
25
+ width: 100vw;
26
+ display: flex;
27
+ flex-direction: column;
28
+ font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
29
+ overflow: hidden;
30
+ max-width: 100%;
31
  }
32
 
33
+ /* Themes */
34
+ .ide-dark {
35
+ background: #1e1e1e;
36
+ color: #ddd;
37
  }
38
 
39
+ .ide-light {
40
+ background: #f5f5f5;
41
+ color: #222;
42
+ }
43
+
44
+ /* ============================
45
+ Menubar
46
+ ============================ */
47
+
48
+ .ide-menubar {
49
+ height: 32px;
50
+ display: flex;
51
+ align-items: center;
52
+ justify-content: space-between;
53
+ padding: 0 10px;
54
+ background: #252526;
55
+ color: #eee;
56
+ font-size: 13px;
57
+ border-bottom: 1px solid #444;
58
+ flex: 0 0 auto;
59
+ }
60
+
61
+ .ide-menubar-left,
62
+ .ide-menubar-right {
63
  display: flex;
 
64
  align-items: center;
65
+ gap: 8px;
66
+ }
67
+
68
+ .ide-logo {
69
+ font-weight: 600;
70
+ margin-right: 12px;
71
+ }
72
+
73
+ .ide-menubar button {
74
+ margin-right: 4px;
75
+ background: transparent;
76
+ border: none;
77
+ color: inherit;
78
+ cursor: pointer;
79
+ padding: 2px 6px;
80
+ border-radius: 3px;
81
+ font-size: 13px;
82
+ }
83
+
84
+ .ide-menubar button:hover {
85
+ background: rgba(255, 255, 255, 0.08);
86
+ }
87
+
88
+ /* ============================
89
+ Body Layout
90
+ ============================ */
91
+
92
+ .ide-body {
93
+ flex: 1;
94
+ display: flex;
95
+ overflow: hidden; /* prevent children from creating page overflow */
96
+ min-height: 0; /* allow children to shrink (important for flex) */
97
+ max-width: 100%;
98
+ }
99
+
100
+ /* ============================
101
+ Sidebar (Explorer)
102
+ ============================ */
103
+
104
+ .ide-sidebar {
105
+ flex: 0 0 210px; /* fixed sidebar width */
106
+ min-width: 0; /* critical for preventing overflow from child */
107
+ width: 210px;
108
+ background: #252526;
109
+ color: #ccc;
110
+ padding: 5px;
111
+ border-right: 1px solid #444;
112
+ overflow-y: auto;
113
+ font-size: 14px;
114
+ }
115
+
116
+ .tree-item {
117
+ padding: 4px 6px;
118
+ cursor: pointer;
119
+ user-select: none;
120
+ border-radius: 3px;
121
+ white-space: nowrap;
122
+ overflow: hidden;
123
+ text-overflow: ellipsis;
124
+ }
125
+
126
+ .tree-item:hover {
127
+ background: #3a3d41;
128
+ }
129
+
130
+ /* ============================
131
+ Main (Editor + Output)
132
+ ============================ */
133
+
134
+ .ide-main {
135
+ flex: 1 1 auto;
136
+ display: flex;
137
+ flex-direction: column;
138
+ position: relative;
139
+ min-width: 0; /* CRITICAL: allow editor to shrink inside flex */
140
+ overflow: hidden;
141
+ }
142
+
143
+ /* Monaco wrapper */
144
+ .ide-editor-wrapper {
145
+ flex: 1 1 auto;
146
+ min-height: 0;
147
+ min-width: 0; /* important for monaco inside flex */
148
+ overflow: hidden;
149
+ }
150
+
151
+ /* Bottom panels (output, stdin, problems) */
152
+ .ide-panels {
153
+ background: #1e1e1e;
154
+ border-top: 1px solid #444;
155
+ padding: 6px;
156
+ font-size: 13px;
157
+ height: 28%;
158
+ overflow: auto;
159
+ min-height: 120px;
160
+ box-sizing: border-box;
161
+ max-width: 100%;
162
+ }
163
+
164
+ .ide-output {
165
+ background: #000;
166
+ color: #0f0;
167
+ padding: 6px;
168
+ min-height: 60px;
169
+ max-height: 120px;
170
+ overflow-y: auto;
171
+ border-radius: 4px;
172
+ font-family: "Consolas", monospace;
173
+ font-size: 12px;
174
+ white-space: pre-wrap;
175
+ word-break: break-word;
176
+ max-width: 100%;
177
+ }
178
+
179
+ .ide-input-box {
180
+ width: 100%;
181
+ margin-top: 4px;
182
+ padding: 6px;
183
+ background: #111;
184
+ border: 1px solid #444;
185
+ color: #eee;
186
+ border-radius: 3px;
187
+ font-size: 12px;
188
+ box-sizing: border-box;
189
+ }
190
+
191
+ /* Problems panel */
192
+ .ide-problems-panel {
193
+ margin-top: 6px;
194
+ padding: 8px;
195
+ background: #3c0000;
196
+ color: #fff;
197
+ border-left: 3px solid red;
198
+ font-size: 12px;
199
+ border-radius: 3px;
200
+ overflow: auto;
201
+ white-space: pre-wrap;
202
+ word-break: break-word;
203
+ }
204
+
205
+ /* ============================
206
+ Terminal / XTerm container
207
+ ============================ */
208
+
209
+ /* Ensure terminal fits container and doesn't push layout */
210
+ #terminal-container {
211
+ width: 100% !important;
212
+ max-width: 100% !important;
213
+ box-sizing: border-box;
214
+ overflow-x: auto; /* keep horizontal scroll inside terminal only */
215
+ height: 180px;
216
+ background: #1e1e1e;
217
+ border-top: 1px solid #333;
218
+ border-radius: 4px;
219
+ }
220
+
221
+ /* Terminal content wrapper (if you have a wrapper class) */
222
+ .terminal-content {
223
+ width: 100%;
224
+ max-width: 100%;
225
+ overflow-x: auto;
226
+ white-space: pre-wrap;
227
+ word-break: break-word;
228
+ font-family: "Consolas", monospace;
229
+ }
230
+
231
+ /* ============================
232
+ Right AI Panel
233
+ ============================ */
234
+
235
+ .ide-right-panel {
236
+ flex: 0 0 280px;
237
+ min-width: 0;
238
+ width: 280px;
239
+ border-left: 1px solid #444;
240
+ background: #252526;
241
+ padding: 8px;
242
+ display: flex;
243
+ flex-direction: column;
244
+ gap: 8px;
245
+ font-size: 13px;
246
+ overflow: auto;
247
+ }
248
+
249
+ .ide-ai-header {
250
+ font-weight: 600;
251
+ margin-bottom: 4px;
252
+ }
253
+
254
+ .ide-ai-section {
255
+ display: flex;
256
+ flex-direction: column;
257
+ gap: 4px;
258
+ }
259
+
260
+ .ide-ai-label {
261
+ font-size: 12px;
262
+ text-transform: uppercase;
263
+ letter-spacing: 0.06em;
264
+ opacity: 0.8;
265
+ }
266
+
267
+ /* AI instruction textarea */
268
+ .ide-agent-textarea {
269
+ width: 100%;
270
+ min-height: 80px;
271
+ max-height: 160px;
272
+ resize: vertical;
273
+ padding: 6px;
274
+ border-radius: 4px;
275
+ border: 1px solid #444;
276
+ background: #1e1e1e;
277
+ color: #eee;
278
+ font-family: Consolas, monospace;
279
+ font-size: 12px;
280
+ box-sizing: border-box;
281
+ }
282
+
283
+ /* AI buttons */
284
+ .ide-ai-buttons {
285
+ display: flex;
286
+ gap: 6px;
287
+ }
288
+
289
+ .ide-ai-buttons button {
290
+ flex: 1;
291
+ padding: 6px;
292
+ font-size: 12px;
293
+ border-radius: 4px;
294
+ border: 1px solid #555;
295
+ background: #0e639c;
296
+ color: #fff;
297
+ cursor: pointer;
298
+ }
299
+
300
+ .ide-ai-buttons button:hover {
301
+ background: #1177bb;
302
+ }
303
+
304
+ /* AI explanation box */
305
+ .ide-explain {
306
+ max-height: 180px;
307
+ overflow-y: auto;
308
+ padding: 6px;
309
+ border-radius: 4px;
310
+ background: #1e1e1e;
311
+ border: 1px solid #444;
312
+ font-size: 12px;
313
+ color: #eee;
314
+ font-family: Consolas, monospace;
315
+ }
316
+
317
+ /* ============================
318
+ AI Suggestions Popup
319
+ ============================ */
320
+
321
+ .ai-popup {
322
+ position: absolute;
323
+ bottom: 35%;
324
+ right: 5px;
325
+ width: 230px;
326
+ background: #333;
327
+ border: 1px solid #444;
328
+ border-radius: 6px;
329
+ padding: 4px;
330
+ animation: fadeIn 0.25s ease-in-out;
331
+ font-size: 12px;
332
+ z-index: 200; /* ensure it sits above other UI */
333
+ max-width: calc(100vw - 24px);
334
+ box-sizing: border-box;
335
+ }
336
+
337
+ .ai-suggest {
338
+ padding: 4px;
339
+ cursor: pointer;
340
  }
341
 
342
+ .ai-suggest:hover {
343
+ background: #444;
344
  }
345
 
346
+ /* ============================
347
+ Search Dialog
348
+ ============================ */
349
+
350
+ .search-dialog {
351
+ position: absolute;
352
+ right: 10px;
353
+ top: 50px;
354
+ background: #333;
355
+ padding: 10px;
356
+ border: 1px solid #444;
357
+ border-radius: 5px;
358
+ z-index: 300;
359
+ max-width: calc(100vw - 24px);
360
+ box-sizing: border-box;
361
+ }
362
+
363
+ .search-dialog input {
364
+ width: 200px;
365
+ padding: 4px 6px;
366
+ border-radius: 3px;
367
+ border: 1px solid #555;
368
+ background: #1e1e1e;
369
+ color: #eee;
370
+ }
371
+
372
+ /* ============================
373
+ Context Menu
374
+ ============================ */
375
+
376
+ .ide-context-menu {
377
+ position: absolute;
378
+ background: #333;
379
+ color: #eee;
380
+ border: 1px solid #555;
381
+ border-radius: 5px;
382
+ padding: 4px;
383
+ z-index: 400;
384
+ max-width: calc(100vw - 24px);
385
+ box-sizing: border-box;
386
+ }
387
+
388
+ .ide-context-menu div {
389
+ padding: 4px 8px;
390
+ cursor: pointer;
391
+ font-size: 13px;
392
+ }
393
+
394
+ .ide-context-menu div:hover {
395
+ background: #555;
396
+ }
397
+
398
+ /* ============================
399
+ Progress / Spinner
400
+ ============================ */
401
+
402
+ .ide-progress-wrap {
403
+ height: 3px;
404
+ background: transparent;
405
+ width: 100%;
406
+ position: relative;
407
+ overflow: hidden;
408
+ }
409
+ .ide-progress {
410
+ position: absolute;
411
+ height: 3px;
412
+ width: 30%;
413
+ left: -30%;
414
+ top: 0;
415
+ background: linear-gradient(90deg, #0e9, #08f);
416
+ animation: progress-slide 1.2s linear infinite;
417
+ border-radius: 2px;
418
+ }
419
+
420
+ @keyframes progress-slide {
421
+ 0% { left: -30%; width: 30%; }
422
+ 50% { left: 35%; width: 40%; }
423
+ 100% { left: 100%; width: 30%; }
424
+ }
425
+
426
+ /* ---------- small inline spinner (optional) ---------- */
427
+ .button-spinner {
428
+ display: inline-block;
429
+ width: 12px;
430
+ height: 12px;
431
+ border: 2px solid rgba(255,255,255,0.25);
432
+ border-top-color: rgba(255,255,255,0.95);
433
+ border-radius: 50%;
434
+ animation: spin 0.8s linear infinite;
435
+ margin-left: 6px;
436
+ vertical-align: middle;
437
+ }
438
+
439
+ @keyframes spin {
440
+ to { transform: rotate(360deg); }
441
+ }
442
+
443
+ /* ============================
444
+ Misc: Text wrapping & safety
445
+ ============================ */
446
+
447
+ .ide-output, .ide-explain, .ide-problems-panel, .tree-item, .ai-popup, .ai-suggest {
448
+ overflow-wrap: break-word;
449
+ word-break: break-word;
450
+ white-space: pre-wrap;
451
+ }
452
+
453
+ /* Buttons, inputs, selects must not exceed container */
454
+ button, input, textarea, select {
455
+ max-width: 100%;
456
+ box-sizing: border-box;
457
+ }
458
+
459
+ /* Final safety: hide any stray horizontal overflow */
460
+ #root, .ide-root, .ide-body, .ide-main { overflow-x: hidden; }
461
+
462
+ /* ============================
463
+ 📱 Mobile / Tablet Responsive
464
+ ============================ */
465
+
466
+ @media (max-width: 900px) {
467
+ /* Stack layout vertically on small screens */
468
+ .ide-body { flex-direction: column; }
469
+
470
+ /* Sidebar becomes top strip */
471
+ .ide-sidebar {
472
+ width: 100%;
473
+ flex: 0 0 auto;
474
+ max-height: 22vh;
475
+ border-right: none;
476
+ border-bottom: 1px solid #444;
477
  }
478
+
479
+ .ide-main { order: 2; min-height: 40vh; }
480
+ .ide-editor-wrapper { height: 40vh; }
481
+
482
+ /* Bottom panels adapt */
483
+ .ide-panels { height: auto; max-height: 26vh; }
484
+
485
+ /* Right panel becomes bottom full width */
486
+ .ide-right-panel {
487
+ width: 100%;
488
+ order: 3;
489
+ border-left: none;
490
+ border-top: 1px solid #444;
491
  }
492
+
493
+ .ai-popup { right: 8px; left: 8px; width: auto; max-width: calc(100vw - 16px); }
494
+ .search-dialog { right: 8px; left: 8px; width: auto; }
495
+ .ide-context-menu { max-width: 70vw; }
496
+ }
497
+
498
+ /* Small-screen tweak for very small devices */
499
+ @media (max-width: 420px) {
500
+ .ide-menubar { font-size: 12px; padding: 0 6px; }
501
+ .ide-ai-buttons button, .ide-menubar button { padding: 4px; font-size: 12px; }
502
  }
src/App.js CHANGED
@@ -1,23 +1,629 @@
1
- import logo from './logo.svg';
2
- import './App.css';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4
  function App() {
5
- return (
6
- <div className="App">
7
- <header className="App-header">
8
- <img src={logo} className="App-logo" alt="logo" />
9
- <p>
10
- Edit <code>src/App.js</code> and save to reload.
11
- </p>
12
- <a
13
- className="App-link"
14
- href="https://reactjs.org"
15
- target="_blank"
16
- rel="noopener noreferrer"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
17
  >
18
- Learn React
19
- </a>
20
- </header>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
21
  </div>
22
  );
23
  }
 
1
+ // src/App.js
2
+ import { useState, useEffect, useRef } from "react";
3
+ import Editor from "@monaco-editor/react";
4
+ import { askAgent } from "./agent/assistant";
5
+ import { runCode } from "./agent/runner";
6
+ import {
7
+ loadTree,
8
+ saveTree,
9
+ addFile,
10
+ addFolder,
11
+ renameNode,
12
+ deleteNode,
13
+ getNodeByPath,
14
+ updateFileContent,
15
+ searchTree,
16
+ } from "./fileStore";
17
+ import { downloadProjectZip } from "./zipExport";
18
+ import { parseProblems } from "./problemParser";
19
+ import "./App.css";
20
+ import "xterm/css/xterm.css";
21
+ import XTerm from "./Terminal"; // your existing wrapper
22
 
23
+ // =================== SUPPORTED LANGUAGES ===================
24
+ const LANGUAGE_OPTIONS = [
25
+ { id: "python", ext: ".py", icon: "🐍", monaco: "python" },
26
+ { id: "javascript", ext: ".js", icon: "🟨", monaco: "javascript" },
27
+ { id: "typescript", ext: ".ts", icon: "🟦", monaco: "typescript" },
28
+ { id: "cpp", ext: ".cpp", icon: "💠", monaco: "cpp" },
29
+ { id: "c", ext: ".c", icon: "🔷", monaco: "c" },
30
+ { id: "java", ext: ".java", icon: "☕", monaco: "java" },
31
+ { id: "html", ext: ".html", icon: "🌐", monaco: "html" },
32
+ { id: "css", ext: ".css", icon: "🎨", monaco: "css" },
33
+ { id: "json", ext: ".json", icon: "🧾", monaco: "json" },
34
+ ];
35
+
36
+ const RUNNABLE_LANGS = ["python", "javascript", "java"];
37
+
38
+ // =================== Heuristics ===================
39
+ // patterns that indicate program is waiting for input
40
+ function outputLooksForInput(output) {
41
+ if (!output) return false;
42
+ const o = output.toString();
43
+ const patterns = [
44
+ /enter.*:/i,
45
+ /input.*:/i,
46
+ /please enter/i,
47
+ /scanner/i,
48
+ /press enter/i,
49
+ /: $/,
50
+ /:\n$/,
51
+ /> $/,
52
+ /awaiting input/i,
53
+ /provide input/i,
54
+ /stdin/i,
55
+ /enter a value/i,
56
+ ];
57
+ return patterns.some((p) => p.test(o));
58
+ }
59
+
60
+ // code-level heuristics to detect input calls
61
+ function codeNeedsInput(code, langId) {
62
+ if (!code) return false;
63
+ try {
64
+ const c = code.toString();
65
+ if (langId === "python") {
66
+ if (/\binput\s*\(/i.test(c)) return true;
67
+ if (/\bsys\.stdin\.(read|readline|readlines)\s*\(/i.test(c)) return true;
68
+ if (/\braw_input\s*\(/i.test(c)) return true;
69
+ }
70
+ if (langId === "java") {
71
+ if (/\bScanner\s*\(/i.test(c)) return true;
72
+ if (/\bBufferedReader\b.*readLine/i.test(c)) return true;
73
+ if (/\bSystem\.console\(\)/i.test(c)) return true;
74
+ if (/\bnext(Int|Line|Double|)\b/i.test(c)) return true;
75
+ }
76
+ if (langId === "javascript") {
77
+ if (/process\.stdin|readline|readlineSync|prompt\(|require\(['"]readline['"]\)/i.test(c)) return true;
78
+ }
79
+ if (langId === "cpp" || langId === "c") {
80
+ if (/\bscanf\s*\(/i.test(c)) return true;
81
+ if (/\bstd::cin\b|cin\s*>>/i.test(c)) return true;
82
+ if (/\bgets?\s*\(/i.test(c)) return true;
83
+ }
84
+ if (/\binput\b|\bscanf\b|\bscanf_s\b|\bcin\b|\bScanner\b|readLine|readline/i.test(c)) return true;
85
+ return false;
86
+ } catch {
87
+ return false;
88
+ }
89
+ }
90
+
91
+ // Helper: focus xterm's hidden textarea (works with xterm.js default markup)
92
+ function focusXtermHelper() {
93
+ setTimeout(() => {
94
+ const ta = document.querySelector("#terminal-container .xterm-helper-textarea");
95
+ if (ta) {
96
+ try {
97
+ ta.focus();
98
+ const len = ta.value?.length ?? 0;
99
+ ta.setSelectionRange(len, len);
100
+ } catch {}
101
+ } else {
102
+ const cont = document.getElementById("terminal-container");
103
+ if (cont) cont.focus();
104
+ }
105
+ }, 120);
106
+ }
107
+
108
+ // =================== APP ===================
109
  function App() {
110
+ // ----- file tree + selection -----
111
+ const [tree, setTree] = useState(loadTree());
112
+ const [activePath, setActivePath] = useState("main.py");
113
+
114
+ // ----- terminal / interactive state -----
115
+ const [accumStdin, setAccumStdin] = useState(""); // accumulated input for interactive runs
116
+ const [awaitingInput, setAwaitingInput] = useState(false);
117
+ const [terminalLines, setTerminalLines] = useState([]); // visible lines in terminal area
118
+ const [output, setOutput] = useState(""); // "write" prop for XTerm (Terminal component picks this up)
119
+ const [interactivePromptShown, setInteractivePromptShown] = useState(false);
120
+
121
+ // ----- AI + editor state -----
122
+ const [prompt, setPrompt] = useState("");
123
+ const [explanation, setExplanation] = useState("");
124
+ const [problems, setProblems] = useState([]);
125
+ const [theme, setTheme] = useState("vs-dark");
126
+ const [searchOpen, setSearchOpen] = useState(false);
127
+ const [searchQuery, setSearchQuery] = useState("");
128
+ const [aiSuggestions, setAiSuggestions] = useState([]);
129
+ const [contextMenu, setContextMenu] = useState(null);
130
+ const [isRunning, setIsRunning] = useState(false);
131
+ const [isFixing, setIsFixing] = useState(false);
132
+ const [isExplaining, setIsExplaining] = useState(false);
133
+
134
+ // refs & helpers
135
+ const editorRef = useRef(null);
136
+ const fileInputRef = useRef(null);
137
+
138
+ useEffect(() => {
139
+ saveTree(tree);
140
+ }, [tree]);
141
+
142
+ const currentNode = getNodeByPath(tree, activePath);
143
+ const langMeta =
144
+ LANGUAGE_OPTIONS.find((l) => currentNode?.name?.endsWith(l.ext)) ||
145
+ LANGUAGE_OPTIONS[0];
146
+
147
+ // ---------- File / Folder actions ----------
148
+ const collectFolderPaths = (node, acc = []) => {
149
+ if (!node) return acc;
150
+ if (node.type === "folder") acc.push(node.path || "");
151
+ node.children?.forEach((c) => collectFolderPaths(c, acc));
152
+ return acc;
153
+ };
154
+
155
+ const handleNewFile = () => {
156
+ const filename = window.prompt("Filename (with extension):", "untitled.js");
157
+ if (!filename) return;
158
+ const selected = getNodeByPath(tree, activePath);
159
+ let parentPath = "";
160
+ if (selected?.type === "folder") parentPath = selected.path;
161
+ else if (selected?.type === "file") {
162
+ const parts = selected.path.split("/").slice(0, -1);
163
+ parentPath = parts.join("/");
164
+ }
165
+ const folders = collectFolderPaths(tree);
166
+ const suggestion = parentPath || folders[0] || "";
167
+ const chosen = window.prompt(
168
+ `Parent folder (enter path). Available:\n${folders.join("\n")}\n\nLeave empty for root.`,
169
+ suggestion
170
+ );
171
+ const targetParent = chosen == null ? parentPath : (chosen.trim() || "");
172
+ const updated = addFile(tree, filename, targetParent);
173
+ setTree(updated);
174
+ const newPath = (targetParent ? targetParent + "/" : "") + filename;
175
+ setActivePath(newPath);
176
+ };
177
+
178
+ const handleNewFolder = () => {
179
+ const name = window.prompt("Folder name:", "new_folder");
180
+ if (!name) return;
181
+ const selected = getNodeByPath(tree, activePath);
182
+ const parentPath = selected && selected.type === "folder" ? selected.path : "";
183
+ const updated = addFolder(tree, name, parentPath);
184
+ setTree(updated);
185
+ };
186
+
187
+ const handleRename = () => {
188
+ if (!activePath) return;
189
+ const node = getNodeByPath(tree, activePath);
190
+ if (!node) return;
191
+ const newName = window.prompt("New name:", node.name);
192
+ if (!newName || newName === node.name) return;
193
+ const updated = renameNode(tree, activePath, newName);
194
+ setTree(updated);
195
+ const parts = activePath.split("/");
196
+ parts.pop();
197
+ const parent = parts.join("/");
198
+ const newPath = (parent ? parent + "/" : "") + newName;
199
+ setActivePath(newPath);
200
+ };
201
+
202
+ const handleDelete = () => {
203
+ if (!activePath) return;
204
+ const node = getNodeByPath(tree, activePath);
205
+ if (!node) return;
206
+ if (node.type === "folder" && node.children?.length > 0) {
207
+ const ok = window.confirm(`Folder "${node.name}" has ${node.children.length} items. Delete anyway?`);
208
+ if (!ok) return;
209
+ } else {
210
+ const ok = window.confirm(`Delete "${node.name}"?`);
211
+ if (!ok) return;
212
+ }
213
+ const updated = deleteNode(tree, activePath);
214
+ setTree(updated);
215
+ setActivePath("");
216
+ };
217
+
218
+ const downloadFile = () => {
219
+ const node = getNodeByPath(tree, activePath);
220
+ if (!node || node.type !== "file") return;
221
+ const blob = new Blob([node.content || ""], { type: "text/plain;charset=utf-8" });
222
+ const a = document.createElement("a");
223
+ a.href = URL.createObjectURL(blob);
224
+ a.download = node.name;
225
+ a.click();
226
+ };
227
+
228
+ const handleImportFileClick = () => fileInputRef.current?.click();
229
+ const handleFileInputChange = async (e) => {
230
+ const f = e.target.files?.[0];
231
+ if (!f) return;
232
+ const text = await f.text();
233
+ const selected = getNodeByPath(tree, activePath);
234
+ let parentPath = "";
235
+ if (selected?.type === "folder") parentPath = selected.path;
236
+ else if (selected?.type === "file") parentPath = selected.path.split("/").slice(0, -1).join("");
237
+ const updated = addFile(tree, f.name, parentPath);
238
+ const newPath = (parentPath ? parentPath + "/" : "") + f.name;
239
+ const finalTree = updateFileContent(updated, newPath, text);
240
+ setTree(finalTree);
241
+ setActivePath(newPath);
242
+ e.target.value = "";
243
+ };
244
+
245
+ // ---------- Terminal helpers ----------
246
+ const appendTerminal = (text) => {
247
+ // push to visible lines and set `output` (which Terminal writes)
248
+ setTerminalLines((prev) => {
249
+ const next = [...prev, text];
250
+ // also keep the XTerm single-output prop to trigger Terminal.writeln
251
+ setOutput(text);
252
+ return next;
253
+ });
254
+ };
255
+
256
+ const clearTerminal = () => {
257
+ // ANSI sequence to clear screen + move cursor home (xterm will honor)
258
+ setTerminalLines([]);
259
+ setOutput("\x1b[2J\x1b[H");
260
+ setAccumStdin("");
261
+ setAwaitingInput(false);
262
+ setInteractivePromptShown(false);
263
+ };
264
+
265
+ const resetTerminal = (keepAccum = false) => {
266
+ setTerminalLines([]);
267
+ setOutput("");
268
+ if (!keepAccum) {
269
+ setAccumStdin("");
270
+ }
271
+ setAwaitingInput(false);
272
+ setInteractivePromptShown(false);
273
+ };
274
+
275
+ // Unified runner used when terminal provides input (or small input)
276
+ const runCodeWithUpdatedInput = async (inputLine) => {
277
+ if (typeof inputLine !== "string") inputLine = String(inputLine || "");
278
+ const trimmed = inputLine.replace(/\r$/, "");
279
+ // if user pressed Enter with empty line and no accum, ignore
280
+ if (trimmed.length === 0 && !accumStdin) {
281
+ return;
282
+ }
283
+
284
+ // append newline like console
285
+ const newAccum = (accumStdin || "") + trimmed + "\n";
286
+ setAccumStdin(newAccum);
287
+ setInteractivePromptShown(false);
288
+
289
+ const node = getNodeByPath(tree, activePath);
290
+ if (!node || node.type !== "file") {
291
+ appendTerminal("[Error] No file selected to run.");
292
+ setAwaitingInput(false);
293
+ return;
294
+ }
295
+
296
+ const selectedLang = LANGUAGE_OPTIONS.find((l) => node.name.endsWith(l.ext))?.id;
297
+ if (!selectedLang || !RUNNABLE_LANGS.includes(selectedLang)) {
298
+ appendTerminal(`[Error] Run not supported for ${node.name}`);
299
+ setAwaitingInput(false);
300
+ return;
301
+ }
302
+
303
+ setIsRunning(true);
304
+ try {
305
+ const res = await runCode(node.content, selectedLang, newAccum);
306
+ const out = res.output ?? "";
307
+ if (out) appendTerminal(out);
308
+ setProblems(res.error ? parseProblems(res.output) : []);
309
+ if (outputLooksForInput(out)) {
310
+ setAwaitingInput(true);
311
+ focusXtermHelper();
312
+ } else {
313
+ setAwaitingInput(false);
314
+ setAccumStdin(""); // finished -> clear accumulated input so next Run is fresh
315
+ }
316
+ } catch (err) {
317
+ appendTerminal(String(err));
318
+ setAwaitingInput(true);
319
+ } finally {
320
+ setIsRunning(false);
321
+ }
322
+ };
323
+
324
+ // ---------- Initial Run handler (fresh runs) ----------
325
+ const handleRun = async () => {
326
+ const node = getNodeByPath(tree, activePath);
327
+ if (!node || node.type !== "file") {
328
+ appendTerminal("Select a file to run.");
329
+ return;
330
+ }
331
+
332
+ const selectedLang = LANGUAGE_OPTIONS.find((l) => node.name.endsWith(l.ext))?.id;
333
+ if (!selectedLang || !RUNNABLE_LANGS.includes(selectedLang)) {
334
+ appendTerminal(`⚠️ Run not supported for this file type.`);
335
+ return;
336
+ }
337
+
338
+ // Force fresh run: clear accumulated input and terminal
339
+ setAccumStdin("");
340
+ resetTerminal(false);
341
+ setAwaitingInput(false);
342
+ setInteractivePromptShown(false);
343
+
344
+ const needs = codeNeedsInput(node.content, selectedLang);
345
+
346
+ if (needs) {
347
+ appendTerminal("[Interactive program detected — type input directly into the terminal]");
348
+ setAwaitingInput(true);
349
+ setInteractivePromptShown(true);
350
+ focusXtermHelper();
351
+ return; // wait for user's input to avoid EOFError
352
+ }
353
+
354
+ // Non-interactive: run immediately with empty stdin
355
+ appendTerminal(`[Running (fresh)]`);
356
+ setIsRunning(true);
357
+ setProblems([]);
358
+ try {
359
+ const res = await runCode(node.content, selectedLang, "");
360
+ const out = res.output ?? "";
361
+ if (out) appendTerminal(out);
362
+ setProblems(res.error ? parseProblems(res.output) : []);
363
+ if (outputLooksForInput(out)) {
364
+ setAwaitingInput(true);
365
+ focusXtermHelper();
366
+ } else {
367
+ setAwaitingInput(false);
368
+ setAccumStdin("");
369
+ }
370
+ } catch (err) {
371
+ appendTerminal(String(err));
372
+ setAwaitingInput(true);
373
+ focusXtermHelper();
374
+ } finally {
375
+ setIsRunning(false);
376
+ }
377
+ };
378
+
379
+ // ---------- Agent functions ----------
380
+ const handleAskFix = async () => {
381
+ const node = getNodeByPath(tree, activePath);
382
+ if (!node || node.type !== "file") {
383
+ appendTerminal("Select a file to apply fix.");
384
+ return;
385
+ }
386
+ setIsFixing(true);
387
+ try {
388
+ const userHint = prompt.trim() ? `User request: ${prompt}` : "";
389
+ const reply = await askAgent(
390
+ `Improve, debug, or refactor this ${LANGUAGE_OPTIONS.find((l) => node.name.endsWith(l.ext))?.id || "file"} file.\n${userHint}\nReturn ONLY updated code, no explanation.\n\nCODE:\n${node.content}`
391
+ );
392
+ const updatedTree = updateFileContent(tree, node.path, reply);
393
+ setTree(updatedTree);
394
+ appendTerminal("[AI] Applied fixes to file.");
395
+ } catch (err) {
396
+ appendTerminal(String(err));
397
+ } finally {
398
+ setIsFixing(false);
399
+ }
400
+ };
401
+
402
+ const handleExplainSelection = async () => {
403
+ const node = getNodeByPath(tree, activePath);
404
+ if (!node || node.type !== "file") {
405
+ setExplanation("Select a file to explain.");
406
+ return;
407
+ }
408
+ setIsExplaining(true);
409
+ try {
410
+ const editor = editorRef.current;
411
+ let selectedCode = "";
412
+ try {
413
+ selectedCode = editor?.getModel()?.getValueInRange(editor.getSelection()) || "";
414
+ } catch {}
415
+ const code = selectedCode.trim() || node.content;
416
+ const userHint = prompt.trim() ? `Focus on: ${prompt}` : "Give a clear and simple explanation.";
417
+ const reply = await askAgent(
418
+ `Explain what this code does, any risks, and improvements.\n${userHint}\n\nCODE:\n${code}`
419
+ );
420
+ setExplanation(reply);
421
+ } catch (err) {
422
+ setExplanation(String(err));
423
+ } finally {
424
+ setIsExplaining(false);
425
+ }
426
+ };
427
+
428
+ // AI suggestions for continuation
429
+ const fetchAiSuggestions = async (code) => {
430
+ if (!code?.trim()) return;
431
+ try {
432
+ const reply = await askAgent(`Suggest possible next lines for continuation. Return 3 short snippets.\n${code}`);
433
+ setAiSuggestions(reply.split("\n").filter((l) => l.trim()));
434
+ } catch {
435
+ // ignore
436
+ }
437
+ };
438
+
439
+ // ---------- Search ----------
440
+ const handleSearchToggle = () => setSearchOpen(!searchOpen);
441
+ const handleSearchNow = () => {
442
+ if (!searchQuery) return;
443
+ const results = searchTree(tree, searchQuery);
444
+ alert(`Found ${results.length} results:\n` + JSON.stringify(results, null, 2));
445
+ };
446
+
447
+ // Editor change
448
+ const updateActiveFileContent = (value) => {
449
+ const node = getNodeByPath(tree, activePath);
450
+ if (!node) return;
451
+ const updated = updateFileContent(tree, activePath, value ?? "");
452
+ setTree(updated);
453
+ };
454
+
455
+ // Render tree
456
+ const renderTree = (node, depth = 0) => {
457
+ const isActive = node.path === activePath;
458
+ return (
459
+ <div key={node.path || node.name} style={{ paddingLeft: depth * 10 }}>
460
+ <div
461
+ className={`tree-item ${node.type} ${isActive ? "ide-file-item-active" : ""}`}
462
+ onClick={() => setActivePath(node.path)}
463
+ onContextMenu={(e) => {
464
+ e.preventDefault();
465
+ setActivePath(node.path);
466
+ setContextMenu({ x: e.pageX, y: e.pageY, file: node.path });
467
+ }}
468
+ style={{ display: "flex", alignItems: "center", gap: 8 }}
469
  >
470
+ <span style={{ width: 18 }}>{node.type === "folder" ? "📁" : "📄"}</span>
471
+ <span className="ide-file-name">{node.name}</span>
472
+ </div>
473
+
474
+ {node.children && node.children.map((c) => renderTree(c, depth + 1))}
475
+ </div>
476
+ );
477
+ };
478
+
479
+ const anyLoading = isRunning || isFixing || isExplaining;
480
+
481
+ // ---------- JSX ----------
482
+ return (
483
+ <div
484
+ className={`ide-root ${theme === "vs-dark" ? "ide-dark" : "ide-light"}`}
485
+ style={{ overflowX: "hidden" }} // prevent horizontal white gap / scroller issue
486
+ >
487
+ <input ref={fileInputRef} id="file-import-input" type="file" style={{ display: "none" }} onChange={handleFileInputChange} />
488
+
489
+ <div className="ide-menubar">
490
+ <div className="ide-menubar-left">
491
+ <span className="ide-logo">⚙️ DevMate IDE</span>
492
+
493
+ <button onClick={handleNewFile} disabled={anyLoading}>📄 New File</button>
494
+ <button onClick={handleNewFolder} disabled={anyLoading}>📁 New Folder</button>
495
+ <button onClick={handleRename} disabled={anyLoading}>✏️ Rename</button>
496
+ <button onClick={handleDelete} disabled={anyLoading}>🗑 Delete</button>
497
+ <button onClick={downloadFile} disabled={anyLoading}>📥 Download</button>
498
+ <button onClick={() => downloadProjectZip()} disabled={anyLoading}>📦 ZIP</button>
499
+ <button onClick={handleImportFileClick} disabled={anyLoading}>📤 Import File</button>
500
+ </div>
501
+
502
+ <div className="ide-menubar-right">
503
+ <button onClick={handleSearchToggle} disabled={anyLoading}>🔍 Search</button>
504
+ <button onClick={handleRun} disabled={isRunning || anyLoading}>{isRunning ? "⏳ Running..." : "▶ Run"}</button>
505
+ <button onClick={handleAskFix} disabled={isFixing || anyLoading}>{isFixing ? "⏳ Fixing..." : "🤖 Fix"}</button>
506
+ <button onClick={handleExplainSelection} disabled={isExplaining || anyLoading}>{isExplaining ? "⏳ Explaining" : "📖 Explain"}</button>
507
+ <button onClick={() => setTheme((t) => (t === "vs-dark" ? "light" : "vs-dark"))} disabled={anyLoading}>
508
+ {theme === "vs-dark" ? "☀️" : "🌙"}
509
+ </button>
510
+ <button onClick={clearTerminal} disabled={anyLoading} title="Clear terminal">🧹 Clear</button>
511
+ </div>
512
+ </div>
513
+
514
+ {(isRunning || isFixing || isExplaining) && (
515
+ <div className="ide-progress-wrap">
516
+ <div className="ide-progress" />
517
+ </div>
518
+ )}
519
+
520
+ <div className="ide-body">
521
+ <div className="ide-sidebar">
522
+ <div className="ide-sidebar-header">
523
+ <span>EXPLORER</span>
524
+ <button className="ide-icon-button" onClick={handleNewFile} title="New File" disabled={anyLoading}>+</button>
525
+ </div>
526
+ <div className="ide-file-list" style={{ padding: 6 }}>{renderTree(tree)}</div>
527
+ </div>
528
+
529
+ <div className="ide-main">
530
+ <div className="ide-editor-wrapper">
531
+ <Editor
532
+ height="100%"
533
+ theme={theme}
534
+ language={langMeta.monaco}
535
+ value={currentNode?.content || ""}
536
+ onChange={updateActiveFileContent}
537
+ onMount={(editor) => (editorRef.current = editor)}
538
+ onBlur={() => fetchAiSuggestions(currentNode?.content)}
539
+ options={{ minimap: { enabled: true }, fontSize: 14, scrollBeyondLastLine: false }}
540
+ />
541
+ </div>
542
+
543
+ {aiSuggestions.length > 0 && (
544
+ <div className="ai-popup">
545
+ {aiSuggestions.map((s, i) => (
546
+ <div key={i} className="ai-suggest" onClick={() => updateFileContent(tree, activePath, (currentNode?.content || "") + "\n" + s)}>{s}</div>
547
+ ))}
548
+ </div>
549
+ )}
550
+
551
+ <div className="ide-panels">
552
+ <div style={{ marginBottom: 8 }}>
553
+ <div style={{ fontSize: 12, color: "#ccc", marginBottom: 6 }}>Terminal</div>
554
+
555
+ <XTerm
556
+ output={output}
557
+ onData={(line) => {
558
+ // line is the typed content (no CR), pass to unified runner
559
+ const trimmed = (line || "").replace(/\r$/, "");
560
+ if (!trimmed && !awaitingInput) {
561
+ // nothing typed and not expecting input
562
+ return;
563
+ }
564
+ runCodeWithUpdatedInput(trimmed);
565
+ }}
566
+ />
567
+
568
+ {awaitingInput && (
569
+ <div style={{ marginTop: 8, padding: 8, background: "#252526", border: "1px solid #333", borderRadius: 6 }}>
570
+ <div style={{ marginBottom: 6, color: "#ddd" }}>
571
+ This program appears to be interactive and requires console input.
572
+ </div>
573
+ <div style={{ display: "flex", gap: 8 }}>
574
+ <button onClick={() => { focusXtermHelper(); }} className="ide-button">▶ Focus Terminal</button>
575
+ <button onClick={() => { resetTerminal(true); appendTerminal("[Interactive session started — type into the terminal]"); setAwaitingInput(true); focusXtermHelper(); }} className="ide-button">▶ Start interactive session</button>
576
+ <div style={{ color: "#999", alignSelf: "center" }}>Type & press Enter in terminal.</div>
577
+ </div>
578
+ </div>
579
+ )}
580
+ </div>
581
+
582
+ {problems.length > 0 && (
583
+ <div className="ide-problems-panel">
584
+ <div>🚨 Problems ({problems.length})</div>
585
+ {problems.map((p, i) => <div key={i}>{p.path}:{p.line} — {p.message}</div>)}
586
+ </div>
587
+ )}
588
+ </div>
589
+ </div>
590
+
591
+ <div className="ide-right-panel">
592
+ <div className="ide-ai-header">🤖 AI Assistant</div>
593
+
594
+ <div className="ide-ai-section">
595
+ <label className="ide-ai-label">Instruction</label>
596
+ <textarea className="ide-agent-textarea" placeholder="Ask the AI (optimize, add tests, convert, etc.)" value={prompt} onChange={(e) => setPrompt(e.target.value)} />
597
+ </div>
598
+
599
+ <div className="ide-ai-buttons">
600
+ <button onClick={handleAskFix} disabled={isFixing || anyLoading}>{isFixing ? "⏳ Apply Fix" : "💡 Apply Fix"}</button>
601
+ <button onClick={handleExplainSelection} disabled={isExplaining || anyLoading}>{isExplaining ? "⏳ Explaining" : "📖 Explain Code"}</button>
602
+ </div>
603
+
604
+ {explanation && (
605
+ <div className="ide-ai-section">
606
+ <label className="ide-ai-label">Explanation</label>
607
+ <div className="ide-explain">{explanation}</div>
608
+ </div>
609
+ )}
610
+ </div>
611
+ </div>
612
+
613
+ {searchOpen && (
614
+ <div className="search-dialog">
615
+ <input placeholder="Search text..." onChange={(e) => setSearchQuery(e.target.value)} />
616
+ <button onClick={handleSearchNow}>Search</button>
617
+ </div>
618
+ )}
619
+
620
+ {contextMenu && (
621
+ <div className="ide-context-menu" style={{ top: contextMenu.y, left: contextMenu.x }} onMouseLeave={() => setContextMenu(null)}>
622
+ <div onClick={() => { setContextMenu(null); handleRename(); }}>✏️ Rename</div>
623
+ <div onClick={() => { setContextMenu(null); handleDelete(); }}>🗑 Delete</div>
624
+ <div onClick={() => { setContextMenu(null); downloadFile(); }}>📥 Download</div>
625
+ </div>
626
+ )}
627
  </div>
628
  );
629
  }
src/Terminal.js ADDED
@@ -0,0 +1,137 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // src/Terminal.js
2
+ import { useEffect, useRef } from "react";
3
+ import { Terminal } from "xterm";
4
+ import { FitAddon } from "xterm-addon-fit";
5
+ import "xterm/css/xterm.css";
6
+
7
+ /**
8
+ * Props:
9
+ * - onData(line: string) -> called when user presses Enter with the typed line (no trailing \r)
10
+ * - output (string) -> append output to the terminal when it changes
11
+ */
12
+ export default function XTerm({ onData, output }) {
13
+ const containerId = "terminal-container";
14
+ const termRef = useRef(null);
15
+ const bufferRef = useRef(""); // collects user typed chars until Enter
16
+ const fitRef = useRef(null);
17
+
18
+ useEffect(() => {
19
+ const term = new Terminal({
20
+ cursorBlink: true,
21
+ fontSize: 14,
22
+ disableStdin: false,
23
+ convertEol: true,
24
+ theme: {
25
+ background: "#1e1e1e",
26
+ foreground: "#ffffff",
27
+ },
28
+ });
29
+
30
+ const fitAddon = new FitAddon();
31
+ fitRef.current = fitAddon;
32
+
33
+ term.loadAddon(fitAddon);
34
+ term.open(document.getElementById(containerId));
35
+ fitAddon.fit();
36
+
37
+ // Keep a reference
38
+ termRef.current = term;
39
+
40
+ // echo typed characters and detect Enter
41
+ term.onData((data) => {
42
+ // xterm sends strings including characters and control chars like '\r'
43
+ // append to visible terminal
44
+ term.write(data);
45
+
46
+ // common Enter is '\r' (CR)
47
+ if (data === "\r" || data === "\n") {
48
+ // capture current buffer as line, trim trailing CR/LF
49
+ const line = (bufferRef.current || "").replace(/\r?\n$/, "");
50
+ bufferRef.current = ""; // reset buffer
51
+ // echo newline if not already
52
+ // (we already wrote the '\r' above)
53
+ // call parent handler
54
+ try {
55
+ if (typeof onData === "function" && line !== null) onData(line);
56
+ } catch (e) {
57
+ // swallow
58
+ console.error("XTerm onData handler threw:", e);
59
+ }
60
+ return;
61
+ }
62
+
63
+ // backspace handling: if user presses backspace key, it may come as '\x7f' or '\b'
64
+ if (data === "\x7f" || data === "\b") {
65
+ bufferRef.current = bufferRef.current.slice(0, -1);
66
+ return;
67
+ }
68
+
69
+ // other control sequences ignore
70
+ if (data.charCodeAt(0) < 32) {
71
+ // ignore other ctrl chars
72
+ return;
73
+ }
74
+
75
+ // normal characters: append to buffer
76
+ bufferRef.current += data;
77
+ });
78
+
79
+ // expose a simple focus method on container element for external focusing
80
+ const container = document.getElementById(containerId);
81
+ if (container) container.tabIndex = 0;
82
+
83
+ return () => {
84
+ try {
85
+ term.dispose();
86
+ } catch {}
87
+ };
88
+ }, [onData]);
89
+
90
+ // Append new output when `output` prop changes
91
+ useEffect(() => {
92
+ const term = termRef.current;
93
+ if (!term || !output) return;
94
+ // write a newline and the output text (preserves newlines)
95
+ term.writeln("");
96
+ // if output includes multiple lines, write each
97
+ const lines = output.split(/\r?\n/);
98
+ lines.forEach((ln, idx) => {
99
+ // avoid extra blank at very start
100
+ if (idx === 0 && ln === "") return;
101
+ term.writeln(ln);
102
+ });
103
+ }, [output]);
104
+
105
+ // helper to focus the hidden xterm textarea
106
+ const focus = () => {
107
+ // xterm's helper textarea is what receives keyboard input
108
+ const ta = document.querySelector(`#${containerId} .xterm-helper-textarea`);
109
+ if (ta) {
110
+ ta.focus();
111
+ const len = ta.value?.length ?? 0;
112
+ try { ta.setSelectionRange(len, len); } catch {}
113
+ } else {
114
+ const cont = document.getElementById(containerId);
115
+ if (cont) cont.focus();
116
+ }
117
+ };
118
+
119
+ // expose a global method in-case parent wants to call it (not ideal but handy)
120
+ useEffect(() => {
121
+ window.__xterm_focus = focus;
122
+ return () => { try { delete window.__xterm_focus } catch {} };
123
+ }, []);
124
+
125
+ return (
126
+ <div
127
+ id={containerId}
128
+ style={{
129
+ width: "100%",
130
+ height: "180px",
131
+ background: "#1e1e1e",
132
+ borderTop: "1px solid #333",
133
+ outline: "none",
134
+ }}
135
+ />
136
+ );
137
+ }
src/agent/assistant.js ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // src/agent/assistant.js
2
+ import { api } from "../apiClient";
3
+
4
+ export async function askAgent(message, history = []) {
5
+ const res = await api.post(
6
+ "/chat-stream",
7
+ { message, history },
8
+ { responseType: "text" } // backend returns text/plain
9
+ );
10
+
11
+ return res.data; // whole reply text
12
+ }
src/agent/projectGenerator.js ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // src/agent/projectGenerator.js
2
+ import { api } from "../apiClient";
3
+
4
+ export async function generateProject(file, frontend, backend, database) {
5
+ const formData = new FormData();
6
+ formData.append("file", file);
7
+ formData.append("frontend", frontend);
8
+ formData.append("backend", backend);
9
+ formData.append("database", database);
10
+
11
+ const res = await api.post("/chat-stream-doc", formData, {
12
+ responseType: "blob", // you’re getting ZIP back
13
+ });
14
+
15
+ // trigger download
16
+ const url = window.URL.createObjectURL(new Blob([res.data]));
17
+ const a = document.createElement("a");
18
+ a.href = url;
19
+ a.download = "generated_project.zip";
20
+ a.click();
21
+ window.URL.revokeObjectURL(url);
22
+ }
src/agent/runner.js ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // src/agent/runner.js
2
+ import { api } from "../apiClient";
3
+
4
+ // Map language id -> file extension
5
+ const EXT_MAP = {
6
+ python: ".py",
7
+ javascript: ".js",
8
+ typescript: ".ts",
9
+ c: ".c",
10
+ cpp: ".cpp",
11
+ java: ".java",
12
+ html: ".html",
13
+ css: ".css",
14
+ json: ".json",
15
+ };
16
+
17
+ export async function runCode(code, language, stdin = "") {
18
+ const ext = EXT_MAP[language] || ".txt";
19
+
20
+ // Use a consistent name – backend only cares about extension
21
+ const filename = "main" + ext;
22
+
23
+ const res = await api.post("/execute", {
24
+ code,
25
+ filename,
26
+ input: stdin,
27
+ });
28
+
29
+ return res.data; // { output, error }
30
+ }
src/apiClient.js ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ import axios from "axios";
2
+
3
+ const API_BASE = process.env.REACT_APP_API_BASE; // 👈 CRA uses REACT_APP_
4
+
5
+ export const api = axios.create({
6
+ baseURL: API_BASE,
7
+ });
src/fileStore.js ADDED
@@ -0,0 +1,211 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // fileStore.js
2
+ const STORAGE_KEY = "devmate_ide_tree_v2";
3
+
4
+ /**
5
+ * Tree node structure:
6
+ * {
7
+ * type: "folder" | "file",
8
+ * name: "src" or "main.py",
9
+ * path: "src" or "src/main.py",
10
+ * children: [ ... ] // only for folders
11
+ * content: "..." // only for files
12
+ * }
13
+ */
14
+
15
+ const defaultTree = {
16
+ type: "folder",
17
+ name: "root",
18
+ path: "",
19
+ children: [
20
+ {
21
+ type: "file",
22
+ name: "main.py",
23
+ path: "main.py",
24
+ content: "# Python\nprint('Hello from IDE')",
25
+ },
26
+ {
27
+ type: "folder",
28
+ name: "src",
29
+ path: "src",
30
+ children: [
31
+ {
32
+ type: "file",
33
+ name: "script.js",
34
+ path: "src/script.js",
35
+ content: "console.log('Hello from src');",
36
+ },
37
+ ],
38
+ },
39
+ ],
40
+ };
41
+
42
+ // ---------- persistence ----------
43
+ export function loadTree() {
44
+ try {
45
+ const raw = localStorage.getItem(STORAGE_KEY);
46
+ return raw ? JSON.parse(raw) : defaultTree;
47
+ } catch (e) {
48
+ console.warn("loadTree failed:", e);
49
+ return defaultTree;
50
+ }
51
+ }
52
+
53
+ export function saveTree(tree) {
54
+ try {
55
+ localStorage.setItem(STORAGE_KEY, JSON.stringify(tree));
56
+ } catch (e) {
57
+ console.warn("saveTree failed:", e);
58
+ }
59
+ }
60
+
61
+ // ---------- traversal helpers ----------
62
+ function clone(obj) {
63
+ return JSON.parse(JSON.stringify(obj));
64
+ }
65
+
66
+ // find node and parent by path
67
+ export function findNodeAndParent(root, path) {
68
+ if (path === "" || path == null) return { node: root, parent: null };
69
+ const parts = path.split("/").filter(Boolean);
70
+ let node = root;
71
+ let parent = null;
72
+ for (let i = 0; i < parts.length; i++) {
73
+ const part = parts[i];
74
+ parent = node;
75
+ if (!parent.children) return { node: null, parent: null };
76
+ node = parent.children.find((c) => c.name === part);
77
+ if (!node) return { node: null, parent: null };
78
+ }
79
+ return { node, parent };
80
+ }
81
+
82
+ export function getNodeByPath(root, path) {
83
+ const r = findNodeAndParent(root, path);
84
+ return r.node || null;
85
+ }
86
+
87
+ // build child path
88
+ function joinPath(parentPath, name) {
89
+ if (!parentPath) return name;
90
+ if (!name) return parentPath;
91
+ return parentPath + "/" + name;
92
+ }
93
+
94
+ // ---------- CRUD operations ----------
95
+
96
+ // Add file under parentPath (string). If parentPath points to file, use its parent.
97
+ export function addFile(tree, filename, parentPath = "") {
98
+ const newTree = clone(tree);
99
+ // find parent
100
+ const { node: parent } = findNodeAndParent(newTree, parentPath);
101
+ const target = parent?.type === "folder" ? parent : newTree;
102
+ // ensure unique name
103
+ let name = filename;
104
+ const exists = (name) =>
105
+ target.children && target.children.some((c) => c.name === name);
106
+ let idx = 1;
107
+ const base = name.includes(".") ? name.slice(0, name.lastIndexOf(".")) : name;
108
+ const ext = name.includes(".") ? name.slice(name.lastIndexOf(".")) : "";
109
+ while (exists(name)) {
110
+ name = `${base}_${idx++}${ext}`;
111
+ }
112
+ const path = joinPath(target.path, name);
113
+ const newNode = { type: "file", name, path, content: `// ${name}\n` };
114
+ target.children = target.children || [];
115
+ target.children.push(newNode);
116
+ return newTree;
117
+ }
118
+
119
+ // Add folder under parentPath
120
+ export function addFolder(tree, folderName, parentPath = "") {
121
+ const newTree = clone(tree);
122
+ const { node: parent } = findNodeAndParent(newTree, parentPath);
123
+ const target = parent?.type === "folder" ? parent : newTree;
124
+ let name = folderName;
125
+ const exists = (name) =>
126
+ target.children && target.children.some((c) => c.name === name && c.type === "folder");
127
+ let idx = 1;
128
+ while (exists(name)) {
129
+ name = `${folderName}_${idx++}`;
130
+ }
131
+ const path = joinPath(target.path, name);
132
+ const newNode = { type: "folder", name, path, children: [] };
133
+ target.children = target.children || [];
134
+ target.children.push(newNode);
135
+ return newTree;
136
+ }
137
+
138
+ // Delete node (file or folder) at given path (recursive for folders)
139
+ export function deleteNode(tree, path) {
140
+ const newTree = clone(tree);
141
+ if (!path) return newTree; // cannot delete root
142
+ const parts = path.split("/").filter(Boolean);
143
+ const nameToDelete = parts.pop();
144
+ const parentPath = parts.join("/");
145
+ const { node: parent } = findNodeAndParent(newTree, parentPath);
146
+ if (!parent || !parent.children) return newTree;
147
+ parent.children = parent.children.filter((c) => c.name !== nameToDelete);
148
+ return newTree;
149
+ }
150
+
151
+ // Rename node: change name and update paths recursively for children
152
+ export function renameNode(tree, path, newName) {
153
+ const newTree = clone(tree);
154
+ const { node, parent } = findNodeAndParent(newTree, path);
155
+ if (!node || !parent) {
156
+ // special case root rename not allowed
157
+ if (node && !parent) return newTree;
158
+ return newTree;
159
+ }
160
+ // ensure no duplicate under parent
161
+ if (parent.children.some((c) => c.name === newName && c.path !== node.path)) {
162
+ // collision: abort
163
+ return newTree;
164
+ }
165
+
166
+ const oldPath = node.path;
167
+ node.name = newName;
168
+ const newPath = joinPath(parent.path, newName);
169
+ // update path recursively
170
+ function updatePaths(n, currPath) {
171
+ n.path = currPath;
172
+ if (n.children) {
173
+ for (let child of n.children) {
174
+ const childNewPath = currPath ? currPath + "/" + child.name : child.name;
175
+ updatePaths(child, childNewPath);
176
+ }
177
+ }
178
+ }
179
+ updatePaths(node, newPath);
180
+ return newTree;
181
+ }
182
+
183
+ // Update file content
184
+ export function updateFileContent(tree, path, content) {
185
+ const newTree = clone(tree);
186
+ const { node } = findNodeAndParent(newTree, path);
187
+ if (node && node.type === "file") {
188
+ node.content = content;
189
+ }
190
+ return newTree;
191
+ }
192
+
193
+ // Search within files (returns array of {path, file, excerpt})
194
+ export function searchTree(node, term, results = []) {
195
+ if (!node) return results;
196
+ if (node.type === "file" && node.content && node.content.includes(term)) {
197
+ results.push({
198
+ path: node.path,
199
+ file: node.name,
200
+ excerpt: node.content
201
+ .split("\n")
202
+ .filter((l) => l.includes(term))
203
+ .slice(0, 5)
204
+ .join("\n"),
205
+ });
206
+ }
207
+ if (node.children) {
208
+ for (const c of node.children) searchTree(c, term, results);
209
+ }
210
+ return results;
211
+ }
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
+ }