STRONGmind / app.py
Strong1Mindtherapist's picture
Update app.py
62ca45e verified
raw
history blame
19.1 kB
from gtts import gTTS
import tempfile
import json
import datetime
import gradio as gr
# ----------------------------
# User data & existing lists
# ----------------------------
user_info = {"name": "", "age": "", "gender": "", "language": "english" , "Guardian_info": ""}
chat_history = []
journal_entries = []
calm_tips = [
"Take 3 deep breaths.", "Listen to nature.", "Stretch your body.",
"Drink water.", "Think of one good thing today.", "Close your eyes for 1 minute.",
"Write your feelings.", "Smile at yourself.", "Imagine a peaceful place.",
"Say a positive affirmation."
]
study_tips = [
"Use Pomodoro: 25min study, 5min break", "Make a daily to-do list",
"Avoid multitasking", "Use color-coded notes", "Take 10-min exercise breaks",
"Sleep 7–9 hrs daily", "Drink water during study", "Use active recall",
"Study hardest topics first", "Test yourself often"
]
tip_index = {"calm": 0, "study": 0}
lang_codes = {
"english": "en", "hindi": "hi", "marathi": "mr", "bengali": "bn",
"tamil": "ta", "telugu": "te", "malayalam": "ml", "spanish": "es",
"french": "fr", "german": "de"
}
# (Keep your harmful_keywords and emotions as-is — shortened here for clarity)
harmful_keywords = [
# ... (keep the full list from your original file) ...
"suicide", "kill myself", "end my life", "harm myself", "cut myself",
"want to die", "die", "jump off", "self-harm", "self-hate", "suicidal",
# etc.
]
harmful_response = (
"⚠️ It sounds like you're going through a really tough time.\n\n"
"Please reach out to a professional:\n\n"
"🇮🇳 **India Helplines**\n"
"🧠 *Dr. Rachna Khanna Singh* – +91 99103 90559\n"
"📞 *iCall Helpline* – +91 9152987821\n"
"📞 *Vandrevala Foundation* – 1860 266 2345 or 1800 233 3330\n\n"
"🌍 **International Helplines**\n"
"📞 *Lifeline (USA)* – 988\n"
"📞 *Samaritans (UK)* – 116 123\n"
"📞 *Lifeline (Australia)* – 13 11 14\n\n"
"You are not alone. There are people who care and want to help 💚"
)
# Simplified emotions dictionary (keep your full mapping; trimmed in this view)
emotions = {
"sad": "I hear you 💙. I’m really sorry you’re feeling sad right now. Do you want to tell me what’s making you feel this way, or would you rather I just try to gently cheer you up?",
"anxiety": "It’s okay — anxiety can feel overwhelming, like your mind is running a race it didn’t sign up for. Try slowing down your breathing: inhale for 4 seconds, hold for 4, exhale for 6.",
"angry": "It’s normal to feel angry — it’s your mind’s way of telling you something feels unfair or hurtful.",
# ... keep the rest of your emotion replies ...
}
# ----------------------------
# Conversation state for Hybrid (C) memory model
# ----------------------------
conversation_state = {
"emotion": None, # e.g., "sad"
"context": None, # e.g., "school", "parents", "friend", "exam"
"followup_stage": 0, # how many follow-ups we've done for this emotion
"turns_since_set": 0 # used to expire after a small idle period if needed
}
# Keywords to detect context quickly
context_markers = {
"school": ["school", "exam", "test", "teacher", "class"],
"parents": ["parent", "mom", "dad", "parents", "home"],
"friend": ["friend", "friends", "bully", "shouted", "argument", "fight"],
"health": ["sick", "unwell", "hospital", "doctor"],
"love": ["crush", "boyfriend", "girlfriend", "heartbroken", "love"],
# add more markers as needed
}
# Follow-up templates (3 steps each, then action suggestions)
followups = {
"sad": [
"I’m really sorry you’re feeling sad. Can you tell me if this happened recently or is it been building up?",
"Thank you for sharing. How did that make you feel — more hurt, embarrassed, or something else?",
"That sounds heavy. Would you like a calming exercise, to write about it in the journal, or to talk more?"
],
"anxiety": [
"Anxiety can be overwhelming. Is it about something specific like exams or relationships?",
"When your anxiety spikes, do you notice physical signs like a racing heart or trouble breathing?",
"Would you like to try a short breathing exercise now, or do you want to talk about what’s triggering it?"
],
"angry": [
"Anger is a valid response. Do you want to share what caused this anger?",
"When you feel angry, what usually helps — space, talking, or doing something active?",
"Would you like ideas to safely express the anger, or would you prefer calming techniques first?"
],
# fallback for other emotions:
"default": [
"Tell me more about that — when did you start feeling this way?",
"How strong does this feeling feel on a scale of 1 to 10?",
"Would you like a calming activity, a journal prompt, or to continue talking?"
]
}
# Reset phrases (user indicates they are fine or want to change topic)
reset_phrases = [
"i'm fine", "i am fine", "i'm okay", "i am okay", "i feel better",
"thank you", "thanks", "that's all", "let's talk about something else",
"stop", "no", "I'm good", "im good"
]
# ----------------------------
# Helper functions
# ----------------------------
def _contains_any(text, keywords):
text = text.lower()
for kw in keywords:
if kw in text:
return True
return False
def detect_harmful(text):
t = text.lower()
for kw in harmful_keywords:
if kw in t:
return True
return False
def detect_emotion_in_text(text):
t = text.lower()
# Prefer longer keys first (to avoid 'sad' matching inside other words) — optional improvement
sorted_keys = sorted(emotions.keys(), key=lambda x: -len(x))
for key in sorted_keys:
if key in t:
return key
return None
def detect_context_in_text(text):
t = text.lower()
for ctx, markers in context_markers.items():
for m in markers:
if m in t:
return ctx
return None
def is_reset_phrase(text):
t = text.lower()
for phrase in reset_phrases:
if phrase in t:
return True
return False
def build_empathy_reply(emotion):
# Base empathy from emotions dict, plus a gentle follow-up question from followups
base = emotions.get(emotion, "")
# Pick a gentle opener if no followups defined
return base
# ----------------------------
# Upgraded reply generator with memory, context, multi-turn flow
# ----------------------------
def generate_reply(input_text):
text = input_text.strip()
if not text:
return "Please say or type something when you are ready."
lower = text.lower()
# 1) Immediate harmful check (escalate without delay)
if detect_harmful(lower):
# Reset conversation state because we escalate
conversation_state["emotion"] = None
conversation_state["context"] = None
conversation_state["followup_stage"] = 0
conversation_state["turns_since_set"] = 0
return harmful_response
# 2) User reset intent — end current flow politely
if is_reset_phrase(lower):
conversation_state["emotion"] = None
conversation_state["context"] = None
conversation_state["followup_stage"] = 0
conversation_state["turns_since_set"] = 0
return "That's ok — we can pause here. If you'd like to talk again later, I'm right here."
# 3) Detect if user explicitly states a new emotion -> override current state
new_emotion = detect_emotion_in_text(lower)
if new_emotion and new_emotion != conversation_state["emotion"]:
# start new flow
conversation_state["emotion"] = new_emotion
conversation_state["context"] = detect_context_in_text(lower)
conversation_state["followup_stage"] = 0
conversation_state["turns_since_set"] = 0
# Compose reply: empathy + first follow-up
empathy = build_empathy_reply(new_emotion)
# get followup prompt
prompts = followups.get(new_emotion, followups["default"])
follow = prompts[0] if prompts else ""
# Combine neatly
combined = f"{empathy}\n\n{follow}"
return combined
# 4) If an emotion is already active, continue that flow
if conversation_state["emotion"]:
emotion = conversation_state["emotion"]
# Update context if we find context in this message
ctx = detect_context_in_text(lower)
if ctx:
conversation_state["context"] = ctx
# If the user provided details (short heuristic: message length > 20 or contains certain markers),
# treat this as progress and advance the follow-up stage
provided_detail = (len(lower) > 20) or any(w in lower for w in ["because", "so", "then", "after", "today", "yesterday", "last", "friend", "parents", "teacher", "exam", "test"])
stage = conversation_state["followup_stage"]
prompts = followups.get(emotion, followups["default"])
# If user asked a question (contains '?'), attempt to respond empathetically referencing emotion/context
if "?" in lower:
# Respond referencing emotion and context
ctx_text = f" about {conversation_state['context']}" if conversation_state["context"] else ""
response = f"I hear your question{ctx_text}. Let's look at that together. {emotions.get(emotion,'')}"
# do not advance stage on simple question
conversation_state["turns_since_set"] += 1
return response
if provided_detail:
# Provide a reply that references the emotion and context and then advance
ctx_text = ""
if conversation_state["context"]:
ctx_text = f" because of {conversation_state['context']}"
# Construct reflective reply
if stage < len(prompts):
response = f"I understand — it makes sense you'd feel {emotion}{ctx_text}. {prompts[stage]}"
conversation_state["followup_stage"] += 1
conversation_state["turns_since_set"] = 0
return response
else:
# Follow-ups exhausted; offer actionable choices
conversation_state["turns_since_set"] += 1
return ("You have shared a lot — would you like to: \n"
"1) Try a calming exercise now\n"
"2) Write this in your journal\n"
"3) Talk more about it\n\n"
"Type '1', '2', or '3' to choose, or tell me if you want a helpline.")
else:
# User gave a short reply — ask a clarifying / gentle follow-up without advancing too far
if stage < len(prompts):
response = prompts[stage]
# only advance if we already showed that prompt previously
conversation_state["turns_since_set"] += 1
# If we had asked the same prompt previously and user still gives short replies, advance stage
if conversation_state["turns_since_set"] > 1:
conversation_state["followup_stage"] = min(len(prompts), stage + 1)
conversation_state["turns_since_set"] = 0
return response
else:
# if stages done but no detail, ask for choice
return ("I’m listening. If it's hard to describe, choose: 1) calming exercise 2) journal 3) talk more. "
"Or say 'I'm fine' to stop.")
# 5) No active emotion and no new emotion detected -> try to detect context or ask to elaborate
ctx = detect_context_in_text(lower)
if ctx:
# Gentle probing
return f"You mentioned {ctx}. Is this making you feel upset or worried? How does it make you feel?"
# Default fallback
return "Tell me more about your day — what happened, or how are you feeling right now?"
# ----------------------------
# Chat function (uses TTS + conversation_state)
# ----------------------------
def chat_function(audio_input, text_input):
# We ignore audio_input here for text-only flow; you can add speech-to-text to use it.
user_text = (text_input or "").strip()
if not user_text:
return "Please type something.", None
reply = generate_reply(user_text)
# Save conversation to history
chat_history.append({"user": user_text, "bot": reply, "emotion": conversation_state.get("emotion"), "context": conversation_state.get("context")})
# Text-to-speech (language selection)
lang_code = lang_codes.get(user_info["language"], "en")
try:
tts = gTTS(reply, lang=lang_code)
audio_file = tempfile.NamedTemporaryFile(delete=False, suffix=".mp3")
tts.save(audio_file.name)
voice_path = audio_file.name
except Exception as e:
# If TTS fails, still return the reply and no voice
voice_path = None
return reply, voice_path
# ----------------------------
# Remaining utility functions (unchanged)
# ----------------------------
def set_personal_info(name, age, gender, language, Guardian_info):
user_info.update({"name": name, "age": age, "gender": gender, "language": language, "Guardian_info": Guardian_info,})
return gr.update(visible=True), f"✅ Welcome {name}! Preferences saved."
def show_personal_data():
today = datetime.date.today().strftime("%Y-%m-%d (%A)")
return f"📅 {today}\n👤 Name: {user_info['name']}\n🎂 Age: {user_info['age']}\n♀ Gender: {user_info['gender']}\n🌐 Language: {user_info['language']}\n Guardian_info: {user_info['Guardian_info']}"
def get_chat_history():
if not chat_history:
return "No conversations yet."
return "\n\n".join([f"You: {c['user']}\nBot: {c['bot']}" for c in chat_history])
def save_journal(entry):
journal_entries.append(entry)
with open("journal.json", "w") as f:
json.dump(journal_entries, f)
return "✅ Journal saved!"
def show_journal_history():
return "\n---\n".join(journal_entries) if journal_entries else "No journal entries yet."
def next_calm_tip():
tip = calm_tips[tip_index["calm"] % len(calm_tips)]
tip_index["calm"] += 1
return tip
def next_study_tip():
tip = study_tips[tip_index["study"] % len(study_tips)]
tip_index["study"] += 1
return tip
# ----------------------------
# Gradio UI (kept same as your original file)
# ----------------------------
with gr.Blocks() as app:
welcome_screen = gr.Column(visible=True)
full_app = gr.Tabs(visible=False)
with welcome_screen:
gr.HTML("""
<style>
#main-title {
text-align: center;
font-size: 2.5em;
}
#subtitle {
text-align: center;
font-size: 1.2em;
color: #ccc;
margin-bottom: 30px;
}
#start-btn {
background-color: #ff69b4;
color: white;
font-weight: bold;
border: none;
border-radius: 10px;
padding: 14px 45px;
font-size: 1.1em;
cursor: pointer;
display: block;
margin: 0 auto;
}
#start-btn:hover {
background-color: #ff85c1;
}
</style>
""")
gr.Markdown("<h1 id='main-title'>StrongMind Therapist 3.0</h1>")
gr.Markdown("<h3 id='subtitle'>Your peaceful space to talk, journal, and focus.</h3>")
start_button = gr.Button("🌸 Get Started", elem_id="start-btn")
def start_app():
return gr.update(visible=False), gr.update(visible=True)
start_button.click(start_app, outputs=[welcome_screen, full_app])
with full_app:
with gr.Tab("1️⃣ Personal Info"):
name = gr.Textbox(label="Name")
age = gr.Textbox(label="Age")
gender = gr.Dropdown(["Male", "Female", "Other"], label="Gender")
language = gr.Dropdown(list(lang_codes.keys()), label="Preferred Language")
Guardian_info = gr.Textbox(label="Guardian information")
btn = gr.Button("Save Info")
popup = gr.Markdown(visible=False, elem_classes="alert-box")
btn.click(set_personal_info, [name, age, gender, language,Guardian_info], [popup, popup])
with gr.Tab("2️⃣ Personal Info Data"):
show = gr.Button("Show My Info")
info_display = gr.Textbox(lines=6)
show.click(show_personal_data, outputs=info_display)
with gr.Tab("3️⃣ Chat"):
gr.Markdown("🗣️ Describe your day in one word.")
audio_input = gr.Audio(type="filepath", label="🎧 Say something")
text_input = gr.Textbox(label="⌨️ Or type here")
send = gr.Button("Send")
bot_reply = gr.Textbox(label="🧠 Therapist")
voice = gr.Audio(label="🔊 Voice Reply")
send.click(chat_function, [audio_input, text_input], [bot_reply, voice])
with gr.Tab("4️⃣ Chat History"):
show_history = gr.Button("📜 Show Chats")
chat_out = gr.Textbox(lines=20, label="History")
show_history.click(get_chat_history, outputs=chat_out)
with gr.Tab("5️⃣ Journal"):
journal_input = gr.Textbox(lines=6, label="Write your thoughts")
save = gr.Button("Save")
journal_status = gr.Textbox()
save.click(save_journal, journal_input, journal_status)
with gr.Tab("6️⃣ Journal History"):
view = gr.Button("View Past Entries")
past = gr.Textbox(lines=15, label="Previous Journals")
view.click(show_journal_history, outputs=past)
with gr.Tab("7️⃣ Calm Space"):
tip_btn = gr.Button("🌿 Give Me a Calm Tip")
calm_text = gr.Textbox()
tip_btn.click(next_calm_tip, outputs=calm_text)
with gr.Tab("8️⃣ Study Tips"):
tip_btn2 = gr.Button("📚 Study Tip")
study_text = gr.Textbox()
tip_btn2.click(next_study_tip, outputs=study_text)
with gr.Tab("9️⃣ Pomodoro"):
gr.Markdown("⏱️ Use 25 min study + 5 min break cycles.\n(For real timer, use front-end JavaScript or Android timers)")
with gr.Tab("💠 Games"):
gr.Markdown("🎮 Play from the game portal below:")
gr.HTML(
'''
<iframe
src="https://www.onlinegames.io/embed/portal/"
width="100%"
height="600"
frameborder="0"
allowfullscreen>
</iframe>
'''
)
if __name__ == "__main__":
app.launch()