VoiceField / src /streamlit_app.py
jostlebot's picture
Add enhanced features: voice switching, quick reactions, and detailed somatic tracking
896e97c
raw
history blame
13.9 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 = "Nurturing"
if 'in_debrief' not in st.session_state:
st.session_state.in_debrief = False
if 'voice_transitions' not in st.session_state:
st.session_state.voice_transitions = []
# 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 selection and setup form
with st.form("setup_form"):
st.header("Set Up Your Exploration")
voice_type = st.selectbox(
"Choose the voice you'd like to explore:",
["Nurturing", "Challenging", "Neutral"],
help="""
πŸ«‚ Nurturing: Warm, empathetic, and supportive. Creates safety and attunement.
πŸ” Challenging: Direct, provocative, growth-oriented. Pushes edges with respect.
βš–οΈ Neutral: Objective, clear, and grounded. Holds space without strong emotional coloring.
"""
)
scenario = st.text_area(
"What would you like to explore or discuss?",
placeholder="Example: I want to understand why I freeze when receiving feedback"
)
somatic_focus = st.text_area(
"What bodily sensations would you like to track?",
placeholder="Example: Tension in shoulders, breath patterns, gut responses"
)
goals = st.text_area(
"What are your exploration goals?",
placeholder="Example: Notice how different voices affect my nervous system activation"
)
submitted = st.form_submit_button("Begin Exploration")
if submitted:
st.session_state.current_voice = voice_type
st.session_state.voice_transitions = [(datetime.now().strftime("%H:%M:%S"), voice_type)]
# Prepare system message with voice parameters
voice_characteristics = {
"Nurturing": {
"style": "warm, empathetic, and supportive",
"language": "gentle, validating, and attuned",
"pacing": "slow and patient",
"focus": "creating safety and connection"
},
"Challenging": {
"style": "direct, provocative, and growth-oriented",
"language": "clear, bold, and respectfully confrontational",
"pacing": "dynamic and engaging",
"focus": "expanding awareness and capacity"
},
"Neutral": {
"style": "objective, clear, and grounded",
"language": "balanced, factual, and measured",
"pacing": "steady and consistent",
"focus": "observing patterns and processes"
}
}
voice = voice_characteristics[voice_type]
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}
Style: {voice['style']}
Language: {voice['language']}
Pacing: {voice['pacing']}
Focus: {voice['focus']}
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 invite somatic awareness with questions like:
- "What sensations are you noticing right now?"
- "How is your breath responding to this?"
- "Where do you feel this in your body?"
If the user types "debrief" or "end exploration", provide a comprehensive therapeutic debrief including:
1. **Somatic Patterns**:
- Track patterns in bodily responses across different voice types
- Note shifts in nervous system state (activation/settling)
- Identify somatic resources and challenges
2. **Nervous System Insights**:
- Connect responses to polyvagal theory (fight/flight/freeze/fawn)
- Explore window of tolerance and regulation capacity
- Highlight moments of co-regulation or dysregulation
3. **Relational Dynamics**:
- Analyze engagement patterns with each voice type
- Note attachment themes or protective patterns
- Identify relational resources and growth edges
4. **Integration Tools**:
- Offer specific somatic practices for regulation
- Suggest relational experiments for expanding capacity
- Provide nervous system education relevant to their experience
5. **Growth Edges**:
- Frame challenges within a developmental context
- Suggest gentle next steps for expanding capacity
- Normalize and validate protective responses
6. **Therapeutic Frame**:
- Provide relevant psychoeducation about their patterns
- Connect individual experience to broader human themes
- Offer compassionate reframes of challenges
Maintain a warm, psychodynamically-informed therapeutic voice in the debrief.
"""
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:
# Voice switching interface
if not st.session_state.in_debrief:
new_voice = st.selectbox(
"Switch voice type:",
["Nurturing", "Challenging", "Neutral"],
index=["Nurturing", "Challenging", "Neutral"].index(st.session_state.current_voice)
)
if new_voice != st.session_state.current_voice:
st.session_state.current_voice = new_voice
timestamp = datetime.now().strftime("%H:%M:%S")
st.session_state.voice_transitions.append((timestamp, new_voice))
st.rerun()
st.subheader(f"Conversation with {st.session_state.current_voice} Voice")
# Display chat history
for message in st.session_state.messages:
with st.chat_message(message["role"]):
st.markdown(message["content"])
# Chat input
if prompt := st.chat_input(f"Chat with {st.session_state.current_voice} voice (type 'debrief' when ready to reflect)"):
# Check for debrief trigger
if prompt.lower() in ["debrief", "end exploration"] and not st.session_state.in_debrief:
st.session_state.in_debrief = True
# Prepare debrief request
voice_history = "\n".join([
f"[{time}] Switched to {voice} voice"
for time, voice in st.session_state.voice_transitions
])
journal_summary = "\n".join([
f"[{entry['timestamp']}] {entry['note']}"
for entry in st.session_state.somatic_journal
])
prompt = f"""Please provide a therapeutic debrief for this somatic exploration:
Voice Transitions:
{voice_history}
Somatic Journal Entries:
{journal_summary}
Conversation History:
{chr(10).join([f"{msg['role']}: {msg['content']}" for msg in st.session_state.messages])}
"""
# 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)}")
with journal_col:
st.subheader("Somatic Journal")
# Quick reaction buttons
st.write("Quick Reactions:")
reaction_cols = st.columns(3)
reactions = {
"Nervous System": ["πŸ”΄ Activated", "🟑 Alert", "🟒 Settled"],
"Emotional": ["😌 Safe", "😰 Tense", "😢 Numb"],
"Engagement": ["πŸ‘₯ Connected", "πŸ›‘οΈ Protected", "❌ Disconnected"]
}
for i, (category, buttons) in enumerate(reactions.items()):
with reaction_cols[i]:
st.write(f"**{category}**")
for button in buttons:
if st.button(button):
timestamp = datetime.now().strftime("%H:%M:%S")
st.session_state.somatic_journal.append({
"timestamp": timestamp,
"note": f"Quick reaction: {button}"
})
st.rerun()
st.markdown("""
Use this space to note bodily sensations, emotions, and nervous system responses as they arise.
Each entry will be automatically timestamped.
πŸ’‘ Consider tracking:
- Physical sensations
- Breath patterns
- Muscle tension/release
- Energy levels
- Emotional shifts
- Relational impulses
""")
# Journal input
journal_entry = st.text_area("What are you noticing in your body right now?", key="journal_input")
if st.button("Add Journal Entry"):
if journal_entry:
timestamp = datetime.now().strftime("%H:%M:%S")
st.session_state.somatic_journal.append({
"timestamp": timestamp,
"note": journal_entry
})
st.rerun()
# Display journal entries
st.markdown("### Journal Timeline")
for entry in reversed(st.session_state.somatic_journal):
st.markdown(f"""
**{entry['timestamp']}**
{entry['note']}
---
""")
# Add restart button after debrief
if st.session_state.in_debrief:
if st.button("Start New Exploration"):
st.session_state.setup_complete = False
st.session_state.in_debrief = False
st.session_state.messages = []
st.session_state.somatic_journal = []
st.session_state.voice_transitions = []
st.session_state.system_message = ""
st.session_state.current_voice = "Nurturing"
st.rerun()
# Footer
st.markdown("---")
st.markdown(
"Created by [Jocelyn Skillman LMHC](http://www.jocelynskillman.com) | "
"Learn more: [@jocelynskillmanlmhc](https://jocelynskillmanlmhc.substack.com/)"
)