MissSqui commited on
Commit
aad01f7
·
verified ·
1 Parent(s): 479e992

Update Bsb

Browse files
Files changed (1) hide show
  1. Bsb +471 -825
Bsb CHANGED
@@ -1,847 +1,493 @@
1
- <!DOCTYPE html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="UTF-8" />
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
6
- <title>Chat Interface</title>
7
- <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
8
- <link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.5/font/bootstrap-icons.css" rel="stylesheet">
9
- <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
10
- <script src="https://cdn.tailwindcss.com"></script>
11
- <style>
12
- /* Unified CSS for Chat, EffortsHomePage, and NotebookPage */
13
-
14
- body {
15
- background-color: #f5f8fc;
16
- color: #1a1a1a;
17
- font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
18
- padding-bottom: 4rem;
19
- }
20
-
21
- .bg-gradient-to-r {
22
- background: linear-gradient(90deg, #4f46e5, #3b82f6) !important;
23
- }
24
-
25
- .text-white {
26
- color: #fff !important;
27
- }
28
-
29
- .card.card-item,
30
- .w-1\/4.bg-white,
31
- .w-3\/4.bg-white {
32
- background-color: #ffffff !important;
33
- border: 1px solid #d0d7e2 !important;
34
- color: #1a1a1a;
35
- border-radius: 10px !important;
36
- box-shadow: 0 1px 4px rgba(0,0,0,0.05) !important;
37
- padding: 1rem 1.5rem !important;
38
- }
39
-
40
- .shadow-md, .shadow {
41
- box-shadow: 0 1px 4px rgba(0,0,0,0.05) !important;
42
- }
43
-
44
- .rounded-2xl, .rounded-lg {
45
- border-radius: 10px !important;
46
- }
47
-
48
- #chat-box {
49
- background: #f8f9fc !important;
50
- border: 1px solid #d0d7e2 !important;
51
- border-radius: 10px !important;
52
- color: #1a1a1a;
53
- height: 26rem;
54
- }
55
-
56
- input.form-control, .form-control, select.form-control,
57
- #persona, #message, #summary-type {
58
- background: #f8f9fc !important;
59
- color: #1a1a1a !important;
60
- border: 1px solid #d0d7e2 !important;
61
- border-radius: 6px !important;
62
- }
63
-
64
- input.form-control::placeholder, .form-control::placeholder,
65
- #persona::placeholder, #message::placeholder {
66
- color: #9aa5bc !important;
67
- }
68
-
69
- .btn-gradient, .upload-button, .submit-btn, .bg-blue-600, .bg-green-600 {
70
- background: linear-gradient(90deg, #4f46e5, #3b82f6) !important;
71
- color: white !important;
72
- border: none !important;
73
- border-radius: 8px !important;
74
- font-weight: 600;
75
- transition: background 0.2s;
76
- }
77
-
78
- .btn-gradient:hover, .upload-button:hover, .submit-btn:hover, .bg-blue-700, .bg-green-700 {
79
- background: linear-gradient(90deg, #4338ca, #2563eb) !important;
80
- }
81
-
82
- .btn-danger, .btn-outline-danger {
83
- background-color: #d9534f !important;
84
- color: white !important;
85
- border: none !important;
86
- border-radius: 8px !important;
87
- font-weight: 600;
88
- }
89
-
90
- .btn-danger:hover, .btn-outline-danger:hover {
91
- background-color: #c9302c !important;
92
- }
93
-
94
- #file-list > li {
95
- border-radius: 10px !important;
96
- border: 1px solid #d0d7e2 !important;
97
- background: #fff !important;
98
- box-shadow: 0 1px 4px rgba(0,0,0,0.05) !important;
99
- padding: 1rem 1.5rem !important;
100
- margin-bottom: 1rem !important;
101
- transition: box-shadow 0.2s, border-color 0.2s;
102
- }
103
-
104
- #file-list > li:hover {
105
- border-color: #a0b4e3 !important;
106
- background-color: #f0f4ff !important;
107
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.06) !important;
108
- }
109
-
110
- .loading-dots {
111
- display: inline-block;
112
- text-align: left;
113
- }
114
- .loading-dots span {
115
- display: inline-block;
116
- width: 8px;
117
- height: 8px;
118
- margin: 0 2px;
119
- background-color: #555;
120
- border-radius: 50%;
121
- animation: blink 1.4s infinite both;
122
- }
123
- .loading-dots span:nth-child(2) { animation-delay: 0.2s; }
124
- .loading-dots span:nth-child(3) { animation-delay: 0.4s; }
125
- @keyframes blink {
126
- 0%, 80%, 100% { opacity: 0; }
127
- 40% { opacity: 1; }
128
- }
129
-
130
- #uploadModal .modal-content,
131
- .modal-content {
132
- background: #ffffff !important;
133
- color: #1a1a1a;
134
- border-radius: 12px !important;
135
- border: 1px solid #d0d7e2 !important;
136
- }
137
-
138
- #uploadModal .drop-zone {
139
- border-radius: 10px !important;
140
- border: 2px dashed #d0d7e2 !important;
141
- background: #f8f9fc !important;
142
- color: #9aa5bc !important;
143
- transition: border-color 0.2s, color 0.2s;
144
- }
145
-
146
- #uploadModal .drop-zone:hover, #uploadModal .drop-zone.border-blue-500 {
147
- border-color: #4f46e5 !important;
148
- color: #4f46e5 !important;
149
- }
150
-
151
- #effort-path, #notebook-path, #file-path {
152
- font-size: 1rem;
153
- font-weight: 600;
154
- }
155
-
156
- @media (max-width: 900px) {
157
- .w-1\/4, .w-3\/4 {
158
- width: 100% !important;
159
- display: block !important;
160
- }
161
- .flex {
162
- flex-direction: column !important;
163
- }
164
- }
165
-
166
- /* Header Styles */
167
- .chat-header {
168
- background: #fff;
169
- border-radius: 12px;
170
- box-shadow: 0 1px 4px rgba(0,0,0,0.05);
171
- padding: 1.25rem 2rem;
172
- display: flex;
173
- justify-content: space-between;
174
- align-items: center;
175
- margin-bottom: 2rem;
176
- border: 1px solid #d0d7e2;
177
- }
178
- .chat-header-title {
179
- font-size: 1.75rem; /* Matches h2 in your other HTML files */
180
- background: linear-gradient(90deg, #4f46e5, #3b82f6);
181
- background-clip: text;
182
- -webkit-background-clip: text;
183
- -webkit-text-fill-color: transparent;
184
- }
185
- .chat-header .no-style {
186
- background: none;
187
- background-clip: initial;
188
- -webkit-background-clip: initial;
189
- -webkit-text-fill-color: initial;
190
- color: inherit;
191
- }
192
- </style>
193
- </head>
194
- <body class="font-sans">
195
-
196
- <!-- Header -->
197
- <div class="chat-header container-title mb-4">
198
- <div class="d-flex align-items-center gap-3">
199
- <button
200
- onclick="window.location.href='/notebook/?effort_id={{ effort_id }}&user_id={{ user_id }}'"
201
- id = "backBtn"
202
- class="btn btn-outline-primary"
203
- aria-label="Go back"
204
- style="padding: 0.375rem 0.75rem;"
205
- >
206
- <i class="bi bi-arrow-left"></i>
207
- </button>
208
- <h2 class="fw-bold mb-0 chat-header-title">
209
- <span class="no-style">📚</span> Document Chat & Summarization
210
- </h2>
211
- </div>
212
- <div class="position-relative" style="min-width: 14rem;">
213
- <select id="zone-dropdown"
214
- class="form-control bg-white text-dark shadow-sm"
215
- style="padding-right: 2.5rem;">
216
- </select>
217
- <span class="position-absolute top-50 end-0 translate-middle-y pe-3">
218
- <svg class="w-4 h-4 text-secondary" fill="none" stroke="currentColor" stroke-width="2"
219
- viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" style="width: 1.25rem; height: 1.25rem;">
220
- <polyline points="6 9 12 15 18 9"></polyline>
221
- </svg>
222
- </span>
223
- </div>
224
- </div>
225
-
226
- <!-- Directory Path Display -->
227
- <div class="max-w-7xl mx-auto mt-6 px-4 text-gray-700 text-sm font-medium select-none">
228
- <span id="effort-path" class="text-gray-800 font-semibold">{{ effort_name }}</span>
229
- <span class="mx-2 text-gray-500">→</span>
230
- <span id="notebook-path" class="text-gray-800 font-semibold">{{ notebook_name }}</span>
231
- <span class="mx-2 text-gray-500">→</span>
232
- <span id="file-path" class="text-blue-700 font-semibold">No File Selected</span>
233
- </div>
234
-
235
- <!-- Main Layout -->
236
- <div class="flex max-w-7xl mx-auto mt-6 gap-6 px-4">
237
- <!-- LEFT: Sidebar -->
238
- <div class="w-1/4 bg-white rounded-2xl shadow-md p-4 h-[34rem] overflow-y-auto">
239
- <button class="upload-button w-full bg-blue-600 hover:bg-blue-700 text-white py-2 px-4 rounded mb-4"
240
- onclick="openModal()">Upload File(s)</button>
241
- <h3 class="text-lg font-semibold mb-2 text-gray-700">📂 Available Files</h3>
242
- <ul id="file-list" class="space-y-3 text-gray-700 text-sm">
243
- <!-- Files injected here -->
244
- </ul>
245
- </div>
246
-
247
- <!-- RIGHT: Chat Panel -->
248
- <div class="w-3/4 bg-white rounded-2xl shadow-md p-6 flex flex-col">
249
- <div id="chat-box" class="overflow-y-scroll border rounded-lg p-4 bg-gray-50 space-y-3 mb-4">
250
- <!-- Chat messages will appear here -->
251
- </div>
252
-
253
- <div class="flex flex-col sm:flex-row items-stretch sm:items-center gap-2 mt-auto">
254
- <input id="persona" type="text" placeholder="Enter persona..." value="You are a helpful assistant"
255
- class="px-4 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-purple-500" />
256
-
257
- <input id="message" type="text" placeholder="Type your message..."
258
- class="flex-grow px-4 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500" />
259
-
260
- <button onclick="sendMessage()"
261
- class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-lg transition">
262
- Send
263
- </button>
264
-
265
- <select id="summary-type"
266
- class="px-3 py-2 border rounded-lg bg-white focus:outline-none focus:ring-2 focus:ring-green-500">
267
- <option value="brief">Brief</option>
268
- <option value="detailed">Detailed</option>
269
- </select>
270
-
271
- <button onclick="summarize()"
272
- class="bg-green-600 hover:bg-green-700 text-white px-4 py-2 rounded-lg transition">
273
- Summarize
274
- </button>
275
- </div>
276
- </div>
277
- </div>
278
-
279
- <!-- Upload Modal -->
280
- <div id="uploadModal" class="modal fixed z-50 inset-0 hidden bg-black bg-opacity-50 flex justify-center items-center">
281
- <div class="modal-content bg-white p-6 rounded-xl w-[90%] max-w-md relative">
282
- <span class="close absolute top-3 right-4 text-xl cursor-pointer" onclick="closeModal()">&times;</span>
283
- <h2 class="text-xl font-bold text-gray-800">NotebookLM</h2>
284
- <p class="text-sm text-gray-600 mt-1">Add sources (PDF only)</p>
285
- <div id="dropZone" class="drop-zone mt-4 border-2 border-dashed border-gray-300 p-6 rounded-lg text-gray-500 text-sm text-center cursor-pointer hover:border-blue-500 hover:text-blue-500"
286
- onclick="triggerFileInput()">Drag & drop or <u>choose files</u><br><small>Supported: PDF</small></div>
287
- <input type="file" id="fileInput" accept=".pdf" multiple class="hidden" />
288
- <div id="selectedFilesList" class="mt-4 text-gray-700 text-sm space-y-1 max-h-40 overflow-y-auto">
289
- <!-- File names shown here -->
290
- </div>
291
- <div class="flex justify-center mt-4">
292
- <button class="submit-btn bg-blue-600 text-white px-4 py-2 rounded" onclick="submitFile()">Upload</button>
293
- </div>
294
- </div>
295
- </div>
296
-
297
- <!-- Delete File Confirmation Modal -->
298
- <div class="modal fade" id="deleteFileModal" tabindex="-1" aria-labelledby="deleteFileModalLabel" aria-hidden="true">
299
- <div class="modal-dialog">
300
- <div class="modal-content">
301
- <div class="modal-header">
302
- <h5 class="modal-title" id="deleteFileModalLabel">Confirm Delete</h5>
303
- <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
304
- </div>
305
- <div class="modal-body">
306
- Are you sure you want to delete <strong id="deleteFileName"></strong>?
307
- </div>
308
- <div class="modal-footer">
309
- <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
310
- <button type="button" class="btn btn-danger" onclick="confirmDeleteFile()">Delete</button>
311
- </div>
312
- </div>
313
- </div>
314
- </div>
315
- <!-- FEEDBACK MODAL (Bootstrap) -->
316
- <div class="modal fade" id="feedbackModal" tabindex="-1" aria-labelledby="feedbackModalLabel" aria-hidden="true">
317
- <div class="modal-dialog" role="document">
318
- <div class="modal-content" style="background-color: white; color: black;">
319
- <div class="modal-header">
320
- <h5 class="modal-title" id="feedbackModalLabel">Rate Your Experience</h5>
321
- <!-- Proper Bootstrap “close” button -->
322
- <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
323
- </div>
324
- <div class="modal-body">
325
- <label for="feedbackLevel">Rating (optional):</label>
326
- <select id="feedbackLevel" class="form-control">
327
- <option value="">-- Select --</option>
328
- <option value="1">Very Bad</option>
329
- <option value="2">Bad</option>
330
- <option value="3">Neutral</option>
331
- <option value="4">Good</option>
332
- <option value="5">Very Good</option>
333
- </select>
334
-
335
- <label for="comment" class="mt-2">Comments (optional):</label>
336
- <textarea id="comment" class="form-control" rows="3" placeholder="Write your feedback here..."></textarea>
337
- <div id ="updateNotice" class = "text-secondary mt-2" style="display:none;font-size:0.9em;">
338
- (Your Previously Submitted Feedback. You can update it if needed.)
339
- </div>
340
- </div>
341
- <div class="modal-footer">
342
- <button id="submitFeedback" class="btn btn-primary">Submit</button>
343
- <button id="skipFeedback" type="button" class="btn btn-secondary">Skip</button>
344
- </div>
345
- </div>
346
- </div>
347
- </div>
348
-
349
- <script>
350
- const userId = "{{ user_id }}";
351
- const effortId = "{{ effort_id }}";
352
- const notebookId = "{{ notebook_id }}"
353
- const effortName = "{{ effort_name }}";
354
- const notebookName = "{{ notebook_name}}"
355
- const preloadedFiles = {{ files | tojson }};
356
- const preloadedZones = {{ zones | tojson }};
357
- const globalZones = {{ global_ezones | tojson }};
358
- const chatHistory = {{ chat_history | tojson }};
359
- const chatBox = document.getElementById("chat-box");
360
- const zoneDropdown = document.getElementById("zone-dropdown");
361
- const fileList = document.getElementById("file-list");
362
- const modal = document.getElementById("uploadModal");
363
- const fileInput = document.getElementById("fileInput");
364
-
365
- //Feedback modal -----
366
- const backButton = document.getElementById("backBtn");
367
- const feedbackModalEl = document.getElementById("feedbackModal");
368
- const bootstrapFeedbackModal = new bootstrap.Modal(feedbackModalEl);
369
-
370
- fileInput.addEventListener("change", () => {
371
- const list = document.getElementById("selectedFilesList");
372
- list.innerHTML = "";
373
-
374
- const files = fileInput.files;
375
- if (!files.length) {
376
- list.textContent = "No files selected.";
377
- return;
378
  }
 
379
 
380
- for (let file of files) {
381
- const item = document.createElement("div");
382
- item.className = "flex items-center gap-2";
383
- item.innerHTML = `📄 <span class="truncate">${file.name}</span>`;
384
- list.appendChild(item);
 
 
385
  }
386
  });
387
 
388
- let currentZoneId = null;
389
- let currentFileUri = "";
390
- let selectedFileElement = null;
391
-
392
- function appendMessage(sender, message, color, alignRight = false) {
393
- const wrapper = document.createElement("div");
394
- wrapper.className = alignRight ? "flex justify-end" : "flex justify-start";
395
-
396
- const bubble = document.createElement("div");
397
- bubble.className = `max-w-xl px-4 py-2 rounded-lg ${color} text-white`;
398
-
399
- // Use <pre> tag to preserve formatting in the message
400
- bubble.innerHTML = `<strong>${sender}:</strong><br><pre class="whitespace-pre-wrap break-words">${message}</pre>`;
401
-
402
- wrapper.appendChild(bubble);
403
- chatBox.appendChild(wrapper);
404
- chatBox.scrollTop = chatBox.scrollHeight;
405
- }
406
-
407
- function loadZones() {
408
- zoneDropdown.innerHTML = "";
409
- const defaultOpt = document.createElement("option");
410
- defaultOpt.disabled = true;
411
- defaultOpt.selected = true;
412
- defaultOpt.textContent = "Select a Configuration";
413
- zoneDropdown.appendChild(defaultOpt);
414
-
415
- try {
416
- const addedZoneIds = new Set();
417
-
418
- preloadedZones.forEach(zone => {
419
- if (!addedZoneIds.has(zone.id)) {
420
- const opt = document.createElement("option");
421
- opt.value = zone.id;
422
- opt.textContent = zone.name;
423
- zoneDropdown.appendChild(opt);
424
- addedZoneIds.add(zone.id);
425
- }
426
- });
427
-
428
- globalZones.forEach(zone => {
429
- if (!addedZoneIds.has(zone.id)) {
430
- const opt = document.createElement("option");
431
- opt.value = zone.id;
432
- opt.textContent = zone.name + " (Global)";
433
- zoneDropdown.appendChild(opt);
434
- addedZoneIds.add(zone.id);
435
- }
436
- });
437
-
438
- const divider = document.createElement("option");
439
- divider.disabled = true;
440
- divider.textContent = "──────────";
441
- zoneDropdown.appendChild(divider);
442
- } catch (err) {
443
- console.error("Error loading zones:", err);
 
 
 
 
 
 
 
444
  }
445
 
446
- const manage = document.createElement("option");
447
- manage.value = "manage";
448
- manage.textContent = "➕ Manage Configurations";
449
- zoneDropdown.appendChild(manage);
450
-
451
- zoneDropdown.addEventListener("change", (e) => {
452
- if (e.target.value === "manage") {
453
- const form = document.createElement("form");
454
- form.method = "POST";
455
- form.action = "/chat_feature/manage_zones";
456
-
457
- const userInput = document.createElement("input");
458
- userInput.type = "hidden";
459
- userInput.name = "user_id";
460
- userInput.value = userId;
461
- form.appendChild(userInput);
462
-
463
- const effortInput = document.createElement("input");
464
- effortInput.type = "hidden";
465
- effortInput.name = "effort_id";
466
- effortInput.value = effortId;
467
- form.appendChild(effortInput);
468
-
469
- const notebookInput = document.createElement("input");
470
- notebookInput.type = "hidden";
471
- notebookInput.name = "notebook_id";
472
- notebookInput.value = notebookId;
473
- form.appendChild(notebookInput);
474
-
475
- document.body.appendChild(form);
476
- form.submit();
477
- } else {
478
- currentZoneId = parseInt(e.target.value);
479
- console.log("Zone selected, currentZoneID:", currentZoneId);
480
- }
481
- });
482
- }
483
-
484
- //feedback Modal POP-UP on back Button
485
- backButton.addEventListener("click", async function(event){
486
- console.log("Back button clicked"); // Debugging line
487
- event.preventDefault();
488
-
489
- if(!currentZoneId){
490
- console.log("no configuration selected, going back directly")
491
- window.location.href = `/notebook/?effort_id=${effortId}&user_id=${userId}`;
492
- return;
493
  }
494
-
495
- console.log("Debug BackBtn:", {userId, notebookId, currentZoneId, effortId});
496
- try {
497
- const response = await fetch(`/chat_feature/user_feedback_eligibility?user_id=${userId}&notebook_id=${notebookId}&zone_id=${currentZoneId}`);
498
- const data = await response.json();
499
-
500
- if (data.eligible_for_feedback) {
501
- console.log("User is eligible for feedback:", data);// Debugging line
502
- if(data.has_feedback){
503
- document.getElementById("feedbackLevel").value = data.rating ||"";
504
- document.getElementById("comment").value = data.comment ||"";
505
- document.getElementById("updateNotice").style.display = "block";
506
-
507
- }
508
- else{
509
- document.getElementById("feedbackLevel").value = data.rating ||"";
510
- document.getElementById("comment").value = data.comment ||"";
511
- document.getElementById("updateNotice").style.display = "none";
512
-
513
- }
514
- bootstrapFeedbackModal.show();
515
- } else {
516
- window.location.href = `/notebook/?effort_id=${effortId}&user_id=${userId}`;
517
- }
518
- } catch (error) {
519
- console.error('Error checking eligibility:', error);
520
- window.location.href = `/notebook/?effort_id=${effortId}&user_id=${userId}`;
521
  }
522
- });
523
- // Feedback Submit
524
- document.getElementById("submitFeedback").addEventListener("click", async function () {
525
- const rating = document.getElementById("feedbackLevel").value;
526
- const comment = document.getElementById("comment").value;
527
-
528
- const payload = {
529
- user_id: userId,
530
- notebook_id: parseInt(notebookId),
531
- effort_id: parseInt(effortId),
532
- zone_id: parseInt(currentZoneId),
533
- rating: rating ? parseInt(rating) : null,
534
- comment: comment
535
- };
536
-
537
- try {
538
- const response = await fetch("/chat_feature/submit_feedback", {
539
- method: "POST",
540
- headers: { "Content-Type": "application/json" },
541
- body: JSON.stringify(payload)
542
- });
543
-
544
- if (response.ok) {
545
- bootstrapFeedbackModal.hide();
546
- window.location.href = `/notebook/?effort_id=${effortId}&user_id=${userId}`;
547
- } else {
548
- alert("Error submitting feedback");
549
- }
550
- } catch (error) {
551
- console.error("Error submitting feedback:", error);
552
- alert("Error submitting feedback");
553
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
554
  });
555
 
556
- // Skip feedback
557
- document.getElementById("skipFeedback").addEventListener("click", function () {
558
  bootstrapFeedbackModal.hide();
559
  window.location.href = `/notebook/?effort_id=${effortId}&user_id=${userId}`;
560
- });
561
-
562
- function loadFiles() {
563
- const files = preloadedFiles || [];
564
- fileList.innerHTML = "";
565
-
566
- files.forEach(file => {
567
- const li = document.createElement("li");
568
- li.className =
569
- "flex items-center gap-3 p-4 rounded-2xl border border-gray-200 bg-white hover:bg-blue-50 hover:shadow-lg transition shadow group";
570
-
571
- const fileIcon = document.createElement("div");
572
- fileIcon.className = "flex-shrink-0 text-blue-600 text-lg";
573
- fileIcon.textContent = "📄";
574
-
575
- const fileName = document.createElement("div");
576
- fileName.className = "flex-1 min-w-0 cursor-pointer";
577
- fileName.innerHTML = `
578
- <p class="font-medium truncate group-hover:whitespace-normal group-hover:break-all" title="${file.name}">
579
- ${file.name}
580
- </p>
581
- `;
582
- fileName.onclick = () => {
583
- currentFileUri = file.uri;
584
- appendMessage("System", `Selected file: ${file.name}`, "bg-purple-600");
585
-
586
- document.getElementById("notebook-path").textContent = notebookName;
587
- document.getElementById("effort-path").textContent = effortName;
588
- document.getElementById("file-path").textContent = file.name;
589
-
590
- if (selectedFileElement) {
591
- selectedFileElement.classList.remove("border-blue-500", "bg-blue-100", "ring", "ring-blue-300", "shadow-lg", "transform", "scale-105");
592
- }
593
-
594
- li.classList.add("border-blue-500", "bg-blue-100", "ring", "ring-blue-300", "shadow-lg", "transform", "scale-105");
595
- selectedFileElement = li;
596
- };
597
-
598
- const deleteButton = document.createElement("button");
599
- deleteButton.className = "btn btn-sm btn-outline-danger";
600
- deleteButton.setAttribute("data-bs-toggle", "modal");
601
- deleteButton.setAttribute("data-bs-target", "#deleteFileModal");
602
- deleteButton.innerHTML = `<i class="bi bi-trash"></i>`;
603
- deleteButton.onclick = (e) => {
604
- e.stopPropagation();
605
- setDeleteFile(file.uri, file.name);
606
- };
607
-
608
- li.appendChild(fileIcon);
609
- li.appendChild(fileName);
610
- li.appendChild(deleteButton);
611
-
612
- fileList.appendChild(li);
613
- });
614
- }
615
-
616
-
617
- async function sendMessage() {
618
- const message = document.getElementById("message").value;
619
- const persona = document.getElementById("persona").value;
620
- if (!message.trim() || !currentFileUri) {
621
- alert("Please select a file and type a message.");
622
- return;
623
- }
624
- if (!currentZoneId) {
625
- alert("Please select a zone before sending a message.");
626
- return;
627
- }
628
-
629
- appendMessage("You", message, "bg-blue-500", true); // Right aligned
630
-
631
- const loadingId = "loading-msg";
632
- const loadingWrapper = document.createElement("div");
633
- loadingWrapper.id = loadingId;
634
- loadingWrapper.className = "flex justify-start";
635
-
636
- const loadingBubble = document.createElement("div");
637
- loadingBubble.className = "max-w-xl px-4 py-2 rounded-lg bg-gray-300 text-gray-800";
638
- loadingBubble.innerHTML = `<strong>Assistant:</strong> <span class="loading-dots"><span></span><span></span><span></span></span>`;
639
- loadingWrapper.appendChild(loadingBubble);
640
- chatBox.appendChild(loadingWrapper);
641
- chatBox.scrollTop = chatBox.scrollHeight;
642
-
643
-
644
- const res = await fetch("/chat_feature/send_message", {
645
- method: "POST",
646
- headers: { "Content-Type": "application/json" },
647
- body: JSON.stringify({
648
- message,
649
- persona,
650
- file_uri: currentFileUri,
651
- experiment_zone_id: currentZoneId,
652
- effortId: effortId,
653
- notebookId: notebookId
654
- })
655
- });
656
-
657
- const data = await res.json();
658
- document.getElementById(loadingId)?.remove();
659
- appendMessage("Assistant", data.message || "No response", "bg-gray-700");
660
- document.getElementById("message").value = "";
661
- }
662
-
663
- async function summarize() {
664
- const summaryType = document.getElementById("summary-type").value;
665
- if (!currentFileUri) {
666
- alert("Please select a file and summarize.");
667
- return;
668
- }
669
-
670
- if (!currentZoneId) {
671
- alert("Please select a zone before summarizing.");
672
- return;
673
- }
674
-
675
- appendMessage("You", `Summarize (${summaryType})`, "bg-green-500", true); // Right aligned
676
-
677
- const loadingId = "loading-msg";
678
- const loadingWrapper = document.createElement("div");
679
- loadingWrapper.id = loadingId;
680
- loadingWrapper.className = "flex justify-start";
681
-
682
- const loadingBubble = document.createElement("div");
683
- loadingBubble.className = "max-w-xl px-4 py-2 rounded-lg bg-gray-300 text-gray-800";
684
- loadingBubble.innerHTML = `<strong>Assistant:</strong> <span class="loading-dots"><span></span><span></span><span></span></span>`;
685
- loadingWrapper.appendChild(loadingBubble);
686
- chatBox.appendChild(loadingWrapper);
687
- chatBox.scrollTop = chatBox.scrollHeight;
688
-
689
-
690
- const res = await fetch("/chat_feature/summarize", {
691
- method: "POST",
692
- headers: { "Content-Type": "application/json" },
693
- body: JSON.stringify({
694
- summary_type: summaryType,
695
- file_uri: currentFileUri,
696
- experiment_zone_id: currentZoneId,
697
- effortId: effortId,
698
- notebookId: notebookId
699
- })
700
- });
701
-
702
- const data = await res.json();
703
- document.getElementById(loadingId)?.remove();
704
- appendMessage("Summary", data.message || "No summary", "bg-gray-700");
705
- }
706
-
707
- function openModal() { modal.classList.remove("hidden"); }
708
- function closeModal() { modal.classList.add("hidden"); fileInput.value = ""; }
709
- function triggerFileInput() { fileInput.click(); }
710
-
711
- async function submitFile() {
712
- const files = fileInput.files;
713
- if (!files.length) return alert("Choose at least one file.");
714
-
715
- const formData = new FormData();
716
- for (let file of files) {
717
- if (file.type !== "application/pdf") {
718
- alert(`Invalid file type: ${file.name}`);
719
- return;
720
- }
721
- formData.append("files", file);
722
  }
723
 
724
- formData.append("effort_id", effortId);
725
-
726
- try {
727
- const res = await fetch("/chat_feature/upload_file", {
728
- method: "POST",
729
- body: formData
730
- });
731
-
732
- const contentType = res.headers.get("content-type") || "";
733
- let responseData = contentType.includes("application/json") ? await res.json() : await res.text();
734
-
735
- if (!res.ok) {
736
- const errorMessage = typeof responseData === "string"
737
- ? responseData
738
- : (responseData.error || JSON.stringify(responseData));
739
- alert(`Upload failed: ${errorMessage}`);
740
- return;
741
- }
742
-
743
- alert("Upload successful!");
744
- location.reload(); // Refresh to get updated file list from server
745
- } catch (err) {
746
- alert("Error: " + err.message);
747
- }
748
- }
749
-
750
- window.onclick = function (event) {
751
- if (event.target == modal) closeModal();
752
- }
753
-
754
- let deleteFileUriToConfirm = "";
755
- let deleteFileName = ""
756
-
757
- function setDeleteFile(fileUri, fileName) {
758
- deleteFileUriToConfirm = fileUri;
759
- deleteFileName = fileName
760
- document.getElementById("deleteFileName").textContent = fileName;
761
- }
762
-
763
- async function confirmDeleteFile() {
764
- try {
765
- const res = await fetch("/chat_feature/delete_file", {
766
- method: "POST",
767
- headers: { "Content-Type": "application/json" },
768
- body: JSON.stringify({
769
- file_uri: deleteFileUriToConfirm,
770
- effort_id: effortId,
771
- filename: deleteFileName
772
- })
773
- });
774
-
775
- if (!res.ok) {
776
- const text = await res.text();
777
- alert("Failed to delete: " + text);
778
- } else {
779
- const modal = bootstrap.Modal.getInstance(document.getElementById("deleteFileModal"));
780
- modal.hide();
781
- alert("File deleted.");
782
- location.reload();
783
- }
784
- } catch (err) {
785
- alert("Error deleting file: " + err.message);
786
- }
787
- }
788
-
789
- const dropZone = document.querySelector(".drop-zone");
790
 
791
- dropZone.addEventListener("dragover", (e) => {
792
- e.preventDefault();
793
- dropZone.classList.add("border-blue-500", "text-blue-500");
794
- });
 
 
 
 
 
795
 
796
- dropZone.addEventListener("dragleave", () => {
797
- dropZone.classList.remove("border-blue-500", "text-blue-500");
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
798
  });
799
 
800
- dropZone.addEventListener("drop", (e) => {
801
- e.preventDefault();
802
- dropZone.classList.remove("border-blue-500", "text-blue-500");
803
-
804
- const files = e.dataTransfer.files;
805
- fileInput.files = files;
806
-
807
- // Optional: Display file names immediately
808
- const list = document.getElementById("selectedFilesList");
809
- list.innerHTML = "";
810
- for (const file of files) {
811
- const p = document.createElement("p");
812
- p.textContent = file.name;
813
- list.appendChild(p);
814
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
815
  });
816
 
817
- window.onload = function () {
818
-
819
- loadFiles();
820
- loadZones();
821
-
822
- let historyFilename = ""
823
-
824
- if (chatHistory && chatHistory.length > 0) {
825
- chatHistory.forEach(entry => {
826
-
827
- if (entry.filename && (entry.filename !== historyFilename || historyFilename === "")) {
828
- historyFilename = entry.filename;
829
- appendMessage("System", `Selected file: ${historyFilename}`, "bg-purple-600");
830
- }
831
-
832
- // User message
833
- const userMessage = entry.question;
834
- appendMessage("You", userMessage, "bg-blue-500", true);
835
- const personaMessage = entry.persona;
836
- appendMessage("Persona", personaMessage, "bg-blue-500", true);
837
- // Assistant response
838
- const botMessage = entry.response;
839
- appendMessage("Assistant", botMessage, "bg-gray-700", false);
840
- });
841
- }
842
- };
843
-
844
- </script>
845
-
846
- </body>
847
- </html>
 
 
 
 
 
 
 
 
 
 
1
+ // CHANGED/ADDED: Declare global variable for modal
2
+ let bootstrapFeedbackModal = null;
3
+
4
+ const userId = "{{ user_id }}";
5
+ const effortId = "{{ effort_id }}";
6
+ const notebookId = "{{ notebook_id }}"
7
+ const effortName = "{{ effort_name }}";
8
+ const notebookName = "{{ notebook_name}}"
9
+ const preloadedFiles = {{ files | tojson }};
10
+ const preloadedZones = {{ zones | tojson }};
11
+ const globalZones = {{ global_ezones | tojson }};
12
+ const chatHistory = {{ chat_history | tojson }};
13
+ const chatBox = document.getElementById("chat-box");
14
+ const zoneDropdown = document.getElementById("zone-dropdown");
15
+ const fileList = document.getElementById("file-list");
16
+ const modal = document.getElementById("uploadModal");
17
+ const fileInput = document.getElementById("fileInput");
18
+ const backButton = document.getElementById("backBtn"); // Moved up for clarity
19
+
20
+ fileInput.addEventListener("change", () => {
21
+ const list = document.getElementById("selectedFilesList");
22
+ list.innerHTML = "";
23
+
24
+ const files = fileInput.files;
25
+ if (!files.length) {
26
+ list.textContent = "No files selected.";
27
+ return;
28
+ }
29
+
30
+ for (let file of files) {
31
+ const item = document.createElement("div");
32
+ item.className = "flex items-center gap-2";
33
+ item.innerHTML = `📄 <span class="truncate">${file.name}</span>`;
34
+ list.appendChild(item);
35
+ }
36
+ });
37
+
38
+ let currentZoneId = null;
39
+ let currentFileUri = "";
40
+ let selectedFileElement = null;
41
+
42
+ function appendMessage(sender, message, color, alignRight = false) {
43
+ const wrapper = document.createElement("div");
44
+ wrapper.className = alignRight ? "flex justify-end" : "flex justify-start";
45
+
46
+ const bubble = document.createElement("div");
47
+ bubble.className = `max-w-xl px-4 py-2 rounded-lg ${color} text-white`;
48
+
49
+ bubble.innerHTML = `<strong>${sender}:</strong><br><pre class="whitespace-pre-wrap break-words">${message}</pre>`;
50
+
51
+ wrapper.appendChild(bubble);
52
+ chatBox.appendChild(wrapper);
53
+ chatBox.scrollTop = chatBox.scrollHeight;
54
+ }
55
+
56
+ function loadZones() {
57
+ zoneDropdown.innerHTML = "";
58
+ const defaultOpt = document.createElement("option");
59
+ defaultOpt.disabled = true;
60
+ defaultOpt.selected = true;
61
+ defaultOpt.textContent = "Select a Configuration";
62
+ zoneDropdown.appendChild(defaultOpt);
63
+
64
+ try {
65
+ const addedZoneIds = new Set();
66
+
67
+ preloadedZones.forEach(zone => {
68
+ if (!addedZoneIds.has(zone.id)) {
69
+ const opt = document.createElement("option");
70
+ opt.value = zone.id;
71
+ opt.textContent = zone.name;
72
+ zoneDropdown.appendChild(opt);
73
+ addedZoneIds.add(zone.id);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
74
  }
75
+ });
76
 
77
+ globalZones.forEach(zone => {
78
+ if (!addedZoneIds.has(zone.id)) {
79
+ const opt = document.createElement("option");
80
+ opt.value = zone.id;
81
+ opt.textContent = zone.name + " (Global)";
82
+ zoneDropdown.appendChild(opt);
83
+ addedZoneIds.add(zone.id);
84
  }
85
  });
86
 
87
+ const divider = document.createElement("option");
88
+ divider.disabled = true;
89
+ divider.textContent = "──────────";
90
+ zoneDropdown.appendChild(divider);
91
+ } catch (err) {
92
+ console.error("Error loading zones:", err);
93
+ }
94
+
95
+ const manage = document.createElement("option");
96
+ manage.value = "manage";
97
+ manage.textContent = "➕ Manage Configurations";
98
+ zoneDropdown.appendChild(manage);
99
+
100
+ zoneDropdown.addEventListener("change", (e) => {
101
+ if (e.target.value === "manage") {
102
+ const form = document.createElement("form");
103
+ form.method = "POST";
104
+ form.action = "/chat_feature/manage_zones";
105
+
106
+ const userInput = document.createElement("input");
107
+ userInput.type = "hidden";
108
+ userInput.name = "user_id";
109
+ userInput.value = userId;
110
+ form.appendChild(userInput);
111
+
112
+ const effortInput = document.createElement("input");
113
+ effortInput.type = "hidden";
114
+ effortInput.name = "effort_id";
115
+ effortInput.value = effortId;
116
+ form.appendChild(effortInput);
117
+
118
+ const notebookInput = document.createElement("input");
119
+ notebookInput.type = "hidden";
120
+ notebookInput.name = "notebook_id";
121
+ notebookInput.value = notebookId;
122
+ form.appendChild(notebookInput);
123
+
124
+ document.body.appendChild(form);
125
+ form.submit();
126
+ } else {
127
+ currentZoneId = parseInt(e.target.value);
128
+ console.log("Zone selected, currentZoneID:", currentZoneId);
129
+ }
130
+ });
131
+ }
132
+
133
+ // CHANGED/ADDED: Modal initialization moved inside window.onload
134
+ window.onload = function () {
135
+ loadFiles();
136
+ loadZones();
137
+
138
+ // CHANGED/ADDED: Modal is now initialized after DOM is loaded
139
+ const feedbackModalEl = document.getElementById("feedbackModal");
140
+ bootstrapFeedbackModal = new bootstrap.Modal(feedbackModalEl);
141
+
142
+ let historyFilename = ""
143
+
144
+ if (chatHistory && chatHistory.length > 0) {
145
+ chatHistory.forEach(entry => {
146
+
147
+ if (entry.filename && (entry.filename !== historyFilename || historyFilename === "")) {
148
+ historyFilename = entry.filename;
149
+ appendMessage("System", `Selected file: ${historyFilename}`, "bg-purple-600");
150
  }
151
 
152
+ const userMessage = entry.question;
153
+ appendMessage("You", userMessage, "bg-blue-500", true);
154
+ const personaMessage = entry.persona;
155
+ appendMessage("Persona", personaMessage, "bg-blue-500", true);
156
+ const botMessage = entry.response;
157
+ appendMessage("Assistant", botMessage, "bg-gray-700", false);
158
+ });
159
+ }
160
+ };
161
+
162
+ // Feedback Modal POP-UP on Back Button
163
+ backButton.addEventListener("click", async function(event){
164
+ console.log("Back button clicked");
165
+ event.preventDefault();
166
+
167
+ if(!currentZoneId){
168
+ console.log("no configuration selected, going back directly")
169
+ window.location.href = `/notebook/?effort_id=${effortId}&user_id=${userId}`;
170
+ return;
171
+ }
172
+
173
+ console.log("Debug BackBtn:", {userId, notebookId, currentZoneId, effortId});
174
+ try {
175
+ const response = await fetch(`/chat_feature/user_feedback_eligibility?user_id=${userId}&notebook_id=${notebookId}&zone_id=${currentZoneId}`);
176
+ const data = await response.json();
177
+
178
+ if (data.eligible_for_feedback) {
179
+ console.log("User is eligible for feedback:", data);
180
+ if(data.has_feedback){
181
+ document.getElementById("feedbackLevel").value = data.rating ||"";
182
+ document.getElementById("comment").value = data.comment ||"";
183
+ document.getElementById("updateNotice").style.display = "block";
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
184
  }
185
+ else{
186
+ document.getElementById("feedbackLevel").value = data.rating ||"";
187
+ document.getElementById("comment").value = data.comment ||"";
188
+ document.getElementById("updateNotice").style.display = "none";
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
189
  }
190
+ // CHANGED/ADDED: Now modal will always be initialized and ready here
191
+ if (bootstrapFeedbackModal) {
192
+ bootstrapFeedbackModal.show();
193
+ } else {
194
+ alert("Feedback modal not initialized!");
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
195
  }
196
+ } else {
197
+ window.location.href = `/notebook/?effort_id=${effortId}&user_id=${userId}`;
198
+ }
199
+ } catch (error) {
200
+ console.error('Error checking eligibility:', error);
201
+ window.location.href = `/notebook/?effort_id=${effortId}&user_id=${userId}`;
202
+ }
203
+ });
204
+
205
+ // Feedback Submit
206
+ document.getElementById("submitFeedback").addEventListener("click", async function () {
207
+ const rating = document.getElementById("feedbackLevel").value;
208
+ const comment = document.getElementById("comment").value;
209
+
210
+ const payload = {
211
+ user_id: userId,
212
+ notebook_id: parseInt(notebookId),
213
+ effort_id: parseInt(effortId),
214
+ zone_id: parseInt(currentZoneId),
215
+ rating: rating ? parseInt(rating) : null,
216
+ comment: comment
217
+ };
218
+
219
+ try {
220
+ const response = await fetch("/chat_feature/submit_feedback", {
221
+ method: "POST",
222
+ headers: { "Content-Type": "application/json" },
223
+ body: JSON.stringify(payload)
224
  });
225
 
226
+ if (response.ok) {
 
227
  bootstrapFeedbackModal.hide();
228
  window.location.href = `/notebook/?effort_id=${effortId}&user_id=${userId}`;
229
+ } else {
230
+ alert("Error submitting feedback");
231
+ }
232
+ } catch (error) {
233
+ console.error("Error submitting feedback:", error);
234
+ alert("Error submitting feedback");
235
+ }
236
+ });
237
+
238
+ // Skip feedback
239
+ document.getElementById("skipFeedback").addEventListener("click", function () {
240
+ bootstrapFeedbackModal.hide();
241
+ window.location.href = `/notebook/?effort_id=${effortId}&user_id=${userId}`;
242
+ });
243
+
244
+ function loadFiles() {
245
+ const files = preloadedFiles || [];
246
+ fileList.innerHTML = "";
247
+
248
+ files.forEach(file => {
249
+ const li = document.createElement("li");
250
+ li.className =
251
+ "flex items-center gap-3 p-4 rounded-2xl border border-gray-200 bg-white hover:bg-blue-50 hover:shadow-lg transition shadow group";
252
+
253
+ const fileIcon = document.createElement("div");
254
+ fileIcon.className = "flex-shrink-0 text-blue-600 text-lg";
255
+ fileIcon.textContent = "📄";
256
+
257
+ const fileName = document.createElement("div");
258
+ fileName.className = "flex-1 min-w-0 cursor-pointer";
259
+ fileName.innerHTML = `
260
+ <p class="font-medium truncate group-hover:whitespace-normal group-hover:break-all" title="${file.name}">
261
+ ${file.name}
262
+ </p>
263
+ `;
264
+ fileName.onclick = () => {
265
+ currentFileUri = file.uri;
266
+ appendMessage("System", `Selected file: ${file.name}`, "bg-purple-600");
267
+
268
+ document.getElementById("notebook-path").textContent = notebookName;
269
+ document.getElementById("effort-path").textContent = effortName;
270
+ document.getElementById("file-path").textContent = file.name;
271
+
272
+ if (selectedFileElement) {
273
+ selectedFileElement.classList.remove("border-blue-500", "bg-blue-100", "ring", "ring-blue-300", "shadow-lg", "transform", "scale-105");
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
274
  }
275
 
276
+ li.classList.add("border-blue-500", "bg-blue-100", "ring", "ring-blue-300", "shadow-lg", "transform", "scale-105");
277
+ selectedFileElement = li;
278
+ };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
279
 
280
+ const deleteButton = document.createElement("button");
281
+ deleteButton.className = "btn btn-sm btn-outline-danger";
282
+ deleteButton.setAttribute("data-bs-toggle", "modal");
283
+ deleteButton.setAttribute("data-bs-target", "#deleteFileModal");
284
+ deleteButton.innerHTML = `<i class="bi bi-trash"></i>`;
285
+ deleteButton.onclick = (e) => {
286
+ e.stopPropagation();
287
+ setDeleteFile(file.uri, file.name);
288
+ };
289
 
290
+ li.appendChild(fileIcon);
291
+ li.appendChild(fileName);
292
+ li.appendChild(deleteButton);
293
+
294
+ fileList.appendChild(li);
295
+ });
296
+ }
297
+
298
+ async function sendMessage() {
299
+ const message = document.getElementById("message").value;
300
+ const persona = document.getElementById("persona").value;
301
+ if (!message.trim() || !currentFileUri) {
302
+ alert("Please select a file and type a message.");
303
+ return;
304
+ }
305
+ if (!currentZoneId) {
306
+ alert("Please select a zone before sending a message.");
307
+ return;
308
+ }
309
+
310
+ appendMessage("You", message, "bg-blue-500", true);
311
+
312
+ const loadingId = "loading-msg";
313
+ const loadingWrapper = document.createElement("div");
314
+ loadingWrapper.id = loadingId;
315
+ loadingWrapper.className = "flex justify-start";
316
+
317
+ const loadingBubble = document.createElement("div");
318
+ loadingBubble.className = "max-w-xl px-4 py-2 rounded-lg bg-gray-300 text-gray-800";
319
+ loadingBubble.innerHTML = `<strong>Assistant:</strong> <span class="loading-dots"><span></span><span></span><span></span></span>`;
320
+ loadingWrapper.appendChild(loadingBubble);
321
+ chatBox.appendChild(loadingWrapper);
322
+ chatBox.scrollTop = chatBox.scrollHeight;
323
+
324
+ const res = await fetch("/chat_feature/send_message", {
325
+ method: "POST",
326
+ headers: { "Content-Type": "application/json" },
327
+ body: JSON.stringify({
328
+ message,
329
+ persona,
330
+ file_uri: currentFileUri,
331
+ experiment_zone_id: currentZoneId,
332
+ effortId: effortId,
333
+ notebookId: notebookId
334
+ })
335
+ });
336
+
337
+ const data = await res.json();
338
+ document.getElementById(loadingId)?.remove();
339
+ appendMessage("Assistant", data.message || "No response", "bg-gray-700");
340
+ document.getElementById("message").value = "";
341
+ }
342
+
343
+ async function summarize() {
344
+ const summaryType = document.getElementById("summary-type").value;
345
+ if (!currentFileUri) {
346
+ alert("Please select a file and summarize.");
347
+ return;
348
+ }
349
+
350
+ if (!currentZoneId) {
351
+ alert("Please select a zone before summarizing.");
352
+ return;
353
+ }
354
+
355
+ appendMessage("You", `Summarize (${summaryType})`, "bg-green-500", true);
356
+
357
+ const loadingId = "loading-msg";
358
+ const loadingWrapper = document.createElement("div");
359
+ loadingWrapper.id = loadingId;
360
+ loadingWrapper.className = "flex justify-start";
361
+
362
+ const loadingBubble = document.createElement("div");
363
+ loadingBubble.className = "max-w-xl px-4 py-2 rounded-lg bg-gray-300 text-gray-800";
364
+ loadingBubble.innerHTML = `<strong>Assistant:</strong> <span class="loading-dots"><span></span><span></span><span></span></span>`;
365
+ loadingWrapper.appendChild(loadingBubble);
366
+ chatBox.appendChild(loadingWrapper);
367
+ chatBox.scrollTop = chatBox.scrollHeight;
368
+
369
+ const res = await fetch("/chat_feature/summarize", {
370
+ method: "POST",
371
+ headers: { "Content-Type": "application/json" },
372
+ body: JSON.stringify({
373
+ summary_type: summaryType,
374
+ file_uri: currentFileUri,
375
+ experiment_zone_id: currentZoneId,
376
+ effortId: effortId,
377
+ notebookId: notebookId
378
+ })
379
+ });
380
+
381
+ const data = await res.json();
382
+ document.getElementById(loadingId)?.remove();
383
+ appendMessage("Summary", data.message || "No summary", "bg-gray-700");
384
+ }
385
+
386
+ function openModal() { modal.classList.remove("hidden"); }
387
+ function closeModal() { modal.classList.add("hidden"); fileInput.value = ""; }
388
+ function triggerFileInput() { fileInput.click(); }
389
+
390
+ async function submitFile() {
391
+ const files = fileInput.files;
392
+ if (!files.length) return alert("Choose at least one file.");
393
+
394
+ const formData = new FormData();
395
+ for (let file of files) {
396
+ if (file.type !== "application/pdf") {
397
+ alert(`Invalid file type: ${file.name}`);
398
+ return;
399
+ }
400
+ formData.append("files", file);
401
+ }
402
+
403
+ formData.append("effort_id", effortId);
404
+
405
+ try {
406
+ const res = await fetch("/chat_feature/upload_file", {
407
+ method: "POST",
408
+ body: formData
409
  });
410
 
411
+ const contentType = res.headers.get("content-type") || "";
412
+ let responseData = contentType.includes("application/json") ? await res.json() : await res.text();
413
+
414
+ if (!res.ok) {
415
+ const errorMessage = typeof responseData === "string"
416
+ ? responseData
417
+ : (responseData.error || JSON.stringify(responseData));
418
+ alert(`Upload failed: ${errorMessage}`);
419
+ return;
420
+ }
421
+
422
+ alert("Upload successful!");
423
+ location.reload();
424
+ } catch (err) {
425
+ alert("Error: " + err.message);
426
+ }
427
+ }
428
+
429
+ window.onclick = function (event) {
430
+ if (event.target == modal) closeModal();
431
+ }
432
+
433
+ let deleteFileUriToConfirm = "";
434
+ let deleteFileName = ""
435
+
436
+ function setDeleteFile(fileUri, fileName) {
437
+ deleteFileUriToConfirm = fileUri;
438
+ deleteFileName = fileName
439
+ document.getElementById("deleteFileName").textContent = fileName;
440
+ }
441
+
442
+ async function confirmDeleteFile() {
443
+ try {
444
+ const res = await fetch("/chat_feature/delete_file", {
445
+ method: "POST",
446
+ headers: { "Content-Type": "application/json" },
447
+ body: JSON.stringify({
448
+ file_uri: deleteFileUriToConfirm,
449
+ effort_id: effortId,
450
+ filename: deleteFileName
451
+ })
452
  });
453
 
454
+ if (!res.ok) {
455
+ const text = await res.text();
456
+ alert("Failed to delete: " + text);
457
+ } else {
458
+ const modal = bootstrap.Modal.getInstance(document.getElementById("deleteFileModal"));
459
+ modal.hide();
460
+ alert("File deleted.");
461
+ location.reload();
462
+ }
463
+ } catch (err) {
464
+ alert("Error deleting file: " + err.message);
465
+ }
466
+ }
467
+
468
+ const dropZone = document.querySelector(".drop-zone");
469
+
470
+ dropZone.addEventListener("dragover", (e) => {
471
+ e.preventDefault();
472
+ dropZone.classList.add("border-blue-500", "text-blue-500");
473
+ });
474
+
475
+ dropZone.addEventListener("dragleave", () => {
476
+ dropZone.classList.remove("border-blue-500", "text-blue-500");
477
+ });
478
+
479
+ dropZone.addEventListener("drop", (e) => {
480
+ e.preventDefault();
481
+ dropZone.classList.remove("border-blue-500", "text-blue-500");
482
+
483
+ const files = e.dataTransfer.files;
484
+ fileInput.files = files;
485
+
486
+ const list = document.getElementById("selectedFilesList");
487
+ list.innerHTML = "";
488
+ for (const file of files) {
489
+ const p = document.createElement("p");
490
+ p.textContent = file.name;
491
+ list.appendChild(p);
492
+ }
493
+ });