Ash-211 commited on
Commit
eaa16d9
·
1 Parent(s): e296091

Fix email validation, re-index on delete, lazy-load DocumentIndex

Browse files
backend/main.py CHANGED
@@ -150,6 +150,13 @@ class UserRegister(BaseModel):
150
 
151
  @app.post("/register")
152
  async def register(user: UserRegister):
 
 
 
 
 
 
 
153
  conn = sqlite3.connect(DB_PATH)
154
  cursor = conn.cursor()
155
 
@@ -481,8 +488,10 @@ async def delete_document(filename: str, username: str = Depends(get_current_use
481
  if not os.path.exists(file_path):
482
  raise HTTPException(status_code=404, detail="File not found")
483
 
 
484
  os.remove(file_path)
485
 
 
486
  indices_dir = os.path.join(user_dir, "indices")
487
  processed_dir = os.path.join(user_dir, "processed")
488
  if os.path.exists(indices_dir):
@@ -490,6 +499,84 @@ async def delete_document(filename: str, username: str = Depends(get_current_use
490
  if os.path.exists(processed_dir):
491
  shutil.rmtree(processed_dir)
492
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
493
  return {"message": f"Deleted {filename}"}
494
 
495
  # --- Conversation Management ---
 
150
 
151
  @app.post("/register")
152
  async def register(user: UserRegister):
153
+ # Validate email format
154
+ if user.email:
155
+ import re
156
+ email_pattern = re.compile(r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$')
157
+ if not email_pattern.match(user.email):
158
+ raise HTTPException(status_code=400, detail="Invalid email address")
159
+
160
  conn = sqlite3.connect(DB_PATH)
161
  cursor = conn.cursor()
162
 
 
488
  if not os.path.exists(file_path):
489
  raise HTTPException(status_code=404, detail="File not found")
490
 
491
+ # Remove the target file
492
  os.remove(file_path)
493
 
494
+ # Wipe old indices and processed images
495
  indices_dir = os.path.join(user_dir, "indices")
496
  processed_dir = os.path.join(user_dir, "processed")
497
  if os.path.exists(indices_dir):
 
499
  if os.path.exists(processed_dir):
500
  shutil.rmtree(processed_dir)
501
 
502
+ # Rebuild indices from remaining files
503
+ remaining_files = []
504
+ if os.path.exists(user_raw_dir):
505
+ remaining_files = [f for f in os.listdir(user_raw_dir) if os.path.isfile(os.path.join(user_raw_dir, f))]
506
+
507
+ if remaining_files:
508
+ USER_PROCESSED_DIR = os.path.join(user_dir, "processed", "images")
509
+ USER_INDICES_DIR = os.path.join(user_dir, "indices")
510
+ USER_CHUNK_INDEX_PATH = os.path.join(USER_INDICES_DIR, "chunks")
511
+ USER_DOC_INDEX_PATH = os.path.join(USER_INDICES_DIR, "docs")
512
+ USER_IMAGE_INDEX_PATH = os.path.join(USER_INDICES_DIR, "images")
513
+
514
+ rebuild_chunk_index = ChunkIndex()
515
+ rebuild_doc_index = DocumentIndex()
516
+ rebuild_image_store = ImageVectorStore()
517
+
518
+ AUDIO_EXTENSIONS = {".mp3", ".wav", ".m4a", ".ogg", ".flac"}
519
+
520
+ for remaining_file in remaining_files:
521
+ remaining_path = os.path.join(user_raw_dir, remaining_file)
522
+ file_ext = os.path.splitext(remaining_file)[1].lower()
523
+
524
+ try:
525
+ if file_ext in AUDIO_EXTENSIONS:
526
+ transcript = ingest_audio(remaining_path)
527
+ if transcript and len(transcript.strip()) > 50:
528
+ chunk_size = 500
529
+ words = transcript.split()
530
+ current_chunk = ""
531
+ audio_chunks = []
532
+ for word in words:
533
+ if len(current_chunk) + len(word) + 1 > chunk_size and current_chunk:
534
+ audio_chunks.append(current_chunk.strip())
535
+ current_chunk = word
536
+ else:
537
+ current_chunk += " " + word
538
+ if current_chunk.strip():
539
+ audio_chunks.append(current_chunk.strip())
540
+ rebuild_doc_index.add_document(transcript, remaining_file)
541
+ rebuild_chunk_index.add_chunks(remaining_file, audio_chunks)
542
+ else:
543
+ text_chunks, _ = ingest_pdf(remaining_path, USER_PROCESSED_DIR)
544
+ from collections import defaultdict
545
+ chunks_by_source = defaultdict(list)
546
+ for chunk in text_chunks:
547
+ chunks_by_source[chunk["source"]].append(chunk["text"])
548
+ for source, chunks in chunks_by_source.items():
549
+ slide_chunks = []
550
+ full_text = ""
551
+ for text in chunks:
552
+ text = text.strip()
553
+ if len(text) > 50:
554
+ full_text += text + "\n"
555
+ slide_chunks.append(text)
556
+ if slide_chunks:
557
+ rebuild_doc_index.add_document(full_text, source)
558
+ rebuild_chunk_index.add_chunks(source, slide_chunks)
559
+
560
+ all_images = glob.glob(os.path.join(USER_PROCESSED_DIR, "*.png"))
561
+ pdf_basename = os.path.basename(remaining_path)
562
+ new_images = [img for img in all_images if pdf_basename in os.path.basename(img)]
563
+ image_metadata = []
564
+ for p in new_images:
565
+ try:
566
+ parts = p.split("_page_")
567
+ page_num = int(parts[1].split("_img_")[0]) if len(parts) > 1 else 0
568
+ except:
569
+ page_num = 0
570
+ image_metadata.append({"image_path": p, "page": page_num})
571
+ if new_images:
572
+ rebuild_image_store.add_images(new_images, image_metadata)
573
+ except Exception as e:
574
+ print(f"[{username}] Warning: failed to re-index {remaining_file}: {e}")
575
+
576
+ rebuild_chunk_index.save_local(USER_CHUNK_INDEX_PATH)
577
+ rebuild_doc_index.save_local(USER_DOC_INDEX_PATH)
578
+ rebuild_image_store.save_local(USER_IMAGE_INDEX_PATH)
579
+
580
  return {"message": f"Deleted {filename}"}
581
 
582
  # --- Conversation Management ---
backend/vectorstore/document_index.py CHANGED
@@ -1,15 +1,21 @@
1
  import faiss
2
  import numpy as np
3
- from sentence_transformers import SentenceTransformer
4
  import os
5
  import pickle
6
 
7
  class DocumentIndex:
8
  def __init__(self):
9
- self.model = SentenceTransformer("all-MiniLM-L6-v2")
10
  self.index = faiss.IndexFlatIP(384)
11
  self.metadata = []
12
 
 
 
 
 
 
 
 
13
  def add_document(self, full_text, source):
14
  embedding = self.model.encode([full_text])
15
  embedding = np.array(embedding).astype("float32")
 
1
  import faiss
2
  import numpy as np
 
3
  import os
4
  import pickle
5
 
6
  class DocumentIndex:
7
  def __init__(self):
8
+ self._model = None
9
  self.index = faiss.IndexFlatIP(384)
10
  self.metadata = []
11
 
12
+ @property
13
+ def model(self):
14
+ if self._model is None:
15
+ from sentence_transformers import SentenceTransformer
16
+ self._model = SentenceTransformer("all-MiniLM-L6-v2")
17
+ return self._model
18
+
19
  def add_document(self, full_text, source):
20
  embedding = self.model.encode([full_text])
21
  embedding = np.array(embedding).astype("float32")