Spaces:
Running
Running
Ajey95
commited on
Commit
·
f39ba75
1
Parent(s):
88dd2fa
Restore app source files without FAISS index
Browse files- .gitattributes +36 -0
- Dockerfile +16 -0
- agents/__init__.py +5 -0
- agents/academic_agent.py +466 -0
- agents/agent_helpers.py +15 -0
- agents/drug_info_agent.py +147 -0
- agents/mnemonic_agent.py +148 -0
- agents/quiz_agent.py +158 -0
- agents/router_agent.py +43 -0
- agents/viva_agent.py +117 -0
- app.py +755 -0
- data/academic_knowledge.json +55 -0
- data/quotes.json +10 -0
- knowledge_base/PharmaLite.in Pharmaceutical Biotechnology (Thakur).pdf +3 -0
- requirements.txt +0 -0
- templates/index.html +876 -0
- templates/rakhi.html +331 -0
- templates/rakhi_greeting.html +445 -0
- utils/__init__.py +4 -0
- utils/file_processor.py +172 -0
- utils/helpers.py +112 -0
.gitattributes
ADDED
@@ -0,0 +1,36 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
*.7z filter=lfs diff=lfs merge=lfs -text
|
2 |
+
*.arrow filter=lfs diff=lfs merge=lfs -text
|
3 |
+
*.bin filter=lfs diff=lfs merge=lfs -text
|
4 |
+
*.bz2 filter=lfs diff=lfs merge=lfs -text
|
5 |
+
*.ckpt filter=lfs diff=lfs merge=lfs -text
|
6 |
+
*.ftz filter=lfs diff=lfs merge=lfs -text
|
7 |
+
*.gz filter=lfs diff=lfs merge=lfs -text
|
8 |
+
*.h5 filter=lfs diff=lfs merge=lfs -text
|
9 |
+
*.joblib filter=lfs diff=lfs merge=lfs -text
|
10 |
+
*.lfs.* filter=lfs diff=lfs merge=lfs -text
|
11 |
+
*.mlmodel filter=lfs diff=lfs merge=lfs -text
|
12 |
+
*.model filter=lfs diff=lfs merge=lfs -text
|
13 |
+
*.msgpack filter=lfs diff=lfs merge=lfs -text
|
14 |
+
*.npy filter=lfs diff=lfs merge=lfs -text
|
15 |
+
*.npz filter=lfs diff=lfs merge=lfs -text
|
16 |
+
*.onnx filter=lfs diff=lfs merge=lfs -text
|
17 |
+
*.ot filter=lfs diff=lfs merge=lfs -text
|
18 |
+
*.parquet filter=lfs diff=lfs merge=lfs -text
|
19 |
+
*.pb filter=lfs diff=lfs merge=lfs -text
|
20 |
+
*.pickle filter=lfs diff=lfs merge=lfs -text
|
21 |
+
*.pkl filter=lfs diff=lfs merge=lfs -text
|
22 |
+
*.pt filter=lfs diff=lfs merge=lfs -text
|
23 |
+
*.pth filter=lfs diff=lfs merge=lfs -text
|
24 |
+
*.rar filter=lfs diff=lfs merge=lfs -text
|
25 |
+
*.safetensors filter=lfs diff=lfs merge=lfs -text
|
26 |
+
saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
27 |
+
*.tar.* filter=lfs diff=lfs merge=lfs -text
|
28 |
+
*.tar filter=lfs diff=lfs merge=lfs -text
|
29 |
+
*.tflite filter=lfs diff=lfs merge=lfs -text
|
30 |
+
*.tgz filter=lfs diff=lfs merge=lfs -text
|
31 |
+
*.wasm filter=lfs diff=lfs merge=lfs -text
|
32 |
+
*.xz filter=lfs diff=lfs merge=lfs -text
|
33 |
+
*.zip filter=lfs diff=lfs merge=lfs -text
|
34 |
+
*.zst filter=lfs diff=lfs merge=lfs -text
|
35 |
+
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
36 |
+
*.pdf filter=lfs diff=lfs merge=lfs -text
|
Dockerfile
ADDED
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Use an official lightweight Python image
|
2 |
+
FROM python:3.11-slim
|
3 |
+
|
4 |
+
# Set the working directory inside the container
|
5 |
+
WORKDIR /app
|
6 |
+
|
7 |
+
# Copy the requirements file and install dependencies
|
8 |
+
COPY requirements.txt .
|
9 |
+
RUN pip install --no-cache-dir -r requirements.txt
|
10 |
+
|
11 |
+
# Copy the rest of your application code into the container
|
12 |
+
COPY . .
|
13 |
+
|
14 |
+
# Command to run your application using Gunicorn
|
15 |
+
# Hugging Face Spaces expects the app to run on port 7860
|
16 |
+
CMD ["gunicorn", "--bind", "0.0.0.0:7860", "--timeout", "120", "app:app"]
|
agents/__init__.py
ADDED
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
|
2 |
+
# agents/__init__.py
|
3 |
+
"""
|
4 |
+
Agents package initialization
|
5 |
+
"""
|
agents/academic_agent.py
ADDED
@@ -0,0 +1,466 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# """
|
2 |
+
# Academic Agent - Handles general academic questions
|
3 |
+
# Now with Gemini API integration and file context support!
|
4 |
+
# """
|
5 |
+
|
6 |
+
# import json
|
7 |
+
# import os
|
8 |
+
# import re
|
9 |
+
|
10 |
+
# class AcademicAgent:
|
11 |
+
# def __init__(self, gemini_model=None):
|
12 |
+
# """
|
13 |
+
# Initializes the agent.
|
14 |
+
|
15 |
+
# Args:
|
16 |
+
# gemini_model: An instance of a Gemini model client for AI-powered responses.
|
17 |
+
# If None, the agent will operate in offline/fallback mode.
|
18 |
+
# """
|
19 |
+
# self.model = gemini_model
|
20 |
+
# self.knowledge_base = self.load_knowledge_base()
|
21 |
+
|
22 |
+
# def load_knowledge_base(self):
|
23 |
+
# """Load pre-built academic knowledge base from a JSON file."""
|
24 |
+
# knowledge_file = 'data/academic_knowledge.json'
|
25 |
+
|
26 |
+
# # Create a default knowledge base if the file doesn't exist
|
27 |
+
# if not os.path.exists(knowledge_file):
|
28 |
+
# default_knowledge = {
|
29 |
+
# "pharmacology": {
|
30 |
+
# "definition": "Pharmacology is the branch of medicine concerned with the uses, effects, and modes of action of drugs.",
|
31 |
+
# "branches": ["Pharmacokinetics", "Pharmacodynamics", "Toxicology", "Clinical Pharmacology"],
|
32 |
+
# "importance": "Essential for understanding drug therapy and patient safety"
|
33 |
+
# },
|
34 |
+
# "pharmacokinetics": {
|
35 |
+
# "definition": "The study of how the body affects a drug (ADME: Absorption, Distribution, Metabolism, Excretion)",
|
36 |
+
# "processes": ["Absorption", "Distribution", "Metabolism", "Excretion"],
|
37 |
+
# "factors": ["Age", "Gender", "Disease state", "Genetic factors"]
|
38 |
+
# },
|
39 |
+
# "pharmacodynamics": {
|
40 |
+
# "definition": "The study of what a drug does to the body - drug actions and effects",
|
41 |
+
# "concepts": ["Receptor theory", "Dose-response relationship", "Therapeutic index"],
|
42 |
+
# "mechanisms": ["Agonism", "Antagonism", "Enzyme inhibition"]
|
43 |
+
# },
|
44 |
+
# "krebs_cycle": {
|
45 |
+
# "definition": "A series of enzymatic reactions that generate energy (ATP) from carbohydrates, fats, and proteins",
|
46 |
+
# "location": "Mitochondrial matrix",
|
47 |
+
# "steps": 8,
|
48 |
+
# "importance": "Central metabolic pathway for energy production"
|
49 |
+
# },
|
50 |
+
# "drug_metabolism": {
|
51 |
+
# "definition": "The biochemical modification of drugs by living organisms",
|
52 |
+
# "phases": ["Phase I (oxidation, reduction, hydrolysis)", "Phase II (conjugation reactions)"],
|
53 |
+
# "location": "Primarily liver, also kidneys, lungs, intestines",
|
54 |
+
# "enzymes": "Cytochrome P450 family"
|
55 |
+
# }
|
56 |
+
# }
|
57 |
+
|
58 |
+
# os.makedirs('data', exist_ok=True)
|
59 |
+
# with open(knowledge_file, 'w') as f:
|
60 |
+
# json.dump(default_knowledge, f, indent=2)
|
61 |
+
|
62 |
+
# return default_knowledge
|
63 |
+
|
64 |
+
# try:
|
65 |
+
# with open(knowledge_file, 'r') as f:
|
66 |
+
# return json.load(f)
|
67 |
+
# except json.JSONDecodeError:
|
68 |
+
# print("Error: Could not decode JSON from knowledge base file.")
|
69 |
+
# return {}
|
70 |
+
# except Exception as e:
|
71 |
+
# print(f"Error loading knowledge base: {e}")
|
72 |
+
# return {}
|
73 |
+
|
74 |
+
# def process_with_ai(self, query, file_context=""):
|
75 |
+
# """Use Gemini AI to provide comprehensive, context-aware answers."""
|
76 |
+
# if not self.model:
|
77 |
+
# return None # Fallback to local knowledge if no AI model is provided
|
78 |
+
|
79 |
+
# try:
|
80 |
+
# # Construct a context-aware prompt for the AI
|
81 |
+
# context_section = ""
|
82 |
+
# if file_context:
|
83 |
+
# context_section = f"""
|
84 |
+
# UPLOADED FILE CONTEXT:
|
85 |
+
# ---
|
86 |
+
# {file_context}
|
87 |
+
# ---
|
88 |
+
# Please reference the uploaded content when relevant to answer the question.
|
89 |
+
# """
|
90 |
+
|
91 |
+
# prompt = f"""
|
92 |
+
# You are an expert pharmacy educator and AI tutor specializing in pharmaceutical sciences.
|
93 |
+
# Your role is to help B.Pharmacy students learn complex concepts in an engaging, culturally-sensitive way.
|
94 |
+
|
95 |
+
# STUDENT QUESTION: {query}
|
96 |
+
# {context_section}
|
97 |
+
|
98 |
+
# Please provide a comprehensive answer that includes:
|
99 |
+
# 1. A clear explanation suitable for a pharmacy student.
|
100 |
+
# 2. Key concepts and terminology.
|
101 |
+
# 3. Real-world applications or examples in medicine.
|
102 |
+
# 4. Any important safety considerations (if the topic is drug-related).
|
103 |
+
# 5. Use some Hindi terms naturally where appropriate (like आयुर्वेद, औषधि, etc.) to create a relatable tone.
|
104 |
+
|
105 |
+
# Format your response to be educational, encouraging, and include relevant emojis.
|
106 |
+
# If the question relates to uploaded file content, please reference it specifically in your answer.
|
107 |
+
|
108 |
+
# Remember: You're helping an Indian pharmacy student, so cultural context and an encouraging tone matter!
|
109 |
+
# """
|
110 |
+
|
111 |
+
# response = self.model.generate_content(prompt)
|
112 |
+
# return response.text
|
113 |
+
|
114 |
+
# except Exception as e:
|
115 |
+
# print(f"Gemini API error in Academic Agent: {e}")
|
116 |
+
# return None # Return None to trigger fallback to local knowledge
|
117 |
+
|
118 |
+
# def extract_key_terms(self, query):
|
119 |
+
# """Extract key terms from the query to search the local knowledge base."""
|
120 |
+
# common_words = {'what', 'is', 'the', 'define', 'explain', 'how', 'does', 'work', 'tell', 'me', 'about'}
|
121 |
+
# words = re.findall(r'\b\w+\b', query.lower())
|
122 |
+
# key_terms = [word for word in words if word not in common_words and len(word) > 2]
|
123 |
+
# return key_terms
|
124 |
+
|
125 |
+
# def find_best_match(self, key_terms):
|
126 |
+
# """Find the best matching topic in the local knowledge base using a scoring system."""
|
127 |
+
# best_match = None
|
128 |
+
# max_score = 0
|
129 |
+
|
130 |
+
# for topic, content in self.knowledge_base.items():
|
131 |
+
# score = 0
|
132 |
+
# topic_words = topic.lower().split('_')
|
133 |
+
|
134 |
+
# # Check for matches in topic keywords and content
|
135 |
+
# for term in key_terms:
|
136 |
+
# if term in topic_words:
|
137 |
+
# score += 3
|
138 |
+
# elif term in topic.lower():
|
139 |
+
# score += 2
|
140 |
+
# if isinstance(content, dict):
|
141 |
+
# content_str = str(content).lower()
|
142 |
+
# if term in content_str:
|
143 |
+
# score += 1
|
144 |
+
|
145 |
+
# if score > max_score:
|
146 |
+
# max_score = score
|
147 |
+
# best_match = topic
|
148 |
+
|
149 |
+
# return best_match if max_score > 0 else None
|
150 |
+
|
151 |
+
# def format_response(self, topic, content):
|
152 |
+
# """Format the local knowledge base content in a user-friendly way with Hindi terms."""
|
153 |
+
# if not isinstance(content, dict):
|
154 |
+
# return f"📚 **{topic.replace('_', ' ').title()}**\n\n{content}"
|
155 |
+
|
156 |
+
# response_parts = [f"📚 **{topic.replace('_', ' ').title()}**\n"]
|
157 |
+
|
158 |
+
# key_map = {
|
159 |
+
# 'definition': 'परिभाषा (Definition)',
|
160 |
+
# 'importance': 'महत्व (Importance)',
|
161 |
+
# 'processes': 'प्रक्रियाएं (Processes)',
|
162 |
+
# 'branches': 'शाखाएं (Branches)',
|
163 |
+
# 'concepts': 'मुख्य अवधारणाएं (Key Concepts)',
|
164 |
+
# 'steps': 'चरण (Steps)',
|
165 |
+
# 'location': 'स्थान (Location)',
|
166 |
+
# 'phases': 'चरण (Phases)',
|
167 |
+
# 'enzymes': 'एंजाइम (Enzymes)'
|
168 |
+
# }
|
169 |
+
|
170 |
+
# for key, title in key_map.items():
|
171 |
+
# if key in content:
|
172 |
+
# value = content[key]
|
173 |
+
# if isinstance(value, list):
|
174 |
+
# value = ', '.join(value)
|
175 |
+
# response_parts.append(f"**{title}:** {value}\n")
|
176 |
+
|
177 |
+
# response_parts.append("💡 *Would you like me to create a quiz or mnemonic for this topic?*")
|
178 |
+
# return "\n".join(response_parts)
|
179 |
+
|
180 |
+
# def generate_general_response(self, query, file_context=""):
|
181 |
+
# """Generate a general helpful response when no specific match is found."""
|
182 |
+
# file_mention = " I can also answer questions about any files you've uploaded!" if file_context else ""
|
183 |
+
|
184 |
+
# # More specific greeting if the query mentions pharmacy
|
185 |
+
# if any(word in query.lower() for word in ['pharmacy', 'pharmaceutical', 'drug']):
|
186 |
+
# return f"""📚 **Pharmacy & Pharmaceutical Sciences**
|
187 |
+
|
188 |
+
# Pharmacy is a fascinating field that bridges chemistry, biology, and medicine! Here are the main areas:
|
189 |
+
|
190 |
+
# 🔬 **Core Subjects:**
|
191 |
+
# • Pharmacology (औषधि विज्ञान - drug actions)
|
192 |
+
# • Pharmacokinetics (drug movement in body)
|
193 |
+
# • Medicinal Chemistry (drug design)
|
194 |
+
# • Pharmaceutics (drug formulation)
|
195 |
+
# • Pharmacognosy (natural drugs)
|
196 |
+
|
197 |
+
# 💊 **Career Paths:**
|
198 |
+
# • Clinical Pharmacist
|
199 |
+
# • Industrial Pharmacist
|
200 |
+
# • Research & Development
|
201 |
+
# • Regulatory Affairs
|
202 |
+
# • Hospital Pharmacy
|
203 |
+
|
204 |
+
# ✨ *"विद्या ददाति विनयं" - Knowledge gives humility*
|
205 |
+
|
206 |
+
# What specific topic would you like to explore?{file_mention}"""
|
207 |
+
|
208 |
+
# return f"""🙏 **Namaste!** I'm here to help with your pharmacy studies! I can assist with:
|
209 |
+
|
210 |
+
# 📚 **Academic Topics:** Pharmacology, Chemistry, Biology concepts
|
211 |
+
# 💊 **Drug Information:** Mechanisms, side effects, interactions
|
212 |
+
# ❓ **Quiz Generation:** Practice questions and flashcards
|
213 |
+
# 🧠 **Mnemonics:** Memory tricks and acronyms
|
214 |
+
# 🗣️ **Viva Practice:** Mock interview sessions
|
215 |
+
# 📄 **File Analysis:** Answer questions about uploaded documents{file_mention}
|
216 |
+
|
217 |
+
# *Please ask me about a specific topic, or try:*
|
218 |
+
# - "Explain pharmacokinetics"
|
219 |
+
# - "Make a quiz on analgesics"
|
220 |
+
# - "Give me a mnemonic for drug classifications"
|
221 |
+
|
222 |
+
# **आपक��� अध्ययन साथी (Your Study Companion)** 📖✨"""
|
223 |
+
|
224 |
+
# def process_query(self, query, file_context=""):
|
225 |
+
# """
|
226 |
+
# Main method to process academic queries.
|
227 |
+
# It first tries the Gemini AI model and falls back to the local knowledge base.
|
228 |
+
# """
|
229 |
+
# try:
|
230 |
+
# # Priority 1: Use AI for a comprehensive response if available.
|
231 |
+
# if self.model:
|
232 |
+
# ai_response = self.process_with_ai(query, file_context)
|
233 |
+
# if ai_response:
|
234 |
+
# return f"🤖 **AI-Powered Response**\n\n{ai_response}"
|
235 |
+
|
236 |
+
# # Priority 2 (Fallback): Use the local knowledge base.
|
237 |
+
# key_terms = self.extract_key_terms(query)
|
238 |
+
|
239 |
+
# if not key_terms:
|
240 |
+
# return self.generate_general_response(query, file_context)
|
241 |
+
|
242 |
+
# best_topic = self.find_best_match(key_terms)
|
243 |
+
|
244 |
+
# if best_topic:
|
245 |
+
# content = self.knowledge_base[best_topic]
|
246 |
+
# response = self.format_response(best_topic, content)
|
247 |
+
# if file_context:
|
248 |
+
# response += f"\n\n📄 *Note: I see you have uploaded files. Feel free to ask specific questions about their content!*"
|
249 |
+
# return response
|
250 |
+
# else:
|
251 |
+
# # No specific match found, provide general guidance.
|
252 |
+
# return self.generate_general_response(query, file_context)
|
253 |
+
|
254 |
+
# except Exception as e:
|
255 |
+
# # This is the completed part: a graceful error handler.
|
256 |
+
# print(f"An unexpected error occurred in AcademicAgent.process_query: {e}")
|
257 |
+
# return f"माफ करें (Sorry), I encountered an unexpected error while processing your request. Please try rephrasing your question or try again later."
|
258 |
+
# agents/academic_agent.py
|
259 |
+
"""
|
260 |
+
Academic Agent - Handles general academic questions.
|
261 |
+
Now returns a standardized dictionary instead of a raw string.
|
262 |
+
"""
|
263 |
+
|
264 |
+
import json
|
265 |
+
import os
|
266 |
+
import re
|
267 |
+
from .agent_helpers import format_history_for_prompt
|
268 |
+
class AcademicAgent:
|
269 |
+
def __init__(self, gemini_model=None):
|
270 |
+
self.model = gemini_model
|
271 |
+
# The knowledge base logic remains the same
|
272 |
+
self.knowledge_base = self.load_knowledge_base()
|
273 |
+
|
274 |
+
# The load_knowledge_base, process_with_ai, extract_key_terms,
|
275 |
+
# find_best_match, format_response, and generate_general_response
|
276 |
+
# methods remain exactly the same as before.
|
277 |
+
# We only need to change the final process_query method.
|
278 |
+
|
279 |
+
def load_knowledge_base(self):
|
280 |
+
"""Load pre-built academic knowledge base from a JSON file."""
|
281 |
+
knowledge_file = 'data/academic_knowledge.json'
|
282 |
+
if not os.path.exists(knowledge_file):
|
283 |
+
# (Content of this method is unchanged)
|
284 |
+
default_knowledge = { "pharmacology": { "definition": "..." } } # (abbreviated for clarity)
|
285 |
+
os.makedirs('data', exist_ok=True)
|
286 |
+
with open(knowledge_file, 'w') as f:
|
287 |
+
json.dump(default_knowledge, f, indent=2)
|
288 |
+
return default_knowledge
|
289 |
+
try:
|
290 |
+
with open(knowledge_file, 'r') as f: return json.load(f)
|
291 |
+
except: return {}
|
292 |
+
|
293 |
+
# def process_with_ai(self, query, file_context=""):
|
294 |
+
# """Use Gemini AI to provide comprehensive, context-aware answers."""
|
295 |
+
# if not self.model: return None
|
296 |
+
# try:
|
297 |
+
# # (Content of this method is unchanged)
|
298 |
+
# context_section = f"UPLOADED FILE CONTEXT:\n{file_context}" if file_context else ""
|
299 |
+
# prompt = f"You are an expert pharmacy educator... STUDENT QUESTION: {query}\n{context_section} ..."
|
300 |
+
# response = self.model.generate_content(prompt)
|
301 |
+
# return response.text
|
302 |
+
# except Exception as e:
|
303 |
+
# print(f"Gemini API error in Academic Agent: {e}")
|
304 |
+
# return None
|
305 |
+
# In agents/academic_agent.py -> class AcademicAgent
|
306 |
+
|
307 |
+
def process_with_ai(self, query, file_context="", chat_history=None):
|
308 |
+
"""Use Gemini AI with conversation history and file context."""
|
309 |
+
if not self.model:
|
310 |
+
return None
|
311 |
+
|
312 |
+
# --- NEW HISTORY AND PROMPT LOGIC ---
|
313 |
+
|
314 |
+
# Format the past conversation for the prompt
|
315 |
+
history_for_prompt = ""
|
316 |
+
if chat_history:
|
317 |
+
for turn in chat_history:
|
318 |
+
# Ensure 'parts' is a list and not empty before accessing
|
319 |
+
if turn.get('parts') and isinstance(turn.get('parts'), list):
|
320 |
+
role = "User" if turn['role'] == 'user' else "AI"
|
321 |
+
history_for_prompt += f"{role}: {turn['parts'][0]}\n"
|
322 |
+
|
323 |
+
# Format the file context
|
324 |
+
context_section = ""
|
325 |
+
if file_context:
|
326 |
+
context_section = f"""
|
327 |
+
---
|
328 |
+
CONTEXT FROM UPLOADED FILE:
|
329 |
+
{file_context}
|
330 |
+
---
|
331 |
+
Use the context from the uploaded file above to answer the user's current question if it is relevant.
|
332 |
+
"""
|
333 |
+
|
334 |
+
# The new prompt structure
|
335 |
+
prompt = f"""You are a helpful and knowledgeable AI pharmacy tutor for a student in India.
|
336 |
+
|
337 |
+
CONVERSATION HISTORY:
|
338 |
+
{history_for_prompt}
|
339 |
+
{context_section}
|
340 |
+
|
341 |
+
CURRENT QUESTION:
|
342 |
+
User: {query}
|
343 |
+
|
344 |
+
Please provide a helpful and accurate answer to the user's CURRENT QUESTION.
|
345 |
+
- If the question is a follow-up, use the CONVERSATION HISTORY to understand the context.
|
346 |
+
- If the question relates to the UPLOADED FILE, prioritize information from that context.
|
347 |
+
- Keep the tone encouraging and professional like Acharya Sushruta.
|
348 |
+
- Also ask the user if they have any doubts or need further clarification.
|
349 |
+
"""
|
350 |
+
try:
|
351 |
+
# This is a more direct and robust way to send the complete context
|
352 |
+
response = self.model.generate_content(prompt)
|
353 |
+
return response.text
|
354 |
+
except Exception as e:
|
355 |
+
print(f"Gemini API error in Academic Agent: {e}")
|
356 |
+
return None
|
357 |
+
|
358 |
+
def extract_key_terms(self, query):
|
359 |
+
"""Extract key terms from the query."""
|
360 |
+
# (Content of this method is unchanged)
|
361 |
+
common_words = {'what', 'is', 'the', 'define', 'explain'}
|
362 |
+
words = re.findall(r'\b\w+\b', query.lower())
|
363 |
+
return [word for word in words if word not in common_words]
|
364 |
+
|
365 |
+
def find_best_match(self, key_terms):
|
366 |
+
"""Find the best matching topic in the local knowledge base."""
|
367 |
+
# (Content of this method is unchanged)
|
368 |
+
best_match, max_score = None, 0
|
369 |
+
for topic, content in self.knowledge_base.items():
|
370 |
+
score = 0
|
371 |
+
# ... scoring logic ...
|
372 |
+
if score > max_score:
|
373 |
+
max_score, best_match = score, topic
|
374 |
+
return best_match
|
375 |
+
|
376 |
+
def format_response(self, topic, content):
|
377 |
+
"""Format the local knowledge base content in a user-friendly way."""
|
378 |
+
# (Content of this method is unchanged)
|
379 |
+
response = f"📚 **{topic.replace('_', ' ').title()}**\n\n"
|
380 |
+
# ... formatting logic ...
|
381 |
+
return response + "💡 *Would you like me to create a quiz or mnemonic?*"
|
382 |
+
|
383 |
+
def generate_general_response(self, query, file_context=""):
|
384 |
+
"""Generate a general helpful response."""
|
385 |
+
# (Content of this method is unchanged)
|
386 |
+
return "🙏 **Namaste!** I'm here to help..."
|
387 |
+
|
388 |
+
# --- THIS IS THE ONLY METHOD THAT CHANGES ---
|
389 |
+
def process_query(self, query: str, file_context: str = "", chat_history: list = None):
|
390 |
+
"""
|
391 |
+
Processes a general academic query using the Gemini model.
|
392 |
+
|
393 |
+
Args:
|
394 |
+
query (str): The user's full query.
|
395 |
+
file_context (str): Context from any uploaded files.
|
396 |
+
chat_history (list): The history of the conversation.
|
397 |
+
|
398 |
+
Returns:
|
399 |
+
dict: A dictionary containing the response message and agent metadata.
|
400 |
+
"""
|
401 |
+
if not self.model:
|
402 |
+
return {'message': "📚 My knowledge circuits are offline! The Gemini API key is missing.", 'agent_used': 'academic', 'status': 'error_no_api_key'}
|
403 |
+
|
404 |
+
history_for_prompt = format_history_for_prompt(chat_history)
|
405 |
+
|
406 |
+
context_section = f"---\nCONTEXT FROM KNOWLEDGE BASE:\n{file_context}\n---" if file_context else ""
|
407 |
+
# if file_context:
|
408 |
+
# context_section = f"---\nCONTEXT FROM UPLOADED FILE:\n{file_context}\n---"
|
409 |
+
|
410 |
+
prompt = f"""You are a helpful and knowledgeable AI pharmacy tutor for a student in India.
|
411 |
+
**CRITICAL INSTRUCTION FOR CITATIONS:** When you use information from the KNOWLEDGE BASE CONTEXT, you MUST cite the source at the end of the relevant sentence using the format `[Source: filename, Page: page_number]`.
|
412 |
+
|
413 |
+
Your reasoning process must be:
|
414 |
+
1. First, analyze the CONVERSATION HISTORY to understand the immediate context of the CURRENT QUESTION. This is especially important to understand what "this," "that," or "it" refers to.
|
415 |
+
2. Once you understand the user's real question, Check if the KNOWLEDGE BASE CONTEXT is relevant to the topic.
|
416 |
+
3. Formulate your answer based on this reasoning, keeping an encouraging and professional tone.
|
417 |
+
|
418 |
+
CONVERSATION HISTORY:
|
419 |
+
{history_for_prompt}
|
420 |
+
{context_section}
|
421 |
+
CURRENT QUESTION:
|
422 |
+
User: {query}
|
423 |
+
"""
|
424 |
+
try:
|
425 |
+
response = self.model.generate_content(prompt)
|
426 |
+
return {'message': response.text, 'agent_used': 'academic', 'status': 'success'}
|
427 |
+
except Exception as e:
|
428 |
+
print(f"Academic Agent Error: {e}")
|
429 |
+
return {'message': f"Sorry, I encountered a problem: {e}", 'agent_used': 'academic', 'status': 'error_api_call'}
|
430 |
+
|
431 |
+
# def process_query(self, query: str, file_context: str = "",chat_history: list = None):
|
432 |
+
# """
|
433 |
+
# Main method to process academic queries.
|
434 |
+
# It now returns a standardized dictionary.
|
435 |
+
# """
|
436 |
+
# response_message = ""
|
437 |
+
# try:
|
438 |
+
# # Priority 1: Use AI for a comprehensive response if available.
|
439 |
+
# if self.model:
|
440 |
+
# ai_response = self.process_with_ai(query, file_context,chat_history)
|
441 |
+
# if ai_response:
|
442 |
+
# response_message = f"🤖 **AI-Powered Response**\n\n{ai_response}"
|
443 |
+
|
444 |
+
# # Priority 2 (Fallback): Use the local knowledge base if AI fails or is unavailable.
|
445 |
+
# if not response_message:
|
446 |
+
# key_terms = self.extract_key_terms(query)
|
447 |
+
# if not key_terms:
|
448 |
+
# response_message = self.generate_general_response(query, file_context)
|
449 |
+
# else:
|
450 |
+
# best_topic = self.find_best_match(key_terms)
|
451 |
+
# if best_topic:
|
452 |
+
# content = self.knowledge_base[best_topic]
|
453 |
+
# response_message = self.format_response(best_topic, content)
|
454 |
+
# else:
|
455 |
+
# response_message = self.generate_general_response(query, file_context)
|
456 |
+
|
457 |
+
# except Exception as e:
|
458 |
+
# print(f"An unexpected error occurred in AcademicAgent.process_query: {e}")
|
459 |
+
# response_message = f"माफ करें (Sorry), I encountered an error. Please try again."
|
460 |
+
|
461 |
+
# # **THE FIX**: Always wrap the final message in the standard dictionary format.
|
462 |
+
# return {
|
463 |
+
# 'message': response_message,
|
464 |
+
# 'agent_used': 'academic',
|
465 |
+
# 'status': 'success'
|
466 |
+
# }
|
agents/agent_helpers.py
ADDED
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# agents/agent_helpers.py
|
2 |
+
|
3 |
+
def format_history_for_prompt(chat_history: list = None) -> str:
|
4 |
+
"""Formats the chat history list into a string for the AI prompt."""
|
5 |
+
if not chat_history:
|
6 |
+
return ""
|
7 |
+
|
8 |
+
history_for_prompt = ""
|
9 |
+
for turn in chat_history:
|
10 |
+
# Ensure 'parts' is a list and not empty before accessing
|
11 |
+
if turn.get('parts') and isinstance(turn.get('parts'), list):
|
12 |
+
role = "User" if turn['role'] == 'user' else "AI"
|
13 |
+
history_for_prompt += f"{role}: {turn['parts'][0]}\n"
|
14 |
+
|
15 |
+
return history_for_prompt
|
agents/drug_info_agent.py
ADDED
@@ -0,0 +1,147 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# agents/drug_info_agent.py
|
2 |
+
"""
|
3 |
+
Drug Information Agent - Handles drug-related queries using Generative AI.
|
4 |
+
"""
|
5 |
+
# agents/drug_info_agent.py
|
6 |
+
|
7 |
+
import re
|
8 |
+
from .agent_helpers import format_history_for_prompt
|
9 |
+
|
10 |
+
class DrugInfoAgent:
|
11 |
+
def __init__(self, gemini_model=None):
|
12 |
+
"""
|
13 |
+
Initializes the agent with the Gemini model.
|
14 |
+
"""
|
15 |
+
self.model = gemini_model
|
16 |
+
|
17 |
+
def _extract_drug_name(self, query: str) -> str:
|
18 |
+
"""
|
19 |
+
A simple helper to extract the drug name from the user's query.
|
20 |
+
This is a crucial step for this agent's reliability.
|
21 |
+
"""
|
22 |
+
# Remove common phrases and get the potential drug name
|
23 |
+
query_lower = query.lower()
|
24 |
+
patterns = [
|
25 |
+
"what are the side effects of", "tell me about", "information on",
|
26 |
+
"info on", "about the drug", "what is", "about"
|
27 |
+
]
|
28 |
+
|
29 |
+
cleaned_query = query_lower
|
30 |
+
for p in patterns:
|
31 |
+
cleaned_query = cleaned_query.replace(p, "")
|
32 |
+
|
33 |
+
# A simple assumption: the first significant word is the drug name.
|
34 |
+
# This can be improved with more advanced NLP in the future.
|
35 |
+
match = re.search(r'\b[a-zA-Z0-9]+\b', cleaned_query)
|
36 |
+
if match:
|
37 |
+
return match.group(0).title()
|
38 |
+
return ""
|
39 |
+
|
40 |
+
def process_query(self, query: str, file_context: str = "", chat_history: list = None):
|
41 |
+
"""
|
42 |
+
Processes a query to retrieve information about a specific drug.
|
43 |
+
"""
|
44 |
+
if not self.model:
|
45 |
+
return {'message': "💊 The pharmacy database is offline! The Gemini API key is missing.", 'agent_used': 'drug_info', 'status': 'error_no_api_key'}
|
46 |
+
|
47 |
+
# --- RESTORED LOGIC ---
|
48 |
+
drug_name = self._extract_drug_name(query)
|
49 |
+
if not drug_name:
|
50 |
+
# If the history also doesn't provide context, ask the user.
|
51 |
+
if "drug" not in str(chat_history).lower():
|
52 |
+
return {
|
53 |
+
'message': "Please tell me which drug you want to know about! For example, try 'info on Paracetamol'.",
|
54 |
+
'agent_used': 'drug_info',
|
55 |
+
'status': 'error_no_topic'
|
56 |
+
}
|
57 |
+
|
58 |
+
history_for_prompt = format_history_for_prompt(chat_history)
|
59 |
+
context_section = f"---\nCONTEXT FROM KNOWLEDGE BASE:\n{file_context}\n---" if file_context else ""
|
60 |
+
prompt = f"""You are a cautious AI Pharmacist Tutor providing educational information like ancient india's Chanakya advisor to Chandragupta Maurya.
|
61 |
+
|
62 |
+
**CRITICAL SAFETY INSTRUCTION:** START EVERY RESPONSE with this disclaimer: "⚠️ **Disclaimer:** This information is for educational purposes ONLY and is not a substitute for professional medical advice."
|
63 |
+
|
64 |
+
Your reasoning process is:
|
65 |
+
1. Analyze the CONVERSATION HISTORY and CURRENT QUESTION to identify the specific drug being discussed. The primary drug to focus on is: **{drug_name}**.
|
66 |
+
2. If the user asks a follow-up (e.g., "what about its dosage forms?"), answer that specific question in the context of the drug.
|
67 |
+
3. Provide a structured summary.
|
68 |
+
|
69 |
+
CONVERSATION HISTORY:
|
70 |
+
{history_for_prompt}
|
71 |
+
{context_section}
|
72 |
+
CURRENT QUESTION:
|
73 |
+
User: {query}
|
74 |
+
|
75 |
+
Provide a structured summary for the drug **{drug_name}** including: Therapeutic Class, Mechanism of Action (MOA), Indications, Side Effects, and Warnings. DO NOT provide specific dosages.
|
76 |
+
"""
|
77 |
+
try:
|
78 |
+
response = self.model.generate_content(prompt)
|
79 |
+
return {'message': response.text, 'agent_used': 'drug_info', 'status': 'success'}
|
80 |
+
except Exception as e:
|
81 |
+
print(f"Drug Info Agent Error: {e}")
|
82 |
+
return {'message': f"Sorry, I couldn't access the drug database. An error occurred.", 'agent_used': 'drug_info', 'status': 'error_api_call'}
|
83 |
+
# def process_query(self, query: str, file_context: str = "", chat_history: list = None):
|
84 |
+
# """
|
85 |
+
# Processes a query to retrieve information about a specific drug.
|
86 |
+
|
87 |
+
# Args:
|
88 |
+
# query (str): The user's full query (e.g., "Tell me about Metformin").
|
89 |
+
# file_context (str): Optional context from uploaded files.
|
90 |
+
|
91 |
+
# Returns:
|
92 |
+
# dict: A dictionary containing the response message and agent metadata.
|
93 |
+
# """
|
94 |
+
# # Fallback response if the AI model is not configured
|
95 |
+
# if not self.model:
|
96 |
+
# return {
|
97 |
+
# 'message': "💊 **Drug Information Agent**\n\nThe pharmacy database is offline! The Gemini API key is missing, so I can't look up drug information. Please configure the API key to enable this feature.",
|
98 |
+
# 'agent_type': 'drug_info',
|
99 |
+
# 'status': 'error_no_api_key'
|
100 |
+
# }
|
101 |
+
|
102 |
+
# drug_name = self._extract_drug_name(query)
|
103 |
+
# if not drug_name:
|
104 |
+
# return {
|
105 |
+
# 'message': "Please tell me which drug you want to know about! For example, try 'info on Paracetamol'.",
|
106 |
+
# 'agent_type': 'drug_info',
|
107 |
+
# 'status': 'error_no_topic'
|
108 |
+
# }
|
109 |
+
|
110 |
+
# # Construct a specialized, safety-conscious prompt for the Gemini model
|
111 |
+
# prompt = f"""
|
112 |
+
# You are a highly knowledgeable and cautious AI Pharmacist Tutor. Your primary role is to provide accurate drug information for a B.Pharmacy student in India for EDUCATIONAL PURPOSES ONLY.
|
113 |
+
|
114 |
+
# **CRITICAL SAFETY INSTRUCTION:**
|
115 |
+
# START EVERY RESPONSE with the following disclaimer, exactly as written:
|
116 |
+
# "⚠️ **Disclaimer:** This information is for educational purposes ONLY and is not a substitute for professional medical advice. Always consult a qualified healthcare provider."
|
117 |
+
|
118 |
+
# **Task:**
|
119 |
+
# Provide a structured summary for the drug: **{drug_name}**
|
120 |
+
|
121 |
+
# **Information to Include:**
|
122 |
+
# 1. **Therapeutic Class:** What family of drugs does it belong to?
|
123 |
+
# 2. **Mechanism of Action (MOA):** How does it work in the body? Explain simply.
|
124 |
+
# 3. **Common Indications:** What is it typically used for?
|
125 |
+
# 4. **Common Side Effects:** List a few of the most common side effects.
|
126 |
+
# 5. **Important Contraindications/Warnings:** Who should not take this drug or be cautious?
|
127 |
+
# 6. **Common Dosage Forms:** What forms is it available in (e.g., Tablets, Syrup, Injection)? DO NOT provide specific dosages like mg or frequency.
|
128 |
+
|
129 |
+
# **Format:**
|
130 |
+
# Use clear headings (like "🔬 Mechanism of Action") and bullet points for readability. Use relevant emojis.
|
131 |
+
# """
|
132 |
+
|
133 |
+
# try:
|
134 |
+
# # Generate content using the AI model
|
135 |
+
# ai_response = self.model.generate_content(prompt, chat_history)
|
136 |
+
# return {
|
137 |
+
# 'message': ai_response.text,
|
138 |
+
# 'agent_used': 'drug_info',
|
139 |
+
# 'status': 'success'
|
140 |
+
# }
|
141 |
+
# except Exception as e:
|
142 |
+
# print(f"Drug Info Agent Error: {e}")
|
143 |
+
# return {
|
144 |
+
# 'message': f"I'm sorry, I couldn't access the drug database at the moment. An error occurred: {str(e)}",
|
145 |
+
# 'agent_type': 'drug_info',
|
146 |
+
# 'status': 'error_api_call'
|
147 |
+
# }
|
agents/mnemonic_agent.py
ADDED
@@ -0,0 +1,148 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# agents/mnemonic_agent.py
|
2 |
+
"""
|
3 |
+
Mnemonic Creation Agent - Creates memory aids and tricks using Generative AI.
|
4 |
+
"""
|
5 |
+
import re
|
6 |
+
from .agent_helpers import format_history_for_prompt
|
7 |
+
class MnemonicAgent:
|
8 |
+
def __init__(self, gemini_model=None):
|
9 |
+
"""
|
10 |
+
Initializes the agent with the Gemini model.
|
11 |
+
|
12 |
+
Args:
|
13 |
+
gemini_model: An instance of the Gemini model client.
|
14 |
+
"""
|
15 |
+
self.model = gemini_model
|
16 |
+
|
17 |
+
def _extract_topic(self, query: str) -> str:
|
18 |
+
"""A simple helper to extract the core topic from the user's query."""
|
19 |
+
# Remove common phrases used to request mnemonics
|
20 |
+
patterns = [
|
21 |
+
r"make a mnemonic for",
|
22 |
+
r"create a mnemonic for",
|
23 |
+
r"give me a mnemonic for",
|
24 |
+
r"mnemonic for",
|
25 |
+
r"memory aid for"
|
26 |
+
]
|
27 |
+
topic = query.lower()
|
28 |
+
for p in patterns:
|
29 |
+
topic = re.sub(p, "", topic)
|
30 |
+
|
31 |
+
# Clean up any extra whitespace
|
32 |
+
return topic.strip()
|
33 |
+
def process_query(self, query: str, file_context: str = "", chat_history: list = None):
|
34 |
+
"""
|
35 |
+
Processes a query to generate a mnemonic.
|
36 |
+
|
37 |
+
Args:
|
38 |
+
query (str): The user's full query (e.g., "Give me a mnemonic for glycolysis steps").
|
39 |
+
file_context (str): Optional context from uploaded files.
|
40 |
+
chat_history (list): The history of the conversation.
|
41 |
+
|
42 |
+
Returns:
|
43 |
+
dict: A dictionary containing the response message and agent metadata.
|
44 |
+
"""
|
45 |
+
if not self.model:
|
46 |
+
return {'message': "🧠 My creative circuits are offline! The Gemini API key is missing.", 'agent_used': 'mnemonic_creation', 'status': 'error_no_api_key'}
|
47 |
+
|
48 |
+
history_for_prompt = format_history_for_prompt(chat_history)
|
49 |
+
topic = self._extract_topic(query)
|
50 |
+
context_section = f"---\nCONTEXT FROM KNOWLEDGE BASE:\n{file_context}\n---" if file_context else ""
|
51 |
+
prompt = f"""You are "Mnemonic Master," a creative AI that creates memorable mnemonics for B.Pharmacy students.
|
52 |
+
**CRITICAL INSTRUCTION FOR CITATIONS:** When you use information from the KNOWLEDGE BASE CONTEXT, you MUST cite the source at the end of the relevant sentence using the format `[Source: filename, Page: page_number]`.
|
53 |
+
get the topic from {topic}
|
54 |
+
CONVERSATION HISTORY:
|
55 |
+
{context_section}
|
56 |
+
{history_for_prompt}
|
57 |
+
CURRENT TASK:
|
58 |
+
User: {query}
|
59 |
+
|
60 |
+
Based on the CURRENT TASK and conversation history, generate a clever mnemonic (acronym, rhyme, or story). If the user is asking for a modification of a previous mnemonic, adjust it accordingly. Explain how the mnemonic works. Be encouraging and fun! with emojis and like tenali ramakrishna
|
61 |
+
"""
|
62 |
+
try:
|
63 |
+
response = self.model.generate_content(prompt)
|
64 |
+
return {'message': response.text, 'agent_used': 'mnemonic_creation', 'status': 'success'}
|
65 |
+
except Exception as e:
|
66 |
+
print(f"Mnemonic Agent Error: {e}")
|
67 |
+
return {'message': f"My creative spark fizzled out. Error: {e}", 'agent_used': 'mnemonic_creation', 'status': 'error_api_call'}
|
68 |
+
|
69 |
+
# def process_query(self, query: str, file_context: str = "",chat_history: list = None):
|
70 |
+
# """
|
71 |
+
# Processes a query to generate a mnemonic.
|
72 |
+
|
73 |
+
# Args:
|
74 |
+
# query (str): The user's full query (e.g., "Give me a mnemonic for glycolysis steps").
|
75 |
+
# file_context (str): Optional context from uploaded files (usually not needed for mnemonics).
|
76 |
+
|
77 |
+
# Returns:
|
78 |
+
# dict: A dictionary containing the response message and agent metadata.
|
79 |
+
# """
|
80 |
+
# # Fallback response if the AI model is not configured
|
81 |
+
# if not self.model:
|
82 |
+
# return {
|
83 |
+
# 'message': "🧠 **Mnemonic Master**\n\nMy creative circuits are offline! The Gemini API key is missing, so I can't generate mnemonics right now. Please configure the API key to enable this feature.",
|
84 |
+
# 'agent_type': 'mnemonic_creation',
|
85 |
+
# 'status': 'error_no_api_key'
|
86 |
+
# }
|
87 |
+
|
88 |
+
# topic = self._extract_topic(query)
|
89 |
+
# if not topic:
|
90 |
+
# return {
|
91 |
+
# 'message': "Please tell me what topic you need a mnemonic for! For example, try 'mnemonic for Krebs cycle intermediates'.",
|
92 |
+
# 'agent_type': 'mnemonic_creation',
|
93 |
+
# 'status': 'error_no_topic'
|
94 |
+
# }
|
95 |
+
# # --- NEW LOGIC ---
|
96 |
+
# task_description = f"Generate a clever mnemonic for the B.Pharmacy topic: **{topic.title()}**."
|
97 |
+
|
98 |
+
# if file_context:
|
99 |
+
# task_description += f"\n\nIf relevant, you can use the following text from the student's uploaded notes for context:\n---\n{file_context}\n---"
|
100 |
+
|
101 |
+
|
102 |
+
# # Construct a specialized prompt for the Gemini model
|
103 |
+
# prompt = f"""
|
104 |
+
# You are "Mnemonic Master," a creative and witty AI that excels at creating memorable mnemonics for B.Pharmacy students in India.
|
105 |
+
|
106 |
+
# **Your Task:**
|
107 |
+
# {task_description}
|
108 |
+
|
109 |
+
# **Topic:** "{topic}"
|
110 |
+
|
111 |
+
# **Instructions:**
|
112 |
+
# 1. **Analyze the Topic:** Identify the key items to be memorized (e.g., steps, drug names, classifications).
|
113 |
+
# 2. **Create the Mnemonic:** Design a creative acronym, rhyme, or short, vivid story.
|
114 |
+
# 3. **Explain It:** Clearly state the mnemonic and then explain how it maps to the concepts.
|
115 |
+
# 4. **Tone:** Be encouraging, fun, and use emojis! The language should be simple and relatable.
|
116 |
+
|
117 |
+
# **Example Output Format:**
|
118 |
+
|
119 |
+
# 🧠 **Mnemonic for [Topic Name]**
|
120 |
+
|
121 |
+
# Here's a fun way to remember it!
|
122 |
+
|
123 |
+
# **The Mnemonic:**
|
124 |
+
# "[The actual acronym or rhyme]"
|
125 |
+
|
126 |
+
# **How it works:**
|
127 |
+
# * **[Letter 1]** stands for [Concept 1]
|
128 |
+
# * **[Letter 2]** stands for [Concept 2]
|
129 |
+
# * ...and so on!
|
130 |
+
|
131 |
+
# Keep up the great work! You've got this! 💪
|
132 |
+
# """
|
133 |
+
|
134 |
+
# try:
|
135 |
+
# # Generate content using the AI model
|
136 |
+
# ai_response = self.model.generate_content(prompt,chat_history)
|
137 |
+
# return {
|
138 |
+
# 'message': ai_response.text,
|
139 |
+
# 'agent_used': 'mnemonic_creation',
|
140 |
+
# 'status': 'success'
|
141 |
+
# }
|
142 |
+
# except Exception as e:
|
143 |
+
# print(f"Mnemonic Agent Error: {e}")
|
144 |
+
# return {
|
145 |
+
# 'message': f"Oh no! My creative spark fizzled out. I ran into an error: {str(e)}",
|
146 |
+
# 'agent_type': 'mnemonic_creation',
|
147 |
+
# 'status': 'error_api_call'
|
148 |
+
# }
|
agents/quiz_agent.py
ADDED
@@ -0,0 +1,158 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# agents/quiz_agent.py
|
2 |
+
"""
|
3 |
+
Quiz Generation Agent - Creates quizzes and flashcards using Generative AI.
|
4 |
+
"""
|
5 |
+
import re
|
6 |
+
from .agent_helpers import format_history_for_prompt
|
7 |
+
class QuizAgent:
|
8 |
+
def __init__(self, gemini_model=None):
|
9 |
+
"""
|
10 |
+
Initializes the agent with the Gemini model.
|
11 |
+
|
12 |
+
Args:
|
13 |
+
gemini_model: An instance of the Gemini model client.
|
14 |
+
"""
|
15 |
+
self.model = gemini_model
|
16 |
+
|
17 |
+
def _extract_topic(self, query: str) -> str:
|
18 |
+
"""A simple helper to extract the core topic from the user's query."""
|
19 |
+
# Remove common phrases used to request a quiz
|
20 |
+
patterns = [
|
21 |
+
r"make a quiz on",
|
22 |
+
r"create a quiz on",
|
23 |
+
r"give me a quiz on",
|
24 |
+
r"quiz on",
|
25 |
+
r"quiz about",
|
26 |
+
r"test me on"
|
27 |
+
]
|
28 |
+
topic = query.lower()
|
29 |
+
for p in patterns:
|
30 |
+
topic = re.sub(p, "", topic)
|
31 |
+
|
32 |
+
# Clean up any extra whitespace
|
33 |
+
return topic.strip()
|
34 |
+
def process_query(self, query: str, file_context: str = "", chat_history: list = None):
|
35 |
+
"""
|
36 |
+
Processes a query to generate a quiz.
|
37 |
+
|
38 |
+
Args:
|
39 |
+
query (str): The user's full query (e.g., "Make a quiz on analgesics").
|
40 |
+
file_context (str): Optional text content from an uploaded file.
|
41 |
+
chat_history (list): The history of the conversation.
|
42 |
+
|
43 |
+
Returns:
|
44 |
+
dict: A dictionary containing the quiz and agent metadata.
|
45 |
+
"""
|
46 |
+
if not self.model:
|
47 |
+
return {'message': "❓ The question bank is locked! The Gemini API key is missing.", 'agent_used': 'quiz_generation', 'status': 'error_no_api_key'}
|
48 |
+
|
49 |
+
history_for_prompt = format_history_for_prompt(chat_history)
|
50 |
+
topic = self._extract_topic(query)
|
51 |
+
context_section = f"---\nCONTEXT FROM KNOWLEDGE BASE:\n{file_context}\n---" if file_context else ""
|
52 |
+
task_description = f"Generate a short quiz (3-5 questions) on the topic: **{topic.title()}**."
|
53 |
+
if file_context:
|
54 |
+
task_description += f"\nIf relevant, use text from the student's notes for context:\n---\n{file_context}\n---"
|
55 |
+
|
56 |
+
prompt = f"""You are "Quiz Master," an AI that creates educational quizzes like Maryada Ramanna. Maryada Ramanna—he’s a legendary character from Indian (particularly South Indian) folklore, often associated with justice, integrity, and cleverness.
|
57 |
+
**CRITICAL INSTRUCTION FOR CITATIONS:** When you use information from the KNOWLEDGE BASE CONTEXT, you MUST cite the source at the end of the relevant sentence using the format `[Source: filename, Page: page_number]`.
|
58 |
+
CONVERSATION HISTORY:
|
59 |
+
{history_for_prompt}
|
60 |
+
{context_section}
|
61 |
+
CURRENT TASK:
|
62 |
+
{task_description}
|
63 |
+
|
64 |
+
Based on the CURRENT TASK and conversation history, create a quiz. If the user is asking for a change to a previous quiz (e.g., "make it harder"), do that.
|
65 |
+
Include a mix of MCQs, True/False, and Fill-in-the-Blank questions.
|
66 |
+
CRITICAL: Provide a clearly separated "Answer Key" section with answers and brief explanations.
|
67 |
+
"""
|
68 |
+
try:
|
69 |
+
response = self.model.generate_content(prompt)
|
70 |
+
return {'message': response.text, 'agent_used': 'quiz_generation', 'status': 'success'}
|
71 |
+
except Exception as e:
|
72 |
+
print(f"Quiz Agent Error: {e}")
|
73 |
+
return {'message': f"My question book seems to be stuck. Error: {e}", 'agent_used': 'quiz_generation', 'status': 'error_api_call'}
|
74 |
+
|
75 |
+
# def process_query(self, query: str, file_context: str = "",chat_history: list = None):
|
76 |
+
# """
|
77 |
+
# Processes a query to generate a quiz. The agent prioritizes file_context if provided.
|
78 |
+
|
79 |
+
# Args:
|
80 |
+
# query (str): The user's full query (e.g., "Make a quiz on analgesics").
|
81 |
+
# file_context (str): Optional text content from an uploaded file.
|
82 |
+
|
83 |
+
# Returns:
|
84 |
+
# dict: A dictionary containing the quiz and agent metadata.
|
85 |
+
# """
|
86 |
+
# if not self.model:
|
87 |
+
# return {
|
88 |
+
# 'message': "❓ **Quiz Master**\n\nThe question bank is locked! The Gemini API key is missing, so I can't generate quizzes. Please configure the API key to enable this feature.",
|
89 |
+
# 'agent_type': 'quiz_generation',
|
90 |
+
# 'status': 'error_no_api_key'
|
91 |
+
# }
|
92 |
+
|
93 |
+
# topic = self._extract_topic(query)
|
94 |
+
# task_description = f"Generate a short quiz (3-5 questions) for a B.Pharmacy student on the topic: **{topic.title()}**."
|
95 |
+
|
96 |
+
# if file_context:
|
97 |
+
# task_description += f"\n\nIf relevant, use the following text from the student's uploaded notes for additional context:\n---\n{file_context}\n---"
|
98 |
+
|
99 |
+
# else:
|
100 |
+
# return {
|
101 |
+
# 'message': "Please tell me what to quiz you on! Either upload a file or ask for a quiz on a specific topic, like 'quiz on antibiotics'.",
|
102 |
+
# 'agent_type': 'quiz_generation',
|
103 |
+
# 'status': 'error_no_topic'
|
104 |
+
# }
|
105 |
+
|
106 |
+
# # Construct a specialized prompt for the Gemini model
|
107 |
+
# prompt = f"""
|
108 |
+
# You are "Quiz Master," an AI that creates engaging and effective study quizzes for B.Pharmacy students in India.
|
109 |
+
|
110 |
+
# **Your Task:**
|
111 |
+
# {task_description}
|
112 |
+
|
113 |
+
# **Instructions:**
|
114 |
+
# 1. **Question Variety:** Create a mix of question types:
|
115 |
+
# * Multiple Choice Questions (MCQs) with 4 options.
|
116 |
+
# * True/False questions.
|
117 |
+
# * Fill-in-the-Blank questions.
|
118 |
+
# 2. **Clarity:** Ensure questions are clear, concise, and relevant.
|
119 |
+
# 3. **Answer Key:** THIS IS ESSENTIAL. After all the questions, provide a clearly separated "🔑 Answer Key" section with the correct answers. For MCQs, also provide a brief (one-sentence) explanation for why the answer is correct.
|
120 |
+
# 4. **Formatting:** Use markdown for headings, bolding, and lists. Use emojis to make it fun and engaging.
|
121 |
+
# Good luck! 🌟
|
122 |
+
# **Example Output Structure:**
|
123 |
+
|
124 |
+
# 📝 **Quiz Time: [Topic Name]**
|
125 |
+
|
126 |
+
# **Q1. [MCQ Question]**
|
127 |
+
# A) Option 1
|
128 |
+
# B) Option 2
|
129 |
+
# ...
|
130 |
+
|
131 |
+
# **Q2. [True/False Question]**
|
132 |
+
|
133 |
+
# **Q3. [Fill-in-the-Blank Question]**
|
134 |
+
|
135 |
+
# ---
|
136 |
+
# 🔑 **Answer Key**
|
137 |
+
# 1. **Answer:** B) Correct Option. *Explanation: [Brief reason why B is correct].*
|
138 |
+
# 2. **Answer:** True.
|
139 |
+
# 3. **Answer:** [Correct word(s)].
|
140 |
+
|
141 |
+
# Let's test your knowledge! Good luck! 🌟
|
142 |
+
# """
|
143 |
+
|
144 |
+
# try:
|
145 |
+
# # Generate content using the AI model
|
146 |
+
# ai_response = self.model.generate_content(prompt, chat_history)
|
147 |
+
# return {
|
148 |
+
# 'message': ai_response.text,
|
149 |
+
# 'agent_used': 'quiz_generation',
|
150 |
+
# 'status': 'success'
|
151 |
+
# }
|
152 |
+
# except Exception as e:
|
153 |
+
# print(f"Quiz Agent Error: {e}")
|
154 |
+
# return {
|
155 |
+
# 'message': f"I'm sorry, my question book seems to be stuck. I ran into an error: {str(e)}",
|
156 |
+
# 'agent_type': 'quiz_generation',
|
157 |
+
# 'status': 'error_api_call'
|
158 |
+
# }
|
agents/router_agent.py
ADDED
@@ -0,0 +1,43 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import re
|
2 |
+
from .academic_agent import AcademicAgent
|
3 |
+
from .drug_info_agent import DrugInfoAgent
|
4 |
+
from .mnemonic_agent import MnemonicAgent
|
5 |
+
from .quiz_agent import QuizAgent
|
6 |
+
from .viva_agent import VivaAgent
|
7 |
+
|
8 |
+
class RouterAgent:
|
9 |
+
def __init__(self, gemini_model=None):
|
10 |
+
self.academic_agent = AcademicAgent(gemini_model)
|
11 |
+
self.drug_info_agent = DrugInfoAgent(gemini_model)
|
12 |
+
self.mnemonic_agent = MnemonicAgent(gemini_model)
|
13 |
+
self.quiz_agent = QuizAgent(gemini_model)
|
14 |
+
self.viva_agent = VivaAgent(gemini_model)
|
15 |
+
|
16 |
+
def route_query(self, query: str, file_context: str, viva_state: dict, chat_history: list):
|
17 |
+
"""
|
18 |
+
Determines user intent and correctly routes the query with all
|
19 |
+
necessary context (file_context, chat_history, etc.) to the
|
20 |
+
correct specialist agent.
|
21 |
+
"""
|
22 |
+
query_lower = query.lower()
|
23 |
+
|
24 |
+
# 1. Viva Agent (High priority)
|
25 |
+
if viva_state and viva_state.get('active'):
|
26 |
+
return self.viva_agent.process_query(query, file_context, viva_state)
|
27 |
+
if any(cmd in query_lower for cmd in ["viva", "interview", "start viva"]):
|
28 |
+
return self.viva_agent.process_query(query, file_context, viva_state)
|
29 |
+
|
30 |
+
# 2. Mnemonic Agent
|
31 |
+
if any(cmd in query_lower for cmd in ["mnemonic", "memory aid", "remember"]):
|
32 |
+
return self.mnemonic_agent.process_query(query, file_context, chat_history)
|
33 |
+
|
34 |
+
# 3. Quiz Agent
|
35 |
+
if any(cmd in query_lower for cmd in ["quiz", "test me", "flashcard"]):
|
36 |
+
return self.quiz_agent.process_query(query, file_context, chat_history)
|
37 |
+
|
38 |
+
# 4. Drug Info Agent
|
39 |
+
if any(cmd in query_lower for cmd in ["drug", "medicine", "medication", "side effect", "dosage"]):
|
40 |
+
return self.drug_info_agent.process_query(query, file_context, chat_history)
|
41 |
+
|
42 |
+
# 5. Default to Academic Agent
|
43 |
+
return self.academic_agent.process_query(query, file_context, chat_history)
|
agents/viva_agent.py
ADDED
@@ -0,0 +1,117 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# agents/viva_agent.py
|
2 |
+
"""
|
3 |
+
Viva Practice Agent - Handles mock interview sessions with state management.
|
4 |
+
"""
|
5 |
+
import re
|
6 |
+
import json
|
7 |
+
|
8 |
+
class VivaAgent:
|
9 |
+
def __init__(self, gemini_model=None):
|
10 |
+
"""
|
11 |
+
Initializes the agent with the Gemini model.
|
12 |
+
|
13 |
+
Args:
|
14 |
+
gemini_model: An instance of the Gemini model client.
|
15 |
+
"""
|
16 |
+
self.model = gemini_model
|
17 |
+
|
18 |
+
def _get_viva_question(self, topic: str, difficulty: str, asked_questions: list, file_context: str) -> str:
|
19 |
+
"""Calls the Gemini API to generate a single, unique viva question."""
|
20 |
+
|
21 |
+
context_source = ""
|
22 |
+
if file_context:
|
23 |
+
context_source =f"---\nCONTEXT FROM KNOWLEDGE BASE:\n{file_context}\n---" if file_context else ""
|
24 |
+
else:
|
25 |
+
context_source = f"The question should be about the B.Pharmacy topic: **{topic}**."
|
26 |
+
|
27 |
+
prompt = f"""
|
28 |
+
You are a "Viva Coach," a professional and encouraging AI examiner for B.Pharmacy students like Gurus from ancient India.
|
29 |
+
**CRITICAL INSTRUCTION FOR CITATIONS:** When you use information from the KNOWLEDGE BASE CONTEXT, you MUST cite the source at the end of the relevant sentence using the format `[Source: filename, Page: page_number]`.
|
30 |
+
**Your Task:**
|
31 |
+
Generate a SINGLE, insightful, open-ended viva question.
|
32 |
+
|
33 |
+
**Instructions:**
|
34 |
+
1. **Context:** {context_source}
|
35 |
+
2. **Difficulty:** The question's difficulty should be **{difficulty}**.
|
36 |
+
3. **Avoid Repetition:** DO NOT ask any of these previously asked questions: {json.dumps(asked_questions)}.
|
37 |
+
4. **Format:** Return ONLY the question itself. No conversational text, no "Here is your question:", just the question.
|
38 |
+
|
39 |
+
Example: "Can you explain the primary mechanism of action for beta-blockers?"
|
40 |
+
"""
|
41 |
+
try:
|
42 |
+
response = self.model.generate_content(prompt)
|
43 |
+
# Basic cleanup of the response
|
44 |
+
return response.text.strip().replace("\"", "")
|
45 |
+
except Exception as e:
|
46 |
+
print(f"Viva Agent AI Error: {e}")
|
47 |
+
return "I'm having trouble thinking of a question right now. Please try again."
|
48 |
+
|
49 |
+
def process_query(self, query: str, file_context: str = "", viva_state: dict = None):
|
50 |
+
"""
|
51 |
+
Processes a query to manage a viva session.
|
52 |
+
|
53 |
+
Args:
|
54 |
+
query (str): The user's command (e.g., "start viva", "next question").
|
55 |
+
file_context (str): Optional text from an uploaded file to base the viva on.
|
56 |
+
viva_state (dict): The current state of the viva session from the user's session.
|
57 |
+
|
58 |
+
Returns:
|
59 |
+
dict: A dictionary containing the response and updated viva state.
|
60 |
+
"""
|
61 |
+
if not self.model:
|
62 |
+
return {
|
63 |
+
'message': "🗣️ **Viva Coach**\n\nThe examiner is unavailable! The Gemini API key is missing, so I can't conduct a viva. Please configure the API key.",
|
64 |
+
'agent_type': 'viva_practice',
|
65 |
+
'status': 'error_no_api_key',
|
66 |
+
'viva_state': None # No state change
|
67 |
+
}
|
68 |
+
|
69 |
+
query = query.lower().strip()
|
70 |
+
viva_state = viva_state or {'active': False, 'topic': '', 'asked_questions': [], 'difficulty': 'easy'}
|
71 |
+
|
72 |
+
# --- Command Handling ---
|
73 |
+
|
74 |
+
# Command: END VIVA
|
75 |
+
if viva_state['active'] and any(cmd in query for cmd in ["end viva", "stop viva", "exit viva"]):
|
76 |
+
message = "Great session! You did well. Keep practicing! 👏\n\nThe viva session has now ended."
|
77 |
+
return {'message': message, 'agent_used': 'viva_practice', 'status': 'session_ended', 'viva_state': {'active': False}}
|
78 |
+
|
79 |
+
# Command: START VIVA
|
80 |
+
if any(cmd in query for cmd in ["start viva", "begin viva"]):
|
81 |
+
topic_match = re.search(r'viva on (.+)', query)
|
82 |
+
topic = topic_match.group(1).title() if topic_match else "General Pharmacy"
|
83 |
+
|
84 |
+
# Reset state for a new session
|
85 |
+
new_state = {'active': True, 'topic': topic, 'asked_questions': [], 'difficulty': 'easy'}
|
86 |
+
|
87 |
+
first_question = self._get_viva_question(topic, new_state['difficulty'], [], file_context)
|
88 |
+
new_state['asked_questions'].append(first_question)
|
89 |
+
|
90 |
+
message = f"Alright, let's begin your viva on **{topic}**! Here is your first question:\n\n**Q1:** {first_question}"
|
91 |
+
return {'message': message, 'agent_used': 'viva_practice', 'status': 'session_started', 'viva_state': new_state}
|
92 |
+
|
93 |
+
# Command: NEXT QUESTION (only if a session is active)
|
94 |
+
if viva_state['active'] and any(cmd in query for cmd in ["next question", "next one", "ask another"]):
|
95 |
+
question_number = len(viva_state['asked_questions']) + 1
|
96 |
+
# Slightly increase difficulty over time
|
97 |
+
if question_number > 5: viva_state['difficulty'] = 'hard'
|
98 |
+
elif question_number > 2: viva_state['difficulty'] = 'medium'
|
99 |
+
|
100 |
+
new_question = self._get_viva_question(viva_state['topic'], viva_state['difficulty'], viva_state['asked_questions'], file_context)
|
101 |
+
viva_state['asked_questions'].append(new_question)
|
102 |
+
|
103 |
+
message = f"Excellent. Here is your next question:\n\n**Q{question_number}:** {new_question}"
|
104 |
+
return {'message': message, 'agent_used': 'viva_practice', 'status': 'next_question', 'viva_state': viva_state}
|
105 |
+
|
106 |
+
# Default response if no command is recognized
|
107 |
+
message = """🗣️ **Viva Coach Ready!**
|
108 |
+
|
109 |
+
You can start a session by typing:
|
110 |
+
* `start viva on [your topic]` (e.g., `start viva on Pharmacokinetics`)
|
111 |
+
* Or just `start viva` for general questions.
|
112 |
+
|
113 |
+
Once started, you can say:
|
114 |
+
* `next question`
|
115 |
+
* `end viva`
|
116 |
+
"""
|
117 |
+
return {'message': message, 'agent_type': 'viva_practice', 'status': 'idle', 'viva_state': viva_state}
|
app.py
ADDED
@@ -0,0 +1,755 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# # # Shiva
|
2 |
+
# # from flask import Flask, render_template, request, jsonify, session
|
3 |
+
# # import os
|
4 |
+
# # from dotenv import load_dotenv
|
5 |
+
# # import json
|
6 |
+
# # import random
|
7 |
+
# # from werkzeug.utils import secure_filename
|
8 |
+
# # import google.generativeai as genai
|
9 |
+
# # from pathlib import Path
|
10 |
+
|
11 |
+
# # # Load environment variables
|
12 |
+
# # load_dotenv()
|
13 |
+
|
14 |
+
# # app = Flask(__name__)
|
15 |
+
# # app.config['SECRET_KEY'] = os.getenv('FLASK_SECRET_KEY', 'dev-secret-key')
|
16 |
+
# # app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024 # 16MB max file size
|
17 |
+
|
18 |
+
# # # Configure upload settings
|
19 |
+
# # UPLOAD_FOLDER = 'uploads'
|
20 |
+
# # ALLOWED_EXTENSIONS = {'txt', 'pdf', 'docx', 'doc', 'json', 'csv'}
|
21 |
+
# # app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
|
22 |
+
|
23 |
+
# # # Create upload directory
|
24 |
+
# # os.makedirs(UPLOAD_FOLDER, exist_ok=True)
|
25 |
+
|
26 |
+
# # # Configure Gemini API
|
27 |
+
# # GEMINI_API_KEY = os.getenv('GEMINI_API_KEY')
|
28 |
+
# # if GEMINI_API_KEY:
|
29 |
+
# # genai.configure(api_key=GEMINI_API_KEY)
|
30 |
+
# # model = genai.GenerativeModel('gemini-1.5-pro')
|
31 |
+
# # print("✅ Gemini API configured successfully!")
|
32 |
+
# # else:
|
33 |
+
# # model = None
|
34 |
+
# # print("⚠️ No Gemini API key found. Using fallback responses.")
|
35 |
+
|
36 |
+
# # # Import agents and utilities
|
37 |
+
# # from agents.router_agent import RouterAgent
|
38 |
+
# # from utils.helpers import load_quotes, get_greeting
|
39 |
+
# # from utils.file_processor import FileProcessor
|
40 |
+
|
41 |
+
# # def allowed_file(filename):
|
42 |
+
# # return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
|
43 |
+
|
44 |
+
# # class MyPharmaAI:
|
45 |
+
# # def __init__(self):
|
46 |
+
# # self.router = RouterAgent(model) # Pass model to router
|
47 |
+
# # self.quotes = load_quotes()
|
48 |
+
# # self.file_processor = FileProcessor()
|
49 |
+
|
50 |
+
# # def process_query(self, query, user_name="Student", uploaded_files=None):
|
51 |
+
# # """Process user query through the router agent with optional file context"""
|
52 |
+
# # try:
|
53 |
+
# # # Check if we have uploaded files to reference
|
54 |
+
# # file_context = ""
|
55 |
+
# # if uploaded_files and 'uploaded_files' in session:
|
56 |
+
# # file_context = self.get_file_context(session['uploaded_files'])
|
57 |
+
|
58 |
+
# # # Route the query to appropriate agent
|
59 |
+
# # response = self.router.route_query(query, file_context)
|
60 |
+
# # return {
|
61 |
+
# # 'success': True,
|
62 |
+
# # 'response': response,
|
63 |
+
# # 'agent_used': response.get('agent_type', 'unknown')
|
64 |
+
# # }
|
65 |
+
# # except Exception as e:
|
66 |
+
# # return {
|
67 |
+
# # 'success': False,
|
68 |
+
# # 'response': f"माफ करें (Sorry), I encountered an error: {str(e)}",
|
69 |
+
# # 'agent_used': 'error'
|
70 |
+
# # }
|
71 |
+
|
72 |
+
# # def get_file_context(self, uploaded_files):
|
73 |
+
# # """Get context from uploaded files"""
|
74 |
+
# # context = ""
|
75 |
+
# # for file_info in uploaded_files[-3:]: # Last 3 files only
|
76 |
+
# # file_path = file_info['path']
|
77 |
+
# # if os.path.exists(file_path):
|
78 |
+
# # try:
|
79 |
+
# # content = self.file_processor.extract_text(file_path)
|
80 |
+
# # if content:
|
81 |
+
# # context += f"\n\n📄 Content from {file_info['original_name']}:\n{content[:2000]}..." # Limit context
|
82 |
+
# # except Exception as e:
|
83 |
+
# # context += f"\n\n❌ Error reading {file_info['original_name']}: {str(e)}"
|
84 |
+
# # return context
|
85 |
+
|
86 |
+
# # def get_daily_quote(self):
|
87 |
+
# # """Get inspirational quote from Gita/Vedas"""
|
88 |
+
# # return random.choice(self.quotes) if self.quotes else "विद्या धनं सर्व धन प्रधानम्"
|
89 |
+
|
90 |
+
# # def process_file_upload(self, file):
|
91 |
+
# # """Process uploaded file and extract information"""
|
92 |
+
# # try:
|
93 |
+
# # if file and allowed_file(file.filename):
|
94 |
+
# # filename = secure_filename(file.filename)
|
95 |
+
# # timestamp = str(int(time.time()))
|
96 |
+
# # filename = f"{timestamp}_{filename}"
|
97 |
+
# # file_path = os.path.join(app.config['UPLOAD_FOLDER'], filename)
|
98 |
+
# # file.save(file_path)
|
99 |
+
|
100 |
+
# # # Extract text content
|
101 |
+
# # content = self.file_processor.extract_text(file_path)
|
102 |
+
|
103 |
+
# # # Store in session
|
104 |
+
# # if 'uploaded_files' not in session:
|
105 |
+
# # session['uploaded_files'] = []
|
106 |
+
|
107 |
+
# # file_info = {
|
108 |
+
# # 'original_name': file.filename,
|
109 |
+
# # 'saved_name': filename,
|
110 |
+
# # 'path': file_path,
|
111 |
+
# # 'size': os.path.getsize(file_path),
|
112 |
+
# # 'preview': content[:500] if content else "No text content extracted"
|
113 |
+
# # }
|
114 |
+
|
115 |
+
# # session['uploaded_files'].append(file_info)
|
116 |
+
# # session.modified = True
|
117 |
+
|
118 |
+
# # return {
|
119 |
+
# # 'success': True,
|
120 |
+
# # 'message': f'File "{file.filename}" uploaded successfully! You can now ask questions about its content.',
|
121 |
+
# # 'file_info': file_info
|
122 |
+
# # }
|
123 |
+
# # else:
|
124 |
+
# # return {
|
125 |
+
# # 'success': False,
|
126 |
+
# # 'message': 'Invalid file type. Supported: TXT, PDF, DOCX, DOC, JSON, CSV'
|
127 |
+
# # }
|
128 |
+
# # except Exception as e:
|
129 |
+
# # return {
|
130 |
+
# # 'success': False,
|
131 |
+
# # 'message': f'Error uploading file: {str(e)}'
|
132 |
+
# # }
|
133 |
+
|
134 |
+
# # # Initialize the AI system
|
135 |
+
# # import time
|
136 |
+
# # pharma_ai = MyPharmaAI()
|
137 |
+
|
138 |
+
# # @app.route('/')
|
139 |
+
# # def index():
|
140 |
+
# # """Main chat interface"""
|
141 |
+
# # greeting = get_greeting()
|
142 |
+
# # daily_quote = pharma_ai.get_daily_quote()
|
143 |
+
|
144 |
+
# # # Get uploaded files info
|
145 |
+
# # uploaded_files = session.get('uploaded_files', [])
|
146 |
+
|
147 |
+
# # return render_template('index.html',
|
148 |
+
# # greeting=greeting,
|
149 |
+
# # daily_quote=daily_quote,
|
150 |
+
# # uploaded_files=uploaded_files,
|
151 |
+
# # api_available=bool(GEMINI_API_KEY))
|
152 |
+
|
153 |
+
# # @app.route('/chat', methods=['POST'])
|
154 |
+
# # def chat():
|
155 |
+
# # """Main chat endpoint"""
|
156 |
+
# # try:
|
157 |
+
# # data = request.get_json()
|
158 |
+
|
159 |
+
# # if not data or 'query' not in data:
|
160 |
+
# # return jsonify({
|
161 |
+
# # 'success': False,
|
162 |
+
# # 'error': 'No query provided'
|
163 |
+
# # }), 400
|
164 |
+
|
165 |
+
# # user_query = data.get('query', '').strip()
|
166 |
+
# # user_name = data.get('user_name', 'Student')
|
167 |
+
|
168 |
+
# # if not user_query:
|
169 |
+
# # return jsonify({
|
170 |
+
# # 'success': False,
|
171 |
+
# # 'error': 'Empty query'
|
172 |
+
# # }), 400
|
173 |
+
|
174 |
+
# # # Process the query (with file context if available)
|
175 |
+
# # result = pharma_ai.process_query(user_query, user_name, session.get('uploaded_files'))
|
176 |
+
|
177 |
+
# # return jsonify(result)
|
178 |
+
|
179 |
+
# # except Exception as e:
|
180 |
+
# # return jsonify({
|
181 |
+
# # 'success': False,
|
182 |
+
# # 'error': f'Server error: {str(e)}'
|
183 |
+
# # }), 500
|
184 |
+
|
185 |
+
# # @app.route('/upload', methods=['POST'])
|
186 |
+
# # def upload_file():
|
187 |
+
# # """Handle file upload"""
|
188 |
+
# # try:
|
189 |
+
# # if 'file' not in request.files:
|
190 |
+
# # return jsonify({
|
191 |
+
# # 'success': False,
|
192 |
+
# # 'error': 'No file provided'
|
193 |
+
# # }), 400
|
194 |
+
|
195 |
+
# # file = request.files['file']
|
196 |
+
|
197 |
+
# # if file.filename == '':
|
198 |
+
# # return jsonify({
|
199 |
+
# # 'success': False,
|
200 |
+
# # 'error': 'No file selected'
|
201 |
+
# # }), 400
|
202 |
+
|
203 |
+
# # result = pharma_ai.process_file_upload(file)
|
204 |
+
# # return jsonify(result)
|
205 |
+
|
206 |
+
# # except Exception as e:
|
207 |
+
# # return jsonify({
|
208 |
+
# # 'success': False,
|
209 |
+
# # 'error': f'Upload error: {str(e)}'
|
210 |
+
# # }), 500
|
211 |
+
|
212 |
+
# # @app.route('/files')
|
213 |
+
# # def get_uploaded_files():
|
214 |
+
# # """Get list of uploaded files"""
|
215 |
+
# # uploaded_files = session.get('uploaded_files', [])
|
216 |
+
# # return jsonify({
|
217 |
+
# # 'files': uploaded_files,
|
218 |
+
# # 'count': len(uploaded_files)
|
219 |
+
# # })
|
220 |
+
|
221 |
+
# # @app.route('/clear_files', methods=['POST'])
|
222 |
+
# # def clear_files():
|
223 |
+
# # """Clear uploaded files"""
|
224 |
+
# # try:
|
225 |
+
# # # Remove files from disk
|
226 |
+
# # if 'uploaded_files' in session:
|
227 |
+
# # for file_info in session['uploaded_files']:
|
228 |
+
# # file_path = file_info['path']
|
229 |
+
# # if os.path.exists(file_path):
|
230 |
+
# # os.remove(file_path)
|
231 |
+
|
232 |
+
# # # Clear session
|
233 |
+
# # session.pop('uploaded_files', None)
|
234 |
+
|
235 |
+
# # return jsonify({
|
236 |
+
# # 'success': True,
|
237 |
+
# # 'message': 'All files cleared successfully'
|
238 |
+
# # })
|
239 |
+
# # except Exception as e:
|
240 |
+
# # return jsonify({
|
241 |
+
# # 'success': False,
|
242 |
+
# # 'error': f'Error clearing files: {str(e)}'
|
243 |
+
# # }), 500
|
244 |
+
|
245 |
+
# # @app.route('/quote')
|
246 |
+
# # def get_quote():
|
247 |
+
# # """Get a random inspirational quote"""
|
248 |
+
# # quote = pharma_ai.get_daily_quote()
|
249 |
+
# # return jsonify({'quote': quote})
|
250 |
+
|
251 |
+
# # @app.route('/health')
|
252 |
+
# # def health_check():
|
253 |
+
# # """Health check endpoint"""
|
254 |
+
# # return jsonify({
|
255 |
+
# # 'status': 'healthy',
|
256 |
+
# # 'app': 'MyPharma AI',
|
257 |
+
# # 'version': '2.0.0',
|
258 |
+
# # 'gemini_api': 'connected' if GEMINI_API_KEY else 'not configured',
|
259 |
+
# # 'features': ['chat', 'file_upload', 'multi_agent', 'indian_theme']
|
260 |
+
# # })
|
261 |
+
|
262 |
+
# # if __name__ == '__main__':
|
263 |
+
# # # Create necessary directories
|
264 |
+
# # for directory in ['data', 'static/css', 'static/js', 'templates', 'agents', 'utils', 'uploads']:
|
265 |
+
# # os.makedirs(directory, exist_ok=True)
|
266 |
+
|
267 |
+
# # print("🇮🇳 MyPharma AI Starting...")
|
268 |
+
# # print(f"📁 Upload folder: {UPLOAD_FOLDER}")
|
269 |
+
# # print(f"🤖 Gemini API: {'✅ Ready' if GEMINI_API_KEY else '❌ Not configured'}")
|
270 |
+
# # print("🚀 Server starting on http://localhost:5000")
|
271 |
+
|
272 |
+
# # # Run the app
|
273 |
+
# # app.run(debug=True, port=5000)
|
274 |
+
# # # #### app.py (Main Application)
|
275 |
+
# # # from flask import Flask, render_template, request, jsonify
|
276 |
+
# # # import os
|
277 |
+
# # # from dotenv import load_dotenv
|
278 |
+
# # # import json
|
279 |
+
# # # import random
|
280 |
+
|
281 |
+
# # # # Load environment variables
|
282 |
+
# # # load_dotenv()
|
283 |
+
|
284 |
+
# # # app = Flask(__name__)
|
285 |
+
# # # app.config['SECRET_KEY'] = os.getenv('FLASK_SECRET_KEY', 'dev-secret-key')
|
286 |
+
|
287 |
+
# # # # Import agents
|
288 |
+
# # # from agents.router_agent import RouterAgent
|
289 |
+
# # # from utils.helpers import load_quotes, get_greeting
|
290 |
+
|
291 |
+
# # # class MyPharmaAI:
|
292 |
+
# # # def __init__(self):
|
293 |
+
# # # self.router = RouterAgent()
|
294 |
+
# # # self.quotes = load_quotes()
|
295 |
+
|
296 |
+
# # # def process_query(self, query, user_name="Student"):
|
297 |
+
# # # """Process user query through the router agent"""
|
298 |
+
# # # try:
|
299 |
+
# # # # Route the query to appropriate agent
|
300 |
+
# # # response = self.router.route_query(query)
|
301 |
+
# # # return {
|
302 |
+
# # # 'success': True,
|
303 |
+
# # # 'response': response,
|
304 |
+
# # # 'agent_used': response.get('agent_type', 'unknown')
|
305 |
+
# # # }
|
306 |
+
# # # except Exception as e:
|
307 |
+
# # # return {
|
308 |
+
# # # 'success': False,
|
309 |
+
# # # 'response': f"माफ करें (Sorry), I encountered an error: {str(e)}",
|
310 |
+
# # # 'agent_used': 'error'
|
311 |
+
# # # }
|
312 |
+
|
313 |
+
# # # def get_daily_quote(self):
|
314 |
+
# # # """Get inspirational quote from Gita/Vedas"""
|
315 |
+
# # # return random.choice(self.quotes) if self.quotes else "विद्या धनं सर्व धन प्रधानम्"
|
316 |
+
|
317 |
+
# # # # Initialize the AI system
|
318 |
+
# # # pharma_ai = MyPharmaAI()
|
319 |
+
|
320 |
+
# # # @app.route('/')
|
321 |
+
# # # def index():
|
322 |
+
# # # """Main chat interface"""
|
323 |
+
# # # greeting = get_greeting()
|
324 |
+
# # # daily_quote = pharma_ai.get_daily_quote()
|
325 |
+
# # # return render_template('index.html',
|
326 |
+
# # # greeting=greeting,
|
327 |
+
# # # daily_quote=daily_quote)
|
328 |
+
|
329 |
+
# # # @app.route('/chat', methods=['POST'])
|
330 |
+
# # # def chat():
|
331 |
+
# # # """Main chat endpoint"""
|
332 |
+
# # # try:
|
333 |
+
# # # data = request.get_json()
|
334 |
+
|
335 |
+
# # # if not data or 'query' not in data:
|
336 |
+
# # # return jsonify({
|
337 |
+
# # # 'success': False,
|
338 |
+
# # # 'error': 'No query provided'
|
339 |
+
# # # }), 400
|
340 |
+
|
341 |
+
# # # user_query = data.get('query', '').strip()
|
342 |
+
# # # user_name = data.get('user_name', 'Student')
|
343 |
+
|
344 |
+
# # # if not user_query:
|
345 |
+
# # # return jsonify({
|
346 |
+
# # # 'success': False,
|
347 |
+
# # # 'error': 'Empty query'
|
348 |
+
# # # }), 400
|
349 |
+
|
350 |
+
# # # # Process the query
|
351 |
+
# # # result = pharma_ai.process_query(user_query, user_name)
|
352 |
+
|
353 |
+
# # # return jsonify(result)
|
354 |
+
|
355 |
+
# # # except Exception as e:
|
356 |
+
# # # return jsonify({
|
357 |
+
# # # 'success': False,
|
358 |
+
# # # 'error': f'Server error: {str(e)}'
|
359 |
+
# # # }), 500
|
360 |
+
|
361 |
+
# # # @app.route('/quote')
|
362 |
+
# # # def get_quote():
|
363 |
+
# # # """Get a random inspirational quote"""
|
364 |
+
# # # quote = pharma_ai.get_daily_quote()
|
365 |
+
# # # return jsonify({'quote': quote})
|
366 |
+
|
367 |
+
# # # @app.route('/health')
|
368 |
+
# # # def health_check():
|
369 |
+
# # # """Health check endpoint"""
|
370 |
+
# # # return jsonify({
|
371 |
+
# # # 'status': 'healthy',
|
372 |
+
# # # 'app': 'MyPharma AI',
|
373 |
+
# # # 'version': '1.0.0'
|
374 |
+
# # # })
|
375 |
+
|
376 |
+
# # # if __name__ == '__main__':
|
377 |
+
# # # # Create data directories if they don't exist
|
378 |
+
# # # os.makedirs('data', exist_ok=True)
|
379 |
+
# # # os.makedirs('static/css', exist_ok=True)
|
380 |
+
# # # os.makedirs('static/js', exist_ok=True)
|
381 |
+
# # # os.makedirs('templates', exist_ok=True)
|
382 |
+
# # # os.makedirs('agents', exist_ok=True)
|
383 |
+
# # # os.makedirs('utils', exist_ok=True)
|
384 |
+
|
385 |
+
# # # # Run the app
|
386 |
+
# # # app.run(debug=True, port=5000)
|
387 |
+
|
388 |
+
# # app.py
|
389 |
+
# # Main Flask application for MyPharma AI
|
390 |
+
|
391 |
+
# from flask import Flask, render_template, request, jsonify, session
|
392 |
+
# import os
|
393 |
+
# import json
|
394 |
+
# import random
|
395 |
+
# import time
|
396 |
+
# from dotenv import load_dotenv
|
397 |
+
# from werkzeug.utils import secure_filename
|
398 |
+
# import google.generativeai as genai
|
399 |
+
|
400 |
+
# # Load environment variables from a .env file
|
401 |
+
# load_dotenv()
|
402 |
+
|
403 |
+
# # --- App Configuration ---
|
404 |
+
# app = Flask(__name__)
|
405 |
+
# app.config['SECRET_KEY'] = os.getenv('FLASK_SECRET_KEY', 'a-very-secret-key-for-dev')
|
406 |
+
# app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024 # 16MB max file size
|
407 |
+
|
408 |
+
# # --- Upload Configuration ---
|
409 |
+
# UPLOAD_FOLDER = '/tmp/uploads'
|
410 |
+
# ALLOWED_EXTENSIONS = {'txt', 'pdf', 'docx', 'json', 'csv'}
|
411 |
+
# app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
|
412 |
+
# os.makedirs(UPLOAD_FOLDER, exist_ok=True)
|
413 |
+
|
414 |
+
# # --- Gemini API Configuration ---
|
415 |
+
# GEMINI_API_KEY = os.getenv('GEMINI_API_KEY')
|
416 |
+
# model = None
|
417 |
+
# if GEMINI_API_KEY:
|
418 |
+
# try:
|
419 |
+
# genai.configure(api_key=GEMINI_API_KEY)
|
420 |
+
# # Using gemini-1.5-flash for speed and cost-effectiveness
|
421 |
+
# model = genai.GenerativeModel('gemini-1.5-flash')
|
422 |
+
# print("✅ Gemini 1.5 Flash Model configured successfully!")
|
423 |
+
# except Exception as e:
|
424 |
+
# print(f"❌ Error configuring Gemini API: {e}")
|
425 |
+
# else:
|
426 |
+
# print("⚠️ No Gemini API key found. AI features will be disabled.")
|
427 |
+
|
428 |
+
# # --- Import Agents and Utilities ---
|
429 |
+
# # (Ensure these files exist in their respective directories)
|
430 |
+
# from agents.router_agent import RouterAgent
|
431 |
+
# from utils.helpers import load_quotes, get_greeting
|
432 |
+
# from utils.file_processor import FileProcessor
|
433 |
+
|
434 |
+
# def allowed_file(filename):
|
435 |
+
# """Check if the uploaded file has an allowed extension."""
|
436 |
+
# return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
|
437 |
+
|
438 |
+
# # --- Main AI Application Class ---
|
439 |
+
# class MyPharmaAI:
|
440 |
+
# """Orchestrator for the entire AI system."""
|
441 |
+
# def __init__(self):
|
442 |
+
# self.router = RouterAgent(model) # The router now gets the configured model
|
443 |
+
# self.quotes = load_quotes()
|
444 |
+
# self.file_processor = FileProcessor()
|
445 |
+
|
446 |
+
# def process_query(self, query, user_name="Student", viva_state=None, uploaded_files=None, chat_history=None):
|
447 |
+
# """Routes a user's query to the appropriate agent, handling context."""
|
448 |
+
# try:
|
449 |
+
# # This block correctly gets the file content from the session data
|
450 |
+
# file_context = ""
|
451 |
+
# if uploaded_files:
|
452 |
+
# file_context = self.get_file_context(uploaded_files)
|
453 |
+
|
454 |
+
# # This passes the file content and chat history to the router
|
455 |
+
# response_data = self.router.route_query(query, file_context, viva_state, chat_history)
|
456 |
+
|
457 |
+
# return {
|
458 |
+
# 'success': True,
|
459 |
+
# **response_data
|
460 |
+
# }
|
461 |
+
# except Exception as e:
|
462 |
+
# print(f"Error in MyPharmaAI.process_query: {e}")
|
463 |
+
# return {
|
464 |
+
# 'success': False,
|
465 |
+
# 'message': f"Sorry, a critical error occurred: {str(e)}",
|
466 |
+
# 'agent_used': 'error'
|
467 |
+
# }
|
468 |
+
|
469 |
+
|
470 |
+
# def get_file_context(self, uploaded_files_session):
|
471 |
+
# """Extracts text from the most recent files to use as context."""
|
472 |
+
# context = ""
|
473 |
+
# for file_info in uploaded_files_session[-3:]: # Limit to last 3 files
|
474 |
+
# file_path = file_info.get('path')
|
475 |
+
# if file_path and os.path.exists(file_path):
|
476 |
+
# try:
|
477 |
+
# content = self.file_processor.extract_text(file_path)
|
478 |
+
# if content:
|
479 |
+
# # Limit context from each file to 2000 characters
|
480 |
+
# context += f"\n\n--- Content from {file_info['original_name']} ---\n{content[:2000]}..."
|
481 |
+
# except Exception as e:
|
482 |
+
# context += f"\n\n--- Error reading {file_info['original_name']}: {str(e)} ---"
|
483 |
+
# return context
|
484 |
+
|
485 |
+
# def get_daily_quote(self):
|
486 |
+
# """Returns a random quote."""
|
487 |
+
# return random.choice(self.quotes) if self.quotes else "विद्या धनं सर्व धन प्रधानम्"
|
488 |
+
|
489 |
+
# # Initialize the AI system
|
490 |
+
# pharma_ai = MyPharmaAI()
|
491 |
+
|
492 |
+
# # --- Flask Routes ---
|
493 |
+
|
494 |
+
# @app.route('/')
|
495 |
+
# def index():
|
496 |
+
# """Renders the main chat interface."""
|
497 |
+
# greeting = get_greeting()
|
498 |
+
# daily_quote = pharma_ai.get_daily_quote()
|
499 |
+
# uploaded_files = session.get('uploaded_files', [])
|
500 |
+
# return render_template('index.html',
|
501 |
+
# greeting=greeting,
|
502 |
+
# daily_quote=daily_quote,
|
503 |
+
# uploaded_files=uploaded_files)
|
504 |
+
|
505 |
+
# @app.route('/chat', methods=['POST'])
|
506 |
+
# def chat():
|
507 |
+
# """Handles the main chat logic, including session management for the Viva Agent."""
|
508 |
+
# try:
|
509 |
+
# data = request.get_json()
|
510 |
+
# query = data.get('query', '').strip()
|
511 |
+
# if not query:
|
512 |
+
# return jsonify({'success': False, 'error': 'Empty query'}), 400
|
513 |
+
|
514 |
+
# # --- HISTORY MANAGEMENT START ---
|
515 |
+
|
516 |
+
# # Get the conversation history from the session (or start a new one)
|
517 |
+
# chat_history = session.get('chat_history', [])
|
518 |
+
|
519 |
+
# # Get current viva state from session for the Viva Agent
|
520 |
+
# viva_state = session.get('viva_state', None)
|
521 |
+
# uploaded_files = session.get('uploaded_files', None)
|
522 |
+
|
523 |
+
# # Process the query through the main orchestrator
|
524 |
+
# result = pharma_ai.process_query(query, viva_state=viva_state, uploaded_files=uploaded_files,chat_history=chat_history)
|
525 |
+
# # If the query was successful, update the history
|
526 |
+
# if result.get('success'):
|
527 |
+
# # Add the user's query and the AI's message to the history
|
528 |
+
# chat_history.append({'role': 'user', 'parts': [query]})
|
529 |
+
# chat_history.append({'role': 'model', 'parts': [result.get('message', '')]})
|
530 |
+
|
531 |
+
# # Keep the history from getting too long (e.g., last 10 exchanges)
|
532 |
+
# session['chat_history'] = chat_history[-20:]
|
533 |
+
|
534 |
+
# # --- HISTORY MANAGEMENT END ---
|
535 |
+
|
536 |
+
# # If the Viva agent returns an updated state, save it to the session
|
537 |
+
# if 'viva_state' in result:
|
538 |
+
# session['viva_state'] = result.get('viva_state')
|
539 |
+
|
540 |
+
# return jsonify(result)
|
541 |
+
|
542 |
+
# except Exception as e:
|
543 |
+
# print(f"Error in /chat endpoint: {e}")
|
544 |
+
# return jsonify({'success': False, 'error': f'Server error: {str(e)}'}), 500
|
545 |
+
|
546 |
+
# @app.route('/upload', methods=['POST'])
|
547 |
+
# def upload_file():
|
548 |
+
# """Handles file uploads."""
|
549 |
+
# if 'file' not in request.files:
|
550 |
+
# return jsonify({'success': False, 'error': 'No file part'}), 400
|
551 |
+
# file = request.files['file']
|
552 |
+
# if file.filename == '':
|
553 |
+
# return jsonify({'success': False, 'error': 'No selected file'}), 400
|
554 |
+
# if file and allowed_file(file.filename):
|
555 |
+
# filename = secure_filename(file.filename)
|
556 |
+
# file_path = os.path.join(app.config['UPLOAD_FOLDER'], filename)
|
557 |
+
# file.save(file_path)
|
558 |
+
|
559 |
+
# if 'uploaded_files' not in session:
|
560 |
+
# session['uploaded_files'] = []
|
561 |
+
|
562 |
+
# file_info = {'original_name': filename, 'path': file_path}
|
563 |
+
# session['uploaded_files'].append(file_info)
|
564 |
+
# session.modified = True
|
565 |
+
|
566 |
+
# return jsonify({
|
567 |
+
# 'success': True,
|
568 |
+
# 'message': f'File "{filename}" uploaded. You can now ask questions about it.',
|
569 |
+
# 'files': session['uploaded_files']
|
570 |
+
# })
|
571 |
+
# return jsonify({'success': False, 'error': 'File type not allowed'}), 400
|
572 |
+
|
573 |
+
# @app.route('/files', methods=['GET'])
|
574 |
+
# def get_uploaded_files():
|
575 |
+
# """Returns the list of uploaded files from the session."""
|
576 |
+
# return jsonify({'files': session.get('uploaded_files', [])})
|
577 |
+
|
578 |
+
# @app.route('/clear_files', methods=['POST'])
|
579 |
+
# def clear_files():
|
580 |
+
# """Deletes uploaded files from disk and clears them from the session."""
|
581 |
+
# if 'uploaded_files' in session:
|
582 |
+
# for file_info in session['uploaded_files']:
|
583 |
+
# if os.path.exists(file_info['path']):
|
584 |
+
# os.remove(file_info['path'])
|
585 |
+
# session.pop('uploaded_files', None)
|
586 |
+
# session.pop('viva_state', None) # Also clear viva state
|
587 |
+
# return jsonify({'success': True, 'message': 'All files and sessions cleared.'})
|
588 |
+
|
589 |
+
# @app.route('/quote')
|
590 |
+
# def get_quote():
|
591 |
+
# """Returns a new random quote."""
|
592 |
+
# return jsonify({'quote': pharma_ai.get_daily_quote()})
|
593 |
+
|
594 |
+
# # --- Main Execution ---
|
595 |
+
# # if __name__ == '__main__':
|
596 |
+
# # # Ensure all necessary directories exist
|
597 |
+
# # for directory in ['data', 'static/css', 'static/js', 'templates', 'agents', 'utils', 'uploads']:
|
598 |
+
# # os.makedirs(directory, exist_ok=True)
|
599 |
+
|
600 |
+
# # print("🇮🇳 MyPharma AI Starting...")
|
601 |
+
# # print(f"🤖 Gemini API Status: {'✅ Ready' if model else '❌ Not configured'}")
|
602 |
+
# # print("🚀 Server starting on http://127.0.0.1:5000")
|
603 |
+
# # app.run(debug=True, port=5000)
|
604 |
+
# if __name__ == '__main__':
|
605 |
+
# # Create necessary directories (this is good practice)
|
606 |
+
# for directory in ['data', 'uploads', 'templates']:
|
607 |
+
# os.makedirs(directory, exist_ok=True)
|
608 |
+
|
609 |
+
# # Get port from environment variable, defaulting to 5000 for local testing
|
610 |
+
# port = int(os.environ.get('PORT', 7860))
|
611 |
+
|
612 |
+
# print("🇮🇳 MyPharma AI Starting...")
|
613 |
+
# print(f"🤖 Gemini API Status: {'✅ Ready' if model else '❌ Not configured'}")
|
614 |
+
# print(f"🚀 Server starting on http://0.0.0.0:{port}")
|
615 |
+
|
616 |
+
# # Run the app to be accessible on the server
|
617 |
+
# app.run(host='0.0.0.0', port=port)
|
618 |
+
|
619 |
+
|
620 |
+
|
621 |
+
|
622 |
+
# app.py
|
623 |
+
|
624 |
+
import os
|
625 |
+
import random
|
626 |
+
from dotenv import load_dotenv
|
627 |
+
from flask import Flask, render_template, request, jsonify, session
|
628 |
+
import google.generativeai as genai
|
629 |
+
|
630 |
+
# Import new langchain components and our helpers
|
631 |
+
from langchain_google_genai import GoogleGenerativeAIEmbeddings
|
632 |
+
from langchain_community.vectorstores import FAISS
|
633 |
+
from utils.helpers import create_vector_store, get_greeting, load_quotes
|
634 |
+
from agents.router_agent import RouterAgent # Re-import the RouterAgent
|
635 |
+
|
636 |
+
# --- Initial Setup ---
|
637 |
+
load_dotenv()
|
638 |
+
# Create the knowledge library on first startup if it doesn't exist
|
639 |
+
create_vector_store()
|
640 |
+
|
641 |
+
# --- App Configuration ---
|
642 |
+
app = Flask(__name__)
|
643 |
+
app.config['SECRET_KEY'] = os.getenv('FLASK_SECRET_KEY', 'a-very-secret-key-for-dev')
|
644 |
+
|
645 |
+
# --- Gemini API & Knowledge Base Configuration ---
|
646 |
+
model = None
|
647 |
+
vector_store = None
|
648 |
+
try:
|
649 |
+
GEMINI_API_KEY = os.getenv('GOOGLE_API_KEY')
|
650 |
+
if GEMINI_API_KEY:
|
651 |
+
genai.configure(api_key=GEMINI_API_KEY)
|
652 |
+
model = genai.GenerativeModel('gemini-1.5-flash')
|
653 |
+
index_path = '/tmp/faiss_index'
|
654 |
+
if os.path.exists(index_path):
|
655 |
+
embeddings = GoogleGenerativeAIEmbeddings(model="models/embedding-001")
|
656 |
+
vector_store = FAISS.load_local(index_path, embeddings, allow_dangerous_deserialization=True)
|
657 |
+
print("✅ Gemini Model and Knowledge Base loaded successfully!")
|
658 |
+
else:
|
659 |
+
print("✅ Gemini Model loaded. No knowledge base found to load.")
|
660 |
+
else:
|
661 |
+
print("⚠️ No Gemini API key found.")
|
662 |
+
except Exception as e:
|
663 |
+
print(f"❌ Error during initialization: {e}")
|
664 |
+
|
665 |
+
# --- Main AI Application Class (Reinstated) ---
|
666 |
+
class MyPharmaAI:
|
667 |
+
def __init__(self, gemini_model, vector_store_db):
|
668 |
+
self.router = RouterAgent(gemini_model)
|
669 |
+
self.quotes = load_quotes()
|
670 |
+
self.vector_store = vector_store_db
|
671 |
+
|
672 |
+
def process_query(self, query, viva_state, chat_history):
|
673 |
+
# This is the core logic that combines both systems:
|
674 |
+
# 1. Search the permanent knowledge base for context.
|
675 |
+
file_context = ""
|
676 |
+
if self.vector_store:
|
677 |
+
relevant_docs = self.vector_store.similarity_search(query, k=4) # Get top 4 results
|
678 |
+
file_context = "\n".join(doc.page_content for doc in relevant_docs)
|
679 |
+
context_with_sources = []
|
680 |
+
for doc in relevant_docs:
|
681 |
+
# Clean up the source path to just the filename
|
682 |
+
source_filename = os.path.basename(doc.metadata.get('source', 'Unknown Source'))
|
683 |
+
# Page numbers from PyPDF are 0-indexed, so we add 1 for readability
|
684 |
+
page_number = doc.metadata.get('page', -1) + 1
|
685 |
+
|
686 |
+
context_with_sources.append(
|
687 |
+
f"[Source: {source_filename}, Page: {page_number}]\n{doc.page_content}"
|
688 |
+
)
|
689 |
+
|
690 |
+
file_context = "\n\n".join(context_with_sources)
|
691 |
+
|
692 |
+
# 2. Pass the retrieved context to the multi-agent router system.
|
693 |
+
return self.router.route_query(query, file_context, viva_state, chat_history)
|
694 |
+
|
695 |
+
pharma_ai = MyPharmaAI(model, vector_store)
|
696 |
+
|
697 |
+
# --- Flask Routes ---
|
698 |
+
@app.route('/')
|
699 |
+
def index():
|
700 |
+
# Use the correct template name
|
701 |
+
return render_template('index.html', greeting=get_greeting(), daily_quote=random.choice(pharma_ai.quotes))
|
702 |
+
|
703 |
+
@app.route('/chat', methods=['POST'])
|
704 |
+
def chat():
|
705 |
+
# This function is now the final, stable version.
|
706 |
+
try:
|
707 |
+
data = request.get_json()
|
708 |
+
query = data.get('query', '').strip()
|
709 |
+
if not query:
|
710 |
+
return jsonify({'success': False, 'error': 'Empty query'}), 400
|
711 |
+
|
712 |
+
chat_history = session.get('chat_history', [])
|
713 |
+
viva_state = session.get('viva_state', None)
|
714 |
+
|
715 |
+
# Get the result dictionary from the agent system
|
716 |
+
agent_result = pharma_ai.process_query(query, viva_state, chat_history)
|
717 |
+
|
718 |
+
# --- THIS IS THE FIX ---
|
719 |
+
# We now build the final JSON response to match what the JavaScript expects.
|
720 |
+
if "error" in agent_result.get('status', ''):
|
721 |
+
final_response = {
|
722 |
+
'success': False,
|
723 |
+
'error': agent_result.get('message', 'An unknown error occurred.'),
|
724 |
+
'agent_used': agent_result.get('agent_used', 'error')
|
725 |
+
}
|
726 |
+
else:
|
727 |
+
final_response = {
|
728 |
+
'success': True,
|
729 |
+
'message': agent_result.get('message', 'Sorry, I could not generate a response.'),
|
730 |
+
'agent_used': agent_result.get('agent_used', 'academic')
|
731 |
+
}
|
732 |
+
# --- END OF FIX ---
|
733 |
+
|
734 |
+
# Update chat history if the call was successful
|
735 |
+
if final_response.get('success'):
|
736 |
+
chat_history.append({'role': 'user', 'parts': [query]})
|
737 |
+
chat_history.append({'role': 'model', 'parts': [final_response.get('message', '')]})
|
738 |
+
session['chat_history'] = chat_history[-10:]
|
739 |
+
|
740 |
+
# Handle Viva state if present (no changes needed here)
|
741 |
+
if 'viva_state' in agent_result:
|
742 |
+
session['viva_state'] = agent_result.get('viva_state')
|
743 |
+
|
744 |
+
return jsonify(final_response)
|
745 |
+
|
746 |
+
except Exception as e:
|
747 |
+
print(f"Critical Error in /chat endpoint: {e}")
|
748 |
+
return jsonify({'success': False, 'error': f'A critical server error occurred: {e}', 'agent_used': 'error'}), 500
|
749 |
+
|
750 |
+
|
751 |
+
# --- Main Execution ---
|
752 |
+
if __name__ == '__main__':
|
753 |
+
# app.run(host='127.0.0.1', port=5000, debug=True)
|
754 |
+
port = int(os.environ.get('PORT', 7860))
|
755 |
+
app.run(host='0.0.0.0', port=port)
|
data/academic_knowledge.json
ADDED
@@ -0,0 +1,55 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"pharmacology": {
|
3 |
+
"definition": "Pharmacology is the branch of medicine concerned with the uses, effects, and modes of action of drugs.",
|
4 |
+
"branches": [
|
5 |
+
"Pharmacokinetics",
|
6 |
+
"Pharmacodynamics",
|
7 |
+
"Toxicology",
|
8 |
+
"Clinical Pharmacology"
|
9 |
+
],
|
10 |
+
"importance": "Essential for understanding drug therapy and patient safety"
|
11 |
+
},
|
12 |
+
"pharmacokinetics": {
|
13 |
+
"definition": "The study of how the body affects a drug (ADME: Absorption, Distribution, Metabolism, Excretion)",
|
14 |
+
"processes": [
|
15 |
+
"Absorption",
|
16 |
+
"Distribution",
|
17 |
+
"Metabolism",
|
18 |
+
"Excretion"
|
19 |
+
],
|
20 |
+
"factors": [
|
21 |
+
"Age",
|
22 |
+
"Gender",
|
23 |
+
"Disease state",
|
24 |
+
"Genetic factors"
|
25 |
+
]
|
26 |
+
},
|
27 |
+
"pharmacodynamics": {
|
28 |
+
"definition": "The study of what a drug does to the body - drug actions and effects",
|
29 |
+
"concepts": [
|
30 |
+
"Receptor theory",
|
31 |
+
"Dose-response relationship",
|
32 |
+
"Therapeutic index"
|
33 |
+
],
|
34 |
+
"mechanisms": [
|
35 |
+
"Agonism",
|
36 |
+
"Antagonism",
|
37 |
+
"Enzyme inhibition"
|
38 |
+
]
|
39 |
+
},
|
40 |
+
"krebs_cycle": {
|
41 |
+
"definition": "A series of enzymatic reactions that generate energy (ATP) from carbohydrates, fats, and proteins",
|
42 |
+
"location": "Mitochondrial matrix",
|
43 |
+
"steps": 8,
|
44 |
+
"importance": "Central metabolic pathway for energy production"
|
45 |
+
},
|
46 |
+
"drug_metabolism": {
|
47 |
+
"definition": "The biochemical modification of drugs by living organisms",
|
48 |
+
"phases": [
|
49 |
+
"Phase I (oxidation, reduction, hydrolysis)",
|
50 |
+
"Phase II (conjugation reactions)"
|
51 |
+
],
|
52 |
+
"location": "Primarily liver, also kidneys, lungs, intestines",
|
53 |
+
"enzymes": "Cytochrome P450 family"
|
54 |
+
}
|
55 |
+
}
|
data/quotes.json
ADDED
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
[
|
2 |
+
"विद्या ददाति विनयं - Knowledge gives humility",
|
3 |
+
"योग: कर्मसु कौशलम् - Yoga is skill in action",
|
4 |
+
"श्रेयान्स्वधर्मो विगुण: - Better is one's own dharma though imperfectly performed",
|
5 |
+
"कर्मण्येवाधिकारस्ते - You have the right to perform action",
|
6 |
+
"विद्या धनं सर्व धन प्रधानम् - Knowledge is the supreme wealth",
|
7 |
+
"सत्यमेव जयते - Truth alone triumphs",
|
8 |
+
"तमसो मा ज्योतिर्गमय - Lead me from darkness to light",
|
9 |
+
"अहिंसा परमो धर्म: - Non-violence is the supreme virtue"
|
10 |
+
]
|
knowledge_base/PharmaLite.in Pharmaceutical Biotechnology (Thakur).pdf
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:bf5f6857a39bf2c469c7fbd3d5c0de3ce5db3ba633c2c9dddddd20e8bf15e14e
|
3 |
+
size 9284177
|
requirements.txt
ADDED
Binary file (510 Bytes). View file
|
|
templates/index.html
ADDED
@@ -0,0 +1,876 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<!DOCTYPE html>
|
2 |
+
<html lang="en">
|
3 |
+
<head>
|
4 |
+
<meta charset="UTF-8">
|
5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
6 |
+
<title>MyPharma AI - Your Study Companion</title>
|
7 |
+
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
|
8 |
+
<style>
|
9 |
+
/* Indian Traditional Theme CSS */
|
10 |
+
:root {
|
11 |
+
--saffron: #FF9933;
|
12 |
+
--maroon: #800000;
|
13 |
+
--gold: #FFD700;
|
14 |
+
--peacock-blue: #005F9E;
|
15 |
+
--cream: #FFF8E7;
|
16 |
+
--dark-gold: #B8860B;
|
17 |
+
--light-saffron: #FFE4B5;
|
18 |
+
}
|
19 |
+
|
20 |
+
* {
|
21 |
+
margin: 0;
|
22 |
+
padding: 0;
|
23 |
+
box-sizing: border-box;
|
24 |
+
}
|
25 |
+
|
26 |
+
body {
|
27 |
+
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
28 |
+
background: linear-gradient(135deg, var(--cream) 0%, var(--light-saffron) 100%);
|
29 |
+
min-height: 100vh;
|
30 |
+
position: relative;
|
31 |
+
overflow-x: hidden;
|
32 |
+
}
|
33 |
+
|
34 |
+
/* Indian pattern background */
|
35 |
+
body::before {
|
36 |
+
content: "";
|
37 |
+
position: fixed;
|
38 |
+
top: 0;
|
39 |
+
left: 0;
|
40 |
+
width: 100%;
|
41 |
+
height: 100%;
|
42 |
+
opacity: 0.05;
|
43 |
+
background-image:
|
44 |
+
radial-gradient(circle at 25% 25%, var(--saffron) 2px, transparent 2px),
|
45 |
+
radial-gradient(circle at 75% 75%, var(--maroon) 1px, transparent 1px);
|
46 |
+
background-size: 50px 50px;
|
47 |
+
z-index: -1;
|
48 |
+
}
|
49 |
+
|
50 |
+
.container {
|
51 |
+
max-width: 1200px;
|
52 |
+
margin: 0 auto;
|
53 |
+
padding: 20px;
|
54 |
+
min-height: 100vh;
|
55 |
+
display: flex;
|
56 |
+
flex-direction: column;
|
57 |
+
}
|
58 |
+
|
59 |
+
/* Header */
|
60 |
+
.header {
|
61 |
+
background: linear-gradient(135deg, var(--maroon) 0%, var(--peacock-blue) 100%);
|
62 |
+
color: white;
|
63 |
+
padding: 20px;
|
64 |
+
border-radius: 15px;
|
65 |
+
margin-bottom: 20px;
|
66 |
+
box-shadow: 0 8px 25px rgba(0,0,0,0.15);
|
67 |
+
position: relative;
|
68 |
+
overflow: hidden;
|
69 |
+
}
|
70 |
+
|
71 |
+
.header::before {
|
72 |
+
content: "🕉️";
|
73 |
+
position: absolute;
|
74 |
+
top: 10px;
|
75 |
+
right: 20px;
|
76 |
+
font-size: 30px;
|
77 |
+
opacity: 0.3;
|
78 |
+
}
|
79 |
+
|
80 |
+
.header h1 {
|
81 |
+
font-size: 2.5em;
|
82 |
+
margin-bottom: 10px;
|
83 |
+
text-shadow: 2px 2px 4px rgba(0,0,0,0.3);
|
84 |
+
}
|
85 |
+
|
86 |
+
.header .subtitle {
|
87 |
+
font-size: 1.2em;
|
88 |
+
opacity: 0.9;
|
89 |
+
font-weight: 300;
|
90 |
+
}
|
91 |
+
|
92 |
+
.quote-container {
|
93 |
+
background: var(--gold);
|
94 |
+
color: var(--maroon);
|
95 |
+
padding: 15px;
|
96 |
+
border-radius: 10px;
|
97 |
+
margin-bottom: 20px;
|
98 |
+
text-align: center;
|
99 |
+
font-style: italic;
|
100 |
+
font-weight: 500;
|
101 |
+
box-shadow: 0 4px 15px rgba(255,215,0,0.3);
|
102 |
+
}
|
103 |
+
|
104 |
+
/* Chat Container */
|
105 |
+
.chat-container {
|
106 |
+
flex: 1;
|
107 |
+
background: white;
|
108 |
+
border-radius: 15px;
|
109 |
+
box-shadow: 0 8px 25px rgba(0,0,0,0.1);
|
110 |
+
display: flex;
|
111 |
+
flex-direction: column;
|
112 |
+
overflow: hidden;
|
113 |
+
border: 3px solid var(--gold);
|
114 |
+
|
115 |
+
}
|
116 |
+
|
117 |
+
.chat-header {
|
118 |
+
background: linear-gradient(135deg, var(--saffron) 0%, var(--gold) 100%);
|
119 |
+
padding: 15px 20px;
|
120 |
+
color: var(--maroon);
|
121 |
+
font-weight: bold;
|
122 |
+
font-size: 1.1em;
|
123 |
+
}
|
124 |
+
|
125 |
+
.chat-messages {
|
126 |
+
flex: 1;
|
127 |
+
padding: 20px;
|
128 |
+
overflow-y: auto;
|
129 |
+
max-height: 500px;
|
130 |
+
min-height: 300px;
|
131 |
+
}
|
132 |
+
|
133 |
+
.message {
|
134 |
+
margin-bottom: 20px;
|
135 |
+
animation: fadeIn 0.3s ease-in;
|
136 |
+
}
|
137 |
+
|
138 |
+
@keyframes fadeIn {
|
139 |
+
from { opacity: 0; transform: translateY(10px); }
|
140 |
+
to { opacity: 1; transform: translateY(0); }
|
141 |
+
}
|
142 |
+
|
143 |
+
.message.user {
|
144 |
+
text-align: right;
|
145 |
+
}
|
146 |
+
|
147 |
+
.message.bot {
|
148 |
+
text-align: left;
|
149 |
+
}
|
150 |
+
|
151 |
+
.message-bubble {
|
152 |
+
display: inline-block;
|
153 |
+
max-width: 80%;
|
154 |
+
padding: 15px 20px;
|
155 |
+
border-radius: 20px;
|
156 |
+
position: relative;
|
157 |
+
word-wrap: break-word;
|
158 |
+
}
|
159 |
+
|
160 |
+
.message.user .message-bubble {
|
161 |
+
background: linear-gradient(135deg, var(--peacock-blue) 0%, var(--maroon) 100%);
|
162 |
+
color: white;
|
163 |
+
border-bottom-right-radius: 5px;
|
164 |
+
}
|
165 |
+
|
166 |
+
.message.bot .message-bubble {
|
167 |
+
background: linear-gradient(135deg, var(--light-saffron) 0%, var(--cream) 100%);
|
168 |
+
color: var(--maroon);
|
169 |
+
border: 2px solid var(--saffron);
|
170 |
+
border-bottom-left-radius: 5px;
|
171 |
+
}
|
172 |
+
|
173 |
+
.agent-badge {
|
174 |
+
display: inline-block;
|
175 |
+
background: var(--gold);
|
176 |
+
color: var(--maroon);
|
177 |
+
padding: 3px 8px;
|
178 |
+
border-radius: 12px;
|
179 |
+
font-size: 0.8em;
|
180 |
+
font-weight: bold;
|
181 |
+
margin-bottom: 5px;
|
182 |
+
}
|
183 |
+
|
184 |
+
/* Input Area */
|
185 |
+
.input-area {
|
186 |
+
padding: 20px;
|
187 |
+
background: var(--cream);
|
188 |
+
border-top: 3px solid var(--gold);
|
189 |
+
}
|
190 |
+
|
191 |
+
.input-container {
|
192 |
+
display: flex;
|
193 |
+
gap: 10px;
|
194 |
+
align-items: center;
|
195 |
+
}
|
196 |
+
|
197 |
+
#messageInput {
|
198 |
+
flex: 1;
|
199 |
+
padding: 15px 20px;
|
200 |
+
border: 2px solid var(--saffron);
|
201 |
+
border-radius: 25px;
|
202 |
+
font-size: 16px;
|
203 |
+
outline: none;
|
204 |
+
background: white;
|
205 |
+
transition: all 0.3s ease;
|
206 |
+
}
|
207 |
+
|
208 |
+
#messageInput:focus {
|
209 |
+
border-color: var(--peacock-blue);
|
210 |
+
box-shadow: 0 0 15px rgba(0,95,158,0.2);
|
211 |
+
}
|
212 |
+
|
213 |
+
#sendBtn {
|
214 |
+
background: linear-gradient(135deg, var(--saffron) 0%, var(--gold) 100%);
|
215 |
+
color: var(--maroon);
|
216 |
+
border: none;
|
217 |
+
padding: 15px 25px;
|
218 |
+
border-radius: 25px;
|
219 |
+
cursor: pointer;
|
220 |
+
font-weight: bold;
|
221 |
+
transition: all 0.3s ease;
|
222 |
+
font-size: 16px;
|
223 |
+
}
|
224 |
+
|
225 |
+
#sendBtn:hover {
|
226 |
+
transform: translateY(-2px);
|
227 |
+
box-shadow: 0 5px 15px rgba(255,153,51,0.4);
|
228 |
+
}
|
229 |
+
|
230 |
+
#sendBtn:disabled {
|
231 |
+
opacity: 0.6;
|
232 |
+
cursor: not-allowed;
|
233 |
+
}
|
234 |
+
|
235 |
+
/* --- CSS FOR THE LOADING INDICATOR --- */
|
236 |
+
.loading {
|
237 |
+
display: none; /* Hidden by default */
|
238 |
+
text-align: center;
|
239 |
+
margin-top: 15px;
|
240 |
+
color: var(--maroon);
|
241 |
+
font-weight: bold;
|
242 |
+
}
|
243 |
+
.loading.show {
|
244 |
+
display: block; /* Visible when .show class is added */
|
245 |
+
}
|
246 |
+
|
247 |
+
/* Quick Actions */
|
248 |
+
.quick-actions {
|
249 |
+
display: flex;
|
250 |
+
gap: 10px;
|
251 |
+
margin-bottom: 15px;
|
252 |
+
flex-wrap: wrap;
|
253 |
+
}
|
254 |
+
|
255 |
+
.quick-btn {
|
256 |
+
background: white;
|
257 |
+
color: var(--peacock-blue);
|
258 |
+
border: 2px solid var(--peacock-blue);
|
259 |
+
padding: 8px 15px;
|
260 |
+
border-radius: 20px;
|
261 |
+
cursor: pointer;
|
262 |
+
font-size: 14px;
|
263 |
+
transition: all 0.3s ease;
|
264 |
+
}
|
265 |
+
|
266 |
+
.quick-btn:hover {
|
267 |
+
background: var(--peacock-blue);
|
268 |
+
color: white;
|
269 |
+
}
|
270 |
+
|
271 |
+
/* Responsive Design */
|
272 |
+
@media (max-width: 768px) {
|
273 |
+
.container {
|
274 |
+
padding: 10px;
|
275 |
+
}
|
276 |
+
|
277 |
+
.header h1 {
|
278 |
+
font-size: 2em;
|
279 |
+
}
|
280 |
+
|
281 |
+
.message-bubble {
|
282 |
+
max-width: 95%;
|
283 |
+
}
|
284 |
+
|
285 |
+
.input-container {
|
286 |
+
flex-direction: column;
|
287 |
+
}
|
288 |
+
|
289 |
+
#messageInput {
|
290 |
+
width: 100%;
|
291 |
+
margin-bottom: 10px;
|
292 |
+
}
|
293 |
+
|
294 |
+
.quick-actions {
|
295 |
+
justify-content: center;
|
296 |
+
}
|
297 |
+
}
|
298 |
+
|
299 |
+
/* Error Messages */
|
300 |
+
.error-message {
|
301 |
+
background: #ffebee;
|
302 |
+
color: #c62828;
|
303 |
+
border: 1px solid #ef5350;
|
304 |
+
padding: 10px;
|
305 |
+
border-radius: 5px;
|
306 |
+
margin: 10px 0;
|
307 |
+
}
|
308 |
+
|
309 |
+
/* Success Messages */
|
310 |
+
.success-message {
|
311 |
+
background: #e8f5e8;
|
312 |
+
color: #2e7d32;
|
313 |
+
border: 1px solid #4caf50;
|
314 |
+
padding: 10px;
|
315 |
+
border-radius: 5px;
|
316 |
+
margin: 10px 0;
|
317 |
+
}
|
318 |
+
</style>
|
319 |
+
</head>
|
320 |
+
<body>
|
321 |
+
<div class="container">
|
322 |
+
|
323 |
+
<div class="header">
|
324 |
+
<h1>🇮🇳 MyPharma AI</h1>
|
325 |
+
<div class="subtitle">{{ greeting or "नमस्ते! Your Intelligent Pharmacy Study Companion" }}</div>
|
326 |
+
</div>
|
327 |
+
|
328 |
+
|
329 |
+
<div class="quote-container" id="quoteContainer">
|
330 |
+
📿 {{ daily_quote or "विद्या धनं सर्व धन प्रधानम् - Knowledge is the supreme wealth" }}
|
331 |
+
</div>
|
332 |
+
|
333 |
+
|
334 |
+
<div class="chat-container">
|
335 |
+
<div class="chat-header">
|
336 |
+
💬 Chat with Your AI Study Buddy
|
337 |
+
</div>
|
338 |
+
|
339 |
+
<div class="chat-messages" id="chatMessages">
|
340 |
+
<div class="message bot">
|
341 |
+
<div class="message-bubble">
|
342 |
+
<div class="agent-badge">🤖 Academic Agent</div>
|
343 |
+
🙏 <strong>Namaste!</strong> I'm your AI study companion for pharmacy subjects!<br><br>
|
344 |
+
|
345 |
+
I can help you with:<br>
|
346 |
+
📚 <strong>Academic Questions</strong> - Pharmacology, chemistry, biology concepts<br>
|
347 |
+
💊 <strong>Drug Information</strong> - Mechanisms, side effects, interactions<br>
|
348 |
+
❓ <strong>Quiz Generation</strong> - Practice questions and tests<br>
|
349 |
+
🧠 <strong>Memory Aids</strong> - Mnemonics and memory tricks<br>
|
350 |
+
🗣️ <strong>Viva Practice</strong> - Mock interview sessions<br><br>
|
351 |
+
|
352 |
+
<em>आपका अध्ययन साथी (Your Study Companion)</em> ✨
|
353 |
+
</div>
|
354 |
+
</div>
|
355 |
+
</div>
|
356 |
+
<div class="input-area">
|
357 |
+
<div class="quick-actions">
|
358 |
+
<button class="quick-btn" onclick="populateInput('Explain ')">📊 Explain Topic</button>
|
359 |
+
<button class="quick-btn" onclick="populateInput('What are the side effects of ')">⚗️ Drug Info</button>
|
360 |
+
<button class="quick-btn" onclick="populateInput('Make a quiz on ')">❓ Create Quiz</button>
|
361 |
+
<button class="quick-btn" onclick="populateInput('Mnemonic for ')">🧠 Mnemonics</button>
|
362 |
+
</div>
|
363 |
+
|
364 |
+
|
365 |
+
|
366 |
+
<div class="input-container">
|
367 |
+
<input type="text"
|
368 |
+
id="messageInput"
|
369 |
+
placeholder="Ask me anything about pharmacy... (e.g., 'Explain the Krebs cycle')"
|
370 |
+
onkeypress="handleKeyPress(event)">
|
371 |
+
<button id="sendBtn" onclick="sendMessage()">Send 📨</button>
|
372 |
+
</div>
|
373 |
+
|
374 |
+
<div class="loading" id="loading">
|
375 |
+
🔄 Processing your query...
|
376 |
+
</div>
|
377 |
+
|
378 |
+
|
379 |
+
</div>
|
380 |
+
</div>
|
381 |
+
</div>
|
382 |
+
<script>
|
383 |
+
let isProcessing = false;
|
384 |
+
|
385 |
+
async function sendMessage() {
|
386 |
+
const input = document.getElementById('messageInput');
|
387 |
+
const message = input.value.trim();
|
388 |
+
if (!message || isProcessing) return;
|
389 |
+
|
390 |
+
addMessage(message, 'user');
|
391 |
+
input.value = '';
|
392 |
+
showLoading(true);
|
393 |
+
|
394 |
+
try {
|
395 |
+
const response = await fetch('/chat', {
|
396 |
+
method: 'POST',
|
397 |
+
headers: { 'Content-Type': 'application/json' },
|
398 |
+
body: JSON.stringify({ query: message })
|
399 |
+
});
|
400 |
+
const data = await response.json();
|
401 |
+
showLoading(false); // Remove loading indicator *before* adding new message
|
402 |
+
if (data.success) {
|
403 |
+
addMessage(data.message, 'bot', data.agent_used);
|
404 |
+
} else {
|
405 |
+
addMessage(`❌ Error: ${data.error || 'Something went wrong'}`, 'bot', 'error');
|
406 |
+
}
|
407 |
+
} catch (error) {
|
408 |
+
showLoading(false);
|
409 |
+
addMessage(`❌ Connection error: ${error.message}`, 'bot', 'error');
|
410 |
+
}
|
411 |
+
}
|
412 |
+
|
413 |
+
function addMessage(text, sender, agentType = '') {
|
414 |
+
const messagesContainer = document.getElementById('chatMessages');
|
415 |
+
const messageDiv = document.createElement('div');
|
416 |
+
messageDiv.className = `message ${sender}`;
|
417 |
+
|
418 |
+
// This dictionary now includes all agent types
|
419 |
+
const agentIcons = {
|
420 |
+
'academic': '📚 Academic Agent',
|
421 |
+
'drug_info': '💊 Drug Info Agent',
|
422 |
+
'quiz_generation': '❓ Quiz Master',
|
423 |
+
'mnemonic_creation': '🧠 Memory Master',
|
424 |
+
'viva_practice': '🗣️ Viva Coach',
|
425 |
+
'error': '⚠️ System'
|
426 |
+
};
|
427 |
+
|
428 |
+
const agentBadge = sender === 'bot' ? `<div class="agent-badge">${agentIcons[agentType] || '🤖 AI Assistant'}</div>` : '';
|
429 |
+
const formattedText = marked.parse(text || 'Sorry, I received an empty response.');
|
430 |
+
messageDiv.innerHTML = `<div class="message-bubble">${agentBadge}${formattedText}</div>`;
|
431 |
+
messagesContainer.appendChild(messageDiv);
|
432 |
+
messagesContainer.scrollTop = messagesContainer.scrollHeight;
|
433 |
+
}
|
434 |
+
|
435 |
+
// This is the correct "typing indicator" loading function
|
436 |
+
function showLoading(show) {
|
437 |
+
isProcessing = show;
|
438 |
+
document.getElementById('sendBtn').disabled = show;
|
439 |
+
const loadingElement = document.getElementById('loading');
|
440 |
+
|
441 |
+
if (show) {
|
442 |
+
loadingElement.classList.add('show');
|
443 |
+
} else {
|
444 |
+
loadingElement.classList.remove('show');
|
445 |
+
}
|
446 |
+
}
|
447 |
+
|
448 |
+
|
449 |
+
function handleKeyPress(event) {
|
450 |
+
if (event.key === 'Enter' && !event.shiftKey) {
|
451 |
+
event.preventDefault();
|
452 |
+
sendMessage();
|
453 |
+
}
|
454 |
+
}
|
455 |
+
|
456 |
+
function populateInput(templateText) {
|
457 |
+
const input = document.getElementById('messageInput');
|
458 |
+
input.value = templateText;
|
459 |
+
input.focus();
|
460 |
+
}
|
461 |
+
|
462 |
+
document.addEventListener('DOMContentLoaded', () => {
|
463 |
+
document.getElementById('messageInput').focus();
|
464 |
+
});
|
465 |
+
</script>
|
466 |
+
|
467 |
+
</body>
|
468 |
+
</html>
|
469 |
+
|
470 |
+
|
471 |
+
<!--<!DOCTYPE html>
|
472 |
+
<html lang="en">
|
473 |
+
<head>
|
474 |
+
<meta charset="UTF-8">
|
475 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
476 |
+
<title>MyPharma AI - Your Study Companion</title>
|
477 |
+
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
|
478 |
+
<style>
|
479 |
+
/* Indian Traditional Theme CSS */
|
480 |
+
:root {
|
481 |
+
--saffron: #FF9933; --maroon: #800000; --gold: #FFD700;
|
482 |
+
--peacock-blue: #005F9E; --cream: #FFF8E7; --light-saffron: #FFE4B5;
|
483 |
+
}
|
484 |
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
485 |
+
body {
|
486 |
+
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
487 |
+
background: linear-gradient(135deg, var(--cream) 0%, var(--light-saffron) 100%);
|
488 |
+
min-height: 100vh;
|
489 |
+
display: flex; align-items: center; justify-content: center; padding: 20px;
|
490 |
+
}
|
491 |
+
.container {
|
492 |
+
max-width: 800px; margin: 0 auto; width: 100%;
|
493 |
+
height: 90vh; max-height: 850px; display: flex; flex-direction: column;
|
494 |
+
background: white; border-radius: 15px; box-shadow: 0 8px 25px rgba(0,0,0,0.1);
|
495 |
+
border: 3px solid var(--gold); overflow: hidden;
|
496 |
+
}
|
497 |
+
.header {
|
498 |
+
background: linear-gradient(135deg, var(--maroon) 0%, var(--peacock-blue) 100%);
|
499 |
+
color: white; padding: 20px; text-align: center;
|
500 |
+
}
|
501 |
+
.header h1 { font-size: 2em; margin-bottom: 5px; }
|
502 |
+
.header .subtitle { font-size: 1.1em; opacity: 0.9; }
|
503 |
+
.quote-container {
|
504 |
+
background: var(--gold); color: var(--maroon); padding: 15px;
|
505 |
+
border-radius: 10px; margin: 20px; text-align: center; font-style: italic;
|
506 |
+
}
|
507 |
+
.chat-container { flex: 1; display: flex; flex-direction: column; overflow: hidden; }
|
508 |
+
.chat-messages { flex: 1; padding: 20px; overflow-y: auto; }
|
509 |
+
.message { margin-bottom: 20px; display: flex; max-width: 85%; animation: fadeIn 0.3s ease-in; }
|
510 |
+
@keyframes fadeIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } }
|
511 |
+
.message.user { margin-left: auto; flex-direction: row-reverse; }
|
512 |
+
.message-bubble {
|
513 |
+
padding: 12px 18px; border-radius: 20px; word-wrap: break-word; line-height: 1.6;
|
514 |
+
}
|
515 |
+
.message.user .message-bubble {
|
516 |
+
background: linear-gradient(135deg, var(--peacock-blue) 0%, var(--maroon) 100%);
|
517 |
+
color: white; border-bottom-right-radius: 5px;
|
518 |
+
}
|
519 |
+
.message.bot .message-bubble {
|
520 |
+
background: linear-gradient(135deg, var(--light-saffron) 0%, var(--cream) 100%);
|
521 |
+
color: var(--maroon); border: 2px solid var(--saffron); border-bottom-left-radius: 5px;
|
522 |
+
}
|
523 |
+
.agent-badge {
|
524 |
+
display: inline-block; background: var(--gold); color: var(--maroon);
|
525 |
+
padding: 3px 8px; border-radius: 12px; font-size: 0.8em; font-weight: bold; margin-bottom: 8px;
|
526 |
+
}
|
527 |
+
.input-area { padding: 20px; background: var(--cream); border-top: 3px solid var(--gold); }
|
528 |
+
.quick-actions { display: flex; gap: 10px; margin-bottom: 15px; flex-wrap: wrap; }
|
529 |
+
.quick-btn {
|
530 |
+
background: white; color: var(--peacock-blue); border: 2px solid var(--peacock-blue);
|
531 |
+
padding: 8px 15px; border-radius: 20px; cursor: pointer; font-size: 14px; transition: all 0.3s ease;
|
532 |
+
}
|
533 |
+
.quick-btn:hover { background: var(--peacock-blue); color: white; }
|
534 |
+
.input-container { display: flex; gap: 10px; align-items: center; }
|
535 |
+
#messageInput {
|
536 |
+
flex: 1; padding: 15px 20px; border: 2px solid var(--saffron);
|
537 |
+
border-radius: 25px; font-size: 16px; outline: none;
|
538 |
+
}
|
539 |
+
#sendBtn {
|
540 |
+
background: linear-gradient(135deg, var(--saffron) 0%, var(--gold) 100%);
|
541 |
+
color: var(--maroon); border: none; padding: 15px 25px; border-radius: 25px;
|
542 |
+
cursor: pointer; font-weight: bold; font-size: 16px; transition: all 0.3s ease;
|
543 |
+
}
|
544 |
+
#sendBtn:hover { transform: translateY(-2px); box-shadow: 0 5px 15px rgba(255,153,51,0.4); }
|
545 |
+
.typing-indicator span { display: inline-block; width: 8px; height: 8px; border-radius: 50%; background-color: #aaa; margin: 0 2px; animation: bounce 1s infinite; }
|
546 |
+
.typing-indicator span:nth-child(2) { animation-delay: 0.1s; }
|
547 |
+
.typing-indicator span:nth-child(3) { animation-delay: 0.2s; }
|
548 |
+
@keyframes bounce { 0%, 60%, 100% { transform: translateY(0); } 30% { transform: translateY(-6px); } }
|
549 |
+
</style>
|
550 |
+
</head>
|
551 |
+
<body>
|
552 |
+
<div class="container">
|
553 |
+
<div class="header">
|
554 |
+
<h1>🇮🇳 MyPharma AI</h1>
|
555 |
+
<div class="subtitle">{{ greeting or "नमस्ते! Your Intelligent Pharmacy Study Companion" }}</div>
|
556 |
+
</div>
|
557 |
+
|
558 |
+
<div class="quote-container" id="quoteContainer">
|
559 |
+
📿 {{ daily_quote or "विद्या धनं स���्व धन प्रधानम् - Knowledge is the supreme wealth" }}
|
560 |
+
</div>
|
561 |
+
|
562 |
+
<div class="chat-container">
|
563 |
+
<div class="chat-messages" id="chatMessages">
|
564 |
+
<div class="message bot">
|
565 |
+
<div class="message-bubble">
|
566 |
+
<div class="agent-badge">🤖 AI Study Buddy</div>
|
567 |
+
🙏 <strong>Namaste!</strong> I have studied the documents in the knowledge library. Ask me anything about your subjects, and I will find the answer for you.
|
568 |
+
</div>
|
569 |
+
</div>
|
570 |
+
</div>
|
571 |
+
|
572 |
+
<div class="input-area">
|
573 |
+
<div class="quick-actions">
|
574 |
+
<button class="quick-btn" onclick="populateInput('Explain ')">📊 Explain Topic</button>
|
575 |
+
<button class="quick-btn" onclick="populateInput('What are the side effects of ')">⚗️ Drug Info</button>
|
576 |
+
<button class="quick-btn" onclick="populateInput('Make a quiz on ')">❓ Create Quiz</button>
|
577 |
+
</div>
|
578 |
+
|
579 |
+
<div class="input-container">
|
580 |
+
<input type="text"
|
581 |
+
id="messageInput"
|
582 |
+
placeholder="Ask about your documents..."
|
583 |
+
onkeypress="handleKeyPress(event)">
|
584 |
+
<button id="sendBtn" onclick="sendMessage()">Send 📨</button>
|
585 |
+
</div>
|
586 |
+
</div>
|
587 |
+
</div>
|
588 |
+
</div>
|
589 |
+
|
590 |
+
<script>
|
591 |
+
let isProcessing = false;
|
592 |
+
|
593 |
+
async function sendMessage() {
|
594 |
+
const input = document.getElementById('messageInput');
|
595 |
+
const message = input.value.trim();
|
596 |
+
if (!message || isProcessing) return;
|
597 |
+
|
598 |
+
addMessage(message, 'user');
|
599 |
+
input.value = '';
|
600 |
+
showLoading(true);
|
601 |
+
|
602 |
+
try {
|
603 |
+
const response = await fetch('/chat', {
|
604 |
+
method: 'POST',
|
605 |
+
headers: { 'Content-Type': 'application/json' },
|
606 |
+
body: JSON.stringify({ query: message })
|
607 |
+
});
|
608 |
+
const data = await response.json();
|
609 |
+
showLoading(false);
|
610 |
+
if (data.success) {
|
611 |
+
addMessage(data.message, 'bot', data.agent_used);
|
612 |
+
} else {
|
613 |
+
addMessage(`❌ Error: ${data.error || 'Something went wrong'}`, 'bot', 'error');
|
614 |
+
}
|
615 |
+
} catch (error) {
|
616 |
+
showLoading(false);
|
617 |
+
addMessage(`❌ Connection error: ${error.message}`, 'bot', 'error');
|
618 |
+
}
|
619 |
+
}
|
620 |
+
|
621 |
+
function addMessage(text, sender, agentType = '') {
|
622 |
+
const messagesContainer = document.getElementById('chatMessages');
|
623 |
+
const messageDiv = document.createElement('div');
|
624 |
+
messageDiv.className = `message ${sender}`;
|
625 |
+
const agentIcons = { 'academic': '📚 Academic Agent', 'drug_info': '💊 Drug Info Agent', 'quiz_generation': '❓ Quiz Master', 'mnemonic_creation': '🧠 Memory Master', 'viva_practice': '🗣️ Viva Coach', 'error': '⚠️ System' };
|
626 |
+
const agentBadge = sender === 'bot' ? `<div class="agent-badge">${agentIcons[agentType] || '🤖 AI Assistant'}</div>` : '';
|
627 |
+
const formattedText = marked.parse(text || 'Sorry, I received an empty response.');
|
628 |
+
messageDiv.innerHTML = `<div class="message-bubble">${agentBadge}${formattedText}</div>`;
|
629 |
+
messagesContainer.appendChild(messageDiv);
|
630 |
+
messagesContainer.scrollTop = messagesContainer.scrollHeight;
|
631 |
+
}
|
632 |
+
|
633 |
+
function showLoading(show) {
|
634 |
+
document.getElementById('sendBtn').disabled = show;
|
635 |
+
const existingLoading = document.getElementById('loading-indicator');
|
636 |
+
if (existingLoading) existingLoading.remove();
|
637 |
+
|
638 |
+
if (show) {
|
639 |
+
const messagesContainer = document.getElementById('chatMessages');
|
640 |
+
const loadingDiv = document.createElement('div');
|
641 |
+
loadingDiv.className = 'message bot';
|
642 |
+
loadingDiv.id = 'loading-indicator';
|
643 |
+
loadingDiv.innerHTML = `<div class="message-bubble"><div class="typing-indicator"><span></span><span></span><span></span></div></div>`;
|
644 |
+
messagesContainer.appendChild(loadingDiv);
|
645 |
+
messagesContainer.scrollTop = messagesContainer.scrollHeight;
|
646 |
+
}
|
647 |
+
}
|
648 |
+
|
649 |
+
function handleKeyPress(event) {
|
650 |
+
if (event.key === 'Enter' && !event.shiftKey) {
|
651 |
+
event.preventDefault();
|
652 |
+
sendMessage();
|
653 |
+
}
|
654 |
+
}
|
655 |
+
|
656 |
+
function populateInput(templateText) {
|
657 |
+
const input = document.getElementById('messageInput');
|
658 |
+
input.value = templateText;
|
659 |
+
input.focus();
|
660 |
+
}
|
661 |
+
|
662 |
+
async function getNewQuote() {
|
663 |
+
try {
|
664 |
+
const response = await fetch('/quote');
|
665 |
+
const data = await response.json();
|
666 |
+
document.getElementById('quoteContainer').innerHTML = `📿 ${data.quote}`;
|
667 |
+
} catch (error) {
|
668 |
+
console.error('Failed to fetch new quote:', error);
|
669 |
+
}
|
670 |
+
}
|
671 |
+
|
672 |
+
document.addEventListener('DOMContentLoaded', () => {
|
673 |
+
document.getElementById('messageInput').focus();
|
674 |
+
setInterval(getNewQuote, 5 * 60 * 1000); // Update quote every 5 mins
|
675 |
+
});
|
676 |
+
</script>
|
677 |
+
old
|
678 |
+
<script>
|
679 |
+
// Global variables
|
680 |
+
let isProcessing = false;
|
681 |
+
|
682 |
+
// In templates/index.html, inside the <script> tag
|
683 |
+
|
684 |
+
// --- File Management Functions ---
|
685 |
+
|
686 |
+
async function uploadFile() {
|
687 |
+
const fileInput = document.getElementById('fileInput');
|
688 |
+
const file = fileInput.files[0];
|
689 |
+
if (!file) {
|
690 |
+
addMessage('Please select a file to upload first.', 'bot', 'error');
|
691 |
+
return;
|
692 |
+
}
|
693 |
+
|
694 |
+
const formData = new FormData();
|
695 |
+
formData.append('file', file);
|
696 |
+
|
697 |
+
showLoading(true);
|
698 |
+
isProcessing = true;
|
699 |
+
|
700 |
+
try {
|
701 |
+
const response = await fetch('/upload', {
|
702 |
+
method: 'POST',
|
703 |
+
body: formData,
|
704 |
+
});
|
705 |
+
const data = await response.json();
|
706 |
+
if (data.success) {
|
707 |
+
addMessage(`✅ ${data.message}`, 'bot', 'academic');
|
708 |
+
updateFileList(data.files);
|
709 |
+
} else {
|
710 |
+
addMessage(`❌ Upload Error: ${data.error}`, 'bot', 'error');
|
711 |
+
}
|
712 |
+
} catch (error) {
|
713 |
+
addMessage(`❌ Connection Error: ${error.message}`, 'bot', 'error');
|
714 |
+
} finally {
|
715 |
+
showLoading(false);
|
716 |
+
isProcessing = false;
|
717 |
+
fileInput.value = ''; // Clear the file input
|
718 |
+
}
|
719 |
+
}
|
720 |
+
|
721 |
+
function updateFileList(files = []) {
|
722 |
+
const fileListDiv = document.getElementById('fileList');
|
723 |
+
if (files.length > 0) {
|
724 |
+
let fileLinks = files.map(f => `<span>${f.original_name}</span>`).join(', ');
|
725 |
+
fileListDiv.innerHTML = `<div style="font-size: 0.9em; margin-top: 10px;"><strong>Active Files:</strong> ${fileLinks} <button onclick="clearFiles()" style="margin-left: 10px; background: #800000; color: white; border: none; border-radius: 5px; cursor: pointer; padding: 2px 8px;">Clear All</button></div>`;
|
726 |
+
} else {
|
727 |
+
fileListDiv.innerHTML = '';
|
728 |
+
}
|
729 |
+
}
|
730 |
+
|
731 |
+
async function clearFiles() {
|
732 |
+
showLoading(true);
|
733 |
+
isProcessing = true;
|
734 |
+
try {
|
735 |
+
const response = await fetch('/clear_files', { method: 'POST' });
|
736 |
+
const data = await response.json();
|
737 |
+
if (data.success) {
|
738 |
+
addMessage('🧹 All uploaded files and sessions have been cleared.', 'bot', 'academic');
|
739 |
+
updateFileList([]);
|
740 |
+
} else {
|
741 |
+
addMessage(`❌ Error: ${data.error}`, 'bot', 'error');
|
742 |
+
}
|
743 |
+
} catch (error) {
|
744 |
+
addMessage(`❌ Connection Error: ${error.message}`, 'bot', 'error');
|
745 |
+
} finally {
|
746 |
+
showLoading(false);
|
747 |
+
isProcessing = false;
|
748 |
+
}
|
749 |
+
}
|
750 |
+
|
751 |
+
// Update the file list on page load
|
752 |
+
document.addEventListener('DOMContentLoaded', function() {
|
753 |
+
fetch('/files').then(res => res.json()).then(data => {
|
754 |
+
updateFileList(data.files);
|
755 |
+
});
|
756 |
+
});
|
757 |
+
// Send message function
|
758 |
+
async function sendMessage() {
|
759 |
+
const input = document.getElementById('messageInput');
|
760 |
+
const message = input.value.trim();
|
761 |
+
|
762 |
+
if (!message || isProcessing) return;
|
763 |
+
|
764 |
+
// Add user message to chat
|
765 |
+
addMessage(message, 'user');
|
766 |
+
input.value = '';
|
767 |
+
|
768 |
+
// Show loading
|
769 |
+
showLoading(true);
|
770 |
+
isProcessing = true;
|
771 |
+
|
772 |
+
try {
|
773 |
+
const response = await fetch('/chat', {
|
774 |
+
method: 'POST',
|
775 |
+
headers: {
|
776 |
+
'Content-Type': 'application/json',
|
777 |
+
},
|
778 |
+
body: JSON.stringify({ query: message })
|
779 |
+
});
|
780 |
+
|
781 |
+
const data = await response.json();
|
782 |
+
|
783 |
+
if (data.success) {
|
784 |
+
addMessage(data.message, 'bot', data.agent_used);
|
785 |
+
} else {
|
786 |
+
addMessage(`❌ Error: ${data.error || 'Something went wrong'}`, 'bot', 'error');
|
787 |
+
}
|
788 |
+
|
789 |
+
} catch (error) {
|
790 |
+
addMessage(`❌ Connection error: ${error.message}`, 'bot', 'error');
|
791 |
+
} finally {
|
792 |
+
showLoading(false);
|
793 |
+
isProcessing = false;
|
794 |
+
}
|
795 |
+
}
|
796 |
+
|
797 |
+
// Add message to chat
|
798 |
+
function addMessage(text, sender, agentType = '') {
|
799 |
+
const messagesContainer = document.getElementById('chatMessages');
|
800 |
+
const messageDiv = document.createElement('div');
|
801 |
+
messageDiv.className = `message ${sender}`;
|
802 |
+
|
803 |
+
let agentBadge = '';
|
804 |
+
if (sender === 'bot' && agentType) {
|
805 |
+
const agentIcons = {
|
806 |
+
'academic': '📚 Academic Agent',
|
807 |
+
'drug_info': '💊 Drug Info Agent',
|
808 |
+
'quiz_generation': '❓ Quiz Master',
|
809 |
+
'mnemonic_creation': '🧠 Memory Master',
|
810 |
+
'viva_practice': '🗣️ Viva Coach',
|
811 |
+
'error': '⚠️ System'
|
812 |
+
};
|
813 |
+
agentBadge = `<div class="agent-badge">${agentIcons[agentType] || '🤖 AI Assistant'}</div>`;
|
814 |
+
}
|
815 |
+
|
816 |
+
// THE FIX: Use marked.parse() to convert markdown to HTML
|
817 |
+
// instead of a simple replace.
|
818 |
+
const formattedText = marked.parse(text || 'Sorry, I received an empty response.');
|
819 |
+
|
820 |
+
messageDiv.innerHTML = `
|
821 |
+
<div class="message-bubble">
|
822 |
+
${agentBadge}
|
823 |
+
${formattedText}
|
824 |
+
</div>
|
825 |
+
`;
|
826 |
+
|
827 |
+
messagesContainer.appendChild(messageDiv);
|
828 |
+
messagesContainer.scrollTop = messagesContainer.scrollHeight;
|
829 |
+
}
|
830 |
+
|
831 |
+
// Show/hide loading
|
832 |
+
function showLoading(show) {
|
833 |
+
const loading = document.getElementById('loading');
|
834 |
+
const sendBtn = document.getElementById('sendBtn');
|
835 |
+
|
836 |
+
loading.classList.toggle('show', show);
|
837 |
+
sendBtn.disabled = show;
|
838 |
+
}
|
839 |
+
|
840 |
+
// Handle Enter key press
|
841 |
+
function handleKeyPress(event) {
|
842 |
+
if (event.key === 'Enter' && !event.shiftKey) {
|
843 |
+
event.preventDefault();
|
844 |
+
sendMessage();
|
845 |
+
}
|
846 |
+
}
|
847 |
+
|
848 |
+
// Quick message function
|
849 |
+
function populateInput(templateText) {
|
850 |
+
const input = document.getElementById('messageInput');
|
851 |
+
input.value = templateText;
|
852 |
+
input.focus(); // This handily puts the cursor in the text box for you!
|
853 |
+
}
|
854 |
+
|
855 |
+
// Get new quote function
|
856 |
+
async function getNewQuote() {
|
857 |
+
try {
|
858 |
+
const response = await fetch('/quote');
|
859 |
+
const data = await response.json();
|
860 |
+
document.getElementById('quoteContainer').innerHTML = `📿 ${data.quote}`;
|
861 |
+
} catch (error) {
|
862 |
+
console.error('Failed to fetch new quote:', error);
|
863 |
+
}
|
864 |
+
}
|
865 |
+
|
866 |
+
// Initialize app
|
867 |
+
document.addEventListener('DOMContentLoaded', function() {
|
868 |
+
// Focus on input
|
869 |
+
document.getElementById('messageInput').focus();
|
870 |
+
|
871 |
+
// Get new quote every 5 minutes
|
872 |
+
setInterval(getNewQuote, 5 * 60 * 1000);
|
873 |
+
});
|
874 |
+
</script>
|
875 |
+
</body>
|
876 |
+
</html>-->
|
templates/rakhi.html
ADDED
@@ -0,0 +1,331 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<!DOCTYPE html>
|
2 |
+
<html lang="en">
|
3 |
+
<head>
|
4 |
+
<meta charset="UTF-8">
|
5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
6 |
+
<title>For My Dearest Sister</title>
|
7 |
+
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
|
8 |
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
9 |
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
10 |
+
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;600;700&family=Kalam:wght@700&display=swap" rel="stylesheet">
|
11 |
+
<style>
|
12 |
+
:root {
|
13 |
+
--pink: #FF69B4;
|
14 |
+
--gold: #FFD700;
|
15 |
+
--cream: #FFFDD0;
|
16 |
+
--turquoise: #40E0D0;
|
17 |
+
--dark-pink: #C71585;
|
18 |
+
--text-dark: #442c2e;
|
19 |
+
}
|
20 |
+
* { box-sizing: border-box; margin: 0; padding: 0; }
|
21 |
+
body {
|
22 |
+
font-family: 'Poppins', sans-serif;
|
23 |
+
background-color: var(--cream);
|
24 |
+
background-image: url("data:image/svg+xml,%3Csvg width='60' height='60' viewBox='0 0 60 60' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='none' fill-rule='evenodd'%3E%3Cg fill='%23FFD700' fill-opacity='0.1'%3E%3Cpath d='M36 34v-4h-2v4h-4v2h4v4h2v-4h4v-2h-4zm0-30V0h-2v4h-4v2h4v4h2V6h4V4h-4zM6 34v-4H4v4H0v2h4v4h2v-4h4v-2H6zM6 4V0H4v4H0v2h4v4h2V6h4V4H6z'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E");
|
25 |
+
display: flex;
|
26 |
+
justify-content: center;
|
27 |
+
align-items: center;
|
28 |
+
min-height: 100vh;
|
29 |
+
padding: 20px;
|
30 |
+
}
|
31 |
+
.container {
|
32 |
+
width: 100%;
|
33 |
+
max-width: 800px;
|
34 |
+
background: white;
|
35 |
+
border-radius: 20px;
|
36 |
+
box-shadow: 0 10px 40px rgba(0,0,0,0.1);
|
37 |
+
border: 5px solid var(--gold);
|
38 |
+
display: flex;
|
39 |
+
flex-direction: column;
|
40 |
+
overflow: hidden;
|
41 |
+
}
|
42 |
+
|
43 |
+
/* --- Rakhi Header --- */
|
44 |
+
.rakhi-header {
|
45 |
+
background: linear-gradient(135deg, var(--pink), var(--turquoise));
|
46 |
+
padding: 30px;
|
47 |
+
text-align: center;
|
48 |
+
color: white;
|
49 |
+
position: relative;
|
50 |
+
}
|
51 |
+
.rakhi-header h1 {
|
52 |
+
font-family: 'Kalam', cursive;
|
53 |
+
font-size: 2.8em;
|
54 |
+
text-shadow: 2px 2px 5px rgba(0,0,0,0.2);
|
55 |
+
margin: 0;
|
56 |
+
}
|
57 |
+
.pixel-rakhi {
|
58 |
+
width: 80px;
|
59 |
+
height: 80px;
|
60 |
+
margin: 15px auto;
|
61 |
+
image-rendering: pixelated;
|
62 |
+
animation: pulse 2s infinite ease-in-out;
|
63 |
+
}
|
64 |
+
@keyframes pulse {
|
65 |
+
0%, 100% { transform: scale(1); }
|
66 |
+
50% { transform: scale(1.1); }
|
67 |
+
}
|
68 |
+
.greeting {
|
69 |
+
font-weight: 600;
|
70 |
+
font-size: 1.1em;
|
71 |
+
}
|
72 |
+
|
73 |
+
/* --- Chat Area --- */
|
74 |
+
.chat-messages {
|
75 |
+
flex: 1;
|
76 |
+
padding: 20px;
|
77 |
+
overflow-y: auto;
|
78 |
+
max-height: 40vh;
|
79 |
+
background: #fffaf0;
|
80 |
+
}
|
81 |
+
.message { margin-bottom: 15px; display: flex; max-width: 90%; }
|
82 |
+
.message.user { margin-left: auto; flex-direction: row-reverse; }
|
83 |
+
.message-bubble { padding: 12px 18px; border-radius: 18px; line-height: 1.5; }
|
84 |
+
.message.user .message-bubble {
|
85 |
+
background: var(--dark-pink);
|
86 |
+
color: white;
|
87 |
+
border-bottom-right-radius: 5px;
|
88 |
+
}
|
89 |
+
.message.bot .message-bubble {
|
90 |
+
background: #f1f1f1;
|
91 |
+
color: var(--text-dark);
|
92 |
+
border-bottom-left-radius: 5px;
|
93 |
+
}
|
94 |
+
.agent-badge {
|
95 |
+
display: block;
|
96 |
+
font-weight: bold;
|
97 |
+
font-size: 0.9em;
|
98 |
+
margin-bottom: 8px;
|
99 |
+
color: var(--pink);
|
100 |
+
}
|
101 |
+
/* Typing animation */
|
102 |
+
.typing-indicator span {
|
103 |
+
display: inline-block;
|
104 |
+
width: 8px; height: 8px;
|
105 |
+
border-radius: 50%;
|
106 |
+
background-color: #aaa;
|
107 |
+
margin: 0 2px;
|
108 |
+
animation: bounce 1s infinite;
|
109 |
+
}
|
110 |
+
.typing-indicator span:nth-child(2) { animation-delay: 0.1s; }
|
111 |
+
.typing-indicator span:nth-child(3) { animation-delay: 0.2s; }
|
112 |
+
@keyframes bounce {
|
113 |
+
0%, 60%, 100% { transform: translateY(0); }
|
114 |
+
30% { transform: translateY(-6px); }
|
115 |
+
}
|
116 |
+
|
117 |
+
/* --- Input Area --- */
|
118 |
+
.input-area { padding: 20px; background: #fffaf0; border-top: 2px solid #eee; }
|
119 |
+
#fileUploadSection { margin-bottom: 15px; }
|
120 |
+
.quick-actions { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 15px; }
|
121 |
+
.quick-btn {
|
122 |
+
background: transparent;
|
123 |
+
border: 2px solid var(--turquoise);
|
124 |
+
color: var(--turquoise);
|
125 |
+
font-weight: 600;
|
126 |
+
padding: 8px 15px;
|
127 |
+
border-radius: 20px;
|
128 |
+
cursor: pointer;
|
129 |
+
transition: all 0.2s ease;
|
130 |
+
}
|
131 |
+
.quick-btn:hover { background: var(--turquoise); color: white; }
|
132 |
+
.input-container { display: flex; gap: 10px; }
|
133 |
+
#messageInput {
|
134 |
+
flex: 1;
|
135 |
+
border: 2px solid #ddd;
|
136 |
+
border-radius: 25px;
|
137 |
+
padding: 12px 20px;
|
138 |
+
font-size: 16px;
|
139 |
+
}
|
140 |
+
#messageInput:focus { outline: none; border-color: var(--pink); }
|
141 |
+
#sendBtn {
|
142 |
+
background: var(--pink);
|
143 |
+
color: white;
|
144 |
+
border: none;
|
145 |
+
border-radius: 50%;
|
146 |
+
width: 50px;
|
147 |
+
height: 50px;
|
148 |
+
font-size: 20px;
|
149 |
+
cursor: pointer;
|
150 |
+
transition: all 0.2s ease;
|
151 |
+
}
|
152 |
+
#sendBtn:hover { transform: scale(1.1); }
|
153 |
+
#sendBtn:disabled { background: #ccc; cursor: not-allowed; }
|
154 |
+
</style>
|
155 |
+
</head>
|
156 |
+
<body>
|
157 |
+
<div class="container">
|
158 |
+
<div class="rakhi-header">
|
159 |
+
<h1>For My Dearest Sister</h1>
|
160 |
+
<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAdNSURBVHhe7Vt7sFxVFL/z7t1rN2naJm2bplFaUqC0oAEV8QGiEB8kER8a8AcijCg+iB8kBkTCBwTRB1QhEAmaQAsKaSGlbVraprFpm7bbvffeM+c/3J27u/fevds2bZN8yZ17zpyZc86558yZMzdh/I8T/yL/T4j/H8P7/z1+fT8C4N+Bf4X/Bf4c/rD/cM8eY9zYg4B7DDAfgNUCfKqA5x3w1gKqE1APgN0K+HMBz7UAFwvgVgHcC3Bb+N6uC/eR/5P/S6gA3AOgD4D/BWC7AA4F4C+B3wvgaQF8HICzALxSADcDeArA1wA8J4CLAdwF4JcAPAvgHQA3A3ghgIcBnAPguwB+CeDrAP4J4AsAVwA4DMDfAPwKQF34/lbg/V+B9ys/Pgv+Bvgf4X7/13d11kQeBbgHwL4ADsD/vQBPBHAGgHcDuAvAiQA+DeAdAHcD2Avg+wDuA/BCAWcCuAbAWwA+CeBLAO4C8EUAfwewL4DPAvgrgH8D+BCAU/D5W+D1W+B1T++v/gT/hE/U+Vd3Vz4S/vI1/0G9hXh3zPj99y2gqICjAPQG4D8BfA/AdQDOA/BfAH+o6QPAaQCeBeAjANYAeBaAWwA8A+B+ABcAOCaAbwA8C+ALAP4bQFcAmgI4B8CvAdwBYBuAywD8DcAjAFwFYGeg/q0C/V8S/gD4R0B9/w8FfFh3dRPx2v/S+a9yA3H/G2b8/ntPQLV/Tz8P4E4APgbgfgDfB/BTAEcCeAaAdwDcC+AuANcB+CmAlwDcAeArAN8D8CcA+wA8C+AvAP8PYC+ATwC8CeA3AKcBmArghAAmAsgEsCrE81cBeBeA3wB4F4DnANgWwAsBPA3gFQC7AnhG+N+tYp7xN8r133t2P5X9g/2FmF/1rM/84v66vPgv+L+AfwH8E6/y1r/K+29dJ/mN+L3XF0DqPwLwG4BvAPwNwDsAPAvgPgBPAzgTwEUA1gF4BIDPA3gDgHsBvA/ApQAeB+ADAD4B4FMAngPwpwA+CeBbAN8GcCeAiwC8A8A1AP4dwDUAPgrgMgBHAPgYgFMB3A7gpwCeAnAYgHcDeB+AzwA8CsB5AE4C8E4AvwLwPYC/BvAGgD0BHAGcAXAGwFwAzwK4A8AHAJzPjE9bX+lB4P8I/w7+u3tBQLn/T7p/w/4O4CMA1wE4CsD7AN4J4CkA7gLwCICnAXgYgHMBHAngewCeAeC9AO4A8AmA3wD8HsBFAJ4F4A0A/wFgD4CvAFwCYB+ApQC+DuArAL8C8DMAewA8CcD7AHwHwC8AnAHgXAD/DuACgHcC+DqArwI4AMDPALwDwNsAXAHgYQB+CeBjAJ4L4D4AjwBwBIDLAKwD8DeAFwH4DICzAHwIwDsBnAfgNwDuAnAAgD0AzgLwMIAHAc4DuBfAVwC+DOA5APsBOBXAbQCeC+AfAH4O4HoAngLgnQBeA3AggFMA/AnAuQBeA+A8APsBnAngXADXAXgAgIcAPAtgL4BnAXgOgHMAHAMwH8CZAG4GcGn4/H0B/C/w/+U36n0BvX8F/j3c94H/Uqj+L681XW+4rR0hC84D8CgAvwPwAIDnAHgUgFMAHAngHwD2AzgagDMB/ArgKwCXALgHwHsAnAvgTwCeDuDPA+4BcA+ADwD8FcC+AG4BcCWAawC8CeAjAD4D8B8AfwCwCcBfAngIwAkAzgXgUwA+CeAZADcB+AqAvQDuA/BGAC8AOCaAzwA4C8AxAPcB+BaA7wL4O4AHAfgwgNMAHAngUgCnAPgRgA8C+DyAlQC+C+AvAFcD+DCAHwH4EIDTALwKwGUA/gTgfABvAvgIgL0APgngeQB+C+A8APcD+CqAGwE8A+C5AJ4D4FoAbgbwKwAvAHgEgCcAOBPARwCcDuAuAPsB+CeAZwA4D8ApAE4F8AyA/QD8HcCfAG4C8AwApwH4JIDvAngUgDMB/BHAUwC+AeAZAPsB+CuAjwA4FsCvARwD4CUAXgHgNgDHAjgDwGkAngPgUwDeAHAqgCMA/BuAUwC8BOC5AK4D8CkA9wHYA+AnAD4N4HoAvwLwZwD2AvAggO8CeCqAzwG4GMDDAZwOYBWAfQDcB+ArAI8D8DCA+QC8CeA8AKcAeB+AdQC8BOC5AK4BcCuAsgA+B+ArAO4A8CUAOwG8COBZAMcD8HkA9wB4FoCLALwEwLUALgLwvAA+A+C5ALcB2AvgfgAuAXAqgO8A+CyAPQA+B+AlAC8AcCeAjQC+CeAdAPcC+CSA0wDcAeA0AJcC+DyA/QDOBPBTAO4BcAuALQC+CeBLAO4C8DMAcwG8CeCvAPYA+ByAsgCeA+ApAI8D8DCAawCcDuBJAHcB2AvgegCuA3ApgH0A3AfgSwDuBvA0gDcA2AvgOwB+DuBMAOcCeB6ApwA8C8AxAF4N4AYAbwJwD4CnAZwK4H4AHgbgDwDcDODpAC4D8CcAPAbgYQD+DuA0gA8DuAvAvgB+CuB+AC4G8CeAUwD8COB+AB4AcCaA3wAcDuArAI8D8DCACwE8B+ATAEcBeADAXACfBXAqgA8D2AfgUwA+C+ApAPcBeBHAfADeBODHAH4BcCOAkwA8COA+APcBeAyAfQDOAvAwgIcAPAngKwB+A/AXAK8AcBeAvwH8EICLADwNwJkAngTgWABfBnB5/x2/D/8F+Nfwr/g313+7V7f9J974X6v4vVd/+XwJ1KcA3A/gUgAvAnhP+FcA7gXwAIBHAewH4GEALgJwCIA/AFgD4BoANwJ4CsCtAN5L9/AFAJcAeAaAZwA8BOA/AO4B8DwANwL4MIDLALwYwEUAXgvgEABvAXAcgL0A/gDgUgA/B3AHgHcC+AuAvwB8EsADAPYB+DuAGwA8A+A0gLMAvBHAPADvA/AFgI8AuAbA/QC+COApAPsBvAXgZgDXAPg/APsBOBPA0wC8CeAlAI8D8CgAZwC4BcDPALgKwC0AnAvgCgDfAnAngDsBnAfgTgDfBvAhAAcB+DyAZQCeAnAfgG8A+AuA1QC8AsAdAM4C8AiApwC8D8CZAF4E4DsAfgvgUgAvAfAYgCsAHAngXgCfBnAfgM8A/AHAawA+BeAyACcD+COA3wK4A8B+AB8G8DGANwC4AcD3ANwE4B8APgvgcgB+CeA8APcBeAyAdwC8CeB5APYA+DyAcwA8DODpAK4D8EkAewD8GMD7AFwP4HkAngfgHAB/BfBHALcD8B+A5gAOAnAHgDcAeBqAXwC4GcC5AF4P4LIAHAngfgCfBfB5AEcCeByAywD8DcAjAP4DwDsBXAHgYQDeBuAZAD8D8DMAZwC4BcBjAJ4F4H4AHgLgSwAvAfA4AH8DcEwA3wNwHoAvAJwD4GEAnwBwOoDLAOwBcCGAXwC4BsA3APwI4DsA3gngeQCeBeAtAJ4E8DCA/QA8A8CVAH4H4A8APgXgHAC7AXwZgDsAPA3geQD2A/AggG8B+DyAVQCuA3AfgP8BcBiA+wC8COBJAM4E8CqAbwC4GsA/AFwP4A0AZwF4GIDLADwYgE0A3gbgNABfBHA9gBMAXAngLgBfBvBXANcD+BKAswC8DsB+AHcB+COAiwCcC+AyACcDeBSAIwA8BOAdAKcCuAvAAwC8COBXAA8A+CSAxwC8AsBFAJ8D8DCA5wI4D8DLAOz2n2gPA7gHwDkAnwBwOoDLADwYgCcCeAbAfgDvBPA0gIcDuBPAtAB+C+BxAC8E8CyAXwC4D8CVAH4A4DsAPgbgYQCeBuAxAJcB+AHAHwC8CMAYwB8AfBHALwA4D8BfAPwXgLMA/BDAPADvAvAbAH8D8B+ALwI4B8D3ALwDwC8A3gXgHACnAngIgCsAHAPgHAB/BXAngDsA3AvgfQCeC+AfAD8G8AyADwC8BeBZAG8D8DyAGwA8A+DCAH4G8E4AvwJwCIA/AFgD4H0AngPgfACPAvgPgJcAvAngYgCPBPBAAPYDuA/AlwA8BuA8AMcB+DCAZwG8CeAlAM8D8GkAzgJwDoCvAXgFgFMA/ADAZQCXAThOwLMB/Gv/o+v8H/kf/z/lP/+H69bXf9cAAAAASUVORK5CYII=" alt="Pixel Art Rakhi" class="pixel-rakhi">
|
161 |
+
<div class="greeting">{{ greeting or "A Special Gift For You!" }}</div>
|
162 |
+
</div>
|
163 |
+
|
164 |
+
<div class="chat-messages" id="chatMessages">
|
165 |
+
<div class="message bot">
|
166 |
+
<div class="message-bubble">
|
167 |
+
<span class="agent-badge">🤖 Your AI Study Bro</span>
|
168 |
+
Happy Raksha Bandhan! I built this little study buddy just for you. Think of me as your personal tutor. You can ask me anything about your B.Pharmacy subjects, get help with drug info, or even practice for your viva exams. Let's start this new chapter of learning together! ❤️
|
169 |
+
</div>
|
170 |
+
</div>
|
171 |
+
</div>
|
172 |
+
|
173 |
+
<div class="input-area">
|
174 |
+
<div id="fileUploadSection">
|
175 |
+
<input type="file" id="fileInput" style="display: none;">
|
176 |
+
<button onclick="document.getElementById('fileInput').click()" class="quick-btn">📄 Upload Notes</button>
|
177 |
+
<button id="uploadBtn" onclick="uploadFile()" style="display:none;">Upload</button>
|
178 |
+
<div id="fileList" style="margin-top: 10px; font-size: 0.9em;"></div>
|
179 |
+
</div>
|
180 |
+
|
181 |
+
<div class="quick-actions">
|
182 |
+
<button class="quick-btn" onclick="populateInput('Explain ')">📊 Explain Topic</button>
|
183 |
+
<button class="quick-btn" onclick="populateInput('Make a quiz on ')">❓ Create Quiz</button>
|
184 |
+
<button class="quick-btn" onclick="populateInput('Mnemonic for ')">🧠 Memory Aid</button>
|
185 |
+
</div>
|
186 |
+
|
187 |
+
<div class="input-container">
|
188 |
+
<input type="text" id="messageInput" placeholder="Ask me anything..." onkeypress="handleKeyPress(event)">
|
189 |
+
<button id="sendBtn" onclick="sendMessage()">➤</button>
|
190 |
+
</div>
|
191 |
+
</div>
|
192 |
+
</div>
|
193 |
+
|
194 |
+
<script>
|
195 |
+
let isProcessing = false;
|
196 |
+
|
197 |
+
// --- Core Messaging ---
|
198 |
+
async function sendMessage() {
|
199 |
+
const input = document.getElementById('messageInput');
|
200 |
+
const message = input.value.trim();
|
201 |
+
if (!message || isProcessing) return;
|
202 |
+
addMessage(message, 'user');
|
203 |
+
input.value = '';
|
204 |
+
showLoading(true);
|
205 |
+
try {
|
206 |
+
const response = await fetch('/chat', {
|
207 |
+
method: 'POST',
|
208 |
+
headers: { 'Content-Type': 'application/json' },
|
209 |
+
body: JSON.stringify({ query: message })
|
210 |
+
});
|
211 |
+
const data = await response.json();
|
212 |
+
showLoading(false);
|
213 |
+
if (data.success) {
|
214 |
+
addMessage(data.message, 'bot', data.agent_used);
|
215 |
+
} else {
|
216 |
+
addMessage(`❌ Error: ${data.error || 'Something went wrong'}`, 'bot', 'error');
|
217 |
+
}
|
218 |
+
} catch (error) {
|
219 |
+
showLoading(false);
|
220 |
+
addMessage(`❌ Connection error: ${error.message}`, 'bot', 'error');
|
221 |
+
}
|
222 |
+
}
|
223 |
+
|
224 |
+
function addMessage(text, sender, agentType = '') {
|
225 |
+
const messagesContainer = document.getElementById('chatMessages');
|
226 |
+
const messageDiv = document.createElement('div');
|
227 |
+
messageDiv.className = `message ${sender}`;
|
228 |
+
const agentIcons = {
|
229 |
+
'academic': '📚 Academic Agent', 'drug_info': '💊 Drug Info Agent',
|
230 |
+
'quiz_generation': '❓ Quiz Master', 'mnemonic_creation': '🧠 Memory Master',
|
231 |
+
'viva_practice': '🗣️ Viva Coach', 'error': '⚠️ System'
|
232 |
+
};
|
233 |
+
const agentBadge = sender === 'bot' ? `<span class="agent-badge">${agentIcons[agentType] || '🤖 AI Assistant'}</span>` : '';
|
234 |
+
const formattedText = marked.parse(text || 'Sorry, I received an empty response.');
|
235 |
+
messageDiv.innerHTML = `<div class="message-bubble">${agentBadge}${formattedText}</div>`;
|
236 |
+
messagesContainer.appendChild(messageDiv);
|
237 |
+
messagesContainer.scrollTop = messagesContainer.scrollHeight;
|
238 |
+
}
|
239 |
+
|
240 |
+
function showLoading(show) {
|
241 |
+
const sendBtn = document.getElementById('sendBtn');
|
242 |
+
const existingLoading = document.getElementById('loading-indicator');
|
243 |
+
if (existingLoading) existingLoading.remove();
|
244 |
+
|
245 |
+
sendBtn.disabled = show;
|
246 |
+
if (show) {
|
247 |
+
const messagesContainer = document.getElementById('chatMessages');
|
248 |
+
const loadingDiv = document.createElement('div');
|
249 |
+
loadingDiv.className = 'message bot';
|
250 |
+
loadingDiv.id = 'loading-indicator';
|
251 |
+
loadingDiv.innerHTML = `<div class="message-bubble"><div class="typing-indicator"><span></span><span></span><span></span></div></div>`;
|
252 |
+
messagesContainer.appendChild(loadingDiv);
|
253 |
+
messagesContainer.scrollTop = messagesContainer.scrollHeight;
|
254 |
+
}
|
255 |
+
}
|
256 |
+
|
257 |
+
function handleKeyPress(event) {
|
258 |
+
if (event.key === 'Enter' && !event.shiftKey) {
|
259 |
+
event.preventDefault();
|
260 |
+
sendMessage();
|
261 |
+
}
|
262 |
+
}
|
263 |
+
|
264 |
+
function populateInput(templateText) {
|
265 |
+
const input = document.getElementById('messageInput');
|
266 |
+
input.value = templateText;
|
267 |
+
input.focus();
|
268 |
+
}
|
269 |
+
|
270 |
+
// --- File Management ---
|
271 |
+
document.getElementById('fileInput').addEventListener('change', function() {
|
272 |
+
if (this.files.length > 0) uploadFile();
|
273 |
+
});
|
274 |
+
|
275 |
+
async function uploadFile() {
|
276 |
+
const fileInput = document.getElementById('fileInput');
|
277 |
+
const file = fileInput.files[0];
|
278 |
+
if (!file) return;
|
279 |
+
|
280 |
+
const formData = new FormData();
|
281 |
+
formData.append('file', file);
|
282 |
+
showLoading(true);
|
283 |
+
try {
|
284 |
+
const response = await fetch('/upload', { method: 'POST', body: formData });
|
285 |
+
const data = await response.json();
|
286 |
+
showLoading(false);
|
287 |
+
if (data.success) {
|
288 |
+
addMessage(`✅ Successfully uploaded "${file.name}". You can now ask questions about it.`, 'bot', 'academic');
|
289 |
+
updateFileList(data.files);
|
290 |
+
} else { addMessage(`❌ Upload Error: ${data.error}`, 'bot', 'error'); }
|
291 |
+
} catch (error) {
|
292 |
+
showLoading(false);
|
293 |
+
addMessage(`❌ Connection Error: ${error.message}`, 'bot', 'error');
|
294 |
+
} finally {
|
295 |
+
fileInput.value = '';
|
296 |
+
}
|
297 |
+
}
|
298 |
+
|
299 |
+
function updateFileList(files = []) {
|
300 |
+
const fileListDiv = document.getElementById('fileList');
|
301 |
+
if (files.length > 0) {
|
302 |
+
let fileLinks = files.map(f => `<span>${f.original_name}</span>`).join(', ');
|
303 |
+
fileListDiv.innerHTML = `<div style="font-size: 0.9em;"><strong>Active Notes:</strong> ${fileLinks} <button onclick="clearFiles()" style="margin-left: 10px; background: var(--dark-pink); color: white; border: none; border-radius: 5px; cursor: pointer; padding: 2px 8px;">Clear</button></div>`;
|
304 |
+
} else {
|
305 |
+
fileListDiv.innerHTML = '';
|
306 |
+
}
|
307 |
+
}
|
308 |
+
|
309 |
+
async function clearFiles() {
|
310 |
+
showLoading(true);
|
311 |
+
try {
|
312 |
+
const response = await fetch('/clear_files', { method: 'POST' });
|
313 |
+
const data = await response.json();
|
314 |
+
showLoading(false);
|
315 |
+
if (data.success) {
|
316 |
+
addMessage('🧹 All uploaded notes have been cleared.', 'bot', 'academic');
|
317 |
+
updateFileList([]);
|
318 |
+
} else { addMessage(`❌ Error: ${data.error}`, 'bot', 'error'); }
|
319 |
+
} catch (error) {
|
320 |
+
showLoading(false);
|
321 |
+
addMessage(`❌ Connection Error: ${error.message}`, 'bot', 'error');
|
322 |
+
}
|
323 |
+
}
|
324 |
+
|
325 |
+
document.addEventListener('DOMContentLoaded', () => {
|
326 |
+
document.getElementById('messageInput').focus();
|
327 |
+
fetch('/files').then(res => res.json()).then(data => updateFileList(data.files));
|
328 |
+
});
|
329 |
+
</script>
|
330 |
+
</body>
|
331 |
+
</html>
|
templates/rakhi_greeting.html
ADDED
@@ -0,0 +1,445 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<!--Shiva-->
|
2 |
+
<!DOCTYPE html>
|
3 |
+
<html lang="en">
|
4 |
+
<head>
|
5 |
+
<meta charset="UTF-8" />
|
6 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
7 |
+
<title>Happy Raksha Bandhan!</title>
|
8 |
+
<link rel="preconnect" href="https://fonts.googleapis.com"/>
|
9 |
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin/>
|
10 |
+
<link href="https://fonts.googleapis.com/css2?family=Tangerine:wght@700&family=Hind:wght@400;600&display=swap" rel="stylesheet"/>
|
11 |
+
|
12 |
+
<style>
|
13 |
+
:root {
|
14 |
+
--rani-pink: #e42c8c;
|
15 |
+
--marigold: #ffc048;
|
16 |
+
--gold: #ffd700;
|
17 |
+
--cream: #fff9e6;
|
18 |
+
--deep-maroon: #800000;
|
19 |
+
--text-brown: #5c3b1e;
|
20 |
+
}
|
21 |
+
|
22 |
+
/* (All previous CSS is the same) */
|
23 |
+
* { box-sizing: border-box; }
|
24 |
+
body {
|
25 |
+
margin: 0; padding: 0; font-family: 'Hind', sans-serif;
|
26 |
+
background: radial-gradient(circle, var(--cream), var(--marigold));
|
27 |
+
display: flex; justify-content: center; align-items: center;
|
28 |
+
min-height: 100vh; padding: 40px 20px;
|
29 |
+
}
|
30 |
+
.card {
|
31 |
+
background: var(--cream); width: 100%; max-width: 500px;
|
32 |
+
padding: 40px 30px; border-radius: 20px; border: 4px solid var(--gold);
|
33 |
+
box-shadow: 0 12px 40px rgba(0, 0, 0, 0.2); text-align: center;
|
34 |
+
position: relative; animation: fadeInCard 1.2s ease-out;
|
35 |
+
}
|
36 |
+
@keyframes fadeInCard { from { opacity: 0; transform: scale(0.9); } to { opacity: 1; transform: scale(1); } }
|
37 |
+
.card::before, .card::after {
|
38 |
+
content: '❀'; font-size: 48px; color: var(--rani-pink); opacity: 0.4;
|
39 |
+
position: absolute; animation: spin 20s linear infinite;
|
40 |
+
}
|
41 |
+
.card::before { top: 15px; left: 15px; }
|
42 |
+
.card::after { bottom: 15px; right: 15px; }
|
43 |
+
@keyframes spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }
|
44 |
+
.title {
|
45 |
+
font-family: 'Tangerine', cursive; font-size: 4.5em; color: var(--rani-pink);
|
46 |
+
margin: 0; text-shadow: 0 0 10px rgba(228, 44, 140, 0.15);
|
47 |
+
animation: glow 2.5s infinite alternate;
|
48 |
+
}
|
49 |
+
@keyframes glow { from { text-shadow: 0 0 5px rgba(228, 44, 140, 0.1); } to { text-shadow: 0 0 20px var(--gold); } }
|
50 |
+
.subtitle { font-size: 1.4em; font-weight: 600; color: var(--deep-maroon); margin-top: 18px; }
|
51 |
+
.rakhi-svg {
|
52 |
+
width: 110px; height: 110px; margin: 30px auto;
|
53 |
+
animation: popIn 1s ease-out, float 4s ease-in-out infinite;
|
54 |
+
}
|
55 |
+
@keyframes popIn { from { transform: scale(0); } to { transform: scale(1); } }
|
56 |
+
@keyframes float { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-12px); } }
|
57 |
+
.message { font-size: 1.1em; color: var(--text-brown); margin: 20px 0; line-height: 1.6; }
|
58 |
+
|
59 |
+
/* --- CSS CHANGES START HERE --- */
|
60 |
+
|
61 |
+
.blessing-container {
|
62 |
+
margin-top: 25px;
|
63 |
+
padding: 18px;
|
64 |
+
background: rgba(255, 223, 100, 0.15);
|
65 |
+
border-radius: 12px;
|
66 |
+
border: 2px dashed var(--gold);
|
67 |
+
position: relative; /* This is needed to position the overlay inside */
|
68 |
+
}
|
69 |
+
|
70 |
+
.blessing-overlay {
|
71 |
+
position: absolute;
|
72 |
+
inset: 0; /* This is a modern shorthand for top:0, right:0, bottom:0, left:0 */
|
73 |
+
background: linear-gradient(135deg, var(--rani-pink), var(--marigold));
|
74 |
+
color: white;
|
75 |
+
display: flex;
|
76 |
+
justify-content: center;
|
77 |
+
align-items: center;
|
78 |
+
border-radius: 10px; /* Match the parent's border-radius */
|
79 |
+
cursor: pointer;
|
80 |
+
transition: opacity 0.8s ease-in-out; /* Controls the fade-out speed */
|
81 |
+
z-index: 2; /* Ensures it sits on top of the text */
|
82 |
+
}
|
83 |
+
|
84 |
+
.blessing-overlay p {
|
85 |
+
font-weight: 600;
|
86 |
+
font-size: 1.2em;
|
87 |
+
padding: 10px;
|
88 |
+
text-shadow: 1px 1px 3px rgba(0,0,0,0.2);
|
89 |
+
}
|
90 |
+
|
91 |
+
/* --- CSS CHANGES END HERE --- */
|
92 |
+
|
93 |
+
.blessing { font-size: 1.15em; font-weight: 600; color: var(--deep-maroon); }
|
94 |
+
.signature { font-family: 'Tangerine', cursive; font-size: 2.4em; color: var(--rani-pink); margin-top: 30px; }
|
95 |
+
|
96 |
+
@media (max-width: 600px) {
|
97 |
+
/* (Responsive CSS is unchanged) */
|
98 |
+
.title { font-size: 3.4em; } .subtitle { font-size: 1.15em; }
|
99 |
+
.message { font-size: 1em; } .blessing { font-size: 1.05em; }
|
100 |
+
.signature { font-size: 2em; }
|
101 |
+
.card::before, .card::after { font-size: 32px; }
|
102 |
+
.rakhi-svg { width: 90px; height: 90px; }
|
103 |
+
}
|
104 |
+
</style>
|
105 |
+
</head>
|
106 |
+
<body>
|
107 |
+
|
108 |
+
<div class="card">
|
109 |
+
<h1 class="title">Happy Raksha Bandhan!</h1>
|
110 |
+
|
111 |
+
<p class="subtitle">To my dearest sister,</p>
|
112 |
+
|
113 |
+
<svg class="rakhi-svg" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
|
114 |
+
<style>
|
115 |
+
.rakhi-thread { stroke: #e42c8c; stroke-width: 2; stroke-dasharray: 2 2; animation: dash 1s linear infinite; }
|
116 |
+
.rakhi-center { fill: #ffc048; filter: drop-shadow(0 0 2px #c71585); }
|
117 |
+
.rakhi-gem { fill: #40E0D0; }
|
118 |
+
.rakhi-pearl { fill: #fff9e6; stroke: #ffd700; stroke-width: 0.5; }
|
119 |
+
@keyframes dash { to { stroke-dashoffset: -8; } }
|
120 |
+
</style>
|
121 |
+
<path class="rakhi-thread" d="M0 50 H 25" />
|
122 |
+
<path class="rakhi-thread" d="M75 50 H 100" />
|
123 |
+
<circle class="rakhi-center" cx="50" cy="50" r="22"/>
|
124 |
+
<circle cx="50" cy="50" r="15" fill="#c71585"/>
|
125 |
+
<path d="M50 38 L55 48 L62 50 L55 52 L50 62 L45 52 L38 50 L45 48 Z" fill="#ffd700"/>
|
126 |
+
<circle class="rakhi-gem" cx="50" cy="50" r="5"/>
|
127 |
+
<circle class="rakhi-pearl" cx="28" cy="50" r="3"/>
|
128 |
+
<circle class="rakhi-pearl" cx="72" cy="50" r="3"/>
|
129 |
+
</svg>
|
130 |
+
|
131 |
+
<p class="message">
|
132 |
+
On this special day, I'm sending you all the love and wishes.Thank you for being someone I've shared talks and walks with, a guide, a best friend.This thread is not just silk and color, but a promise of my love and support — forever and always.
|
133 |
+
</p>
|
134 |
+
|
135 |
+
<div class="blessing-container">
|
136 |
+
<div class="blessing-overlay" onclick="revealBlessing(this)">
|
137 |
+
<p>🎁 A Special Note For You...<br>(Tap to Reveal)</p>
|
138 |
+
</div>
|
139 |
+
|
140 |
+
<p class="blessing">
|
141 |
+
<strong>Dear Sister,</strong><br>
|
142 |
+
Akka! I never did anything great to your life. It was you who stood by in all situations holding this family.<br>
|
143 |
+
I'm really blessed to have you in my life.Never be worried/sad, let's be honest, it dosen't suit you :)<br>
|
144 |
+
In the next birth! I want to be your sister and you to be my brother. SO I can take care of you just like how you did!<br>
|
145 |
+
I wnat you to reach greater achievements and be the best version of yourself.<br>
|
146 |
+
May this Raksha Bandhan bring you all the happiness and success you deserve. May your life be filled with joy, love, and endless opportunities. Remember, I am always here for you, cheering you on every step of the way.
|
147 |
+
</p>
|
148 |
+
</div>
|
149 |
+
|
150 |
+
<p class="signature">With Love,<br>Your Brother</p>
|
151 |
+
</div>
|
152 |
+
|
153 |
+
<script>
|
154 |
+
function revealBlessing(element) {
|
155 |
+
// This makes the overlay fade out
|
156 |
+
element.style.opacity = '0';
|
157 |
+
// This makes the faded overlay unclickable
|
158 |
+
element.style.pointerEvents = 'none';
|
159 |
+
}
|
160 |
+
</script>
|
161 |
+
|
162 |
+
</body>
|
163 |
+
</html>
|
164 |
+
<!--
|
165 |
+
<!DOCTYPE html>
|
166 |
+
<html lang="en">
|
167 |
+
<head>
|
168 |
+
<meta charset="UTF-8" />
|
169 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
170 |
+
<title>Happy Raksha Bandhan!</title>
|
171 |
+
<link rel="preconnect" href="https://fonts.googleapis.com"/>
|
172 |
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin/>
|
173 |
+
<link href="https://fonts.googleapis.com/css2?family=Tangerine:wght@700&family=Hind:wght@400;600&display=swap" rel="stylesheet"/>
|
174 |
+
|
175 |
+
<style>
|
176 |
+
/* --- Root Variables and Original Card Styles (Untouched) --- */
|
177 |
+
:root {
|
178 |
+
--rani-pink: #e42c8c;
|
179 |
+
--marigold: #ffc048;
|
180 |
+
--gold: #ffd700;
|
181 |
+
--cream: #fff9e6;
|
182 |
+
--deep-maroon: #800000;
|
183 |
+
--text-brown: #5c3b1e;
|
184 |
+
--envelope-bg: #f7e4a8;
|
185 |
+
--envelope-shadow: rgba(0,0,0,0.2);
|
186 |
+
}
|
187 |
+
|
188 |
+
* {
|
189 |
+
box-sizing: border-box;
|
190 |
+
margin: 0;
|
191 |
+
padding: 0;
|
192 |
+
}
|
193 |
+
|
194 |
+
body {
|
195 |
+
font-family: 'Hind', sans-serif;
|
196 |
+
background: radial-gradient(circle, var(--cream), var(--marigold));
|
197 |
+
display: flex;
|
198 |
+
justify-content: center;
|
199 |
+
align-items: center;
|
200 |
+
min-height: 100vh;
|
201 |
+
padding: 40px 20px;
|
202 |
+
}
|
203 |
+
|
204 |
+
/* --- New Envelope Styles --- */
|
205 |
+
.envelope-wrapper {
|
206 |
+
position: relative;
|
207 |
+
cursor: pointer;
|
208 |
+
}
|
209 |
+
|
210 |
+
.envelope {
|
211 |
+
position: relative;
|
212 |
+
width: 320px;
|
213 |
+
height: 200px;
|
214 |
+
background-color: var(--envelope-bg);
|
215 |
+
box-shadow: 0 4px 20px var(--envelope-shadow);
|
216 |
+
transition: all 0.5s 0.8s ease-in-out; /* Delay transition */
|
217 |
+
}
|
218 |
+
|
219 |
+
.envelope::before { /* Front bottom part of envelope */
|
220 |
+
content: '';
|
221 |
+
position: absolute;
|
222 |
+
top: 100px;
|
223 |
+
left: 0;
|
224 |
+
width: 100%;
|
225 |
+
height: 100px;
|
226 |
+
background: var(--envelope-bg);
|
227 |
+
z-index: 3;
|
228 |
+
border-top: 1px solid rgba(0,0,0,0.1);
|
229 |
+
}
|
230 |
+
|
231 |
+
.flap {
|
232 |
+
position: absolute;
|
233 |
+
width: 100%;
|
234 |
+
height: 100px;
|
235 |
+
background-color: var(--envelope-bg);
|
236 |
+
top: 0;
|
237 |
+
left: 0;
|
238 |
+
z-index: 4;
|
239 |
+
transform-origin: top;
|
240 |
+
transition: transform 0.5s ease-in-out;
|
241 |
+
clip-path: polygon(0 0, 100% 0, 50% 100%);
|
242 |
+
border-bottom: 2px solid rgba(0,0,0,0.1);
|
243 |
+
}
|
244 |
+
|
245 |
+
/* --- The user's original card, now positioned inside the envelope --- */
|
246 |
+
.card {
|
247 |
+
background: var(--cream);
|
248 |
+
width: 100%;
|
249 |
+
max-width: 500px;
|
250 |
+
padding: 40px 30px;
|
251 |
+
border-radius: 20px;
|
252 |
+
border: 4px solid var(--gold);
|
253 |
+
box-shadow: 0 12px 40px rgba(0, 0, 0, 0.2);
|
254 |
+
text-align: center;
|
255 |
+
position: absolute; /* Changed for positioning */
|
256 |
+
bottom: 0;
|
257 |
+
left: 50%;
|
258 |
+
z-index: 2;
|
259 |
+
opacity: 0; /* Initially hidden */
|
260 |
+
transform: translateX(-50%) translateY(100px); /* Start below */
|
261 |
+
transition: transform 0.8s 0.5s cubic-bezier(0.23, 1, 0.32, 1), opacity 0.5s 0.5s;
|
262 |
+
}
|
263 |
+
|
264 |
+
/* --- Envelope Opening Animations --- */
|
265 |
+
.envelope-wrapper.open .flap {
|
266 |
+
transform: rotateX(180deg);
|
267 |
+
z-index: 1;
|
268 |
+
transition-delay: 0s;
|
269 |
+
}
|
270 |
+
|
271 |
+
.envelope-wrapper.open .card {
|
272 |
+
opacity: 1;
|
273 |
+
transform: translateX(-50%) translateY(-150px);
|
274 |
+
transition-delay: 0.5s;
|
275 |
+
}
|
276 |
+
|
277 |
+
.envelope-wrapper.open .envelope {
|
278 |
+
transform: translateY(1100px);
|
279 |
+
transition-delay: 1s; /* Envelope fades after card comes up */
|
280 |
+
}
|
281 |
+
|
282 |
+
/* --- Original Card Animations & Styles (Untouched) --- */
|
283 |
+
@keyframes fadeInCard {
|
284 |
+
from { opacity: 0; transform: scale(0.9); }
|
285 |
+
to { opacity: 1; transform: scale(1); }
|
286 |
+
}
|
287 |
+
|
288 |
+
.card::before, .card::after {
|
289 |
+
content: '❀';
|
290 |
+
font-size: 48px;
|
291 |
+
color: var(--rani-pink);
|
292 |
+
opacity: 0.4;
|
293 |
+
position: absolute;
|
294 |
+
animation: spin 20s linear infinite;
|
295 |
+
}
|
296 |
+
|
297 |
+
.card::before { top: 15px; left: 15px; }
|
298 |
+
.card::after { bottom: 15px; right: 15px; }
|
299 |
+
|
300 |
+
@keyframes spin {
|
301 |
+
from { transform: rotate(0deg); }
|
302 |
+
to { transform: rotate(360deg); }
|
303 |
+
}
|
304 |
+
|
305 |
+
.title {
|
306 |
+
font-family: 'Tangerine', cursive;
|
307 |
+
font-size: 4.5em;
|
308 |
+
color: var(--rani-pink);
|
309 |
+
margin: 0;
|
310 |
+
text-shadow: 0 0 10px rgba(228, 44, 140, 0.15);
|
311 |
+
animation: glow 2.5s infinite alternate;
|
312 |
+
}
|
313 |
+
|
314 |
+
@keyframes glow {
|
315 |
+
from { text-shadow: 0 0 5px rgba(228, 44, 140, 0.1); }
|
316 |
+
to { text-shadow: 0 0 20px var(--gold); }
|
317 |
+
}
|
318 |
+
|
319 |
+
.subtitle {
|
320 |
+
font-size: 1.4em;
|
321 |
+
font-weight: 600;
|
322 |
+
color: var(--deep-maroon);
|
323 |
+
margin-top: 18px;
|
324 |
+
}
|
325 |
+
|
326 |
+
.rakhi-svg {
|
327 |
+
width: 110px;
|
328 |
+
height: 110px;
|
329 |
+
margin: 30px auto;
|
330 |
+
animation: popIn 1s ease-out 1.5s, float 4s ease-in-out infinite 2.5s;
|
331 |
+
animation-fill-mode: backwards;
|
332 |
+
}
|
333 |
+
|
334 |
+
@keyframes popIn {
|
335 |
+
from { transform: scale(0); }
|
336 |
+
to { transform: scale(1); }
|
337 |
+
}
|
338 |
+
|
339 |
+
@keyframes float {
|
340 |
+
0%, 100% { transform: translateY(0); }
|
341 |
+
50% { transform: translateY(-12px); }
|
342 |
+
}
|
343 |
+
|
344 |
+
.message {
|
345 |
+
font-size: 1.1em;
|
346 |
+
color: var(--text-brown);
|
347 |
+
margin: 20px 0;
|
348 |
+
line-height: 1.6;
|
349 |
+
}
|
350 |
+
|
351 |
+
.blessing-container {
|
352 |
+
margin-top: 25px;
|
353 |
+
padding: 18px;
|
354 |
+
background: rgba(255, 223, 100, 0.15);
|
355 |
+
border-radius: 12px;
|
356 |
+
border: 2px dashed var(--gold);
|
357 |
+
}
|
358 |
+
|
359 |
+
.blessing {
|
360 |
+
font-size: 1.15em;
|
361 |
+
font-weight: 600;
|
362 |
+
color: var(--deep-maroon);
|
363 |
+
}
|
364 |
+
|
365 |
+
.signature {
|
366 |
+
font-family: 'Tangerine', cursive;
|
367 |
+
font-size: 2.4em;
|
368 |
+
color: var(--rani-pink);
|
369 |
+
margin-top: 30px;
|
370 |
+
}
|
371 |
+
|
372 |
+
/* --- Original Responsive Styles (Untouched) --- */
|
373 |
+
@media (max-width: 600px) {
|
374 |
+
.card {
|
375 |
+
padding: 30px 20px;
|
376 |
+
transform: translateX(-50%) translateY(-120px) scale(0.9);
|
377 |
+
}
|
378 |
+
.envelope-wrapper.open .card {
|
379 |
+
transform: translateX(-50%) translateY(-120px) scale(0.9);
|
380 |
+
}
|
381 |
+
.title { font-size: 3.4em; }
|
382 |
+
.subtitle { font-size: 1.15em; }
|
383 |
+
.message { font-size: 1em; }
|
384 |
+
.blessing { font-size: 1.05em; }
|
385 |
+
.signature { font-size: 2em; }
|
386 |
+
.card::before, .card::after { font-size: 32px; }
|
387 |
+
.rakhi-svg { width: 90px; height: 90px; }
|
388 |
+
}
|
389 |
+
|
390 |
+
</style>
|
391 |
+
</head>
|
392 |
+
<body>
|
393 |
+
|
394 |
+
<div class="envelope-wrapper">
|
395 |
+
<div class="envelope">
|
396 |
+
<div class="flap"></div>
|
397 |
+
<div class="card">
|
398 |
+
<h1 class="title">Happy Raksha Bandhan!</h1>
|
399 |
+
|
400 |
+
<p class="title">To my dearest sister,</p>
|
401 |
+
|
402 |
+
<svg class="rakhi-svg" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
|
403 |
+
<style>
|
404 |
+
.rakhi-thread { stroke: #e42c8c; stroke-width: 2; stroke-dasharray: 2 2; animation: dash 1s linear infinite; }
|
405 |
+
.rakhi-center { fill: #ffc048; filter: drop-shadow(0 0 2px #c71585); }
|
406 |
+
.rakhi-gem { fill: #40E0D0; }
|
407 |
+
.rakhi-pearl { fill: #fff9e6; stroke: #ffd700; stroke-width: 0.5; }
|
408 |
+
@keyframes dash { to { stroke-dashoffset: -8; } }
|
409 |
+
</style>
|
410 |
+
<path class="rakhi-thread" d="M0 50 H 25" />
|
411 |
+
<path class="rakhi-thread" d="M75 50 H 100" />
|
412 |
+
<circle class="rakhi-center" cx="50" cy="50" r="22"/>
|
413 |
+
<circle cx="50" cy="50" r="15" fill="#c71585"/>
|
414 |
+
<path d="M50 38 L55 48 L62 50 L55 52 L50 62 L45 52 L38 50 L45 48 Z" fill="#ffd700"/>
|
415 |
+
<circle class="rakhi-gem" cx="50" cy="50" r="5"/>
|
416 |
+
<circle class="rakhi-pearl" cx="28" cy="50" r="3"/>
|
417 |
+
<circle class="rakhi-pearl" cx="72" cy="50" r="3"/>
|
418 |
+
</svg>
|
419 |
+
|
420 |
+
<p class="message">
|
421 |
+
On this special day, I'm sending you all the love and wishes. <br>Thank you for being someone I've shared talks and walks with, a guide, a best friend. <br>This thread is not just silk and color, but a promise of my love and support — forever and always.
|
422 |
+
</p>
|
423 |
+
|
424 |
+
<div class="blessing-container">
|
425 |
+
<p class="blessing">
|
426 |
+
Have an amazing time filled with success, joy, and happiness in everything you do have going forward!<br>
|
427 |
+
Never be worried/sad, let's be honest, it dosen't suit you :)<br>
|
428 |
+
PS don't forget, There is always a support behind you in me!<br>
|
429 |
+
</p>
|
430 |
+
</div>
|
431 |
+
|
432 |
+
<p class="signature">With Love,<br>Your Brother</p>
|
433 |
+
</div>
|
434 |
+
</div>
|
435 |
+
</div>
|
436 |
+
|
437 |
+
<script>
|
438 |
+
const envelope = document.querySelector('.envelope-wrapper');
|
439 |
+
envelope.addEventListener('click', () => {
|
440 |
+
envelope.classList.add('open');
|
441 |
+
});
|
442 |
+
</script>
|
443 |
+
|
444 |
+
</body>
|
445 |
+
</html>-->
|
utils/__init__.py
ADDED
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# utils/__init__.py
|
2 |
+
"""
|
3 |
+
Utils package initialization
|
4 |
+
"""
|
utils/file_processor.py
ADDED
@@ -0,0 +1,172 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# # utils/file_processor.py
|
2 |
+
|
3 |
+
# import os
|
4 |
+
# import json
|
5 |
+
# import csv
|
6 |
+
# import docx # From python-docx
|
7 |
+
# import PyPDF2
|
8 |
+
|
9 |
+
# class FileProcessor:
|
10 |
+
# """
|
11 |
+
# A utility class to process various file types and extract their text content.
|
12 |
+
# Supports .txt, .pdf, .docx, .json, and .csv files.
|
13 |
+
# """
|
14 |
+
|
15 |
+
# def __init__(self):
|
16 |
+
# """Initializes the FileProcessor."""
|
17 |
+
# pass
|
18 |
+
|
19 |
+
# def extract_text(self, file_path: str) -> str:
|
20 |
+
# """
|
21 |
+
# Extracts text content from a given file based on its extension.
|
22 |
+
|
23 |
+
# Args:
|
24 |
+
# file_path (str): The full path to the file.
|
25 |
+
|
26 |
+
# Returns:
|
27 |
+
# str: The extracted text content, or an empty string if extraction fails.
|
28 |
+
# """
|
29 |
+
# if not os.path.exists(file_path):
|
30 |
+
# print(f"Warning: File not found at {file_path}")
|
31 |
+
# return ""
|
32 |
+
|
33 |
+
# # Get the file extension and normalize it
|
34 |
+
# _, extension = os.path.splitext(file_path)
|
35 |
+
# extension = extension.lower()
|
36 |
+
|
37 |
+
# try:
|
38 |
+
# if extension == '.txt':
|
39 |
+
# return self._read_txt(file_path)
|
40 |
+
# elif extension == '.pdf':
|
41 |
+
# return self._read_pdf(file_path)
|
42 |
+
# elif extension == '.docx':
|
43 |
+
# return self._read_docx(file_path)
|
44 |
+
# elif extension == '.json':
|
45 |
+
# return self._read_json(file_path)
|
46 |
+
# elif extension == '.csv':
|
47 |
+
# return self._read_csv(file_path)
|
48 |
+
# elif extension == '.doc':
|
49 |
+
# return "Legacy .doc files are not supported. Please convert to .docx."
|
50 |
+
# else:
|
51 |
+
# print(f"Warning: Unsupported file type: {extension}")
|
52 |
+
# return ""
|
53 |
+
# except Exception as e:
|
54 |
+
# print(f"Error processing file {file_path}: {e}")
|
55 |
+
# return f"Error extracting content from file. It may be corrupted or protected."
|
56 |
+
|
57 |
+
# def _read_txt(self, file_path: str) -> str:
|
58 |
+
# """Reads content from a .txt file."""
|
59 |
+
# with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
|
60 |
+
# return f.read()
|
61 |
+
|
62 |
+
# def _read_pdf(self, file_path: str) -> str:
|
63 |
+
# """Reads content from a .pdf file using PyPDF2."""
|
64 |
+
# text = []
|
65 |
+
# with open(file_path, 'rb') as f:
|
66 |
+
# reader = PyPDF2.PdfReader(f)
|
67 |
+
# for page in reader.pages:
|
68 |
+
# page_text = page.extract_text()
|
69 |
+
# if page_text:
|
70 |
+
# text.append(page_text)
|
71 |
+
# return "\n".join(text)
|
72 |
+
|
73 |
+
# def _read_docx(self, file_path: str) -> str:
|
74 |
+
# """Reads content from a .docx file using python-docx."""
|
75 |
+
# doc = docx.Document(file_path)
|
76 |
+
# text = [p.text for p in doc.paragraphs]
|
77 |
+
# return "\n".join(text)
|
78 |
+
|
79 |
+
# def _read_json(self, file_path: str) -> str:
|
80 |
+
# """Reads and pretty-prints content from a .json file."""
|
81 |
+
# with open(file_path, 'r', encoding='utf-8') as f:
|
82 |
+
# data = json.load(f)
|
83 |
+
# # Convert JSON object to a nicely formatted string
|
84 |
+
# return json.dumps(data, indent=2)
|
85 |
+
|
86 |
+
# def _read_csv(self, file_path: str) -> str:
|
87 |
+
# """Reads content from a .csv file and formats it as a string."""
|
88 |
+
# text = []
|
89 |
+
# with open(file_path, 'r', encoding='utf-8', newline='') as f:
|
90 |
+
# reader = csv.reader(f)
|
91 |
+
# for row in reader:
|
92 |
+
# text.append(", ".join(row))
|
93 |
+
# return "\n".join(text)
|
94 |
+
# utils/file_processor.py
|
95 |
+
|
96 |
+
import os
|
97 |
+
import json
|
98 |
+
import csv
|
99 |
+
import docx
|
100 |
+
import fitz # PyMuPDF library
|
101 |
+
|
102 |
+
class FileProcessor:
|
103 |
+
"""
|
104 |
+
A utility class to process various file types and extract their text content.
|
105 |
+
Now uses the powerful PyMuPDF library for superior PDF text extraction.
|
106 |
+
"""
|
107 |
+
|
108 |
+
def extract_text(self, file_path: str) -> str:
|
109 |
+
"""
|
110 |
+
Extracts text content from a given file based on its extension.
|
111 |
+
"""
|
112 |
+
if not os.path.exists(file_path):
|
113 |
+
print(f"Warning: File not found at {file_path}")
|
114 |
+
return ""
|
115 |
+
|
116 |
+
_, extension = os.path.splitext(file_path)
|
117 |
+
extension = extension.lower()
|
118 |
+
|
119 |
+
try:
|
120 |
+
if extension == '.txt':
|
121 |
+
return self._read_txt(file_path)
|
122 |
+
elif extension == '.pdf':
|
123 |
+
# Using the new, better PDF reader
|
124 |
+
return self._read_pdf_with_pymupdf(file_path)
|
125 |
+
elif extension == '.docx':
|
126 |
+
return self._read_docx(file_path)
|
127 |
+
elif extension == '.json':
|
128 |
+
return self._read_json(file_path)
|
129 |
+
elif extension == '.csv':
|
130 |
+
return self._read_csv(file_path)
|
131 |
+
elif extension == '.doc':
|
132 |
+
return "Legacy .doc files are not supported. Please convert to .docx."
|
133 |
+
else:
|
134 |
+
print(f"Warning: Unsupported file type: {extension}")
|
135 |
+
return ""
|
136 |
+
except Exception as e:
|
137 |
+
print(f"Error processing file {file_path}: {e}")
|
138 |
+
return f"Error extracting content from file. It may be corrupted or protected."
|
139 |
+
|
140 |
+
def _read_txt(self, file_path: str) -> str:
|
141 |
+
"""Reads content from a .txt file."""
|
142 |
+
with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
|
143 |
+
return f.read()
|
144 |
+
|
145 |
+
def _read_pdf_with_pymupdf(self, file_path: str) -> str:
|
146 |
+
"""Reads content from a .pdf file using the PyMuPDF (fitz) library."""
|
147 |
+
text = []
|
148 |
+
with fitz.open(file_path) as doc:
|
149 |
+
for page in doc:
|
150 |
+
text.append(page.get_text())
|
151 |
+
return "\n".join(text)
|
152 |
+
|
153 |
+
def _read_docx(self, file_path: str) -> str:
|
154 |
+
"""Reads content from a .docx file using python-docx."""
|
155 |
+
doc = docx.Document(file_path)
|
156 |
+
text = [p.text for p in doc.paragraphs]
|
157 |
+
return "\n".join(text)
|
158 |
+
|
159 |
+
def _read_json(self, file_path: str) -> str:
|
160 |
+
"""Reads and pretty-prints content from a .json file."""
|
161 |
+
with open(file_path, 'r', encoding='utf-8') as f:
|
162 |
+
data = json.load(f)
|
163 |
+
return json.dumps(data, indent=2)
|
164 |
+
|
165 |
+
def _read_csv(self, file_path: str) -> str:
|
166 |
+
"""Reads content from a .csv file and formats it as a string."""
|
167 |
+
text = []
|
168 |
+
with open(file_path, 'r', encoding='utf-8', newline='') as f:
|
169 |
+
reader = csv.reader(f)
|
170 |
+
for row in reader:
|
171 |
+
text.append(", ".join(row))
|
172 |
+
return "\n".join(text)
|
utils/helpers.py
ADDED
@@ -0,0 +1,112 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# utils/helpers.py
|
2 |
+
"""
|
3 |
+
Helper utility functions
|
4 |
+
"""
|
5 |
+
|
6 |
+
import json
|
7 |
+
import os
|
8 |
+
import random
|
9 |
+
from datetime import datetime
|
10 |
+
from zoneinfo import ZoneInfo
|
11 |
+
from langchain_community.document_loaders import PyPDFDirectoryLoader
|
12 |
+
from langchain.text_splitter import RecursiveCharacterTextSplitter
|
13 |
+
from langchain_google_genai import GoogleGenerativeAIEmbeddings
|
14 |
+
from langchain_community.vectorstores import FAISS
|
15 |
+
from dotenv import load_dotenv
|
16 |
+
load_dotenv()
|
17 |
+
GEMINI_API_KEY = os.getenv('GEMINI_API_KEY')
|
18 |
+
def create_vector_store():
|
19 |
+
"""
|
20 |
+
Checks if a vector store index exists. If not, it creates one from
|
21 |
+
the PDFs in the knowledge_base folder.
|
22 |
+
"""
|
23 |
+
persist_directory = '/tmp/faiss_index'
|
24 |
+
if os.path.exists(persist_directory):
|
25 |
+
print("--- Knowledge base (FAISS index) already exists. Loading... ---")
|
26 |
+
return
|
27 |
+
|
28 |
+
# Check if there are files to process
|
29 |
+
if not os.path.exists("./knowledge_base") or not os.listdir("./knowledge_base"):
|
30 |
+
print("--- 'knowledge_base' folder is empty or does not exist. Skipping index creation. ---")
|
31 |
+
return
|
32 |
+
|
33 |
+
print("--- Creating new knowledge base... ---")
|
34 |
+
loader = PyPDFDirectoryLoader("./knowledge_base/")
|
35 |
+
documents = loader.load()
|
36 |
+
if not documents:
|
37 |
+
print("--- No documents could be loaded. Skipping index creation. ---")
|
38 |
+
return
|
39 |
+
|
40 |
+
print(f"--- Loaded {len(documents)} document(s). Splitting text... ---")
|
41 |
+
text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=100)
|
42 |
+
docs = text_splitter.split_documents(documents)
|
43 |
+
|
44 |
+
print(f"--- Creating embeddings and vector store. This may take a moment... ---")
|
45 |
+
embeddings = GoogleGenerativeAIEmbeddings(model="models/embedding-001", google_api_key=GEMINI_API_KEY)
|
46 |
+
db = FAISS.from_documents(docs, embeddings)
|
47 |
+
db.save_local(persist_directory)
|
48 |
+
print("--- Knowledge base created successfully. ---")
|
49 |
+
|
50 |
+
def load_quotes():
|
51 |
+
"""Load inspirational quotes from Gita/Vedas"""
|
52 |
+
quotes_file = 'data/quotes.json'
|
53 |
+
|
54 |
+
default_quotes = [
|
55 |
+
"विद्या ददाति विनयं - Knowledge gives humility",
|
56 |
+
"योग: कर्मसु कौशलम् - Yoga is skill in action",
|
57 |
+
"श्रेयान्स्वधर्मो विगुण: - Better is one's own dharma though imperfectly performed",
|
58 |
+
"कर्मण्येवाधिकारस्ते - You have the right to perform action",
|
59 |
+
"विद्या धनं सर्व धन प्रधानम् - Knowledge is the supreme wealth",
|
60 |
+
"सत्यमेव जयते - Truth alone triumphs",
|
61 |
+
"तमसो मा ज्योतिर्गमय - Lead me from darkness to light",
|
62 |
+
"अहिंसा परमो धर्म: - Non-violence is the supreme virtue"
|
63 |
+
]
|
64 |
+
|
65 |
+
if not os.path.exists(quotes_file):
|
66 |
+
os.makedirs('data', exist_ok=True)
|
67 |
+
with open(quotes_file, 'w', encoding='utf-8') as f:
|
68 |
+
json.dump(default_quotes, f, indent=2, ensure_ascii=False)
|
69 |
+
return default_quotes
|
70 |
+
|
71 |
+
try:
|
72 |
+
with open(quotes_file, 'r', encoding='utf-8') as f:
|
73 |
+
return json.load(f)
|
74 |
+
except:
|
75 |
+
return default_quotes
|
76 |
+
|
77 |
+
def get_greeting():
|
78 |
+
"""
|
79 |
+
Returns a time-of-day appropriate greeting in English and Hindi,
|
80 |
+
specifically for the Indian Standard Time (IST) timezone.
|
81 |
+
"""
|
82 |
+
# Define the Indian Standard Time timezone
|
83 |
+
ist_timezone = ZoneInfo("Asia/Kolkata")
|
84 |
+
|
85 |
+
# Get the current time in the IST timezone
|
86 |
+
current_time_ist = datetime.now(ist_timezone)
|
87 |
+
current_hour = current_time_ist.hour
|
88 |
+
|
89 |
+
if 5 <= current_hour < 12:
|
90 |
+
return "☀️ सुप्रभात (Good Morning)! Ready to start the day?"
|
91 |
+
elif 12 <= current_hour < 17:
|
92 |
+
return "☀️ नमस्कार (Good Afternoon)! Time for a study session?"
|
93 |
+
elif 17 <= current_hour < 21:
|
94 |
+
return "🌇 शुभ संध्या (Good Evening)! Wrapping up your studies?"
|
95 |
+
else:
|
96 |
+
return "🌙 शुभ रात्रि (Good Night)! Late night study session?"
|
97 |
+
|
98 |
+
|
99 |
+
def format_indian_text(text, add_emojis=True):
|
100 |
+
"""Format text with Indian cultural elements"""
|
101 |
+
if add_emojis:
|
102 |
+
# Add relevant emojis based on content
|
103 |
+
if any(word in text.lower() for word in ['drug', 'medicine', 'pharmaceutical']):
|
104 |
+
text = f"💊 {text}"
|
105 |
+
elif any(word in text.lower() for word in ['study', 'learn', 'education']):
|
106 |
+
text = f"📚 {text}"
|
107 |
+
elif any(word in text.lower() for word in ['quiz', 'test', 'exam']):
|
108 |
+
text = f"❓ {text}"
|
109 |
+
elif any(word in text.lower() for word in ['memory', 'remember', 'mnemonic']):
|
110 |
+
text = f"🧠 {text}"
|
111 |
+
|
112 |
+
return text
|