testtest123 commited on
Commit
0e82360
·
1 Parent(s): f4970d3

fix: robust OCR/image ingestion parsing and add one-click Supabase document deletion to Search Playground

Browse files
RAG_FULL_APPLICATION_BACKEND/app/routers/ingest.py CHANGED
@@ -69,13 +69,13 @@ async def list_documents(user: dict = Depends(get_current_user)):
69
  raise HTTPException(status_code=500, detail="Database error")
70
 
71
  @router.delete("/documents/{doc_id}")
72
- async def delete_document(doc_id: str, user_id: str = Depends(get_current_user)):
73
  try:
74
- # 1. Database cleanup
75
- await supabase_service.delete_document(doc_id, user_id["id"])
76
- # 2. BM25 cleanup
77
  bm25_service.delete_document(doc_id)
78
- return {"status": "success", "message": f"Document {doc_id} deleted"}
79
  except Exception as e:
80
  logger.error(f"Failed to delete document {doc_id}: {e}")
81
  raise HTTPException(status_code=500, detail=str(e))
 
69
  raise HTTPException(status_code=500, detail="Database error")
70
 
71
  @router.delete("/documents/{doc_id}")
72
+ async def delete_document(doc_id: str, user: dict = Depends(get_current_user)):
73
  try:
74
+ # 1. Database cleanup from Supabase (documents + chunks + vectors)
75
+ await supabase_service.delete_document(doc_id, user["id"])
76
+ # 2. Local BM25 cleanup
77
  bm25_service.delete_document(doc_id)
78
+ return {"status": "success", "message": f"Document {doc_id} deleted successfully from Supabase"}
79
  except Exception as e:
80
  logger.error(f"Failed to delete document {doc_id}: {e}")
81
  raise HTTPException(status_code=500, detail=str(e))
RAG_FULL_APPLICATION_BACKEND/app/services/file_parser.py CHANGED
@@ -47,17 +47,41 @@ async def _parse_pdf(file_path: str, job_id: str, ws_manager: Any, user_id: str)
47
  return [{"text": text, "metadata": {"source": Path(file_path).name, "page": 1}}]
48
 
49
  async def _parse_image(file_path: str, job_id: str, ws_manager: Any, user_id: str):
 
 
 
 
 
50
  try:
51
- await ws_manager.emit(job_id, user_id, {"step": "IMAGE_ANALYZE", "color": "#8B5CF6", "detail": "Qwen-VL analyzing image (Primary)..."})
52
- description = vision_service.understand_image(file_path)
53
- return [{"text": description, "metadata": {"source": Path(file_path).name, "page": 1}}]
54
- except Exception as e:
55
- logger.warning(f"Vision service failed, falling back to Tesseract: {e}")
56
- await ws_manager.emit(job_id, user_id, {"step": "FALLBACK", "color": "#F59E0B", "detail": "Vision failed. Falling back to Tesseract OCR..."})
57
  import pytesseract
58
  from PIL import Image
59
- text = pytesseract.image_to_string(Image.open(file_path))
60
- return [{"text": text, "metadata": {"source": Path(file_path).name, "page": 1}}]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
61
 
62
  def _parse_docx(file_path: str):
63
  doc = Document(file_path)
 
47
  return [{"text": text, "metadata": {"source": Path(file_path).name, "page": 1}}]
48
 
49
  async def _parse_image(file_path: str, job_id: str, ws_manager: Any, user_id: str):
50
+ filename = Path(file_path).name
51
+ extracted_content = []
52
+
53
+ # 1. Tesseract OCR (Fast, Reliable Local OCR)
54
+ ocr_text = ""
55
  try:
 
 
 
 
 
 
56
  import pytesseract
57
  from PIL import Image
58
+ img = Image.open(file_path)
59
+ ocr_text = pytesseract.image_to_string(img).strip()
60
+ if ocr_text:
61
+ extracted_content.append(f"Visual Text Content Extracted via OCR:\n{ocr_text}")
62
+ except Exception as e:
63
+ logger.warning(f"Tesseract OCR failed: {e}")
64
+
65
+ # 2. Vision Space Analysis (if available)
66
+ try:
67
+ await ws_manager.emit(job_id, user_id, {"step": "IMAGE_ANALYZE", "color": "#8B5CF6", "detail": "Analyzing visual image contents..."})
68
+ description = vision_service.understand_image(file_path)
69
+ if description and "failed to understand" not in description.lower():
70
+ extracted_content.append(f"Visual Scene Description:\n{description}")
71
+ except Exception as e:
72
+ logger.warning(f"Vision space analysis skipped/failed: {e}")
73
+
74
+ # 3. Fallback to image metadata if no text or vision
75
+ if not extracted_content:
76
+ try:
77
+ from PIL import Image
78
+ img = Image.open(file_path)
79
+ extracted_content.append(f"Image Document: {filename}\nResolution: {img.width}x{img.height}\nFormat: {img.format}\nMode: {img.mode}\nStatus: Visual image document indexed in knowledge base.")
80
+ except Exception:
81
+ extracted_content.append(f"Image Document: {filename}\nStatus: Visual image document indexed in knowledge base.")
82
+
83
+ final_text = "\n\n".join(extracted_content)
84
+ return [{"text": final_text, "metadata": {"source": filename, "page": 1}}]
85
 
86
  def _parse_docx(file_path: str):
87
  doc = Document(file_path)
RAG_FULL_APPLICATION_BACKEND/app/services/supabase_client.py CHANGED
@@ -106,12 +106,30 @@ class SupabaseService:
106
  return result.data
107
 
108
  async def delete_document(self, document_id: str, user_id: str):
109
- """Delete document + chunks + vectors (CASCADE)"""
110
  try:
111
- # 1. Chunks (will cascade to vectors)
112
- self.client.table("chunks").delete().eq("document_id", document_id).eq("user_id", user_id).execute()
113
- # 2. Document
114
- self.client.table("documents").delete().eq("id", document_id).eq("user_id", user_id).execute()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
115
  except Exception as e:
116
  logger.error(f"Failed to delete document {document_id}: {e}")
117
  raise
 
106
  return result.data
107
 
108
  async def delete_document(self, document_id: str, user_id: str):
109
+ """Delete document + chunks + vectors + colbert tokens (CASCADE)"""
110
  try:
111
+ # 1. Delete vector embeddings
112
+ try:
113
+ self.client.table("chunk_vectors").delete().eq("document_id", document_id).eq("user_id", user_id).execute()
114
+ except Exception as e:
115
+ logger.warning(f"chunk_vectors delete notice: {e}")
116
+
117
+ # 2. Delete colbert tokens
118
+ try:
119
+ self.client.table("colbert_tokens").delete().eq("document_id", document_id).execute()
120
+ except Exception as e:
121
+ logger.warning(f"colbert_tokens delete notice: {e}")
122
+
123
+ # 3. Delete chunks
124
+ try:
125
+ self.client.table("chunks").delete().eq("document_id", document_id).eq("user_id", user_id).execute()
126
+ except Exception as e:
127
+ logger.warning(f"chunks delete notice: {e}")
128
+
129
+ # 4. Delete document entry
130
+ result = self.client.table("documents").delete().eq("id", document_id).eq("user_id", user_id).execute()
131
+ logger.info(f"Document {document_id} and all associated embeddings deleted from Supabase.")
132
+ return result
133
  except Exception as e:
134
  logger.error(f"Failed to delete document {document_id}: {e}")
135
  raise
RAG_FULL_APPLICATION_BACKEND/app/services/vision_service.py CHANGED
@@ -20,24 +20,25 @@ class VisionService:
20
  """
21
  Send image to Qwen-VL HF Space for description.
22
  """
23
- try:
24
- # Check if file exists and is not empty to avoid crash
25
- import os
26
- if not os.path.exists(image_path) or os.path.getsize(image_path) == 0:
27
- return ""
28
 
 
29
  self.client.predict(api_name="/clear_conversation_history")
30
  file_arg = [handle_file(image_path)]
31
- prompt = "Please describe the contents of this image in detail."
32
 
33
  result = self.client.predict(
34
  input_value={"files": file_arg, "text": prompt},
35
  api_name="/add_message"
36
  )
37
  response_text = result[1]['value'][1]['content'][0]['content']
38
- return str(response_text)
 
 
39
  except Exception as e:
40
- logger.error(f"Image understanding failed: {e}")
41
- return "Failed to understand image."
42
 
43
  vision_service = VisionService()
 
20
  """
21
  Send image to Qwen-VL HF Space for description.
22
  """
23
+ import os
24
+ if not os.path.exists(image_path) or os.path.getsize(image_path) == 0:
25
+ return ""
 
 
26
 
27
+ try:
28
  self.client.predict(api_name="/clear_conversation_history")
29
  file_arg = [handle_file(image_path)]
30
+ prompt = "Please describe the contents of this image in detail, including all visible text, objects, and layout."
31
 
32
  result = self.client.predict(
33
  input_value={"files": file_arg, "text": prompt},
34
  api_name="/add_message"
35
  )
36
  response_text = result[1]['value'][1]['content'][0]['content']
37
+ if response_text and str(response_text).strip():
38
+ return str(response_text).strip()
39
+ raise ValueError("Empty response from vision space")
40
  except Exception as e:
41
+ logger.warning(f"Image understanding via space failed: {e}")
42
+ raise
43
 
44
  vision_service = VisionService()
RAG_FULL_APPLICATION_FRONTEND/src/pages/SearchPlaygroundPage.jsx CHANGED
@@ -102,6 +102,22 @@ export default function SearchPlaygroundPage() {
102
  }
103
  };
104
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
105
  return (
106
  <div className="max-w-7xl mx-auto px-4 py-8 space-y-8">
107
  {/* Header */}
@@ -159,8 +175,17 @@ export default function SearchPlaygroundPage() {
159
  }`}
160
  >
161
  <div className="flex justify-between items-center text-xs font-semibold">
162
- <span className="truncate max-w-[180px]">{doc.filename}</span>
163
- <span className="text-[9px] font-mono bg-surface-800 px-1.5 py-0.5 rounded text-accent-400 uppercase">{doc.file_type}</span>
 
 
 
 
 
 
 
 
 
164
  </div>
165
  </div>
166
  ))}
 
102
  }
103
  };
104
 
105
+ const handleDeleteDoc = async (e, docId) => {
106
+ e.stopPropagation();
107
+ if (!window.confirm('Delete this document and all its embeddings from Supabase database?')) return;
108
+ try {
109
+ await api.delete(`/ingest/documents/${docId}`);
110
+ const updatedDocs = documents.filter(d => d.id !== docId);
111
+ setDocuments(updatedDocs);
112
+ if (selectedDoc?.id === docId) {
113
+ setSelectedDoc(updatedDocs.length > 0 ? updatedDocs[0] : null);
114
+ }
115
+ } catch (error) {
116
+ console.error('Delete failed', error);
117
+ alert('Failed to delete document: ' + (error?.response?.data?.detail || error.message));
118
+ }
119
+ };
120
+
121
  return (
122
  <div className="max-w-7xl mx-auto px-4 py-8 space-y-8">
123
  {/* Header */}
 
175
  }`}
176
  >
177
  <div className="flex justify-between items-center text-xs font-semibold">
178
+ <span className="truncate max-w-[170px] text-white" title={doc.filename}>{doc.filename}</span>
179
+ <div className="flex items-center gap-1.5 shrink-0">
180
+ <span className="text-[9px] font-mono bg-surface-800 px-1.5 py-0.5 rounded text-accent-400 uppercase">{doc.file_type}</span>
181
+ <button
182
+ onClick={(e) => handleDeleteDoc(e, doc.id)}
183
+ className="p-1 text-gray-500 hover:text-red-400 hover:bg-red-500/10 rounded transition-colors"
184
+ title="Delete from Supabase"
185
+ >
186
+ <Trash2 className="w-3.5 h-3.5" />
187
+ </button>
188
+ </div>
189
  </div>
190
  </div>
191
  ))}