Ethanshibu commited on
Commit
e48e7db
Β·
verified Β·
1 Parent(s): 5689cd9

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +237 -192
app.py CHANGED
@@ -1,239 +1,284 @@
1
  import os
2
- import gradio as gr
3
  import tempfile
 
 
 
 
 
 
 
 
4
  from typing import List, Optional
5
- import shutil
6
- import uuid
7
-
8
- from langchain_community.embeddings import HuggingFaceEmbeddings
9
- from langchain_community.vectorstores import Chroma
10
- from langchain.text_splitter import RecursiveCharacterTextSplitter
11
- from langchain_community.document_loaders import PyPDFLoader
12
- from langchain.chains import RetrievalQA
13
- from langchain.llms.base import LLM
14
- from groq import Groq
15
-
16
- # ---- Custom LLM using Groq ----
17
- class GroqLLM(LLM):
18
- model: str = "llama3-8b-8192"
19
- api_key: str = ""
20
- temperature: float = 0.7
21
-
22
- def __init__(self, api_key: str, **kwargs):
23
- super().__init__(**kwargs)
24
- self.api_key = api_key
25
-
26
- def _call(self, prompt: str, stop: Optional[List[str]] = None) -> str:
27
- if not self.api_key:
28
- raise ValueError("GROQ API key is required")
29
-
30
- client = Groq(api_key=self.api_key)
31
- messages = [
32
- {"role": "system", "content": "You are Stitch, a friendly and intelligent academic tutor."},
33
- {"role": "user", "content": prompt}
34
- ]
35
- response = client.chat.completions.create(
36
- model=self.model,
37
- messages=messages,
38
- temperature=self.temperature,
39
- )
40
- return response.choices[0].message.content
41
 
42
- @property
43
- def _llm_type(self) -> str:
44
- return "groq-llm"
 
45
 
46
- # --- Global Context ---
47
- rag_context = {"retriever": None, "mode": "Simple"}
 
 
 
48
 
49
- # --- PDF Processor ---
50
- def process_pdf(file, api_key):
51
- if file is None:
52
- return "❌ Please upload a PDF."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
53
 
54
- if not api_key:
55
- return "❌ Please enter your Groq API key."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
56
 
57
  try:
58
- # Create a more persistent temp directory
59
- temp_dir = f"./temp_{uuid.uuid4().hex}"
60
- os.makedirs(temp_dir, exist_ok=True)
61
-
62
- temp_pdf_path = os.path.join(temp_dir, "uploaded.pdf")
63
- shutil.copy(file.name, temp_pdf_path)
64
 
65
- loader = PyPDFLoader(temp_pdf_path)
66
- documents = loader.load()
67
-
68
- if not documents:
69
- return "❌ No content found in PDF."
70
-
71
- splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=50)
72
- chunks = splitter.split_documents(documents)
73
 
74
- embeddings = HuggingFaceEmbeddings(model_name="all-MiniLM-L6-v2")
75
- vectorstore = Chroma.from_documents(chunks, embeddings, persist_directory=temp_dir)
76
- vectorstore.persist()
77
 
78
- rag_context["retriever"] = vectorstore.as_retriever()
79
- rag_context["api_key"] = api_key
80
- return "βœ… PDF processed successfully!"
 
 
 
 
 
81
 
 
82
  except Exception as e:
83
- return f"❌ Failed to process PDF: {str(e)}"
 
84
 
85
- # --- QA Chain Handler ---
86
- def ask_stitch(question, mode, temperature, show_sources):
87
- retriever = rag_context.get("retriever")
88
- api_key = rag_context.get("api_key")
89
-
90
- if not retriever:
91
- return "❌ Please upload and process a PDF first.", ""
92
 
93
- if not api_key:
94
- return "❌ API key not found. Please re-upload PDF with API key.", ""
95
-
96
  try:
97
- llm = GroqLLM(api_key=api_key, temperature=float(temperature))
98
- qa = RetrievalQA.from_chain_type(
99
- llm=llm,
100
- retriever=retriever,
101
- return_source_documents=show_sources
102
- )
103
-
104
- mode_prompt = "Explain simply and clearly." if mode == "Simple" else "Explain in detail with reasoning, examples, and context."
105
- result = qa({"query": f"{mode_prompt}\n\nQuestion: {question}"})
106
- answer = result["result"]
107
 
108
- sources = ""
109
- if show_sources and result.get("source_documents"):
110
- sources = "\n\nπŸ”Ž **Sources:**\n"
111
- for i, doc in enumerate(result.get("source_documents", [])[:3]): # Limit to 3 sources
112
- source_info = doc.metadata.get("source", "Unknown")
113
- page = doc.metadata.get("page", "Unknown")
114
- sources += f"- Source {i+1}: {source_info} (Page: {page})\n"
115
 
116
- return answer, sources
 
 
 
 
 
 
117
 
118
  except Exception as e:
119
- return f"❌ Error: {str(e)}", ""
120
 
121
- def chat_interface(message, history, mode, temperature, show_sources):
 
 
 
 
 
 
122
  if not message.strip():
123
- return history, ""
124
 
125
- answer, sources = ask_stitch(message, mode, temperature, show_sources)
126
- history.append([message, answer + sources])
127
- return history, ""
128
-
129
- # ---- Gradio UI ----
130
- def create_interface():
131
- with gr.Blocks(
132
- theme=gr.themes.Soft(primary_hue="indigo"),
133
- title="Stitch AI Academic Tutor"
134
- ) as app:
135
 
136
- gr.Markdown("""
137
- ## πŸ“˜ Meet **Stitch** β€” Your AI Academic Tutor
138
- Upload academic PDFs and ask questions naturally. Stitch will help you understand complex concepts!
 
139
 
140
- **Note:** You need a Groq API key to use this service. Get one free at [console.groq.com](https://console.groq.com)
141
- """)
142
-
143
- with gr.Row():
144
- with gr.Column(scale=2):
145
- api_key_input = gr.Textbox(
146
- label="πŸ”‘ Groq API Key",
147
- placeholder="Enter your Groq API key here...",
148
- type="password"
149
- )
150
- with gr.Column(scale=2):
151
- pdf_input = gr.File(label="πŸ“„ Upload PDF", file_types=[".pdf"])
152
- with gr.Column(scale=1):
153
- upload_btn = gr.Button("πŸ“₯ Process PDF", variant="primary")
154
 
155
- status = gr.Textbox(label="πŸ“Š Status", interactive=False, value="Ready to process PDF...")
 
 
 
 
 
 
 
 
 
156
 
157
- upload_btn.click(
158
- fn=process_pdf,
159
- inputs=[pdf_input, api_key_input],
160
- outputs=status
 
 
 
 
 
161
  )
162
-
163
- gr.Markdown("### πŸ’¬ Chat with Stitch")
164
 
165
  with gr.Row():
166
  with gr.Column(scale=1):
167
- mode = gr.Radio(
168
- ["Simple", "Deep"],
169
- label="πŸ“š Explanation Mode",
170
- value="Simple"
171
- )
172
- temperature = gr.Slider(
173
- 0.0, 1.0,
174
- value=0.7,
175
- label="πŸŽ›οΈ Creativity Level",
176
- info="Higher = more creative, Lower = more focused"
177
- )
178
- show_sources = gr.Checkbox(
179
- label="πŸ” Show Sources",
180
- value=True,
181
- info="Display source references"
182
  )
183
-
184
- with gr.Column(scale=3):
185
- chatbot = gr.Chatbot(
186
- label="Chat with Stitch",
187
- height=400,
188
- avatar_images=("πŸ‘¨β€πŸŽ“", "πŸ€–")
189
  )
190
 
191
- msg = gr.Textbox(
192
- label="Ask a question",
193
- placeholder="e.g., 'Summarize the main points of chapter 2' or 'Explain the methodology used'",
194
- lines=2
 
195
  )
196
 
197
  with gr.Row():
198
- submit_btn = gr.Button("Send", variant="primary")
199
- clear_btn = gr.Button("Clear Chat")
200
-
 
 
 
 
 
 
 
201
  # Event handlers
202
- def respond(message, history, mode, temp, sources):
203
- return chat_interface(message, history, mode, temp, sources)
204
-
205
- submit_btn.click(
206
- respond,
207
- inputs=[msg, chatbot, mode, temperature, show_sources],
208
- outputs=[chatbot, msg]
209
  )
210
 
211
- msg.submit(
212
- respond,
213
- inputs=[msg, chatbot, mode, temperature, show_sources],
214
- outputs=[chatbot, msg]
 
 
 
215
  )
216
 
217
- clear_btn.click(lambda: [], outputs=chatbot)
218
-
219
- gr.Markdown("""
220
- ### πŸ“‹ How to Use:
221
- 1. **Get API Key**: Sign up at [console.groq.com](https://console.groq.com) for a free Groq API key
222
- 2. **Enter API Key**: Paste your API key in the field above
223
- 3. **Upload PDF**: Choose an academic paper, textbook chapter, or research document
224
- 4. **Process**: Click "Process PDF" and wait for confirmation
225
- 5. **Ask Questions**: Start chatting with Stitch about your document!
226
 
227
- ### πŸ’‘ Example Questions:
228
- - "Summarize the main findings"
229
- - "Explain the methodology in simple terms"
230
- - "What are the key conclusions?"
231
- - "How does this relate to [specific concept]?"
232
- """)
233
-
234
- return app
 
 
 
 
235
 
236
  # Launch the app
237
  if __name__ == "__main__":
238
- app = create_interface()
239
- app.launch()
 
 
 
 
 
1
  import os
 
2
  import tempfile
3
+ import gradio as gr
4
+ import PyPDF2
5
+ import faiss
6
+ import numpy as np
7
+ from gtts import gTTS
8
+ from sentence_transformers import SentenceTransformer
9
+ import requests
10
+ import json
11
  from typing import List, Optional
12
+ import io
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13
 
14
+ # Configuration
15
+ GROQ_API_KEY = "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"
16
+ GROQ_URL = "https://api.groq.com/openai/v1/chat/completions"
17
+ GROQ_MODEL = "llama3-8b-8192"
18
 
19
+ # Global variables to store the current session
20
+ current_index = None
21
+ current_texts = None
22
+ current_embeddings = None
23
+ embed_model = None
24
 
25
+ def initialize_model():
26
+ """Initialize the embedding model"""
27
+ global embed_model
28
+ if embed_model is None:
29
+ embed_model = SentenceTransformer('all-MiniLM-L6-v2')
30
+ return embed_model
31
+
32
+ def load_pdf_text(file_path):
33
+ """Extract text from PDF file"""
34
+ try:
35
+ with open(file_path, 'rb') as file:
36
+ pdf_reader = PyPDF2.PdfReader(file)
37
+ text = ""
38
+ for page in pdf_reader.pages:
39
+ text += page.extract_text() + "\n"
40
+ return text
41
+ except Exception as e:
42
+ return f"Error reading PDF: {str(e)}"
43
+
44
+ def split_text(text, chunk_size=500, overlap=50):
45
+ """Split text into chunks with overlap"""
46
+ chunks = []
47
+ start = 0
48
+ while start < len(text):
49
+ end = min(start + chunk_size, len(text))
50
+ chunks.append(text[start:end])
51
+ start += chunk_size - overlap
52
+ return chunks
53
+
54
+ def build_faiss_index(texts):
55
+ """Build FAISS index from text chunks"""
56
+ global embed_model, current_index, current_texts, current_embeddings
57
+
58
+ embed_model = initialize_model()
59
+ embeddings = embed_model.encode(texts, convert_to_numpy=True)
60
+
61
+ dim = embeddings.shape[1]
62
+ index = faiss.IndexFlatL2(dim)
63
+ index.add(embeddings.astype('float32'))
64
+
65
+ current_index = index
66
+ current_texts = texts
67
+ current_embeddings = embeddings
68
+
69
+ return index
70
+
71
+ def search_relevant_chunks(question, top_k=3):
72
+ """Search for relevant text chunks"""
73
+ global current_index, current_texts, embed_model
74
+
75
+ if current_index is None or current_texts is None:
76
+ return []
77
+
78
+ question_embedding = embed_model.encode([question], convert_to_numpy=True)
79
+ D, I = current_index.search(question_embedding.astype('float32'), top_k)
80
 
81
+ relevant_chunks = [current_texts[i] for i in I[0] if i < len(current_texts)]
82
+ return relevant_chunks
83
+
84
+ def generate_tutor_response(context, question):
85
+ """Generate response using Groq API"""
86
+ prompt = (
87
+ f"You are a friendly and knowledgeable AI tutor. Based on the provided context from the student's notes, "
88
+ f"answer their question in a clear, educational manner. If the context doesn't contain enough information, "
89
+ f"say so and provide general guidance.\n\n"
90
+ f"Context from notes:\n{context}\n\n"
91
+ f"Student Question: {question}\n\n"
92
+ f"Please provide a helpful, tutor-like response:"
93
+ )
94
+
95
+ headers = {
96
+ "Authorization": f"Bearer {GROQ_API_KEY}",
97
+ "Content-Type": "application/json"
98
+ }
99
+
100
+ data = {
101
+ "model": GROQ_MODEL,
102
+ "messages": [
103
+ {"role": "system", "content": "You are a helpful and friendly AI tutor who explains concepts clearly and encourages learning."},
104
+ {"role": "user", "content": prompt}
105
+ ],
106
+ "temperature": 0.7,
107
+ "max_tokens": 1000
108
+ }
109
 
110
  try:
111
+ response = requests.post(GROQ_URL, headers=headers, json=data, timeout=30)
112
+ result = response.json()
 
 
 
 
113
 
114
+ if "error" in result:
115
+ return f"I'm sorry, I encountered an error: {result['error']['message']}"
 
 
 
 
 
 
116
 
117
+ return result["choices"][0]["message"]["content"]
118
+ except Exception as e:
119
+ return f"I'm sorry, I couldn't process your question right now. Error: {str(e)}"
120
 
121
+ def create_audio_response(text):
122
+ """Create audio file from text using gTTS"""
123
+ try:
124
+ tts = gTTS(text=text, lang='en', slow=False)
125
+
126
+ # Create temporary file
127
+ temp_file = tempfile.NamedTemporaryFile(delete=False, suffix='.mp3')
128
+ tts.save(temp_file.name)
129
 
130
+ return temp_file.name
131
  except Exception as e:
132
+ print(f"Error creating audio: {e}")
133
+ return None
134
 
135
+ def upload_pdf(file):
136
+ """Handle PDF upload and processing"""
137
+ if file is None:
138
+ return "Please upload a PDF file.", None
 
 
 
139
 
 
 
 
140
  try:
141
+ # Extract text from PDF
142
+ text = load_pdf_text(file.name)
143
+
144
+ if text.startswith("Error"):
145
+ return text, None
 
 
 
 
 
146
 
147
+ # Split into chunks
148
+ chunks = split_text(text, chunk_size=500, overlap=50)
 
 
 
 
 
149
 
150
+ if len(chunks) == 0:
151
+ return "No text found in the PDF. Please upload a different file.", None
152
+
153
+ # Build FAISS index
154
+ build_faiss_index(chunks)
155
+
156
+ return f"βœ… PDF processed successfully! Found {len(chunks)} text chunks. You can now ask questions about your document.", None
157
 
158
  except Exception as e:
159
+ return f"Error processing PDF: {str(e)}", None
160
 
161
+ def chat_with_pdf(message, history):
162
+ """Handle chat interaction"""
163
+ global current_index, current_texts
164
+
165
+ if current_index is None or current_texts is None:
166
+ return "Please upload a PDF file first before asking questions."
167
+
168
  if not message.strip():
169
+ return "Please ask a question about your uploaded document."
170
 
171
+ try:
172
+ # Find relevant chunks
173
+ relevant_chunks = search_relevant_chunks(message, top_k=3)
 
 
 
 
 
 
 
174
 
175
+ if not relevant_chunks:
176
+ context = "No relevant information found in the uploaded document."
177
+ else:
178
+ context = " ".join(relevant_chunks)
179
 
180
+ # Generate response
181
+ response = generate_tutor_response(context, message)
182
+
183
+ return response
184
+
185
+ except Exception as e:
186
+ return f"I'm sorry, I encountered an error while processing your question: {str(e)}"
 
 
 
 
 
 
 
187
 
188
+ def get_audio_response(message, history):
189
+ """Get audio version of the latest response"""
190
+ if not history:
191
+ return None
192
+
193
+ latest_response = history[-1][1] # Get the latest bot response
194
+ if latest_response:
195
+ audio_file = create_audio_response(latest_response)
196
+ return audio_file
197
+ return None
198
 
199
+ # Create Gradio interface
200
+ def create_interface():
201
+ with gr.Blocks(title="AI PDF Tutor", theme=gr.themes.Soft()) as iface:
202
+ gr.Markdown(
203
+ """
204
+ # πŸ“š AI PDF Tutor with Voice
205
+ Upload your PDF study materials and chat with an AI tutor that can answer questions about your documents.
206
+ The tutor can also read responses aloud!
207
+ """
208
  )
 
 
209
 
210
  with gr.Row():
211
  with gr.Column(scale=1):
212
+ gr.Markdown("### πŸ“„ Upload Your PDF")
213
+ file_upload = gr.File(
214
+ label="Upload PDF Document",
215
+ file_types=[".pdf"],
216
+ type="filepath"
 
 
 
 
 
 
 
 
 
 
217
  )
218
+ upload_status = gr.Textbox(
219
+ label="Upload Status",
220
+ value="No file uploaded yet.",
221
+ interactive=False
 
 
222
  )
223
 
224
+ with gr.Column(scale=2):
225
+ gr.Markdown("### πŸ’¬ Chat with Your Document")
226
+ chatbot = gr.Chatbot(
227
+ label="AI Tutor Chat",
228
+ height=400
229
  )
230
 
231
  with gr.Row():
232
+ msg_input = gr.Textbox(
233
+ label="Ask a question about your document",
234
+ placeholder="e.g., What are the key concepts in chapter 1?",
235
+ lines=2,
236
+ scale=4
237
+ )
238
+ audio_btn = gr.Button("πŸ”Š Listen", scale=1)
239
+
240
+ audio_output = gr.Audio(label="Tutor Response (Audio)", visible=True)
241
+
242
  # Event handlers
243
+ file_upload.change(
244
+ fn=upload_pdf,
245
+ inputs=[file_upload],
246
+ outputs=[upload_status, audio_output]
 
 
 
247
  )
248
 
249
+ msg_input.submit(
250
+ fn=chat_with_pdf,
251
+ inputs=[msg_input, chatbot],
252
+ outputs=[chatbot]
253
+ ).then(
254
+ lambda: "",
255
+ outputs=[msg_input]
256
  )
257
 
258
+ audio_btn.click(
259
+ fn=get_audio_response,
260
+ inputs=[msg_input, chatbot],
261
+ outputs=[audio_output]
262
+ )
 
 
 
 
263
 
264
+ # Examples
265
+ gr.Examples(
266
+ examples=[
267
+ ["What are the main topics covered in this document?"],
268
+ ["Can you summarize the key points?"],
269
+ ["Explain the methodology used in this research."],
270
+ ["What are the conclusions of this study?"]
271
+ ],
272
+ inputs=[msg_input]
273
+ )
274
+
275
+ return iface
276
 
277
  # Launch the app
278
  if __name__ == "__main__":
279
+ interface = create_interface()
280
+ interface.launch(
281
+ server_name="0.0.0.0",
282
+ server_port=7860,
283
+ share=True
284
+ )