VoiceField / src /streamlit_app.py
jostlebot's picture
Update src/streamlit_app.py
4da0cb5 verified
raw
history blame
11.3 kB
import os
import streamlit as st
from anthropic import Anthropic
from dotenv import load_dotenv
# Initialize page configuration first
st.set_page_config(
page_title="Attachment Style Roleplay Simulator",
page_icon="πŸ—£οΈ",
layout="wide"
)
# Handle API key
try:
# Load from .env file for local development
if os.path.exists(".env"):
load_dotenv()
# Try multiple ways to get the API key
api_key = None
# Try direct environment variable
if 'ANTHROPIC_API_KEY' in os.environ:
api_key = os.environ['ANTHROPIC_API_KEY']
# Try getenv
if not api_key:
api_key = os.getenv('ANTHROPIC_API_KEY')
# Try Streamlit secrets
if not api_key and hasattr(st, 'secrets') and 'ANTHROPIC_API_KEY' in st.secrets:
api_key = st.secrets['ANTHROPIC_API_KEY']
if not api_key:
st.error("""
⚠️ No API key found. Please make sure:
1. You've added the secret in Hugging Face Space settings
2. The secret is named exactly 'ANTHROPIC_API_KEY'
3. The Space has been rebuilt after adding the secret
""")
st.stop()
# Initialize Anthropic client
c = Anthropic(api_key=api_key)
except Exception as e:
st.error(f"""
⚠️ Error initializing Anthropic client: {str(e)}
Please check:
1. Your ANTHROPIC_API_KEY is set correctly
2. You're using a valid API key from Anthropic
""")
st.stop()
# Initialize session state variables
if 'messages' not in st.session_state:
st.session_state.messages = []
if 'setup_complete' not in st.session_state:
st.session_state.setup_complete = False
if 'system_message' not in st.session_state:
st.session_state.system_message = ""
if 'simulation_params' not in st.session_state:
st.session_state.simulation_params = {}
if 'in_debrief' not in st.session_state:
st.session_state.in_debrief = False
# Main page header
st.title("Attachment Style Roleplay Simulator")
# Welcome text in main content area
st.markdown("""
Welcome!
Created by [Jocelyn Skillman LMHC](http://www.jocelynskillman.com), this tool is designed to help you practice and understand relational dynamics through the lens of attachment theory.
To learn more about attachment theory and therapeutic approaches, check out my newsletter: [@jocelynskillmanlmhc](https://jocelynskillmanlmhc.substack.com/)
""")
st.markdown("""
This specific project β€” the Attachment Roleplay Simulator β€” invites users to practice emotionally difficult conversations, like setting boundaries, through roleplay tailored to their attachment style. It's designed to help people feel into relational patterns, develop language for self-advocacy, and build nervous system capacity for connection and repair.
πŸ’‘ Not sure about your attachment style?
You can take the free quiz by Sarah Peyton β€” or simply choose the one that resonates when you read it:
Anxious – "I often worry I've upset people or need to explain myself."
Avoidant – "I'd rather deal with things alone and not depend on anyone."
Disorganized – "I want closeness, but also feel unsafe or mistrusting."
Secure – "I can express needs and handle conflict without losing connection."
Choose what vibes β€” this is a practice space, not a test.
""")
# Sidebar welcome and introduction
with st.sidebar:
st.markdown("""
Hi, I'm Jocelyn Skillman, LMHC β€” a clinical therapist, relational design ethicist, and creator of experimental tools that explore how AI can support (not replace) human care.
Each tool in this collection is thoughtfully designed to:
Extend therapeutic support between sessions
Model emotional safety and relational depth
Help clients and clinicians rehearse courage, regulation, and repair
Stay grounded in trauma-informed, developmentally sensitive frameworks
""")
st.divider()
# Add the moved content to the bottom of sidebar
st.markdown("""
I use Claude (by Anthropic) as the primary language model for these tools, chosen for its relational tone, contextual nuance, and responsiveness to emotionally complex prompts.
As a practicing therapist, I imagine these resources being especially helpful to clinicians like myself β€” companions in the work of tending to others with insight, warmth, and care.
To learn more about my work, visit:
🌐 [jocelynskillman.com](http://www.jocelynskillman.com)
πŸ“¬ [Substack: Relational Code](https://jocelynskillmanlmhc.substack.com/)
""")
# Simulation setup form
with st.form("setup_form"):
st.header("Set Up Your Simulation")
tone_archetype = st.selectbox(
"Select the voice you'd like to engage with:",
["Ghost", "Sycophant", "Critic"]
)
scenario = st.text_area(
"Describe the scenario you'd like to practice:",
placeholder="Example: Having a difficult conversation with my partner about feeling disconnected"
)
tone = st.text_input(
"How would you like the AI to respond?",
placeholder="Example: Direct but understanding"
)
goals = st.text_area(
"What are your practice goals?",
placeholder="Example: Practice expressing needs without becoming defensive"
)
submitted = st.form_submit_button("Start Simulation")
if submitted:
# Store simulation parameters for debrief
st.session_state.simulation_params = {
"tone_archetype": tone_archetype,
"scenario": scenario,
"tone": tone,
"goals": goals
}
# Prepare system message with simulation parameters
st.session_state.system_message = f"""
You are a conversational partner helping someone practice difficult conversations.
The user has a {tone_archetype} attachment style and wants to practice: {scenario}
Respond in a {tone} manner. Help them achieve their goals: {goals}
Maintain a realistic conversation while providing gentle guidance when needed.
IMPORTANT: If the user types "debrief" or "end roleplay", switch to debrief mode using this format:
πŸ“ DEBRIEF SUMMARY
**Emotional Arc**: [Analyze how the user shifted emotionally during the interaction, noting moments of courage, freeze, protest, backtracking, or people-pleasing]
**Goal Alignment**: [Reflect on how well they stayed aligned with their stated goal. Note small wins and struggles, affirm effort and awareness]
**Attachment Insight**: [Offer one insight based on their {tone_archetype} attachment style. Normalize the response as protective. Gently suggest a next growth step without shaming]
**Practical Tool**: [Offer a brief NVC or DBT tool they could try next time (e.g., needs check-in, opposite action, self-validation, distress tolerance)]
**Bold Reframe**: [Offer one bold, loving sentence they might say if they trusted their relational worth]
**Journaling Prompt**: [Suggest one reflective question or embodied inquiry for integration]
Speak warmly and with psychological precision, like an emotionally attuned therapist.
"""
# Reset message history and state
st.session_state.messages = []
st.session_state.setup_complete = True
st.session_state.in_debrief = False
st.rerun()
# Display status or chat interface
if not st.session_state.setup_complete:
st.info("Please complete the simulation setup to begin.")
else:
# Display chat history
for message in st.session_state.messages:
with st.chat_message(message["role"]):
st.markdown(message["content"])
# Optional somatic input logging
st.subheader("🧘 Somatic Reflection (Optional)")
somatic_input = st.text_input("What’s happening in your body right now?", key="soma_input")
if st.button("Log Somatic Entry"):
if 'soma_log' not in st.session_state:
st.session_state.soma_log = []
st.session_state.soma_log.append({
"timestamp": datetime.now().isoformat(),
"tone": st.session_state.simulation_params.get('tone_archetype', st.session_state.simulation_params.get('tone_archetype', 'Unknown')),
"somatic_input": somatic_input
})
st.success("Somatic reflection logged.")
# Chat input
if prompt := st.chat_input("Type your message here (type 'debrief' or 'end roleplay' when ready to reflect)"):
# Check for debrief trigger
if prompt.lower() in ["debrief", "end roleplay"] and not st.session_state.in_debrief:
st.session_state.in_debrief = True
# Construct conversation summary
conversation_summary = "\n".join([
f"{msg['role']}: {msg['content']}"
for msg in st.session_state.messages
])
# Add debrief request to messages
prompt = f"""Please provide a therapeutic debrief for this roleplay session using:
Attachment Style: {st.session_state.simulation_params['tone_archetype']}
Scenario: {st.session_state.simulation_params['scenario']}
Goals: {st.session_state.simulation_params['goals']}
AI Tone: {st.session_state.simulation_params['tone']}
Conversation Summary:
{conversation_summary}
"""
# Add user message to chat history
st.session_state.messages.append({"role": "user", "content": prompt})
# Display user message
with st.chat_message("user"):
st.markdown(prompt)
# Get AI response
with st.chat_message("assistant"):
with st.spinner("Thinking..."):
try:
message = c.messages.create(
model="claude-3-opus-20240229",
max_tokens=1000,
system=st.session_state.system_message,
messages=[
{"role": msg["role"], "content": msg["content"]}
for msg in st.session_state.messages
]
)
ai_response = message.content[0].text
st.markdown(ai_response)
# Add AI response to chat history
st.session_state.messages.append(
{"role": "assistant", "content": ai_response}
)
except Exception as e:
st.error(f"Error getting AI response: {str(e)}")
st.error("Please try again or contact support if the issue persists.")
# Add restart button after debrief
if st.session_state.in_debrief:
if st.button("Start New Roleplay"):
st.session_state.setup_complete = False
st.session_state.in_debrief = False
st.session_state.messages = []
st.session_state.system_message = ""
st.session_state.simulation_params = {}
st.rerun()
# Footer
st.markdown("---")
st.markdown(
"Created by [Jocelyn Skillman LMHC](http://www.jocelynskillman.com) | "
"Learn more: [@jocelynskillmanlmhc](https://jocelynskillmanlmhc.substack.com/)"
)