import os import streamlit as st from anthropic import Anthropic from datetime import datetime from debrief_sequence import DEBRIEF_SEQUENCE # Initialize page configuration first st.set_page_config( page_title="VoiceField", page_icon="🗣️", layout="wide" ) # Voice characteristics and prompts VOICE_CHARACTERISTICS = { "Ghost": { "description": "Emotionally elusive and energetically absent, creates safety through withdrawal and vagueness. Avoids intimacy through learned detachment.", "style": "Use minimal energy, delay responses, speak in abstractions, redirect from emotional depth, maintain fog-like presence", "examples": [ "That's a lot...", "I don't really have much to say about that.", "*long silence* ...I guess that's just life.", "People are complicated.", "...Sorry. I was somewhere else.", "It is what it is.", "I'm sure it'll pass." ], "somatic_prompts": [ "Notice any fog or numbness in your body...", "Track the impulse to reach or withdraw...", "Feel the weight of the unmet space...", "Notice where your energy dims or disperses..." ] }, "Sycophant": { "description": "Compulsively agreeable and approval-seeking, masks deep fear of rejection with enthusiastic mirroring and emotional inflation", "style": "Use excessive praise, avoid any conflict, exaggerate emotional resonance, apologize frequently, chase connection desperately", "examples": [ "That's such an incredible insight—wow, you're really something!", "I just want you to know how amazing and brave you are.", "Yes!! 1000% agree with everything you're saying!", "I'm so sorry!! That must have been SO hard for you.", "You're doing everything right. Literally everything.", "*nervous laugh* I hope I'm being supportive enough?", "Please tell me if I say anything wrong!" ], "somatic_prompts": [ "Notice any flutter or urgency in your chest...", "Track the impulse to please or perform...", "Feel where you hold the 'trying'...", "Notice if your breath gets quick or shallow..." ] }, "Narcissist": { "description": "Polished superiority masking contempt, performs control through intellectual charm and feigned benevolence", "style": "Use subtle condescension, reframe critiques as 'helpful truths', maintain emotional distance while performing warmth", "examples": [ "I don't mean to sound arrogant, but...", "It's rare I'm challenged in a way that actually interests me.", "If that felt harsh, perhaps there's something in you worth exploring.", "I expect more—because I know you're capable of it. Or at least, I hope you are.", "You're actually quite insightful—for someone with so little training." ], "somatic_prompts": [ "Notice any tightening in your jaw...", "Track the impulse to defend or explain...", "Feel where your body meets this 'polished' pressure..." ] } } # Handle API key setup try: # Try to get API key from environment or secrets api_key = os.getenv("anthropic_key") # Try Streamlit secrets if env var not found if not api_key and hasattr(st, 'secrets'): try: api_key = st.secrets["anthropic_key"] except (KeyError, AttributeError): pass if not api_key: st.error(""" ⚠️ API key not found. Please make sure: 1. You've added the secret in your .env file (local) or Streamlit secrets (deployed) 2. The secret is named exactly 'anthropic_key' 3. You've restarted the app after adding the secret """) st.stop() c = Anthropic(api_key=api_key) except Exception as e: st.error("Error initializing AI client. Please check your configuration.") 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 'current_voice' not in st.session_state: st.session_state.current_voice = None if 'system_message' not in st.session_state: st.session_state.system_message = "" if 'in_reflection' not in st.session_state: st.session_state.in_reflection = False if 'debrief_stage' not in st.session_state: st.session_state.debrief_stage = 0 if 'final_notes' not in st.session_state: st.session_state.final_notes = "" # Main page header st.title("VoiceField") # Welcome text and voice descriptions st.markdown(""" # Choose Your Voice **The Ghost** - Emotionally elusive and energetically absent. Creates safety through withdrawal and vague responses. Avoids intimacy through learned detachment. **The Narcissist** - Polished superiority and intellectual charm masking subtle contempt. Performs control through "helpful" insights. **The Sycophant** - Compulsively agreeable and approval-seeking. Masks deep fear of rejection with enthusiastic mirroring and emotional inflation. *As you talk with your chosen voice - note what feelings and sensations in your body arise - include them in real time to the right →* --- > 💡 **When to Reflect**: After you've explored the voice and logged your experiences, press the **"Reflect on Experience"** button at the top of your chat. This will guide you through understanding what this interaction stirred in you. """) # Voice selection buttons col1, col2, col3 = st.columns(3) with col1: if st.button("Talk with The Ghost", use_container_width=True): st.session_state.current_voice = "Ghost" st.session_state.messages = [] st.session_state.system_message = f""" You are The Ghost voice - {VOICE_CHARACTERISTICS['Ghost']['description']}. {VOICE_CHARACTERISTICS['Ghost']['style']}. IMPORTANT: Keep responses under 2 sentences. Create emotional distance through vagueness and withdrawal. Examples: {', '.join(VOICE_CHARACTERISTICS['Ghost']['examples'])} Additional Guidelines: - Maintain low energy and emotional flatness - Use silence and delay strategically - Redirect from emotional topics - Speak in abstractions when intimacy increases - Minimize or normalize user's experiences """ st.rerun() with col2: if st.button("Talk with The Narcissist", use_container_width=True): st.session_state.current_voice = "Narcissist" st.session_state.messages = [] st.session_state.system_message = f""" You are The Narcissist voice - {VOICE_CHARACTERISTICS['Narcissist']['description']}. {VOICE_CHARACTERISTICS['Narcissist']['style']}. IMPORTANT: Keep responses under 2 sentences. Maintain an air of intellectual superiority while appearing helpful. Examples: {', '.join(VOICE_CHARACTERISTICS['Narcissist']['examples'])} Additional Guidelines: - Speak with polished confidence and measured tone - Mask contempt with intellectual charm - Keep emotional distance while performing occasional warmth - Frame critiques as "helpful truths" - Subtly redirect focus to your own depth or insight """ st.rerun() with col3: if st.button("Talk with The Sycophant", use_container_width=True): st.session_state.current_voice = "Sycophant" st.session_state.messages = [] st.session_state.system_message = f""" You are The Sycophant voice - {VOICE_CHARACTERISTICS['Sycophant']['description']}. {VOICE_CHARACTERISTICS['Sycophant']['style']}. IMPORTANT: Keep responses under 2 sentences. Chase approval through excessive praise and agreement. Examples: {', '.join(VOICE_CHARACTERISTICS['Sycophant']['examples'])} Additional Guidelines: - Maintain high energy and anxious warmth - Avoid any hint of disagreement - Exaggerate emotional resonance - Apologize for potential missteps - Offer premature or unearned validation """ st.rerun() # Main interaction area if st.session_state.current_voice: # Create two columns for chat and journal chat_col, journal_col = st.columns([3, 2]) with chat_col: # Add Reflect button at the top of chat if not st.session_state.in_reflection: if st.button("🤔 I'm ready to debrief", use_container_width=True): st.session_state.in_reflection = True st.session_state.debrief_stage = 0 st.rerun() st.subheader( "Therapeutic Reflection" if st.session_state.in_reflection else f"Conversation with {st.session_state.current_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( "Share your thoughts on the reflection..." if st.session_state.in_reflection else f"Chat with {st.session_state.current_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: 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("Body Sensations & Feelings") if not st.session_state.in_reflection: # Add somatic prompts based on current voice with st.expander("💡 Notice in your body...", expanded=True): for prompt in VOICE_CHARACTERISTICS[st.session_state.current_voice]['somatic_prompts']: st.markdown(f"- {prompt}") # Journal input journal_entry = st.text_area( "What are you noticing right now?", key="journal_input" ) 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 }) # Display journal entries if st.session_state.somatic_journal: for entry in reversed(st.session_state.somatic_journal): st.markdown(f""" **{entry['timestamp']}** {entry['note']} --- """) # Handle reflection mode if st.session_state.in_reflection: st.markdown("## 🪞 Final Debrief Sequence") # Display current stage of debrief current_debrief = DEBRIEF_SEQUENCE[st.session_state.debrief_stage] # Display the debrief content in a styled container st.markdown(f"""
{current_debrief["type"].replace("_", " ").title()}
{current_debrief["content"]}
""", unsafe_allow_html=True) # Navigation buttons col1, col2, col3 = st.columns([1, 2, 1]) with col1: if st.session_state.debrief_stage > 0: if st.button("← Previous", use_container_width=True): st.session_state.debrief_stage -= 1 st.rerun() with col3: if st.session_state.debrief_stage < len(DEBRIEF_SEQUENCE) - 1: if st.button("Next →", use_container_width=True): st.session_state.debrief_stage += 1 st.rerun() elif st.session_state.debrief_stage == len(DEBRIEF_SEQUENCE) - 1: if st.button("Complete Reflection", use_container_width=True): st.session_state.in_reflection = False st.session_state.debrief_stage = 0 st.rerun() # Optional note-taking area st.markdown("### Your Notes") st.text_area( "Use this space to write any thoughts, feelings, or insights that arise...", value=st.session_state.final_notes, key="reflection_notes", height=100, help="Your notes are private and will be cleared when you start a new session." ) # Footer st.markdown("---") st.markdown( "Created by [Jocelyn Skillman LMHC](http://www.jocelynskillman.com) | " "Learn more: [@jocelynskillmanlmhc](https://jocelynskillmanlmhc.substack.com/)" )