|
from flask import Flask, render_template, request, session, redirect, url_for, jsonify, Response , send_file |
|
import requests |
|
import numpy as np |
|
import os |
|
import io |
|
from datetime import datetime |
|
import json |
|
from pathlib import Path |
|
import re |
|
import uuid |
|
import gc |
|
|
|
from functools import wraps |
|
from werkzeug.security import generate_password_hash, check_password_hash |
|
from sqlalchemy import create_engine, Column, Integer, String, DateTime, Boolean, Float, Text |
|
from sqlalchemy import ForeignKey |
|
from sqlalchemy.orm import relationship |
|
from sqlalchemy.ext.declarative import declarative_base |
|
from sqlalchemy.orm import sessionmaker |
|
import secrets |
|
|
|
|
|
import asyncio |
|
import edge_tts |
|
|
|
|
|
import tempfile |
|
|
|
from functools import lru_cache |
|
import hashlib |
|
from dataclasses import dataclass |
|
from typing import Dict, Any, Optional |
|
|
|
|
|
@dataclass |
|
class MessageRequest: |
|
message: str |
|
user_context: Dict[str, Any] |
|
session_id: Optional[str] = None |
|
|
|
@dataclass |
|
class ChatMessageRequest: |
|
message: str |
|
user_context: Dict[str, Any] |
|
session_id: Optional[str] = None |
|
|
|
|
|
app = Flask(__name__) |
|
app.secret_key = os.getenv('SECRET_KEY', os.getenv('FLASK_SECRET_KEY', 'mental_health_app')) |
|
app.config["SESSION_COOKIE_NAME"] = "mental_health_app_session" |
|
app.config["SESSION_COOKIE_HTTPONLY"] = True |
|
app.config["SESSION_COOKIE_SECURE"] = False |
|
app.config["SESSION_COOKIE_SAMESITE"] = "Lax" |
|
app.config["SESSION_PERMANENT"] = False |
|
app.config["PERMANENT_SESSION_LIFETIME"] = 3600 |
|
|
|
|
|
BACKEND_URL = os.getenv('BACKEND_URL', 'http://localhost:8000') |
|
|
|
|
|
|
|
def extract_topics(text): |
|
"""Extract mental health related topics from text.""" |
|
topics = [] |
|
topic_patterns = { |
|
"anxiety": r"\b(anxious|anxiety|worried|worry|nervous)\b", |
|
"depression": r"\b(depress|sad|hopeless|down|blue)\b", |
|
"stress": r"\b(stress|overwhelm|pressure|burden)\b", |
|
"sleep": r"\b(sleep|insomnia|tired|fatigue|rest)\b", |
|
"therapy": r"\b(therapy|therapist|counseling|treatment)\b", |
|
"medication": r"\b(medication|medicine|pills|prescription)\b", |
|
"coping": r"\b(cope|coping|manage|deal|handle)\b", |
|
"relationships": r"\b(relationship|family|friend|partner|social)\b", |
|
"work": r"\b(work|job|career|workplace|boss)\b", |
|
"school": r"\b(school|test|exam|study|fail|grade)\b", |
|
"self-care": r"\b(self-care|exercise|meditation|mindfulness)\b" |
|
} |
|
|
|
text_lower = text.lower() |
|
for topic, pattern in topic_patterns.items(): |
|
if re.search(pattern, text_lower): |
|
topics.append(topic) |
|
|
|
return topics |
|
|
|
|
|
|
|
whisper_model = None |
|
|
|
|
|
if os.environ.get('SKIP_AI_MODELS') == '1' or os.environ.get('MEMORY_MODE') == 'free_tier': |
|
print("🔧 Skipping Whisper model loading - running in free tier mode") |
|
whisper_model = None |
|
else: |
|
try: |
|
import openai_whisper as whisper |
|
if hasattr(whisper, 'load_model'): |
|
|
|
whisper_model = whisper.load_model("tiny", device="cpu", download_root="/tmp") |
|
print("✅ OpenAI Whisper model loaded successfully (tiny, CPU-optimized)") |
|
else: |
|
print("⚠️ OpenAI Whisper load_model not available") |
|
whisper_model = None |
|
except ImportError: |
|
try: |
|
import whisper |
|
if hasattr(whisper, 'load_model'): |
|
whisper_model = whisper.load_model("tiny", device="cpu", download_root="/tmp") |
|
print("✅ Whisper model loaded successfully (tiny, CPU-optimized)") |
|
else: |
|
print("⚠️ Whisper load_model not available, using fallback") |
|
whisper_model = None |
|
except Exception as e: |
|
print(f"⚠️ Whisper not available: {e}") |
|
whisper_model = None |
|
|
|
|
|
tts_cache = {} |
|
|
|
@lru_cache(maxsize=100) |
|
def get_cached_tts(text_hash): |
|
"""Cache TTS results to avoid regenerating same text""" |
|
return tts_cache.get(text_hash) |
|
|
|
@app.route('/transcribe', methods=['POST']) |
|
def transcribe(): |
|
try: |
|
|
|
if not whisper_model: |
|
return jsonify({ |
|
'error': 'Voice transcription is not available in this deployment mode. Please type your message instead.', |
|
'transcript': '' |
|
}), 503 |
|
|
|
if 'audio' not in request.files: |
|
return jsonify({'error': 'No audio file provided'}), 400 |
|
|
|
audio_file = request.files['audio'] |
|
if audio_file.filename == '': |
|
return jsonify({'error': 'No audio file selected'}), 400 |
|
|
|
audio_data = audio_file.read() |
|
print(f"Received audio file: {audio_file.filename}, size: {len(audio_data)} bytes") |
|
|
|
|
|
with tempfile.NamedTemporaryFile(delete=False, suffix='.webm') as temp_file: |
|
temp_file.write(audio_data) |
|
temp_file.flush() |
|
print(f"Saved temp file: {temp_file.name}") |
|
|
|
try: |
|
|
|
if whisper_model: |
|
print("Starting fast transcription...") |
|
|
|
|
|
result = whisper_model.transcribe( |
|
temp_file.name, |
|
language='en', |
|
task='transcribe', |
|
temperature=0.0, |
|
no_speech_threshold=0.3, |
|
logprob_threshold=-1.0, |
|
compression_ratio_threshold=2.4, |
|
fp16=False, |
|
verbose=False, |
|
word_timestamps=False |
|
) |
|
|
|
transcript = result['text'].strip() |
|
print(f"Fast transcription result: '{transcript}'") |
|
|
|
|
|
os.unlink(temp_file.name) |
|
|
|
|
|
del result |
|
gc.collect() |
|
|
|
if not transcript or len(transcript) < 2: |
|
return jsonify({ |
|
'transcript': '', |
|
'error': 'No speech detected. Please speak clearly and hold the button longer.' |
|
}) |
|
|
|
return jsonify({'transcript': transcript}) |
|
else: |
|
return jsonify({'error': 'Whisper model not available'}), 500 |
|
|
|
except Exception as e: |
|
print(f"Transcription error: {e}") |
|
try: |
|
os.unlink(temp_file.name) |
|
except: |
|
pass |
|
return jsonify({'error': f'Transcription failed: {str(e)}'}), 500 |
|
|
|
except Exception as e: |
|
print(f"General transcription error: {e}") |
|
return jsonify({'error': str(e)}), 500 |
|
|
|
|
|
@app.route('/generate-speech', methods=['POST']) |
|
def generate_speech(): |
|
try: |
|
data = request.get_json() |
|
text = data.get('text', '') |
|
voice = data.get('voice', 'en-US-AriaNeural') |
|
|
|
if not text: |
|
return jsonify({'error': 'No text provided'}), 400 |
|
|
|
clean_text = clean_text_for_tts(text) |
|
|
|
|
|
if not clean_text or len(clean_text.strip()) < 3: |
|
return jsonify({'error': 'Text too short after cleaning'}), 400 |
|
|
|
print(f"Generating TTS for: '{clean_text[:100]}...' with voice: {voice}") |
|
|
|
|
|
cache_key = f"edge_{voice}_{hashlib.md5(clean_text.encode()).hexdigest()}" |
|
if cache_key in tts_cache: |
|
print("Using cached TTS") |
|
return send_file( |
|
io.BytesIO(tts_cache[cache_key]), |
|
mimetype='audio/mpeg', |
|
as_attachment=False |
|
) |
|
|
|
|
|
loop = asyncio.new_event_loop() |
|
asyncio.set_event_loop(loop) |
|
|
|
try: |
|
audio_data = loop.run_until_complete(generate_edge_tts(clean_text, voice)) |
|
|
|
if not audio_data or len(audio_data) < 100: |
|
raise Exception(f"No audio generated. Voice: {voice}, Text length: {len(clean_text)}") |
|
|
|
|
|
tts_cache[cache_key] = audio_data |
|
|
|
print(f"Edge TTS generated successfully, audio size: {len(audio_data)} bytes") |
|
|
|
return send_file( |
|
io.BytesIO(audio_data), |
|
mimetype='audio/mpeg', |
|
as_attachment=False |
|
) |
|
|
|
finally: |
|
loop.close() |
|
|
|
except Exception as e: |
|
print(f"Edge TTS error: {e}") |
|
|
|
try: |
|
fallback_text = "I apologize, but I'm having trouble with voice generation right now." |
|
loop = asyncio.new_event_loop() |
|
asyncio.set_event_loop(loop) |
|
|
|
try: |
|
audio_data = loop.run_until_complete(generate_edge_tts(fallback_text, 'en-US-AriaNeural')) |
|
if audio_data and len(audio_data) > 100: |
|
return send_file( |
|
io.BytesIO(audio_data), |
|
mimetype='audio/mpeg', |
|
as_attachment=False |
|
) |
|
finally: |
|
loop.close() |
|
except: |
|
pass |
|
|
|
return jsonify({'error': f'TTS generation failed: {str(e)}'}), 500 |
|
|
|
async def generate_edge_tts(text, voice): |
|
"""Generate speech using Edge TTS with speed control""" |
|
try: |
|
print(f"Edge TTS - Voice: {voice}, Text: '{text[:50]}...'") |
|
|
|
|
|
if not voice.startswith('en-'): |
|
voice = 'en-US-AriaNeural' |
|
|
|
|
|
communicate = edge_tts.Communicate( |
|
text, |
|
voice, |
|
rate="+15%", |
|
|
|
|
|
) |
|
|
|
audio_data = b"" |
|
async for chunk in communicate.stream(): |
|
if chunk["type"] == "audio": |
|
audio_data += chunk["data"] |
|
|
|
print(f"Generated {len(audio_data)} bytes of audio at +40% speed") |
|
return audio_data |
|
|
|
except Exception as e: |
|
print(f"Error in generate_edge_tts: {e}") |
|
raise e |
|
|
|
def clean_text_for_tts(text): |
|
"""Enhanced text cleaning for TTS""" |
|
if not text: |
|
return '' |
|
|
|
|
|
|
|
|
|
text = text.replace('```', '') |
|
text = text.replace('`', '') |
|
text = text.replace('**', '') |
|
text = text.replace('*', '') |
|
text = text.replace('__', '') |
|
text = text.replace('_', '') |
|
text = text.replace('#', '') |
|
|
|
|
|
text = text.replace('- ', '. ') |
|
text = text.replace('+ ', '. ') |
|
text = text.replace('* ', '. ') |
|
|
|
|
|
import re |
|
text = re.sub(r'^\s*\d+\.\s+', '. ', text, flags=re.MULTILINE) |
|
|
|
|
|
text = re.sub(r'\[([^\]]+)\]\([^)]+\)', r'\1', text) |
|
|
|
|
|
text = re.sub(r'\n{2,}', '. ', text) |
|
text = text.replace('\n', ' ') |
|
|
|
|
|
text = re.sub(r'\s+', ' ', text) |
|
|
|
|
|
text = text.strip() |
|
if text and not text.endswith(('.', '!', '?')): |
|
text += '.' |
|
|
|
|
|
if len(text) > 1000: |
|
|
|
sentences = text.split('. ') |
|
truncated = '' |
|
for sentence in sentences: |
|
if len(truncated + sentence + '. ') <= 950: |
|
truncated += sentence + '. ' |
|
else: |
|
break |
|
if truncated: |
|
text = truncated.strip() |
|
else: |
|
text = text[:950] + '...' |
|
|
|
return text |
|
|
|
|
|
|
|
|
|
from sqlalchemy.orm import declarative_base |
|
Base = declarative_base() |
|
|
|
|
|
database_url = os.getenv('DATABASE_URL', os.getenv('SUPABASE_DB_URI', 'sqlite:///mental_health_app.db')) |
|
print(f"🔗 Connecting to database: {database_url.split('://')[0]}://...") |
|
|
|
engine = create_engine(database_url) |
|
DBSession = sessionmaker(bind=engine) |
|
|
|
|
|
class User(Base): |
|
__tablename__ = 'users' |
|
|
|
id = Column(Integer, primary_key=True) |
|
username = Column(String(80), unique=True, nullable=False) |
|
email = Column(String(120), unique=True, nullable=False) |
|
password_hash = Column(String(255), nullable=False) |
|
full_name = Column(String(100)) |
|
created_at = Column(DateTime, default=datetime.utcnow) |
|
last_login = Column(DateTime) |
|
is_active = Column(Boolean, default=True) |
|
has_completed_initial_survey = Column(Boolean, default=False) |
|
initial_survey_date = Column(DateTime) |
|
|
|
def set_password(self, password): |
|
self.password_hash = generate_password_hash(password) |
|
|
|
def check_password(self, password): |
|
return check_password_hash(self.password_hash, password) |
|
|
|
|
|
from sqlalchemy import ForeignKey |
|
from sqlalchemy.orm import relationship |
|
|
|
class UserAssessment(Base): |
|
__tablename__ = 'user_assessments' |
|
|
|
id = Column(Integer, primary_key=True) |
|
user_id = Column(Integer, ForeignKey('users.id'), nullable=False) |
|
assessment_type = Column(String(50), default='comprehensive') |
|
basic_info = Column(Text) |
|
questionnaire_data = Column(Text) |
|
assessment_result = Column(Text) |
|
created_at = Column(DateTime, default=datetime.utcnow) |
|
is_active = Column(Boolean, default=True) |
|
|
|
class ConversationHistory(Base): |
|
__tablename__ = 'conversation_history' |
|
|
|
id = Column(Integer, primary_key=True) |
|
user_id = Column(Integer, ForeignKey('users.id'), nullable=False) |
|
session_id = Column(String(100)) |
|
message = Column(Text, nullable=False) |
|
response = Column(Text, nullable=False) |
|
agent_name = Column(String(100)) |
|
timestamp = Column(DateTime, default=datetime.utcnow) |
|
|
|
|
|
|
|
|
|
Base.metadata.create_all(engine) |
|
|
|
|
|
def login_required(f): |
|
@wraps(f) |
|
def decorated_function(*args, **kwargs): |
|
if 'user_id' not in session: |
|
return redirect(url_for('login', next=request.url)) |
|
return f(*args, **kwargs) |
|
return decorated_function |
|
|
|
|
|
@app.before_request |
|
def initialize_session(): |
|
if 'user_data' not in session: |
|
session['user_data'] = { |
|
"name": "Guest", |
|
"age": "", |
|
"gender": "", |
|
"emotion": "neutral/unsure", |
|
"score": None, |
|
"result": "Unknown" |
|
} |
|
|
|
|
|
|
|
|
|
@app.route("/") |
|
def home(): |
|
return render_template("home.html") |
|
|
|
|
|
@app.route("/health") |
|
def health(): |
|
"""Health check endpoint for monitoring and load balancers""" |
|
try: |
|
|
|
from sqlalchemy import text |
|
db_session = DBSession() |
|
|
|
db_session.execute(text("SELECT 1")) |
|
db_session.close() |
|
|
|
return jsonify({ |
|
"status": "healthy", |
|
"service": "Mental Health Chatbot Flask App", |
|
"timestamp": datetime.utcnow().isoformat(), |
|
"version": "1.0.0" |
|
}), 200 |
|
except Exception as e: |
|
return jsonify({ |
|
"status": "unhealthy", |
|
"service": "Mental Health Chatbot Flask App", |
|
"error": str(e), |
|
"timestamp": datetime.utcnow().isoformat() |
|
}), 503 |
|
|
|
|
|
|
|
@app.route('/login', methods=['GET', 'POST']) |
|
def login(): |
|
if request.method == 'POST': |
|
username = request.form.get('username') |
|
password = request.form.get('password') |
|
remember = request.form.get('remember') |
|
|
|
db_session = DBSession() |
|
|
|
user = db_session.query(User).filter( |
|
(User.username == username) | (User.email == username) |
|
).first() |
|
|
|
if user and user.check_password(password): |
|
user.last_login = datetime.utcnow() |
|
db_session.commit() |
|
|
|
|
|
session['user_id'] = user.id |
|
session['username'] = user.username |
|
|
|
|
|
latest_assessment = db_session.query(UserAssessment).filter_by( |
|
user_id=user.id, |
|
is_active=True |
|
).order_by(UserAssessment.created_at.desc()).first() |
|
|
|
if latest_assessment: |
|
|
|
session['assessment_data'] = { |
|
'basic_info': json.loads(latest_assessment.basic_info), |
|
'questionnaire_data': json.loads(latest_assessment.questionnaire_data), |
|
'assessment_result': json.loads(latest_assessment.assessment_result), |
|
'completed_date': latest_assessment.created_at.isoformat() |
|
} |
|
|
|
basic_info = json.loads(latest_assessment.basic_info) |
|
assessment_result = json.loads(latest_assessment.assessment_result) |
|
|
|
session['user_data'] = { |
|
'name': user.full_name or basic_info.get('name', ''), |
|
'age': basic_info.get('age', ''), |
|
'gender': basic_info.get('gender', ''), |
|
'location': basic_info.get('location', ''), |
|
'emotion': basic_info.get('emotion', 'neutral'), |
|
'has_completed_survey': True, |
|
'result': assessment_result.get('overall_status', 'Assessment Complete'), |
|
'assessment_date': latest_assessment.created_at.strftime('%Y-%m-%d') |
|
} |
|
|
|
print(f"✅ Loaded assessment data for user {user.username}") |
|
else: |
|
|
|
session['user_data'] = { |
|
'name': user.full_name, |
|
'has_completed_survey': False, |
|
'result': 'No Assessment' |
|
} |
|
print(f"ℹ️ No previous assessment found for user {user.username}") |
|
|
|
if remember: |
|
session.permanent = True |
|
|
|
db_session.close() |
|
return redirect(url_for('user_dashboard')) |
|
else: |
|
db_session.close() |
|
return render_template('home.html', |
|
login_error='Invalid username or password', |
|
show_login_modal=True) |
|
|
|
return render_template('home.html', show_login_modal=True) |
|
|
|
|
|
@app.route('/signup', methods=['GET', 'POST']) |
|
def signup(): |
|
if request.method == 'POST': |
|
full_name = request.form.get('full_name') |
|
username = request.form.get('username') |
|
email = request.form.get('email') |
|
password = request.form.get('password') |
|
confirm_password = request.form.get('confirm_password') |
|
|
|
|
|
if password != confirm_password: |
|
return render_template('home.html', |
|
signup_error='Passwords do not match', |
|
show_signup_modal=True) |
|
|
|
db_session = DBSession() |
|
|
|
|
|
if not re.match(r'^[a-zA-Z0-9_]{3,20}$', username): |
|
db_session.close() |
|
return render_template('home.html', |
|
signup_error='Invalid username format', |
|
show_signup_modal=True) |
|
|
|
|
|
existing_user = db_session.query(User).filter( |
|
(User.username == username) | (User.email == email) |
|
).first() |
|
|
|
if existing_user: |
|
db_session.close() |
|
|
|
if existing_user.username == username and existing_user.email == email: |
|
error_msg = 'Both username and email are already registered' |
|
elif existing_user.username == username: |
|
error_msg = f'Username "{username}" is already taken' |
|
else: |
|
error_msg = f'Email "{email}" is already registered' |
|
|
|
return render_template('home.html', |
|
signup_error=error_msg, |
|
show_signup_modal=True) |
|
|
|
|
|
new_user = User( |
|
username=username, |
|
email=email, |
|
full_name=full_name |
|
) |
|
new_user.set_password(password) |
|
|
|
db_session.add(new_user) |
|
db_session.commit() |
|
|
|
|
|
session['user_id'] = new_user.id |
|
session['username'] = new_user.username |
|
session['user_data'] = { |
|
'name': new_user.full_name, |
|
'has_completed_survey': False |
|
} |
|
|
|
db_session.close() |
|
|
|
|
|
return redirect(url_for('user_dashboard')) |
|
|
|
return render_template('home.html', show_signup_modal=True) |
|
|
|
|
|
@app.route('/user_dashboard') |
|
@login_required |
|
def user_dashboard(): |
|
"""User dashboard""" |
|
user_data = session.get('user_data', {}) |
|
|
|
|
|
return render_template('user_dashboard.html', user_data=user_data) |
|
|
|
|
|
|
|
|
|
|
|
@app.route('/assessment') |
|
@login_required |
|
def assessment(): |
|
"""Show the assessment form with step management""" |
|
|
|
if 'assessment_data' not in session: |
|
session['assessment_data'] = {} |
|
session['current_step'] = 1 |
|
|
|
current_step = session.get('current_step', 1) |
|
return render_template('assessment.html', current_step=current_step, |
|
assessment_data=session.get('assessment_data', {})) |
|
|
|
|
|
@app.route('/assessment/submit', methods=['POST']) |
|
def submit_assessment(): |
|
try: |
|
|
|
form_data = request.form.to_dict() |
|
|
|
|
|
basic_info = { |
|
'name': form_data.get('Name', ''), |
|
'age': form_data.get('Age', ''), |
|
'gender': form_data.get('Sex', ''), |
|
'location': form_data.get('Location', ''), |
|
'days_indoors': form_data.get('days_indoors', ''), |
|
'emotion': form_data.get('Emotion', ''), |
|
'history_of_mental_illness': form_data.get('history_of_mental_illness', ''), |
|
'treatment': form_data.get('treatment', '') |
|
} |
|
|
|
|
|
questionnaire_data = {} |
|
|
|
|
|
phq9_scores = [] |
|
for i in range(1, 10): |
|
score = int(form_data.get(f'PHQ9_{i}', 0)) |
|
phq9_scores.append(score) |
|
questionnaire_data['PHQ9'] = phq9_scores |
|
questionnaire_data['PHQ9_total'] = sum(phq9_scores) |
|
|
|
|
|
gad7_scores = [] |
|
for i in range(1, 8): |
|
score = int(form_data.get(f'GAD7_{i}', 0)) |
|
gad7_scores.append(score) |
|
questionnaire_data['GAD7'] = gad7_scores |
|
questionnaire_data['GAD7_total'] = sum(gad7_scores) |
|
|
|
|
|
dast_scores = [] |
|
for i in range(1, 11): |
|
response = form_data.get(f'DAST_{i}', 'No') |
|
score = 1 if response == 'Yes' else 0 |
|
dast_scores.append(score) |
|
questionnaire_data['DAST'] = dast_scores |
|
questionnaire_data['DAST_total'] = sum(dast_scores) |
|
|
|
|
|
audit_scores = [] |
|
audit_mapping = { |
|
'Never': 0, 'Monthly or less': 1, '2 to 4 times a month': 2, |
|
'2 to 3 times a week': 3, '4 or more times a week': 4, |
|
'1 or 2': 0, '3 or 4': 1, '5 or 6': 2, '7, 8, or 9': 3, '10 or more': 4, |
|
'Less than monthly': 1, 'Monthly': 2, 'Weekly': 3, 'Daily or almost daily': 4, |
|
'No': 0, 'Yes, but not in the last year': 2, 'Yes, during the last year': 4 |
|
} |
|
|
|
for i in range(1, 11): |
|
response = form_data.get(f'AUDIT_{i}', 'Never') |
|
score = audit_mapping.get(response, 0) |
|
audit_scores.append(score) |
|
questionnaire_data['AUDIT'] = audit_scores |
|
questionnaire_data['AUDIT_total'] = sum(audit_scores) |
|
|
|
|
|
bipolar_scores = [] |
|
for i in range(1, 12): |
|
response = form_data.get(f'BIPOLAR_{i}', 'No') |
|
score = 1 if response == 'Yes' else 0 |
|
bipolar_scores.append(score) |
|
questionnaire_data['BIPOLAR'] = bipolar_scores |
|
questionnaire_data['BIPOLAR_total'] = sum(bipolar_scores) |
|
|
|
|
|
assessment_result = generate_assessment_interpretation(questionnaire_data) |
|
|
|
|
|
session['assessment_data'] = { |
|
'basic_info': basic_info, |
|
'questionnaire_data': questionnaire_data, |
|
'assessment_result': assessment_result, |
|
'completed_date': datetime.now().isoformat() |
|
} |
|
|
|
|
|
if session.get('user_id'): |
|
db_session = DBSession() |
|
user = db_session.query(User).filter_by(id=session['user_id']).first() |
|
|
|
if user: |
|
|
|
db_session.query(UserAssessment).filter_by( |
|
user_id=user.id, |
|
is_active=True |
|
).update({'is_active': False}) |
|
|
|
|
|
new_assessment = UserAssessment( |
|
user_id=user.id, |
|
assessment_type='comprehensive', |
|
basic_info=json.dumps(basic_info), |
|
questionnaire_data=json.dumps(questionnaire_data), |
|
assessment_result=json.dumps(assessment_result), |
|
is_active=True |
|
) |
|
|
|
|
|
user.has_completed_initial_survey = True |
|
user.initial_survey_date = datetime.utcnow() |
|
|
|
db_session.add(new_assessment) |
|
db_session.commit() |
|
|
|
print(f"✅ Saved assessment to database for user {user.username}") |
|
|
|
db_session.close() |
|
|
|
|
|
session['user_data'].update({ |
|
'age': basic_info['age'], |
|
'gender': basic_info['gender'], |
|
'location': basic_info['location'], |
|
'emotion': basic_info['emotion'], |
|
'has_completed_survey': True, |
|
'result': assessment_result.get('overall_status', 'Assessment Complete') |
|
}) |
|
|
|
session.modified = True |
|
|
|
|
|
return redirect(url_for('chatbot', assessment_complete=True)) |
|
|
|
except Exception as e: |
|
print(f"Error submitting assessment: {e}") |
|
import traceback |
|
traceback.print_exc() |
|
return redirect(url_for('assessment')) |
|
|
|
def generate_assessment_interpretation(data): |
|
"""Generate interpretation of assessment scores""" |
|
result = { |
|
'detailed_scores': {}, |
|
'recommendations': [], |
|
'emergency_info': None, |
|
'overall_status': 'Normal' |
|
} |
|
|
|
|
|
phq9_total = data.get('PHQ9_total', 0) |
|
if phq9_total <= 4: |
|
phq9_level = 'Minimal depression' |
|
elif phq9_total <= 9: |
|
phq9_level = 'Mild depression' |
|
elif phq9_total <= 14: |
|
phq9_level = 'Moderate depression' |
|
elif phq9_total <= 19: |
|
phq9_level = 'Moderately severe depression' |
|
else: |
|
phq9_level = 'Severe depression' |
|
|
|
result['detailed_scores']['PHQ9'] = { |
|
'score': phq9_total, |
|
'level': phq9_level, |
|
'max_score': 27 |
|
} |
|
|
|
|
|
gad7_total = data.get('GAD7_total', 0) |
|
if gad7_total <= 4: |
|
gad7_level = 'Minimal anxiety' |
|
elif gad7_total <= 9: |
|
gad7_level = 'Mild anxiety' |
|
elif gad7_total <= 14: |
|
gad7_level = 'Moderate anxiety' |
|
else: |
|
gad7_level = 'Severe anxiety' |
|
|
|
result['detailed_scores']['GAD7'] = { |
|
'score': gad7_total, |
|
'level': gad7_level, |
|
'max_score': 21 |
|
} |
|
|
|
|
|
dast_total = data.get('DAST_total', 0) |
|
if dast_total == 0: |
|
dast_level = 'No drug problems reported' |
|
elif dast_total <= 2: |
|
dast_level = 'Low level' |
|
elif dast_total <= 5: |
|
dast_level = 'Moderate level' |
|
else: |
|
dast_level = 'Substantial to severe level' |
|
|
|
result['detailed_scores']['DAST'] = { |
|
'score': dast_total, |
|
'level': dast_level, |
|
'max_score': 10 |
|
} |
|
|
|
|
|
audit_total = data.get('AUDIT_total', 0) |
|
if audit_total <= 7: |
|
audit_level = 'Low risk' |
|
elif audit_total <= 15: |
|
audit_level = 'Hazardous drinking' |
|
elif audit_total <= 19: |
|
audit_level = 'Harmful drinking' |
|
else: |
|
audit_level = 'Possible alcohol dependence' |
|
|
|
result['detailed_scores']['AUDIT'] = { |
|
'score': audit_total, |
|
'level': audit_level, |
|
'max_score': 40 |
|
} |
|
|
|
|
|
bipolar_total = data.get('BIPOLAR_total', 0) |
|
if bipolar_total <= 6: |
|
bipolar_level = 'Unlikely' |
|
else: |
|
bipolar_level = 'Possible bipolar symptoms' |
|
|
|
result['detailed_scores']['BIPOLAR'] = { |
|
'score': bipolar_total, |
|
'level': bipolar_level, |
|
'max_score': 11 |
|
} |
|
|
|
|
|
high_scores = [] |
|
if phq9_total >= 10: |
|
high_scores.append('depression') |
|
if gad7_total >= 10: |
|
high_scores.append('anxiety') |
|
if dast_total >= 3: |
|
high_scores.append('substance use') |
|
if audit_total >= 8: |
|
high_scores.append('alcohol use') |
|
if bipolar_total >= 7: |
|
high_scores.append('bipolar symptoms') |
|
|
|
if high_scores: |
|
result['overall_status'] = 'Elevated concerns detected' |
|
result['recommendations'].extend([ |
|
'Consider speaking with a mental health professional', |
|
'These scores suggest you may benefit from professional support', |
|
'Remember that early intervention can be very helpful' |
|
]) |
|
else: |
|
result['overall_status'] = 'No significant concerns detected' |
|
result['recommendations'].extend([ |
|
'Continue maintaining good mental health practices', |
|
'Stay connected with supportive people in your life', |
|
'Don\'t hesitate to seek help if you notice changes in your mood or behavior' |
|
]) |
|
|
|
|
|
if data.get('PHQ9', [0]*9)[8] >= 1: |
|
result['emergency_info'] = { |
|
'level': 'high', |
|
'message': 'You indicated thoughts of self-harm. Please reach out for immediate support.', |
|
'resources': [ |
|
'Emergency: Call 112 (Bhutan Emergency)', |
|
'Mental Health Helpline: Contact your local health center', |
|
'Crisis support is available 24/7' |
|
] |
|
} |
|
result['overall_status'] = 'Immediate support recommended' |
|
|
|
return result |
|
|
|
def process_complete_assessment(): |
|
"""Process the complete assessment and calculate scores""" |
|
try: |
|
assessment_data = session.get('assessment_data', {}) |
|
|
|
|
|
try: |
|
from crew_ai.questionnaire import ( |
|
calculate_phq9_score, calculate_gad7_score, |
|
calculate_dast10_score, calculate_audit_score, |
|
calculate_bipolar_score, get_assessment_recommendations |
|
) |
|
use_advanced_scoring = True |
|
except ImportError: |
|
print("⚠️ Advanced scoring unavailable - using basic scoring") |
|
use_advanced_scoring = False |
|
|
|
|
|
scores = {} |
|
|
|
if use_advanced_scoring: |
|
|
|
phq9_responses = [int(assessment_data.get(f'PHQ9_{i}', 0)) for i in range(1, 10)] |
|
scores['phq9'] = calculate_phq9_score(phq9_responses) |
|
|
|
|
|
gad7_responses = [int(assessment_data.get(f'GAD7_{i}', 0)) for i in range(1, 8)] |
|
scores['gad7'] = calculate_gad7_score(gad7_responses) |
|
|
|
|
|
dast_responses = [] |
|
for i in range(1, 11): |
|
response = assessment_data.get(f'DAST_{i}', 'No') |
|
|
|
if i == 3: |
|
dast_responses.append(1 if response == 'No' else 0) |
|
else: |
|
dast_responses.append(1 if response == 'Yes' else 0) |
|
scores['dast10'] = calculate_dast10_score(dast_responses) |
|
else: |
|
|
|
|
|
phq9_responses = [int(assessment_data.get(f'PHQ9_{i}', 0)) for i in range(1, 10)] |
|
scores['phq9'] = { |
|
'score': sum(phq9_responses), |
|
'interpretation': 'Basic scoring - total: ' + str(sum(phq9_responses)), |
|
'severity': 'mild' if sum(phq9_responses) < 10 else 'moderate' if sum(phq9_responses) < 15 else 'severe' |
|
} |
|
|
|
|
|
gad7_responses = [int(assessment_data.get(f'GAD7_{i}', 0)) for i in range(1, 8)] |
|
scores['gad7'] = { |
|
'score': sum(gad7_responses), |
|
'interpretation': 'Basic scoring - total: ' + str(sum(gad7_responses)), |
|
'severity': 'mild' if sum(gad7_responses) < 10 else 'moderate' if sum(gad7_responses) < 15 else 'severe' |
|
} |
|
|
|
|
|
scores['dast10'] = { |
|
'score': 0, |
|
'interpretation': 'Basic scoring not available for substance use assessment', |
|
'severity': 'unknown' |
|
} |
|
|
|
if use_advanced_scoring: |
|
|
|
audit_responses = [] |
|
audit_scoring = { |
|
'AUDIT_1': {'Never': 0, 'Monthly or less': 1, '2 to 4 times a month': 2, '2 to 3 times a week': 3, '4 or more times a week': 4}, |
|
'AUDIT_2': {'1 or 2': 0, '3 or 4': 1, '5 or 6': 2, '7, 8, or 9': 3, '10 or more': 4}, |
|
'AUDIT_3': {'Never': 0, 'Less than monthly': 1, 'Monthly': 2, 'Weekly': 3, 'Daily or almost daily': 4}, |
|
'AUDIT_4': {'Never': 0, 'Less than monthly': 1, 'Monthly': 2, 'Weekly': 3, 'Daily or almost daily': 4}, |
|
'AUDIT_5': {'Never': 0, 'Less than monthly': 1, 'Monthly': 2, 'Weekly': 3, 'Daily or almost daily': 4}, |
|
'AUDIT_6': {'Never': 0, 'Less than monthly': 1, 'Monthly': 2, 'Weekly': 3, 'Daily or almost daily': 4}, |
|
'AUDIT_7': {'Never': 0, 'Less than monthly': 1, 'Monthly': 2, 'Weekly': 3, 'Daily or almost daily': 4}, |
|
'AUDIT_8': {'Never': 0, 'Less than monthly': 1, 'Monthly': 2, 'Weekly': 3, 'Daily or almost daily': 4}, |
|
'AUDIT_9': {'No': 0, 'Yes, but not in the last year': 2, 'Yes, during the last year': 4}, |
|
'AUDIT_10': {'No': 0, 'Yes, but not in the last year': 2, 'Yes, during the last year': 4} |
|
} |
|
|
|
for i in range(1, 11): |
|
field = f'AUDIT_{i}' |
|
response = assessment_data.get(field, '') |
|
score = audit_scoring.get(field, {}).get(response, 0) |
|
audit_responses.append(score) |
|
scores['audit'] = calculate_audit_score(audit_responses) |
|
|
|
|
|
bipolar_responses = [1 if assessment_data.get(f'BIPOLAR_{i}', 'No') == 'Yes' else 0 for i in range(1, 12)] |
|
scores['bipolar'] = calculate_bipolar_score(bipolar_responses) |
|
else: |
|
|
|
scores['audit'] = { |
|
'score': 0, |
|
'interpretation': 'Basic scoring not available for alcohol assessment', |
|
'severity': 'unknown' |
|
} |
|
|
|
|
|
scores['bipolar'] = { |
|
'score': 0, |
|
'interpretation': 'Basic scoring not available for bipolar assessment', |
|
'severity': 'unknown' |
|
} |
|
|
|
|
|
if use_advanced_scoring: |
|
recommendations = get_assessment_recommendations(scores) |
|
else: |
|
|
|
recommendations = { |
|
'overall_assessment': 'Basic assessment completed. Professional evaluation recommended for detailed analysis.', |
|
'next_steps': ['Consult with a mental health professional', 'Monitor your mental health regularly'], |
|
'resources': ['Contact local health services', 'Consider professional counseling'] |
|
} |
|
|
|
|
|
assessment_results = { |
|
'scores': scores, |
|
'recommendations': recommendations, |
|
'basic_info': { |
|
'name': assessment_data.get('Name', ''), |
|
'age': assessment_data.get('Age', ''), |
|
'gender': assessment_data.get('Sex', ''), |
|
'location': assessment_data.get('Location', ''), |
|
'emotion': assessment_data.get('Emotion', 'neutral') |
|
}, |
|
'timestamp': datetime.now().isoformat() |
|
} |
|
|
|
|
|
session['assessment_results'] = assessment_results |
|
session['user_data'] = { |
|
'name': assessment_data.get('Name', session.get('user_data', {}).get('name', '')), |
|
'age': assessment_data.get('Age', ''), |
|
'gender': assessment_data.get('Sex', ''), |
|
'emotion': assessment_data.get('Emotion', 'neutral'), |
|
'score': scores, |
|
'result': recommendations.get('overall_status', 'Completed'), |
|
'assessment_complete': True |
|
} |
|
|
|
|
|
session.pop('assessment_data', None) |
|
session.pop('current_step', None) |
|
session.modified = True |
|
|
|
|
|
return redirect(url_for('chatbot')) |
|
|
|
except Exception as e: |
|
print(f"Error processing assessment: {e}") |
|
return render_template('assessment.html', |
|
error="There was an error processing your assessment. Please try again.") |
|
|
|
|
|
@app.route('/chatbot') |
|
def chatbot(): |
|
"""Main chatbot interface""" |
|
user_data = session.get('user_data', {}) |
|
is_guest = session.get('is_guest', False) |
|
|
|
|
|
has_assessment = 'assessment_data' in session |
|
assessment_complete = request.args.get('assessment_complete', False) |
|
|
|
|
|
suggest_assessment = ( |
|
not is_guest and |
|
not has_assessment and |
|
not user_data.get('has_completed_survey', False) |
|
) |
|
|
|
|
|
if 'chat_session' not in session: |
|
session['chat_session'] = { |
|
'session_id': datetime.now().strftime('%Y%m%d_%H%M%S'), |
|
'user_name': user_data.get('name', 'Guest'), |
|
'messages': [], |
|
'topics': [] |
|
} |
|
print(f"Chatbot session: {session['chat_session']}") |
|
return render_template('ChatbotUI.html', |
|
user_data=user_data, |
|
is_guest=is_guest, |
|
suggest_assessment=suggest_assessment, |
|
assessment_complete=assessment_complete, |
|
has_assessment=has_assessment, |
|
chat_history=session['chat_session'].get('messages', [])) |
|
|
|
|
|
|
|
@app.route('/guest_access') |
|
def guest_access(): |
|
|
|
session['user_data'] = { |
|
'name': 'Guest', |
|
'age': '', |
|
'gender': '', |
|
'emotion': 'neutral/unsure', |
|
'score': None, |
|
'result': 'Unknown' |
|
} |
|
session['is_guest'] = True |
|
|
|
return redirect(url_for('chatbot')) |
|
|
|
|
|
@app.route('/logout') |
|
def logout(): |
|
session.clear() |
|
return redirect(url_for('home')) |
|
|
|
@app.route("/about") |
|
def about(): |
|
return render_template("aboutt.html") |
|
|
|
|
|
|
|
@app.route('/chat', methods=['POST']) |
|
def chat(): |
|
try: |
|
data = request.get_json() |
|
message = data.get('message', '') |
|
user_data = data.get('user_data', {}) |
|
|
|
|
|
session_user_data = session.get('user_data', {}) |
|
user_emotion = session_user_data.get('emotion', 'neutral/unsure') |
|
user_name = session_user_data.get('name', 'Guest') |
|
prediction_result = session_user_data.get('result', '') or 'Unknown' |
|
user_age = session_user_data.get('age', '') |
|
|
|
|
|
message_count = session.get('message_count', 0) + 1 |
|
session['message_count'] = message_count |
|
|
|
|
|
if 'chat_session' not in session: |
|
session['chat_session'] = { |
|
'session_id': datetime.now().strftime('%Y%m%d_%H%M%S'), |
|
'user_name': user_name, |
|
'messages': [], |
|
'topics': [] |
|
} |
|
|
|
|
|
user_topics = extract_topics(message) |
|
session['chat_session']['messages'].append({ |
|
'role': 'user', |
|
'content': message, |
|
'timestamp': datetime.now().isoformat(), |
|
'topics': user_topics |
|
}) |
|
|
|
|
|
user_context = { |
|
"emotion": user_emotion, |
|
"name": user_name, |
|
"mental_health_status": prediction_result, |
|
"age": user_age, |
|
"original_query": message, |
|
"message_count": message_count |
|
} |
|
|
|
|
|
if 'assessment_data' in session: |
|
assessment_data = session['assessment_data'] |
|
user_context.update({ |
|
"detailed_scores": assessment_data.get('assessment_result', {}).get('detailed_scores', {}), |
|
"recommendations": assessment_data.get('assessment_result', {}).get('recommendations', []), |
|
"assessment_type": "comprehensive", |
|
"emergency_info": assessment_data.get('assessment_result', {}).get('emergency_info') |
|
}) |
|
|
|
|
|
try: |
|
response = requests.post( |
|
f"{BACKEND_URL}/process_message", |
|
json={ |
|
"message": message, |
|
"user_context": user_context, |
|
"session_id": session['chat_session']['session_id'] |
|
}, |
|
timeout=30 |
|
) |
|
|
|
if response.status_code == 200: |
|
result = response.json() |
|
response_text = result.get("response", "I'm sorry, I couldn't process your request.") |
|
agent_name = result.get("agent", "Assistant") |
|
method_used = result.get("method", "unknown") |
|
|
|
print(f"✅ FastAPI Response - Agent: {agent_name}, Method: {method_used}") |
|
|
|
|
|
response_topics = extract_topics(response_text) |
|
session['chat_session']['messages'].append({ |
|
'role': 'assistant', |
|
'content': response_text, |
|
'timestamp': datetime.now().isoformat(), |
|
'agent': agent_name, |
|
'method': method_used, |
|
'topics': response_topics |
|
}) |
|
|
|
|
|
session.modified = True |
|
|
|
return jsonify({ |
|
"response": response_text, |
|
"agent": agent_name, |
|
"method": method_used |
|
}) |
|
else: |
|
print(f"FastAPI Backend error: {response.status_code} - {response.text}") |
|
return _flask_fallback_response(message, user_context) |
|
|
|
except requests.exceptions.RequestException as e: |
|
print(f"Backend connection error: {e}") |
|
return _flask_fallback_response(message, user_context) |
|
|
|
except Exception as e: |
|
print(f"Error in chat: {str(e)}") |
|
return jsonify({ |
|
"response": "I apologize, but I encountered an error. Please try again.", |
|
"agent": "Error Handler" |
|
}), 500 |
|
|
|
|
|
@app.route("/send_message", methods=["POST"]) |
|
def send_message(): |
|
try: |
|
user_query = request.form.get("message", "") |
|
|
|
|
|
user_data = session.get('user_data', {}) |
|
user_emotion = user_data.get('emotion', 'neutral/unsure') |
|
user_name = user_data.get('name', 'Guest') |
|
prediction_result = user_data.get('result', '') or 'Unknown' |
|
user_age = user_data.get('age', '') |
|
|
|
|
|
message_count = session.get('message_count', 0) + 1 |
|
session['message_count'] = message_count |
|
session.modified = True |
|
|
|
|
|
if 'chat_session' not in session: |
|
session['chat_session'] = { |
|
'session_id': datetime.now().strftime('%Y%m%d_%H%M%S'), |
|
'user_name': user_name, |
|
'messages': [], |
|
'topics': [] |
|
} |
|
|
|
|
|
user_topics = extract_topics(user_query) |
|
session['chat_session']['messages'].append({ |
|
'role': 'user', |
|
'content': user_query, |
|
'timestamp': datetime.now().isoformat(), |
|
'topics': user_topics |
|
}) |
|
|
|
|
|
user_context = { |
|
"emotion": user_emotion, |
|
"name": user_name, |
|
"mental_health_status": prediction_result, |
|
"age": user_age, |
|
"original_query": user_query, |
|
"message_count": message_count |
|
} |
|
|
|
|
|
response = requests.post( |
|
f"{BACKEND_URL}/process_message", |
|
json={ |
|
"message": user_query, |
|
"user_context": user_context, |
|
"session_id": session['chat_session']['session_id'] |
|
}, |
|
timeout=30 |
|
) |
|
|
|
if response.status_code == 200: |
|
result = response.json() |
|
response_text = result.get("response", "I'm sorry, I couldn't process your request.") |
|
agent_name = result.get("agent", "Assistant") |
|
method_used = result.get("method", "unknown") |
|
|
|
print(f"✅ Response - Agent: {agent_name}, Method: {method_used}") |
|
|
|
|
|
response_topics = extract_topics(response_text) |
|
session['chat_session']['messages'].append({ |
|
'role': 'assistant', |
|
'content': response_text, |
|
'timestamp': datetime.now().isoformat(), |
|
'agent': agent_name, |
|
'method': method_used, |
|
'topics': response_topics |
|
}) |
|
|
|
|
|
if len(session['chat_session']['messages']) % 5 == 0: |
|
save_chat_session_to_backend(session['chat_session']) |
|
|
|
return jsonify({ |
|
"response": response_text, |
|
"agent": agent_name, |
|
"method": method_used, |
|
"user_emotion": user_emotion, |
|
"mental_health_status": prediction_result, |
|
"message_count": message_count |
|
}) |
|
else: |
|
return _flask_fallback_response(user_query, user_context) |
|
|
|
except Exception as e: |
|
print(f"Error in send_message: {str(e)}") |
|
return _flask_fallback_response("", {}) |
|
|
|
|
|
|
|
|
|
|
|
@app.route('/delete_account', methods=['POST']) |
|
@login_required |
|
def delete_account(): |
|
"""Permanently delete user account and all associated data""" |
|
try: |
|
user_id = session.get('user_id') |
|
username = session.get('username') |
|
|
|
if not user_id: |
|
return jsonify({ |
|
'status': 'error', |
|
'message': 'User not found in session' |
|
}), 400 |
|
|
|
print(f"🗑️ Starting account deletion for user ID: {user_id}, username: {username}") |
|
|
|
|
|
try: |
|
response = requests.delete( |
|
f"{BACKEND_URL}/api/v1/delete/{user_id}", |
|
timeout=10 |
|
) |
|
|
|
if response.status_code == 200: |
|
print("✅ Successfully deleted user data from FastAPI backend") |
|
else: |
|
print(f"⚠️ FastAPI deletion failed: {response.status_code}") |
|
except Exception as backend_error: |
|
print(f"⚠️ Backend deletion error (continuing anyway): {backend_error}") |
|
|
|
|
|
db_session = DBSession() |
|
|
|
try: |
|
|
|
user = db_session.query(User).filter_by(id=user_id).first() |
|
|
|
if not user: |
|
db_session.close() |
|
return jsonify({ |
|
'status': 'error', |
|
'message': 'User not found in database' |
|
}), 404 |
|
|
|
|
|
print("🗑️ Deleting conversation history...") |
|
conversation_count = db_session.query(ConversationHistory).filter_by(user_id=user_id).count() |
|
db_session.query(ConversationHistory).filter_by(user_id=user_id).delete() |
|
print(f"✅ Deleted {conversation_count} conversation records") |
|
|
|
print("🗑️ Deleting user assessments...") |
|
assessment_count = db_session.query(UserAssessment).filter_by(user_id=user_id).count() |
|
db_session.query(UserAssessment).filter_by(user_id=user_id).delete() |
|
print(f"✅ Deleted {assessment_count} assessment records") |
|
|
|
|
|
print("🗑️ Deleting user account...") |
|
db_session.delete(user) |
|
|
|
|
|
db_session.commit() |
|
print(f"✅ Successfully deleted user account: {username}") |
|
|
|
except Exception as db_error: |
|
db_session.rollback() |
|
print(f"❌ Database deletion error: {db_error}") |
|
raise db_error |
|
finally: |
|
db_session.close() |
|
|
|
|
|
session.clear() |
|
|
|
return jsonify({ |
|
'status': 'success', |
|
'message': f'Account "{username}" has been permanently deleted', |
|
'redirect': '/' |
|
}) |
|
|
|
except Exception as e: |
|
print(f"❌ Critical error in delete_account: {e}") |
|
import traceback |
|
traceback.print_exc() |
|
|
|
return jsonify({ |
|
'status': 'error', |
|
'message': f'Failed to delete account: {str(e)}' |
|
}), 500 |
|
|
|
|
|
@app.route('/chat_message', methods=['POST']) |
|
def chat_message(): |
|
try: |
|
print("🎯 /chat_message endpoint hit!") |
|
data = request.get_json() |
|
print(f"📨 Received data: {data}") |
|
|
|
if not data: |
|
print("❌ No JSON data received") |
|
return jsonify({"error": "No data received"}), 400 |
|
|
|
|
|
message = data.get('message', '') |
|
user_data = data.get('user_data', {}) |
|
|
|
print(f"💬 Processing message: {message}") |
|
|
|
|
|
if not message: |
|
print("❌ Empty message") |
|
return jsonify({"error": "Empty message"}), 400 |
|
|
|
|
|
|
|
if message == "Test message from debug": |
|
print("⏭️ Skipping debug test message") |
|
return jsonify({ |
|
"response": "Debug test received - please type a real message.", |
|
"agent": "System", |
|
"method": "debug_skip" |
|
}) |
|
|
|
|
|
if 'chat_session' not in session: |
|
session['chat_session'] = { |
|
'session_id': str(uuid.uuid4()), |
|
'messages': [], |
|
'topics': [], |
|
'mood_history': [] |
|
} |
|
|
|
|
|
session['chat_session']['messages'].append({ |
|
'role': 'user', |
|
'content': message, |
|
'timestamp': datetime.now().isoformat() |
|
}) |
|
|
|
|
|
user_context = { |
|
'name': user_data.get('name', 'Guest'), |
|
'emotion': user_data.get('emotion', 'neutral'), |
|
'mental_health_status': user_data.get('result', 'Unknown'), |
|
'session_id': session['chat_session']['session_id'], |
|
'message_history': [msg['content'] for msg in session['chat_session']['messages'][-5:]] |
|
} |
|
|
|
|
|
if 'assessment_data' in session: |
|
assessment_data = session['assessment_data'] |
|
user_context.update({ |
|
"detailed_scores": assessment_data.get('assessment_result', {}).get('detailed_scores', {}), |
|
"recommendations": assessment_data.get('assessment_result', {}).get('recommendations', []), |
|
"assessment_type": "comprehensive", |
|
"emergency_info": assessment_data.get('assessment_result', {}).get('emergency_info') |
|
}) |
|
|
|
|
|
try: |
|
print("🚀 Connecting to FastAPI unified endpoint...") |
|
|
|
response = requests.post( |
|
f"{BACKEND_URL}/process_message", |
|
json={ |
|
"message": message, |
|
"user_context": user_context |
|
}, |
|
timeout=15 |
|
) |
|
|
|
if response.status_code == 200: |
|
result = response.json() |
|
response_text = result.get("response", "I'm here to support you.") |
|
agent_name = result.get("agent", "Assistant") |
|
method_used = result.get("method", "unified") |
|
|
|
print(f"✅ FastAPI Success - Agent: {agent_name}, Method: {method_used}") |
|
|
|
|
|
session['chat_session']['messages'].append({ |
|
'role': 'assistant', |
|
'content': response_text, |
|
'timestamp': datetime.now().isoformat(), |
|
'agent': agent_name, |
|
'method': method_used |
|
}) |
|
|
|
return jsonify({ |
|
"response": response_text, |
|
"agent": agent_name, |
|
"method": method_used |
|
}), 200 |
|
|
|
else: |
|
print(f"⚠️ FastAPI failed with status: {response.status_code}") |
|
return _flask_fallback_response(message, user_context) |
|
|
|
except Exception as api_error: |
|
print(f"⚠️ FastAPI connection failed: {api_error}") |
|
return _flask_fallback_response(message, user_context) |
|
|
|
except Exception as e: |
|
print(f"❌ Critical error in chat_message: {e}") |
|
import traceback |
|
traceback.print_exc() |
|
|
|
return jsonify({ |
|
"response": "I apologize, but I encountered an error. Please try again.", |
|
"agent": "Error Handler", |
|
"method": "error_fallback" |
|
}), 500 |
|
|
|
|
|
|
|
@app.route('/test_backend_connection') |
|
def test_backend_connection(): |
|
"""Test if FastAPI backend is available""" |
|
try: |
|
response = requests.get(f"{BACKEND_URL}/debug_systems", timeout=5) |
|
if response.status_code == 200: |
|
return jsonify({"status": "connected", "backend": "FastAPI"}) |
|
else: |
|
return jsonify({"status": "error", "message": f"Backend returned {response.status_code}"}), 500 |
|
except requests.exceptions.RequestException as e: |
|
return jsonify({"status": "error", "message": f"Backend not available: {str(e)}"}), 500 |
|
|
|
|
|
def _flask_fallback_response(message: str, user_context: dict): |
|
"""Generate intelligent fallback response when FastAPI is unavailable""" |
|
try: |
|
message_lower = message.lower() |
|
user_name = user_context.get('name', 'there') |
|
|
|
|
|
if any(word in message_lower for word in ['suicide', 'kill myself', 'want to die', 'hurt myself']): |
|
response_text = f"🆘 I'm very concerned about what you've shared, {user_name}. Please reach out for immediate help. In Bhutan: Emergency Services (112), National Mental Health Program (1717). Your life has value." |
|
agent_name = "Crisis Support Assistant" |
|
|
|
|
|
elif any(word in message_lower for word in ['sad', 'depressed', 'down', 'hopeless']): |
|
response_text = f"I understand you're feeling sad, {user_name}. These feelings are valid and you're not alone. Depression can feel overwhelming, but there are effective ways to manage it. Would you like to explore some coping strategies?" |
|
agent_name = "Mental Health Support Assistant" |
|
|
|
elif any(word in message_lower for word in ['anxious', 'worried', 'panic', 'nervous']): |
|
response_text = f"I hear that you're experiencing anxiety, {user_name}. These feelings can be very challenging, but there are proven techniques that can help. Would you like to try some breathing exercises?" |
|
agent_name = "Mental Health Support Assistant" |
|
|
|
elif any(word in message_lower for word in ['angry', 'frustrated', 'mad']): |
|
response_text = f"I understand you're feeling angry or frustrated, {user_name}. Anger is a normal emotion. What's been contributing to these feelings lately?" |
|
agent_name = "Mental Health Support Assistant" |
|
|
|
else: |
|
response_text = f"Thank you for sharing with me, {user_name}. I'm here to support you with your mental health concerns. While I'm experiencing some technical difficulties, I want you to know that your feelings matter and help is available." |
|
agent_name = "Local Mental Health Assistant" |
|
|
|
return jsonify({ |
|
"response": response_text, |
|
"agent": agent_name, |
|
"method": "flask_fallback" |
|
}) |
|
|
|
except Exception as e: |
|
print(f"Error in fallback response: {e}") |
|
return jsonify({ |
|
"response": "I'm here to support you, though I'm having some technical difficulties. For immediate mental health support in Bhutan, contact the National Mental Health Program at 1717.", |
|
"agent": "Emergency Fallback", |
|
"method": "emergency_fallback" |
|
}) |
|
|
|
|
|
@app.get("/debug_rag_status") |
|
async def debug_rag_status(): |
|
"""Debug RAG system status""" |
|
try: |
|
rag = app.state.rag |
|
|
|
|
|
test_result = rag.process_query( |
|
"test query about mental health", |
|
user_emotion="neutral", |
|
mental_health_status="Unknown" |
|
) |
|
|
|
return { |
|
"rag_available": True, |
|
"knowledge_folder_exists": Path("knowledge").exists(), |
|
"pdf_files": list(Path("knowledge").glob("*.pdf")) if Path("knowledge").exists() else [], |
|
"test_confidence": test_result.get("confidence", 0.0), |
|
"test_response_preview": test_result.get("response", "")[:100] |
|
} |
|
except Exception as e: |
|
return {"rag_available": False, "error": str(e)} |
|
|
|
def save_chat_session_to_backend(chat_session): |
|
"""Helper function to save chat session to backend""" |
|
try: |
|
|
|
session_data = { |
|
"session_id": chat_session['session_id'], |
|
"user_name": chat_session['user_name'], |
|
"messages": chat_session['messages'], |
|
"metadata": { |
|
"topics": chat_session.get('topics', []) |
|
} |
|
} |
|
response = requests.post(f"{BACKEND_URL}/save_chat_session", json=session_data) |
|
if response.status_code != 200: |
|
print(f"Failed to save chat session: {response.status_code}") |
|
except Exception as e: |
|
print(f"Error saving chat session: {e}") |
|
|
|
|
|
|
|
|
|
@app.route('/load_conversation_history') |
|
def load_conversation_history(): |
|
"""Load conversation history for all users""" |
|
try: |
|
user_id = session.get('user_id') |
|
user_name = session.get('user_data', {}).get('name', 'Guest') |
|
|
|
|
|
if not user_id or user_name == 'Guest': |
|
return jsonify({'messages': []}), 200 |
|
|
|
|
|
messages = [] |
|
|
|
|
|
if 'chat_session' in session and session['chat_session'].get('messages'): |
|
for msg in session['chat_session']['messages']: |
|
if msg['role'] in ['user', 'assistant']: |
|
messages.append({ |
|
'role': msg['role'], |
|
'content': msg['content'], |
|
'timestamp': msg['timestamp'] |
|
}) |
|
|
|
return jsonify({'messages': messages}) |
|
|
|
except Exception as e: |
|
print(f"Error loading conversation history: {e}") |
|
return jsonify({'messages': []}), 200 |
|
|
|
@app.route('/delete_conversation_history', methods=['POST']) |
|
@login_required |
|
def delete_conversation_history(): |
|
"""Delete conversation history for logged-in users""" |
|
try: |
|
user_id = session.get('user_id') |
|
if not user_id: |
|
return jsonify({'status': 'error', 'message': 'User not found'}), 400 |
|
|
|
|
|
if 'chat_session' in session: |
|
session['chat_session']['messages'] = [] |
|
session.modified = True |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return jsonify({'status': 'success'}) |
|
|
|
except Exception as e: |
|
print(f"Error deleting conversation history: {e}") |
|
return jsonify({'status': 'error', 'message': str(e)}), 500 |
|
|
|
|
|
|
|
|
|
|
|
|
|
@app.route('/save_conversation', methods=['POST']) |
|
def save_conversation_route(): |
|
"""Save conversation to FastAPI backend""" |
|
try: |
|
data = request.get_json() |
|
|
|
|
|
user_id = session.get('user_uuid', session.get('user_id', 'guest')) |
|
|
|
|
|
conversation_data = { |
|
"user_id": str(user_id), |
|
"message": data.get('message', ''), |
|
"response": data.get('response', ''), |
|
"timestamp": datetime.now().isoformat() |
|
} |
|
|
|
|
|
response = requests.post( |
|
f"{BACKEND_URL}/api/v1/chat/save", |
|
json=conversation_data, |
|
timeout=10 |
|
) |
|
|
|
if response.status_code == 200: |
|
return jsonify({'status': 'success'}) |
|
else: |
|
print(f"FastAPI save error: {response.status_code}") |
|
return jsonify({'status': 'success'}) |
|
|
|
except Exception as e: |
|
print(f"Error saving conversation: {e}") |
|
return jsonify({'status': 'success'}) |
|
|
|
|
|
@app.route("/clear_session", methods=["POST"]) |
|
def clear_session(): |
|
"""Clear session and save chat data""" |
|
try: |
|
|
|
if 'chat_session' in session and len(session['chat_session']['messages']) > 0: |
|
|
|
user_data = session.get('user_data', {}) |
|
if user_data.get('name', 'Guest').lower() not in ['guest', 'guest user', '']: |
|
save_chat_session_to_backend(session['chat_session']) |
|
except Exception as e: |
|
print(f"Error saving chat session: {e}") |
|
|
|
session.clear() |
|
return jsonify({"status": "success", "redirect": url_for('home')}) |
|
|
|
|
|
def get_chat_response(message, session_id=None): |
|
"""Simple chat response function for integration with Gradio app""" |
|
try: |
|
|
|
backend_url = os.getenv('BACKEND_URL', 'http://localhost:8000') |
|
|
|
|
|
user_context = { |
|
"emotion": "neutral", |
|
"name": "User", |
|
"mental_health_status": "Unknown", |
|
"age": "", |
|
"original_query": message, |
|
"message_count": 1 |
|
} |
|
|
|
|
|
response = requests.post( |
|
f"{backend_url}/process_message", |
|
json={ |
|
"message": message, |
|
"user_context": user_context, |
|
"session_id": session_id or "gradio_session" |
|
}, |
|
timeout=30 |
|
) |
|
|
|
if response.status_code == 200: |
|
result = response.json() |
|
return result.get("response", "I'm sorry, I couldn't process your request.") |
|
else: |
|
return "I'm having trouble connecting to the chat backend. Please try again." |
|
|
|
except Exception as e: |
|
print(f"Error in get_chat_response: {e}") |
|
return "I'm sorry, I'm having technical difficulties. Please try again later." |
|
|
|
|
|
def init_db(): |
|
"""Initialize the database with tables""" |
|
try: |
|
Base.metadata.create_all(engine) |
|
print("✅ Database tables created/verified successfully") |
|
return True |
|
except Exception as e: |
|
print(f"❌ Error creating database tables: {e}") |
|
return False |
|
|
|
|
|
if __name__ == "__main__": |
|
|
|
debug_mode = os.getenv('DEBUG', 'False').lower() == 'true' |
|
host = os.getenv('HOST', '0.0.0.0') |
|
port = int(os.getenv('PORT', 5000)) |
|
|
|
|
|
init_db() |
|
|
|
if debug_mode: |
|
app.run(debug=True, host=host, port=port) |
|
else: |
|
|
|
app.run(debug=False, host=host, port=port, threaded=True) |