Spaces:
Sleeping
Sleeping
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/)" | |
) | |