# app.py # Minimal Serenity backend that serves index.html and a /chat endpoint. # If OPENAI_API_KEY is set it will use OpenAI (gpt-3.5-turbo) for nicer replies; # otherwise it uses a small local fallback so chat still works. import os import json import random from datetime import datetime from flask import Flask, request, jsonify, send_from_directory from flask_cors import CORS # Optional OpenAI usage try: import openai OPENAI_PRESENT = True OPENAI_KEY = os.getenv("OPENAI_API_KEY") if OPENAI_KEY: openai.api_key = OPENAI_KEY else: OPENAI_PRESENT = False except Exception: OPENAI_PRESENT = False app = Flask(__name__, static_folder=".", static_url_path="/") CORS(app) # Simple memory (session -> data) kept in memory + optionally saved to file MEM_FILE = "memory.json" try: if os.path.exists(MEM_FILE): with open(MEM_FILE, "r", encoding="utf-8") as f: MEMORY = json.load(f) else: MEMORY = {} except Exception: MEMORY = {} def save_memory(): try: with open(MEM_FILE, "w", encoding="utf-8") as f: json.dump(MEMORY, f, indent=2, ensure_ascii=False) except Exception: pass # Simple emotion heuristic (fallback) def simple_emotion(text: str) -> str: t = text.lower() if any(w in t for w in ["happy","great","good","awesome","yay","joy"]): return "happy" if any(w in t for w in ["sad","down","depress","lonely","cry"]): return "sad" if any(w in t for w in ["angry","mad","furious"]): return "angry" if any(w in t for w in ["scared","afraid","anxious","panic","worried"]): return "worried" if any(w in t for w in ["calm","fine","okay","ok","relaxed"]): return "calm" if any(w in t for w in ["motivate","motivation","guidance","inspire"]): return "motivated" return "neutral" # Basic fallback reply generator (when OpenAI not available) def fallback_reply(user_text: str, persona: str, name: str = None) -> str: greetings = [ "Thanks for sharing — I'm here with you.", "I hear you. Tell me more if you'd like.", "That matters. Would you like to say more about it?" ] casual = [ "Haha, nice! What's new with you?", "I’m here — how’s your day going?", "That sounds fun — tell me more!" ] motivate = [ "You’ve got this — small steps add up.", "Take one small step today; it can make a difference.", "I believe in you. What is one tiny thing you can try now?" ] # choose style based on keywords txt = user_text.lower() if any(k in txt for k in ["motivate","motivation","guidance","inspire","quote"]): return random.choice(motivate) if any(k in txt for k in ["hi","hello","hey","how are you","sup","what's up"]): return random.choice(casual) # emotional catch if any(k in txt for k in ["sad","depress","lonely","upset","not good","terrible","bad"]): return random.choice(greetings) return random.choice(greetings) # Serve the frontend (IMPORTANT: this serves index.html at root) @app.route("/", methods=["GET"]) def index(): # Serve index.html from same directory return send_from_directory(".", "index.html") # Optional small status endpoint (not the app root) @app.route("/status", methods=["GET"]) def status(): return jsonify({"message": "🌸 Serenity Emotional Support Chatbot API is running!"}) # Main chat endpoint @app.route("/chat", methods=["POST"]) def chat(): """ Expected JSON: { "session": "optional-session-id", "message": "user text", "personality": "calm" (optional) } Response JSON: { "reply": "...", "emotion": "sad|happy|calm|... ", "voice": "calm_male" (suggested voice key for frontend) } """ data = request.get_json(force=True) or {} session = data.get("session") or request.remote_addr or "default" text = (data.get("message") or "").strip() personality = (data.get("personality") or "calm").lower() if not text: return jsonify({"reply":"Please send a message.", "emotion":"neutral", "voice":"neutral"}) # initialize session data slot = MEMORY.get(session, {"name": None, "age": None, "last_mood": None, "history": []}) # optional: try to detect a name if not slot.get("name"): # very naive name detection if text.lower().startswith("i am ") or text.lower().startswith("i'm "): name = text.split()[2:3] if name: slot["name"] = name[0].capitalize() # Use OpenAI if available reply_text = None emotion_label = None voice_key = "neutral" if OPENAI_PRESENT: try: # Compose a short system prompt that balances support + casual + knowledge persona_text = { "calm": "You are calm, gentle, and concise.", "friendly": "You are warm, friendly and playful when appropriate.", "motivational": "You are upbeat, encouraging, motivational.", "spiritual": "You are nurturing, mindful, non-denominational.", "neutral": "You are neutral, clear, and friendly." }.get(personality, "You are calm and supportive.") system_msg = ( f"You are Serenity, an empathetic emotional-support assistant. {persona_text} " "Do not act like a therapist. If the user asks factual questions, answer clearly and then check in emotionally. " "Vary phrasing; avoid repeating the same canned sentences." ) # ask OpenAI to both classify emotion and reply in a friendly supportive tone # We'll call chat completion twice cheaply: classify then reply (keeps prompt simpler) classify_prompt = ( "Classify the primary emotion from the short user message. " "Respond with one word from: happy, sad, angry, worried, calm, neutral, motivated, fearful, love.\n\n" f"Message: {text}" ) classification = openai.ChatCompletion.create( model="gpt-3.5-turbo", messages=[{"role":"system","content":"You are a concise classifier."}, {"role":"user","content":classify_prompt}], temperature=0.0, max_tokens=6 ) emotion_label = (classification.choices[0].message.content.strip().split()[0].lower())[:20] except Exception: # fallback classification emotion_label = simple_emotion(text) try: # Now produce a reply using the persona/system prompt user_msg_for_ai = f"User: {text}" ai_resp = openai.ChatCompletion.create( model="gpt-3.5-turbo", messages=[ {"role":"system","content":system_msg}, {"role":"user","content":user_msg_for_ai} ], temperature=0.8, max_tokens=250 ) reply_text = ai_resp.choices[0].message.content.strip() except Exception as e: # fallback to simple reply if OpenAI fails reply_text = fallback_reply(text, personality, slot.get("name")) if not emotion_label: emotion_label = simple_emotion(text) else: # OpenAI not available — fallback behavior emotion_label = simple_emotion(text) reply_text = fallback_reply(text, personality, slot.get("name")) # choose a voice key suggestion for frontend to map to browser voices voice_map = { "calm": "calm_male", "friendly": "friendly_female", "motivational": "deep_male", "spiritual": "soothing_female", "neutral": "neutral" } voice_key = voice_map.get(personality, "neutral") # update memory slot["last_mood"] = emotion_label slot.setdefault("history", []).append({"user": text, "bot": reply_text, "ts": datetime.utcnow().isoformat()}) slot["history"] = slot["history"][-50:] MEMORY[session] = slot save_memory() return jsonify({"reply": reply_text, "emotion": emotion_label, "voice": voice_key}) if __name__ == "__main__": port = int(os.getenv("PORT", 7860)) app.run(host="0.0.0.0", port=port)