MissSqui commited on
Commit
f9e92fa
·
verified ·
1 Parent(s): 887ddaa

Update abc12

Browse files
Files changed (1) hide show
  1. abc12 +1092 -801
abc12 CHANGED
@@ -1,804 +1,1095 @@
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
-
8
- <!-- Bootstrap CSS & Icons -->
9
- <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
10
- <link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.5/font/bootstrap-icons.css" rel="stylesheet">
11
-
12
- <!-- Tailwind (only if you still need it) -->
13
- <script src="https://cdn.tailwindcss.com"></script>
14
-
15
- <style>
16
- body {
17
- background: linear-gradient(to bottom right, #e0f2ff, #ede9fe);
18
- padding-bottom: 4rem;
19
- }
20
- .loading-dots {
21
- display: inline-block;
22
- text-align: left;
23
- }
24
- .loading-dots span {
25
- display: inline-block;
26
- width: 8px;
27
- height: 8px;
28
- margin: 0 2px;
29
- background-color: #555;
30
- border-radius: 50%;
31
- animation: blink 1.4s infinite both;
32
- }
33
- .loading-dots span:nth-child(2) {
34
- animation-delay: 0.2s;
35
- }
36
- .loading-dots span:nth-child(3) {
37
- animation-delay: 0.4s;
38
- }
39
- @keyframes blink {
40
- 0%, 80%, 100% { opacity: 0; }
41
- 40% { opacity: 1; }
42
- }
43
- </style>
44
- </head>
45
- <body class="font-sans">
46
-
47
- <!-- HEADER -->
48
- <div class="bg-gradient-to-r from-blue-500 to-violet-600 text-white shadow-md py-4 px-6 flex justify-between items-center">
49
- <div class="flex items-center gap-4">
50
- <!-- Back button now has id="backBtn" and no inline onclick -->
51
- <button id="backBtn"
52
- class="text-white hover:text-gray-200 transition"
53
- aria-label="Go back">
54
- <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none"
55
- viewBox="0 0 24 24" stroke="currentColor">
56
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
57
- d="M15 19l-7-7 7-7" />
58
- </svg>
59
- </button>
60
- <h2 class="text-2xl font-bold">📚 Document Chat &amp; Summarization</h2>
61
- </div>
62
- <div class="relative inline-block text-left">
63
- <select id="zone-dropdown"
64
- class="min-w-[14rem] appearance-none px-3 py-2 border rounded-lg bg-white text-gray-800 shadow focus:outline-none focus:ring-2 focus:ring-blue-500">
65
- </select>
66
- <div class="pointer-events-none absolute inset-y-0 right-3 flex items-center">
67
- <svg class="w-4 h-4 text-gray-500" fill="none" stroke="currentColor" stroke-width="2"
68
- viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round">
69
- <polyline points="6 9 12 15 18 9"></polyline>
70
- </svg>
71
- </div>
72
- </div>
73
- </div>
74
-
75
- <!-- DIRECTORY PATH DISPLAY -->
76
- <div class="max-w-7xl mx-auto mt-6 px-4 text-gray-700 text-sm font-medium select-none">
77
- <span id="effort-path" class="text-gray-800 font-semibold">{{ effort_name }}</span>
78
- <span class="mx-2 text-gray-500">→</span>
79
- <span id="notebook-path" class="text-gray-800 font-semibold">{{ notebook_name }}</span>
80
- <span class="mx-2 text-gray-500">→</span>
81
- <span id="file-path" class="text-blue-700 font-semibold">No File Selected</span>
82
- </div>
83
-
84
- <!-- MAIN LAYOUT -->
85
- <div class="flex max-w-7xl mx-auto mt-6 gap-6 px-4">
86
-
87
- <!-- LEFT: Sidebar -->
88
- <div class="w-1/4 bg-white rounded-2xl shadow-md p-4 h-[34rem] overflow-y-auto">
89
- <button class="upload-button w-full bg-blue-600 hover:bg-blue-700 text-white py-2 px-4 rounded mb-4"
90
- onclick="openModal()">Upload File(s)</button>
91
- <h3 class="text-lg font-semibold mb-2 text-gray-700">📂 Available Files</h3>
92
- <ul id="file-list" class="space-y-3 text-gray-700 text-sm">
93
- <!-- Files injected here -->
94
- </ul>
95
- </div>
96
-
97
- <!-- RIGHT: Chat Panel -->
98
- <div class="w-3/4 bg-white rounded-2xl shadow-md p-6 flex flex-col">
99
- <div id="chat-box" class="h-80 overflow-y-scroll border rounded-lg p-4 bg-gray-50 space-y-3 mb-4">
100
- <!-- Chat messages will appear here -->
101
- </div>
102
-
103
- <div class="flex flex-col sm:flex-row items-stretch sm:items-center gap-2 mt-auto">
104
- <input id="persona" type="text" placeholder="Enter persona..." value="You are a helpful assistant"
105
- class="px-4 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-purple-500" />
106
-
107
- <input id="message" type="text" placeholder="Type your message..."
108
- class="flex-grow px-4 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500" />
109
-
110
- <button onclick="sendMessage()"
111
- class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-lg transition">
112
- Send
113
- </button>
114
-
115
- <select id="summary-type"
116
- class="px-3 py-2 border rounded-lg bg-white focus:outline-none focus:ring-2 focus:ring-green-500">
117
- <option value="brief">Brief</option>
118
- <option value="detailed">Detailed</option>
119
- </select>
120
-
121
- <button onclick="summarize()"
122
- class="bg-green-600 hover:bg-green-700 text-white px-4 py-2 rounded-lg transition">
123
- Summarize
124
- </button>
125
- </div>
126
- </div>
127
- </div>
128
-
129
- <!-- UPLOAD MODAL -->
130
- <div id="uploadModal" class="modal fixed z-50 inset-0 hidden bg-black bg-opacity-50 flex justify-center items-center">
131
- <div class="modal-content bg-white p-6 rounded-xl w-[90%] max-w-md relative">
132
- <span class="close absolute top-3 right-4 text-xl cursor-pointer" onclick="closeModal()">&times;</span>
133
- <h2 class="text-xl font-bold text-gray-800">NotebookLM</h2>
134
- <p class="text-sm text-gray-600 mt-1">Add sources (PDF only)</p>
135
- <div id="dropZone"
136
- 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"
137
- onclick="triggerFileInput()">
138
- Drag &amp; drop or <u>choose files</u><br>
139
- <small>Supported: PDF</small>
140
- </div>
141
- <input type="file" id="fileInput" accept=".pdf" multiple class="hidden" />
142
- <div id="selectedFilesList" class="mt-4 text-gray-700 text-sm space-y-1 max-h-40 overflow-y-auto">
143
- <!-- File names shown here -->
144
- </div>
145
- <div class="flex justify-center mt-4">
146
- <button class="submit-btn bg-blue-600 text-white px-4 py-2 rounded" onclick="submitFile()">Upload</button>
147
- </div>
148
- </div>
149
- </div>
150
-
151
- <!-- FEEDBACK MODAL (Bootstrap) -->
152
- <div class="modal fade" id="feedbackModal" tabindex="-1" aria-labelledby="feedbackModalLabel" aria-hidden="true">
153
- <div class="modal-dialog" role="document">
154
- <div class="modal-content" style="background-color: white; color: black;">
155
- <div class="modal-header">
156
- <h5 class="modal-title" id="feedbackModalLabel">Rate Your Experience</h5>
157
- <!-- Proper Bootstrap “close” button -->
158
- <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
159
- </div>
160
- <div class="modal-body">
161
- <label for="feedbackLevel">Rating (optional):</label>
162
- <select id="feedbackLevel" class="form-control">
163
- <option value="">-- Select --</option>
164
- <option value="1">Very Bad</option>
165
- <option value="2">Bad</option>
166
- <option value="3">Neutral</option>
167
- <option value="4">Good</option>
168
- <option value="5">Very Good</option>
169
- </select>
170
-
171
- <label for="comment" class="mt-2">Comments (optional):</label>
172
- <textarea id="comment" class="form-control" rows="3" placeholder="Write your feedback here..."></textarea>
173
- </div>
174
- <div class="modal-footer">
175
- <button id="submitFeedback" class="btn btn-primary">Submit</button>
176
- <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Skip</button>
177
- </div>
178
- </div>
179
- </div>
180
- </div>
181
-
182
- <!-- DELETE FILE CONFIRMATION MODAL (unchanged) -->
183
- <div class="modal fade" id="deleteFileModal" tabindex="-1" aria-labelledby="deleteFileModalLabel" aria-hidden="true">
184
- <div class="modal-dialog">
185
- <div class="modal-content">
186
- <div class="modal-header">
187
- <h5 class="modal-title" id="deleteFileModalLabel">Confirm Delete</h5>
188
- <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
189
- </div>
190
- <div class="modal-body">
191
- Are you sure you want to delete <strong id="deleteFileName"></strong>?
192
- </div>
193
- <div class="modal-footer">
194
- <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
195
- <button type="button" class="btn btn-danger" onclick="confirmDeleteFile()">Delete</button>
196
- </div>
197
- </div>
198
- </div>
199
- </div>
200
-
201
-
202
- <!-- SCRIPTS -->
203
- <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
204
- <script>
205
- document.addEventListener("DOMContentLoaded", function () {
206
- // Cache references
207
- const backButton = document.getElementById("backBtn");
208
- const feedbackModalEl = document.getElementById("feedbackModal");
209
- const bootstrapFeedbackModal = new bootstrap.Modal(feedbackModalEl);
210
-
211
- // Open feedback modal when “Back” is clicked
212
- backButton.addEventListener("click", function (event) {
213
- event.preventDefault();
214
- bootstrapFeedbackModal.show();
215
- });
216
-
217
- // When user clicks "Submit" inside feedback modal, navigate back
218
- document.getElementById("submitFeedback").addEventListener("click", function () {
219
- // You can grab rating/comment if needed:
220
- const rating = document.getElementById("feedbackLevel").value || "No rating";
221
- const comment = document.getElementById("comment").value || "";
222
-
223
- console.log("Feedback submitted:", { rating, comment });
224
-
225
- // Hide the modal first
226
- bootstrapFeedbackModal.hide();
227
-
228
- // Then navigate back to the notebook page:
229
- window.location.href = `/notebook/?effort_id=${effortId}&user_id=${userId}`;
230
- });
231
- });
232
- </script>
233
-
234
- <script>
235
- // Your existing chat/upload/delete code starts here:
236
- const userId = "{{ user_id }}";
237
- const effortId = "{{ effort_id }}";
238
- const notebookId = "{{ notebook_id }}";
239
- const effortName = "{{ effort_name }}";
240
- const notebookName = "{{ notebook_name }}";
241
- const preloadedFiles = {{ files | tojson }};
242
- const preloadedZones = {{ zones | tojson }};
243
- const chatHistory = {{ chat_history | tojson }};
244
-
245
- const chatBox = document.getElementById("chat-box");
246
- const zoneDropdown = document.getElementById("zone-dropdown");
247
- const fileList = document.getElementById("file-list");
248
- const modal = document.getElementById("uploadModal");
249
- const fileInput = document.getElementById("fileInput");
250
-
251
- fileInput.addEventListener("change", () => {
252
- const list = document.getElementById("selectedFilesList");
253
- list.innerHTML = "";
254
- const files = fileInput.files;
255
- if (!files.length) {
256
- list.textContent = "No files selected.";
257
- return;
258
- }
259
- for (let file of files) {
260
- const item = document.createElement("div");
261
- item.className = "flex items-center gap-2";
262
- item.innerHTML = `📄 <span class="truncate">${file.name}</span>`;
263
- list.appendChild(item);
264
- }
265
- });
266
-
267
- let currentZoneId = null;
268
- let currentFileUri = "";
269
- let selectedFileElement = null;
270
-
271
- function appendMessage(sender, message, color, alignRight = false) {
272
- const wrapper = document.createElement("div");
273
- wrapper.className = alignRight ? "flex justify-end" : "flex justify-start";
274
-
275
- const bubble = document.createElement("div");
276
- bubble.className = `max-w-xl px-4 py-2 rounded-lg ${color} text-white`;
277
- bubble.innerHTML = `<strong>${sender}:</strong> ${message}`;
278
-
279
- wrapper.appendChild(bubble);
280
- chatBox.appendChild(wrapper);
281
- chatBox.scrollTop = chatBox.scrollHeight;
282
- }
283
-
284
- function loadZones() {
285
- zoneDropdown.innerHTML = "";
286
- const defaultOpt = document.createElement("option");
287
- defaultOpt.disabled = true;
288
- defaultOpt.selected = true;
289
- defaultOpt.textContent = "Select a Configuration";
290
- zoneDropdown.appendChild(defaultOpt);
291
-
292
- try {
293
- preloadedZones.forEach(zone => {
294
- const opt = document.createElement("option");
295
- opt.value = zone.id;
296
- opt.textContent = zone.name;
297
- zoneDropdown.appendChild(opt);
298
- });
299
-
300
- const divider = document.createElement("option");
301
- divider.disabled = true;
302
- divider.textContent = "──────────";
303
- zoneDropdown.appendChild(divider);
304
- } catch (err) {
305
- console.error("Error loading zones:", err);
306
- }
307
-
308
- const manage = document.createElement("option");
309
- manage.value = "manage";
310
- manage.textContent = "➕ Manage Configurations";
311
- zoneDropdown.appendChild(manage);
312
-
313
- zoneDropdown.addEventListener("change", (e) => {
314
- if (e.target.value === "manage") {
315
- const form = document.createElement("form");
316
- form.method = "POST";
317
- form.action = "/chat_feature/manage_zones";
318
-
319
- const userInput = document.createElement("input");
320
- userInput.type = "hidden";
321
- userInput.name = "user_id";
322
- userInput.value = userId;
323
- form.appendChild(userInput);
324
-
325
- const effortInput = document.createElement("input");
326
- effortInput.type = "hidden";
327
- effortInput.name = "effort_id";
328
- effortInput.value = effortId;
329
- form.appendChild(effortInput);
330
-
331
- const notebookInput = document.createElement("input");
332
- notebookInput.type = "hidden";
333
- notebookInput.name = "notebook_id";
334
- notebookInput.value = notebookId;
335
- form.appendChild(notebookInput);
336
-
337
- document.body.appendChild(form);
338
- form.submit();
339
- } else {
340
- currentZoneId = parseInt(e.target.value);
341
- }
342
- });
343
- }
344
-
345
- function loadFiles() {
346
- const files = preloadedFiles || [];
347
- fileList.innerHTML = "";
348
-
349
- files.forEach(file => {
350
- const li = document.createElement("li");
351
- li.className =
352
- "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";
353
-
354
- const fileIcon = document.createElement("div");
355
- fileIcon.className = "flex-shrink-0 text-blue-600 text-lg";
356
- fileIcon.textContent = "📄";
357
-
358
- const fileName = document.createElement("div");
359
- fileName.className = "flex-1 min-w-0 cursor-pointer";
360
- fileName.innerHTML = `
361
- <p class="font-medium truncate group-hover:whitespace-normal group-hover:break-all" title="${file.name}">
362
- ${file.name}
363
- </p>
364
- `;
365
- fileName.onclick = () => {
366
- currentFileUri = file.uri;
367
- appendMessage("System", `Selected file: ${file.name}`, "bg-purple-600");
368
-
369
- document.getElementById("notebook-path").textContent = notebookName;
370
- document.getElementById("effort-path").textContent = effortName;
371
- document.getElementById("file-path").textContent = file.name;
372
-
373
- if (selectedFileElement) {
374
- selectedFileElement.classList.remove(
375
- "border-blue-500", "bg-blue-100", "ring", "ring-blue-300", "shadow-lg", "transform", "scale-105"
376
- );
377
- }
378
-
379
- li.classList.add("border-blue-500", "bg-blue-100", "ring", "ring-blue-300", "shadow-lg", "transform", "scale-105");
380
- selectedFileElement = li;
381
- };
382
-
383
- const deleteButton = document.createElement("button");
384
- deleteButton.className = "btn btn-sm btn-outline-danger";
385
- deleteButton.setAttribute("data-bs-toggle", "modal");
386
- deleteButton.setAttribute("data-bs-target", "#deleteFileModal");
387
- deleteButton.innerHTML = `<i class="bi bi-trash"></i>`;
388
- deleteButton.onclick = (e) => {
389
- e.stopPropagation();
390
- setDeleteFile(file.uri, file.name);
391
- };
392
-
393
- li.appendChild(fileIcon);
394
- li.appendChild(fileName);
395
- li.appendChild(deleteButton);
396
-
397
- fileList.appendChild(li);
398
- });
399
- }
400
-
401
- async function sendMessage() {
402
- const message = document.getElementById("message").value;
403
- const persona = document.getElementById("persona").value;
404
- if (!message.trim() || !currentFileUri) {
405
- alert("Please select a file and type a message.");
406
- return;
407
- }
408
- if (!currentZoneId) {
409
- alert("Please select a zone before sending a message.");
410
- return;
411
- }
412
-
413
- appendMessage("You", message, "bg-blue-500", true); // Right aligned
414
-
415
- const loadingId = "loading-msg";
416
- const loadingWrapper = document.createElement("div");
417
- loadingWrapper.id = loadingId;
418
- loadingWrapper.className = "flex justify-start";
419
-
420
- const loadingBubble = document.createElement("div");
421
- loadingBubble.className = "max-w-xl px-4 py-2 rounded-lg bg-gray-300 text-gray-800";
422
- loadingBubble.innerHTML = `<strong>Assistant:</strong> <span class="loading-dots"><span></span><span></span><span></span></span>`;
423
- loadingWrapper.appendChild(loadingBubble);
424
- chatBox.appendChild(loadingWrapper);
425
- chatBox.scrollTop = chatBox.scrollHeight;
426
-
427
- const res = await fetch("/chat_feature/send_message", {
428
- method: "POST",
429
- headers: { "Content-Type": "application/json" },
430
- body: JSON.stringify({
431
- message,
432
- persona,
433
- file_uri: currentFileUri,
434
- experiment_zone_id: currentZoneId,
435
- effortId: effortId,
436
- notebookId: notebookId
437
- })
438
- });
439
-
440
- const data = await res.json();
441
- document.getElementById(loadingId)?.remove();
442
- appendMessage("Assistant", data.message || "No response", "bg-gray-700");
443
- document.getElementById("message").value = "";
444
- }
445
-
446
- async function summarize() {
447
- const summaryType = document.getElementById("summary-type").value;
448
- if (!currentFileUri) {
449
- alert("Please select a file and summarize.");
450
- return;
451
- }
452
- if (!currentZoneId) {
453
- alert("Please select a zone before summarizing.");
454
- return;
455
- }
456
-
457
- appendMessage("You", `Summarize (${summaryType})`, "bg-green-500", true); // Right aligned
458
-
459
- const loadingId = "loading-msg";
460
- const loadingWrapper = document.createElement("div");
461
- loadingWrapper.id = loadingId;
462
- loadingWrapper.className = "flex justify-start";
463
-
464
- const loadingBubble = document.createElement("div");
465
- loadingBubble.className = "max-w-xl px-4 py-2 rounded-lg bg-gray-300 text-gray-800";
466
- loadingBubble.innerHTML = `<strong>Assistant:</strong> <span class="loading-dots"><span></span><span></span><span></span></span>`;
467
- loadingWrapper.appendChild(loadingBubble);
468
- chatBox.appendChild(loadingWrapper);
469
- chatBox.scrollTop = chatBox.scrollHeight;
470
-
471
- const res = await fetch("/chat_feature/summarize", {
472
- method: "POST",
473
- headers: { "Content-Type": "application/json" },
474
- body: JSON.stringify({
475
- summary_type: summaryType,
476
- file_uri: currentFileUri,
477
- experiment_zone_id: currentZoneId,
478
- effortId: effortId,
479
- notebookId: notebookId
480
- })
481
- });
482
-
483
- const data = await res.json();
484
- document.getElementById(loadingId)?.remove();
485
- appendMessage("Summary", data.message || "No summary", "bg-gray-700");
486
- }
487
-
488
- function openModal() { modal.classList.remove("hidden"); }
489
- function closeModal() {
490
- modal.classList.add("hidden");
491
- fileInput.value = "";
492
- }
493
- function triggerFileInput() { fileInput.click(); }
494
-
495
- async function submitFile() {
496
- const files = fileInput.files;
497
- if (!files.length) return alert("Choose at least one file.");
498
-
499
- const formData = new FormData();
500
- for (let file of files) {
501
- if (file.type !== "application/pdf") {
502
- alert(`Invalid file type: ${file.name}`);
503
- return;
504
- }
505
- formData.append("files", file);
506
- }
507
- formData.append("effort_id", effortId);
508
-
509
- try {
510
- const res = await fetch("/chat_feature/upload_file", {
511
- method: "POST",
512
- body: formData
513
- });
514
-
515
- const contentType = res.headers.get("content-type") || "";
516
- let responseData = contentType.includes("application/json") ? await res.json() : await res.text();
517
-
518
- if (!res.ok) {
519
- const errorMessage = typeof responseData === "string"
520
- ? responseData
521
- : (responseData.error || JSON.stringify(responseData));
522
- alert(`Upload failed: ${errorMessage}`);
523
- return;
524
- }
525
-
526
- alert("Upload successful!");
527
- location.reload(); // Refresh to get updated file list from server
528
- } catch (err) {
529
- alert("Error: " + err.message);
530
- }
531
- }
532
-
533
- window.onclick = function (event) {
534
- if (event.target == modal) closeModal();
535
- };
536
-
537
- let deleteFileUriToConfirm = "";
538
- let deleteFileName = "";
539
-
540
- function setDeleteFile(fileUri, fileName) {
541
- deleteFileUriToConfirm = fileUri;
542
- deleteFileName = fileName;
543
- document.getElementById("deleteFileName").textContent = fileName;
544
- }
545
-
546
- async function confirmDeleteFile() {
547
- try {
548
- const res = await fetch("/chat_feature/delete_file", {
549
- method: "POST",
550
- headers: { "Content-Type": "application/json" },
551
- body: JSON.stringify({
552
- file_uri: deleteFileUriToConfirm,
553
- effort_id: effortId,
554
- filename: deleteFileName
555
- })
556
- });
557
-
558
- if (!res.ok) {
559
- const text = await res.text();
560
- alert("Failed to delete: " + text);
561
- } else {
562
- const modal = bootstrap.Modal.getInstance(document.getElementById("deleteFileModal"));
563
- modal.hide();
564
- alert("File deleted.");
565
- location.reload();
566
- }
567
- } catch (err) {
568
- alert("Error deleting file: " + err.message);
569
- }
570
- }
571
-
572
- const dropZone = document.querySelector(".drop-zone");
573
- dropZone.addEventListener("dragover", (e) => {
574
- e.preventDefault();
575
- dropZone.classList.add("border-blue-500", "text-blue-500");
576
- });
577
- dropZone.addEventListener("dragleave", () => {
578
- dropZone.classList.remove("border-blue-500", "text-blue-500");
579
- });
580
- dropZone.addEventListener("drop", (e) => {
581
- e.preventDefault();
582
- dropZone.classList.remove("border-blue-500", "text-blue-500");
583
-
584
- const files = e.dataTransfer.files;
585
- fileInput.files = files;
586
-
587
- const list = document.getElementById("selectedFilesList");
588
- list.innerHTML = "";
589
- for (const file of files) {
590
- const p = document.createElement("p");
591
- p.textContent = file.name;
592
- list.appendChild(p);
593
- }
594
- });
595
-
596
- window.onload = function () {
597
- loadFiles();
598
- loadZones();
599
-
600
- let historyFilename = "";
601
-
602
- if (chatHistory && chatHistory.length > 0) {
603
- chatHistory.forEach(entry => {
604
- if (entry.filename && (entry.filename !== historyFilename || historyFilename === "")) {
605
- historyFilename = entry.filename;
606
- appendMessage("System", `Selected file: ${historyFilename}`, "bg-purple-600");
607
- }
608
-
609
- const userMessage = entry.question;
610
- appendMessage("You", userMessage, "bg-blue-500", true);
611
-
612
- const personaMessage = entry.persona;
613
- appendMessage("Persona", personaMessage, "bg-blue-500", true);
614
-
615
- const botMessage = entry.response;
616
- appendMessage("Assistant", botMessage, "bg-gray-700", false);
617
- });
618
- }
619
- };
620
- </script>
621
- </body>
622
- </html>
623
-
624
-
625
- -----------------------------------------------------------
626
-
627
- from flask import Flask, render_template, request, jsonify, Blueprint, redirect, url_for, make_response
628
- import requests
629
- from authentication import login_required
630
-
631
- #app = Flask(__name__)
632
-
633
- chat_feature_bp = Blueprint("chat_feature", __name__, template_folder="templates")
634
-
635
- API_URL = "http://localhost:8047/chat/chat_with_document"
636
- FILE_URI = "C:/Users/Jitmanyu/Desktop/Backend/file_store\\Factors_Influencing_the_Likelihood_of_Cu 1.pdf"
637
- EXPERIMENT_ZONE_ID = 1
638
- NOTEBOOK_ID = "1"
639
- EFFORT_ID = 1
640
-
641
- @chat_feature_bp.route("/")
642
- @login_required
643
- def home():
644
- notebook_id = request.args.get("notebook_id")
645
- effort_id = request.args.get("effort_id")
646
- user_id = request.args.get("user_id")
647
-
648
- if not notebook_id or not effort_id:
649
- return "Notebook ID and effort ID are required", 400
650
-
651
- files_in_effort = []
652
- ezones = []
653
- notebook_name = None
654
- effort_name = None
655
- notebook_chat = []
656
-
657
- try:
658
- # Fetch user details
659
- response = requests.get(
660
- "http://localhost:8047/user/get_user_details/",
661
- params={"User_ID": user_id},
662
- headers={"accept": "application/json"}
663
- )
664
- response.raise_for_status()
665
- data = response.json()
666
-
667
- # Parse files and ezones
668
- all_files_by_effort = data.get("files", {})
669
- effort_files = all_files_by_effort.get(str(effort_id), {})
670
- if effort_files:
671
- for name, path in effort_files.items():
672
- files_in_effort.append({"name": name, "uri": path})
673
-
674
- ezones = data.get("ezones", {}).get(str(effort_id), [])
675
-
676
- # Parse notebook name and effort name
677
- notebook_name = next(
678
- (notebook['name'] for notebook in data['notebooks'].get(str(effort_id), []) if notebook['id'] == int(notebook_id)),
679
- None
680
- )
681
- effort_name = next(
682
- (effort["name"] for effort in data.get("efforts", []) if str(effort["id"]) == str(effort_id)),
683
- None
684
- )
685
-
686
- # Fetch notebook details using new API
687
- notebook_response = requests.get(
688
- f"http://localhost:8047/user/get_notebook_details/{notebook_id}",
689
- headers={"accept": "application/json"}
690
- )
691
- notebook_response.raise_for_status()
692
- notebook_data = notebook_response.json()
693
-
694
- # Extract chat from notebook details
695
- notebook_chat = notebook_data.get("chat", {}).get("message", [])
696
-
697
- except requests.RequestException as e:
698
- print("Error during API calls:", e)
699
- return "Failed to load data", 500
700
-
701
- return render_template(
702
- "chat.html",
703
- user_id=user_id,
704
- notebook_id=notebook_id,
705
- effort_id=effort_id,
706
- effort_name=effort_name,
707
- notebook_name=notebook_name,
708
- files=files_in_effort,
709
- zones=ezones,
710
- chat_history=notebook_chat # Pass chat messages to template
711
- )
712
-
713
- FASTAPI_URL = "http://localhost:8047"
714
-
715
- @chat_feature_bp.route('/upload_file', methods=['POST'])
716
- @login_required
717
- def upload_file():
718
- effort_id = request.form.get("effort_id")
719
-
720
- uploaded_files = request.files.getlist('files')
721
- if not uploaded_files:
722
- return jsonify({'error': 'No files uploaded'}), 400
723
-
724
- data = {
725
- "effort_id": effort_id,
726
- }
727
-
728
- results = []
729
- for uploaded_file in uploaded_files:
730
- files = {'file': (uploaded_file.filename, uploaded_file.stream, uploaded_file.mimetype)}
731
- response = requests.post('http://localhost:8047/doc/upload/', files=files, data=data)
732
-
733
- if response.ok:
734
- results.append({'filename': uploaded_file.filename, 'status': 'success'})
735
- else:
736
- results.append({'filename': uploaded_file.filename, 'status': 'error', 'details': response.text})
737
-
738
- return jsonify({'results': results}), 200
739
-
740
- @chat_feature_bp.route('/delete_file', methods=['POST'])
741
- @login_required
742
- def delete_file():
743
- data = request.json
744
-
745
- payload = {
746
- "effort_id": data["effort_id"],
747
- "filename": data["filename"]
748
- }
749
-
750
- response = requests.delete("http://localhost:8047/doc/delete/", data=payload)
751
-
752
- if response.status_code != 200:
753
- return make_response(jsonify({
754
- "message": f"Delete failed: {response.text}"
755
- }), response.status_code)
756
-
757
- return jsonify(response.json())
758
-
759
- @chat_feature_bp.route("/manage_zones", methods=["POST"])
760
- @login_required
761
- def manage_zones():
762
- effort_id = request.form.get("effort_id") # Get from POST form data
763
- notebook_id = request.form.get("notebook_id") # Get from POST form data
764
- user_id = request.form.get("user_id")
765
- return redirect(url_for("zone_page.home", user_id = user_id, effort_id=effort_id, notebook_id=notebook_id))
766
-
767
- @chat_feature_bp.route("/send_message", methods=["POST"])
768
- @login_required
769
- def send_message():
770
- data = request.json
771
- payload = {
772
- "file_uri": data["file_uri"],
773
- "experiment_zone_id": data["experiment_zone_id"],
774
- "query_prompt": data["message"],
775
- "persona": data["persona"],
776
- "RAG": "Y",
777
- "summary_prompt": "", # unused in chat
778
- "notebook_id": data["notebookId"],
779
- "effort_id": data["effortId"]
780
- }
781
- response = requests.post(API_URL, json=payload)
782
- print("API call is being made")
783
- return jsonify(response.json())
784
-
785
- @chat_feature_bp.route("/summarize", methods=["POST"])
786
- @login_required
787
- def summarize():
788
- data = request.json
789
- payload = {
790
- "file_uri": data["file_uri"],
791
- "experiment_zone_id": data["experiment_zone_id"],
792
- "query_prompt": "",
793
- "persona": "You are a helpful assistant",
794
- "RAG": "N",
795
- "summary_prompt": data["summary_type"], # unused in summarization
796
- "notebook_id": data["notebookId"],
797
- "effort_id": data["effortId"]
798
- }
799
- response = requests.post(API_URL, json=payload)
800
- print("API call is being made")
801
- return jsonify(response.json())
802
 
 
803
 
 
804
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2
 
3
+ import psycopg2
4
 
5
+ import psycopg2.extras
6
 
7
+ import json
8
+
9
+ import re
10
+
11
+ from logging import getLogger
12
+
13
+ from fastapi import HTTPException
14
+
15
+ from dotenv import load_dotenv
16
+
17
+ from pydantic import BaseModel
18
+
19
+ from typing import List
20
+
21
+ from logging_log.logger import logger
22
+
23
+
24
+
25
+ # Load environment variables from the .env file
26
+
27
+ load_dotenv()
28
+
29
+
30
+
31
+ #logger = getLogger(__name__)
32
+
33
+
34
+
35
+ # Pydantic model
36
+
37
+ class UserCreate(BaseModel):
38
+
39
+ user_id: str
40
+
41
+ password: str
42
+
43
+ ad_groups: List[str]
44
+
45
+ is_admin: bool = False
46
+
47
+
48
+
49
+ # Database connection function
50
+
51
+ def get_db_connection():
52
+
53
+ try:
54
+
55
+ # Create the connection
56
+
57
+ conn = psycopg2.connect(
58
+
59
+ host=os.getenv("HOSTNAME"),
60
+
61
+ database=os.getenv("DATABASE"),
62
+
63
+ user=os.getenv("DB_USERNAME"),
64
+
65
+ password=os.getenv("DB_PASSWORD"),
66
+
67
+ port=os.getenv("PORT")
68
+
69
+ )
70
+
71
+ logger.info("Database connection established.")
72
+
73
+ return conn
74
+
75
+ except Exception as e:
76
+
77
+ logger.error(f"Error connecting to database: {str(e)}")
78
+
79
+ raise e
80
+
81
+
82
+
83
+ # Validate user ID
84
+
85
+ def validate_user_id(user_id: str):
86
+
87
+ if not re.match(r'^[a-zA-Z0-9]{5,}$', user_id):
88
+
89
+ raise HTTPException(status_code=400, detail="Invalid user ID format")
90
+
91
+ if not user_id:
92
+
93
+ raise HTTPException(status_code=400, detail="User ID cannot be empty")
94
+
95
+
96
+
97
+ # Validate notebook ID
98
+
99
+ def validate_notebook_id(notebook_id: str):
100
+
101
+ if not notebook_id.isdigit():
102
+
103
+ raise HTTPException(status_code=406, detail="Invalid notebook ID format")
104
+
105
+
106
+
107
+ # Create a new user in the DB
108
+
109
+ def create_user(user_id: str, password: str, ad_groups: List[str], is_admin: bool):
110
+
111
+
112
+
113
+ insert_query = """
114
+
115
+ INSERT INTO users (user_id, password, ad_groups, is_admin)
116
+
117
+ VALUES (%s, %s, %s, %s)
118
+
119
+ """
120
+
121
+
122
+
123
+ try:
124
+
125
+ with get_db_connection() as conn:
126
+
127
+ with conn.cursor() as curr:
128
+
129
+ curr.execute(insert_query, (user_id, password, ad_groups, is_admin))
130
+
131
+ conn.commit()
132
+
133
+ logger.info(f"User {user_id} created")
134
+
135
+ except Exception as e:
136
+
137
+ conn.rollback()
138
+
139
+ logger.error(f"Error creating new user {user_id}")
140
+
141
+
142
+
143
+ # Update an existing user in the DB
144
+
145
+ def update_user(user_id: str, password: str, ad_groups: List[str], is_admin: bool):
146
+
147
+ update_query = """
148
+
149
+ UPDATE users
150
+
151
+ SET password = %s,
152
+
153
+ ad_groups = %s,
154
+
155
+ is_admin = %s
156
+
157
+ WHERE user_id = %s
158
+
159
+ """
160
+
161
+ try:
162
+
163
+ with get_db_connection() as conn:
164
+
165
+ with conn.cursor() as curr:
166
+
167
+ curr.execute(update_query, (password, ad_groups, is_admin, user_id))
168
+
169
+ conn.commit()
170
+
171
+ logger.info(f"User {user_id} updated")
172
+
173
+ except Exception as e:
174
+
175
+ conn.rollback()
176
+
177
+ logger.error(f"Error updating user {user_id}: {str(e)}")
178
+
179
+ raise
180
+
181
+
182
+
183
+ # delete a user in the DB
184
+
185
+ def delete_user(user_id: str):
186
+
187
+ delete_query = """
188
+
189
+ DELETE FROM users WHERE user_id = %s;
190
+
191
+ """
192
+
193
+ try:
194
+
195
+ with get_db_connection() as conn:
196
+
197
+ with conn.cursor() as curr:
198
+
199
+ curr.execute(delete_query, (user_id, ))
200
+
201
+ conn.commit()
202
+
203
+ logger.info(f"User {user_id} deleted")
204
+
205
+ except Exception as e:
206
+
207
+ conn.rollback()
208
+
209
+ logger.error(f"Error deleting user {user_id}: {str(e)}")
210
+
211
+ raise
212
+
213
+
214
+
215
+ def get_user_credentials(user_id: str):
216
+
217
+ select_query = """
218
+
219
+ SELECT * FROM users WHERE user_id = %s;
220
+
221
+ """
222
+
223
+ try:
224
+
225
+ with get_db_connection() as conn:
226
+
227
+ with conn.cursor() as curr:
228
+
229
+ curr.execute(select_query, (user_id,))
230
+
231
+ user = curr.fetchone()
232
+
233
+ if user:
234
+
235
+ logger.info(f"User {user_id} fetched")
236
+
237
+ return {
238
+
239
+ "user_id": user[0],
240
+
241
+ "password": user[1],
242
+
243
+ "ad_groups": user[2],
244
+
245
+ "is_admin": user[3]
246
+
247
+ }
248
+
249
+ else:
250
+
251
+ logger.warning(f"User {user_id} not found")
252
+
253
+ return {"error": "User not found"}
254
+
255
+ except Exception as e:
256
+
257
+ logger.error(f"Error fetching user {user_id}: {str(e)}")
258
+
259
+ raise
260
+
261
+
262
+
263
+
264
+
265
+
266
+
267
+ # Fetch user details
268
+
269
+ def get_user_details(User_ID):
270
+
271
+ validate_user_id(User_ID)
272
+
273
+ conn = get_db_connection()
274
+
275
+ cursor = conn.cursor(cursor_factory=psycopg2.extras.DictCursor)
276
+
277
+ try:
278
+
279
+ cursor.execute(
280
+
281
+ "SELECT ad_groups, is_admin FROM users WHERE user_id=%s",
282
+
283
+ (User_ID, )
284
+
285
+ )
286
+
287
+ user_row = cursor.fetchone()
288
+
289
+ if not user_row:
290
+
291
+ raise HTTPException(status_code=404, detail="User not found")
292
+
293
+
294
+
295
+ if user_row:
296
+
297
+ ad_groups, is_admin = user_row
298
+
299
+
300
+
301
+ cursor.execute("SELECT id, name, files, permission FROM efforts")
302
+
303
+ efforts_data = cursor.fetchall()
304
+
305
+
306
+
307
+ permissions = {
308
+
309
+ "permissions": [],
310
+
311
+ "efforts": [],
312
+
313
+ "notebooks": {},
314
+
315
+ "ezones": {},
316
+
317
+ "files": {}
318
+
319
+ }
320
+
321
+ """
322
+
323
+ if ad_group in user_permissions.get("can_create_effort", []):
324
+
325
+ permissions["permissions"].append("can_create_effort")
326
+
327
+ if ad_group in user_permissions.get("can_view_effort", []):
328
+
329
+ permissions["permissions"].append("can_view_effort")
330
+
331
+ if ad_group in user_permissions.get("can_revoke_effort", []):
332
+
333
+ permissions["permissions"].append("can_revoke_effort")
334
+
335
+ if ad_group in user_permissions.get("can_modify_effort", []):
336
+
337
+ permissions["permissions"].append("can_modify_effort")
338
+
339
+ """
340
+
341
+
342
+
343
+ if is_admin:
344
+
345
+ permissions["permissions"].append("can_create_efforts")
346
+
347
+ permissions["permissions"].append("can_view_efforts")
348
+
349
+ permissions["permissions"].append("can_revoke_efforts")
350
+
351
+ permissions["permissions"].append("can_modify_efforts")
352
+
353
+
354
+
355
+ for effort in efforts_data:
356
+
357
+
358
+
359
+ effort_id = effort["id"]
360
+
361
+ effort_name = effort["name"]
362
+
363
+ files = effort["files"] if effort["files"] else []
364
+
365
+ permission_json = effort["permission"]
366
+
367
+ effort_permissions = []
368
+
369
+
370
+
371
+ if "can_view_effort" not in effort_permissions:
372
+
373
+ effort_permissions.append("can_view_effort")
374
+
375
+ permissions["efforts"].append({"name": effort_name, "id": effort_id})
376
+
377
+ permissions["files"][effort_id] = files
378
+
379
+
380
+
381
+ cursor.execute("SELECT notebook_id, notebook_name FROM notebook_details WHERE effort_id=%s", (effort_id,))
382
+
383
+ notebook_details = cursor.fetchall()
384
+
385
+ permissions["notebooks"][effort_id] = [{"name": note["notebook_name"], "id": note["notebook_id"]} for note in notebook_details]
386
+
387
+
388
+
389
+ cursor.execute("SELECT id, name FROM zones WHERE effort_id=%s", (effort_id,))
390
+
391
+ zones = cursor.fetchall()
392
+
393
+ permissions["ezones"][effort_id] = [{"name": zone["name"], "id": zone["id"]} for zone in zones]
394
+
395
+
396
+
397
+
398
+
399
+ if "can_create_zones" not in effort_permissions:
400
+
401
+ effort_permissions.append("can_create_zones")
402
+
403
+ if "can_modify_zones" not in effort_permissions:
404
+
405
+ effort_permissions.append("can_modify_zones")
406
+
407
+ if "can_delete_zones" not in effort_permissions:
408
+
409
+ effort_permissions.append("can_delete_zones")
410
+
411
+
412
+
413
+ if "can_create_notebooks" not in effort_permissions:
414
+
415
+ effort_permissions.append("can_create_notebooks")
416
+
417
+ if "can_delete_notebooks" not in effort_permissions:
418
+
419
+ effort_permissions.append("can_delete_notebooks")
420
+
421
+
422
+
423
+ if effort_permissions:
424
+
425
+ permissions["permissions"].append({effort_id: effort_permissions})
426
+
427
+
428
+
429
+ else:
430
+
431
+ permissions["permissions"].append("can_view_efforts")
432
+
433
+
434
+
435
+ for effort in efforts_data:
436
+
437
+
438
+
439
+ effort_id = effort["id"]
440
+
441
+ effort_name = effort["name"]
442
+
443
+ files = effort["files"] if effort["files"] else []
444
+
445
+ permission_json = effort["permission"]
446
+
447
+ effort_permissions = []
448
+
449
+
450
+
451
+ """
452
+
453
+ if ad_group in permission_json.get("can_create_zones", []):
454
+
455
+ effort_permissions.append("can_create_zones")
456
+
457
+ if ad_group in permission_json.get("can_modify_zones", []):
458
+
459
+ effort_permissions.append("can_modify_zones")
460
+
461
+ if ad_group in permission_json.get("can_delete_zones", []):
462
+
463
+ effort_permissions.append("can_delete_zones")
464
+
465
+ if ad_group in permission_json.get("can_create_notebooks", []):
466
+
467
+ effort_permissions.append("can_create_notebooks")
468
+
469
+ if ad_group in permission_json.get("can_delete_notebooks", []):
470
+
471
+ effort_permissions.append("can_delete_notebooks")
472
+
473
+ """
474
+
475
+
476
+
477
+ for ad_group in ad_groups:
478
+
479
+
480
+
481
+ if ad_group in permission_json.get("can_view_effort", []):
482
+
483
+ if "can_view_effort" not in effort_permissions:
484
+
485
+ effort_permissions.append("can_view_effort")
486
+
487
+ permissions["efforts"].append({"name": effort_name, "id": effort_id})
488
+
489
+ permissions["files"][effort_id] = files
490
+
491
+
492
+
493
+ cursor.execute("SELECT notebook_id, notebook_name FROM notebook_details WHERE effort_id=%s", (effort_id,))
494
+
495
+ notebook_details = cursor.fetchall()
496
+
497
+ permissions["notebooks"][effort_id] = [{"name": note["notebook_name"], "id": note["notebook_id"]} for note in notebook_details]
498
+
499
+
500
+
501
+ cursor.execute("SELECT id, name FROM zones WHERE effort_id=%s", (effort_id,))
502
+
503
+ zones = cursor.fetchall()
504
+
505
+ permissions["ezones"][effort_id] = [{"name": zone["name"], "id": zone["id"]} for zone in zones]
506
+
507
+
508
+
509
+ if ad_group in permission_json.get("can_create_and_modify_zones", []):
510
+
511
+ if "can_create_zones" not in effort_permissions:
512
+
513
+ effort_permissions.append("can_create_zones")
514
+
515
+ if "can_modify_zones" not in effort_permissions:
516
+
517
+ effort_permissions.append("can_modify_zones")
518
+
519
+ if "can_delete_zones" not in effort_permissions:
520
+
521
+ effort_permissions.append("can_delete_zones")
522
+
523
+
524
+
525
+ if ad_group in permission_json.get("can_create_and_modify_notebooks", []):
526
+
527
+ if "can_create_notebooks" not in effort_permissions:
528
+
529
+ effort_permissions.append("can_create_notebooks")
530
+
531
+ if "can_delete_notebooks" not in effort_permissions:
532
+
533
+ effort_permissions.append("can_delete_notebooks")
534
+
535
+
536
+
537
+ if effort_permissions:
538
+
539
+ permissions["permissions"].append({effort_id: effort_permissions})
540
+
541
+
542
+
543
+ return {
544
+
545
+ "permissions": permissions["permissions"],
546
+
547
+ "efforts": permissions["efforts"],
548
+
549
+ "notebooks": permissions["notebooks"],
550
+
551
+ "ezones": permissions["ezones"],
552
+
553
+ "files": permissions["files"]
554
+
555
+ }
556
+
557
+ except Exception as e:
558
+
559
+ logger.error(f"Error fetching user details: {str(e)}")
560
+
561
+ raise e
562
+
563
+ finally:
564
+
565
+ cursor.close()
566
+
567
+ conn.close()
568
+
569
+
570
+
571
+
572
+
573
+ # Fetch notebook details
574
+
575
+ def get_notebook_details(notebook_id):
576
+
577
+ validate_notebook_id(notebook_id)
578
+
579
+ conn = get_db_connection()
580
+
581
+ cursor = conn.cursor(cursor_factory=psycopg2.extras.DictCursor)
582
+
583
+ try:
584
+
585
+ cursor.execute(
586
+
587
+ "SELECT notebook_id, notebook_name, chat_history, created_at, effort_id FROM notebook_details WHERE notebook_id=%s",
588
+
589
+ (notebook_id,)
590
+
591
+ )
592
+
593
+ notebook = cursor.fetchone()
594
+
595
+ if not notebook:
596
+
597
+ raise HTTPException(status_code=404, detail="Notebook not found")
598
+
599
+
600
+
601
+ cursor.execute("SELECT name FROM efforts WHERE id=%s", (notebook["effort_id"],))
602
+
603
+ effort = cursor.fetchone()
604
+
605
+
606
+
607
+ return {
608
+
609
+ "id": notebook["notebook_id"],
610
+
611
+ "name": notebook["notebook_name"],
612
+
613
+ "chat": notebook["chat_history"],
614
+
615
+ "Created at": notebook["created_at"],
616
+
617
+ "effort_name": effort["name"] if effort else None
618
+
619
+ }
620
+
621
+ except Exception as e:
622
+
623
+ logger.error(f"Error fetching notebook details: {str(e)}")
624
+
625
+ raise HTTPException(status_code=500, detail=f"Error fetching notebook details: {str(e)}")
626
+
627
+ finally:
628
+
629
+ cursor.close()
630
+
631
+ conn.close()
632
+
633
+
634
+
635
+ # Effort Management
636
+
637
+
638
+
639
+ # Create effort
640
+
641
+ def create_effort(name, permission):
642
+
643
+ conn = None
644
+
645
+ cursor = None
646
+
647
+ try:
648
+
649
+ conn = get_db_connection()
650
+
651
+ cursor = conn.cursor()
652
+
653
+ cursor.execute(
654
+
655
+ "INSERT INTO efforts (name, permission) VALUES (%s, %s)",
656
+
657
+ (name, psycopg2.extras.Json(permission))
658
+
659
+ )
660
+
661
+ conn.commit()
662
+
663
+ logger.info(f"Effort '{name}' created.")
664
+
665
+ except Exception as e:
666
+
667
+ if conn:
668
+
669
+ conn.rollback()
670
+
671
+ logger.error(f"Error creating effort '{name}': {str(e)}")
672
+
673
+ raise e
674
+
675
+ finally:
676
+
677
+ if cursor:
678
+
679
+ cursor.close()
680
+
681
+ if conn:
682
+
683
+ conn.close()
684
+
685
+
686
+
687
+ # Delete effort
688
+
689
+ def delete_effort(effort_id):
690
+
691
+ conn = None
692
+
693
+ cursor = None
694
+
695
+ try:
696
+
697
+ conn = get_db_connection()
698
+
699
+ cursor = conn.cursor()
700
+
701
+ cursor.execute("DELETE FROM efforts WHERE id = %s", (effort_id,))
702
+
703
+ conn.commit()
704
+
705
+ if cursor.rowcount == 0:
706
+
707
+ raise ValueError(f"Effort with ID {effort_id} not found")
708
+
709
+ logger.info(f"Effort with ID {effort_id} deleted successfully.")
710
+
711
+ except Exception as e:
712
+
713
+ if conn:
714
+
715
+ conn.rollback()
716
+
717
+ logger.error(f"Error deleting effort with ID {effort_id}: {str(e)}")
718
+
719
+ raise e
720
+
721
+ finally:
722
+
723
+ if cursor:
724
+
725
+ cursor.close()
726
+
727
+ if conn:
728
+
729
+ conn.close()
730
+
731
+
732
+
733
+ # Modify effort
734
+
735
+ def modify_effort(effort_id, name, permission):
736
+
737
+ conn = None
738
+
739
+ cursor = None
740
+
741
+ try:
742
+
743
+ conn = get_db_connection()
744
+
745
+ cursor = conn.cursor()
746
+
747
+ cursor.execute(
748
+
749
+ "UPDATE efforts SET name = %s, permission = %s WHERE id = %s",
750
+
751
+ (name, psycopg2.extras.Json(permission), effort_id)
752
+
753
+ )
754
+
755
+ conn.commit()
756
+
757
+ if cursor.rowcount == 0:
758
+
759
+ raise ValueError(f"Effort with ID {effort_id} not found")
760
+
761
+ logger.info(f"Effort with ID {effort_id} updated successfully.")
762
+
763
+ except Exception as e:
764
+
765
+ if conn:
766
+
767
+ conn.rollback()
768
+
769
+ logger.error(f"Error modifying effort with ID {effort_id}: {str(e)}")
770
+
771
+ raise e
772
+
773
+ finally:
774
+
775
+ if cursor:
776
+
777
+ cursor.close()
778
+
779
+ if conn:
780
+
781
+ conn.close()
782
+
783
+
784
+
785
+ # List effort details
786
+
787
+ def list_effort_details(effort_id):
788
+
789
+ conn = None
790
+
791
+ cursor = None
792
+
793
+ try:
794
+
795
+ conn = get_db_connection()
796
+
797
+ cursor = conn.cursor(cursor_factory=psycopg2.extras.DictCursor)
798
+
799
+ cursor.execute("SELECT * FROM efforts WHERE id = %s", (effort_id,))
800
+
801
+ effort_details = cursor.fetchone()
802
+
803
+ if effort_details:
804
+
805
+ logger.info(f"Effort details fetched for ID {effort_id}")
806
+
807
+ return dict(effort_details)
808
+
809
+ else:
810
+
811
+ raise ValueError(f"Effort with ID {effort_id} not found")
812
+
813
+ except Exception as e:
814
+
815
+ logger.error(f"Error fetching effort details for ID {effort_id}: {str(e)}")
816
+
817
+ raise e
818
+
819
+ finally:
820
+
821
+ if cursor:
822
+
823
+ cursor.close()
824
+
825
+ if conn:
826
+
827
+ conn.close()
828
+
829
+
830
+
831
+
832
+
833
+ # Zone Management
834
+
835
+
836
+
837
+ # Delete experiment zone
838
+
839
+ def delete_experiment_zone(zone_id: int):
840
+
841
+ try:
842
+
843
+ conn = get_db_connection()
844
+
845
+ cursor = conn.cursor()
846
+
847
+ cursor.execute("DELETE FROM zones WHERE id = %s", (zone_id,))
848
+
849
+ conn.commit()
850
+
851
+ if cursor.rowcount == 0:
852
+
853
+ raise ValueError(f"Zone with ID {zone_id} not found")
854
+
855
+ logger.info(f"Experiment zone with ID {zone_id} deleted successfully.")
856
+
857
+ except Exception as e:
858
+
859
+ logger.error(f"Error while deleting experiment zone: {str(e)}")
860
+
861
+ raise e
862
+
863
+ finally:
864
+
865
+ cursor.close()
866
+
867
+ conn.close()
868
+
869
+
870
+
871
+ # Fetch zone details
872
+
873
+ def get_zone_details(zone_id: int):
874
+
875
+ try:
876
+
877
+ conn = get_db_connection()
878
+
879
+ cursor = conn.cursor(cursor_factory=psycopg2.extras.DictCursor)
880
+
881
+ cursor.execute("SELECT id, name, config, created_at FROM zones WHERE id = %s", (zone_id,))
882
+
883
+ zone = cursor.fetchone()
884
+
885
+ if not zone:
886
+
887
+ logger.warning(f"No experiment zone found with ID {zone_id}.")
888
+
889
+ raise ValueError(f"Zone with ID {zone_id} not found")
890
+
891
+ logger.info(f"Details for experiment zone ID {zone_id} fetched successfully.")
892
+
893
+ return zone
894
+
895
+ except Exception as e:
896
+
897
+ logger.error(f"Error while fetching zone details: {str(e)}")
898
+
899
+ raise e
900
+
901
+ finally:
902
+
903
+ cursor.close()
904
+
905
+ conn.close()
906
+
907
+
908
+
909
+ # Create experiment zone
910
+
911
+ def create_experiment_zone(effort_id: int, name: str, vector_db: str, llm_model: str, chunking_strategy: str, embedding_model: str, indexer: str, top_k: int, overlap: int, chunk_size: int):
912
+
913
+ try:
914
+
915
+ conn = get_db_connection()
916
+
917
+ cursor = conn.cursor()
918
+
919
+ if not name:
920
+
921
+ raise HTTPException(status_code=400, detail="Zone name cannot be empty")
922
+
923
+
924
+
925
+ config = {
926
+
927
+ "llm_model": llm_model,
928
+
929
+ "embedding_model": embedding_model,
930
+
931
+ "vector_db": vector_db,
932
+
933
+ "chunking_strategy": chunking_strategy,
934
+
935
+ "indexer": indexer,
936
+
937
+ "top_k": top_k,
938
+
939
+ "overlap": overlap,
940
+
941
+ "chunk_size": chunk_size
942
+
943
+ }
944
+
945
+ cursor.execute(
946
+
947
+ "INSERT INTO zones (effort_id, name, config) VALUES (%s, %s, %s) RETURNING id",
948
+
949
+ (effort_id, name, json.dumps(config))
950
+
951
+ )
952
+
953
+ zone_id = cursor.fetchone()[0]
954
+
955
+ conn.commit()
956
+
957
+ logger.info(f"Experiment zone '{name}' created with ID {zone_id}.")
958
+
959
+ return {"zone_id": zone_id}
960
+
961
+ except Exception as e:
962
+
963
+ logger.error(f"Error while creating experiment zone: {str(e)}")
964
+
965
+ raise e
966
+
967
+ finally:
968
+
969
+ cursor.close()
970
+
971
+ conn.close()
972
+
973
+
974
+
975
+ # Modify experiment zone
976
+
977
+ def modify_experiment_zone(zone_id: int, name: str, vector_db: str, llm_model: str, chunking_strategy: str, embedding_model: str, indexer: str, top_k: int, overlap: int, chunk_size: int):
978
+
979
+ try:
980
+
981
+
982
+
983
+ conn = get_db_connection()
984
+
985
+ cursor = conn.cursor()
986
+
987
+ if not name:
988
+
989
+ raise HTTPException(status_code=400, detail="Zone name cannot be empty")
990
+
991
+
992
+
993
+ cursor.execute("SELECT effort_id FROM zones WHERE id = %s", (zone_id,))
994
+
995
+
996
+
997
+ if cursor.rowcount == 0:
998
+
999
+ raise ValueError(f"Experiment zone with ID {zone_id} not found")
1000
+
1001
+
1002
+
1003
+ effort_id = cursor.fetchone()[0]
1004
+
1005
+
1006
+
1007
+ config = {
1008
+
1009
+ "llm_model": llm_model,
1010
+
1011
+ "embedding_model": embedding_model,
1012
+
1013
+ "vector_db": vector_db,
1014
+
1015
+ "chunking_strategy": chunking_strategy,
1016
+
1017
+ "indexer": indexer,
1018
+
1019
+ "top_k": top_k,
1020
+
1021
+ "overlap": overlap,
1022
+
1023
+ "chunk_size": chunk_size
1024
+
1025
+ }
1026
+
1027
+
1028
+
1029
+ config_json = json.dumps(config)
1030
+
1031
+ cursor.execute(
1032
+
1033
+ "INSERT INTO zones (effort_id, name, config) VALUES (%s, %s, %s) RETURNING id",
1034
+
1035
+ (effort_id, name, config_json)
1036
+
1037
+ )
1038
+
1039
+ new_zone_id = cursor.fetchone()[0]
1040
+
1041
+
1042
+
1043
+ cursor.execute("""
1044
+
1045
+ DELETE FROM zones
1046
+
1047
+ WHERE id = %s
1048
+
1049
+ """, (zone_id,))
1050
+
1051
+
1052
+
1053
+ cursor.execute("""
1054
+
1055
+ DELETE FROM chunk_embeddings
1056
+
1057
+ WHERE zone_id = %s
1058
+
1059
+ """, (zone_id,))
1060
+
1061
+
1062
+
1063
+ cursor.execute("""
1064
+
1065
+ DELETE FROM file_chunks
1066
+
1067
+ WHERE zone_id = %s
1068
+
1069
+ """, (zone_id,))
1070
+
1071
+
1072
+
1073
+ conn.commit()
1074
+
1075
+
1076
+
1077
+ logger.info(f"Experiment zone with ID {zone_id} modified successfully. New zone ID is {new_zone_id}")
1078
+
1079
+
1080
+
1081
+ return new_zone_id
1082
+
1083
+
1084
+
1085
+ except Exception as e:
1086
+
1087
+ logger.error(f"Error while modifying experiment zone: {str(e)}")
1088
+
1089
+ raise e
1090
+
1091
+ finally:
1092
+
1093
+ cursor.close()
1094
+
1095
+ conn.close()