JigneshPrajapati18 commited on
Commit
41f311a
·
verified ·
1 Parent(s): 0bc1255

Upload folder using huggingface_hub

Browse files
RAG.py CHANGED
The diff for this file is too large to render. See raw diff
 
__pycache__/RAG.cpython-312.pyc CHANGED
Binary files a/__pycache__/RAG.cpython-312.pyc and b/__pycache__/RAG.cpython-312.pyc differ
 
__pycache__/app.cpython-312.pyc CHANGED
Binary files a/__pycache__/app.cpython-312.pyc and b/__pycache__/app.cpython-312.pyc differ
 
app.py CHANGED
@@ -1,4 +1,5 @@
1
- # from fastapi import FastAPI, File, UploadFile, HTTPException, Form, Request
 
2
  # from fastapi.responses import HTMLResponse, JSONResponse
3
  # from fastapi.staticfiles import StaticFiles
4
  # from fastapi.templating import Jinja2Templates
@@ -6,13 +7,14 @@
6
  # import os
7
  # import tempfile
8
  # import shutil
9
- # from typing import List, Dict, Any
10
  # import logging
 
 
11
 
 
12
  # import sys
13
- # import os
14
  # sys.path.append(os.path.dirname(os.path.abspath(__file__)))
15
-
16
  # try:
17
  # from RAG import RAGSystem
18
  # except ImportError:
@@ -20,10 +22,21 @@
20
  # print("Make sure RAG.py is in the same directory as app.py")
21
  # sys.exit(1)
22
 
23
- # logging.basicConfig(level=logging.DEBUG)
 
 
 
 
24
  # logger = logging.getLogger(__name__)
25
 
26
- # app = FastAPI(title="RAG PDF QA System")
 
 
 
 
 
 
 
27
 
28
  # # Setup templates directory
29
  # templates = Jinja2Templates(directory="templates")
@@ -37,56 +50,126 @@
37
  # # Initialize RAG System
38
  # try:
39
  # rag_system = RAGSystem()
40
- # logger.info("RAG System initialized successfully")
41
  # except Exception as e:
42
  # logger.error(f"Failed to initialize RAG System: {e}")
43
  # rag_system = None
44
 
 
45
  # class QuestionRequest(BaseModel):
46
  # question: str
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
47
 
 
 
 
48
  # @app.get("/", response_class=HTMLResponse)
49
  # async def read_root(request: Request):
 
50
  # try:
51
  # return templates.TemplateResponse("index.html", {"request": request})
52
  # except Exception as e:
53
  # logger.error(f"Error serving index.html from templates folder: {e}")
54
- # return HTMLResponse(content=f"""
55
- # <html>
56
- # <body>
57
- # <h1>RAG PDF QA System</h1>
58
- # <p>Error: Could not load index.html from templates folder</p>
59
- # <p>Error details: {str(e)}</p>
60
- # <p>Make sure you have:</p>
61
- # <ul>
62
- # <li>A 'templates' folder in the same directory as app.py</li>
63
- # <li>index.html file inside the templates folder</li>
64
- # <li>Installed jinja2: pip install jinja2</li>
65
- # </ul>
66
- # </body>
67
- # </html>
68
- # """)
69
 
70
  # @app.post("/upload")
71
  # async def upload_document(file: UploadFile = File(...)):
 
72
  # try:
73
  # if rag_system is None:
74
- # raise HTTPException(status_code=500, detail="RAG System not initialized")
75
 
76
  # if not file.filename:
77
  # raise HTTPException(status_code=400, detail="No file selected")
78
 
 
79
  # allowed_extensions = ['.pdf', '.docx', '.txt', '.csv']
80
  # file_extension = os.path.splitext(file.filename)[1].lower()
81
 
82
  # if file_extension not in allowed_extensions:
83
- # raise HTTPException(status_code=400, detail=f"File type {file_extension} not supported. Supported types: {', '.join(allowed_extensions)}")
 
 
 
 
 
 
 
 
 
 
 
84
 
85
  # # Create temporary file
86
  # with tempfile.NamedTemporaryFile(delete=False, suffix=file_extension) as temp_file:
87
- # shutil.copyfileobj(file.file, temp_file)
88
  # temp_path = temp_file.name
89
 
 
 
90
  # # Process document
91
  # success = rag_system.add_document(temp_path)
92
 
@@ -97,7 +180,13 @@
97
  # logger.warning(f"Failed to cleanup temp file: {cleanup_error}")
98
 
99
  # if success:
100
- # return JSONResponse(content={"message": f"Document '{file.filename}' uploaded and processed successfully"})
 
 
 
 
 
 
101
  # else:
102
  # raise HTTPException(status_code=500, detail="Failed to process document")
103
 
@@ -107,84 +196,292 @@
107
  # logger.error(f"Upload error: {e}", exc_info=True)
108
  # raise HTTPException(status_code=500, detail=f"Internal server error: {str(e)}")
109
 
110
- # @app.post("/ask")
111
  # async def ask_question(request: QuestionRequest):
 
112
  # try:
113
  # if rag_system is None:
114
- # raise HTTPException(status_code=500, detail="RAG System not initialized")
115
 
116
  # if not request.question.strip():
117
  # raise HTTPException(status_code=400, detail="Question cannot be empty")
118
 
119
- # result = rag_system.ask_question(request.question)
120
 
121
- # return JSONResponse(content={
122
- # "answer": result["answer"],
123
- # "sources": result["sources"]
124
- # })
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
125
 
126
  # except HTTPException:
127
  # raise
128
  # except Exception as e:
129
- # logger.error(f"Question error: {e}", exc_info=True)
130
  # raise HTTPException(status_code=500, detail=f"Internal server error: {str(e)}")
131
 
132
  # @app.get("/documents")
133
  # async def get_documents():
 
134
  # try:
135
  # if rag_system is None:
136
- # raise HTTPException(status_code=500, detail="RAG System not initialized")
137
 
138
  # docs = rag_system.list_documents()
139
- # return JSONResponse(content={"documents": docs})
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
140
  # except HTTPException:
141
  # raise
142
  # except Exception as e:
143
  # logger.error(f"Documents list error: {e}", exc_info=True)
144
  # raise HTTPException(status_code=500, detail=f"Internal server error: {str(e)}")
145
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
146
  # @app.delete("/clear")
147
  # async def clear_documents():
 
148
  # try:
149
  # if rag_system is None:
150
- # raise HTTPException(status_code=500, detail="RAG System not initialized")
151
 
 
 
152
  # success = rag_system.clear_index()
153
  # if success:
154
- # return JSONResponse(content={"message": "All documents cleared successfully"})
 
 
 
155
  # else:
156
  # raise HTTPException(status_code=500, detail="Failed to clear documents")
 
157
  # except HTTPException:
158
  # raise
159
  # except Exception as e:
160
  # logger.error(f"Clear error: {e}", exc_info=True)
161
  # raise HTTPException(status_code=500, detail=f"Internal server error: {str(e)}")
162
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
163
  # @app.get("/health")
164
  # async def health_check():
165
- # return {
166
- # "status": "healthy",
167
- # "rag_system_initialized": rag_system is not None,
168
- # "message": "RAG PDF QA System is running"
169
- # }
170
-
171
- # if __name__ == "__main__":
172
- # import uvicorn
173
- # logger.info("Starting FastAPI server...")
174
- # uvicorn.run(app, host="0.0.0.0", port=8000, log_level="debug")
175
-
176
-
177
-
178
-
 
 
 
 
 
 
 
179
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
180
 
 
 
 
 
 
 
181
 
 
 
 
 
 
 
 
182
 
 
 
 
 
 
 
 
 
 
 
 
183
 
 
 
 
 
 
 
 
 
 
 
 
184
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
185
 
186
 
187
- # # second code
188
  # from fastapi import FastAPI, File, UploadFile, HTTPException, Request
189
  # from fastapi.responses import HTMLResponse, JSONResponse
190
  # from fastapi.staticfiles import StaticFiles
@@ -193,13 +490,14 @@
193
  # import os
194
  # import tempfile
195
  # import shutil
196
- # from typing import List, Dict, Any
197
  # import logging
 
 
198
 
 
199
  # import sys
200
- # import os
201
  # sys.path.append(os.path.dirname(os.path.abspath(__file__)))
202
-
203
  # try:
204
  # from RAG import RAGSystem
205
  # except ImportError:
@@ -207,10 +505,21 @@
207
  # print("Make sure RAG.py is in the same directory as app.py")
208
  # sys.exit(1)
209
 
210
- # logging.basicConfig(level=logging.INFO)
 
 
 
 
211
  # logger = logging.getLogger(__name__)
212
 
213
- # app = FastAPI(title="RAG PDF QA System")
 
 
 
 
 
 
 
214
 
215
  # # Setup templates directory
216
  # templates = Jinja2Templates(directory="templates")
@@ -224,57 +533,126 @@
224
  # # Initialize RAG System
225
  # try:
226
  # rag_system = RAGSystem()
227
- # logger.info("RAG System initialized successfully")
228
  # except Exception as e:
229
  # logger.error(f"Failed to initialize RAG System: {e}")
230
  # rag_system = None
231
 
 
232
  # class QuestionRequest(BaseModel):
233
  # question: str
234
  # top_k: int = 3
235
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
236
  # @app.get("/", response_class=HTMLResponse)
237
  # async def read_root(request: Request):
 
238
  # try:
239
  # return templates.TemplateResponse("index.html", {"request": request})
240
  # except Exception as e:
241
  # logger.error(f"Error serving index.html from templates folder: {e}")
242
- # return HTMLResponse(content=f"""
243
- # <html>
244
- # <body>
245
- # <h1>RAG PDF QA System</h1>
246
- # <p>Error: Could not load index.html from templates folder</p>
247
- # <p>Error details: {str(e)}</p>
248
- # <p>Make sure you have:</p>
249
- # <ul>
250
- # <li>A 'templates' folder in the same directory as app.py</li>
251
- # <li>index.html file inside the templates folder</li>
252
- # <li>Installed jinja2: pip install jinja2</li>
253
- # </ul>
254
- # </body>
255
- # </html>
256
- # """)
257
 
258
  # @app.post("/upload")
259
  # async def upload_document(file: UploadFile = File(...)):
 
260
  # try:
261
  # if rag_system is None:
262
- # raise HTTPException(status_code=500, detail="RAG System not initialized")
263
 
264
  # if not file.filename:
265
  # raise HTTPException(status_code=400, detail="No file selected")
266
 
 
267
  # allowed_extensions = ['.pdf', '.docx', '.txt', '.csv']
268
  # file_extension = os.path.splitext(file.filename)[1].lower()
269
 
270
  # if file_extension not in allowed_extensions:
271
- # raise HTTPException(status_code=400, detail=f"File type {file_extension} not supported. Supported types: {', '.join(allowed_extensions)}")
 
 
 
 
 
 
 
 
 
 
 
272
 
273
  # # Create temporary file
274
  # with tempfile.NamedTemporaryFile(delete=False, suffix=file_extension) as temp_file:
275
- # shutil.copyfileobj(file.file, temp_file)
276
  # temp_path = temp_file.name
277
 
 
 
278
  # # Process document
279
  # success = rag_system.add_document(temp_path)
280
 
@@ -285,7 +663,13 @@
285
  # logger.warning(f"Failed to cleanup temp file: {cleanup_error}")
286
 
287
  # if success:
288
- # return JSONResponse(content={"message": f"Document '{file.filename}' uploaded and processed successfully"})
 
 
 
 
 
 
289
  # else:
290
  # raise HTTPException(status_code=500, detail="Failed to process document")
291
 
@@ -295,55 +679,146 @@
295
  # logger.error(f"Upload error: {e}", exc_info=True)
296
  # raise HTTPException(status_code=500, detail=f"Internal server error: {str(e)}")
297
 
298
- # @app.post("/ask")
299
  # async def ask_question(request: QuestionRequest):
 
300
  # try:
301
  # if rag_system is None:
302
- # raise HTTPException(status_code=500, detail="RAG System not initialized")
303
 
304
  # if not request.question.strip():
305
  # raise HTTPException(status_code=400, detail="Question cannot be empty")
306
 
 
 
 
307
  # result = rag_system.ask_question(request.question, top_k=request.top_k)
308
 
309
- # return JSONResponse(content={
310
- # "answer": result["answer"],
311
- # "sources": result["sources"],
312
- # "question_chunks": result.get("question_chunks", []),
313
- # "relevant_chunks": result.get("relevant_chunks", [])
314
- # })
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
315
 
316
  # except HTTPException:
317
  # raise
318
  # except Exception as e:
319
- # logger.error(f"Question error: {e}", exc_info=True)
320
  # raise HTTPException(status_code=500, detail=f"Internal server error: {str(e)}")
321
 
322
- # @app.get("/documents")
323
- # async def get_documents():
 
324
  # try:
325
  # if rag_system is None:
326
- # raise HTTPException(status_code=500, detail="RAG System not initialized")
327
 
328
  # docs = rag_system.list_documents()
329
- # return JSONResponse(content={"documents": docs})
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
330
  # except HTTPException:
331
  # raise
332
  # except Exception as e:
333
- # logger.error(f"Documents list error: {e}", exc_info=True)
334
  # raise HTTPException(status_code=500, detail=f"Internal server error: {str(e)}")
335
 
336
  # @app.delete("/clear")
337
  # async def clear_documents():
 
338
  # try:
339
  # if rag_system is None:
340
- # raise HTTPException(status_code=500, detail="RAG System not initialized")
341
 
 
 
342
  # success = rag_system.clear_index()
343
  # if success:
344
- # return JSONResponse(content={"message": "All documents cleared successfully"})
 
 
 
345
  # else:
346
  # raise HTTPException(status_code=500, detail="Failed to clear documents")
 
347
  # except HTTPException:
348
  # raise
349
  # except Exception as e:
@@ -352,19 +827,34 @@
352
 
353
  # @app.post("/search")
354
  # async def search_chunks(request: QuestionRequest):
 
355
  # try:
356
  # if rag_system is None:
357
- # raise HTTPException(status_code=500, detail="RAG System not initialized")
358
 
359
  # if not request.question.strip():
360
  # raise HTTPException(status_code=400, detail="Search query cannot be empty")
361
 
 
 
362
  # chunks = rag_system.doc_processor.search_chunks(request.question, top_k=request.top_k)
363
 
 
 
 
 
 
 
 
 
 
 
 
 
364
  # return JSONResponse(content={
365
  # "query": request.question,
366
- # "chunks": chunks,
367
- # "total_found": len(chunks)
368
  # })
369
 
370
  # except HTTPException:
@@ -375,51 +865,107 @@
375
 
376
  # @app.get("/health")
377
  # async def health_check():
378
- # return {
379
- # "status": "healthy",
380
- # "rag_system_initialized": rag_system is not None,
381
- # "message": "RAG PDF QA System is running",
382
- # "indexed_documents": len(rag_system.list_documents()) if rag_system else 0
383
- # }
384
-
385
- # @app.get("/stats")
386
- # async def get_stats():
387
  # try:
388
- # if rag_system is None:
389
- # raise HTTPException(status_code=500, detail="RAG System not initialized")
390
-
391
- # docs = rag_system.list_documents()
392
-
393
- # total_chunks = sum(doc.get("chunk_count", 0) for doc in docs)
394
- # total_pages = sum(doc.get("total_pages", 1) for doc in docs)
395
-
396
- # return JSONResponse(content={
397
- # "total_documents": len(docs),
398
- # "total_chunks": total_chunks,
399
- # "total_pages": total_pages,
400
- # "documents": docs
401
- # })
402
 
403
- # except HTTPException:
404
- # raise
 
 
 
 
 
 
 
405
  # except Exception as e:
406
- # logger.error(f"Stats error: {e}", exc_info=True)
407
- # raise HTTPException(status_code=500, detail=f"Internal server error: {str(e)}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
408
 
409
  # @app.on_event("shutdown")
410
  # async def shutdown_event():
 
411
  # if rag_system:
412
  # rag_system.close()
413
- # logger.info("RAG System closed gracefully")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
414
 
 
415
  # if __name__ == "__main__":
416
  # import uvicorn
417
- # logger.info("Starting FastAPI server...")
418
- # uvicorn.run(app, host="0.0.0.0", port=8000, log_level="info")
 
 
 
 
 
 
 
 
 
 
 
419
 
420
 
421
 
422
- # complate code
423
  # from fastapi import FastAPI, File, UploadFile, HTTPException, Request
424
  # from fastapi.responses import HTMLResponse, JSONResponse
425
  # from fastapi.staticfiles import StaticFiles
@@ -460,13 +1006,13 @@
460
  # )
461
 
462
  # # Setup templates directory
463
- # templates = Jinja2Templates(directory="templates")
464
 
465
  # # Try to mount static files directory
466
  # try:
467
- # app.mount("/static", StaticFiles(directory="static"), name="static")
468
  # except Exception as e:
469
- # logger.warning(f"Static files directory not found: {e}")
470
 
471
  # # Initialize RAG System
472
  # try:
@@ -488,12 +1034,19 @@
488
  # chunk_count: int
489
  # total_pages: Optional[int] = None
490
 
491
- # # Fixed AnswerResponse model to handle both strings and dictionaries
 
 
 
 
 
 
492
  # class AnswerResponse(BaseModel):
493
  # answer: str
494
  # sources: List[Dict[str, Any]]
495
  # question_chunks: List[Union[str, Dict[str, Any]]] = []
496
  # relevant_chunks: List[Union[str, Dict[str, Any]]] = []
 
497
 
498
  # class StatsResponse(BaseModel):
499
  # total_documents: int
@@ -542,6 +1095,7 @@
542
 
543
  # return extracted
544
 
 
545
  # # Routes
546
  # @app.get("/", response_class=HTMLResponse)
547
  # async def read_root(request: Request):
@@ -550,10 +1104,8 @@
550
  # return templates.TemplateResponse("index.html", {"request": request})
551
  # except Exception as e:
552
  # logger.error(f"Error serving index.html from templates folder: {e}")
553
- # # Return the embedded HTML if templates folder is not available
554
- # with open("scholar_archive.html", "r", encoding="utf-8") as f:
555
- # html_content = f.read()
556
- # return HTMLResponse(content=html_content)
557
 
558
  # @app.post("/upload")
559
  # async def upload_document(file: UploadFile = File(...)):
@@ -631,27 +1183,24 @@
631
  # # Get answer from RAG system
632
  # result = rag_system.ask_question(request.question, top_k=request.top_k)
633
 
634
- # # Handle the chunks data properly
635
- # question_chunks = result.get("question_chunks", [])
636
- # relevant_chunks = result.get("relevant_chunks", [])
637
-
638
- # # Log the structure to understand what we're getting
639
- # logger.info(f"Question chunks type: {type(question_chunks)}")
640
- # logger.info(f"Relevant chunks type: {type(relevant_chunks)}")
641
- # if question_chunks:
642
- # logger.info(f"First question chunk type: {type(question_chunks[0])}")
643
- # if relevant_chunks:
644
- # logger.info(f"First relevant chunk type: {type(relevant_chunks[0])}")
645
 
646
  # # Format the response - keep original structure but ensure it's serializable
647
  # response = AnswerResponse(
648
  # answer=result["answer"],
649
- # sources=result["sources"],
650
- # question_chunks=question_chunks,
651
- # relevant_chunks=relevant_chunks
 
652
  # )
653
 
654
- # logger.info(f"Successfully answered question with {len(result['sources'])} sources")
655
 
656
  # return response
657
 
@@ -661,6 +1210,51 @@
661
  # logger.error(f"Question processing error: {e}", exc_info=True)
662
  # raise HTTPException(status_code=500, detail=f"Internal server error: {str(e)}")
663
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
664
  # @app.get("/documents")
665
  # async def get_documents():
666
  # """Get list of all uploaded documents"""
@@ -772,10 +1366,10 @@
772
  # formatted_chunks = []
773
  # for chunk in chunks:
774
  # formatted_chunk = {
775
- # "content": chunk.get("content", ""),
776
- # "document": chunk.get("document", "Unknown"),
777
  # "similarity": chunk.get("similarity", 0.0),
778
- # "page": chunk.get("page"),
779
  # "chunk_index": chunk.get("chunk_index")
780
  # }
781
  # formatted_chunks.append(formatted_chunk)
@@ -826,6 +1420,7 @@
826
  # "endpoints": {
827
  # "upload": "POST /upload - Upload documents",
828
  # "ask": "POST /ask - Ask questions",
 
829
  # "documents": "GET /documents - List documents",
830
  # "stats": "GET /stats - Get statistics",
831
  # "search": "POST /search - Search chunks",
@@ -896,7 +1491,8 @@
896
 
897
 
898
 
899
- # perfect code
 
900
  from fastapi import FastAPI, File, UploadFile, HTTPException, Request
901
  from fastapi.responses import HTMLResponse, JSONResponse
902
  from fastapi.staticfiles import StaticFiles
@@ -937,13 +1533,15 @@ app = FastAPI(
937
  )
938
 
939
  # Setup templates directory
940
- templates = Jinja2Templates(directory="templates")
 
941
 
942
- # Try to mount static files directory
 
943
  try:
944
  app.mount("/static", StaticFiles(directory="static"), name="static")
945
  except Exception as e:
946
- logger.warning(f"Static files directory not found: {e}")
947
 
948
  # Initialize RAG System
949
  try:
@@ -965,11 +1563,19 @@ class DocumentInfo(BaseModel):
965
  chunk_count: int
966
  total_pages: Optional[int] = None
967
 
 
 
 
 
 
 
 
968
  class AnswerResponse(BaseModel):
969
  answer: str
970
  sources: List[Dict[str, Any]]
971
  question_chunks: List[Union[str, Dict[str, Any]]] = []
972
  relevant_chunks: List[Union[str, Dict[str, Any]]] = []
 
973
 
974
  class StatsResponse(BaseModel):
975
  total_documents: int
@@ -1000,38 +1606,17 @@ def format_file_size(size_bytes: int) -> str:
1000
  i += 1
1001
  return f"{size_bytes:.1f} {size_names[i]}"
1002
 
1003
- def extract_content_from_chunks(chunks):
1004
- """Extract string content from chunk data structures"""
1005
- if not chunks:
1006
- return []
1007
-
1008
- extracted = []
1009
- for chunk in chunks:
1010
- if isinstance(chunk, str):
1011
- extracted.append(chunk)
1012
- elif isinstance(chunk, dict):
1013
- # Try different possible keys for text content
1014
- content = chunk.get('text') or chunk.get('content') or chunk.get('document') or str(chunk)
1015
- extracted.append(content)
1016
- else:
1017
- extracted.append(str(chunk))
1018
-
1019
- return extracted
1020
-
1021
-
1022
-
1023
  # Routes
1024
  @app.get("/", response_class=HTMLResponse)
1025
  async def read_root(request: Request):
1026
  """Serve the main classical interface"""
1027
  try:
 
1028
  return templates.TemplateResponse("index.html", {"request": request})
1029
  except Exception as e:
1030
- logger.error(f"Error serving index.html from templates folder: {e}")
1031
- # Return the embedded HTML if templates folder is not available
1032
- with open("scholar_archive.html", "r", encoding="utf-8") as f:
1033
- html_content = f.read()
1034
- return HTMLResponse(content=html_content)
1035
 
1036
  @app.post("/upload")
1037
  async def upload_document(file: UploadFile = File(...)):
@@ -1109,32 +1694,21 @@ async def ask_question(request: QuestionRequest):
1109
  # Get answer from RAG system
1110
  result = rag_system.ask_question(request.question, top_k=request.top_k)
1111
 
1112
- # Handle the chunks data properly
1113
- question_chunks = result.get("question_chunks", [])
1114
- relevant_chunks = result.get("relevant_chunks", [])
1115
-
1116
  # Add page numbers to sources
1117
  sources = result.get("sources", [])
1118
  for source in sources:
1119
  if isinstance(source, dict):
1120
- page_num = source.get('page')
1121
  if page_num:
1122
  source['page_reference'] = f"Page {page_num}"
1123
 
1124
- # Log the structure to understand what we're getting
1125
- logger.info(f"Question chunks type: {type(question_chunks)}")
1126
- logger.info(f"Relevant chunks type: {type(relevant_chunks)}")
1127
- if question_chunks:
1128
- logger.info(f"First question chunk type: {type(question_chunks[0])}")
1129
- if relevant_chunks:
1130
- logger.info(f"First relevant chunk type: {type(relevant_chunks[0])}")
1131
-
1132
  # Format the response - keep original structure but ensure it's serializable
1133
  response = AnswerResponse(
1134
  answer=result["answer"],
1135
  sources=sources,
1136
- question_chunks=question_chunks,
1137
- relevant_chunks=relevant_chunks
 
1138
  )
1139
 
1140
  logger.info(f"Successfully answered question with {len(sources)} sources")
@@ -1147,6 +1721,51 @@ async def ask_question(request: QuestionRequest):
1147
  logger.error(f"Question processing error: {e}", exc_info=True)
1148
  raise HTTPException(status_code=500, detail=f"Internal server error: {str(e)}")
1149
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1150
  @app.get("/documents")
1151
  async def get_documents():
1152
  """Get list of all uploaded documents"""
@@ -1163,7 +1782,7 @@ async def get_documents():
1163
  "title": doc.get("title", "Unknown Document"),
1164
  "chunk_count": doc.get("chunk_count", 0),
1165
  "total_pages": doc.get("total_pages"),
1166
- "file_type": os.path.splitext(doc.get("title", ""))[1].lower(),
1167
  "upload_date": doc.get("upload_date", datetime.now().isoformat()),
1168
  "icon": get_file_type_icon(doc.get("title", ""))
1169
  }
@@ -1194,7 +1813,7 @@ async def get_stats():
1194
  for doc in docs:
1195
  formatted_doc = DocumentInfo(
1196
  title=doc.get("title", "Unknown Document"),
1197
- file_type=os.path.splitext(doc.get("title", ""))[1].lower(),
1198
  upload_date=doc.get("upload_date", datetime.now().isoformat()),
1199
  chunk_count=doc.get("chunk_count", 0),
1200
  total_pages=doc.get("total_pages")
@@ -1258,10 +1877,10 @@ async def search_chunks(request: QuestionRequest):
1258
  formatted_chunks = []
1259
  for chunk in chunks:
1260
  formatted_chunk = {
1261
- "content": chunk.get("content", ""),
1262
- "document": chunk.get("document", "Unknown"),
1263
  "similarity": chunk.get("similarity", 0.0),
1264
- "page": chunk.get("page"),
1265
  "chunk_index": chunk.get("chunk_index")
1266
  }
1267
  formatted_chunks.append(formatted_chunk)
@@ -1312,6 +1931,7 @@ async def api_info():
1312
  "endpoints": {
1313
  "upload": "POST /upload - Upload documents",
1314
  "ask": "POST /ask - Ask questions",
 
1315
  "documents": "GET /documents - List documents",
1316
  "stats": "GET /stats - Get statistics",
1317
  "search": "POST /search - Search chunks",
@@ -1376,4 +1996,7 @@ if __name__ == "__main__":
1376
  log_level="info",
1377
  reload=False,
1378
  access_log=True
1379
- )
 
 
 
 
1
+ # perfect code
2
+ # from fastapi import FastAPI, File, UploadFile, HTTPException, Request
3
  # from fastapi.responses import HTMLResponse, JSONResponse
4
  # from fastapi.staticfiles import StaticFiles
5
  # from fastapi.templating import Jinja2Templates
 
7
  # import os
8
  # import tempfile
9
  # import shutil
10
+ # from typing import List, Dict, Any, Optional, Union
11
  # import logging
12
+ # from datetime import datetime
13
+ # import mimetypes
14
 
15
+ # # Import your RAG system
16
  # import sys
 
17
  # sys.path.append(os.path.dirname(os.path.abspath(__file__)))
 
18
  # try:
19
  # from RAG import RAGSystem
20
  # except ImportError:
 
22
  # print("Make sure RAG.py is in the same directory as app.py")
23
  # sys.exit(1)
24
 
25
+ # # Configure logging
26
+ # logging.basicConfig(
27
+ # level=logging.INFO,
28
+ # format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
29
+ # )
30
  # logger = logging.getLogger(__name__)
31
 
32
+ # # Initialize FastAPI app
33
+ # app = FastAPI(
34
+ # title="Scholar's Archive - Document Intelligence System",
35
+ # description="A sophisticated platform for intelligent document analysis and question answering using advanced retrieval-augmented generation technology",
36
+ # version="1.0.0",
37
+ # docs_url="/api/docs",
38
+ # redoc_url="/api/redoc"
39
+ # )
40
 
41
  # # Setup templates directory
42
  # templates = Jinja2Templates(directory="templates")
 
50
  # # Initialize RAG System
51
  # try:
52
  # rag_system = RAGSystem()
53
+ # logger.info("Scholar's Archive RAG System initialized successfully")
54
  # except Exception as e:
55
  # logger.error(f"Failed to initialize RAG System: {e}")
56
  # rag_system = None
57
 
58
+ # # Pydantic models
59
  # class QuestionRequest(BaseModel):
60
  # question: str
61
+ # top_k: int = 3
62
+
63
+ # class DocumentInfo(BaseModel):
64
+ # title: str
65
+ # file_type: str
66
+ # upload_date: str
67
+ # chunk_count: int
68
+ # total_pages: Optional[int] = None
69
+
70
+ # class AnswerResponse(BaseModel):
71
+ # answer: str
72
+ # sources: List[Dict[str, Any]]
73
+ # question_chunks: List[Union[str, Dict[str, Any]]] = []
74
+ # relevant_chunks: List[Union[str, Dict[str, Any]]] = []
75
+
76
+ # class StatsResponse(BaseModel):
77
+ # total_documents: int
78
+ # total_chunks: int
79
+ # total_pages: int
80
+ # documents: List[DocumentInfo]
81
+
82
+ # # Utility functions
83
+ # def get_file_type_icon(filename: str) -> str:
84
+ # """Get appropriate icon for file type"""
85
+ # ext = os.path.splitext(filename)[1].lower()
86
+ # icons = {
87
+ # '.pdf': 'fas fa-file-pdf',
88
+ # '.docx': 'fas fa-file-word',
89
+ # '.txt': 'fas fa-file-alt',
90
+ # '.csv': 'fas fa-file-csv'
91
+ # }
92
+ # return icons.get(ext, 'fas fa-file')
93
+
94
+ # def format_file_size(size_bytes: int) -> str:
95
+ # """Format file size in human readable format"""
96
+ # if size_bytes == 0:
97
+ # return "0 B"
98
+ # size_names = ["B", "KB", "MB", "GB"]
99
+ # i = 0
100
+ # while size_bytes >= 1024 and i < len(size_names) - 1:
101
+ # size_bytes /= 1024.0
102
+ # i += 1
103
+ # return f"{size_bytes:.1f} {size_names[i]}"
104
+
105
+ # def extract_content_from_chunks(chunks):
106
+ # """Extract string content from chunk data structures"""
107
+ # if not chunks:
108
+ # return []
109
+
110
+ # extracted = []
111
+ # for chunk in chunks:
112
+ # if isinstance(chunk, str):
113
+ # extracted.append(chunk)
114
+ # elif isinstance(chunk, dict):
115
+ # # Try different possible keys for text content
116
+ # content = chunk.get('text') or chunk.get('content') or chunk.get('document') or str(chunk)
117
+ # extracted.append(content)
118
+ # else:
119
+ # extracted.append(str(chunk))
120
+
121
+ # return extracted
122
 
123
+
124
+
125
+ # # Routes
126
  # @app.get("/", response_class=HTMLResponse)
127
  # async def read_root(request: Request):
128
+ # """Serve the main classical interface"""
129
  # try:
130
  # return templates.TemplateResponse("index.html", {"request": request})
131
  # except Exception as e:
132
  # logger.error(f"Error serving index.html from templates folder: {e}")
133
+ # # Return the embedded HTML if templates folder is not available
134
+ # with open("scholar_archive.html", "r", encoding="utf-8") as f:
135
+ # html_content = f.read()
136
+ # return HTMLResponse(content=html_content)
 
 
 
 
 
 
 
 
 
 
 
137
 
138
  # @app.post("/upload")
139
  # async def upload_document(file: UploadFile = File(...)):
140
+ # """Upload and process a document"""
141
  # try:
142
  # if rag_system is None:
143
+ # raise HTTPException(status_code=500, detail="Scholar's Archive system not initialized")
144
 
145
  # if not file.filename:
146
  # raise HTTPException(status_code=400, detail="No file selected")
147
 
148
+ # # Validate file type
149
  # allowed_extensions = ['.pdf', '.docx', '.txt', '.csv']
150
  # file_extension = os.path.splitext(file.filename)[1].lower()
151
 
152
  # if file_extension not in allowed_extensions:
153
+ # raise HTTPException(
154
+ # status_code=400,
155
+ # detail=f"File type {file_extension} not supported. Supported formats: {', '.join(allowed_extensions)}"
156
+ # )
157
+
158
+ # # Check file size (limit to 50MB)
159
+ # file_size = 0
160
+ # content = await file.read()
161
+ # file_size = len(content)
162
+
163
+ # if file_size > 50 * 1024 * 1024: # 50MB limit
164
+ # raise HTTPException(status_code=400, detail="File size too large. Maximum size is 50MB")
165
 
166
  # # Create temporary file
167
  # with tempfile.NamedTemporaryFile(delete=False, suffix=file_extension) as temp_file:
168
+ # temp_file.write(content)
169
  # temp_path = temp_file.name
170
 
171
+ # logger.info(f"Processing document: {file.filename} ({format_file_size(file_size)})")
172
+
173
  # # Process document
174
  # success = rag_system.add_document(temp_path)
175
 
 
180
  # logger.warning(f"Failed to cleanup temp file: {cleanup_error}")
181
 
182
  # if success:
183
+ # logger.info(f"Successfully processed document: {file.filename}")
184
+ # return JSONResponse(content={
185
+ # "message": f"Document '{file.filename}' has been successfully added to the Scholar's Archive",
186
+ # "filename": file.filename,
187
+ # "size": format_file_size(file_size),
188
+ # "type": file_extension
189
+ # })
190
  # else:
191
  # raise HTTPException(status_code=500, detail="Failed to process document")
192
 
 
196
  # logger.error(f"Upload error: {e}", exc_info=True)
197
  # raise HTTPException(status_code=500, detail=f"Internal server error: {str(e)}")
198
 
199
+ # @app.post("/ask", response_model=AnswerResponse)
200
  # async def ask_question(request: QuestionRequest):
201
+ # """Ask a question about the uploaded documents"""
202
  # try:
203
  # if rag_system is None:
204
+ # raise HTTPException(status_code=500, detail="Scholar's Archive system not initialized")
205
 
206
  # if not request.question.strip():
207
  # raise HTTPException(status_code=400, detail="Question cannot be empty")
208
 
209
+ # logger.info(f"Processing question: {request.question[:100]}...")
210
 
211
+ # # Get answer from RAG system
212
+ # result = rag_system.ask_question(request.question, top_k=request.top_k)
213
+
214
+ # # Handle the chunks data properly
215
+ # question_chunks = result.get("question_chunks", [])
216
+ # relevant_chunks = result.get("relevant_chunks", [])
217
+
218
+ # # Add page numbers to sources
219
+ # sources = result.get("sources", [])
220
+ # for source in sources:
221
+ # if isinstance(source, dict):
222
+ # page_num = source.get('page')
223
+ # if page_num:
224
+ # source['page_reference'] = f"Page {page_num}"
225
+
226
+ # # Log the structure to understand what we're getting
227
+ # logger.info(f"Question chunks type: {type(question_chunks)}")
228
+ # logger.info(f"Relevant chunks type: {type(relevant_chunks)}")
229
+ # if question_chunks:
230
+ # logger.info(f"First question chunk type: {type(question_chunks[0])}")
231
+ # if relevant_chunks:
232
+ # logger.info(f"First relevant chunk type: {type(relevant_chunks[0])}")
233
+
234
+ # # Format the response - keep original structure but ensure it's serializable
235
+ # response = AnswerResponse(
236
+ # answer=result["answer"],
237
+ # sources=sources,
238
+ # question_chunks=question_chunks,
239
+ # relevant_chunks=relevant_chunks
240
+ # )
241
+
242
+ # logger.info(f"Successfully answered question with {len(sources)} sources")
243
+
244
+ # return response
245
 
246
  # except HTTPException:
247
  # raise
248
  # except Exception as e:
249
+ # logger.error(f"Question processing error: {e}", exc_info=True)
250
  # raise HTTPException(status_code=500, detail=f"Internal server error: {str(e)}")
251
 
252
  # @app.get("/documents")
253
  # async def get_documents():
254
+ # """Get list of all uploaded documents"""
255
  # try:
256
  # if rag_system is None:
257
+ # raise HTTPException(status_code=500, detail="Scholar's Archive system not initialized")
258
 
259
  # docs = rag_system.list_documents()
260
+
261
+ # # Format documents with additional metadata
262
+ # formatted_docs = []
263
+ # for doc in docs:
264
+ # formatted_doc = {
265
+ # "title": doc.get("title", "Unknown Document"),
266
+ # "chunk_count": doc.get("chunk_count", 0),
267
+ # "total_pages": doc.get("total_pages"),
268
+ # "file_type": os.path.splitext(doc.get("title", ""))[1].lower(),
269
+ # "upload_date": doc.get("upload_date", datetime.now().isoformat()),
270
+ # "icon": get_file_type_icon(doc.get("title", ""))
271
+ # }
272
+ # formatted_docs.append(formatted_doc)
273
+
274
+ # return JSONResponse(content={"documents": formatted_docs})
275
+
276
  # except HTTPException:
277
  # raise
278
  # except Exception as e:
279
  # logger.error(f"Documents list error: {e}", exc_info=True)
280
  # raise HTTPException(status_code=500, detail=f"Internal server error: {str(e)}")
281
 
282
+ # @app.get("/stats", response_model=StatsResponse)
283
+ # async def get_stats():
284
+ # """Get statistics about the document collection"""
285
+ # try:
286
+ # if rag_system is None:
287
+ # raise HTTPException(status_code=500, detail="Scholar's Archive system not initialized")
288
+
289
+ # docs = rag_system.list_documents()
290
+
291
+ # total_chunks = sum(doc.get("chunk_count", 0) for doc in docs)
292
+ # total_pages = sum(doc.get("total_pages", 1) for doc in docs if doc.get("total_pages"))
293
+
294
+ # # Format documents
295
+ # formatted_docs = []
296
+ # for doc in docs:
297
+ # formatted_doc = DocumentInfo(
298
+ # title=doc.get("title", "Unknown Document"),
299
+ # file_type=os.path.splitext(doc.get("title", ""))[1].lower(),
300
+ # upload_date=doc.get("upload_date", datetime.now().isoformat()),
301
+ # chunk_count=doc.get("chunk_count", 0),
302
+ # total_pages=doc.get("total_pages")
303
+ # )
304
+ # formatted_docs.append(formatted_doc)
305
+
306
+ # stats = StatsResponse(
307
+ # total_documents=len(docs),
308
+ # total_chunks=total_chunks,
309
+ # total_pages=total_pages,
310
+ # documents=formatted_docs
311
+ # )
312
+
313
+ # return stats
314
+
315
+ # except HTTPException:
316
+ # raise
317
+ # except Exception as e:
318
+ # logger.error(f"Stats error: {e}", exc_info=True)
319
+ # raise HTTPException(status_code=500, detail=f"Internal server error: {str(e)}")
320
+
321
  # @app.delete("/clear")
322
  # async def clear_documents():
323
+ # """Clear all documents from the archive"""
324
  # try:
325
  # if rag_system is None:
326
+ # raise HTTPException(status_code=500, detail="Scholar's Archive system not initialized")
327
 
328
+ # logger.info("Clearing all documents from Scholar's Archive")
329
+
330
  # success = rag_system.clear_index()
331
  # if success:
332
+ # logger.info("Successfully cleared all documents")
333
+ # return JSONResponse(content={
334
+ # "message": "All documents have been successfully removed from the Scholar's Archive"
335
+ # })
336
  # else:
337
  # raise HTTPException(status_code=500, detail="Failed to clear documents")
338
+
339
  # except HTTPException:
340
  # raise
341
  # except Exception as e:
342
  # logger.error(f"Clear error: {e}", exc_info=True)
343
  # raise HTTPException(status_code=500, detail=f"Internal server error: {str(e)}")
344
 
345
+ # @app.post("/search")
346
+ # async def search_chunks(request: QuestionRequest):
347
+ # """Search for relevant document chunks"""
348
+ # try:
349
+ # if rag_system is None:
350
+ # raise HTTPException(status_code=500, detail="Scholar's Archive system not initialized")
351
+
352
+ # if not request.question.strip():
353
+ # raise HTTPException(status_code=400, detail="Search query cannot be empty")
354
+
355
+ # logger.info(f"Searching chunks for: {request.question[:100]}...")
356
+
357
+ # chunks = rag_system.doc_processor.search_chunks(request.question, top_k=request.top_k)
358
+
359
+ # # Format chunks with additional metadata
360
+ # formatted_chunks = []
361
+ # for chunk in chunks:
362
+ # formatted_chunk = {
363
+ # "content": chunk.get("content", ""),
364
+ # "document": chunk.get("document", "Unknown"),
365
+ # "similarity": chunk.get("similarity", 0.0),
366
+ # "page": chunk.get("page"),
367
+ # "chunk_index": chunk.get("chunk_index")
368
+ # }
369
+ # formatted_chunks.append(formatted_chunk)
370
+
371
+ # return JSONResponse(content={
372
+ # "query": request.question,
373
+ # "chunks": formatted_chunks,
374
+ # "total_found": len(formatted_chunks)
375
+ # })
376
+
377
+ # except HTTPException:
378
+ # raise
379
+ # except Exception as e:
380
+ # logger.error(f"Search error: {e}", exc_info=True)
381
+ # raise HTTPException(status_code=500, detail=f"Internal server error: {str(e)}")
382
+
383
  # @app.get("/health")
384
  # async def health_check():
385
+ # """Health check endpoint"""
386
+ # try:
387
+ # doc_count = len(rag_system.list_documents()) if rag_system else 0
388
+
389
+ # return {
390
+ # "status": "healthy",
391
+ # "service": "Scholar's Archive - Document Intelligence System",
392
+ # "version": "1.0.0",
393
+ # "rag_system_initialized": rag_system is not None,
394
+ # "indexed_documents": doc_count,
395
+ # "timestamp": datetime.now().isoformat(),
396
+ # "message": "Scholar's Archive is operational and ready to serve"
397
+ # }
398
+ # except Exception as e:
399
+ # logger.error(f"Health check error: {e}")
400
+ # return {
401
+ # "status": "degraded",
402
+ # "service": "Scholar's Archive - Document Intelligence System",
403
+ # "error": str(e),
404
+ # "timestamp": datetime.now().isoformat()
405
+ # }
406
 
407
+ # @app.get("/api/info")
408
+ # async def api_info():
409
+ # """Get API information"""
410
+ # return {
411
+ # "name": "Scholar's Archive API",
412
+ # "description": "Document Intelligence System API",
413
+ # "version": "1.0.0",
414
+ # "endpoints": {
415
+ # "upload": "POST /upload - Upload documents",
416
+ # "ask": "POST /ask - Ask questions",
417
+ # "documents": "GET /documents - List documents",
418
+ # "stats": "GET /stats - Get statistics",
419
+ # "search": "POST /search - Search chunks",
420
+ # "clear": "DELETE /clear - Clear all documents",
421
+ # "health": "GET /health - Health check"
422
+ # },
423
+ # "supported_formats": [".pdf", ".docx", ".txt", ".csv"],
424
+ # "max_file_size": "50MB"
425
+ # }
426
 
427
+ # # Event handlers
428
+ # @app.on_event("startup")
429
+ # async def startup_event():
430
+ # """Application startup event"""
431
+ # logger.info("Starting Scholar's Archive - Document Intelligence System")
432
+ # logger.info("System initialized and ready to serve scholarly inquiries")
433
 
434
+ # @app.on_event("shutdown")
435
+ # async def shutdown_event():
436
+ # """Application shutdown event"""
437
+ # if rag_system:
438
+ # rag_system.close()
439
+ # logger.info("Scholar's Archive system closed gracefully")
440
+ # logger.info("Scholar's Archive shutdown complete")
441
 
442
+ # # Error handlers
443
+ # @app.exception_handler(404)
444
+ # async def not_found_handler(request: Request, exc):
445
+ # """Custom 404 handler"""
446
+ # return JSONResponse(
447
+ # status_code=404,
448
+ # content={
449
+ # "detail": "The requested resource was not found in the Scholar's Archive",
450
+ # "path": str(request.url.path)
451
+ # }
452
+ # )
453
 
454
+ # @app.exception_handler(500)
455
+ # async def internal_error_handler(request: Request, exc):
456
+ # """Custom 500 handler"""
457
+ # logger.error(f"Internal server error: {exc}")
458
+ # return JSONResponse(
459
+ # status_code=500,
460
+ # content={
461
+ # "detail": "An internal error occurred in the Scholar's Archive system",
462
+ # "message": "Please try again later or contact support"
463
+ # }
464
+ # )
465
 
466
+ # # Main execution
467
+ # if __name__ == "__main__":
468
+ # import uvicorn
469
+
470
+ # logger.info("Launching Scholar's Archive - Document Intelligence System")
471
+ # logger.info("Access the interface at: http://localhost:8000")
472
+ # logger.info("API documentation at: http://localhost:8000/api/docs")
473
+
474
+ # uvicorn.run(
475
+ # app,
476
+ # host="0.0.0.0",
477
+ # port=7860,
478
+ # log_level="info",
479
+ # reload=False,
480
+ # access_log=True
481
+ # )
482
 
483
 
484
+ # complete worked code
485
  # from fastapi import FastAPI, File, UploadFile, HTTPException, Request
486
  # from fastapi.responses import HTMLResponse, JSONResponse
487
  # from fastapi.staticfiles import StaticFiles
 
490
  # import os
491
  # import tempfile
492
  # import shutil
493
+ # from typing import List, Dict, Any, Optional, Union
494
  # import logging
495
+ # from datetime import datetime
496
+ # import mimetypes
497
 
498
+ # # Import your RAG system
499
  # import sys
 
500
  # sys.path.append(os.path.dirname(os.path.abspath(__file__)))
 
501
  # try:
502
  # from RAG import RAGSystem
503
  # except ImportError:
 
505
  # print("Make sure RAG.py is in the same directory as app.py")
506
  # sys.exit(1)
507
 
508
+ # # Configure logging
509
+ # logging.basicConfig(
510
+ # level=logging.INFO,
511
+ # format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
512
+ # )
513
  # logger = logging.getLogger(__name__)
514
 
515
+ # # Initialize FastAPI app
516
+ # app = FastAPI(
517
+ # title="Scholar's Archive - Document Intelligence System",
518
+ # description="A sophisticated platform for intelligent document analysis and question answering using advanced retrieval-augmented generation technology",
519
+ # version="1.0.0",
520
+ # docs_url="/api/docs",
521
+ # redoc_url="/api/redoc"
522
+ # )
523
 
524
  # # Setup templates directory
525
  # templates = Jinja2Templates(directory="templates")
 
533
  # # Initialize RAG System
534
  # try:
535
  # rag_system = RAGSystem()
536
+ # logger.info("Scholar's Archive RAG System initialized successfully")
537
  # except Exception as e:
538
  # logger.error(f"Failed to initialize RAG System: {e}")
539
  # rag_system = None
540
 
541
+ # # Pydantic models
542
  # class QuestionRequest(BaseModel):
543
  # question: str
544
  # top_k: int = 3
545
 
546
+ # class DocumentInfo(BaseModel):
547
+ # title: str
548
+ # file_type: str
549
+ # upload_date: str
550
+ # chunk_count: int
551
+ # total_pages: Optional[int] = None
552
+
553
+ # class AnswerResponse(BaseModel):
554
+ # answer: str
555
+ # sources: List[Dict[str, Any]]
556
+ # question_chunks: List[Union[str, Dict[str, Any]]] = []
557
+ # relevant_chunks: List[Union[str, Dict[str, Any]]] = []
558
+
559
+ # class StatsResponse(BaseModel):
560
+ # total_documents: int
561
+ # total_chunks: int
562
+ # total_pages: int
563
+ # documents: List[DocumentInfo]
564
+
565
+ # # Utility functions
566
+ # def get_file_type_icon(filename: str) -> str:
567
+ # """Get appropriate icon for file type"""
568
+ # ext = os.path.splitext(filename)[1].lower()
569
+ # icons = {
570
+ # '.pdf': 'fas fa-file-pdf',
571
+ # '.docx': 'fas fa-file-word',
572
+ # '.txt': 'fas fa-file-alt',
573
+ # '.csv': 'fas fa-file-csv'
574
+ # }
575
+ # return icons.get(ext, 'fas fa-file')
576
+
577
+ # def format_file_size(size_bytes: int) -> str:
578
+ # """Format file size in human readable format"""
579
+ # if size_bytes == 0:
580
+ # return "0 B"
581
+ # size_names = ["B", "KB", "MB", "GB"]
582
+ # i = 0
583
+ # while size_bytes >= 1024 and i < len(size_names) - 1:
584
+ # size_bytes /= 1024.0
585
+ # i += 1
586
+ # return f"{size_bytes:.1f} {size_names[i]}"
587
+
588
+ # def extract_content_from_chunks(chunks):
589
+ # """Extract string content from chunk data structures"""
590
+ # if not chunks:
591
+ # return []
592
+
593
+ # extracted = []
594
+ # for chunk in chunks:
595
+ # if isinstance(chunk, str):
596
+ # extracted.append(chunk)
597
+ # elif isinstance(chunk, dict):
598
+ # # Try different possible keys for text content
599
+ # content = chunk.get('text') or chunk.get('content') or chunk.get('document') or str(chunk)
600
+ # extracted.append(content)
601
+ # else:
602
+ # extracted.append(str(chunk))
603
+
604
+ # return extracted
605
+
606
+
607
+
608
+ # # Routes
609
  # @app.get("/", response_class=HTMLResponse)
610
  # async def read_root(request: Request):
611
+ # """Serve the main classical interface"""
612
  # try:
613
  # return templates.TemplateResponse("index.html", {"request": request})
614
  # except Exception as e:
615
  # logger.error(f"Error serving index.html from templates folder: {e}")
616
+ # # Return the embedded HTML if templates folder is not available
617
+ # with open("scholar_archive.html", "r", encoding="utf-8") as f:
618
+ # html_content = f.read()
619
+ # return HTMLResponse(content=html_content)
 
 
 
 
 
 
 
 
 
 
 
620
 
621
  # @app.post("/upload")
622
  # async def upload_document(file: UploadFile = File(...)):
623
+ # """Upload and process a document"""
624
  # try:
625
  # if rag_system is None:
626
+ # raise HTTPException(status_code=500, detail="Scholar's Archive system not initialized")
627
 
628
  # if not file.filename:
629
  # raise HTTPException(status_code=400, detail="No file selected")
630
 
631
+ # # Validate file type
632
  # allowed_extensions = ['.pdf', '.docx', '.txt', '.csv']
633
  # file_extension = os.path.splitext(file.filename)[1].lower()
634
 
635
  # if file_extension not in allowed_extensions:
636
+ # raise HTTPException(
637
+ # status_code=400,
638
+ # detail=f"File type {file_extension} not supported. Supported formats: {', '.join(allowed_extensions)}"
639
+ # )
640
+
641
+ # # Check file size (limit to 50MB)
642
+ # file_size = 0
643
+ # content = await file.read()
644
+ # file_size = len(content)
645
+
646
+ # if file_size > 50 * 1024 * 1024: # 50MB limit
647
+ # raise HTTPException(status_code=400, detail="File size too large. Maximum size is 50MB")
648
 
649
  # # Create temporary file
650
  # with tempfile.NamedTemporaryFile(delete=False, suffix=file_extension) as temp_file:
651
+ # temp_file.write(content)
652
  # temp_path = temp_file.name
653
 
654
+ # logger.info(f"Processing document: {file.filename} ({format_file_size(file_size)})")
655
+
656
  # # Process document
657
  # success = rag_system.add_document(temp_path)
658
 
 
663
  # logger.warning(f"Failed to cleanup temp file: {cleanup_error}")
664
 
665
  # if success:
666
+ # logger.info(f"Successfully processed document: {file.filename}")
667
+ # return JSONResponse(content={
668
+ # "message": f"Document '{file.filename}' has been successfully added to the Scholar's Archive",
669
+ # "filename": file.filename,
670
+ # "size": format_file_size(file_size),
671
+ # "type": file_extension
672
+ # })
673
  # else:
674
  # raise HTTPException(status_code=500, detail="Failed to process document")
675
 
 
679
  # logger.error(f"Upload error: {e}", exc_info=True)
680
  # raise HTTPException(status_code=500, detail=f"Internal server error: {str(e)}")
681
 
682
+ # @app.post("/ask", response_model=AnswerResponse)
683
  # async def ask_question(request: QuestionRequest):
684
+ # """Ask a question about the uploaded documents"""
685
  # try:
686
  # if rag_system is None:
687
+ # raise HTTPException(status_code=500, detail="Scholar's Archive system not initialized")
688
 
689
  # if not request.question.strip():
690
  # raise HTTPException(status_code=400, detail="Question cannot be empty")
691
 
692
+ # logger.info(f"Processing question: {request.question[:100]}...")
693
+
694
+ # # Get answer from RAG system
695
  # result = rag_system.ask_question(request.question, top_k=request.top_k)
696
 
697
+ # # Handle the chunks data properly
698
+ # question_chunks = result.get("question_chunks", [])
699
+ # relevant_chunks = result.get("relevant_chunks", [])
700
+
701
+ # # Add page numbers to sources
702
+ # sources = result.get("sources", [])
703
+ # for source in sources:
704
+ # if isinstance(source, dict):
705
+ # page_num = source.get('page_number') # Changed from 'page' to 'page_number'
706
+ # if page_num:
707
+ # source['page_reference'] = f"Page {page_num}"
708
+
709
+ # # Log the structure to understand what we're getting
710
+ # logger.info(f"Question chunks type: {type(question_chunks)}")
711
+ # logger.info(f"Relevant chunks type: {type(relevant_chunks)}")
712
+ # if question_chunks:
713
+ # logger.info(f"First question chunk type: {type(question_chunks[0])}")
714
+ # if relevant_chunks:
715
+ # logger.info(f"First relevant chunk type: {type(relevant_chunks[0])}")
716
+
717
+ # # Format the response - keep original structure but ensure it's serializable
718
+ # response = AnswerResponse(
719
+ # answer=result["answer"],
720
+ # sources=sources,
721
+ # question_chunks=question_chunks,
722
+ # relevant_chunks=relevant_chunks
723
+ # )
724
+
725
+ # logger.info(f"Successfully answered question with {len(sources)} sources")
726
+
727
+ # return response
728
+
729
+ # except HTTPException:
730
+ # raise
731
+ # except Exception as e:
732
+ # logger.error(f"Question processing error: {e}", exc_info=True)
733
+ # raise HTTPException(status_code=500, detail=f"Internal server error: {str(e)}")
734
+
735
+ # @app.get("/documents")
736
+ # async def get_documents():
737
+ # """Get list of all uploaded documents"""
738
+ # try:
739
+ # if rag_system is None:
740
+ # raise HTTPException(status_code=500, detail="Scholar's Archive system not initialized")
741
+
742
+ # docs = rag_system.list_documents()
743
+
744
+ # # Format documents with additional metadata
745
+ # formatted_docs = []
746
+ # for doc in docs:
747
+ # formatted_doc = {
748
+ # "title": doc.get("title", "Unknown Document"),
749
+ # "chunk_count": doc.get("chunk_count", 0),
750
+ # "total_pages": doc.get("total_pages"),
751
+ # "file_type": os.path.splitext(doc.get("title", ""))[1].lower(),
752
+ # "upload_date": doc.get("upload_date", datetime.now().isoformat()),
753
+ # "icon": get_file_type_icon(doc.get("title", ""))
754
+ # }
755
+ # formatted_docs.append(formatted_doc)
756
+
757
+ # return JSONResponse(content={"documents": formatted_docs})
758
 
759
  # except HTTPException:
760
  # raise
761
  # except Exception as e:
762
+ # logger.error(f"Documents list error: {e}", exc_info=True)
763
  # raise HTTPException(status_code=500, detail=f"Internal server error: {str(e)}")
764
 
765
+ # @app.get("/stats", response_model=StatsResponse)
766
+ # async def get_stats():
767
+ # """Get statistics about the document collection"""
768
  # try:
769
  # if rag_system is None:
770
+ # raise HTTPException(status_code=500, detail="Scholar's Archive system not initialized")
771
 
772
  # docs = rag_system.list_documents()
773
+
774
+ # total_chunks = sum(doc.get("chunk_count", 0) for doc in docs)
775
+ # total_pages = sum(doc.get("total_pages", 1) for doc in docs if doc.get("total_pages"))
776
+
777
+ # # Format documents
778
+ # formatted_docs = []
779
+ # for doc in docs:
780
+ # formatted_doc = DocumentInfo(
781
+ # title=doc.get("title", "Unknown Document"),
782
+ # file_type=os.path.splitext(doc.get("title", ""))[1].lower(),
783
+ # upload_date=doc.get("upload_date", datetime.now().isoformat()),
784
+ # chunk_count=doc.get("chunk_count", 0),
785
+ # total_pages=doc.get("total_pages")
786
+ # )
787
+ # formatted_docs.append(formatted_doc)
788
+
789
+ # stats = StatsResponse(
790
+ # total_documents=len(docs),
791
+ # total_chunks=total_chunks,
792
+ # total_pages=total_pages,
793
+ # documents=formatted_docs
794
+ # )
795
+
796
+ # return stats
797
+
798
  # except HTTPException:
799
  # raise
800
  # except Exception as e:
801
+ # logger.error(f"Stats error: {e}", exc_info=True)
802
  # raise HTTPException(status_code=500, detail=f"Internal server error: {str(e)}")
803
 
804
  # @app.delete("/clear")
805
  # async def clear_documents():
806
+ # """Clear all documents from the archive"""
807
  # try:
808
  # if rag_system is None:
809
+ # raise HTTPException(status_code=500, detail="Scholar's Archive system not initialized")
810
 
811
+ # logger.info("Clearing all documents from Scholar's Archive")
812
+
813
  # success = rag_system.clear_index()
814
  # if success:
815
+ # logger.info("Successfully cleared all documents")
816
+ # return JSONResponse(content={
817
+ # "message": "All documents have been successfully removed from the Scholar's Archive"
818
+ # })
819
  # else:
820
  # raise HTTPException(status_code=500, detail="Failed to clear documents")
821
+
822
  # except HTTPException:
823
  # raise
824
  # except Exception as e:
 
827
 
828
  # @app.post("/search")
829
  # async def search_chunks(request: QuestionRequest):
830
+ # """Search for relevant document chunks"""
831
  # try:
832
  # if rag_system is None:
833
+ # raise HTTPException(status_code=500, detail="Scholar's Archive system not initialized")
834
 
835
  # if not request.question.strip():
836
  # raise HTTPException(status_code=400, detail="Search query cannot be empty")
837
 
838
+ # logger.info(f"Searching chunks for: {request.question[:100]}...")
839
+
840
  # chunks = rag_system.doc_processor.search_chunks(request.question, top_k=request.top_k)
841
 
842
+ # # Format chunks with additional metadata
843
+ # formatted_chunks = []
844
+ # for chunk in chunks:
845
+ # formatted_chunk = {
846
+ # "content": chunk.get("text", ""),
847
+ # "document": chunk.get("document", "Unknown"),
848
+ # "similarity": chunk.get("similarity", 0.0),
849
+ # "page": chunk.get("page_number"), # Changed from 'page' to 'page_number'
850
+ # "chunk_index": chunk.get("chunk_index")
851
+ # }
852
+ # formatted_chunks.append(formatted_chunk)
853
+
854
  # return JSONResponse(content={
855
  # "query": request.question,
856
+ # "chunks": formatted_chunks,
857
+ # "total_found": len(formatted_chunks)
858
  # })
859
 
860
  # except HTTPException:
 
865
 
866
  # @app.get("/health")
867
  # async def health_check():
868
+ # """Health check endpoint"""
 
 
 
 
 
 
 
 
869
  # try:
870
+ # doc_count = len(rag_system.list_documents()) if rag_system else 0
 
 
 
 
 
 
 
 
 
 
 
 
 
871
 
872
+ # return {
873
+ # "status": "healthy",
874
+ # "service": "Scholar's Archive - Document Intelligence System",
875
+ # "version": "1.0.0",
876
+ # "rag_system_initialized": rag_system is not None,
877
+ # "indexed_documents": doc_count,
878
+ # "timestamp": datetime.now().isoformat(),
879
+ # "message": "Scholar's Archive is operational and ready to serve"
880
+ # }
881
  # except Exception as e:
882
+ # logger.error(f"Health check error: {e}")
883
+ # return {
884
+ # "status": "degraded",
885
+ # "service": "Scholar's Archive - Document Intelligence System",
886
+ # "error": str(e),
887
+ # "timestamp": datetime.now().isoformat()
888
+ # }
889
+
890
+ # @app.get("/api/info")
891
+ # async def api_info():
892
+ # """Get API information"""
893
+ # return {
894
+ # "name": "Scholar's Archive API",
895
+ # "description": "Document Intelligence System API",
896
+ # "version": "1.0.0",
897
+ # "endpoints": {
898
+ # "upload": "POST /upload - Upload documents",
899
+ # "ask": "POST /ask - Ask questions",
900
+ # "documents": "GET /documents - List documents",
901
+ # "stats": "GET /stats - Get statistics",
902
+ # "search": "POST /search - Search chunks",
903
+ # "clear": "DELETE /clear - Clear all documents",
904
+ # "health": "GET /health - Health check"
905
+ # },
906
+ # "supported_formats": [".pdf", ".docx", ".txt", ".csv"],
907
+ # "max_file_size": "50MB"
908
+ # }
909
+
910
+ # # Event handlers
911
+ # @app.on_event("startup")
912
+ # async def startup_event():
913
+ # """Application startup event"""
914
+ # logger.info("Starting Scholar's Archive - Document Intelligence System")
915
+ # logger.info("System initialized and ready to serve scholarly inquiries")
916
 
917
  # @app.on_event("shutdown")
918
  # async def shutdown_event():
919
+ # """Application shutdown event"""
920
  # if rag_system:
921
  # rag_system.close()
922
+ # logger.info("Scholar's Archive system closed gracefully")
923
+ # logger.info("Scholar's Archive shutdown complete")
924
+
925
+ # # Error handlers
926
+ # @app.exception_handler(404)
927
+ # async def not_found_handler(request: Request, exc):
928
+ # """Custom 404 handler"""
929
+ # return JSONResponse(
930
+ # status_code=404,
931
+ # content={
932
+ # "detail": "The requested resource was not found in the Scholar's Archive",
933
+ # "path": str(request.url.path)
934
+ # }
935
+ # )
936
+
937
+ # @app.exception_handler(500)
938
+ # async def internal_error_handler(request: Request, exc):
939
+ # """Custom 500 handler"""
940
+ # logger.error(f"Internal server error: {exc}")
941
+ # return JSONResponse(
942
+ # status_code=500,
943
+ # content={
944
+ # "detail": "An internal error occurred in the Scholar's Archive system",
945
+ # "message": "Please try again later or contact support"
946
+ # }
947
+ # )
948
 
949
+ # # Main execution
950
  # if __name__ == "__main__":
951
  # import uvicorn
952
+
953
+ # logger.info("Launching Scholar's Archive - Document Intelligence System")
954
+ # logger.info("Access the interface at: http://localhost:8000")
955
+ # logger.info("API documentation at: http://localhost:8000/api/docs")
956
+
957
+ # uvicorn.run(
958
+ # app,
959
+ # host="0.0.0.0",
960
+ # port=7860,
961
+ # log_level="info",
962
+ # reload=False,
963
+ # access_log=True
964
+ # )
965
 
966
 
967
 
968
+ # complatete code
969
  # from fastapi import FastAPI, File, UploadFile, HTTPException, Request
970
  # from fastapi.responses import HTMLResponse, JSONResponse
971
  # from fastapi.staticfiles import StaticFiles
 
1006
  # )
1007
 
1008
  # # Setup templates directory
1009
+ # templates = Jinja2Templates(directory="templates") # Changed to 'static' to serve index.html directly
1010
 
1011
  # # Try to mount static files directory
1012
  # try:
1013
+ # app.mount("/templates", StaticFiles(directory="templates"), name="templates")
1014
  # except Exception as e:
1015
+ # logger.warning(f"templates files directory not found: {e}")
1016
 
1017
  # # Initialize RAG System
1018
  # try:
 
1034
  # chunk_count: int
1035
  # total_pages: Optional[int] = None
1036
 
1037
+ # class ExplanationStep(BaseModel):
1038
+ # title: str
1039
+ # content: str
1040
+ # details: Optional[str] = None
1041
+ # chunks: Optional[List[Dict[str, Any]]] = None # For retrieved chunks info
1042
+ # error: Optional[bool] = False
1043
+
1044
  # class AnswerResponse(BaseModel):
1045
  # answer: str
1046
  # sources: List[Dict[str, Any]]
1047
  # question_chunks: List[Union[str, Dict[str, Any]]] = []
1048
  # relevant_chunks: List[Union[str, Dict[str, Any]]] = []
1049
+ # explanation: Optional[List[ExplanationStep]] = None # Added for explanation
1050
 
1051
  # class StatsResponse(BaseModel):
1052
  # total_documents: int
 
1095
 
1096
  # return extracted
1097
 
1098
+
1099
  # # Routes
1100
  # @app.get("/", response_class=HTMLResponse)
1101
  # async def read_root(request: Request):
 
1104
  # return templates.TemplateResponse("index.html", {"request": request})
1105
  # except Exception as e:
1106
  # logger.error(f"Error serving index.html from templates folder: {e}")
1107
+ # # Fallback if templates directory is not set up correctly
1108
+ # return HTMLResponse(content="<h1>Error: Frontend not found.</h1><p>Please ensure 'index.html' is in the 'templates' directory.</p>")
 
 
1109
 
1110
  # @app.post("/upload")
1111
  # async def upload_document(file: UploadFile = File(...)):
 
1183
  # # Get answer from RAG system
1184
  # result = rag_system.ask_question(request.question, top_k=request.top_k)
1185
 
1186
+ # # Add page numbers to sources
1187
+ # sources = result.get("sources", [])
1188
+ # for source in sources:
1189
+ # if isinstance(source, dict):
1190
+ # page_num = source.get('page_number')
1191
+ # if page_num:
1192
+ # source['page_reference'] = f"Page {page_num}"
 
 
 
 
1193
 
1194
  # # Format the response - keep original structure but ensure it's serializable
1195
  # response = AnswerResponse(
1196
  # answer=result["answer"],
1197
+ # sources=sources,
1198
+ # question_chunks=result.get("question_chunks", []),
1199
+ # relevant_chunks=result.get("relevant_chunks", []),
1200
+ # explanation=None # No explanation for standard ask
1201
  # )
1202
 
1203
+ # logger.info(f"Successfully answered question with {len(sources)} sources")
1204
 
1205
  # return response
1206
 
 
1210
  # logger.error(f"Question processing error: {e}", exc_info=True)
1211
  # raise HTTPException(status_code=500, detail=f"Internal server error: {str(e)}")
1212
 
1213
+ # @app.post("/explain_ask", response_model=AnswerResponse)
1214
+ # async def explain_ask_question(request: QuestionRequest):
1215
+ # """Ask a question and get a detailed explanation of the RAG process."""
1216
+ # try:
1217
+ # if rag_system is None:
1218
+ # raise HTTPException(status_code=500, detail="Scholar's Archive system not initialized")
1219
+
1220
+ # if not request.question.strip():
1221
+ # raise HTTPException(status_code=400, detail="Question cannot be empty")
1222
+
1223
+ # logger.info(f"Processing question for explanation: {request.question[:100]}...")
1224
+
1225
+ # # Get detailed explanation from RAG system
1226
+ # explanation_result = rag_system.explain_retrieval(request.question, top_k=request.top_k)
1227
+
1228
+ # # Add page numbers to sources
1229
+ # sources = explanation_result.get("sources", [])
1230
+ # for source in sources:
1231
+ # if isinstance(source, dict):
1232
+ # page_num = source.get('page_number')
1233
+ # if page_num:
1234
+ # source['page_reference'] = f"Page {page_num}"
1235
+
1236
+ # # Ensure explanation steps are correctly typed for Pydantic
1237
+ # explanation_steps_typed = [ExplanationStep(**step) for step in explanation_result.get("explanation", [])]
1238
+
1239
+ # response = AnswerResponse(
1240
+ # answer=explanation_result["answer"],
1241
+ # sources=sources,
1242
+ # question_chunks=[], # Not directly used for explanation display in frontend
1243
+ # relevant_chunks=[], # Not directly used for explanation display in frontend
1244
+ # explanation=explanation_steps_typed
1245
+ # )
1246
+
1247
+ # logger.info(f"Successfully generated explanation for question: {request.question[:50]}")
1248
+
1249
+ # return response
1250
+
1251
+ # except HTTPException:
1252
+ # raise
1253
+ # except Exception as e:
1254
+ # logger.error(f"Explanation processing error: {e}", exc_info=True)
1255
+ # raise HTTPException(status_code=500, detail=f"Internal server error: {str(e)}")
1256
+
1257
+
1258
  # @app.get("/documents")
1259
  # async def get_documents():
1260
  # """Get list of all uploaded documents"""
 
1366
  # formatted_chunks = []
1367
  # for chunk in chunks:
1368
  # formatted_chunk = {
1369
+ # "content": chunk.get("text", ""),
1370
+ # "document": chunk.get("doc_title", "Unknown"),
1371
  # "similarity": chunk.get("similarity", 0.0),
1372
+ # "page": chunk.get("page_number"),
1373
  # "chunk_index": chunk.get("chunk_index")
1374
  # }
1375
  # formatted_chunks.append(formatted_chunk)
 
1420
  # "endpoints": {
1421
  # "upload": "POST /upload - Upload documents",
1422
  # "ask": "POST /ask - Ask questions",
1423
+ # "explain_ask": "POST /explain_ask - Ask questions with detailed RAG process explanation",
1424
  # "documents": "GET /documents - List documents",
1425
  # "stats": "GET /stats - Get statistics",
1426
  # "search": "POST /search - Search chunks",
 
1491
 
1492
 
1493
 
1494
+
1495
+
1496
  from fastapi import FastAPI, File, UploadFile, HTTPException, Request
1497
  from fastapi.responses import HTMLResponse, JSONResponse
1498
  from fastapi.staticfiles import StaticFiles
 
1533
  )
1534
 
1535
  # Setup templates directory
1536
+ # This line is changed to point to the 'templates' directory as per your setup.
1537
+ templates = Jinja2Templates(directory="templates")
1538
 
1539
+ # Mount static files directory
1540
+ # This remains 'static' for other static assets like CSS, JS, images if they exist in a 'static' folder.
1541
  try:
1542
  app.mount("/static", StaticFiles(directory="static"), name="static")
1543
  except Exception as e:
1544
+ logger.warning(f"Static files directory not found or could not be mounted: {e}")
1545
 
1546
  # Initialize RAG System
1547
  try:
 
1563
  chunk_count: int
1564
  total_pages: Optional[int] = None
1565
 
1566
+ class ExplanationStep(BaseModel):
1567
+ title: str
1568
+ content: str
1569
+ details: Optional[str] = None
1570
+ chunks: Optional[List[Dict[str, Any]]] = None # For retrieved chunks info
1571
+ error: Optional[bool] = False
1572
+
1573
  class AnswerResponse(BaseModel):
1574
  answer: str
1575
  sources: List[Dict[str, Any]]
1576
  question_chunks: List[Union[str, Dict[str, Any]]] = []
1577
  relevant_chunks: List[Union[str, Dict[str, Any]]] = []
1578
+ explanation: Optional[List[ExplanationStep]] = None # Added for explanation
1579
 
1580
  class StatsResponse(BaseModel):
1581
  total_documents: int
 
1606
  i += 1
1607
  return f"{size_bytes:.1f} {size_names[i]}"
1608
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1609
  # Routes
1610
  @app.get("/", response_class=HTMLResponse)
1611
  async def read_root(request: Request):
1612
  """Serve the main classical interface"""
1613
  try:
1614
+ # This will now correctly look for 'index.html' inside the 'templates' directory
1615
  return templates.TemplateResponse("index.html", {"request": request})
1616
  except Exception as e:
1617
+ logger.error(f"Error serving index.html from templates directory: {e}") # Updated log message
1618
+ # Updated fallback message to guide the user correctly
1619
+ return HTMLResponse(content="<h1>Error: Frontend not found.</h1><p>Please ensure 'index.html' is in the 'templates' directory.</p>")
 
 
1620
 
1621
  @app.post("/upload")
1622
  async def upload_document(file: UploadFile = File(...)):
 
1694
  # Get answer from RAG system
1695
  result = rag_system.ask_question(request.question, top_k=request.top_k)
1696
 
 
 
 
 
1697
  # Add page numbers to sources
1698
  sources = result.get("sources", [])
1699
  for source in sources:
1700
  if isinstance(source, dict):
1701
+ page_num = source.get('page_number')
1702
  if page_num:
1703
  source['page_reference'] = f"Page {page_num}"
1704
 
 
 
 
 
 
 
 
 
1705
  # Format the response - keep original structure but ensure it's serializable
1706
  response = AnswerResponse(
1707
  answer=result["answer"],
1708
  sources=sources,
1709
+ question_chunks=result.get("question_chunks", []),
1710
+ relevant_chunks=result.get("relevant_chunks", []),
1711
+ explanation=None # No explanation for standard ask
1712
  )
1713
 
1714
  logger.info(f"Successfully answered question with {len(sources)} sources")
 
1721
  logger.error(f"Question processing error: {e}", exc_info=True)
1722
  raise HTTPException(status_code=500, detail=f"Internal server error: {str(e)}")
1723
 
1724
+ @app.post("/explain_ask", response_model=AnswerResponse)
1725
+ async def explain_ask_question(request: QuestionRequest):
1726
+ """Ask a question and get a detailed explanation of the RAG process."""
1727
+ try:
1728
+ if rag_system is None:
1729
+ raise HTTPException(status_code=500, detail="Scholar's Archive system not initialized")
1730
+
1731
+ if not request.question.strip():
1732
+ raise HTTPException(status_code=400, detail="Question cannot be empty")
1733
+
1734
+ logger.info(f"Processing question for explanation: {request.question[:100]}...")
1735
+
1736
+ # Get detailed explanation from RAG system
1737
+ explanation_result = rag_system.explain_retrieval(request.question, top_k=request.top_k)
1738
+
1739
+ # Add page numbers to sources
1740
+ sources = explanation_result.get("sources", [])
1741
+ for source in sources:
1742
+ if isinstance(source, dict):
1743
+ page_num = source.get('page_number')
1744
+ if page_num:
1745
+ source['page_reference'] = f"Page {page_num}"
1746
+
1747
+ # Ensure explanation steps are correctly typed for Pydantic
1748
+ explanation_steps_typed = [ExplanationStep(**step) for step in explanation_result.get("explanation", [])]
1749
+
1750
+ response = AnswerResponse(
1751
+ answer=explanation_result["answer"],
1752
+ sources=sources,
1753
+ question_chunks=[], # Not directly used for explanation display in frontend
1754
+ relevant_chunks=[], # Not directly used for explanation display in frontend
1755
+ explanation=explanation_steps_typed
1756
+ )
1757
+
1758
+ logger.info(f"Successfully generated explanation for question: {request.question[:50]}")
1759
+
1760
+ return response
1761
+
1762
+ except HTTPException:
1763
+ raise
1764
+ except Exception as e:
1765
+ logger.error(f"Explanation processing error: {e}", exc_info=True)
1766
+ raise HTTPException(status_code=500, detail=f"Internal server error: {str(e)}")
1767
+
1768
+
1769
  @app.get("/documents")
1770
  async def get_documents():
1771
  """Get list of all uploaded documents"""
 
1782
  "title": doc.get("title", "Unknown Document"),
1783
  "chunk_count": doc.get("chunk_count", 0),
1784
  "total_pages": doc.get("total_pages"),
1785
+ "file_type": os.path.splitext(doc.get("title", "") if doc.get("title") else "")[1].lower(), # Handle None title
1786
  "upload_date": doc.get("upload_date", datetime.now().isoformat()),
1787
  "icon": get_file_type_icon(doc.get("title", ""))
1788
  }
 
1813
  for doc in docs:
1814
  formatted_doc = DocumentInfo(
1815
  title=doc.get("title", "Unknown Document"),
1816
+ file_type=os.path.splitext(doc.get("title", "") if doc.get("title") else "")[1].lower(), # Handle None title
1817
  upload_date=doc.get("upload_date", datetime.now().isoformat()),
1818
  chunk_count=doc.get("chunk_count", 0),
1819
  total_pages=doc.get("total_pages")
 
1877
  formatted_chunks = []
1878
  for chunk in chunks:
1879
  formatted_chunk = {
1880
+ "content": chunk.get("text", ""),
1881
+ "document": chunk.get("doc_title", "Unknown"),
1882
  "similarity": chunk.get("similarity", 0.0),
1883
+ "page": chunk.get("page_number"),
1884
  "chunk_index": chunk.get("chunk_index")
1885
  }
1886
  formatted_chunks.append(formatted_chunk)
 
1931
  "endpoints": {
1932
  "upload": "POST /upload - Upload documents",
1933
  "ask": "POST /ask - Ask questions",
1934
+ "explain_ask": "POST /explain_ask - Ask questions with detailed RAG process explanation",
1935
  "documents": "GET /documents - List documents",
1936
  "stats": "GET /stats - Get statistics",
1937
  "search": "POST /search - Search chunks",
 
1996
  log_level="info",
1997
  reload=False,
1998
  access_log=True
1999
+ )
2000
+
2001
+
2002
+
rag_storage/metadata.pkl CHANGED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:ea47f16e743fe3b0236ad60c5bf23262ac2a654bf3471d0ef3da50af2813bd3f
3
- size 53947
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:914888c9de5250152457f0bd7c4c18253a6769a99c5ce8e275ee05ad32e6b448
3
+ size 44
templates/index.html CHANGED
The diff for this file is too large to render. See raw diff