Eminem-Data-Entry / chatbot.py
arkapravac366's picture
Upload 10 files
a7908fe verified
Raw History Blame Contribute Delete
15.9 kB
import google.generativeai as genai
import sqlite3
import json
import os
from database import DonationDatabase
from typing import Dict, Any
from datetime import datetime
class ChatBot:
def __init__(self):
# Initialize database connection lazily
self._db = None
self._model = None
# Set API key directly
self._api_key = "AIzaSyA9fEe8HncZmvJ3BaurihNMaS9ydSGqblk"
if not self._api_key:
raise ValueError("API key is not set")
# Initialize conversation context
self.conversation_context = []
self.context_window = 5
@property
def db(self):
if self._db is None:
try:
self._db = DonationDatabase()
except Exception as e:
print(f"Error initializing database: {str(e)}")
raise
return self._db
@property
def model(self):
if self._model is None:
try:
genai.configure(api_key=self._api_key)
self._model = genai.GenerativeModel("gemini-2.0-flash")
except Exception as e:
print(f"Error initializing Gemini model: {str(e)}")
raise
return self._model
def _get_database_context(self) -> str:
"""Get current database context for the model"""
try:
if not self.db:
return "Database connection not available"
total_donations = self.db.get_total_donations()
recent_donations = self.db.get_recent_donations(5)
categories = self.db.get_categories()
if total_donations is None or recent_donations is None or categories is None:
return "Error retrieving database information"
context = f"Current total donations: ${total_donations:.2f}\n"
context += "Available categories: " + ", ".join(categories) + "\n"
context += "Recent donations:\n"
for donation in recent_donations:
context += f"- {donation['donor_name']}: ${donation['amount']} ({donation['category']})\n"
return context
except Exception as e:
print(f"Error in _get_database_context: {str(e)}")
return "Error getting database context"
def _format_prompt(self, user_input: str) -> str:
"""Format the prompt with context and user input"""
system_prompt = (
"I am Eminem 1.0.0, a helpful and intelligent donation management assistant created by Arkaprava Chakraborty. "
"I understand natural language queries and provide detailed, contextual responses. "
"I can help you manage donations, analyze trends, generate insights, and provide personalized recommendations. "
"I maintain context across conversations and can handle complex queries about donations, donors, and financial statistics. "
"You have access to the following database context and capabilities:\n"
"- Generate monthly donation reports\n"
"- Export donation data to Excel\n"
"- Analyze donation trends and patterns\n"
"- Access comprehensive donor statistics\n"
)
db_context = self._get_database_context()
# Add more context about the conversation flow
conversation_summary = ""
if self.conversation_context:
recent_topics = set()
for msg in self.conversation_context[-self.context_window:]:
if msg['role'] == 'user':
# Extract key topics from user messages
topics = [word.lower() for word in msg['content'].split()
if len(word) > 3 and word.lower() not in {'what', 'when', 'how', 'why', 'where', 'who'}]
recent_topics.update(topics)
if recent_topics:
conversation_summary = f"\nRecent conversation topics: {', '.join(recent_topics)}\n"
conversation_context = "\n".join([f"{msg['role']}: {msg['content']}"
for msg in self.conversation_context[-self.context_window:]])
# Enhanced prompt with better context integration
return f"{system_prompt}\n{db_context}\n{conversation_summary}\nConversation history:\n{conversation_context}\n\nUser: {user_input}\n\nPlease provide a detailed and contextually relevant response."
def _execute_db_command(self, command: Dict[str, Any]) -> str:
"""Execute database commands"""
if not isinstance(command, dict):
return "Invalid command format"
try:
action = command.get("action")
if not action:
return "No action specified in command"
if not self.db:
return "Database connection not available"
if action == "generate_report":
year = command.get("year", datetime.now().year)
month = command.get("month", datetime.now().month)
report = self.db.generate_monthly_report(year, month)
if report:
return f"Monthly report for {year}-{month:02d}:\n" + \
f"Total donations: {report['total_donations']}\n" + \
f"Total amount: ${report['total_amount']:.2f}\n" + \
f"Average amount: ${report['average_amount']:.2f}"
return "Failed to generate report"
elif action == "export_excel":
start_date = command.get("start_date")
end_date = command.get("end_date")
filepath = self.db.export_to_excel(start_date, end_date)
if filepath:
return f"Data exported successfully to {filepath}"
return "Failed to export data"
elif action == "analyze_trends":
months = command.get("months", 12)
trends = self.db.analyze_trends(months)
if trends:
summary = trends['summary']
return f"Trend analysis for the past {months} months:\n" + \
f"Growth rate: {summary['total_growth']:.1f}%\n" + \
f"Average monthly donations: {summary['avg_monthly_donations']:.1f}\n" + \
f"Top category: {summary['top_category']}\n" + \
f"Donor retention rate: {summary['retention_rate']:.1f}%"
return "Failed to analyze trends"
elif action == "add_donation":
success = self.db.add_donation(
donor_name=command["donor_name"],
amount=command["amount"],
category=command["category"],
notes=command.get("notes"),
is_recurring=command.get("is_recurring", False),
recurring_interval=command.get("recurring_interval"),
next_donation_date=command.get("next_donation_date")
)
return "Donation added successfully" if success else "Failed to add donation"
elif action == "update_donation":
with sqlite3.connect('donations.db') as conn:
cursor = conn.cursor()
update_fields = []
params = []
if "amount" in command:
update_fields.append("amount = ?")
params.append(command["amount"])
if "category" in command:
update_fields.append("category = ?")
params.append(command["category"])
if "notes" in command:
update_fields.append("notes = ?")
params.append(command["notes"])
if update_fields:
params.append(command["donation_id"])
cursor.execute(f"UPDATE donations SET {', '.join(update_fields)} WHERE id = ?", params)
conn.commit()
return "Donation updated successfully" if cursor.rowcount > 0 else "Donation not found"
return "No fields to update"
elif action == "get_donations":
donations = self.db.get_recent_donations(command.get("limit", 5))
total_count = len(donations)
if total_count == 0:
return "There are currently no donations in the system."
response = f"There are {total_count} recent donations. "
if total_count > 0:
response += "Here are the details:\n"
for donation in donations:
response += f"- {donation['donor_name']} donated ${donation['amount']:.2f} for {donation['category']}\n"
return response
elif action == "get_donor_statistics":
stats = self.db.get_donor_statistics()
response = f"Total number of donors: {stats['total_donors']}\n"
response += f"Average donation amount: ${stats['average_donation']:.2f}\n"
if stats['top_donors']:
response += "\nTop donors:\n"
for donor in stats['top_donors']:
response += f"- {donor['name']}: ${donor['total_amount']:.2f} ({donor['donation_count']} donations)\n"
return response
elif action == "get_donor_info":
donor_name = command.get("donor_name")
with sqlite3.connect('donations.db') as conn:
cursor = conn.cursor()
cursor.execute("""
SELECT dp.*, SUM(d.amount) as total_donated
FROM donor_profiles dp
LEFT JOIN donations d ON dp.name = d.donor_name
WHERE dp.name = ?
GROUP BY dp.id
""", (donor_name,))
donor = cursor.fetchone()
if donor:
return f"Donor found: {donor[1]}, Total donations: ${donor[6]:.2f}"
return "Donor not found"
elif action == "add_donor":
with sqlite3.connect('donations.db') as conn:
cursor = conn.cursor()
cursor.execute("""
INSERT INTO donor_profiles (name, email, phone, address)
VALUES (?, ?, ?, ?)
""", (command["donor_name"], command.get("email"), command.get("phone"), command.get("address")))
conn.commit()
return "Donor added successfully"
elif action == "update_donor":
with sqlite3.connect('donations.db') as conn:
cursor = conn.cursor()
update_fields = []
params = []
for field in ["email", "phone", "address", "preferred_category", "notification_preferences"]:
if field in command:
update_fields.append(f"{field} = ?")
params.append(command[field])
if update_fields:
params.append(command["donor_name"])
cursor.execute(f"UPDATE donor_profiles SET {', '.join(update_fields)} WHERE name = ?", params)
conn.commit()
return "Donor updated successfully" if cursor.rowcount > 0 else "Donor not found"
return "No fields to update"
elif action == "remove_donor":
with sqlite3.connect('donations.db') as conn:
cursor = conn.cursor()
cursor.execute("DELETE FROM donor_profiles WHERE name = ?", (command["donor_name"],))
if cursor.rowcount > 0:
conn.commit()
return "Donor removed successfully"
return "Donor not found"
return "Unknown database command"
except Exception as e:
return f"Error executing database command: {str(e)}"
def _extract_db_command(self, response: str) -> Dict[str, Any]:
"""Extract database commands from the response"""
try:
if "[DB_COMMAND]" in response:
command_start = response.index("[DB_COMMAND]")
command_end = response.index("[/DB_COMMAND]")
command_json = response[command_start + 12:command_end].strip()
return json.loads(command_json)
return None
except Exception:
return None
def get_response(self, message: str) -> str:
if not message or not message.strip():
return "Please provide a valid message"
try:
# Format prompt with context
prompt = self._format_prompt(message)
# Get response from Gemini with timeout and retry
max_retries = 3
retry_count = 0
response = None
last_error = None
while retry_count < max_retries:
try:
response = self.model.generate_content(prompt)
break
except Exception as e:
last_error = e
retry_count += 1
print(f"Retry {retry_count} after error: {str(e)}")
if retry_count < max_retries:
import time
time.sleep(1) # Add delay between retries
if not response and last_error:
raise last_error
# Extract response text and handle potential None
if not response or not hasattr(response, 'text'):
return "I apologize, but I couldn't generate a response. Please try again."
response_text = response.text.strip()
if not response_text:
return "I apologize, but I received an empty response. Please try again."
# Extract and execute any database commands
try:
db_command = self._extract_db_command(response_text)
if db_command:
db_result = self._execute_db_command(db_command)
response_text = response_text.replace(
f"[DB_COMMAND]{json.dumps(db_command)}[/DB_COMMAND]",
f"\n{db_result}\n"
)
except Exception as e:
print(f"Error processing database command: {str(e)}")
# Continue with the response even if DB command fails
# Update conversation context
self.conversation_context.append({"role": "user", "content": message})
self.conversation_context.append({"role": "assistant", "content": response_text})
# Trim context if needed
if len(self.conversation_context) > self.context_window * 2:
self.conversation_context = self.conversation_context[-self.context_window * 2:]
return response_text.strip()
except Exception as e:
print(f"Error in get_response: {str(e)}")
# Reset conversation context on critical errors
self.conversation_context = []
return "I apologize, but I'm having trouble processing that. Please try asking about donations, reports, or categories."