mahmoudalrefaey commited on
Commit
4b500d0
·
verified ·
1 Parent(s): b6babda

Upload 2 files

Browse files
Files changed (2) hide show
  1. app.py +363 -0
  2. config.py +108 -0
app.py ADDED
@@ -0,0 +1,363 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ PDFPal - A lightweight, chat-based RAG application
3
+ Built with free, local models and deployable via Gradio
4
+ """
5
+
6
+ import os
7
+ import tempfile
8
+ import gradio as gr
9
+ import time
10
+ from typing import List, Dict, Any
11
+ from pathlib import Path
12
+
13
+ # Import our custom modules
14
+ from modules.pdf_processor import PDFProcessor
15
+ from modules.embedding_manager import EmbeddingManager
16
+ from modules.llm_manager import LLMManager
17
+ from modules.rag_pipeline import RAGPipeline
18
+ from modules.chat_manager import ChatManager
19
+ from config import Config
20
+
21
+ class PDFPalApp:
22
+ """Main PDFPal application using Gradio"""
23
+
24
+ def __init__(self):
25
+ """Initialize the PDFPal application"""
26
+ self.chat_manager = ChatManager()
27
+ self.rag_pipeline = None
28
+ self.uploaded_files = []
29
+ self.current_model = Config.DEFAULT_LLM_MODEL
30
+
31
+ # Initialize components
32
+ self.pdf_processor = PDFProcessor()
33
+ self.embedding_manager = EmbeddingManager()
34
+ self.llm_manager = None
35
+
36
+ # Create Gradio interface
37
+ self.interface = self._create_interface()
38
+
39
+ def _create_interface(self):
40
+ """Create the Gradio interface"""
41
+
42
+ # Custom CSS for better styling
43
+ css = """
44
+ .gradio-container {
45
+ max-width: 1200px !important;
46
+ margin: auto !important;
47
+ }
48
+ .chat-container {
49
+ height: 600px;
50
+ overflow-y: auto;
51
+ border: 1px solid #e0e0e0;
52
+ border-radius: 8px;
53
+ padding: 20px;
54
+ background: #fafafa;
55
+ }
56
+ .file-upload {
57
+ border: 2px dashed #007bff;
58
+ border-radius: 8px;
59
+ padding: 20px;
60
+ text-align: center;
61
+ background: #f8f9fa;
62
+ }
63
+ """
64
+
65
+ with gr.Blocks(css=css, title="PDFPal - AI Chatbot", theme=gr.themes.Soft()) as interface:
66
+
67
+ # Header
68
+ gr.Markdown("""
69
+ # 📚 PDFPal - AI Chatbot
70
+
71
+ **Chat with your PDF documents using local AI models!**
72
+
73
+ Upload one or more PDF files and start asking questions in natural language.
74
+ """)
75
+
76
+ with gr.Row():
77
+ with gr.Column(scale=1):
78
+ # Sidebar for configuration
79
+ gr.Markdown("### ⚙️ Configuration")
80
+
81
+ # Model selection
82
+ model_dropdown = gr.Dropdown(
83
+ choices=Config.get_model_names(),
84
+ value=Config.get_recommended_model(),
85
+ label="🤖 Language Model",
86
+ info="Choose a lightweight local model"
87
+ )
88
+
89
+ # Advanced settings
90
+ with gr.Accordion("🔧 Advanced Settings", open=False):
91
+ chunk_size = gr.Slider(
92
+ minimum=500, maximum=2000, value=800, step=100,
93
+ label="Chunk Size", info="Size of text chunks (smaller = faster)"
94
+ )
95
+ chunk_overlap = gr.Slider(
96
+ minimum=50, maximum=500, value=100, step=50,
97
+ label="Chunk Overlap", info="Overlap between chunks"
98
+ )
99
+ max_tokens = gr.Slider(
100
+ minimum=100, maximum=1000, value=300, step=50,
101
+ label="Max Response Tokens", info="Maximum response length (smaller = faster)"
102
+ )
103
+ temperature = gr.Slider(
104
+ minimum=0.0, maximum=1.0, value=0.7, step=0.1,
105
+ label="Temperature", info="Creativity level"
106
+ )
107
+
108
+ # File upload section
109
+ gr.Markdown("### 📁 Upload Documents")
110
+ file_upload = gr.File(
111
+ file_count="multiple",
112
+ file_types=[".pdf"],
113
+ label="Choose PDF files"
114
+ )
115
+
116
+ process_btn = gr.Button("🔄 Process Documents", variant="primary")
117
+ process_status = gr.Textbox(label="Status", interactive=False)
118
+
119
+ # Model info
120
+ model_info = gr.JSON(label="Model Information", visible=False)
121
+
122
+ with gr.Column(scale=2):
123
+ # Chat interface
124
+ gr.Markdown("### 💬 Chat Interface")
125
+
126
+ # Chat history display
127
+ chat_history = gr.Chatbot(
128
+ label="Conversation",
129
+ height=500,
130
+ show_label=False,
131
+ container=True,
132
+ bubble_full_width=False
133
+ )
134
+
135
+ # Chat input
136
+ with gr.Row():
137
+ chat_input = gr.Textbox(
138
+ placeholder="Ask a question about your documents...",
139
+ label="Your Question",
140
+ scale=4
141
+ )
142
+ send_btn = gr.Button("Send", variant="primary", scale=1)
143
+
144
+ # Clear chat button
145
+ clear_btn = gr.Button("🗑️ Clear Chat", variant="secondary")
146
+
147
+ # Export options
148
+ with gr.Row():
149
+ export_json_btn = gr.Button("📄 Export JSON")
150
+ export_txt_btn = gr.Button("📝 Export Text")
151
+
152
+ # Statistics
153
+ stats_display = gr.JSON(label="Chat Statistics", visible=False)
154
+
155
+ # Event handlers
156
+ model_dropdown.change(
157
+ fn=self._change_model,
158
+ inputs=[model_dropdown],
159
+ outputs=[model_info, process_status]
160
+ )
161
+
162
+ process_btn.click(
163
+ fn=self._process_documents,
164
+ inputs=[file_upload, chunk_size, chunk_overlap, model_dropdown],
165
+ outputs=[process_status, model_info]
166
+ )
167
+
168
+ send_btn.click(
169
+ fn=self._send_message,
170
+ inputs=[chat_input, max_tokens, temperature],
171
+ outputs=[chat_history, chat_input, stats_display],
172
+ show_progress=True
173
+ )
174
+
175
+ chat_input.submit(
176
+ fn=self._send_message,
177
+ inputs=[chat_input, max_tokens, temperature],
178
+ outputs=[chat_history, chat_input, stats_display],
179
+ show_progress=True
180
+ )
181
+
182
+ clear_btn.click(
183
+ fn=self._clear_chat,
184
+ outputs=[chat_history, stats_display]
185
+ )
186
+
187
+ export_json_btn.click(
188
+ fn=self._export_conversation_json,
189
+ outputs=[gr.File()]
190
+ )
191
+
192
+ export_txt_btn.click(
193
+ fn=self._export_conversation_text,
194
+ outputs=[gr.File()]
195
+ )
196
+
197
+ return interface
198
+
199
+ def _change_model(self, model_name):
200
+ """Change the language model"""
201
+ try:
202
+ self.current_model = model_name
203
+ self.llm_manager = LLMManager(model_name=model_name)
204
+
205
+ model_info = self.llm_manager.get_model_info()
206
+ return model_info, f"✅ Model changed to {model_name}"
207
+ except Exception as e:
208
+ return {}, f"❌ Error changing model: {str(e)}"
209
+
210
+ def _process_documents(self, files, chunk_size, chunk_overlap, model_name):
211
+ """Process uploaded PDF documents"""
212
+ if not files:
213
+ return "⚠️ Please upload PDF files first", {}
214
+
215
+ try:
216
+ # Update processor settings
217
+ self.pdf_processor = PDFProcessor(chunk_size=chunk_size, chunk_overlap=chunk_overlap)
218
+
219
+ # Initialize LLM manager
220
+ self.llm_manager = LLMManager(model_name=model_name)
221
+
222
+ # Process all files
223
+ all_chunks = []
224
+ self.uploaded_files = []
225
+
226
+ for file in files:
227
+ # Handle different file object types from Gradio
228
+ if hasattr(file, 'read'):
229
+ # File-like object
230
+ file_content = file.read()
231
+ file_name = getattr(file, 'name', f'file_{len(self.uploaded_files)}.pdf')
232
+ elif isinstance(file, str):
233
+ # File path string
234
+ with open(file, 'rb') as f:
235
+ file_content = f.read()
236
+ file_name = os.path.basename(file)
237
+ else:
238
+ # Try to get content as bytes
239
+ file_content = bytes(file) if hasattr(file, '__bytes__') else str(file).encode()
240
+ file_name = f'file_{len(self.uploaded_files)}.pdf'
241
+
242
+ # Save uploaded file temporarily
243
+ with tempfile.NamedTemporaryFile(delete=False, suffix='.pdf') as tmp_file:
244
+ tmp_file.write(file_content)
245
+ tmp_path = tmp_file.name
246
+
247
+ try:
248
+ # Process PDF
249
+ chunks = self.pdf_processor.process_pdf(tmp_path)
250
+ all_chunks.extend(chunks)
251
+ self.uploaded_files.append(file_name)
252
+ finally:
253
+ # Clean up temporary file
254
+ os.unlink(tmp_path)
255
+
256
+ if all_chunks:
257
+ # Create knowledge base
258
+ knowledge_base = self.embedding_manager.create_knowledge_base(all_chunks)
259
+
260
+ # Initialize RAG pipeline
261
+ self.rag_pipeline = RAGPipeline(
262
+ knowledge_base=knowledge_base,
263
+ llm_manager=self.llm_manager
264
+ )
265
+
266
+ model_info = self.llm_manager.get_model_info()
267
+ status = f"✅ Processed {len(all_chunks)} text chunks from {len(files)} file(s)"
268
+ return status, model_info
269
+ else:
270
+ return "❌ No text could be extracted from the uploaded files", {}
271
+
272
+ except Exception as e:
273
+ return f"❌ Error processing files: {str(e)}", {}
274
+
275
+ def _send_message(self, message, max_tokens, temperature):
276
+ """Send a message and get response"""
277
+ start_time = time.time()
278
+
279
+ if not message.strip():
280
+ return self.chat_manager.get_messages(), "", {}
281
+
282
+ if not self.rag_pipeline:
283
+ # Add user message
284
+ self.chat_manager.add_message("user", message)
285
+
286
+ # Add error response
287
+ error_msg = "⚠️ Please upload and process documents first!"
288
+ self.chat_manager.add_message("assistant", error_msg)
289
+
290
+ return self.chat_manager.get_messages(), "", self.chat_manager.get_statistics()
291
+
292
+ try:
293
+ # Add user message
294
+ self.chat_manager.add_message("user", message)
295
+
296
+ # Get AI response with timing
297
+ response_start = time.time()
298
+ response = self.rag_pipeline.get_response(
299
+ message,
300
+ max_tokens=max_tokens,
301
+ temperature=temperature
302
+ )
303
+ response_time = time.time() - response_start
304
+
305
+ # Add AI response
306
+ self.chat_manager.add_message("assistant", response)
307
+
308
+ # Add performance info to statistics
309
+ total_time = time.time() - start_time
310
+ stats = self.chat_manager.get_statistics()
311
+ stats.update({
312
+ "response_time_seconds": round(response_time, 2),
313
+ "total_time_seconds": round(total_time, 2),
314
+ "performance_note": f"Response generated in {round(response_time, 2)}s"
315
+ })
316
+
317
+ return self.chat_manager.get_messages(), "", stats
318
+
319
+ except Exception as e:
320
+ error_msg = f"❌ Error: {str(e)}"
321
+ self.chat_manager.add_message("assistant", error_msg)
322
+ return self.chat_manager.get_messages(), "", self.chat_manager.get_statistics()
323
+
324
+ def _clear_chat(self):
325
+ """Clear chat history"""
326
+ self.chat_manager.clear_history()
327
+ return [], {}
328
+
329
+ def _export_conversation_json(self):
330
+ """Export conversation as JSON"""
331
+ try:
332
+ temp_file = tempfile.NamedTemporaryFile(delete=False, suffix='.json')
333
+ self.chat_manager.save_conversation(temp_file.name)
334
+ return temp_file.name
335
+ except Exception as e:
336
+ return None
337
+
338
+ def _export_conversation_text(self):
339
+ """Export conversation as text"""
340
+ try:
341
+ temp_file = tempfile.NamedTemporaryFile(delete=False, suffix='.txt')
342
+ self.chat_manager.export_conversation_text(temp_file.name)
343
+ return temp_file.name
344
+ except Exception as e:
345
+ return None
346
+
347
+ def launch(self, **kwargs):
348
+ """Launch the Gradio interface"""
349
+ return self.interface.launch(**kwargs)
350
+
351
+ def main():
352
+ """Main entry point"""
353
+ app = PDFPalApp()
354
+ app.launch(
355
+ server_name="0.0.0.0",
356
+ server_port=7860,
357
+ share=False,
358
+ debug=True
359
+ )
360
+
361
+ if __name__ == "__main__":
362
+ main()
363
+
config.py ADDED
@@ -0,0 +1,108 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Configuration file for PDFPal RAG application
3
+ Centralized settings for easy customization
4
+ """
5
+
6
+ import os
7
+ from typing import Dict, Any
8
+
9
+ class Config:
10
+ """Application configuration"""
11
+
12
+ # Application settings
13
+ APP_NAME = "PDFPal - AI Chatbot"
14
+ APP_VERSION = "1.0.0"
15
+ DEBUG = os.getenv("DEBUG", "False").lower() == "true"
16
+
17
+ # Model configurations
18
+ DEFAULT_LLM_MODEL = "TinyLlama/TinyLlama-1.1B-Chat-v1.0"
19
+ DEFAULT_EMBEDDING_MODEL = "all-MiniLM-L6-v2"
20
+
21
+ # Available models
22
+ AVAILABLE_MODELS = {
23
+ "TinyLlama/TinyLlama-1.1B-Chat-v1.0": {
24
+ "name": "TinyLlama 1.1B",
25
+ "description": "Fast and efficient 1.1B parameter model",
26
+ "size": "1.1B",
27
+ "recommended": True
28
+ },
29
+ "microsoft/DialoGPT-medium": {
30
+ "name": "DialoGPT Medium",
31
+ "description": "Conversational model optimized for chat",
32
+ "size": "345M",
33
+ "recommended": False
34
+ },
35
+ "microsoft/phi-2": {
36
+ "name": "Phi-2",
37
+ "description": "High-quality 2.7B parameter model",
38
+ "size": "2.7B",
39
+ "recommended": False
40
+ }
41
+ }
42
+
43
+ # Default processing settings
44
+ DEFAULT_CHUNK_SIZE = 800 # Reduced from 1000 for faster processing
45
+ DEFAULT_CHUNK_OVERLAP = 100 # Reduced from 200
46
+ DEFAULT_MAX_TOKENS = 300 # Reduced from 500 for faster generation
47
+ DEFAULT_TEMPERATURE = 0.7
48
+
49
+ # RAG settings
50
+ DEFAULT_RETRIEVAL_K = 2 # Reduced from 4 for faster retrieval
51
+ DEFAULT_SEARCH_TYPE = "similarity"
52
+
53
+ # Chat settings
54
+ MAX_CHAT_HISTORY = 50 # Reduced from 100
55
+ MAX_CONTEXT_MESSAGES = 3 # Reduced from 5
56
+
57
+ # File settings
58
+ MAX_FILE_SIZE_MB = 25 # Reduced from 50
59
+ SUPPORTED_FILE_TYPES = ["pdf"]
60
+
61
+ # Performance settings
62
+ CACHE_RESPONSES = True
63
+ MAX_RETRIEVAL_DOCS = 1 # Reduced from 2 for fastest retrieval
64
+ ENABLE_RESPONSE_CACHING = True
65
+ USE_FASTER_MODEL = True
66
+ OPTIMIZE_FOR_SPEED = True
67
+ ENABLE_GPU = True
68
+ ENABLE_QUANTIZATION = True
69
+ CACHE_DIR = os.getenv("CACHE_DIR", ".cache")
70
+
71
+ # UI settings
72
+ SIDEBAR_EXPANDED = True
73
+ PAGE_LAYOUT = "wide"
74
+
75
+ @classmethod
76
+ def get_model_config(cls, model_name: str) -> Dict[str, Any]:
77
+ """Get configuration for a specific model"""
78
+ return cls.AVAILABLE_MODELS.get(model_name, cls.AVAILABLE_MODELS[cls.DEFAULT_LLM_MODEL])
79
+
80
+ @classmethod
81
+ def get_recommended_model(cls) -> str:
82
+ """Get the recommended model name"""
83
+ for model_name, config in cls.AVAILABLE_MODELS.items():
84
+ if config.get("recommended", False):
85
+ return model_name
86
+ return cls.DEFAULT_LLM_MODEL
87
+
88
+ @classmethod
89
+ def get_model_names(cls) -> list:
90
+ """Get list of available model names"""
91
+ return list(cls.AVAILABLE_MODELS.keys())
92
+
93
+ @classmethod
94
+ def validate_model_name(cls, model_name: str) -> bool:
95
+ """Validate if a model name is supported"""
96
+ return model_name in cls.AVAILABLE_MODELS
97
+
98
+ @classmethod
99
+ def get_ui_config(cls) -> Dict[str, Any]:
100
+ """Get UI configuration"""
101
+ return {
102
+ "page_title": cls.APP_NAME,
103
+ "page_icon": "📚",
104
+ "layout": cls.PAGE_LAYOUT,
105
+ "initial_sidebar_state": "expanded" if cls.SIDEBAR_EXPANDED else "collapsed"
106
+ }
107
+
108
+