Athspi commited on
Commit
1403fec
·
verified ·
1 Parent(s): 4c5723c

Update templates/index.html

Browse files
Files changed (1) hide show
  1. templates/index.html +163 -501
templates/index.html CHANGED
@@ -10,10 +10,8 @@
10
  <link href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600&display=swap" rel="stylesheet">
11
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
12
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github-dark.min.css">
13
- <link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/diff2html/bundles/css/diff2html.min.css"/>
14
 
15
  <style>
16
- /* --- Root Variables for Theming --- */
17
  :root {
18
  --font-family: 'Poppins', sans-serif;
19
  --background-primary: #1e1e2e;
@@ -22,568 +20,232 @@
22
  --text-primary: #cdd6f4;
23
  --text-secondary: #a6adc8;
24
  --primary-color: #89b4fa;
25
- --secondary-color: #cba6f7;
26
  --accent-color: #f5c2e7;
27
  --success-color: #a6e3a1;
28
- --warning-color: #fab387;
29
  --error-color: #f38ba8;
30
  --border-color: #45475a;
31
- --shadow-color: rgba(0, 0, 0, 0.2);
32
- }
33
-
34
- /* --- Global Styles & Resets --- */
35
- * {
36
- margin: 0;
37
- padding: 0;
38
- box-sizing: border-box;
39
- }
40
-
41
- body {
42
- font-family: var(--font-family);
43
- background-color: var(--background-primary);
44
- color: var(--text-primary);
45
- display: flex;
46
- height: 100vh;
47
- overflow: hidden;
48
- }
49
-
50
- /* --- Custom Scrollbar --- */
51
- ::-webkit-scrollbar {
52
- width: 8px;
53
- }
54
- ::-webkit-scrollbar-track {
55
- background: var(--background-secondary);
56
- }
57
- ::-webkit-scrollbar-thumb {
58
- background-color: var(--background-tertiary);
59
- border-radius: 10px;
60
- }
61
- ::-webkit-scrollbar-thumb:hover {
62
- background-color: var(--primary-color);
63
- }
64
-
65
- /* --- Main Layout --- */
66
- .container {
67
- display: flex;
68
- width: 100%;
69
- height: 100%;
70
- }
71
-
72
- .sidebar {
73
- display: flex;
74
- flex-direction: column;
75
- background-color: var(--background-secondary);
76
- border-right: 1px solid var(--border-color);
77
- padding: 20px;
78
- height: 100vh;
79
- transition: width 0.3s ease;
80
  }
 
 
 
 
 
 
 
 
81
  .left-sidebar { width: 20%; min-width: 250px; }
82
  .right-sidebar { width: 25%; min-width: 300px; border-right: none; border-left: 1px solid var(--border-color); }
83
-
84
- .sidebar-header {
85
- display: flex;
86
- align-items: center;
87
- gap: 12px;
88
- padding-bottom: 15px;
89
- border-bottom: 1px solid var(--border-color);
90
- margin-bottom: 15px;
91
- flex-shrink: 0;
92
- }
93
- .sidebar-header i {
94
- color: var(--primary-color);
95
- font-size: 1.2rem;
96
- }
97
- .sidebar-header h2 {
98
- font-size: 1.1rem;
99
- font-weight: 500;
100
- }
101
-
102
- .sidebar-content {
103
- flex-grow: 1;
104
- overflow-y: auto;
105
- }
106
-
107
- .sidebar-footer {
108
- flex-shrink: 0;
109
- padding-top: 15px;
110
- border-top: 1px solid var(--border-color);
111
- }
112
-
113
- /* --- File Tree Styling --- */
114
- #file-tree div {
115
- padding: 8px 12px;
116
- cursor: pointer;
117
- border-radius: 6px;
118
- margin-bottom: 4px;
119
- transition: background-color 0.2s ease, color 0.2s ease;
120
- font-size: 0.9rem;
121
- color: var(--text-secondary);
122
- white-space: nowrap;
123
- overflow: hidden;
124
- text-overflow: ellipsis;
125
- }
126
- #file-tree div:hover {
127
- background-color: var(--background-tertiary);
128
- color: var(--text-primary);
129
- }
130
- #file-tree div.active {
131
- background-color: var(--primary-color);
132
- color: var(--background-primary);
133
- font-weight: 500;
134
- }
135
-
136
- /* --- File Content Viewer --- */
137
- #file-content-display {
138
- background-color: #161625;
139
- border-radius: 8px;
140
- height: 100%;
141
- padding: 15px;
142
- font-size: 0.85rem;
143
- line-height: 1.6;
144
- }
145
- #file-content-display code {
146
- font-family: 'Fira Code', 'Courier New', monospace;
147
- }
148
-
149
- /* --- Main Content Area --- */
150
- .main-content {
151
- flex-grow: 1;
152
- display: flex;
153
- flex-direction: column;
154
- padding: 0;
155
- height: 100vh;
156
- }
157
-
158
- /* --- Upload Section --- */
159
- #upload-section {
160
- display: flex;
161
- flex-direction: column;
162
- justify-content: center;
163
- align-items: center;
164
- height: 100%;
165
- text-align: center;
166
- padding: 20px;
167
- }
168
- .upload-box {
169
- background: var(--background-secondary);
170
- padding: 40px;
171
- border-radius: 12px;
172
- border: 1px dashed var(--border-color);
173
- width: 90%;
174
- max-width: 500px;
175
- }
176
- .upload-icon {
177
- font-size: 3rem;
178
- color: var(--primary-color);
179
- margin-bottom: 20px;
180
- }
181
- .upload-box h2 {
182
- font-size: 1.8rem;
183
- margin-bottom: 10px;
184
- }
185
- .upload-box p {
186
- color: var(--text-secondary);
187
- margin-bottom: 30px;
188
- }
189
- .upload-label {
190
- background-color: var(--primary-color);
191
- color: var(--background-primary);
192
- padding: 12px 20px;
193
- border-radius: 8px;
194
- cursor: pointer;
195
- display: inline-block;
196
- font-weight: 600;
197
- transition: background-color 0.2s ease;
198
- }
199
- .upload-label:hover {
200
- background-color: var(--accent-color);
201
- }
202
- .upload-label i {
203
- margin-right: 8px;
204
- }
205
- .file-name {
206
- margin-top: 15px;
207
- color: var(--success-color);
208
- font-weight: 500;
209
- }
210
- #progress-indicator p {
211
- margin-bottom: 15px;
212
- font-size: 1.1rem;
213
- }
214
- .loader {
215
- border: 4px solid var(--background-tertiary);
216
- border-radius: 50%;
217
- border-top: 4px solid var(--primary-color);
218
- width: 50px;
219
- height: 50px;
220
- animation: spin 1.5s linear infinite;
221
- margin: 20px auto;
222
- }
223
- @keyframes spin {
224
- 0% { transform: rotate(0deg); }
225
- 100% { transform: rotate(360deg); }
226
- }
227
-
228
- /* --- Chat Section --- */
229
- #chat-section {
230
- display: none;
231
- flex-direction: column;
232
- height: 100%;
233
- background-color: var(--background-primary);
234
- }
235
-
236
- #chat-window {
237
- flex-grow: 1;
238
- overflow-y: auto;
239
- padding: 20px;
240
- display: flex;
241
- flex-direction: column;
242
- gap: 20px;
243
- }
244
-
245
- .message {
246
- padding: 12px 18px;
247
- border-radius: 18px;
248
- max-width: 85%;
249
- line-height: 1.6;
250
- }
251
- .user-message {
252
- background-color: var(--primary-color);
253
- color: var(--background-primary);
254
- border-bottom-right-radius: 4px;
255
- align-self: flex-end;
256
- word-wrap: break-word;
257
- }
258
- .ai-message {
259
- background-color: var(--background-secondary);
260
- color: var(--text-primary);
261
- border-bottom-left-radius: 4px;
262
- align-self: flex-start;
263
- word-wrap: break-word;
264
- }
265
- .ai-message pre {
266
- background-color: var(--background-primary);
267
- border: 1px solid var(--border-color);
268
- border-radius: 8px;
269
- padding: 15px;
270
- margin-top: 10px;
271
- font-size: 0.85rem;
272
- white-space: pre-wrap; /* Since CSS 2.1 */
273
- word-wrap: break-word; /* Internet Explorer 5.5+ */
274
- }
275
- .ai-message code {
276
- font-family: 'Fira Code', 'Courier New', monospace;
277
- }
278
-
279
- .chat-input-area {
280
- display: flex;
281
- align-items: center;
282
- padding: 15px 20px;
283
- border-top: 1px solid var(--border-color);
284
- flex-shrink: 0;
285
- }
286
-
287
- #message-input {
288
- flex-grow: 1;
289
- background-color: var(--background-secondary);
290
- color: var(--text-primary);
291
- border: 1px solid var(--border-color);
292
- border-radius: 10px;
293
- padding: 12px 15px;
294
- resize: none;
295
- font-family: var(--font-family);
296
- font-size: 1rem;
297
- line-height: 1.5;
298
- transition: border-color 0.2s ease;
299
- max-height: 200px;
300
- overflow-y: auto;
301
- }
302
- #message-input:focus {
303
- outline: none;
304
- border-color: var(--primary-color);
305
- }
306
-
307
- #send-btn, #download-btn {
308
- background-color: transparent;
309
- border: none;
310
- color: var(--primary-color);
311
- font-size: 1.5rem;
312
- cursor: pointer;
313
- transition: color 0.2s ease;
314
- padding: 10px;
315
- margin-left: 10px;
316
- }
317
- #send-btn:hover, #download-btn:hover {
318
- color: var(--accent-color);
319
- }
320
-
321
- #download-btn {
322
- width: 100%;
323
- background-color: var(--primary-color);
324
- color: var(--background-primary);
325
- display: flex;
326
- align-items: center;
327
- justify-content: center;
328
- gap: 10px;
329
- font-size: 1rem;
330
- font-weight: 600;
331
- border-radius: 8px;
332
- padding: 12px;
333
- }
334
- #download-btn:hover {
335
- background-color: var(--accent-color);
336
- color: var(--background-primary);
337
- }
338
- #download-btn i {
339
- font-size: 1.1rem;
340
- }
341
  </style>
342
  </head>
343
  <body>
344
  <div class="container">
345
  <div class="sidebar left-sidebar">
346
- <div class="sidebar-header">
347
- <i class="fa-solid fa-folder-tree"></i>
348
- <h2>Project Files</h2>
349
- </div>
350
  <div id="file-tree" class="sidebar-content"></div>
351
- <div class="sidebar-footer">
352
- <button id="download-btn">
353
- <i class="fa-solid fa-download"></i>
354
- <span>Download Project</span>
355
- </button>
356
- </div>
357
  </div>
358
-
359
  <div class="main-content">
360
  <div id="upload-section">
361
  <div class="upload-box">
362
  <i class="fa-solid fa-rocket upload-icon"></i>
363
  <h2>AI Code Assistant</h2>
364
- <p>Upload a .zip of your project to begin analyzing and improving your code.</p>
365
  <form id="upload-form" enctype="multipart/form-data">
366
- <label for="project-zip" class="upload-label">
367
- <i class="fa-solid fa-paperclip"></i>
368
- <span>Choose a .zip file...</span>
369
- </label>
370
  <input type="file" id="project-zip" name="project_zip" accept=".zip" hidden>
371
- <button type="submit" id="upload-button" style="display: none;">Upload and Analyze</button>
372
  </form>
373
  <p id="file-name-display" class="file-name"></p>
374
  </div>
375
- <div id="progress-indicator" style="display: none;">
376
- <p>AI is understanding your project...</p>
377
- <div class="loader"></div>
378
- </div>
379
  </div>
380
-
381
- <div id="chat-section" style="display: none;">
382
- <div id="chat-window">
383
- </div>
384
  <div class="chat-input-area">
385
  <textarea id="message-input" placeholder="Ask the AI to refactor, explain, or improve your code..." rows="1"></textarea>
386
  <button id="send-btn"><i class="fa-solid fa-paper-plane"></i></button>
387
  </div>
388
  </div>
389
  </div>
390
-
391
  <div class="sidebar right-sidebar">
392
- <div class="sidebar-header">
393
- <i class="fa-solid fa-file-lines"></i>
394
- <h2>File Content</h2>
395
- </div>
396
- <div class="sidebar-content">
397
- <pre id="file-content-display"><code class="plaintext">Select a file from the left to view its content.</code></pre>
398
- </div>
399
  </div>
400
  </div>
401
-
402
  <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
403
- <script type="text/javascript" src="https://cdn.jsdelivr.net/npm/diff2html/bundles/js/diff2html.min.js"></script>
404
-
405
  <script>
406
  document.addEventListener('DOMContentLoaded', function() {
407
- const uploadForm = document.getElementById('upload-form');
408
- const progressIndicator = document.getElementById('progress-indicator');
409
- const chatSection = document.getElementById('chat-section');
410
- const uploadSection = document.getElementById('upload-section');
411
- const chatWindow = document.getElementById('chat-window');
412
- const messageInput = document.getElementById('message-input');
413
- const sendBtn = document.getElementById('send-btn');
414
- const fileTree = document.getElementById('file-tree');
415
- const fileContentDisplay = document.querySelector('#file-content-display code');
416
- const downloadBtn = document.getElementById('download-btn');
417
-
418
- const projectZipInput = document.getElementById('project-zip');
419
- const fileNameDisplay = document.getElementById('file-name-display');
420
- const uploadButton = document.getElementById('upload-button');
421
-
422
- // --- Event Listeners ---
423
- projectZipInput.addEventListener('change', () => {
424
- if (projectZipInput.files.length > 0) {
425
- const fileName = projectZipInput.files[0].name;
426
- fileNameDisplay.textContent = `Selected: ${fileName}`;
427
- uploadButton.click(); // Automatically trigger the form submission
428
- }
429
- });
430
-
431
- uploadForm.addEventListener('submit', async function(e) {
432
- e.preventDefault();
433
- const formData = new FormData(this);
434
-
435
- document.querySelector('.upload-box').style.display = 'none';
436
- progressIndicator.style.display = 'block';
437
-
438
  try {
439
- const response = await fetch('/upload', {
440
- method: 'POST',
441
- body: formData
442
- });
443
-
444
  const data = await response.json();
445
- progressIndicator.style.display = 'none';
446
-
447
  if (data.error) {
448
- alert(data.error);
449
- document.querySelector('.upload-box').style.display = 'block';
450
- return;
451
  }
452
-
453
- uploadSection.style.display = 'none';
454
- chatSection.style.display = 'flex';
 
 
 
 
 
 
 
 
 
 
 
 
455
  updateFileTree(data.file_tree);
456
- chatWindow.innerHTML = '';
457
  data.chat_history.forEach(msg => {
458
- const role = msg.role === 'model' ? 'assistant' : msg.role;
459
- let content = msg.content;
460
- // For the very first message, don't show the full file context
461
- if (role === 'user' && content.startsWith('The user has uploaded a new project')) {
462
- content = 'Project context sent to AI.'
463
- }
464
- appendMessage(role, content);
465
  });
466
- } catch (error) {
467
- progressIndicator.style.display = 'none';
468
- alert('An error occurred during upload. Please check the console and ensure the backend is running.');
469
- document.querySelector('.upload-box').style.display = 'block';
470
- }
471
- });
472
-
473
- sendBtn.addEventListener('click', sendMessage);
474
- messageInput.addEventListener('keypress', function(e) {
475
- if (e.key === 'Enter' && !e.shiftKey) {
476
- e.preventDefault();
477
- sendMessage();
478
  }
479
- });
480
-
481
- downloadBtn.addEventListener('click', function() {
482
- window.location.href = '/download';
483
- });
484
 
485
- // --- Core Functions ---
486
- async function sendMessage() {
487
- const message = messageInput.value.trim();
488
  if (!message) return;
489
-
490
  appendMessage('user', message);
491
- messageInput.value = '';
492
- messageInput.style.height = 'auto'; // Reset height
493
-
494
- try {
495
- const response = await fetch('/chat', {
496
- method: 'POST',
497
- headers: { 'Content-Type': 'application/json' },
498
- body: JSON.stringify({ message: message })
499
- });
500
-
501
- const data = await response.json();
502
-
503
- if(data.error) {
504
- appendMessage('assistant', `**Error:** ${data.error}`);
505
- } else {
506
- appendMessage('assistant', data.reply);
507
- }
508
-
509
- updateFileTree(); // Refresh file tree in case files were created/deleted
510
- } catch (error) {
511
- appendMessage('assistant', `**Error:** Could not connect to the backend. Please ensure it is running correctly.`);
512
  }
513
- }
514
 
515
- function appendMessage(sender, text) {
516
  const messageElement = document.createElement('div');
517
  messageElement.classList.add('message', sender === 'user' ? 'user-message' : 'ai-message');
 
518
 
519
- // Basic Markdown to HTML conversion
520
- let html = text.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>'); // Bold
521
- html = html.replace(/\`(.*?)\`/g, '<code>$1</code>'); // Inline code
522
-
523
- // Convert Markdown code blocks to highlighted HTML
524
  html = html.replace(/```(\w+)?\n([\s\S]*?)```/g, (match, lang, code) => {
525
  const trimmedCode = code.trim();
526
  const highlighted = hljs.highlight(trimmedCode, { language: lang || 'plaintext', ignoreIllegals: true }).value;
527
- return `<pre><code class="hljs <span class="math-inline">\{lang\}"\></span>{highlighted}</code></pre>`;
528
  });
529
-
530
  messageElement.innerHTML = html.replace(/\n/g, '<br>');
531
-
532
- chatWindow.appendChild(messageElement);
533
- chatWindow.scrollTop = chatWindow.scrollHeight;
534
- }
535
-
536
- async function updateFileTree() {
537
- try {
538
- const response = await fetch('/file_tree');
539
- const data = await response.json();
540
- if (data.file_tree) {
541
- const currentlyActive = document.querySelector('#file-tree .active');
542
- const activeFilePath = currentlyActive ? currentlyActive.dataset.path : null;
543
-
544
- fileTree.innerHTML = '';
545
- data.file_tree.forEach(path => {
546
- const fileElement = document.createElement('div');
547
- fileElement.textContent = path;
548
- fileElement.dataset.path = path;
549
- fileElement.addEventListener('click', () => {
550
- document.querySelectorAll('#file-tree div').forEach(el => el.classList.remove('active'));
551
- fileElement.classList.add('active');
552
- fetchFileContent(path);
553
- });
554
-
555
- if(path === activeFilePath) {
556
- fileElement.classList.add('active');
557
- }
558
-
559
- fileTree.appendChild(fileElement);
560
  });
561
- }
562
- } catch(error) {
563
- console.error("Could not update file tree:", error);
564
  }
565
- }
566
-
567
- async function fetchFileContent(path) {
568
- try {
569
- const response = await fetch(`/file_content?path=${encodeURIComponent(path)}`);
570
- const data = await response.json();
571
- if (data.content) {
572
- fileContentDisplay.textContent = data.content;
573
- hljs.highlightElement(fileContentDisplay);
574
- } else {
575
- fileContentDisplay.textContent = 'Could not load file content.';
576
- }
577
- } catch (error) {
578
- fileContentDisplay.textContent = 'Error loading file content.';
579
  }
580
- }
581
 
582
- // Auto-resize textarea
583
- messageInput.addEventListener('input', function () {
584
- this.style.height = 'auto';
585
- this.style.height = (this.scrollHeight) + 'px';
 
586
  });
 
 
 
 
 
587
  });
588
  </script>
589
  </body>
 
10
  <link href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600&display=swap" rel="stylesheet">
11
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
12
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github-dark.min.css">
 
13
 
14
  <style>
 
15
  :root {
16
  --font-family: 'Poppins', sans-serif;
17
  --background-primary: #1e1e2e;
 
20
  --text-primary: #cdd6f4;
21
  --text-secondary: #a6adc8;
22
  --primary-color: #89b4fa;
 
23
  --accent-color: #f5c2e7;
24
  --success-color: #a6e3a1;
 
25
  --error-color: #f38ba8;
26
  --border-color: #45475a;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
27
  }
28
+ * { margin: 0; padding: 0; box-sizing: border-box; }
29
+ body { font-family: var(--font-family); background-color: var(--background-primary); color: var(--text-primary); display: flex; height: 100vh; overflow: hidden; }
30
+ ::-webkit-scrollbar { width: 8px; }
31
+ ::-webkit-scrollbar-track { background: var(--background-secondary); }
32
+ ::-webkit-scrollbar-thumb { background-color: var(--background-tertiary); border-radius: 10px; }
33
+ ::-webkit-scrollbar-thumb:hover { background-color: var(--primary-color); }
34
+ .container { display: flex; width: 100%; height: 100%; }
35
+ .sidebar { display: flex; flex-direction: column; background-color: var(--background-secondary); border-right: 1px solid var(--border-color); padding: 20px; height: 100vh; transition: width 0.3s ease; }
36
  .left-sidebar { width: 20%; min-width: 250px; }
37
  .right-sidebar { width: 25%; min-width: 300px; border-right: none; border-left: 1px solid var(--border-color); }
38
+ .sidebar-header { display: flex; align-items: center; gap: 12px; padding-bottom: 15px; border-bottom: 1px solid var(--border-color); margin-bottom: 15px; flex-shrink: 0; }
39
+ .sidebar-header i { color: var(--primary-color); font-size: 1.2rem; }
40
+ .sidebar-header h2 { font-size: 1.1rem; font-weight: 500; }
41
+ .sidebar-content { flex-grow: 1; overflow-y: auto; }
42
+ .sidebar-footer { flex-shrink: 0; padding-top: 15px; border-top: 1px solid var(--border-color); }
43
+ #file-tree div { padding: 8px 12px; cursor: pointer; border-radius: 6px; margin-bottom: 4px; transition: background-color 0.2s ease, color 0.2s ease; font-size: 0.9rem; color: var(--text-secondary); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
44
+ #file-tree div:hover { background-color: var(--background-tertiary); color: var(--text-primary); }
45
+ #file-tree div.active { background-color: var(--primary-color); color: var(--background-primary); font-weight: 500; }
46
+ #file-content-display { background-color: #161625; border-radius: 8px; height: 100%; padding: 15px; font-size: 0.85rem; line-height: 1.6; }
47
+ #file-content-display code { font-family: 'Fira Code', 'Courier New', monospace; }
48
+ .main-content { flex-grow: 1; display: flex; flex-direction: column; padding: 0; height: 100vh; }
49
+ #upload-section { display: flex; flex-direction: column; justify-content: center; align-items: center; height: 100%; text-align: center; padding: 20px; }
50
+ .upload-box { background: var(--background-secondary); padding: 40px; border-radius: 12px; border: 1px dashed var(--border-color); width: 90%; max-width: 500px; }
51
+ .upload-icon { font-size: 3rem; color: var(--primary-color); margin-bottom: 20px; }
52
+ .upload-box h2 { font-size: 1.8rem; margin-bottom: 10px; }
53
+ .upload-box p { color: var(--text-secondary); margin-bottom: 30px; }
54
+ .upload-label { background-color: var(--primary-color); color: var(--background-primary); padding: 12px 20px; border-radius: 8px; cursor: pointer; display: inline-block; font-weight: 600; transition: background-color 0.2s ease; }
55
+ .upload-label:hover { background-color: var(--accent-color); }
56
+ .upload-label i { margin-right: 8px; }
57
+ .file-name { margin-top: 15px; color: var(--success-color); font-weight: 500; }
58
+ #progress-indicator { display: none; }
59
+ #progress-indicator p { margin-bottom: 15px; font-size: 1.1rem; }
60
+ .loader { border: 4px solid var(--background-tertiary); border-radius: 50%; border-top: 4px solid var(--primary-color); width: 50px; height: 50px; animation: spin 1.5s linear infinite; margin: 20px auto; }
61
+ @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
62
+ #chat-section { display: none; flex-direction: column; height: 100%; background-color: var(--background-primary); }
63
+ #chat-window { flex-grow: 1; overflow-y: auto; padding: 20px; display: flex; flex-direction: column; gap: 20px; }
64
+ .message { padding: 12px 18px; border-radius: 18px; max-width: 85%; line-height: 1.6; word-wrap: break-word; }
65
+ .user-message { background-color: var(--primary-color); color: var(--background-primary); border-bottom-right-radius: 4px; align-self: flex-end; }
66
+ .ai-message { background-color: var(--background-secondary); color: var(--text-primary); border-bottom-left-radius: 4px; align-self: flex-start; }
67
+ .ai-message.error-message { background-color: var(--error-color); color: var(--background-primary); }
68
+ .ai-message pre { background-color: var(--background-primary); border: 1px solid var(--border-color); border-radius: 8px; padding: 15px; margin-top: 10px; font-size: 0.85rem; white-space: pre-wrap; }
69
+ .ai-message code { font-family: 'Fira Code', 'Courier New', monospace; }
70
+ .chat-input-area { display: flex; align-items: center; padding: 15px 20px; border-top: 1px solid var(--border-color); flex-shrink: 0; }
71
+ #message-input { flex-grow: 1; background-color: var(--background-secondary); color: var(--text-primary); border: 1px solid var(--border-color); border-radius: 10px; padding: 12px 15px; resize: none; font-family: var(--font-family); font-size: 1rem; line-height: 1.5; max-height: 200px; overflow-y: auto; }
72
+ #message-input:focus { outline: none; border-color: var(--primary-color); }
73
+ #send-btn, #download-btn { background-color: transparent; border: none; color: var(--primary-color); font-size: 1.5rem; cursor: pointer; transition: color 0.2s ease; padding: 10px; margin-left: 10px; }
74
+ #send-btn:hover { color: var(--accent-color); }
75
+ #download-btn { width: 100%; background-color: var(--primary-color); color: var(--background-primary); display: flex; align-items: center; justify-content: center; gap: 10px; font-size: 1rem; font-weight: 600; border-radius: 8px; padding: 12px; }
76
+ #download-btn:hover { background-color: var(--accent-color); color: var(--background-primary); }
77
+ #download-btn i { font-size: 1.1rem; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
78
  </style>
79
  </head>
80
  <body>
81
  <div class="container">
82
  <div class="sidebar left-sidebar">
83
+ <div class="sidebar-header"><i class="fa-solid fa-folder-tree"></i><h2>Project Files</h2></div>
 
 
 
84
  <div id="file-tree" class="sidebar-content"></div>
85
+ <div class="sidebar-footer"><button id="download-btn"><i class="fa-solid fa-download"></i><span>Download Project</span></button></div>
 
 
 
 
 
86
  </div>
 
87
  <div class="main-content">
88
  <div id="upload-section">
89
  <div class="upload-box">
90
  <i class="fa-solid fa-rocket upload-icon"></i>
91
  <h2>AI Code Assistant</h2>
92
+ <p>Upload a .zip project file to begin analyzing and improving your code.</p>
93
  <form id="upload-form" enctype="multipart/form-data">
94
+ <label for="project-zip" class="upload-label"><i class="fa-solid fa-paperclip"></i><span>Choose a .zip file...</span></label>
 
 
 
95
  <input type="file" id="project-zip" name="project_zip" accept=".zip" hidden>
96
+ <button type="submit" id="upload-button" style="display: none;">Upload</button>
97
  </form>
98
  <p id="file-name-display" class="file-name"></p>
99
  </div>
100
+ <div id="progress-indicator"><p>AI is analyzing your project...</p><div class="loader"></div></div>
 
 
 
101
  </div>
102
+ <div id="chat-section">
103
+ <div id="chat-window"></div>
 
 
104
  <div class="chat-input-area">
105
  <textarea id="message-input" placeholder="Ask the AI to refactor, explain, or improve your code..." rows="1"></textarea>
106
  <button id="send-btn"><i class="fa-solid fa-paper-plane"></i></button>
107
  </div>
108
  </div>
109
  </div>
 
110
  <div class="sidebar right-sidebar">
111
+ <div class="sidebar-header"><i class="fa-solid fa-file-lines"></i><h2>File Content</h2></div>
112
+ <div class="sidebar-content"><pre id="file-content-display"><code class="plaintext">Select a file from the left to view its content.</code></pre></div>
 
 
 
 
 
113
  </div>
114
  </div>
 
115
  <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
 
 
116
  <script>
117
  document.addEventListener('DOMContentLoaded', function() {
118
+ const ui = {
119
+ uploadForm: document.getElementById('upload-form'),
120
+ progressIndicator: document.getElementById('progress-indicator'),
121
+ chatSection: document.getElementById('chat-section'),
122
+ uploadSection: document.getElementById('upload-section'),
123
+ uploadBox: document.querySelector('.upload-box'),
124
+ chatWindow: document.getElementById('chat-window'),
125
+ messageInput: document.getElementById('message-input'),
126
+ sendBtn: document.getElementById('send-btn'),
127
+ fileTree: document.getElementById('file-tree'),
128
+ fileContentDisplay: document.querySelector('#file-content-display code'),
129
+ downloadBtn: document.getElementById('download-btn'),
130
+ projectZipInput: document.getElementById('project-zip'),
131
+ fileNameDisplay: document.getElementById('file-name-display'),
132
+ uploadButton: document.getElementById('upload-button'),
133
+ };
134
+
135
+ const handleApiError = (message) => {
136
+ appendMessage('assistant', message, true);
137
+ };
138
+
139
+ const fetchApi = async (url, options) => {
 
 
 
 
 
 
 
 
 
140
  try {
141
+ const response = await fetch(url, options);
 
 
 
 
142
  const data = await response.json();
 
 
143
  if (data.error) {
144
+ handleApiError(data.error);
145
+ return null;
 
146
  }
147
+ return data;
148
+ } catch (error) {
149
+ handleApiError('Failed to connect to the backend. Please ensure it is running.');
150
+ return null;
151
+ }
152
+ };
153
+
154
+ const uploadProject = async (formData) => {
155
+ ui.uploadBox.style.display = 'none';
156
+ ui.progressIndicator.style.display = 'block';
157
+ const data = await fetchApi('/upload', { method: 'POST', body: formData });
158
+ ui.progressIndicator.style.display = 'none';
159
+ if (data) {
160
+ ui.uploadSection.style.display = 'none';
161
+ ui.chatSection.style.display = 'flex';
162
  updateFileTree(data.file_tree);
163
+ ui.chatWindow.innerHTML = '';
164
  data.chat_history.forEach(msg => {
165
+ let content = (msg.role === 'user' && msg.content.startsWith('The user has uploaded')) ? 'Project context sent to AI.' : msg.content;
166
+ appendMessage(msg.role === 'model' ? 'assistant' : msg.role, content);
 
 
 
 
 
167
  });
168
+ } else {
169
+ ui.uploadBox.style.display = 'block';
 
 
 
 
 
 
 
 
 
 
170
  }
171
+ };
 
 
 
 
172
 
173
+ const sendMessage = async () => {
174
+ const message = ui.messageInput.value.trim();
 
175
  if (!message) return;
 
176
  appendMessage('user', message);
177
+ ui.messageInput.value = '';
178
+ ui.messageInput.style.height = 'auto';
179
+ const data = await fetchApi('/chat', {
180
+ method: 'POST',
181
+ headers: { 'Content-Type': 'application/json' },
182
+ body: JSON.stringify({ message })
183
+ });
184
+ if (data && data.reply) {
185
+ appendMessage('assistant', data.reply);
186
+ updateFileTree();
 
 
 
 
 
 
 
 
 
 
 
187
  }
188
+ };
189
 
190
+ const appendMessage = (sender, text, isError = false) => {
191
  const messageElement = document.createElement('div');
192
  messageElement.classList.add('message', sender === 'user' ? 'user-message' : 'ai-message');
193
+ if (isError) messageElement.classList.add('error-message');
194
 
195
+ let html = text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
196
+ html = html.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>');
197
+ html = html.replace(/`([^`]+)`/g, '<code>$1</code>');
 
 
198
  html = html.replace(/```(\w+)?\n([\s\S]*?)```/g, (match, lang, code) => {
199
  const trimmedCode = code.trim();
200
  const highlighted = hljs.highlight(trimmedCode, { language: lang || 'plaintext', ignoreIllegals: true }).value;
201
+ return `<pre><code class="hljs ${lang}">${highlighted}</code></pre>`;
202
  });
 
203
  messageElement.innerHTML = html.replace(/\n/g, '<br>');
204
+ ui.chatWindow.appendChild(messageElement);
205
+ ui.chatWindow.scrollTop = ui.chatWindow.scrollHeight;
206
+ };
207
+
208
+ const updateFileTree = async () => {
209
+ const data = await fetchApi('/file_tree');
210
+ if (data && data.file_tree) {
211
+ const activeFilePath = document.querySelector('#file-tree .active')?.dataset.path;
212
+ ui.fileTree.innerHTML = '';
213
+ data.file_tree.forEach(path => {
214
+ const fileElement = document.createElement('div');
215
+ fileElement.textContent = path;
216
+ fileElement.dataset.path = path;
217
+ fileElement.addEventListener('click', () => {
218
+ document.querySelectorAll('#file-tree div').forEach(el => el.classList.remove('active'));
219
+ fileElement.classList.add('active');
220
+ fetchFileContent(path);
 
 
 
 
 
 
 
 
 
 
 
 
221
  });
222
+ if (path === activeFilePath) fileElement.classList.add('active');
223
+ ui.fileTree.appendChild(fileElement);
224
+ });
225
  }
226
+ };
227
+
228
+ const fetchFileContent = async (path) => {
229
+ const data = await fetchApi(`/file_content?path=${encodeURIComponent(path)}`);
230
+ if (data && data.content) {
231
+ ui.fileContentDisplay.textContent = data.content;
232
+ hljs.highlightElement(ui.fileContentDisplay);
233
+ } else {
234
+ ui.fileContentDisplay.textContent = 'Could not load file content.';
 
 
 
 
 
235
  }
236
+ };
237
 
238
+ ui.projectZipInput.addEventListener('change', () => {
239
+ if (ui.projectZipInput.files.length > 0) {
240
+ ui.fileNameDisplay.textContent = `Selected: ${ui.projectZipInput.files[0].name}`;
241
+ ui.uploadButton.click();
242
+ }
243
  });
244
+ ui.uploadForm.addEventListener('submit', (e) => { e.preventDefault(); uploadProject(new FormData(ui.uploadForm)); });
245
+ ui.sendBtn.addEventListener('click', sendMessage);
246
+ ui.messageInput.addEventListener('keypress', (e) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); sendMessage(); } });
247
+ ui.downloadBtn.addEventListener('click', () => { window.location.href = '/download'; });
248
+ ui.messageInput.addEventListener('input', function () { this.style.height = 'auto'; this.style.height = (this.scrollHeight) + 'px'; });
249
  });
250
  </script>
251
  </body>