Spaces:
Sleeping
Sleeping
import os | |
import streamlit as st | |
from anthropic import Anthropic | |
from dotenv import load_dotenv | |
from datetime import datetime | |
from debrief_sequence import DEBRIEF_SEQUENCE | |
# Initialize page configuration first | |
# Trigger rebuild for Hugging Face Space | |
st.set_page_config( | |
page_title="VoiceField", | |
page_icon="🗣️", | |
layout="wide" | |
) | |
# Handle API key setup | |
try: | |
api_key = "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" | |
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 '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 = "" | |
# 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..." | |
] | |
} | |
} | |
# 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("🤔 Reflect on Experience", 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: | |
# Use different system message for reflection mode | |
if st.session_state.in_reflection: | |
# Progress through debrief stages | |
next_prompts = [ | |
# Stage 1: Emotional Arc (already done in initial prompt) | |
"""I hear you. Now, let's turn to what your body was sharing... | |
I noticed you shared some body-based reflections. What stood out most in your body? | |
A tightness? A shift in breath? A sensation that surprised you? | |
Remember, these aren't symptoms to fix—your body may be remembering, or simply protecting you.""", | |
"""Thank you for sharing that. Let's wonder together about the voice styles... | |
Which voice stirred something in you - The Ghost, the Sycophant, or the Narcissist? | |
What do you imagine that tone reminded your body of? A past experience? A relational pattern? | |
There's no 'correct' reaction—your nervous system is wise.""", | |
"""As we begin to wrap up, I'd like to invite you into a moment of self-compassion... | |
If you could speak to yourself now—with the tone you most longed for—what would you say? | |
Some examples if helpful: | |
- "It's okay to not know how I feel yet—my clarity is worth waiting for." | |
- "Even when this is hard, I'm not abandoning myself." | |
- "I can be with myself gently, even in the mess." | |
What words does your heart want to offer?""", | |
"""For our final reflection, I'd like to offer you a question to sit with: | |
What did your body want to do in response to that voice? | |
Is there a younger part of you that recognized this tone? | |
What would it be like to speak to yourself the way you needed to be spoken to? | |
Feel free to take your time with this, or simply hold these questions gently.""" | |
] | |
if st.session_state.debrief_stage < len(next_prompts): | |
next_prompt = next_prompts[st.session_state.debrief_stage] | |
st.session_state.debrief_stage += 1 | |
message = c.messages.create( | |
model="claude-3-opus-20240229", | |
max_tokens=1500, | |
system=f"""{reflection_system} | |
Context for reflection: | |
CHAT HISTORY: | |
{chat_history} | |
SOMATIC JOURNAL: | |
{somatic_entries} | |
""", | |
messages=[ | |
{"role": "user", "content": prompt}, | |
{"role": "assistant", "content": next_prompt} | |
] | |
) | |
else: | |
message = c.messages.create( | |
model="claude-3-opus-20240229", | |
max_tokens=1500, | |
system=f"""{reflection_system} | |
Previous conversation: | |
{chr(10).join([f"[{msg['role']}]: {msg['content']}" for msg in st.session_state.messages])} | |
""", | |
messages=[ | |
{"role": "user", "content": prompt} | |
] | |
) | |
else: | |
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']} | |
--- | |
""") | |
# Add button to start new conversation | |
if st.session_state.in_reflection: | |
if st.button("Start New Conversation", use_container_width=True): | |
st.session_state.in_reflection = False | |
st.session_state.messages = [] | |
st.session_state.somatic_journal = [] | |
st.session_state.current_voice = None | |
st.rerun() | |
# 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] | |
# Replace placeholder with actual voice type | |
content = current_debrief["content"].replace( | |
"[The Ghost / The Sycophant / The Narcissist]", | |
f"The {st.session_state.current_voice}" | |
) | |
# Display the debrief content in a styled container | |
st.markdown(f""" | |
<div style="background-color: #f8f9fa; padding: 20px; border-radius: 10px; margin: 20px 0;"> | |
<div style="color: #666; font-size: 0.9em; margin-bottom: 10px;"> | |
{current_debrief["type"].replace("_", " ").title()} | |
</div> | |
<div style="white-space: pre-line;"> | |
{content} | |
</div> | |
</div> | |
""", 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/)" | |
) | |