pramodsai2000
Added new code
c0b34e4
# import os
# from flask import Flask, render_template, request, jsonify, send_from_directory
# from dotenv import load_dotenv
# from supabase import create_client, Client
# import openai
# app = Flask(__name__)
# # Load environment variables
# load_dotenv()
# SUPABASE_URL = os.getenv("SUPABASE_URL")
# SUPABASE_KEY = os.getenv("SUPABASE_KEY")
# # Initialize Supabase client
# supabase = create_client(SUPABASE_URL, SUPABASE_KEY)
# @app.route('/')
# def index():
# return send_from_directory(os.path.join(app.root_path, 'static'), 'index.html')
# @app.route('/login', methods=['POST'])
# def login():
# data = request.get_json()
# email = data['email']
# password = data['password']
# # Replace with actual login logic using Supabase or another method
# try:
# user = supabase.auth.sign_in(email=email, password=password)
# return jsonify({
# "message": "Login successful",
# "user_id": user.id
# })
# except Exception as e:
# return jsonify({"message": f"Login failed: {e}"}), 400
# @app.route('/signup', methods=['POST'])
# def signup():
# data = request.get_json()
# email = data['email']
# password = data['password']
# # Replace with actual signup logic using Supabase or another method
# try:
# user = supabase.auth.sign_up(email=email, password=password)
# return jsonify({"message": "Signup successful"})
# except Exception as e:
# return jsonify({"message": f"Signup failed: {e}"}), 400
# @app.route('/chat', methods=['POST'])
# def chat():
# data = request.get_json()
# message = data['message']
# chat_history = data.get('history', [])
# # Add your chatbot logic here (using OpenAI or another API)
# response = "This is a placeholder response" # Replace with chatbot response logic
# chat_history.append(f"User: {message}")
# chat_history.append(f"Bot: {response}")
# return jsonify({
# "bot_response": response,
# "history": chat_history
# })
# if __name__ == '__main__':
# app.run(debug=True, host='0.0.0.0', port=7860)
# import os
# from flask import Flask, send_from_directory, request, redirect, url_for, session
# from supabase import create_client
# import openai
# from dotenv import load_dotenv
# import json
# from json import jsonify
# # Load environment variables
# load_dotenv()
# OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
# SUPABASE_URL = os.getenv("SUPABASE_URL")
# SUPABASE_KEY = os.getenv("SUPABASE_KEY")
# # Initialize OpenAI and Supabase clients
# openai.api_key = OPENAI_API_KEY
# supabase = create_client(SUPABASE_URL, SUPABASE_KEY)
# # Flask app setup
# app = Flask(__name__)
# app.secret_key = os.getenv('FLASK_SECRET_KEY')
# # Global variables
# required_slots = {
# "patient_name": "May I know your full name?",
# "date_of_birth": "What is your date of birth?",
# "country_of_birth": "Where were you born?",
# "sex": "What is your biological sex?",
# "gender": "What gender do you identify as?",
# "race": "What is your racial background?",
# "school_behavior": "How was your behavior at school?",
# "school_grades": "How were your grades at school?",
# "school_failures": "Did you fail any class or year in school?",
# "school_behavior_issues": "Did you have problems at school due to your behavior?",
# "education_level": "What is your highest level of education?",
# "citizenship_status": "What is your citizenship or immigration status?",
# "marital_status": "What is your current relationship status?",
# "offsprings": "Do you have children? If yes, how many?",
# "living_situation": "What is your current living situation?",
# "employment_status": "What is your employment status?",
# "income_source": "What is your main source of income?",
# "legal_issues": "Do you have any history of legal issues?"
# }
# # Routes
# @app.route('/')
# def index():
# return send_from_directory('static', 'index.html')
# # Signup route
# @app.route('/signup', methods=['POST'])
# def signup():
# """Handle user signup."""
# email = request.form.get('email')
# password = request.form.get('password')
# try:
# # Correctly pass email and password as part of the dictionary
# user = supabase.auth.sign_up({
# "email": email,
# "password": password
# })
# return redirect(url_for('login'))
# except Exception as e:
# return f"Signup failed: {e}"
# @app.route('/login', methods=['GET', 'POST'])
# def login():
# if request.method == 'POST':
# email = request.form.get('email')
# password = request.form.get('password')
# try:
# # Attempt to log in with Supabase
# result = supabase.auth.sign_in_with_password({"email": email, "password": password})
# # Store user_id in session after login
# session['user_id'] = result.user.id
# session['user_email'] = result.user.email
# # Redirect to the chat page after login
# return redirect(url_for('chat'))
# except Exception as e:
# # If login fails, show the error message
# return f"Login failed: {e}"
# # For GET request, show the login page
# return send_from_directory('static', 'login.html')
# @app.route('/chat', methods=['GET', 'POST'])
# def chat():
# if 'user_id' not in session: # Check if the user is logged in
# return redirect(url_for('login')) # If not, redirect to login page
# # If user is logged in, show the chat page
# user_id = session.get('user_id')
# history = []
# if request.method == 'POST':
# user_message = request.form.get('message')
# history.append({"role": "user", "content": user_message})
# # OpenAI API call to get the response from the model
# response = openai.ChatCompletion.create(
# model="gpt-4o",
# messages=history,
# temperature=0.7
# )
# bot_response = response.choices[0].message['content']
# history.append({"role": "assistant", "content": bot_response})
# return send_from_directory('static', 'chat.html',
# mimetype='text/html',
# headers={'history': history, 'user_message': user_message, 'bot_response': bot_response})
# # Render the chat page
# return send_from_directory('static', 'chat.html')
# @app.route('/test_session')
# def test_session():
# return jsonify({"session": dict(session)})
# # Visit `/test_session` in your browser to check session data
# @app.route('/logout')
# def logout():
# session.clear() # Clear the session
# return redirect(url_for('login')) # Redirect to login after logout
# # Run the app
# if __name__ == "__main__":
# app.run(debug=True, host='0.0.0.0', port=7860)
# import os
# from flask import Flask, send_from_directory, request, redirect, url_for, session
# from flask_session import Session
# from supabase import create_client
# import openai
# from dotenv import load_dotenv
# # Load environment variables
# load_dotenv()
# OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
# SUPABASE_URL = os.getenv("SUPABASE_URL")
# SUPABASE_KEY = os.getenv("SUPABASE_KEY")
# # Initialize OpenAI and Supabase clients
# openai.api_key = OPENAI_API_KEY
# supabase = create_client(SUPABASE_URL, SUPABASE_KEY)
# # Flask app setup
# app = Flask(__name__)
# app.secret_key = os.getenv('FLASK_SECRET_KEY') # Set secret key
# app.config['SESSION_TYPE'] = 'filesystem' # Use filesystem to store session data
# app.config['SESSION_PERMANENT'] = False # Make the session non-permanent
# Session(app)
# # Global variables (slot filling)
# required_slots = {
# "patient_name": "May I know your full name?",
# "date_of_birth": "What is your date of birth?",
# "country_of_birth": "Where were you born?",
# "sex": "What is your biological sex?",
# "gender": "What gender do you identify as?",
# "race": "What is your racial background?",
# "school_behavior": "How was your behavior at school?",
# "school_grades": "How were your grades at school?",
# "school_failures": "Did you fail any class or year in school?",
# "school_behavior_issues": "Did you have problems at school due to your behavior?",
# "education_level": "What is your highest level of education?",
# "citizenship_status": "What is your citizenship or immigration status?",
# "marital_status": "What is your current relationship status?",
# "offsprings": "Do you have children? If yes, how many?",
# "living_situation": "What is your current living situation?",
# "employment_status": "What is your employment status?",
# "income_source": "What is your main source of income?",
# "legal_issues": "Do you have any history of legal issues?"
# }
# # Routes
# @app.route('/')
# def index():
# # Welcome page with buttons to Log In and Sign Up
# return send_from_directory('static', 'index.html')
# @app.route('/signup', methods=['GET', 'POST'])
# def signup():
# """Handle user signup."""
# if request.method == 'POST':
# email = request.form.get('email')
# password = request.form.get('password')
# try:
# # Sign up logic with Supabase
# user = supabase.auth.sign_up({
# "email": email,
# "password": password
# })
# # After successful signup, log in the user and redirect to the chat page
# session['user_id'] = user.id
# session['user_email'] = user.email
# return redirect(url_for('chat'))
# except Exception as e:
# return f"Signup failed: {e}"
# # For GET request, show the signup page
# return send_from_directory('static', 'signup.html')
# @app.route('/login', methods=['GET', 'POST'])
# def login():
# # """Handle user login."""
# # if request.method == 'POST':
# # email = request.form.get('email')
# # password = request.form.get('password')
# # try:
# # # Attempt to log in with Supabase
# # result = supabase.auth.sign_in_with_password({"email": email, "password": password})
# # # Store user_id in session after login
# # session['user_id'] = result.user.id
# # session['user_email'] = result.user.email
# # print(f"This is Login page User logged in: {session['user_email']}")
# # # Redirect to the chat page after successful login
# # return redirect(url_for('chat'))
# # except Exception as e:
# # # If login fails, show the error message
# # return f"Login failed: {e}"
# # # For GET request, show the login page
# # return send_from_directory('static', 'login.html')
# """Handle user login."""
# if request.method == 'POST':
# email = request.form.get('email')
# password = request.form.get('password')
# try:
# # Attempt to log in with Supabase (using the correct method)
# result = supabase.auth.sign_in_with_password({"email": email, "password": password})
# # Check if the login was successful
# if result.user:
# # Store user_id in session after login
# session['user_id'] = result.user.id
# session['user_email'] = result.user.email
# # Debugging: Print session data to check if it's set
# print(f"User logged in: {session['user_email']}")
# print(f"Session data in login page: {session}")
# # Redirect to the chat page after successful login
# return redirect(url_for('chat'))
# else:
# return "Invalid credentials, please try again."
# except Exception as e:
# return f"Login failed: {e}"
# # For GET request, show the login page
# return send_from_directory('static', 'login.html')
# # Chat route
# @app.route('/chat', methods=['GET', 'POST'])
# def chat():
# """Handle chat interactions."""
# # Check if user is logged in
# print(f"Checking session for user_id in chat page : {session.get('user_id')}")
# print(f"Session data in chat page: {session}")
# # if 'user_id' not in session:
# # return redirect(url_for('login')) # Redirect to login page if not logged in
# # If user is logged in, show the chat page
# print(f"User accessing chat: {session.get('user_email')}")
# user_id = session.get('user_id')
# history = []
# if request.method == 'POST':
# user_message = request.form.get('message')
# history.append({"role": "user", "content": user_message})
# # OpenAI API call to get the response from the model
# response = openai.ChatCompletion.create(
# model="gpt-4o",
# messages=history,
# temperature=0.7
# )
# bot_response = response.choices[0].message['content']
# history.append({"role": "assistant", "content": bot_response})
# return send_from_directory('static', 'chat.html',
# mimetype='text/html',
# headers={'history': history, 'user_message': user_message, 'bot_response': bot_response})
# # Render the chat page
# return send_from_directory('static', 'chat.html')
# # Logout route
# @app.route('/logout')
# def logout():
# session.clear() # Clear the session
# return redirect(url_for('login')) # Redirect to login after logout
# # Run the app
# if __name__ == "__main__":
# app.run(debug=True, host='0.0.0.0', port=7860)
# import os
# from flask import Flask, render_template, request, redirect, url_for, session
# from flask_session import Session
# from supabase import create_client
# import openai
# from dotenv import load_dotenv
# # Load environment variables
# load_dotenv()
# OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
# SUPABASE_URL = os.getenv("SUPABASE_URL")
# SUPABASE_KEY = os.getenv("SUPABASE_KEY")
# # Initialize OpenAI and Supabase clients
# openai.api_key = OPENAI_API_KEY
# supabase = create_client(SUPABASE_URL, SUPABASE_KEY)
# # Flask app setup
# app = Flask(__name__, static_folder="static", template_folder="templates")
# app.secret_key = os.getenv('FLASK_SECRET_KEY') # Set secret key
# app.config['SESSION_TYPE'] = 'filesystem' # Use filesystem to store session data
# app.config['SESSION_PERMANENT'] = False # Make the session non-permanent
# Session(app) # Initialize Flask-Session
# # Global variables (slot filling)
# required_slots = {
# "patient_name": "May I know your full name?",
# "date_of_birth": "What is your date of birth?",
# "country_of_birth": "Where were you born?",
# "sex": "What is your biological sex?",
# "gender": "What gender do you identify as?",
# "race": "What is your racial background?",
# "school_behavior": "How was your behavior at school?",
# "school_grades": "How were your grades at school?",
# "school_failures": "Did you fail any class or year in school?",
# "school_behavior_issues": "Did you have problems at school due to your behavior?",
# "education_level": "What is your highest level of education?",
# "citizenship_status": "What is your citizenship or immigration status?",
# "marital_status": "What is your current relationship status?",
# "offsprings": "Do you have children? If yes, how many?",
# "living_situation": "What is your current living situation?",
# "employment_status": "What is your employment status?",
# "income_source": "What is your main source of income?",
# "legal_issues": "Do you have any history of legal issues?"
# }
# # Routes
# @app.route("/")
# def index():
# return render_template("index.html", title="Welcome")
# # Signup route
# @app.route('/signup', methods=['GET', 'POST'])
# def signup():
# """Handle user signup."""
# if request.method == 'POST':
# email = request.form.get('email')
# password = request.form.get('password')
# try:
# # Sign up logic with Supabase
# user = supabase.auth.sign_up({
# "email": email,
# "password": password
# })
# # After successful signup, log in the user and redirect to the chat page
# session['user_id'] = user.id
# session['user_email'] = user.email
# return redirect(url_for('chat'))
# except Exception as e:
# return f"Signup failed: {e}"
# # For GET request, show the signup page
# return render_template('signup.html')
# # Login route
# @app.route('/login', methods=['GET', 'POST'])
# def login():
# """Handle user login."""
# if request.method == 'POST':
# email = request.form.get('email')
# password = request.form.get('password')
# try:
# # Attempt to log in with Supabase
# result = supabase.auth.sign_in_with_password({"email": email, "password": password})
# # Store user_id in session after login
# session['user_id'] = result.user.id
# session['user_email'] = result.user.email
# # Debugging: Print session data to check if it's set
# print(f"User logged in: {session['user_email']}")
# # Redirect to the chat page after successful login
# return redirect(url_for('chat'))
# except Exception as e:
# return f"Login failed: {e}"
# # For GET request, show the login page
# return render_template('login.html')
# # Chat route
# @app.route('/chat', methods=['GET', 'POST'])
# def chat():
# """Handle chat interactions."""
# # if 'user_id' not in session:
# # return redirect(url_for('login')) # Redirect to login page if not logged in
# user_id = session.get('user_id')
# history = session.get('chat_history', [])
# # Initial system prompt (context for the chatbot)
# system_prompt = {
# "role": "system",
# "content": """The following is a conversation with an AI assistant that can have meaningful conversations with users.
# You are a clinical assistant who assesses patients' mental health by asking relevant questions.
# You are helpful, empathetic, and friendly. Your objective is to make the user feel better by feeling heard.
# Focus solely on evaluating and supporting the user’s mental health: ask about their feelings, symptoms, coping strategies, and well-being."""
# }
# if request.method == 'POST':
# user_message = request.form.get('message')
# history.append({"role": "user", "content": user_message})
# # Combine history with system prompt
# messages = [system_prompt] + [{"role": msg['role'], "content": msg['content']} for msg in history]
# # OpenAI API call to get the response from the model
# response = openai.ChatCompletion.create(
# model="gpt-4o",
# messages=messages,
# temperature=0.7
# )
# bot_response = response.choices[0].message['content']
# history.append({"role": "assistant", "content": bot_response})
# # Store the updated history in the session
# session['chat_history'] = history
# return render_template('chat.html', history=history)
# # Render the chat page
# return render_template('chat.html', history=history)
# # Logout route
# @app.route('/logout')
# def logout():
# session.clear() # Clear the session
# return redirect(url_for('login')) # Redirect to login after logout
# # Run the app
# if __name__ == "__main__":
# app.run(debug=True, host='0.0.0.0', port=7860)
# import os
# from flask import Flask, render_template, request, redirect, url_for, session, jsonify
# from flask_session import Session
# from dotenv import load_dotenv
# from supabase import create_client, Client
# from openai import OpenAI
# # =============== Env & Clients ===============
# load_dotenv()
# OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
# SUPABASE_URL = os.getenv("SUPABASE_URL")
# SUPABASE_KEY = os.getenv("SUPABASE_KEY")
# FLASK_SECRET_KEY = os.getenv("FLASK_SECRET_KEY", "dev-secret")
# if not OPENAI_API_KEY:
# raise RuntimeError("OPENAI_API_KEY is not set")
# if not SUPABASE_URL or not SUPABASE_KEY:
# raise RuntimeError("SUPABASE_URL or SUPABASE_KEY is not set")
# client = OpenAI(api_key=OPENAI_API_KEY)
# supabase: Client = create_client(SUPABASE_URL, SUPABASE_KEY)
# # =============== Flask App ===============
# app = Flask(__name__, static_folder="static", template_folder="templates")
# app.secret_key = FLASK_SECRET_KEY
# app.config["SESSION_TYPE"] = "filesystem"
# app.config["SESSION_PERMANENT"] = False
# app.config["SESSION_FILE_DIR"] = os.path.join("/tmp", "flask_session")
# os.makedirs(app.config["SESSION_FILE_DIR"], exist_ok=True)
# Session(app)
# # =============== Chat Config ===============
# SYSTEM_PROMPT = {
# "role": "system",
# "content": (
# "You are a clinical assistant who assesses patients' mental health by asking relevant questions. "
# "You are helpful, empathetic, and friendly. Focus solely on evaluating and supporting the user's mental health: "
# "ask about feelings, symptoms, coping strategies, and well-being. Keep responses under 120 words."
# ),
# }
# # =============== Helpers ===============
# def get_history() -> list[dict]:
# """Return chat history stored in session (list of {role, content})."""
# if "chat_history" not in session:
# session["chat_history"] = []
# return session["chat_history"]
# def set_history(history: list[dict]) -> None:
# session["chat_history"] = history
# # =============== Routes ===============
# @app.route("/")
# def index():
# return render_template("index.html", title="Welcome")
# @app.route("/login", methods=["GET", "POST"])
# def login():
# if request.method == "POST":
# email = request.form.get("email", "").strip()
# password = request.form.get("password", "")
# try:
# resp = supabase.auth.sign_in_with_password({"email": email, "password": password})
# # supabase-py v2 returns an object with .user
# if not resp.user:
# return render_template("login.html", title="Login", error="Invalid credentials.")
# session["user_id"] = resp.user.id
# session["user_email"] = resp.user.email
# return redirect(url_for("chat"))
# except Exception as e:
# return render_template("login.html", title="Login", error=str(e))
# return render_template("login.html", title="Login")
# @app.route("/signup", methods=["GET", "POST"])
# def signup():
# if request.method == "POST":
# email = request.form.get("email", "").strip()
# password = request.form.get("password", "")
# try:
# resp = supabase.auth.sign_up({"email": email, "password": password})
# if not resp.user:
# return render_template("signup.html", title="Sign Up", error="Signup failed.")
# session["user_id"] = resp.user.id
# session["user_email"] = resp.user.email
# return redirect(url_for("chat"))
# except Exception as e:
# return render_template("signup.html", title="Sign Up", error=str(e))
# return render_template("signup.html", title="Sign Up")
# @app.route("/chat", methods=["GET"])
# def chat():
# # Optional gate:
# # if "user_id" not in session: return redirect(url_for("login"))
# history = get_history()
# return render_template("chat.html", history=history, title="Chat")
# @app.post("/message")
# def message():
# """JSON endpoint consumed by static/script.js"""
# data = request.get_json(force=True) or {}
# user_text = (data.get("message") or "").strip()
# if not user_text:
# return jsonify({"error": "Empty message"}), 400
# history = get_history()
# # Append user turn
# history.append({"role": "user", "content": user_text})
# # Build messages window to keep token usage reasonable
# recent = history[-10:]
# messages = [SYSTEM_PROMPT] + recent
# try:
# resp = client.chat.completions.create(
# model="gpt-4o-mini",
# messages=messages,
# temperature=0.6,
# )
# bot_reply = resp.choices[0].message.content
# except Exception as e:
# bot_reply = f"Oops, I couldn't reach the AI service: {e}"
# # Append assistant turn & persist in session
# history.append({"role": "assistant", "content": bot_reply})
# set_history(history)
# return jsonify({"reply": bot_reply})
# @app.route("/logout")
# def logout():
# session.clear()
# return redirect(url_for("login"))
# # =============== Main ===============
# if __name__ == "__main__":
# # Hugging Face Spaces expects the app to listen on 7860
# app.run(host="0.0.0.0", port=7860, debug=True)
import os
import re
import json
import logging
from datetime import datetime
import base64
import uuid
from datetime import date
from flask import Flask, render_template, request, redirect, url_for, session, jsonify
from flask_session import Session
from dotenv import load_dotenv
from supabase import create_client, Client
from openai import OpenAI
from google.cloud import dialogflowcx_v3 as dfx
from google.oauth2 import service_account
from google.protobuf import struct_pb2
from google.api_core.client_options import ClientOptions
# =============== Env & Clients ===============
load_dotenv()
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
SUPABASE_URL = os.getenv("SUPABASE_URL")
SUPABASE_KEY = os.getenv("SUPABASE_KEY")
FLASK_SECRET_KEY = os.getenv("FLASK_SECRET_KEY", "dev-secret")
DF_PROJECT = os.getenv("DIALOGFLOW_PROJECT_ID")
DF_LOCATION = os.getenv("DIALOGFLOW_LOCATION", "us-central1")
DF_AGENT_ID = os.getenv("DIALOGFLOW_AGENT_ID", "")
DF_BEARER = os.getenv("DF_WEBHOOK_BEARER", "")
YES = re.compile(r'^(y|ya|yes|yep|yeah|sure|ok|okay|affirmative|i agree|consent)\.?$', re.I)
NO = re.compile(r'^(n|no|nope|nah|not now|decline|don\'t|do not)\.?$', re.I)
if not OPENAI_API_KEY:
raise RuntimeError("OPENAI_API_KEY is not set")
if not SUPABASE_URL or not SUPABASE_KEY:
raise RuntimeError("SUPABASE_URL or SUPABASE_KEY is not set")
client = OpenAI(api_key=OPENAI_API_KEY)
supabase: Client = create_client(SUPABASE_URL, SUPABASE_KEY)
# =============== Logging ===============
# Logs go to STDOUT (visible in Spaces logs)
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s [%(levelname)s] %(message)s",
)
log = logging.getLogger("app")
# =============== Flask App ===============
app = Flask(__name__, static_folder="static", template_folder="templates")
app.secret_key = FLASK_SECRET_KEY
app.config["SESSION_TYPE"] = "filesystem"
app.config["SESSION_PERMANENT"] = False
app.config["SESSION_FILE_DIR"] = os.path.join("/tmp", "flask_session")
os.makedirs(app.config["SESSION_FILE_DIR"], exist_ok=True)
Session(app)
app.config.update(
SESSION_COOKIE_NAME="mh_session",
SESSION_COOKIE_HTTPONLY=True,
SESSION_COOKIE_SAMESITE="None",
SESSION_COOKIE_SECURE=True, # HTTPS on Spaces
)
@app.before_request
def _log_request():
log.info(
"REQ %s %s ip=%s ua=%s",
request.method,
request.path,
request.headers.get("X-Forwarded-For", request.remote_addr),
request.headers.get("User-Agent", "-"),
)
if request.is_json:
try:
log.info("REQ JSON: %s", json.dumps(request.get_json(force=False), ensure_ascii=False))
except Exception:
pass
@app.after_request
def _log_response(resp):
log.info("RESP %s %s status=%s", request.method, request.path, resp.status_code)
return resp
# =============== Chat Config ===============
SYSTEM_PROMPT = {
"role": "system",
"content": (
"You are a clinical assistant who assesses patients' mental health by asking relevant questions. "
"You are helpful, empathetic, and friendly. Focus solely on evaluating and supporting the user's mental health: "
"ask about feelings, symptoms, coping strategies, and well-being. Keep responses under 120 words."
),
}
# =============== Helpers ===============
def _auth_ok(req):
# Accept either Authorization: Bearer <token> or X-DF-Token: <token>
auth = req.headers.get("Authorization", "")
if auth == f"Bearer {DF_BEARER}":
return True
return False
def get_history() -> list[dict]:
"""Return chat history stored in session (list of {role, content})."""
hist = session.get("chat_history")
if not isinstance(hist, list):
hist = []
session["chat_history"] = hist
return hist
def set_history(history: list[dict]) -> None:
session["chat_history"] = history
def safe_email() -> str:
return session.get("user_email") or "anonymous@local"
def needs_socio_row(user_id: str) -> bool:
try:
res = (supabase.table("Patient Table")
.select("patient_id", count="exact") # ask PostgREST to count
.eq("patient_id", user_id)
.limit(1)
.execute())
log.info("Supabase sociodemographic check for %s: %s", user_id, res)
# Normal case:
if res is not None and getattr(res, "count", None) is not None:
return res.count == 0
# Fallbacks:
rows = (getattr(res, "data", None) or [])
return len(rows) == 0
except Exception as e:
app.logger.exception("Supabase sociodemographic check failed: %s", e)
return True # be conservative: collect intake
def _cx_endpoint(location: str) -> str:
# CX requires regional endpoints (except 'global')
return "dialogflow.googleapis.com" if location == "global" else f"{location}-dialogflow.googleapis.com"
def _load_df_credentials():
raw = os.getenv("DIALOGFLOW_KEY", "")
if not raw:
raise RuntimeError("DIALOGFLOW_KEY not set")
# Try JSON object first
try:
info = json.loads(raw)
return service_account.Credentials.from_service_account_info(
info,
scopes=["https://www.googleapis.com/auth/dialogflow"]
)
except Exception:
pass
# Try base64 JSON
try:
decoded = base64.b64decode(raw).decode("utf-8")
info = json.loads(decoded)
return service_account.Credentials.from_service_account_info(
info,
scopes=["https://www.googleapis.com/auth/dialogflow"]
)
except Exception:
pass
# Fall back: treat as file path
if os.path.exists(raw):
return service_account.Credentials.from_service_account_file(
raw,
scopes=["https://www.googleapis.com/auth/dialogflow"]
)
raise RuntimeError("DIALOGFLOW_KEY must be service account JSON (plain or base64) or a valid file path.")
_creds = _load_df_credentials()
# ---- Build CX Sessions client and agent path
_df_sessions = dfx.SessionsClient(credentials=_creds, client_options=ClientOptions(api_endpoint=_cx_endpoint(DF_LOCATION)))
DF_AGENT_PATH = f"projects/{DF_PROJECT}/locations/{DF_LOCATION}/agents/{DF_AGENT_ID}"
def cx_texts(resp) -> str:
"""Flatten Dialogflow CX response_messages into a single string."""
if not resp or not getattr(resp, "query_result", None):
return "…"
texts = []
for m in resp.query_result.response_messages:
if getattr(m, "text", None) and m.text.text:
# m.text.text is a list of strings: take them all and join
texts.extend([t for t in m.text.text if t])
return "\n\n".join(texts).strip() or "…"
def _mk_query_params(params: dict | None):
if not params:
return None
s = struct_pb2.Struct()
# Only put JSON-serializable values (str/int/bool/float/list/dict); skip None
for k, v in params.items():
if v is not None:
s.update({k: v})
return dfx.QueryParameters(parameters=s)
def cx_detect_text(text: str, session_id: str, params: dict | None = None):
session_path = f"{DF_AGENT_PATH}/sessions/{session_id or 'anon'}"
qp = _mk_query_params(params)
return _df_sessions.detect_intent(
request=dfx.DetectIntentRequest(
session=session_path,
query_input=dfx.QueryInput(
text=dfx.TextInput(text=text),
language_code="en",
),
query_params=qp,
)
)
def cx_trigger_event(event_name: str, session_id: str, params: dict | None = None):
session_path = f"{DF_AGENT_PATH}/sessions/{session_id or 'anon'}"
qp = _mk_query_params(params)
return _df_sessions.detect_intent(
request=dfx.DetectIntentRequest(
session=session_path,
query_input=dfx.QueryInput(
event=dfx.EventInput(event=event_name),
language_code="en",
),
query_params=qp,
)
)
def is_yes_no(s: str) -> bool:
t = (s or "").strip()
return bool(YES.match(t) or NO.match(t))
def ensure_cx_session():
if not session.get("cx_session_id"):
session["cx_session_id"] = f'{session.get("user_id","anon")}-{uuid.uuid4().hex}'
session["kicked_socio"] = False
def classify_with_openai(text:str)->dict:
try:
r = client.chat.completions.create(
model="gpt-4o-mini",
temperature=0,
response_format={"type":"json_object"},
messages=[{
"role":"system",
"content":(
"Return JSON with fields: kind ∈ {on_topic, smalltalk}, "
"and note (≤30 words, empathetic, no clinical advice). "
"If user asks casual or unrelated questions → smalltalk."
)
},{"role":"user","content":text}]
)
return json.loads(r.choices[0].message.content)
except Exception:
return {"kind":"on_topic","note":""}
# =============== Routes ===============
@app.get("/health")
def health():
return jsonify({"ok": True, "time": datetime.utcnow().isoformat() + "Z"})
@app.route("/")
def index():
return render_template("index.html", title="Welcome")
@app.post("/dialogflow")
def dialogflow_webhook():
if not _auth_ok(request):
return ("Unauthorized", 401)
log.info("Dialogflow webhook called with auth OK")
body = request.get_json(force=True) or {}
tag = ((body.get("fulfillmentInfo") or {}).get("tag")) or ""
params = (body.get("sessionInfo") or {}).get("parameters") or {}
user_id = params.get("user_id")
log.info("Dialogflow webhook called with tag=%s, user_id=%s", tag, user_id)
log.debug("Dialogflow webhook params: %s", json.dumps(params, ensure_ascii=False))
# --- helpers ---
def join_text(x):
if x is None: return None
if isinstance(x, list): return ", ".join([str(v) for v in x if v is not None])
return str(x)
def yes_no(v):
if isinstance(v, bool): return "yes" if v else "no"
s = str(v or "").strip().lower()
return "yes" if s in {"y","yes","true","1","sure","ok","okay","affirmative"} else ("no" if s in {"n","no","false","0","nah","nope","negative"} else None)
def only_digits(x):
if x is None: return None
s = re.sub(r"\D+", "", str(x))
return int(s) if s else None
def iso_date(s):
# keep as given; DB column is varchar. If you want strict ISO, coerce here.
return str(s) if s else None
def calc_age(dob_str):
try:
y, m, d = [int(t) for t in str(dob_str)[:10].split("-")]
today = date.today()
a = today.year - y - ((today.month, today.day) < (m, d))
return a
except Exception:
return None
# --- consent logging (optional example) ---
if tag == "record_consent":
try:
supabase.table("consent_log").insert({
"user_id": user_id,
"consent": (params.get("consent") == "yes"),
}).execute()
except Exception as e:
log.exception("Failed to record consent: %s", e)
return {"sessionInfo": {"parameters": params}}
# --- main save ---
if tag == "save_socio":
if not user_id:
# Without a patient_id we can't satisfy DB constraints -> ask CX to reprompt via INVALID (or handle upstream)
return {
"sessionInfo": {"parameters": params},
"fulfillment_response": {"messages": [{"text": {"text": [
"I’m missing your account id. Please log in again."
]}}]}
}
# Collect inputs with tolerant keys (use whichever your CX params are using)
full_name = params.get("patient_name") or params.get("full_name") or params.get("name")
dob = params.get("date_of_birth")
phone_in = params.get("patient_phone") or params.get("phone_number")
country = params.get("country_of_birth")
sex_val = params.get("sex") or params.get("sex_assigned_at_birth")
gender_val = params.get("gender") or params.get("gender_identity")
race_val = params.get("race") or params.get("race_ethnicity")
sch_beh = params.get("school_performance_behavior") or params.get("school_behavior")
grades = params.get("grades_overall") or params.get("school_grades")
failed = params.get("failed_class_or_year") or params.get("school_failures")
# beh_probs = params.get("school_behavior_problems") or params.get("school_behavior_issues")
edu = params.get("highest_education") or params.get("education_level")
citizen = params.get("citizenship_status")
marital = params.get("marital_status")
kids = params.get("offsprings") or params.get("offspring_count")
living = params.get("living_situation")
employment = params.get("employment_status")
income = params.get("income_source")
legal_hist = params.get("legal_issues_history") or params.get("legal_issues")
# legal_det = params.get("legal_issues_details")
# Normalize
dob_txt = iso_date(dob)
age_val = params.get("age") or (calc_age(dob_txt) if dob_txt else None)
phone_num = only_digits(phone_in)
race_txt = join_text(race_val)
income_txt= join_text(income)
fail_txt = yes_no(failed)
# beh_txt = yes_no(beh_probs)
legal_txt = yes_no(legal_hist)
# If you want details captured, append when legal is yes
# if legal_txt == "yes" and legal_det:
# legal_txt = f"yes - {str(legal_det)}"
row = {
"patient_id": user_id, # NOT NULL, PK
"patient_name": full_name,
"age": age_val,
"date_of_birth": dob_txt, # varchar in your schema
"phone": phone_num,
"country_of_birth": country,
"sex": sex_val,
"gender": gender_val,
"race": race_txt,
"school_behavior": sch_beh,
"school_grades": grades,
"school_failures": fail_txt, # "yes"/"no"
# "school_behavior_issues": beh_txt, # "yes"/"no"
"education_level": edu,
"citizenship_status": citizen,
"marital_status": marital,
"offsprings": str(kids) if kids is not None else None,
"living_situation": living,
"employment_status": employment,
"income_source": income_txt,
"legal_issues": legal_txt,
"patient_phone": phone_num, # also fill the duplicate column
}
# Upsert by patient_id
try:
supabase.table("Patient Table").upsert(row, on_conflict="patient_id").execute()
msg = "Thanks — I’ve saved your information."
except Exception as e:
log.exception('Supabase upsert failed: %s', e)
msg = "I had trouble saving your answers, but we can continue."
# Optionally reflect normalized values back into the session
params.update({
"age": age_val,
"date_of_birth": dob_txt,
"phone": phone_num,
"patient_phone": phone_num,
"race": race_txt,
"income_source": income_txt,
"school_failures": fail_txt,
"school_behavior_issues": beh_txt,
"legal_issues": legal_txt,
})
return {
"sessionInfo": {"parameters": params},
"fulfillment_response": {"messages": [{"text": {"text": [msg]}}]},
}
# default no-op
log.info("DF Webhook called with tag=%s params=%s", tag, params)
return {"sessionInfo": {"parameters": params}}
@app.route("/login", methods=["GET", "POST"])
def login():
if request.method == "POST":
email = request.form.get("email", "").strip()
password = request.form.get("password", "")
try:
log.info("Attempting login for %s", email)
resp = supabase.auth.sign_in_with_password({"email": email, "password": password})
if not getattr(resp, "user", None):
log.warning("Login failed for %s (no user in response)", email)
return render_template("login.html", title="Login", error="Invalid credentials.")
session["user_id"] = resp.user.id
session["user_email"] = resp.user.email
log.info("Login success user_id=%s email=%s", resp.user.id, resp.user.email)
# try:
# # s = supabase.table("Patient Table") \
# # .select("patient_id") \
# # .eq("patient_id", session["user_id"]) \
# # .maybe_single().execute()
# # need_socio = (len(getattr(s, "data", []) or s.get("data", [])) == 0)
# resp = (
# supabase.table("Patient Table")
# .select("patient_id")
# .eq("patient_id", session["user_id"])
# .maybe_single() # ← key line
# .execute()
# )
# need_socio = (resp.data is None)
# except Exception as e:
# app.logger.exception("Supabase sociodemographic check failed")
# need_socio = True # collect if unsure
session["cx_session_id"] = f'{session["user_id"]}-{uuid.uuid4().hex}'
session["kicked_socio"] = False
session["chat_history"] = []
need_socio = needs_socio_row(resp.user.id)
session["phase"] = "intake_socio" if need_socio else "place_holder"
log.info("The Phase of the session %s", session["phase"])
return redirect(url_for("chat"))
except Exception as e:
log.exception("Login error for %s", email)
return render_template("login.html", title="Login", error=str(e))
return render_template("login.html", title="Login")
@app.route("/signup", methods=["GET", "POST"])
def signup():
if request.method == "POST":
email = request.form.get("email", "").strip()
password = request.form.get("password", "")
try:
log.info("Attempting signup for %s", email)
resp = supabase.auth.sign_up({"email": email, "password": password})
if not getattr(resp, "user", None):
log.warning("Signup failed for %s (no user in response)", email)
return render_template("signup.html", title="Sign Up", error="Signup failed.")
session["user_id"] = resp.user.id
session["user_email"] = resp.user.email
log.info("Signup success user_id=%s email=%s", resp.user.id, resp.user.email)
# try:
# s = supabase.table("Patient Table") \
# .select("patient_id") \
# .eq("patient_id", session["user_id"]) \
# .limit(1).execute()
# need_socio = (len(getattr(s, "data", []) or s.get("data", [])) == 0)
# except Exception as e:
# app.logger.exception("Supabase sociodemographic check failed")
# need_socio = True # collect if unsure
# need_socio = needs_socio_row(resp.user.id)
session["cx_session_id"] = f'{session["user_id"]}-{uuid.uuid4().hex}'
session["kicked_socio"] = False
session["chat_history"] = []
session["phase"] = "intake_socio"
log.info("The Phase of the session %s", session["phase"])
return redirect(url_for("chat"))
except Exception as e:
log.exception("Signup error for %s", email)
return render_template("signup.html", title="Sign Up", error=str(e))
return render_template("signup.html", title="Sign Up")
@app.route("/chat", methods=["GET"])
def chat():
# Optional gate:
# if "user_id" not in session: return redirect(url_for("login"))
ensure_cx_session()
history = get_history()
log.info("Render chat for %s with %d history msgs", safe_email(), len(history))
if session.get("phase") == "intake_socio" and not session.get("kicked_socio"):
session["kicked_socio"] = True
#change
session_id = session.get("cx_session_id") or session.get("user_id") or "anon"
params={"user_id": session.get("user_id"), "user_email": session.get("user_email")}
try:
df = cx_trigger_event(
"start_socio",
session_id,
params
)
except Exception as e:
app.logger.warning("start_socio not handled; falling back: %s", e)
df = cx_detect_text("", session_id, params)
log.info("CX triggered start_socio event, got %d response messages", len(df.query_result.response_messages))
# extract first text reply from CX
# texts = [m.text.text for m in df.query_result.response_messages if m.text and m.text.text]
# if texts:
# history.append({"role": "assistant", "content": texts[0]})
# set_history(history)
msg = cx_texts(df)
history.append({"role": "assistant", "content": msg})
set_history(history)
cr = df.query_result
log.info("CX kickoff -> page=%s", getattr(cr.current_page, "display_name", None))
return render_template("chat.html", history=history, title="Chat")
@app.post("/message")
def message():
"""JSON endpoint consumed by static/script.js"""
try:
data = request.get_json(force=True) or {}
user_text = (data.get("message") or "").strip()
if not user_text:
log.warning("Empty message received")
return jsonify({"error": "Empty message"}), 400
history = get_history()
history.append({"role": "user", "content": user_text})
set_history(history)
log.info("USER: %s", user_text)
phase = session.get("phase") or "assessment"
session_params = {"user_id": session.get("user_id"), "user_email": session.get("user_email")}
session_id = session.get("cx_session_id") or session.get("user_id") or "anon"
# def first_cx_text(df_resp):
# msgs = [m.text.text for m in df_resp.query_result.response_messages if m.text and m.text.text]
# return (msgs[0] if msgs and msgs[0] else "Okay.")
# --- Intake phases use CX ---
if phase == "intake_socio":
# 1) quick classify smalltalk/off-topic; also extract any obvious values
df = cx_detect_text(user_text, session_id, session_params)
bot_reply = cx_texts(df)
log.info("CX response: %s", bot_reply)
if bot_reply.strip().lower() in {"i missed that, say that again?", "sorry, could you repeat that?"}:
#Try out code block
# if is_yes_no(user_text):
# df = cx_detect_text(user_text, session.get("user_id","anon"), session_params)
# bot_reply = cx_texts(df)
# else:
# 2) classify with OpenAI
decision = classify_with_openai(user_text) # returns {"kind":"on_topic"/"smalltalk","note": "..."}
if decision["kind"] == "smalltalk":
# brief supportive reply, then nudge back with the current CX prompt (ask CX what’s next without consuming user input)
empathetic = decision["note"]
# Query CX with an empty text to get the next prompt (or you can store last CX prompt in session)
df_peek = cx_detect_text("", session_id, session_params)
# nudge = first_cx_text(df_peek)
nudge = cx_texts(df_peek)
log.info("CX smalltalk nudge: %s", nudge)
bot_reply = f"{empathetic}\n\n{nudge}"
#tryout block
# else:
# # 2) normal CX turn with the user’s text
# df = cx_detect_text(user_text, session.get("user_id","anon"), session_params)
# # bot_reply = first_cx_text(df)
# bot_reply = cx_texts(df)
# 3) if the form just finished, CX will satisfy $page.params.status == "FINAL".
# Easiest: add a Page Route in CX that calls your webhook (tag=save_socio) and transitions.
# Here, we can also flip the local phase if CX already moved you.
if df.query_result.current_page and df.query_result.current_page.display_name != "Socio form":
# assume we left SocioForm -> go next phase
log.info("Current page is %s, display name is %s", df.query_result.current_page,df.query_result.current_page.display_name)
session["phase"] = "place_holder" # or "intake_clinical"
log.info("CX moved to page %s, switching phase to %s",
df.query_result.current_page.display_name, session["phase"])
else:
try:
recent = history[-10:]
messages = [SYSTEM_PROMPT] + recent
resp = client.chat.completions.create(
model="gpt-4o-mini",
messages=messages,
temperature=0.6,
)
bot_reply = resp.choices[0].message.content
log.info("ASSISTANT: %s", bot_reply)
except Exception as e:
log.exception("OpenAI call failed")
bot_reply = f"Oops, I couldn't reach the AI service: {e}"
history.append({"role": "assistant", "content": bot_reply})
set_history(history)
# NOTE: If you want to persist to Supabase, you can insert here safely
# supabase.table("conversations").insert({...})
return jsonify({"reply": bot_reply})
except Exception as e:
log.exception("Unhandled error in /message")
return jsonify({"error": f"Server error: {e}"}), 500
@app.route("/logout")
def logout():
log.info("Logout %s", safe_email())
session.clear()
return redirect(url_for("login"))
# =============== Main ===============
if __name__ == "__main__":
# Hugging Face Spaces expects the app to listen on 7860
port = int(os.getenv("PORT", "7860"))
app.run(host="0.0.0.0", port=port, debug=True)