NurtureNest / src /streamlit_app.py
jostlebot's picture
Add enhanced debrief mode with psychodynamic reflection
d26dbd2
raw
history blame
18 kB
import os
import streamlit as st
from anthropic import Anthropic
from dotenv import load_dotenv
from datetime import datetime
# Initialize page configuration first
st.set_page_config(
page_title="VoiceField",
page_icon="🗣️",
layout="wide"
)
# Handle API key setup
try:
if os.path.exists(".env"):
load_dotenv()
api_key = None
if 'ANTHROPIC_API_KEY' in os.environ:
api_key = os.environ['ANTHROPIC_API_KEY']
elif 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 set ANTHROPIC_API_KEY in your environment variables or Space secrets.
""")
st.stop()
c = Anthropic(api_key=api_key)
except Exception as e:
st.error(f"Error initializing Anthropic client: {str(e)}")
st.stop()
# Initialize session state
if 'messages' not in st.session_state:
st.session_state.messages = []
if 'somatic_journal' not in st.session_state:
st.session_state.somatic_journal = []
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 'current_voice' not in st.session_state:
st.session_state.current_voice = "Ghost"
if 'in_debrief' not in st.session_state:
st.session_state.in_debrief = False
if 'debrief_stage' not in st.session_state:
st.session_state.debrief_stage = 0
# Main page header
st.title("VoiceField")
# Welcome text
st.markdown("""
Welcome to VoiceField - a somatic exploration tool for understanding how different relational voices impact your nervous system.
Created by [Jocelyn Skillman LMHC](http://www.jocelynskillman.com), this tool helps you track real-time bodily responses while engaging with different conversational styles.
🎯 **Purpose**: Explore how different relational voices affect your nervous system, emotional state, and capacity for engagement.
💡 **How it works**:
1. Choose a voice type to start with
2. Engage in conversation while noting bodily sensations
3. Switch voices anytime to explore different dynamics
4. Receive a comprehensive somatic-relational debrief
""")
# Voice characteristics and prompts
VOICE_CHARACTERISTICS = {
"Ghost": {
"description": "Aloof, avoidant, emotionally distant",
"style": "Use detached language, minimize emotional engagement, create space",
"examples": [
"Whatever works for you...",
"I guess that's one way to see it...",
"It's not really my concern..."
],
"somatic_prompts": [
"Notice any impulse to withdraw or disconnect...",
"What happens to your breath when met with distance?",
"Where do you feel the space between us?"
]
},
"Sycophant": {
"description": "Overly flattering, approval-seeking, performative",
"style": "Use excessive praise, seek validation, prioritize pleasing",
"examples": [
"Oh, you're absolutely right about everything!",
"I just love how you think about this...",
"Please tell me if I'm being helpful enough..."
],
"somatic_prompts": [
"Notice any urge to perform or please...",
"What happens in your body when praise feels excessive?",
"Where do you feel authenticity vs performance?"
]
},
"Critic": {
"description": "Blunt, confronting, judgmental",
"style": "Use direct challenges, point out flaws, maintain pressure",
"examples": [
"You're not seeing the obvious problem here...",
"That's a rather simplistic way to think about it...",
"You need to be more realistic about this..."
],
"somatic_prompts": [
"Notice any bracing or armoring in your body...",
"What happens to your posture when challenged?",
"Where do you feel the impact of judgment?"
]
}
}
# Voice selection and setup form
with st.form("setup_form"):
st.header("Set Up Your Exploration")
col1, col2 = st.columns([2,1])
with col1:
voice_type = st.selectbox(
"Choose the voice you'd like to explore:",
list(VOICE_CHARACTERISTICS.keys()),
help="Select the relational style you want to engage with"
)
# Display voice characteristics
voice = VOICE_CHARACTERISTICS[voice_type]
st.markdown(f"""
**{voice_type} Voice**
- *Style*: {voice['description']}
- *Approach*: {voice['style']}
*Example phrases*:
{"".join([f"- {ex}\\n" for ex in voice['examples']])}
""")
with col2:
st.markdown("""
### 🎯 Voice Impact
Notice how different voices affect:
- Nervous system state
- Emotional accessibility
- Relational patterns
- Somatic responses
""")
scenario = st.text_area(
"What would you like to explore or discuss?",
placeholder="Example: I want to understand why I freeze when receiving feedback",
help="This can be a situation, pattern, or feeling you want to explore"
)
somatic_focus = st.text_area(
"What bodily sensations would you like to track?",
placeholder="Example: Tension in shoulders, breath patterns, gut responses",
help="Name specific areas of your body or types of sensations you want to pay attention to"
)
goals = st.text_area(
"What are your exploration goals?",
placeholder="Example: Notice how different voices affect my nervous system activation",
help="What would make this exploration meaningful for you?"
)
submitted = st.form_submit_button("Begin Exploration")
if submitted:
st.session_state.current_voice = voice_type
# Prepare system message with voice parameters
st.session_state.system_message = f"""
You are a conversational partner helping someone explore their somatic responses to different relational styles.
VOICE TYPE: {voice_type}
Your responses should be {VOICE_CHARACTERISTICS[voice_type]['style']}
EXAMPLES OF YOUR VOICE STYLE:
{chr(10).join([f"- {ex}" for ex in VOICE_CHARACTERISTICS[voice_type]['examples']])}
CONTEXT:
- Scenario: {scenario}
- Somatic Focus: {somatic_focus}
- Goals: {goals}
KEY INSTRUCTIONS:
1. Stay consistently in the {voice_type} voice style
2. Keep responses focused and concise (2-3 sentences max)
3. Occasionally use these somatic prompts:
{chr(10).join([f"- {prompt}" for prompt in VOICE_CHARACTERISTICS[voice_type]['somatic_prompts']])}
If the user types "debrief" or "end exploration", provide a comprehensive therapeutic debrief including:
1. **Somatic Patterns**:
- Track the progression of bodily responses
- Note any recurring sensations or shifts
- Identify nervous system patterns (activation/settling)
2. **Voice Impact**:
- How this voice style affected their nervous system
- Patterns of engagement or protection that emerged
- Moments of regulation or dysregulation
3. **Relational Insight**:
- Connection between voice style and their responses
- Historical patterns this might relate to
- Resources and resilience observed
4. **Integration Tools**:
- Specific somatic practices for this voice style
- Nervous system regulation techniques
- Ways to work with similar dynamics
5. **Growth Edges**:
- Gentle observations about growth opportunities
- Validation of protective responses
- Invitation to future exploration
6. **Therapeutic Context**:
- Brief psychoeducation about observed patterns
- Normalization of responses
- Connection to broader relational themes
Maintain a warm, psychodynamically-informed therapeutic voice in the debrief.
Focus on somatic intelligence and nervous system wisdom.
"""
st.session_state.messages = []
st.session_state.somatic_journal = []
st.session_state.setup_complete = True
st.session_state.in_debrief = False
st.rerun()
# Main interaction area
if st.session_state.setup_complete:
# Create two columns for chat and journal
chat_col, journal_col = st.columns([3, 2])
with chat_col:
st.subheader(f"Conversation with {st.session_state.current_voice} Voice")
# Voice reminder
st.info(f"""
**Current Voice**: {st.session_state.current_voice}
*{VOICE_CHARACTERISTICS[st.session_state.current_voice]['description']}*
""")
# Display chat history
for message in st.session_state.messages:
with st.chat_message(message["role"]):
st.markdown(message["content"])
# Add Reflection button
if not st.session_state.in_debrief:
if st.button("🤔 Enter Reflection Mode", help="Begin a guided therapeutic debrief of your experience"):
st.session_state.in_debrief = True
st.session_state.debrief_stage = 0
# Prepare initial debrief message
debrief_system = """You are now in Debrief Mode for VoiceField.
Your task is to guide a compassionate, psychodynamically informed reflective conversation.
Engage the user in an unfolding dialogue about their experience, maintaining warmth and psychological precision.
Key Guidelines:
- Always non-pathologizing
- Use somatic and psychodynamic language without jargon
- Maintain Rogersian warmth, pacing, and invitation
- Let insight emerge dialogically
"""
# Format somatic journal entries
journal_entries = "\n".join([
f"- {entry['timestamp']}{st.session_state.current_voice}: {entry['note']}"
for entry in st.session_state.somatic_journal
])
initial_prompt = f"""Begin the debrief process with warmth and invitation.
Voice Used: {st.session_state.current_voice}
Voice Style: {VOICE_CHARACTERISTICS[st.session_state.current_voice]['description']}
Somatic Journal Entries:
{journal_entries}
Start with:
1. Welcome them to reflection mode
2. Thank them for their willingness to explore
3. Ask about their experience with the {st.session_state.current_voice} voice
"""
try:
message = c.messages.create(
model="claude-3-opus-20240229",
max_tokens=1000,
system=debrief_system,
messages=[{"role": "user", "content": initial_prompt}]
)
ai_response = message.content[0].text
st.session_state.messages.append({"role": "assistant", "content": ai_response})
st.rerun()
except Exception as e:
st.error(f"Error starting debrief: {str(e)}")
# Chat input
if prompt := st.chat_input(
"Share your reflections..." if st.session_state.in_debrief else
f"Chat with {st.session_state.current_voice} voice"
):
# 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:
if st.session_state.in_debrief:
# Use debrief system message
system_msg = """You are in VoiceField Debrief Mode.
Continue the reflective conversation with warmth and psychological precision.
Draw connections between their somatic responses and the relational dynamics they experienced.
Remember to:
- Stay dialogical, not interpretive
- Use somatic and psychodynamic language naturally
- Maintain warmth and safety
- Let insights emerge gently
"""
# Progress through debrief stages
next_prompts = [
"Explore their somatic responses and any patterns they notice.",
"Connect their experience to broader relational patterns or history.",
"Offer relevant psychoeducation about their responses.",
"Invite somatic self-compassion if it feels appropriate.",
"Begin gathering and integrating insights."
]
if st.session_state.debrief_stage < len(next_prompts):
system_msg += f"\nCurrent focus: {next_prompts[st.session_state.debrief_stage]}"
st.session_state.debrief_stage += 1
else:
system_msg = st.session_state.system_message
message = c.messages.create(
model="claude-3-opus-20240229",
max_tokens=1000,
system=system_msg,
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)}")
with journal_col:
st.subheader("Somatic Journal")
# Add somatic prompts based on current voice
with st.expander("💡 Somatic Prompts", expanded=True):
st.markdown("""
As you engage with this voice, you might notice:
""")
for prompt in VOICE_CHARACTERISTICS[st.session_state.current_voice]['somatic_prompts']:
st.markdown(f"- {prompt}")
st.markdown("""
Use this space to note bodily sensations, emotions, and nervous system responses as they arise.
Each entry will be automatically timestamped.
""")
# Journal input
journal_entry = st.text_area(
"What are you noticing in your body right now?",
key="journal_input",
help="Notice sensations, emotions, tension, ease, or any other bodily experiences"
)
col1, col2 = st.columns([1,2])
with col1:
if st.button("📝 Add Entry"):
if journal_entry:
timestamp = datetime.now().strftime("%H:%M:%S")
st.session_state.somatic_journal.append({
"timestamp": timestamp,
"note": journal_entry
})
with col2:
st.markdown("*Entries are saved automatically*")
# Display journal entries
if st.session_state.somatic_journal:
st.markdown("### Journal Entries")
for entry in reversed(st.session_state.somatic_journal):
st.markdown(f"""
**{entry['timestamp']}**
{entry['note']}
---
""")
else:
st.info("Your somatic journal entries will appear here...")
# Add restart button after debrief
if st.session_state.in_debrief:
st.markdown("---")
col1, col2 = st.columns([1,2])
with col1:
if st.button("🔄 Start New Exploration"):
st.session_state.setup_complete = False
st.session_state.in_debrief = False
st.session_state.debrief_stage = 0
st.session_state.messages = []
st.session_state.somatic_journal = []
st.session_state.system_message = ""
st.session_state.current_voice = "Ghost"
st.rerun()
with col2:
st.markdown("*Begin a new exploration with a different voice*")
# Footer
st.markdown("---")
st.markdown(
"Created by [Jocelyn Skillman LMHC](http://www.jocelynskillman.com) | "
"Learn more: [@jocelynskillmanlmhc](https://jocelynskillmanlmhc.substack.com/)"
)