markobinario commited on
Commit
8b04568
·
verified ·
1 Parent(s): 8126956

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +601 -211
app.py CHANGED
@@ -1,240 +1,630 @@
1
- import os
2
  import gradio as gr
3
- import pandas as pd
4
- import numpy as np
5
- from ai_chatbot import AIChatbot
6
- from database_recommender import CourseRecommender
7
- import warnings
8
- import logging
9
-
10
- # Suppress warnings
11
- warnings.filterwarnings('ignore')
12
- logging.getLogger('tensorflow').setLevel(logging.ERROR)
13
-
14
- # Initialize components
15
- try:
16
- chatbot = AIChatbot()
17
- print("✅ Chatbot initialized successfully")
18
- except Exception as e:
19
- print(f"⚠️ Warning: Could not initialize chatbot: {e}")
20
- chatbot = None
21
-
22
- try:
23
- recommender = CourseRecommender()
24
- print("✅ Recommender initialized successfully")
25
- except Exception as e:
26
- print(f"⚠️ Warning: Could not initialize recommender: {e}")
27
- recommender = None
28
-
29
- def chat_with_bot(message, history):
30
- """Handle chatbot interactions"""
31
- if chatbot is None:
32
- return "Sorry, the chatbot is not available at the moment. Please try again later."
33
-
34
- if not message.strip():
35
- return "Please enter a message to start the conversation."
36
-
37
- # Get answer from chatbot
38
- answer, confidence = chatbot.find_best_match(message)
39
-
40
- # For general conversation, just return the answer
41
- # For FAQ questions, include suggested questions
42
- if confidence > 0.7: # High confidence FAQ match
43
- suggested_questions = chatbot.get_suggested_questions(message)
44
- if suggested_questions:
45
- response = f"{answer}\n\n**Related Questions:**\n"
46
- for i, q in enumerate(suggested_questions, 1):
47
- response += f"{i}. {q}\n"
48
- return response
49
-
50
- # For general conversation or low confidence, just return the answer
51
- return answer
52
 
53
- def get_course_recommendations(stanine, gwa, strand, hobbies):
54
- """Get course recommendations"""
55
- if recommender is None:
56
- return "Sorry, the recommendation system is not available at the moment. Please try again later."
57
-
58
- try:
59
- # Validate and convert inputs
60
- try:
61
- stanine = int(stanine.strip()) if stanine else 0
62
- except (ValueError, AttributeError):
63
- return "❌ Stanine score must be a valid number between 1 and 9"
64
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
65
  try:
66
- gwa = float(gwa.strip()) if gwa else 0
67
- except (ValueError, AttributeError):
68
- return "❌ GWA must be a valid number between 75 and 100"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
69
 
70
- # Validate ranges
71
- if not (1 <= stanine <= 9):
72
- return "❌ Stanine score must be between 1 and 9"
73
 
74
- if not (75 <= gwa <= 100):
75
- return "❌ GWA must be between 75 and 100"
 
76
 
77
- if not strand:
78
- return "❌ Please select a strand"
 
 
 
79
 
80
- if not hobbies or not hobbies.strip():
81
- return "❌ Please enter your hobbies/interests"
 
 
 
 
82
 
83
- # Get recommendations
84
- recommendations = recommender.recommend_courses(
85
- stanine=stanine,
86
- gwa=gwa,
87
- strand=strand,
88
- hobbies=hobbies
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
89
  )
90
 
91
- if not recommendations:
92
- return "No recommendations available at the moment."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
93
 
94
- # Format recommendations
95
- response = f"## 🎯 Course Recommendations for You\n\n"
96
- response += f"**Profile:** Stanine {stanine}, GWA {gwa}, {strand} Strand\n"
97
- response += f"**Interests:** {hobbies}\n\n"
 
 
 
98
 
99
- for i, rec in enumerate(recommendations, 1):
100
- response += f"### {i}. {rec['code']} - {rec['name']}\n"
101
- response += f"**Match Score:** {rec.get('rating', rec.get('probability', 0)):.1f}%\n\n"
 
 
 
 
 
 
 
 
 
 
 
102
 
103
- return response
 
 
 
 
 
104
 
105
- except Exception as e:
106
- return f"❌ Error getting recommendations: {str(e)}"
107
-
108
- def get_faqs():
109
- """Get available FAQs"""
110
- if chatbot and chatbot.faqs:
111
- faq_text = "## 📚 Frequently Asked Questions\n\n"
112
- for i, faq in enumerate(chatbot.faqs, 1):
113
- faq_text += f"**{i}. {faq['question']}**\n"
114
- faq_text += f"{faq['answer']}\n\n"
115
- return faq_text
116
- return "No FAQs available at the moment."
117
-
118
- def get_available_courses():
119
- """Get available courses"""
120
- if recommender and recommender.courses:
121
- course_text = "## 🎓 Available Courses\n\n"
122
- for code, name in recommender.courses.items():
123
- course_text += f"**{code}** - {name}\n"
124
- return course_text
125
- return "No courses available at the moment."
126
 
127
- # Create Gradio interface
128
- with gr.Blocks(title="PSAU AI Chatbot & Course Recommender", theme=gr.themes.Soft()) as demo:
129
- gr.Markdown(
130
- """
131
- # 🤖 PSAU AI Chatbot & Course Recommender
132
 
133
- Welcome to the Pangasinan State University AI-powered admission assistant!
134
- Get instant answers to your questions and receive personalized course recommendations.
135
- """
136
- )
137
-
138
- with gr.Tabs():
139
- # Chatbot Tab
140
- with gr.Tab("🤖 AI Chatbot"):
141
- gr.Markdown("""
142
- **Chat with the PSAU AI Assistant!**
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
143
 
144
- I can help you with:
145
- University admission questions
146
- Course information and guidance
147
- General conversation
148
- Academic support
 
 
 
149
 
150
- Just type your message below and I'll respond naturally!
151
- """)
 
 
 
 
 
152
 
153
- chatbot_interface = gr.ChatInterface(
154
- fn=chat_with_bot,
155
- title="PSAU AI Assistant",
156
- description="Chat with me about university admissions, courses, or just say hello!",
157
- examples=[
158
- "Hello!",
159
- "What are the admission requirements?",
160
- "How are you?",
161
- "What courses are available?",
162
- "Tell me about PSAU",
163
- "What can you help me with?",
164
- "Thank you",
165
- "Goodbye"
166
- ],
167
- cache_examples=True
168
- )
169
-
170
- # Course Recommender Tab
171
- with gr.Tab("🎯 Course Recommender"):
172
- gr.Markdown("""
173
- Get personalized course recommendations based on your academic profile and interests!
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
174
 
175
- **Input Guidelines:**
176
- - **Stanine Score**: Enter a number between 1-9 (from your entrance exam)
177
- - **GWA**: Enter your General Weighted Average (75-100)
178
- - **Strand**: Select your senior high school strand
179
- - **Hobbies**: Describe your interests and hobbies in detail
180
- """)
181
 
182
- with gr.Row():
183
- with gr.Column():
184
- stanine_input = gr.Textbox(
185
- label="Stanine Score (1-9)",
186
- placeholder="Enter your stanine score (1-9)",
187
- info="Your stanine score from entrance examination",
188
- value="7"
189
- )
190
- gwa_input = gr.Textbox(
191
- label="GWA (75-100)",
192
- placeholder="Enter your GWA (75-100)",
193
- info="Your General Weighted Average",
194
- value="85.0"
195
- )
196
- strand_input = gr.Dropdown(
197
- choices=["STEM", "ABM", "HUMSS"],
198
- value="STEM",
199
- label="High School Strand",
200
- info="Your senior high school strand"
201
- )
202
- hobbies_input = gr.Textbox(
203
- label="Hobbies & Interests",
204
- placeholder="e.g., programming, gaming, business, teaching, healthcare...",
205
- info="Describe your interests and hobbies"
206
- )
207
-
208
- recommend_btn = gr.Button("Get Recommendations", variant="primary")
209
-
210
- with gr.Column():
211
- recommendations_output = gr.Markdown()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
212
 
213
- recommend_btn.click(
214
- fn=get_course_recommendations,
215
- inputs=[stanine_input, gwa_input, strand_input, hobbies_input],
216
- outputs=recommendations_output
217
- )
218
-
219
- # Information Tab
220
- with gr.Tab("📚 Information"):
221
- with gr.Row():
222
- with gr.Column():
223
- gr.Markdown("### FAQ Section")
224
- faq_btn = gr.Button("Show FAQs")
225
- faq_output = gr.Markdown()
226
- faq_btn.click(fn=get_faqs, outputs=faq_output)
227
-
228
- with gr.Column():
229
- gr.Markdown("### Available Courses")
230
- courses_btn = gr.Button("Show Courses")
231
- courses_output = gr.Markdown()
232
- courses_btn.click(fn=get_available_courses, outputs=courses_output)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
233
 
 
234
  if __name__ == "__main__":
235
- demo.launch(
 
236
  server_name="0.0.0.0",
237
  server_port=7860,
238
  share=False,
239
- show_error=True
240
  )
 
 
1
  import gradio as gr
2
+ import requests
3
+ import json
4
+ import re
5
+ from typing import List, Tuple, Optional
6
+ from difflib import SequenceMatcher
7
+ import string
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8
 
9
+ class AIChatbot:
10
+ def __init__(self, database_url: str = "https://database-dhe2.onrender.com"):
11
+ self.database_url = database_url
12
+ self.conversation_history = []
 
 
 
 
 
 
 
13
 
14
+ # Simple conversation patterns
15
+ self.greeting_patterns = [
16
+ r'\b(hi|hello|hey|good morning|good afternoon|good evening)\b',
17
+ r'\b(how are you|how\'s it going|what\'s up)\b'
18
+ ]
19
+
20
+ self.help_patterns = [
21
+ r'\b(help|assist|support|guide)\b',
22
+ r'\b(what can you do|what do you do|your capabilities)\b'
23
+ ]
24
+
25
+ self.thanks_patterns = [
26
+ r'\b(thank you|thanks|appreciate|grateful)\b'
27
+ ]
28
+
29
+ self.goodbye_patterns = [
30
+ r'\b(bye|goodbye|see you|farewell|exit|quit)\b'
31
+ ]
32
+
33
+ def is_greeting(self, message: str) -> bool:
34
+ """Check if the message is a greeting"""
35
+ message_lower = message.lower()
36
+ for pattern in self.greeting_patterns:
37
+ if re.search(pattern, message_lower):
38
+ return True
39
+ return False
40
+
41
+ def is_help_request(self, message: str) -> bool:
42
+ """Check if the message is asking for help"""
43
+ message_lower = message.lower()
44
+ for pattern in self.help_patterns:
45
+ if re.search(pattern, message_lower):
46
+ return True
47
+ return False
48
+
49
+ def is_thanks(self, message: str) -> bool:
50
+ """Check if the message is expressing thanks"""
51
+ message_lower = message.lower()
52
+ for pattern in self.thanks_patterns:
53
+ if re.search(pattern, message_lower):
54
+ return True
55
+ return False
56
+
57
+ def is_goodbye(self, message: str) -> bool:
58
+ """Check if the message is a goodbye"""
59
+ message_lower = message.lower()
60
+ for pattern in self.goodbye_patterns:
61
+ if re.search(pattern, message_lower):
62
+ return True
63
+ return False
64
+
65
+ def get_greeting_response(self) -> str:
66
+ """Generate a greeting response"""
67
+ responses = [
68
+ "Hello! I'm your AI assistant. How can I help you today?",
69
+ "Hi there! I'm here to assist you with any questions you might have.",
70
+ "Hello! Welcome! I can help you with general conversation or answer specific questions from our database.",
71
+ "Hey! Nice to meet you! What can I do for you today?"
72
+ ]
73
+ import random
74
+ return random.choice(responses)
75
+
76
+ def get_help_response(self) -> str:
77
+ """Generate a help response"""
78
+ return """I'm an AI chatbot that can help you in two ways:
79
+
80
+ 1. **General Conversation**: I can chat with you about various topics, answer greetings, and have friendly conversations.
81
+
82
+ 2. **Specific Questions**: I can search our database for specific information and provide detailed answers to your questions.
83
+
84
+ **Smart Learning**: If I can't find an answer to your question, I'll automatically save it for review so we can improve our knowledge base and provide better answers in the future.
85
+
86
+ Just type your question or start a conversation, and I'll do my best to help you!"""
87
+
88
+ def get_thanks_response(self) -> str:
89
+ """Generate a thanks response"""
90
+ responses = [
91
+ "You're welcome! I'm happy to help.",
92
+ "My pleasure! Feel free to ask if you need anything else.",
93
+ "Glad I could assist you! Is there anything else you'd like to know?",
94
+ "You're very welcome! I'm here whenever you need help."
95
+ ]
96
+ import random
97
+ return random.choice(responses)
98
+
99
+ def get_goodbye_response(self) -> str:
100
+ """Generate a goodbye response"""
101
+ responses = [
102
+ "Goodbye! Have a great day!",
103
+ "See you later! Take care!",
104
+ "Farewell! Feel free to come back anytime.",
105
+ "Bye! I enjoyed chatting with you!"
106
+ ]
107
+ import random
108
+ return random.choice(responses)
109
+
110
+ def save_unanswered_question(self, question: str) -> bool:
111
+ """Save unanswered question to the database"""
112
  try:
113
+ # Try different possible endpoints for saving unanswered questions
114
+ endpoints = [
115
+ f"{self.database_url}/unanswered_questions",
116
+ f"{self.database_url}/api/unanswered_questions",
117
+ f"{self.database_url}/save_question",
118
+ f"{self.database_url}/api/save_question"
119
+ ]
120
+
121
+ for endpoint in endpoints:
122
+ try:
123
+ # Try POST request with JSON body - matching your table structure
124
+ response = requests.post(
125
+ endpoint,
126
+ json={
127
+ "question": question,
128
+ "created_at": self._get_timestamp()
129
+ },
130
+ headers={"Content-Type": "application/json"},
131
+ timeout=10
132
+ )
133
+ if response.status_code in [200, 201]:
134
+ return True
135
+ except:
136
+ try:
137
+ # Try GET request with query parameters
138
+ response = requests.get(
139
+ endpoint,
140
+ params={
141
+ "question": question,
142
+ "created_at": self._get_timestamp()
143
+ },
144
+ timeout=10
145
+ )
146
+ if response.status_code in [200, 201]:
147
+ return True
148
+ except:
149
+ continue
150
+
151
+ return False
152
+
153
+ except Exception as e:
154
+ print(f"Error saving unanswered question: {e}")
155
+ return False
156
+
157
+ def _get_timestamp(self) -> str:
158
+ """Get current timestamp in ISO format"""
159
+ from datetime import datetime
160
+ return datetime.now().isoformat()
161
+
162
+ def _normalize_text(self, text: str) -> str:
163
+ """Normalize text for better matching"""
164
+ # Convert to lowercase
165
+ text = text.lower()
166
+ # Remove punctuation
167
+ text = text.translate(str.maketrans('', '', string.punctuation))
168
+ # Remove extra whitespace
169
+ text = ' '.join(text.split())
170
+ return text
171
+
172
+ def _extract_keywords(self, text: str) -> List[str]:
173
+ """Extract important keywords from text with enhanced processing"""
174
+ # Extended stop words to ignore
175
+ stop_words = {
176
+ 'the', 'a', 'an', 'and', 'or', 'but', 'in', 'on', 'at', 'to', 'for', 'of', 'with', 'by',
177
+ 'is', 'are', 'was', 'were', 'be', 'been', 'being', 'have', 'has', 'had', 'do', 'does', 'did',
178
+ 'will', 'would', 'could', 'should', 'may', 'might', 'can', 'what', 'how', 'when', 'where', 'why',
179
+ 'who', 'which', 'this', 'that', 'these', 'those', 'i', 'you', 'he', 'she', 'it', 'we', 'they',
180
+ 'me', 'him', 'her', 'us', 'them', 'my', 'your', 'his', 'her', 'its', 'our', 'their',
181
+ 'there', 'here', 'some', 'any', 'all', 'each', 'every', 'much', 'many', 'more', 'most',
182
+ 'very', 'just', 'only', 'also', 'even', 'still', 'yet', 'so', 'too', 'well', 'now', 'then'
183
+ }
184
+
185
+ # Normalize and split into words
186
+ words = self._normalize_text(text).split()
187
+
188
+ # Enhanced keyword extraction
189
+ keywords = []
190
+ for word in words:
191
+ # Filter out stop words and very short words
192
+ if word not in stop_words and len(word) > 2:
193
+ # Add the word
194
+ keywords.append(word)
195
+ # Add common variations and stems
196
+ if word.endswith('s') and len(word) > 3:
197
+ keywords.append(word[:-1]) # Remove 's' for plurals
198
+ if word.endswith('ing') and len(word) > 4:
199
+ keywords.append(word[:-3]) # Remove 'ing'
200
+ if word.endswith('ed') and len(word) > 3:
201
+ keywords.append(word[:-2]) # Remove 'ed'
202
+
203
+ return list(set(keywords)) # Remove duplicates
204
+
205
+ def _calculate_similarity(self, text1: str, text2: str) -> float:
206
+ """Calculate similarity between two texts using advanced methods"""
207
+ norm1 = self._normalize_text(text1)
208
+ norm2 = self._normalize_text(text2)
209
 
210
+ # Method 1: Sequence matcher on normalized text
211
+ sequence_similarity = SequenceMatcher(None, norm1, norm2).ratio()
 
212
 
213
+ # Method 2: Enhanced keyword overlap with stemming
214
+ keywords1 = set(self._extract_keywords(text1))
215
+ keywords2 = set(self._extract_keywords(text2))
216
 
217
+ keyword_similarity = 0.0
218
+ if keywords1 and keywords2:
219
+ intersection = keywords1.intersection(keywords2)
220
+ union = keywords1.union(keywords2)
221
+ keyword_similarity = len(intersection) / len(union) if union else 0.0
222
 
223
+ # Method 3: Substring containment (both directions)
224
+ contains_similarity = 0.0
225
+ if norm1 in norm2:
226
+ contains_similarity = max(contains_similarity, 0.9 * (len(norm1) / len(norm2)))
227
+ if norm2 in norm1:
228
+ contains_similarity = max(contains_similarity, 0.9 * (len(norm2) / len(norm1)))
229
 
230
+ # Method 4: Word order similarity
231
+ words1 = norm1.split()
232
+ words2 = norm2.split()
233
+ word_order_similarity = 0.0
234
+ if words1 and words2:
235
+ # Check for common word sequences
236
+ common_sequences = 0
237
+ max_len = min(len(words1), len(words2))
238
+ for i in range(max_len):
239
+ if words1[i] == words2[i]:
240
+ common_sequences += 1
241
+ word_order_similarity = common_sequences / max_len if max_len > 0 else 0.0
242
+
243
+ # Method 5: Semantic similarity using word relationships
244
+ semantic_similarity = self._calculate_semantic_similarity(keywords1, keywords2)
245
+
246
+ # Method 6: Length similarity (shorter queries should match longer answers)
247
+ length_similarity = 0.0
248
+ if len(norm1) > 0 and len(norm2) > 0:
249
+ length_ratio = min(len(norm1), len(norm2)) / max(len(norm1), len(norm2))
250
+ length_similarity = length_ratio * 0.3 # Lower weight for length
251
+
252
+ # Method 7: Phrase matching (for common phrases)
253
+ phrase_similarity = self._calculate_phrase_similarity(norm1, norm2)
254
+
255
+ # Combine all methods with optimized weights
256
+ final_similarity = (
257
+ sequence_similarity * 0.25 +
258
+ keyword_similarity * 0.30 +
259
+ contains_similarity * 0.20 +
260
+ word_order_similarity * 0.10 +
261
+ semantic_similarity * 0.10 +
262
+ length_similarity * 0.03 +
263
+ phrase_similarity * 0.02
264
  )
265
 
266
+ return min(final_similarity, 1.0) # Cap at 1.0
267
+
268
+ def _calculate_semantic_similarity(self, keywords1: set, keywords2: set) -> float:
269
+ """Calculate semantic similarity using word relationships"""
270
+ if not keywords1 or not keywords2:
271
+ return 0.0
272
+
273
+ # Common semantic relationships
274
+ semantic_groups = {
275
+ 'money': {'cost', 'price', 'tuition', 'fee', 'payment', 'money', 'financial', 'aid', 'scholarship'},
276
+ 'time': {'deadline', 'when', 'time', 'date', 'schedule', 'duration', 'period'},
277
+ 'contact': {'contact', 'phone', 'email', 'address', 'office', 'reach', 'call'},
278
+ 'requirements': {'requirement', 'need', 'required', 'must', 'prerequisite', 'condition'},
279
+ 'application': {'apply', 'application', 'submit', 'process', 'procedure'},
280
+ 'programs': {'program', 'course', 'major', 'degree', 'study', 'academic'},
281
+ 'admission': {'admission', 'admit', 'accept', 'enroll', 'entry', 'enter'}
282
+ }
283
 
284
+ # Check if keywords belong to the same semantic group
285
+ semantic_score = 0.0
286
+ for group, words in semantic_groups.items():
287
+ group1_match = any(keyword in words for keyword in keywords1)
288
+ group2_match = any(keyword in words for keyword in keywords2)
289
+ if group1_match and group2_match:
290
+ semantic_score += 0.3
291
 
292
+ return min(semantic_score, 1.0)
293
+
294
+ def _calculate_phrase_similarity(self, text1: str, text2: str) -> float:
295
+ """Calculate similarity based on common phrases"""
296
+ # Common phrases that should match
297
+ common_phrases = [
298
+ ('admission requirements', 'requirements admission'),
299
+ ('financial aid', 'aid financial'),
300
+ ('tuition cost', 'cost tuition'),
301
+ ('application deadline', 'deadline application'),
302
+ ('contact admissions', 'admissions contact'),
303
+ ('gpa requirement', 'requirement gpa'),
304
+ ('academic requirements', 'requirements academic')
305
+ ]
306
 
307
+ phrase_score = 0.0
308
+ for phrase1, phrase2 in common_phrases:
309
+ if (phrase1 in text1 and phrase1 in text2) or (phrase2 in text1 and phrase2 in text2):
310
+ phrase_score += 0.5
311
+ elif (phrase1 in text1 and phrase2 in text2) or (phrase2 in text1 and phrase1 in text2):
312
+ phrase_score += 0.4
313
 
314
+ return min(phrase_score, 1.0)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
315
 
316
+ def _find_best_match(self, user_question: str, database_questions: List[str], threshold: float = 0.25) -> Optional[str]:
317
+ """Find the best matching question from database with improved logic"""
318
+ if not database_questions:
319
+ return None
 
320
 
321
+ best_match = None
322
+ best_score = 0.0
323
+ all_scores = []
324
+
325
+ # Calculate similarity for all questions
326
+ for db_question in database_questions:
327
+ similarity = self._calculate_similarity(user_question, db_question)
328
+ all_scores.append((db_question, similarity))
329
+ if similarity > best_score:
330
+ best_score = similarity
331
+ best_match = db_question
332
+
333
+ # Sort by similarity score
334
+ all_scores.sort(key=lambda x: x[1], reverse=True)
335
+
336
+ # If the best score is above threshold, return it
337
+ if best_score >= threshold:
338
+ return best_match
339
+
340
+ # If no single match is above threshold, try adaptive threshold
341
+ if all_scores:
342
+ # Use the top score if it's reasonably close to threshold
343
+ top_score = all_scores[0][1]
344
+ if top_score >= threshold * 0.8: # 80% of threshold
345
+ return all_scores[0][0]
346
+
347
+ # Last resort: if user question is very short, be more lenient
348
+ if len(user_question.split()) <= 3 and all_scores:
349
+ # For short queries, use a lower threshold
350
+ if all_scores[0][1] >= 0.15:
351
+ return all_scores[0][0]
352
+
353
+ return None
354
+
355
+ def fetch_from_database(self, question: str) -> str:
356
+ """Fetch answer from the database with smart matching"""
357
+ try:
358
+ # First, try to get all available questions for smart matching
359
+ all_questions = self._get_all_questions()
360
 
361
+ # If we have all questions, try smart matching first
362
+ if all_questions:
363
+ best_match = self._find_best_match(question, all_questions)
364
+ if best_match:
365
+ # Try to get answer for the best matching question
366
+ answer = self._get_answer_for_question(best_match)
367
+ if answer and not self._is_no_answer_response(answer):
368
+ return answer
369
 
370
+ # Fallback to original method if smart matching doesn't work
371
+ endpoints = [
372
+ f"{self.database_url}/faqs",
373
+ f"{self.database_url}/search",
374
+ f"{self.database_url}/query",
375
+ f"{self.database_url}/api/faq"
376
+ ]
377
 
378
+ for endpoint in endpoints:
379
+ try:
380
+ # Try GET request with query parameter
381
+ response = requests.get(endpoint, params={"question": question}, timeout=10)
382
+ if response.status_code == 200:
383
+ data = response.json()
384
+ if isinstance(data, dict):
385
+ answer = data.get('answer', data.get('response', str(data)))
386
+ # Check if we got a meaningful answer
387
+ if answer and answer.strip() and not self._is_no_answer_response(answer):
388
+ return answer
389
+ elif isinstance(data, list) and len(data) > 0:
390
+ answer = str(data[0])
391
+ if answer and answer.strip() and not self._is_no_answer_response(answer):
392
+ return answer
393
+ else:
394
+ answer = str(data)
395
+ if answer and answer.strip() and not self._is_no_answer_response(answer):
396
+ return answer
397
+ except:
398
+ try:
399
+ # Try POST request with JSON body
400
+ response = requests.post(
401
+ endpoint,
402
+ json={"question": question},
403
+ headers={"Content-Type": "application/json"},
404
+ timeout=10
405
+ )
406
+ if response.status_code == 200:
407
+ data = response.json()
408
+ if isinstance(data, dict):
409
+ answer = data.get('answer', data.get('response', str(data)))
410
+ # Check if we got a meaningful answer
411
+ if answer and answer.strip() and not self._is_no_answer_response(answer):
412
+ return answer
413
+ elif isinstance(data, list) and len(data) > 0:
414
+ answer = str(data[0])
415
+ if answer and answer.strip() and not self._is_no_answer_response(answer):
416
+ return answer
417
+ else:
418
+ answer = str(data)
419
+ if answer and answer.strip() and not self._is_no_answer_response(answer):
420
+ return answer
421
+ except:
422
+ continue
423
 
424
+ # If no answer found, save the question as unanswered
425
+ self.save_unanswered_question(question)
426
+ return "I'm sorry, I couldn't find a specific answer to your question in our database. I've saved your question for review, and we'll work on providing a better answer in the future. Could you try rephrasing your question or ask me something else?"
 
 
 
427
 
428
+ except requests.exceptions.Timeout:
429
+ # Save the question even if there's a timeout
430
+ self.save_unanswered_question(question)
431
+ return "I'm sorry, the database is taking too long to respond. I've saved your question for review. Please try again in a moment."
432
+ except requests.exceptions.ConnectionError:
433
+ # Save the question even if there's a connection error
434
+ self.save_unanswered_question(question)
435
+ return "I'm sorry, I'm having trouble connecting to our database right now. I've saved your question for review. Please try again later."
436
+ except Exception as e:
437
+ # Save the question even if there's an unexpected error
438
+ self.save_unanswered_question(question)
439
+ return f"I encountered an error while searching our database: {str(e)}. I've saved your question for review. Please try again."
440
+
441
+ def _get_all_questions(self) -> List[str]:
442
+ """Get all available questions from the database for smart matching"""
443
+ try:
444
+ # Try different endpoints to get all questions
445
+ endpoints = [
446
+ f"{self.database_url}/questions",
447
+ f"{self.database_url}/faq/all",
448
+ f"{self.database_url}/api/questions",
449
+ f"{self.database_url}/all_questions"
450
+ ]
451
+
452
+ for endpoint in endpoints:
453
+ try:
454
+ response = requests.get(endpoint, timeout=10)
455
+ if response.status_code == 200:
456
+ data = response.json()
457
+ if isinstance(data, list):
458
+ return [str(item) for item in data]
459
+ elif isinstance(data, dict) and 'questions' in data:
460
+ return [str(q) for q in data['questions']]
461
+ except:
462
+ continue
463
+
464
+ return []
465
+ except:
466
+ return []
467
+
468
+ def _get_answer_for_question(self, question: str) -> Optional[str]:
469
+ """Get answer for a specific question"""
470
+ try:
471
+ endpoints = [
472
+ f"{self.database_url}/faqs",
473
+ f"{self.database_url}/search",
474
+ f"{self.database_url}/query",
475
+ f"{self.database_url}/api/faq"
476
+ ]
477
+
478
+ for endpoint in endpoints:
479
+ try:
480
+ response = requests.get(endpoint, params={"question": question}, timeout=10)
481
+ if response.status_code == 200:
482
+ data = response.json()
483
+ if isinstance(data, dict):
484
+ return data.get('answer', data.get('response', str(data)))
485
+ elif isinstance(data, list) and len(data) > 0:
486
+ return str(data[0])
487
+ else:
488
+ return str(data)
489
+ except:
490
+ try:
491
+ response = requests.post(
492
+ endpoint,
493
+ json={"question": question},
494
+ headers={"Content-Type": "application/json"},
495
+ timeout=10
496
+ )
497
+ if response.status_code == 200:
498
+ data = response.json()
499
+ if isinstance(data, dict):
500
+ return data.get('answer', data.get('response', str(data)))
501
+ elif isinstance(data, list) and len(data) > 0:
502
+ return str(data[0])
503
+ else:
504
+ return str(data)
505
+ except:
506
+ continue
507
 
508
+ return None
509
+ except:
510
+ return None
511
+
512
+ def _is_no_answer_response(self, answer: str) -> bool:
513
+ """Check if the response indicates no answer was found"""
514
+ no_answer_indicators = [
515
+ "no answer",
516
+ "not found",
517
+ "no results",
518
+ "no data",
519
+ "empty",
520
+ "null",
521
+ "none",
522
+ "i don't know",
523
+ "i don't have",
524
+ "cannot find",
525
+ "unable to find"
526
+ ]
527
+
528
+ answer_lower = answer.lower().strip()
529
+ return any(indicator in answer_lower for indicator in no_answer_indicators)
530
+
531
+ def chat(self, message: str, history: List[List[str]]) -> str:
532
+ """Main chat function"""
533
+ if not message.strip():
534
+ return "Please enter a message so I can help you!"
535
+
536
+ # Store conversation history
537
+ self.conversation_history.append(("user", message))
538
+
539
+ # Check for conversation patterns
540
+ if self.is_greeting(message):
541
+ response = self.get_greeting_response()
542
+ elif self.is_help_request(message):
543
+ response = self.get_help_response()
544
+ elif self.is_thanks(message):
545
+ response = self.get_thanks_response()
546
+ elif self.is_goodbye(message):
547
+ response = self.get_goodbye_response()
548
+ else:
549
+ # Try to fetch from database
550
+ response = self.fetch_from_database(message)
551
+
552
+ # Store bot response
553
+ self.conversation_history.append(("bot", response))
554
+
555
+ return response
556
+
557
+ # Initialize the chatbot
558
+ chatbot = AIChatbot()
559
+
560
+ # Create Gradio interface
561
+ def create_interface():
562
+ with gr.Blocks(
563
+ title="AI Chatbot",
564
+ theme=gr.themes.Soft(),
565
+ css="""
566
+ .gradio-container {
567
+ max-width: 800px !important;
568
+ margin: auto !important;
569
+ }
570
+ .chat-message {
571
+ padding: 10px;
572
+ margin: 5px 0;
573
+ border-radius: 10px;
574
+ }
575
+ """
576
+ ) as interface:
577
+
578
+ gr.Markdown(
579
+ """
580
+ # 🤖 AI Chatbot Assistant
581
+
582
+ Welcome! I'm your AI assistant that can help you with:
583
+ - **General conversation** and friendly chat
584
+ - **Specific questions** answered from our knowledge database
585
+
586
+ Just type your message below and I'll do my best to help you!
587
+ """
588
+ )
589
+
590
+ # Chat interface
591
+ chatbot_interface = gr.ChatInterface(
592
+ fn=chatbot.chat,
593
+ title="Chat with AI Assistant",
594
+ description="Ask me anything or just have a conversation!",
595
+ examples=[
596
+ "Hello!",
597
+ "What can you help me with?",
598
+ "How do I contact support?",
599
+ "What are your services?",
600
+ "Thank you for your help!"
601
+ ],
602
+ cache_examples=False,
603
+ retry_btn="🔄 Retry",
604
+ undo_btn="↩️ Undo",
605
+ clear_btn="🗑️ Clear",
606
+ submit_btn="Send",
607
+ stop_btn="⏹️ Stop",
608
+ additional_inputs=None
609
+ )
610
+
611
+ # Footer
612
+ gr.Markdown(
613
+ """
614
+ ---
615
+ **Note**: This chatbot can handle general conversation and search our database for specific information.
616
+ If you don't get the answer you're looking for, try rephrasing your question!
617
+ """
618
+ )
619
+
620
+ return interface
621
 
622
+ # Launch the application
623
  if __name__ == "__main__":
624
+ interface = create_interface()
625
+ interface.launch(
626
  server_name="0.0.0.0",
627
  server_port=7860,
628
  share=False,
629
+ debug=True
630
  )