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