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