lvvignesh2122 commited on
Commit
5f2824f
·
1 Parent(s): 5bf211e

feat: UI overhaul with dark mode, glassmorphism, and tavily dependency fix

Browse files
frontend/index.html CHANGED
@@ -1,442 +1,96 @@
1
  <!DOCTYPE html>
2
- <html lang="en">
3
 
4
  <head>
5
- <meta charset="UTF-8" />
 
6
  <title>Gemini RAG Assistant</title>
7
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
8
 
9
- <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
 
10
 
11
- <style>
12
- :root {
13
- --bg: radial-gradient(1200px 600px at top, #e0e7ff 0%, #f8fafc 60%);
14
- --card: rgba(255, 255, 255, 0.9);
15
- --border: rgba(15, 23, 42, 0.08);
16
- --primary: #4f46e5;
17
- --secondary: #0ea5e9;
18
- --text: #0f172a;
19
- --muted: #64748b;
20
- --error: #dc2626;
21
- --success: #16a34a;
22
- }
23
-
24
- [data-theme="dark"] {
25
- --bg: radial-gradient(1200px 600px at top, #1e1b4b 0%, #0f172a 60%);
26
- --card: rgba(30, 41, 59, 0.9);
27
- --border: rgba(148, 163, 184, 0.1);
28
- --primary: #818cf8;
29
- --secondary: #38bdf8;
30
- --text: #f1f5f9;
31
- --muted: #94a3b8;
32
- --error: #f87171;
33
- --success: #4ade80;
34
- }
35
-
36
- * {
37
- box-sizing: border-box;
38
- font-family: Inter, sans-serif;
39
- }
40
-
41
- body {
42
- margin: 0;
43
- min-height: 100vh;
44
- background: var(--bg);
45
- display: flex;
46
- color: var(--text);
47
- }
48
-
49
- /* Layout */
50
- .app_layout {
51
- display: grid;
52
- grid-template-columns: 260px 1fr;
53
- width: 100%;
54
- height: 100vh;
55
- }
56
-
57
- /* Sidebar */
58
- .sidebar {
59
- background: rgba(255, 255, 255, 0.5);
60
- /* Glass-ish */
61
- backdrop-filter: blur(12px);
62
- border-right: 1px solid var(--border);
63
- padding: 24px;
64
- display: flex;
65
- flex-direction: column;
66
- height: 100%;
67
- overflow-y: auto;
68
- }
69
-
70
- .main-content {
71
- padding: 40px;
72
- overflow-y: auto;
73
- display: flex;
74
- justify-content: center;
75
- }
76
-
77
- .container {
78
- width: 100%;
79
- max-width: 800px;
80
- /* background: var(--card); Removed container bg for cleaner look in main area */
81
- /* border-radius: 24px; */
82
- /* padding: 36px; */
83
- /* border: 1px solid var(--border); */
84
- /* box-shadow: 0 40px 120px rgba(15, 23, 42, .15); */
85
- }
86
-
87
- .history-item {
88
- padding: 10px 14px;
89
- margin-bottom: 8px;
90
- background: var(--card);
91
- border: 1px solid var(--border);
92
- border-radius: 10px;
93
- cursor: pointer;
94
- font-size: 0.9rem;
95
- transition: all 0.2s;
96
- white-space: nowrap;
97
- overflow: hidden;
98
- text-overflow: ellipsis;
99
- }
100
-
101
- .history-item:hover {
102
- background: var(--primary);
103
- color: white;
104
- }
105
-
106
- .sidebar-header {
107
- margin-bottom: 20px;
108
- display: flex;
109
- justify-content: space-between;
110
- align-items: center;
111
- }
112
-
113
- .new-chat-btn {
114
- width: 100%;
115
- padding: 10px;
116
- margin-bottom: 20px;
117
- background: var(--primary);
118
- color: white;
119
- border: none;
120
- border-radius: 10px;
121
- cursor: pointer;
122
- font-weight: 600;
123
- }
124
-
125
- h1 {
126
- font-size: 2.2rem;
127
- margin: 0;
128
- font-weight: 700;
129
- background: linear-gradient(135deg, #4f46e5, #06b6d4);
130
- background-clip: text;
131
- -webkit-background-clip: text;
132
- -webkit-text-fill-color: transparent;
133
- }
134
-
135
- .subtitle {
136
- margin-top: 8px;
137
- color: var(--muted);
138
- }
139
-
140
- .card {
141
- margin-top: 28px;
142
- padding: 24px;
143
- border-radius: 18px;
144
- border: 1px solid var(--border);
145
- background: var(--card);
146
- }
147
 
148
- textarea,
149
- input[type="file"] {
150
- width: 100%;
151
- padding: 14px;
152
- border-radius: 14px;
153
- border: 1px solid var(--border);
154
- background: var(--card);
155
- color: var(--text);
156
- }
157
 
158
- textarea {
159
- min-height: 100px;
160
- }
161
 
162
- button {
163
- padding: 12px 24px;
164
- border-radius: 14px;
165
- border: none;
166
- background: var(--primary);
167
- color: white;
168
- font-weight: 600;
169
- cursor: pointer;
170
- }
171
 
172
- button.secondary {
173
- background: var(--secondary);
174
- }
175
 
176
- .row {
177
- display: flex;
178
- gap: 12px;
179
- margin-top: 12px;
180
- }
181
 
182
- .answer {
183
- margin-top: 24px;
184
- padding: 22px;
185
- border-radius: 16px;
186
- border: 1px solid var(--border);
187
- background: var(--card);
188
- line-height: 1.6;
189
- }
190
 
191
- .confidence-badge {
192
- margin-top: 12px;
193
- display: inline-block;
194
- padding: 4px 12px;
195
- border-radius: 20px;
196
- background: #dcfce7;
197
- color: #166534;
198
- font-size: 0.8rem;
199
- font-weight: 600;
200
- }
201
 
202
- .citations {
203
- margin-top: 14px;
204
- font-size: 0.85rem;
205
- color: var(--muted);
206
- }
207
- </style>
208
- </head>
209
 
210
- <body>
211
- <div class="app_layout">
212
- <div class="sidebar">
213
- <div class="sidebar-header">
214
- <h2 style="font-size: 1.2rem; margin:0;">History</h2>
215
- <button onclick="clearHistory()"
216
- style="background:none; border:none; color:var(--error); cursor:pointer; padding:0; font-size:0.8rem; width:auto; text-decoration:underline; margin:0;">Clear</button>
217
  </div>
218
- <button class="new-chat-btn" onclick="newChat()">+ New Chat</button>
219
- <div id="historyList"></div>
220
- </div>
221
- <div class="main-content">
222
- <div class="container">
223
- <h1>Gemini RAG Assistant</h1>
224
- <div class="subtitle">
225
- Upload documents · Ask questions · Get grounded answers ·
226
- <a href="/frontend/analytics.html">📊 Analytics</a>
227
- </div>
228
 
229
- <div class="card">
230
- <h3>Upload Knowledge</h3>
231
- <input type="file" id="files" multiple />
232
- <div class="row">
233
- <button onclick="upload()">Upload</button>
234
- </div>
 
 
 
 
 
 
 
 
235
 
236
- <!-- Progress Bar Container -->
237
- <div id="progressContainer" style="display: none; margin-top: 16px;">
238
- <div style="background: var(--border); border-radius: 8px; overflow: hidden; height: 10px;">
239
- <div id="progressBar"
240
- style="width: 0%; height: 100%; background: var(--primary); transition: width 0.2s;">
241
- </div>
242
- </div>
243
- <div id="progressText"
244
- style="margin-top: 6px; font-size: 0.85rem; color: var(--muted); text-align: center;">0%
245
- </div>
246
- </div>
247
 
248
- <div id="uploadStatus" style="margin-top: 12px; font-weight: 500;"></div>
 
249
  </div>
250
 
251
- <div class="card">
252
- <h3>Ask or Summarize</h3>
253
- <textarea id="question"></textarea>
254
- <div class="row">
255
- <button onclick="ask()">Ask</button>
256
- <button class="secondary" onclick="summarize()">Summarize</button>
257
- </div>
 
 
258
  </div>
259
-
260
- <div id="answerBox" class="answer" style="display:none;"></div>
261
  </div>
262
- </div>
263
- </div>
264
-
265
- <script>
266
- const MAX_FILE_SIZE = 50 * 1024 * 1024; // 50MB
267
- let threadId = sessionStorage.getItem("rag_thread_id") || crypto.randomUUID();
268
- sessionStorage.setItem("rag_thread_id", threadId);
269
-
270
- async function upload() {
271
- const fileInput = document.getElementById("files");
272
- const files = fileInput.files;
273
- const statusDiv = document.getElementById("uploadStatus");
274
- const progressContainer = document.getElementById("progressContainer");
275
- const progressBar = document.getElementById("progressBar");
276
- const progressText = document.getElementById("progressText");
277
 
278
- if (!files.length) {
279
- alert("Please select at least one file.");
280
- return;
281
- }
282
-
283
- // 1. Client-side Validation
284
- for (let f of files) {
285
- if (f.size > MAX_FILE_SIZE) {
286
- alert(`File "${f.name}" is too large (>${MAX_FILE_SIZE / 1024 / 1024}MB). Max allowed is 50MB.`);
287
- return;
288
- }
289
- }
290
-
291
- // Reset UI
292
- statusDiv.innerText = "";
293
- statusDiv.style.color = "var(--text)";
294
- progressContainer.style.display = "block";
295
- progressBar.style.width = "0%";
296
- progressText.innerText = "0%";
297
-
298
- const fd = new FormData();
299
- for (let f of files) fd.append("files", f);
300
-
301
- // 2. Upload via XMLHttpRequest for progress events
302
- const xhr = new XMLHttpRequest();
303
-
304
- xhr.upload.addEventListener("progress", (event) => {
305
- if (event.lengthComputable) {
306
- const percent = Math.round((event.loaded / event.total) * 100);
307
- progressBar.style.width = percent + "%";
308
- progressText.innerText = percent + "%";
309
- }
310
- });
311
-
312
-
313
-
314
- xhr.addEventListener("load", () => {
315
- if (xhr.status >= 200 && xhr.status < 300) {
316
- try {
317
- const data = JSON.parse(xhr.responseText);
318
- statusDiv.innerText = data.message || "Upload complete!";
319
- statusDiv.style.color = "var(--success)";
320
- progressBar.style.width = "100%";
321
- progressText.innerText = "Processing complete";
322
- fileInput.value = ""; // Clear input
323
- } catch (e) {
324
- statusDiv.innerText = "Error parsing server response.";
325
- statusDiv.style.color = "var(--error)";
326
- }
327
- } else {
328
- statusDiv.innerText = `Upload failed: ${xhr.statusText || xhr.status}`;
329
- statusDiv.style.color = "var(--error)";
330
- }
331
- });
332
-
333
- xhr.addEventListener("error", () => {
334
- statusDiv.innerText = "Network error during upload.";
335
- statusDiv.style.color = "var(--error)";
336
- });
337
-
338
- xhr.open("POST", "/upload");
339
- xhr.send(fd);
340
- }
341
-
342
- // --- HISTORY LOGIC ---
343
- function loadHistory() {
344
- const list = document.getElementById("historyList");
345
- list.innerHTML = "";
346
- const history = JSON.parse(localStorage.getItem("rag_history") || "[]");
347
-
348
- history.forEach((item, index) => {
349
- const div = document.createElement("div");
350
- div.className = "history-item";
351
- div.innerText = item.query;
352
- div.onclick = () => loadSession(index);
353
- list.appendChild(div);
354
- });
355
- }
356
-
357
- function saveToHistory(query, answerHtml) {
358
- const history = JSON.parse(localStorage.getItem("rag_history") || "[]");
359
- // Prepend new item
360
- history.unshift({ query, answerHtml, timestamp: Date.now() });
361
- // Keep max 50
362
- if (history.length > 50) history.pop();
363
-
364
- localStorage.setItem("rag_history", JSON.stringify(history));
365
- loadHistory();
366
- }
367
-
368
- function loadSession(index) {
369
- const history = JSON.parse(localStorage.getItem("rag_history") || "[]");
370
- const item = history[index];
371
- if (!item) return;
372
-
373
- document.getElementById("question").value = item.query;
374
- const box = document.getElementById("answerBox");
375
- box.style.display = "block";
376
- box.innerHTML = item.answerHtml;
377
- }
378
-
379
- function newChat() {
380
- document.getElementById("answerBox").style.display = "none";
381
- document.getElementById("answerBox").innerHTML = "";
382
- threadId = crypto.randomUUID();
383
- sessionStorage.setItem("rag_thread_id", threadId);
384
- }
385
-
386
- function clearHistory() {
387
- if (confirm("Clear all history?")) {
388
- localStorage.removeItem("rag_history");
389
- loadHistory();
390
- newChat();
391
- }
392
- }
393
-
394
- // Init
395
- loadHistory();
396
-
397
- async function ask() {
398
- const q = document.getElementById("question").value.trim();
399
- if (!q) return;
400
-
401
- const box = document.getElementById("answerBox");
402
- box.style.display = "block";
403
- box.innerHTML = "Thinking...";
404
-
405
- const res = await fetch("/ask", {
406
- method: "POST",
407
- headers: { "Content-Type": "application/json" },
408
- body: JSON.stringify({ prompt: q, thread_id: threadId })
409
- });
410
-
411
- const data = await res.json();
412
-
413
- let html = `<strong>Answer:</strong><br>${data.answer.replace(/\n/g, "<br>")}`;
414
-
415
- if (data.confidence > 0) {
416
- let label = "Low";
417
- if (data.confidence >= 0.7) label = "High";
418
- else if (data.confidence >= 0.5) label = "Medium";
419
-
420
- html += `<div class="confidence-badge">Confidence: ${label} (${Math.round(data.confidence * 100)}%)</div>`;
421
- }
422
-
423
- if (data.citations?.length) {
424
- html += "<div class='citations'><strong>Sources:</strong><ul>";
425
- data.citations.forEach(c => {
426
- html += `<li>${c.source} (Page ${c.page})</li>`;
427
- });
428
- html += "</ul></div>";
429
- }
430
-
431
- box.innerHTML = html;
432
- saveToHistory(q, html); // Save to history
433
- }
434
 
435
- function summarize() {
436
- document.getElementById("question").value = "Summarize the uploaded documents";
437
- ask();
438
- }
439
- </script>
440
  </body>
441
 
442
  </html>
 
1
  <!DOCTYPE html>
2
+ <html lang="en" data-theme="light">
3
 
4
  <head>
5
+ <meta charset="UTF-8">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
  <title>Gemini RAG Assistant</title>
 
8
 
9
+ <!-- Fonts -->
10
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet">
11
 
12
+ <!-- CSS -->
13
+ <link rel="stylesheet" href="/frontend/style.css">
14
+ </head>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15
 
16
+ <body>
 
 
 
 
 
 
 
 
17
 
18
+ <div class="app-container">
 
 
19
 
20
+ <!-- SIDEBAR -->
21
+ <aside class="sidebar">
22
+ <div class="brand">Gemini RAG</div>
 
 
 
 
 
 
23
 
24
+ <button class="new-chat-btn" onclick="newChat()">
25
+ <span>+</span> New Chat
26
+ </button>
27
 
28
+ <div class="history-list" id="historyList">
29
+ <!-- History items injected by JS -->
30
+ </div>
 
 
31
 
32
+ <div class="sidebar-footer">
33
+ <a href="/frontend/analytics.html" style="font-size: 0.9rem; color: var(--text-muted);">📊 Analytics
34
+ Dashboard</a>
35
+ <button onclick="clearHistory()"
36
+ style="background:none; border:none; color:var(--text-muted); cursor:pointer; font-size:0.8rem;">Clear
37
+ All</button>
38
+ </div>
39
+ </aside>
40
 
41
+ <!-- MAIN CONTENT -->
42
+ <main class="main-content" id="mainContent">
 
 
 
 
 
 
 
 
43
 
44
+ <!-- TOP BAR (Theme Toggle) -->
45
+ <div class="top-bar">
46
+ <button class="theme-toggle" id="themeToggleBtn" onclick="toggleTheme()" title="Toggle Dark Mode">
47
+ <!-- Icon injected by JS -->
48
+ </button>
49
+ </div>
 
50
 
51
+ <!-- CHAT AREA -->
52
+ <div class="chat-container" id="chatContainer">
53
+ <!-- Messages injected by JS -->
 
 
 
 
54
  </div>
 
 
 
 
 
 
 
 
 
 
55
 
56
+ <!-- INPUT AREA (Floating) -->
57
+ <div class="input-area glass input-card">
58
+
59
+ <div class="file-row">
60
+ <label class="file-upload-label" for="files">
61
+ <svg width="20" height="20" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
62
+ <path
63
+ d="M21.44 11.05l-9.19 9.19a6 1.83 6 1.83 0 0 1-2.59-2.59l8.6-8.6a4 1.83 4 1.83 0 0 1 2.59 2.59l-8.6 8.6a2 1.83 2 1.83 0 0 1-2.59-2.59l9.19-9.19">
64
+ </path>
65
+ </svg>
66
+ Upload Documents (PDF/TXT)
67
+ </label>
68
+ <div id="uploadStatus" style="font-size: 0.8rem; margin-left: auto;"></div>
69
+ </div>
70
 
71
+ <input type="file" id="files" multiple onchange="upload()">
 
 
 
 
 
 
 
 
 
 
72
 
73
+ <div class="progress-container">
74
+ <div class="progress-bar"></div>
75
  </div>
76
 
77
+ <div class="textarea-wrapper">
78
+ <textarea id="question" placeholder="Ask a question or type 'Summarize'..."
79
+ onkeydown="handleKey(event)"></textarea>
80
+ <button class="send-btn" onclick="ask()">
81
+ <svg width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
82
+ <line x1="22" y1="2" x2="11" y2="13"></line>
83
+ <polygon points="22 2 15 22 11 13 2 9 22 2"></polygon>
84
+ </svg>
85
+ </button>
86
  </div>
 
 
87
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
88
 
89
+ </main>
90
+ </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
91
 
92
+ <!-- JS -->
93
+ <script src="/frontend/script.js"></script>
 
 
 
94
  </body>
95
 
96
  </html>
frontend/script.js ADDED
@@ -0,0 +1,260 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const MAX_FILE_SIZE = 50 * 1024 * 1024; // 50MB
2
+ let threadId = sessionStorage.getItem("rag_thread_id") || crypto.randomUUID();
3
+ sessionStorage.setItem("rag_thread_id", threadId);
4
+
5
+ // =========================================
6
+ // THEME LOGIC
7
+ // =========================================
8
+ function initTheme() {
9
+ const savedTheme = localStorage.getItem("theme") || "light";
10
+ document.documentElement.setAttribute("data-theme", savedTheme);
11
+ updateThemeIcon(savedTheme);
12
+ }
13
+
14
+ function toggleTheme() {
15
+ const current = document.documentElement.getAttribute("data-theme");
16
+ const next = current === "light" ? "dark" : "light";
17
+ document.documentElement.setAttribute("data-theme", next);
18
+ localStorage.setItem("theme", next);
19
+ updateThemeIcon(next);
20
+ }
21
+
22
+ function updateThemeIcon(theme) {
23
+ const btn = document.getElementById("themeToggleBtn");
24
+ if (!btn) return;
25
+ // Simple Moon/Sun icons
26
+ btn.innerHTML = theme === "light"
27
+ ? `<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="5"></circle><line x1="12" y1="1" x2="12" y2="3"></line><line x1="12" y1="21" x2="12" y2="23"></line><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line><line x1="1" y1="12" x2="3" y2="12"></line><line x1="21" y1="12" x2="23" y2="12"></line><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line></svg>` // Sun
28
+ : `<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path></svg>`; // Moon
29
+ }
30
+
31
+ // =========================================
32
+ // CHAT & UPLOAD LOGIC
33
+ // =========================================
34
+
35
+ // Handle Enter key to send
36
+ function handleKey(e) {
37
+ if (e.key === "Enter" && !e.shiftKey) {
38
+ e.preventDefault();
39
+ ask();
40
+ }
41
+ }
42
+
43
+ async function upload() {
44
+ const fileInput = document.getElementById("files");
45
+ const files = fileInput.files;
46
+ const statusDiv = document.getElementById("uploadStatus");
47
+ const progressContainer = document.querySelector(".progress-container");
48
+ const progressBar = document.querySelector(".progress-bar");
49
+
50
+ if (!files.length) {
51
+ alert("Please select at least one file.");
52
+ return;
53
+ }
54
+
55
+ // Validation
56
+ for (let f of files) {
57
+ if (f.size > MAX_FILE_SIZE) {
58
+ alert(`File "${f.name}" is too large. Max allowed is 50MB.`);
59
+ return;
60
+ }
61
+ }
62
+
63
+ // Reset UI
64
+ if (statusDiv) statusDiv.innerText = "Uploading...";
65
+ if (progressContainer) {
66
+ progressContainer.style.display = "block";
67
+ progressBar.style.width = "0%";
68
+ }
69
+
70
+ const fd = new FormData();
71
+ for (let f of files) fd.append("files", f);
72
+
73
+ const xhr = new XMLHttpRequest();
74
+
75
+ xhr.upload.addEventListener("progress", (event) => {
76
+ if (event.lengthComputable) {
77
+ const percent = Math.round((event.loaded / event.total) * 100);
78
+ if (progressBar) progressBar.style.width = percent + "%";
79
+ }
80
+ });
81
+
82
+ xhr.addEventListener("load", () => {
83
+ if (xhr.status >= 200 && xhr.status < 300) {
84
+ try {
85
+ const data = JSON.parse(xhr.responseText);
86
+ if (statusDiv) {
87
+ statusDiv.innerText = data.message || "Upload complete!";
88
+ statusDiv.style.color = "var(--success, green)";
89
+ }
90
+ if (progressBar) progressBar.style.width = "100%";
91
+ fileInput.value = "";
92
+
93
+ // Add a system message to chat
94
+ addMessage("ai", `✅ **System:** ${data.message || "Files processed successfully."}`);
95
+ } catch (e) {
96
+ if (statusDiv) statusDiv.innerText = "Error parsing server response.";
97
+ }
98
+ } else {
99
+ if (statusDiv) statusDiv.innerText = "Upload failed.";
100
+ }
101
+ });
102
+
103
+ xhr.addEventListener("error", () => {
104
+ if (statusDiv) statusDiv.innerText = "Network error.";
105
+ });
106
+
107
+ xhr.open("POST", "/upload");
108
+ xhr.send(fd);
109
+ }
110
+
111
+ async function ask() {
112
+ const input = document.getElementById("question");
113
+ const q = input.value.trim();
114
+ if (!q) return;
115
+
116
+ // Clear input
117
+ input.value = "";
118
+
119
+ // Add User Message
120
+ addMessage("user", q);
121
+
122
+ // Show thinking bubble
123
+ const thinkingId = addMessage("ai", '<div class="typing-indicator">Thinking...</div>');
124
+
125
+ try {
126
+ const res = await fetch("/ask", {
127
+ method: "POST",
128
+ headers: { "Content-Type": "application/json" },
129
+ body: JSON.stringify({ prompt: q, thread_id: threadId })
130
+ });
131
+
132
+ const data = await res.json();
133
+
134
+ // Remove thinking bubble and add real answer
135
+ removeMessage(thinkingId);
136
+
137
+ let html = formatAnswer(data);
138
+ addMessage("ai", html);
139
+
140
+ saveToHistory(q, html);
141
+
142
+ } catch (e) {
143
+ removeMessage(thinkingId);
144
+ addMessage("ai", "❌ **Error:** Could not connect to the server.");
145
+ }
146
+ }
147
+
148
+ function formatAnswer(data) {
149
+ let html = data.answer
150
+ .replace(/\n/g, "<br>")
151
+ .replace(/\*\*(.*?)\*\*/g, "<strong>$1</strong>"); // Basic Markdown bold
152
+
153
+ if (data.confidence > 0) {
154
+ let label = "Low";
155
+ if (data.confidence >= 0.7) label = "High";
156
+ else if (data.confidence >= 0.5) label = "Medium";
157
+ html += `<br><div class="confidence-badge">Confidence: ${label} (${Math.round(data.confidence * 100)}%)</div>`;
158
+ }
159
+
160
+ if (data.citations?.length) {
161
+ html += "<div class='citation-box'><strong>Sources:</strong><ul>";
162
+ data.citations.forEach(c => {
163
+ html += `<li>${c.source} (Page ${c.page})</li>`;
164
+ });
165
+ html += "</ul></div>";
166
+ }
167
+ return html;
168
+ }
169
+
170
+ function summarize() {
171
+ const input = document.getElementById("question");
172
+ input.value = "Summarize the uploaded documents";
173
+ ask();
174
+ }
175
+
176
+ // =========================================
177
+ // UI HELPERS
178
+ // =========================================
179
+ function addMessage(type, html) {
180
+ const chatContainer = document.getElementById("chatContainer");
181
+ const div = document.createElement("div");
182
+ div.className = `message ${type}-message`;
183
+ div.id = "msg-" + Date.now();
184
+
185
+ div.innerHTML = `
186
+ <div class="bubble ${type}-bubble glass">
187
+ ${html}
188
+ </div>
189
+ `;
190
+
191
+ chatContainer.appendChild(div);
192
+ scrollToBottom();
193
+ return div.id;
194
+ }
195
+
196
+ function removeMessage(id) {
197
+ const el = document.getElementById(id);
198
+ if (el) el.remove();
199
+ }
200
+
201
+ function scrollToBottom() {
202
+ const container = document.getElementById("mainContent"); // The scrollable area
203
+ container.scrollTo({ top: container.scrollHeight, behavior: 'smooth' });
204
+ }
205
+
206
+ // =========================================
207
+ // HISTORY
208
+ // =========================================
209
+ function loadHistory() {
210
+ const list = document.getElementById("historyList");
211
+ if (!list) return;
212
+ list.innerHTML = "";
213
+ const history = JSON.parse(localStorage.getItem("rag_history") || "[]");
214
+
215
+ history.forEach((item, index) => {
216
+ const div = document.createElement("div");
217
+ div.className = "history-item";
218
+ div.innerText = item.query;
219
+ div.onclick = () => loadSession(item);
220
+ list.appendChild(div);
221
+ });
222
+ }
223
+
224
+ function saveToHistory(query, answerHtml) {
225
+ const history = JSON.parse(localStorage.getItem("rag_history") || "[]");
226
+ history.unshift({ query, answerHtml, timestamp: Date.now() });
227
+ if (history.length > 50) history.pop();
228
+ localStorage.setItem("rag_history", JSON.stringify(history));
229
+ loadHistory();
230
+ }
231
+
232
+ function loadSession(item) {
233
+ // Clear chat and load just this exchange? Or append?
234
+ // Let's clear for "session-like" feel
235
+ document.getElementById("chatContainer").innerHTML = "";
236
+ addMessage("user", item.query);
237
+ addMessage("ai", item.answerHtml);
238
+ }
239
+
240
+ function newChat() {
241
+ document.getElementById("chatContainer").innerHTML = "";
242
+ addMessage("ai", "Hello! I am your Agentic RAG Assistant. Upload a document or ask me anything.");
243
+ threadId = crypto.randomUUID();
244
+ sessionStorage.setItem("rag_thread_id", threadId);
245
+ }
246
+
247
+ function clearHistory() {
248
+ if (confirm("Delete all history?")) {
249
+ localStorage.removeItem("rag_history");
250
+ loadHistory();
251
+ newChat();
252
+ }
253
+ }
254
+
255
+ // Init
256
+ window.addEventListener("DOMContentLoaded", () => {
257
+ initTheme();
258
+ loadHistory();
259
+ newChat(); // Show welcome message
260
+ });
frontend/style.css ADDED
@@ -0,0 +1,363 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ :root {
2
+ /* Default (Light) Theme */
3
+ --bg-gradient: radial-gradient(circle at top left, #eef2ff, #f8fafc, #fff1f2);
4
+ --glass-bg: rgba(255, 255, 255, 0.65);
5
+ --glass-border: rgba(255, 255, 255, 0.4);
6
+ --glass-shadow: 0 8px 32px 0 rgba(31, 38, 135, 0.1);
7
+
8
+ --primary: #6366f1; /* Indigo 500 */
9
+ --primary-hover: #4f46e5;
10
+ --secondary: #0ea5e9; /* Sky 500 */
11
+ --text-main: #1e293b; /* Slate 800 */
12
+ --text-muted: #64748b; /* Slate 500 */
13
+ --accent-glow: rgba(99, 102, 241, 0.2);
14
+
15
+ --sidebar-bg: rgba(255, 255, 255, 0.5);
16
+ --input-bg: rgba(255, 255, 255, 0.8);
17
+ --user-bubble: linear-gradient(135deg, #6366f1, #8b5cf6);
18
+ --ai-bubble: rgba(255, 255, 255, 0.7);
19
+
20
+ /* Scrollbar */
21
+ --scroll-thumb: #cbd5e1;
22
+ }
23
+
24
+ [data-theme="dark"] {
25
+ /* Dark Theme - Deep Space / Cyberpunk vibes */
26
+ --bg-gradient: radial-gradient(circle at top, #0f172a, #1e1b4b);
27
+ --glass-bg: rgba(30, 41, 59, 0.6);
28
+ --glass-border: rgba(255, 255, 255, 0.08);
29
+ --glass-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.3);
30
+
31
+ --primary: #818cf8; /* Indigo 400 */
32
+ --primary-hover: #6366f1;
33
+ --secondary: #38bdf8;
34
+ --text-main: #f1f5f9; /* Slate 100 */
35
+ --text-muted: #94a3b8; /* Slate 400 */
36
+ --accent-glow: rgba(129, 140, 248, 0.15);
37
+
38
+ --sidebar-bg: rgba(15, 23, 42, 0.6);
39
+ --input-bg: rgba(30, 41, 59, 0.6);
40
+ --user-bubble: linear-gradient(135deg, #4f46e5, #7c3aed);
41
+ --ai-bubble: rgba(30, 41, 59, 0.7);
42
+
43
+ --scroll-thumb: #475569;
44
+ }
45
+
46
+ * {
47
+ box-sizing: border-box;
48
+ transition: background-color 0.3s ease, border-color 0.3s ease, color 0.3s ease;
49
+ }
50
+
51
+ body {
52
+ margin: 0;
53
+ font-family: 'Inter', sans-serif;
54
+ background: var(--bg-gradient);
55
+ background-attachment: fixed; /* Keep heavy gradient fixed */
56
+ color: var(--text-main);
57
+ height: 100vh;
58
+ overflow: hidden;
59
+ }
60
+
61
+ /* Glassmorphism Utility */
62
+ .glass {
63
+ background: var(--glass-bg);
64
+ backdrop-filter: blur(16px);
65
+ -webkit-backdrop-filter: blur(16px);
66
+ border: 1px solid var(--glass-border);
67
+ box-shadow: var(--glass-shadow);
68
+ }
69
+
70
+ /* Layout */
71
+ .app-container {
72
+ display: flex;
73
+ height: 100vh;
74
+ width: 100%;
75
+ }
76
+
77
+ /* Sidebar */
78
+ .sidebar {
79
+ width: 280px;
80
+ display: flex;
81
+ flex-direction: column;
82
+ padding: 1.5rem;
83
+ border-right: 1px solid var(--glass-border);
84
+ background: var(--sidebar-bg);
85
+ backdrop-filter: blur(12px);
86
+ z-index: 10;
87
+ }
88
+
89
+ .brand {
90
+ font-size: 1.5rem;
91
+ font-weight: 800;
92
+ background: linear-gradient(135deg, var(--primary), var(--secondary));
93
+ -webkit-background-clip: text;
94
+ -webkit-text-fill-color: transparent;
95
+ margin-bottom: 2rem;
96
+ letter-spacing: -0.5px;
97
+ }
98
+
99
+ .new-chat-btn {
100
+ background: linear-gradient(135deg, var(--primary), #4338ca);
101
+ color: white;
102
+ border: none;
103
+ padding: 0.8rem 1rem;
104
+ border-radius: 12px;
105
+ font-weight: 600;
106
+ cursor: pointer;
107
+ box-shadow: 0 4px 12px rgba(99, 102, 241, 0.3);
108
+ transition: transform 0.2s, box-shadow 0.2s;
109
+ display: flex;
110
+ align-items: center;
111
+ justify-content: center;
112
+ gap: 8px;
113
+ }
114
+
115
+ .new-chat-btn:hover {
116
+ transform: translateY(-2px);
117
+ box-shadow: 0 6px 16px rgba(99, 102, 241, 0.4);
118
+ }
119
+
120
+ .history-list {
121
+ margin-top: 1.5rem;
122
+ overflow-y: auto;
123
+ flex: 1;
124
+ }
125
+
126
+ .history-item {
127
+ padding: 0.75rem 1rem;
128
+ border-radius: 10px;
129
+ margin-bottom: 0.5rem;
130
+ cursor: pointer;
131
+ font-size: 0.9rem;
132
+ color: var(--text-muted);
133
+ white-space: nowrap;
134
+ overflow: hidden;
135
+ text-overflow: ellipsis;
136
+ border: 1px solid transparent;
137
+ }
138
+
139
+ .history-item:hover {
140
+ background: var(--accent-glow);
141
+ color: var(--primary);
142
+ border-color: var(--glass-border);
143
+ }
144
+
145
+ .sidebar-footer {
146
+ margin-top: auto;
147
+ padding-top: 1rem;
148
+ border-top: 1px solid var(--glass-border);
149
+ display: flex;
150
+ justify-content: space-between;
151
+ align-items: center;
152
+ }
153
+
154
+ /* Main Content */
155
+ .main-content {
156
+ flex: 1;
157
+ position: relative;
158
+ display: flex;
159
+ flex-direction: column;
160
+ height: 100%;
161
+ }
162
+
163
+ .top-bar {
164
+ padding: 1.5rem 2rem;
165
+ display: flex;
166
+ justify-content: flex-end; /* Just the theme toggle here */
167
+ align-items: center;
168
+ position: absolute;
169
+ top: 0;
170
+ right: 0;
171
+ width: 100%;
172
+ pointer-events: none; /* Let clicks pass through to chat */
173
+ }
174
+ .top-bar > * { pointer-events: auto; }
175
+
176
+ .theme-toggle {
177
+ background: var(--glass-bg);
178
+ border: 1px solid var(--glass-border);
179
+ padding: 0.5rem;
180
+ border-radius: 50%;
181
+ cursor: pointer;
182
+ color: var(--text-main);
183
+ display: flex;
184
+ align-items: center;
185
+ justify-content: center;
186
+ width: 40px;
187
+ height: 40px;
188
+ backdrop-filter: blur(4px);
189
+ transition: background 0.2s;
190
+ }
191
+ .theme-toggle:hover {
192
+ background: var(--accent-glow);
193
+ }
194
+
195
+ /* Chat Area */
196
+ .chat-container {
197
+ flex: 1;
198
+ overflow-y: auto;
199
+ padding: 2rem 15% 10rem 15%; /* Bottom padding for input area */
200
+ scroll-behavior: smooth;
201
+ }
202
+
203
+ /* Bubbles */
204
+ .message {
205
+ display: flex;
206
+ margin-bottom: 1.5rem;
207
+ opacity: 0;
208
+ animation: slideIn 0.3s forwards;
209
+ }
210
+ @keyframes slideIn { to { opacity: 1; transform: translateY(0); } from { opacity: 0; transform: translateY(10px); } }
211
+
212
+ .user-message { justify-content: flex-end; }
213
+ .ai-message { justify-content: flex-start; }
214
+
215
+ .bubble {
216
+ max-width: 80%;
217
+ padding: 1rem 1.5rem;
218
+ border-radius: 18px;
219
+ line-height: 1.6;
220
+ font-size: 0.95rem;
221
+ position: relative;
222
+ }
223
+
224
+ .user-bubble {
225
+ background: var(--user-bubble);
226
+ color: white;
227
+ border-bottom-right-radius: 4px;
228
+ box-shadow: 0 4px 12px rgba(99, 102, 241, 0.25);
229
+ }
230
+
231
+ .ai-bubble {
232
+ background: var(--ai-bubble);
233
+ border: 1px solid var(--glass-border);
234
+ border-bottom-left-radius: 4px;
235
+ backdrop-filter: blur(8px);
236
+ }
237
+
238
+ /* Inputs & Forms - Floating at bottom */
239
+ .input-area {
240
+ position: absolute;
241
+ bottom: 2rem;
242
+ left: 50%;
243
+ transform: translateX(-50%);
244
+ width: 70%;
245
+ max-width: 900px;
246
+ z-index: 20;
247
+ }
248
+
249
+ .input-card {
250
+ border-radius: 20px;
251
+ padding: 1rem;
252
+ display: flex;
253
+ flex-direction: column;
254
+ gap: 10px;
255
+ }
256
+
257
+ .textarea-wrapper {
258
+ position: relative;
259
+ width: 100%;
260
+ }
261
+
262
+ textarea {
263
+ width: 100%;
264
+ background: var(--input-bg);
265
+ border: 1px solid var(--glass-border);
266
+ border-radius: 14px;
267
+ padding: 1rem 3rem 1rem 1rem; /* Space for send button */
268
+ color: var(--text-main);
269
+ font-family: inherit;
270
+ resize: none;
271
+ min-height: 60px;
272
+ max-height: 200px;
273
+ box-shadow: inset 0 2px 4px rgba(0,0,0,0.05);
274
+ }
275
+ textarea:focus {
276
+ outline: none;
277
+ border-color: var(--primary);
278
+ box-shadow: 0 0 0 2px var(--accent-glow);
279
+ }
280
+
281
+ .send-btn {
282
+ position: absolute;
283
+ right: 12px;
284
+ bottom: 12px;
285
+ background: var(--primary);
286
+ color: white;
287
+ border: none;
288
+ border-radius: 8px;
289
+ width: 32px;
290
+ height: 32px;
291
+ display: flex;
292
+ align-items: center;
293
+ justify-content: center;
294
+ cursor: pointer;
295
+ transition: all 0.2s;
296
+ }
297
+ .send-btn:hover { background: var(--primary-hover); transform: scale(1.05); }
298
+
299
+ .file-row {
300
+ display: flex;
301
+ justify-content: space-between;
302
+ align-items: center;
303
+ padding: 0 0.5rem;
304
+ }
305
+
306
+ .file-upload-label {
307
+ display: flex;
308
+ align-items: center;
309
+ gap: 6px;
310
+ font-size: 0.85rem;
311
+ color: var(--text-muted);
312
+ cursor: pointer;
313
+ }
314
+ .file-upload-label:hover { color: var(--primary); }
315
+
316
+ #files { display: none; } /* Hide default file input */
317
+
318
+ /* Progress Bar */
319
+ .progress-container {
320
+ height: 4px;
321
+ background: var(--glass-border);
322
+ border-radius: 2px;
323
+ margin-top: 8px;
324
+ overflow: hidden;
325
+ display: none;
326
+ }
327
+ .progress-bar {
328
+ height: 100%;
329
+ background: var(--success, #10b981);
330
+ width: 0%;
331
+ transition: width 0.3s;
332
+ }
333
+
334
+ /* Citations & Analytics Links inside bubbles */
335
+ a { color: var(--primary); text-decoration: none; font-weight: 500; }
336
+ a:hover { text-decoration: underline; }
337
+
338
+ .confidence-badge {
339
+ display: inline-flex;
340
+ align-items: center;
341
+ padding: 2px 8px;
342
+ background: rgba(16, 185, 129, 0.1);
343
+ color: #10b981;
344
+ border-radius: 12px;
345
+ font-size: 0.75rem;
346
+ font-weight: 600;
347
+ margin-top: 8px;
348
+ }
349
+
350
+ .citation-box {
351
+ background: rgba(0,0,0,0.03);
352
+ border-left: 3px solid var(--primary);
353
+ padding: 8px 12px;
354
+ margin-top: 8px;
355
+ font-size: 0.85rem;
356
+ border-radius: 0 8px 8px 0;
357
+ }
358
+
359
+ /* Scrollbar Styling */
360
+ ::-webkit-scrollbar { width: 8px; }
361
+ ::-webkit-scrollbar-track { background: transparent; }
362
+ ::-webkit-scrollbar-thumb { background: var(--scroll-thumb); border-radius: 4px; }
363
+ ::-webkit-scrollbar-thumb:hover { background: var(--text-muted); }
rag_eval_logs.jsonl CHANGED
@@ -61,3 +61,23 @@
61
  {"timestamp": 1768121326.683464, "query": "what is the location ?", "retrieved_count": 5, "confidence": 1.0, "answer_known": true}
62
  {"timestamp": 1768121412.7319663, "query": "what is the conditions for OSHC?", "retrieved_count": 5, "confidence": 1.0, "answer_known": true}
63
  {"timestamp": 1768207382.1637495, "query": "what is application ID number?", "retrieved_count": 5, "confidence": 0.95, "answer_known": false}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
61
  {"timestamp": 1768121326.683464, "query": "what is the location ?", "retrieved_count": 5, "confidence": 1.0, "answer_known": true}
62
  {"timestamp": 1768121412.7319663, "query": "what is the conditions for OSHC?", "retrieved_count": 5, "confidence": 1.0, "answer_known": true}
63
  {"timestamp": 1768207382.1637495, "query": "what is application ID number?", "retrieved_count": 5, "confidence": 0.95, "answer_known": false}
64
+ {"timestamp": 1768315860.3629532, "query": "Compare the OSHC requirements in this letter to the current 2026 Australian student visa health insurance laws.", "retrieved_count": 4, "confidence": 0.9, "answer_known": true, "source_type": "internal_pdf"}
65
+ {"timestamp": 1768315871.1952653, "query": "Compare the OSHC requirements in this letter to the current 2026 Australian student visa health insurance laws.", "retrieved_count": 4, "confidence": 0.9, "answer_known": true, "source_type": "internal_pdf"}
66
+ {"timestamp": 1768315930.346082, "query": "Compare the OSHC requirements in this letter to the current 2026 Australian student visa health insurance laws.", "retrieved_count": 1, "confidence": 0.7, "answer_known": true, "source_type": "external_web"}
67
+ {"timestamp": 1768315944.8304691, "query": "Compare the OSHC requirements in this letter to the current 2026 Australian student visa health insurance laws.", "retrieved_count": 4, "confidence": 0.9, "answer_known": true, "source_type": "internal_pdf"}
68
+ {"timestamp": 1768315957.5130162, "query": "Compare the OSHC requirements in this letter to the current 2026 Australian student visa health insurance laws.", "retrieved_count": 4, "confidence": 0.9, "answer_known": true, "source_type": "internal_pdf"}
69
+ {"timestamp": 1768315975.0261414, "query": "Compare the OSHC requirements in this letter to the current 2026 Australian student visa health insurance laws.", "retrieved_count": 1, "confidence": 0.7, "answer_known": true, "source_type": "external_web"}
70
+ {"timestamp": 1768315993.1562846, "query": "Compare the OSHC requirements in this letter to the current 2026 Australian student visa health insurance laws.", "retrieved_count": 4, "confidence": 0.9, "answer_known": true, "source_type": "internal_pdf"}
71
+ {"timestamp": 1768316005.2227218, "query": "Compare the OSHC requirements in this letter to the current 2026 Australian student visa health insurance laws.", "retrieved_count": 1, "confidence": 0.7, "answer_known": true, "source_type": "external_web"}
72
+ {"timestamp": 1768316027.8866844, "query": "Compare the OSHC requirements in this letter to the current 2026 Australian student visa health insurance laws.", "retrieved_count": 1, "confidence": 0.7, "answer_known": true, "source_type": "external_web"}
73
+ {"timestamp": 1768316040.887511, "query": "Compare the OSHC requirements in this letter to the current 2026 Australian student visa health insurance laws.", "retrieved_count": 4, "confidence": 0.9, "answer_known": true, "source_type": "internal_pdf"}
74
+ {"timestamp": 1768316051.8234782, "query": "\"What is the specific refund percentage for a withdrawal 2 weeks before the start date?\"", "retrieved_count": 4, "confidence": 0.9, "answer_known": true, "source_type": "internal_pdf"}
75
+ {"timestamp": 1768316054.490359, "query": "\"What is the specific refund percentage for a withdrawal 2 weeks before the start date?\"", "retrieved_count": 4, "confidence": 0.9, "answer_known": true, "source_type": "internal_pdf"}
76
+ {"timestamp": 1768316058.364325, "query": "\"What is the specific refund percentage for a withdrawal 2 weeks before the start date?\"", "retrieved_count": 4, "confidence": 0.9, "answer_known": true, "source_type": "internal_pdf"}
77
+ {"timestamp": 1768316061.0153518, "query": "\"What is the specific refund percentage for a withdrawal 2 weeks before the start date?\"", "retrieved_count": 4, "confidence": 0.9, "answer_known": true, "source_type": "internal_pdf"}
78
+ {"timestamp": 1768316070.7566097, "query": "\"What is the specific refund percentage for a withdrawal 2 weeks before the start date?\"", "retrieved_count": 4, "confidence": 0.9, "answer_known": true, "source_type": "internal_pdf"}
79
+ {"timestamp": 1768316080.946659, "query": "\"What is the specific refund percentage for a withdrawal 2 weeks before the start date?\"", "retrieved_count": 4, "confidence": 0.9, "answer_known": true, "source_type": "internal_pdf"}
80
+ {"timestamp": 1768316094.0294836, "query": "\"What is the specific refund percentage for a withdrawal 2 weeks before the start date?\"", "retrieved_count": 4, "confidence": 0.9, "answer_known": true, "source_type": "internal_pdf"}
81
+ {"timestamp": 1768316097.8382766, "query": "\"What is the specific refund percentage for a withdrawal 2 weeks before the start date?\"", "retrieved_count": 4, "confidence": 0.9, "answer_known": true, "source_type": "internal_pdf"}
82
+ {"timestamp": 1768316102.216011, "query": "\"What is the specific refund percentage for a withdrawal 2 weeks before the start date?\"", "retrieved_count": 4, "confidence": 0.9, "answer_known": true, "source_type": "internal_pdf"}
83
+ {"timestamp": 1768316113.0092435, "query": "\"What is the specific refund percentage for a withdrawal 2 weeks before the start date?\"", "retrieved_count": 4, "confidence": 0.9, "answer_known": true, "source_type": "internal_pdf"}
requirements.txt CHANGED
@@ -9,3 +9,4 @@ numpy
9
  python-multipart
10
 
11
  rank_bm25
 
 
9
  python-multipart
10
 
11
  rank_bm25
12
+ tavily-python