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)")