ntdservices commited on
Commit
0f84df9
Β·
verified Β·
1 Parent(s): 856c6b8

Update templates/index.html

Browse files
Files changed (1) hide show
  1. templates/index.html +151 -40
templates/index.html CHANGED
@@ -20,7 +20,6 @@
20
  --shadow: 0 10px 30px rgba(0,0,0,.35), 0 2px 8px rgba(0,0,0,.25);
21
  }
22
 
23
- /* Background */
24
  html,body{height:100%;}
25
  body{
26
  margin:0;
@@ -33,7 +32,6 @@
33
  line-height:1.45;
34
  }
35
 
36
- /* Layout */
37
  .wrap{
38
  max-width: 980px;
39
  margin: 48px auto 120px;
@@ -49,7 +47,6 @@
49
  box-shadow: inset 0 0 0 1px #ffffff08;
50
  }
51
 
52
- /* Cards */
53
  .card{
54
  background: linear-gradient(180deg, #ffffff08, #00000022);
55
  border: 1px solid #ffffff1c;
@@ -66,12 +63,8 @@
66
  .card .hdr h2{margin:0; font-size:18px; font-weight:700; color:#fff;}
67
  .card .body{ padding:18px; }
68
 
69
- /* Uploader */
70
- .uploader{
71
- display:grid; gap:14px;
72
- }
73
 
74
- /* Hide original input but keep for logic */
75
  #fileUpload{
76
  position: absolute;
77
  width:1px; height:1px; overflow:hidden; clip:rect(0 0 0 0);
@@ -105,7 +98,6 @@
105
 
106
  .row{ display:flex; flex-wrap:wrap; gap:10px; align-items:center; }
107
 
108
- /* Buttons */
109
  .btn{
110
  --btn-bg: #2331a6;
111
  --btn-fg: #eaf0ff;
@@ -121,7 +113,6 @@
121
  .btn.secondary{ --btn-bg:#1a213f; --btn-fg:#dbe2ff; }
122
  .btn.danger { --btn-bg:#4a1020; --btn-fg:#ffd7df; border-color:#ff6b6b44; }
123
 
124
- /* Progress + status */
125
  progress{
126
  width: 320px; height: 14px; border-radius: 999px; overflow:hidden; vertical-align: middle;
127
  background: #293079;
@@ -133,14 +124,12 @@
133
  }
134
  #uploadStatus, #searchStatus{ margin-left:10px; font-weight:700; color:#d5dcff; }
135
 
136
- /* Client list chips */
137
  #uploadedListClient ul{ list-style:none; padding:0; margin:8px 0 0; display:flex; flex-wrap:wrap; gap:8px;}
138
  #uploadedListClient li{
139
  padding:6px 10px; border-radius:999px; font-size:12px; color:#dfe5ff;
140
  background: linear-gradient(180deg,#ffffff0a,#00000025); border:1px solid #ffffff1a;
141
  }
142
 
143
- /* Forms */
144
  label{ color:#cdd4ff; font-weight:600; letter-spacing:.2px; }
145
  textarea, input[type="number"]{
146
  width:100%; box-sizing:border-box; color:#fff; background:#0d1333; border:1px solid #ffffff22;
@@ -151,18 +140,52 @@
151
 
152
  hr{ border:none; height:1px; background:#ffffff1a; margin:26px 0; }
153
 
154
- /* Results */
155
  ol{ padding-left: 22px; }
156
- ol li{ margin: 10px 0; }
157
  a{ color: var(--accent2); }
158
 
159
- /* Small helpers */
160
  .muted{ color:var(--muted); font-size:13px; }
161
  .cluster{ display:flex; flex-wrap:wrap; gap:10px; align-items:center; }
162
 
163
- @media (max-width:700px){
164
- .row{ gap:8px; }
165
- progress{ width:100%; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
166
  }
167
  </style>
168
  </head>
@@ -187,10 +210,7 @@
187
  </div>
188
  <div class="body">
189
  <div class="uploader">
190
- <!-- Original input kept for logic -->
191
  <input type="file" id="fileUpload" multiple>
192
-
193
- <!-- Drop zone (click also opens the hidden input) -->
194
  <div id="dropZone" class="drop" tabindex="0" role="button" aria-label="Drop files here or click to browse">
195
  <div class="big">Drag & Drop files here</div>
196
  <div class="small">.txt, .pdf supported</div>
@@ -202,7 +222,6 @@
202
  <span id="uploadStatus"></span>
203
  </div>
204
 
205
- <!-- This shows uploaded files immediately after form submission (server-rendered) -->
206
  {% if uploaded_filenames %}
207
  <h4 style="margin:14px 0 6px;">Uploaded Files</h4>
208
  <ul>
@@ -212,7 +231,6 @@
212
  </ul>
213
  {% endif %}
214
 
215
- <!-- Client-side list -->
216
  <div id="uploadedListClient" style="display:none; margin-top:10px;"></div>
217
  </div>
218
  </div>
@@ -226,7 +244,6 @@
226
  <h2 id="ret">Search your uploads</h2>
227
  </div>
228
  <div class="body">
229
- <!-- ⬇️ the hidden sid travels with every form submit -->
230
  <form method="post">
231
  <input type="hidden" name="sid" value="{{ sid }}">
232
 
@@ -262,8 +279,11 @@
262
 
263
  <h3 style="margin-top:18px;">Matching Paragraphs</h3>
264
  <ol>
265
- {% for para in results %}
266
- <li><p>{{ para }}</p></li>
 
 
 
267
  {% endfor %}
268
  </ol>
269
  {% endif %}
@@ -271,11 +291,26 @@
271
  </section>
272
  </div>
273
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
274
  <script>
275
  const SID = "{{ sid }}";
276
  let uploadedNames = [];
 
277
 
278
- // Keep original input-change logic (unchanged)
279
  document.getElementById("fileUpload").addEventListener("change", function () {
280
  const files = this.files;
281
  if (files.length === 0) return;
@@ -317,7 +352,7 @@
317
  xhr.send(formData);
318
  });
319
 
320
- // Show client-side list (unchanged IDs / flow)
321
  document.getElementById("showFilesBtn").addEventListener("click", function () {
322
  const listDiv = document.getElementById("uploadedListClient");
323
  listDiv.innerHTML = "";
@@ -333,28 +368,22 @@
333
  listDiv.style.display = "block";
334
  });
335
 
336
- // Maintain existing search progress behavior
337
  document.querySelector('form[method="post"]').addEventListener("submit", function () {
338
  const progressBar = document.getElementById("searchProgress");
339
  const statusText = document.getElementById("searchStatus");
340
-
341
- // Show spinner + message (indeterminate)
342
  progressBar.removeAttribute("value");
343
  progressBar.style.display = "inline-block";
344
  statusText.textContent = "πŸ” Searching...";
345
  });
346
 
347
- // New: Drag-and-drop zone (reuses identical upload flow; does not alter existing logic)
348
  const dropZone = document.getElementById('dropZone');
349
  const hiddenInput = document.getElementById('fileUpload');
350
-
351
- // Clicking the zone opens the hidden input (so your original listener runs)
352
  dropZone.addEventListener('click', () => hiddenInput.click());
353
  dropZone.addEventListener('keypress', (e) => {
354
  if(e.key === 'Enter' || e.key === ' '){ e.preventDefault(); hiddenInput.click(); }
355
  });
356
-
357
- // Visual state
358
  ['dragenter','dragover'].forEach(evt => {
359
  dropZone.addEventListener(evt, e => {
360
  e.preventDefault(); e.stopPropagation();
@@ -364,14 +393,11 @@
364
  ['dragleave','dragend','drop'].forEach(evt => {
365
  dropZone.addEventListener(evt, () => dropZone.classList.remove('dragover'));
366
  });
367
-
368
- // Handle drop by sending the exact same request shape as the original change-handler
369
  dropZone.addEventListener('drop', (e) => {
370
  e.preventDefault(); e.stopPropagation();
371
  const files = e.dataTransfer.files;
372
  if(!files || files.length === 0) return;
373
 
374
- // Note: we duplicate the same logic here to avoid modifying your original handler.
375
  const formData = new FormData();
376
  for (const file of files) {
377
  formData.append("file", file);
@@ -408,6 +434,91 @@
408
 
409
  xhr.send(formData);
410
  });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
411
  </script>
412
  </body>
413
- </html>
 
20
  --shadow: 0 10px 30px rgba(0,0,0,.35), 0 2px 8px rgba(0,0,0,.25);
21
  }
22
 
 
23
  html,body{height:100%;}
24
  body{
25
  margin:0;
 
32
  line-height:1.45;
33
  }
34
 
 
35
  .wrap{
36
  max-width: 980px;
37
  margin: 48px auto 120px;
 
47
  box-shadow: inset 0 0 0 1px #ffffff08;
48
  }
49
 
 
50
  .card{
51
  background: linear-gradient(180deg, #ffffff08, #00000022);
52
  border: 1px solid #ffffff1c;
 
63
  .card .hdr h2{margin:0; font-size:18px; font-weight:700; color:#fff;}
64
  .card .body{ padding:18px; }
65
 
66
+ .uploader{ display:grid; gap:14px; }
 
 
 
67
 
 
68
  #fileUpload{
69
  position: absolute;
70
  width:1px; height:1px; overflow:hidden; clip:rect(0 0 0 0);
 
98
 
99
  .row{ display:flex; flex-wrap:wrap; gap:10px; align-items:center; }
100
 
 
101
  .btn{
102
  --btn-bg: #2331a6;
103
  --btn-fg: #eaf0ff;
 
113
  .btn.secondary{ --btn-bg:#1a213f; --btn-fg:#dbe2ff; }
114
  .btn.danger { --btn-bg:#4a1020; --btn-fg:#ffd7df; border-color:#ff6b6b44; }
115
 
 
116
  progress{
117
  width: 320px; height: 14px; border-radius: 999px; overflow:hidden; vertical-align: middle;
118
  background: #293079;
 
124
  }
125
  #uploadStatus, #searchStatus{ margin-left:10px; font-weight:700; color:#d5dcff; }
126
 
 
127
  #uploadedListClient ul{ list-style:none; padding:0; margin:8px 0 0; display:flex; flex-wrap:wrap; gap:8px;}
128
  #uploadedListClient li{
129
  padding:6px 10px; border-radius:999px; font-size:12px; color:#dfe5ff;
130
  background: linear-gradient(180deg,#ffffff0a,#00000025); border:1px solid #ffffff1a;
131
  }
132
 
 
133
  label{ color:#cdd4ff; font-weight:600; letter-spacing:.2px; }
134
  textarea, input[type="number"]{
135
  width:100%; box-sizing:border-box; color:#fff; background:#0d1333; border:1px solid #ffffff22;
 
140
 
141
  hr{ border:none; height:1px; background:#ffffff1a; margin:26px 0; }
142
 
 
143
  ol{ padding-left: 22px; }
144
+ ol li{ margin: 12px 0; }
145
  a{ color: var(--accent2); }
146
 
 
147
  .muted{ color:var(--muted); font-size:13px; }
148
  .cluster{ display:flex; flex-wrap:wrap; gap:10px; align-items:center; }
149
 
150
+ /* Sidebar for context */
151
+ .sidebar-backdrop{
152
+ position: fixed;
153
+ inset: 0;
154
+ background: rgba(0,0,0,.35);
155
+ opacity: 0;
156
+ pointer-events: none;
157
+ transition: .18s ease;
158
+ z-index: 60;
159
+ }
160
+ .sidebar{
161
+ position: fixed;
162
+ top: 0; right: 0; bottom: 0;
163
+ width: min(520px, 90vw);
164
+ background: linear-gradient(180deg, #0e1330, #0a0f28);
165
+ border-left: 1px solid #ffffff22;
166
+ box-shadow: -16px 0 40px rgba(0,0,0,.35);
167
+ transform: translateX(100%);
168
+ transition: transform .22s ease;
169
+ z-index: 70;
170
+ display: grid;
171
+ grid-template-rows: auto 1fr;
172
+ }
173
+ .sidebar.open{ transform: translateX(0); }
174
+ .sidebar-backdrop.open{ opacity: 1; pointer-events: auto; }
175
+ .sidebar .sbar-hdr{
176
+ display:flex; align-items:center; justify-content:space-between; gap:10px;
177
+ padding:14px 16px; border-bottom:1px solid #ffffff1a;
178
+ }
179
+ .sidebar .sbar-hdr h3{ margin:0; font-size:16px; }
180
+ .sidebar .sbar-body{
181
+ overflow: auto; padding: 16px;
182
+ }
183
+ .ctx-p{ margin: 12px 0; padding: 10px 12px; border-radius: 12px; background: #ffffff06; border:1px solid #ffffff14; }
184
+ .ctx-p.hl{ outline: 2px solid #61e7ff; box-shadow: 0 0 0 3px #61e7ff22; }
185
+
186
+ .smallbtn{
187
+ font-size:12px; padding:6px 8px; border-radius:10px;
188
+ background: #1a213f; color:#dbe2ff; border:1px solid #ffffff18; cursor:pointer;
189
  }
190
  </style>
191
  </head>
 
210
  </div>
211
  <div class="body">
212
  <div class="uploader">
 
213
  <input type="file" id="fileUpload" multiple>
 
 
214
  <div id="dropZone" class="drop" tabindex="0" role="button" aria-label="Drop files here or click to browse">
215
  <div class="big">Drag & Drop files here</div>
216
  <div class="small">.txt, .pdf supported</div>
 
222
  <span id="uploadStatus"></span>
223
  </div>
224
 
 
225
  {% if uploaded_filenames %}
226
  <h4 style="margin:14px 0 6px;">Uploaded Files</h4>
227
  <ul>
 
231
  </ul>
232
  {% endif %}
233
 
 
234
  <div id="uploadedListClient" style="display:none; margin-top:10px;"></div>
235
  </div>
236
  </div>
 
244
  <h2 id="ret">Search your uploads</h2>
245
  </div>
246
  <div class="body">
 
247
  <form method="post">
248
  <input type="hidden" name="sid" value="{{ sid }}">
249
 
 
279
 
280
  <h3 style="margin-top:18px;">Matching Paragraphs</h3>
281
  <ol>
282
+ {% for r in results %}
283
+ <li>
284
+ <p>{{ r.text }}</p>
285
+ <button type="button" class="smallbtn" onclick="openContext({{ r.idx }})">πŸ”Ž View context</button>
286
+ </li>
287
  {% endfor %}
288
  </ol>
289
  {% endif %}
 
291
  </section>
292
  </div>
293
 
294
+ <!-- Sidebar + backdrop -->
295
+ <div id="sidebarBackdrop" class="sidebar-backdrop"></div>
296
+ <aside id="sidebar" class="sidebar" aria-hidden="true">
297
+ <div class="sbar-hdr">
298
+ <h3>Paragraph context</h3>
299
+ <div style="display:flex; gap:8px; align-items:center;">
300
+ <button class="smallbtn" id="expandLess" title="Show less context">βˆ’</button>
301
+ <button class="smallbtn" id="expandMore" title="Show more context">+</button>
302
+ <button class="smallbtn" id="closeSidebar">βœ•</button>
303
+ </div>
304
+ </div>
305
+ <div id="sidebarBody" class="sbar-body"></div>
306
+ </aside>
307
+
308
  <script>
309
  const SID = "{{ sid }}";
310
  let uploadedNames = [];
311
+ let currentWindow = 3;
312
 
313
+ // Upload (unchanged behavior)
314
  document.getElementById("fileUpload").addEventListener("change", function () {
315
  const files = this.files;
316
  if (files.length === 0) return;
 
352
  xhr.send(formData);
353
  });
354
 
355
+ // Show client-side list
356
  document.getElementById("showFilesBtn").addEventListener("click", function () {
357
  const listDiv = document.getElementById("uploadedListClient");
358
  listDiv.innerHTML = "";
 
368
  listDiv.style.display = "block";
369
  });
370
 
371
+ // Search progress (unchanged)
372
  document.querySelector('form[method="post"]').addEventListener("submit", function () {
373
  const progressBar = document.getElementById("searchProgress");
374
  const statusText = document.getElementById("searchStatus");
 
 
375
  progressBar.removeAttribute("value");
376
  progressBar.style.display = "inline-block";
377
  statusText.textContent = "πŸ” Searching...";
378
  });
379
 
380
+ // Drag & drop (unchanged)
381
  const dropZone = document.getElementById('dropZone');
382
  const hiddenInput = document.getElementById('fileUpload');
 
 
383
  dropZone.addEventListener('click', () => hiddenInput.click());
384
  dropZone.addEventListener('keypress', (e) => {
385
  if(e.key === 'Enter' || e.key === ' '){ e.preventDefault(); hiddenInput.click(); }
386
  });
 
 
387
  ['dragenter','dragover'].forEach(evt => {
388
  dropZone.addEventListener(evt, e => {
389
  e.preventDefault(); e.stopPropagation();
 
393
  ['dragleave','dragend','drop'].forEach(evt => {
394
  dropZone.addEventListener(evt, () => dropZone.classList.remove('dragover'));
395
  });
 
 
396
  dropZone.addEventListener('drop', (e) => {
397
  e.preventDefault(); e.stopPropagation();
398
  const files = e.dataTransfer.files;
399
  if(!files || files.length === 0) return;
400
 
 
401
  const formData = new FormData();
402
  for (const file of files) {
403
  formData.append("file", file);
 
434
 
435
  xhr.send(formData);
436
  });
437
+
438
+ // ── Context sidebar logic ──────────────────────────────────────────────
439
+ const sidebar = document.getElementById('sidebar');
440
+ const sidebarBody = document.getElementById('sidebarBody');
441
+ const backdrop = document.getElementById('sidebarBackdrop');
442
+ const btnClose = document.getElementById('closeSidebar');
443
+ const btnMore = document.getElementById('expandMore');
444
+ const btnLess = document.getElementById('expandLess');
445
+
446
+ function openSidebar(){
447
+ sidebar.classList.add('open');
448
+ backdrop.classList.add('open');
449
+ sidebar.setAttribute('aria-hidden', 'false');
450
+ }
451
+ function closeSidebar(){
452
+ sidebar.classList.remove('open');
453
+ backdrop.classList.remove('open');
454
+ sidebar.setAttribute('aria-hidden', 'true');
455
+ sidebarBody.innerHTML = '';
456
+ }
457
+ btnClose.addEventListener('click', closeSidebar);
458
+ backdrop.addEventListener('click', closeSidebar);
459
+
460
+ async function fetchContext(idx, windowSize){
461
+ const res = await fetch(`/api/context?sid=${encodeURIComponent(SID)}&idx=${idx}&window=${windowSize}`);
462
+ if(!res.ok){
463
+ const t = await res.text();
464
+ throw new Error(t || 'Failed to fetch context');
465
+ }
466
+ return res.json();
467
+ }
468
+
469
+ function renderContext(ctxJson){
470
+ sidebarBody.innerHTML = '';
471
+ ctxJson.paras.forEach((p, i) => {
472
+ const div = document.createElement('div');
473
+ div.className = 'ctx-p' + (i === ctxJson.center ? ' hl' : '');
474
+ div.innerText = p;
475
+ if(i === ctxJson.center){
476
+ div.id = 'centerPara';
477
+ }
478
+ sidebarBody.appendChild(div);
479
+ });
480
+ // Scroll the highlighted one into view
481
+ setTimeout(() => {
482
+ const el = document.getElementById('centerPara');
483
+ if(el){ el.scrollIntoView({behavior:'smooth', block:'center'}); }
484
+ }, 0);
485
+ }
486
+
487
+ let lastIdx = null;
488
+
489
+ window.openContext = async function(idx){
490
+ try{
491
+ lastIdx = idx;
492
+ const data = await fetchContext(idx, currentWindow);
493
+ renderContext(data);
494
+ openSidebar();
495
+ }catch(err){
496
+ alert('Error: ' + err.message);
497
+ }
498
+ }
499
+
500
+ btnMore.addEventListener('click', async () => {
501
+ if(lastIdx === null) return;
502
+ currentWindow = Math.min(currentWindow + 2, 20);
503
+ const data = await fetchContext(lastIdx, currentWindow);
504
+ renderContext(data);
505
+ });
506
+
507
+ btnLess.addEventListener('click', async () => {
508
+ if(lastIdx === null) return;
509
+ currentWindow = Math.max(1, currentWindow - 2);
510
+ const data = await fetchContext(lastIdx, currentWindow);
511
+ renderContext(data);
512
+ });
513
+
514
+ // Maintain existing search progress behavior
515
+ document.querySelector('form[method="post"]').addEventListener("submit", function () {
516
+ const progressBar = document.getElementById("searchProgress");
517
+ const statusText = document.getElementById("searchStatus");
518
+ progressBar.removeAttribute("value");
519
+ progressBar.style.display = "inline-block";
520
+ statusText.textContent = "πŸ” Searching...";
521
+ });
522
  </script>
523
  </body>
524
+ </html>