File size: 10,211 Bytes
4c59e23 e64cf98 4c59e23 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 |
import os
import streamlit as st
# import google.generativeai as gen_ai # Removed Google import
import openai # Added OpenAI import
import pyttsx3
import threading
from dotenv import load_dotenv
# Load environment variables
load_dotenv()
# Configure Streamlit page settings
st.set_page_config(
page_title="ShadowBox",
page_icon="🖤", # Favicon - a simple heart or other calm icon
layout="centered",
)
# Retrieve OpenAI API Key
# Google_API_Key = os.getenv("Google_API_Key") # Removed Google Key
# if not Google_API_Key:
# st.error("Google API Key not found. Please set the Google_API_Key environment variable.")
# st.stop()
OpenAI_API_Key = os.getenv("OPENAI_API_Key") # Added OpenAI Key check
if not OpenAI_API_Key:
st.error("OpenAI API Key not found. Please set the OPENAI_API_Key environment variable.")
st.stop()
# Set up OpenAI Client
try:
# gen_ai.configure(api_key=Google_API_Key) # Removed Google config
# model = gen_ai.GenerativeModel('gemini-1.5-flash') # Removed Google model init
client = openai.OpenAI(api_key=OpenAI_API_Key) # Added OpenAI client init
except Exception as e:
# st.error(f"Failed to configure Google AI: {e}") # Updated error message
st.error(f"Failed to configure OpenAI client: {e}")
st.stop()
# Function to translate roles between Gemini-Pro and Streamlit terminology
# def translate_role_for_streamlit(user_role): # This function is no longer needed for OpenAI structure
# return "assistant" if user_role == "model" else user_role
# Function to handle text-to-speech (TTS) in a separate thread
# Consider if TTS aligns with the "calm, slow" UX later
def speak_text(text):
try:
engine = pyttsx3.init()
engine.say(text)
engine.runAndWait()
except Exception as e:
print(f"TTS Error: {e}") # Log TTS errors quietly
# Initialize chat session in Streamlit if not already present
# Changed session state key from chat_session to messages
if "messages" not in st.session_state:
# Load the system prompt from the file
try:
with open("system_prompt.txt", "r", encoding="utf-8") as f:
system_prompt = f.read()
st.session_state.messages = [{"role": "system", "content": system_prompt}]
except FileNotFoundError:
st.error("System prompt file (system_prompt.txt) not found. Cannot initialize chat.")
st.stop()
except Exception as e:
st.error(f"Failed to load system prompt: {e}")
st.stop()
# Initialize with a system message or starting message if desired # Commented out
# For now, just an empty list # Commented out
# st.session_state.messages = [] # Commented out
# Example with initial system prompt (uncomment if needed): # Commented out
# st.session_state.messages = [{"role": "system", "content": "You are ShadowBox, a calm AI companion."}] # Commented out
# --- Sidebar Content ---
with st.sidebar:
st.header("👁🗨 Box your Shadows")
st.markdown("Start a chat in the main window.")
st.divider()
st.header("🤝 About ShadowBox")
st.markdown("""
**What Is ShadowBox?**
Not a therapist, hotline, or fixer. It's a slow, anonymous, digital companion for youth carrying thoughts they don't feel safe saying out loud—especially about harming others or themselves.
It offers a place to say the unspeakable without fear, met with dignity, not danger.
*(More details in the full 'About' section - TBD)*
""")
st.divider()
st.header("📚 Resources + Ethics")
st.markdown("""
**How ShadowBox Works:** Runs on a carefully trained generative AI prompt structure, shaped by clinical insight for accountability, distress tolerance, and respect for voice.
**Privacy:** Does NOT collect identity, IP, or store conversations. No tracking. No surveillance. You are witnessed, not watched.
*(More details in the full 'Resources & Ethics' section - TBD)*
""")
st.divider()
st.header("🧠 Why This Matters")
st.markdown("""
Addresses the gap in how systems respond to youth with intrusive thoughts. Grounded in clinical wisdom, developmental attunement, and radical respect. Offers a practice field for emotional honesty.
*(More details in the full 'Why This Matters' section - TBD)*
""")
st.divider()
st.header("📖 A Short History")
st.markdown("""
Born from hope and fear about AI's role in mental health. Youth already turn to AI; ShadowBox aims to provide a *designed relationship* grounded in ethics, safety, and clinical insight.
*(More details in the full 'A Short History' section - TBD)*
""")
st.divider()
st.header("🆘 Need Help Right Now?")
st.warning("ShadowBox is not a crisis line. If you need immediate support from real people:")
st.markdown("""
* **Call or Text 988:** (Suicide & Crisis Lifeline) - 24/7, free, confidential.
* **The Trevor Project:** 1-866-488-7386 or Text 'START' to 678-678 (for LGBTQIA+ youth)
* **Crisis Text Line:** Text 'HOME' to 741741
* **YouthLine:** Text 'teen2teen' to 839863 or Call 1-877-968-8491 (teens helping teens)
* **In an Emergency:** Call 911 or go to the nearest ER. State: "I need mental health support. This is not a crime."
""")
# --- Main Page Content ---
st.markdown("<h1 style='text-align: center; color: #333;'>ShadowBox</h1>", unsafe_allow_html=True)
st.markdown("<p style='text-align: center; font-size: 18px; color: #555;'>An Anonymous AI Chat to Box Shadows</p>", unsafe_allow_html=True)
st.markdown("""
Welcome. We've found our way to a special space—a place made to hold what's hard to say out loud.
ShadowBox is a digital companion, powered by generative AI and grounded in clinical mental health care, designed for youth navigating the stormy terrain of thoughts like rage, despair, and even violent ideas or urges.
ShadowBox stays present and warm with the hardest parts of ourselves so we can learn to, too.
It isn't a hotline. It's not therapy. And it's definitely not surveillance. The thoughts that alarm us don't set off alarms here.
Lots of us—more than we think—have experienced scary or unwanted thoughts. Thoughts of harm. Thoughts about hurting ourselves or others. These thoughts don't make us dangerous. They make us human.
Intrusive thoughts are often how the brain reacts to stress. When our nervous system feels overwhelmed, it can kick into fight, flight, or freeze. Sometimes the urge to harm is our brain's way of trying to protect us, push pain away, or signal an unmet need.
Sharing these thoughts can feel scary, fearing judgment or consequences. ShadowBox is a bridge—a place to build internal safety and practice sharing shadow parts before connecting with others.
*"Every act of violence is a tragic expression of an unmet need."* — Marshall Rosenberg
---
**Important Notes:**
* **Safety & Privacy:** ShadowBox is designed to care for dark thoughts but won't generate harmful content. **It does not store your identity or conversations.** No accounts, no tracking. Your privacy is paramount. Using *this* platform is designed for anonymity.
* **Not Therapy:** You're talking to AI shaped by a therapist, not a real person. It's intended as a bridge to human support. It can't feel, breathe with you, or hug you—things we all need.
* **Warmth & Education:** Responses aim for warmth and friendship. It might offer mental health education, encourage connection, or suggest grounding practices. It remembers the *current* conversation but saves nothing long-term.
* **Mandatory Reporting:** This bot is **NOT** a mandatory reporter. It can help explore sharing difficult feelings safely. *If you express a serious, immediate plan to harm yourself or someone else, standard mental health practice involves a 'Duty to Warn' for safety. Learn more [here](link_to_confidentiality_info - TBD).*
---
This is a prototype. Feedback is valuable (link to feedback form - TBD).
""")
# Display chat history
# Add a system message/intro from ShadowBox? (TBD based on prompt)
# Updated loop to work with the new messages list structure
for message in st.session_state.messages:
# Filter out system messages from display if they exist
if message["role"] in ["user", "assistant"]:
with st.chat_message(message["role"]):
st.markdown(message["content"])
# User input field
user_prompt = st.chat_input("You can start with silence. Or just 'hi'...")
# If user enters a prompt
if user_prompt:
# Append user's message to the session state list
st.session_state.messages.append({"role": "user", "content": user_prompt})
# Display user's message
st.chat_message("user").markdown(user_prompt)
# Show a loading indicator while waiting for a response
with st.spinner("..."): # Simpler spinner
try:
# Replace Gemini API call with OpenAI API call
# gemini_response = st.session_state.chat_session.send_message(user_prompt)
# response_text = gemini_response.text
openai_response = client.chat.completions.create(
model="gpt-4o",
messages=st.session_state.messages # Pass the entire history
)
response_text = openai_response.choices[0].message.content
# Append assistant's response to the session state list
st.session_state.messages.append({"role": "assistant", "content": response_text})
except Exception as e:
response_text = f"Sorry, I encountered an error: {e}"
st.error(response_text) # Display error in chat too
# Display assistant's response
if response_text: # Check if response_text was successfully generated
with st.chat_message("assistant"):
st.markdown(response_text)
# Run text-to-speech in the background (Optional)
# Consider removing if it clashes with the calm UX
# threading.Thread(target=speak_text, args=(response_text,), daemon=True).start()
pass # TTS disabled for now to maintain calm UX
st.markdown("---")
st.caption("ShadowBox created by Jocelyn Skillman, LMHC. [Learn More](link_to_substack - TBD)")
|