File size: 11,304 Bytes
5f61ab7
 
 
 
 
0a22466
5f61ab7
 
 
 
 
 
0a22466
 
 
 
 
 
7eee57b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
0a22466
 
7eee57b
 
 
 
0a22466
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5f61ab7
 
 
 
 
 
 
 
bf623c0
 
 
 
5f61ab7
 
 
81fe2c9
 
5f61ab7
81fe2c9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5f61ab7
 
 
 
 
81fe2c9
 
 
 
 
 
 
 
 
 
 
 
 
 
5f61ab7
81fe2c9
 
 
 
 
 
 
 
 
5f61ab7
81fe2c9
 
 
 
5f61ab7
4da0cb5
 
 
 
81fe2c9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
bf623c0
 
4da0cb5
bf623c0
 
 
 
 
81fe2c9
 
 
4da0cb5
5f61ab7
81fe2c9
5f61ab7
81fe2c9
bf623c0
 
 
 
 
 
 
 
 
4da0cb5
bf623c0
 
 
 
 
 
 
 
81fe2c9
5f61ab7
bf623c0
81fe2c9
 
bf623c0
81fe2c9
5f61ab7
 
 
81fe2c9
5f61ab7
 
 
 
 
4da0cb5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5f61ab7
 
bf623c0
 
 
 
 
 
 
 
 
 
 
 
 
4da0cb5
bf623c0
 
 
 
 
 
 
 
5f61ab7
 
 
 
 
 
 
 
 
 
81fe2c9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5f61ab7
bf623c0
 
 
 
 
 
 
 
 
 
5f61ab7
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
import os
import streamlit as st
from anthropic import Anthropic
from dotenv import load_dotenv

# Initialize page configuration first
st.set_page_config(
    page_title="Attachment Style Roleplay Simulator",
    page_icon="πŸ—£οΈ",
    layout="wide"
)

# Handle API key
try:
    # Load from .env file for local development
    if os.path.exists(".env"):
        load_dotenv()
    
    # Try multiple ways to get the API key
    api_key = None
    
    # Try direct environment variable
    if 'ANTHROPIC_API_KEY' in os.environ:
        api_key = os.environ['ANTHROPIC_API_KEY']
    
    # Try getenv
    if not api_key:
        api_key = os.getenv('ANTHROPIC_API_KEY')
    
    # Try Streamlit secrets
    if not api_key and 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 make sure:
        1. You've added the secret in Hugging Face Space settings
        2. The secret is named exactly 'ANTHROPIC_API_KEY'
        3. The Space has been rebuilt after adding the secret
        """)
        st.stop()

    # Initialize Anthropic client
    c = Anthropic(api_key=api_key)
    
except Exception as e:
    st.error(f"""
    ⚠️ Error initializing Anthropic client: {str(e)}
    
    Please check:
    1. Your ANTHROPIC_API_KEY is set correctly
    2. You're using a valid API key from Anthropic
    """)
    st.stop()

# Initialize session state variables
if 'messages' not in st.session_state:
    st.session_state.messages = []
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 'simulation_params' not in st.session_state:
    st.session_state.simulation_params = {}
if 'in_debrief' not in st.session_state:
    st.session_state.in_debrief = False

# Main page header
st.title("Attachment Style Roleplay Simulator")

# Welcome text in main content area
st.markdown("""
Welcome!

Created by [Jocelyn Skillman LMHC](http://www.jocelynskillman.com), this tool is designed to help you practice and understand relational dynamics through the lens of attachment theory.

To learn more about attachment theory and therapeutic approaches, check out my newsletter: [@jocelynskillmanlmhc](https://jocelynskillmanlmhc.substack.com/)
""")

st.markdown("""
This specific project β€” the Attachment Roleplay Simulator β€” invites users to practice emotionally difficult conversations, like setting boundaries, through roleplay tailored to their attachment style. It's designed to help people feel into relational patterns, develop language for self-advocacy, and build nervous system capacity for connection and repair.

πŸ’‘ Not sure about your attachment style?
You can take the free quiz by Sarah Peyton β€” or simply choose the one that resonates when you read it:

Anxious – "I often worry I've upset people or need to explain myself."

Avoidant – "I'd rather deal with things alone and not depend on anyone."

Disorganized – "I want closeness, but also feel unsafe or mistrusting."

Secure – "I can express needs and handle conflict without losing connection."

Choose what vibes β€” this is a practice space, not a test.
""")

# Sidebar welcome and introduction
with st.sidebar:
    st.markdown("""
Hi, I'm Jocelyn Skillman, LMHC β€” a clinical therapist, relational design ethicist, and creator of experimental tools that explore how AI can support (not replace) human care.

Each tool in this collection is thoughtfully designed to:

Extend therapeutic support between sessions

Model emotional safety and relational depth

Help clients and clinicians rehearse courage, regulation, and repair

Stay grounded in trauma-informed, developmentally sensitive frameworks
    """)
    
    st.divider()
    
    # Add the moved content to the bottom of sidebar
    st.markdown("""
I use Claude (by Anthropic) as the primary language model for these tools, chosen for its relational tone, contextual nuance, and responsiveness to emotionally complex prompts.

As a practicing therapist, I imagine these resources being especially helpful to clinicians like myself β€” companions in the work of tending to others with insight, warmth, and care.

To learn more about my work, visit:
🌐 [jocelynskillman.com](http://www.jocelynskillman.com)
πŸ“¬ [Substack: Relational Code](https://jocelynskillmanlmhc.substack.com/)
    """)

# Simulation setup form
with st.form("setup_form"):
    st.header("Set Up Your Simulation")
    
   tone_archetype = st.selectbox(
    "Select the voice you'd like to engage with:",
    ["Ghost", "Sycophant", "Critic"]
)
    
    scenario = st.text_area(
        "Describe the scenario you'd like to practice:",
        placeholder="Example: Having a difficult conversation with my partner about feeling disconnected"
    )
    
    tone = st.text_input(
        "How would you like the AI to respond?",
        placeholder="Example: Direct but understanding"
    )
    
    goals = st.text_area(
        "What are your practice goals?",
        placeholder="Example: Practice expressing needs without becoming defensive"
    )
    
    submitted = st.form_submit_button("Start Simulation")
    
    if submitted:
        # Store simulation parameters for debrief
        st.session_state.simulation_params = {
            "tone_archetype": tone_archetype,
            "scenario": scenario,
            "tone": tone,
            "goals": goals
        }
        
        # Prepare system message with simulation parameters
        st.session_state.system_message = f"""
        You are a conversational partner helping someone practice difficult conversations. 
        The user has a {tone_archetype} attachment style and wants to practice: {scenario}
        
        Respond in a {tone} manner. Help them achieve their goals: {goals}
        
        Maintain a realistic conversation while providing gentle guidance when needed.
        
        IMPORTANT: If the user types "debrief" or "end roleplay", switch to debrief mode using this format:

        πŸ“ DEBRIEF SUMMARY

        **Emotional Arc**: [Analyze how the user shifted emotionally during the interaction, noting moments of courage, freeze, protest, backtracking, or people-pleasing]

        **Goal Alignment**: [Reflect on how well they stayed aligned with their stated goal. Note small wins and struggles, affirm effort and awareness]

        **Attachment Insight**: [Offer one insight based on their {tone_archetype} attachment style. Normalize the response as protective. Gently suggest a next growth step without shaming]

        **Practical Tool**: [Offer a brief NVC or DBT tool they could try next time (e.g., needs check-in, opposite action, self-validation, distress tolerance)]

        **Bold Reframe**: [Offer one bold, loving sentence they might say if they trusted their relational worth]

        **Journaling Prompt**: [Suggest one reflective question or embodied inquiry for integration]

        Speak warmly and with psychological precision, like an emotionally attuned therapist.
        """
        
        # Reset message history and state
        st.session_state.messages = []
        st.session_state.setup_complete = True
        st.session_state.in_debrief = False
        st.rerun()

# Display status or chat interface
if not st.session_state.setup_complete:
    st.info("Please complete the simulation setup to begin.")
else:
    # Display chat history
    for message in st.session_state.messages:
        with st.chat_message(message["role"]):
            st.markdown(message["content"])

    # Optional somatic input logging
st.subheader("🧘 Somatic Reflection (Optional)")
somatic_input = st.text_input("What’s happening in your body right now?", key="soma_input")

if st.button("Log Somatic Entry"):
    if 'soma_log' not in st.session_state:
        st.session_state.soma_log = []
    st.session_state.soma_log.append({
        "timestamp": datetime.now().isoformat(),
        "tone": st.session_state.simulation_params.get('tone_archetype', st.session_state.simulation_params.get('tone_archetype', 'Unknown')),
        "somatic_input": somatic_input
    })
    st.success("Somatic reflection logged.")

    
    # Chat input
    if prompt := st.chat_input("Type your message here (type 'debrief' or 'end roleplay' when ready to reflect)"):
        # Check for debrief trigger
        if prompt.lower() in ["debrief", "end roleplay"] and not st.session_state.in_debrief:
            st.session_state.in_debrief = True
            # Construct conversation summary
            conversation_summary = "\n".join([
                f"{msg['role']}: {msg['content']}" 
                for msg in st.session_state.messages
            ])
            
            # Add debrief request to messages
            prompt = f"""Please provide a therapeutic debrief for this roleplay session using:

            Attachment Style: {st.session_state.simulation_params['tone_archetype']}
            Scenario: {st.session_state.simulation_params['scenario']}
            Goals: {st.session_state.simulation_params['goals']}
            AI Tone: {st.session_state.simulation_params['tone']}
            
            Conversation Summary:
            {conversation_summary}
            """
        
        # 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)}")
                    st.error("Please try again or contact support if the issue persists.")

# Add restart button after debrief
if st.session_state.in_debrief:
    if st.button("Start New Roleplay"):
        st.session_state.setup_complete = False
        st.session_state.in_debrief = False
        st.session_state.messages = []
        st.session_state.system_message = ""
        st.session_state.simulation_params = {}
        st.rerun()

# Footer
st.markdown("---")
st.markdown(
    "Created by [Jocelyn Skillman LMHC](http://www.jocelynskillman.com) | "
    "Learn more: [@jocelynskillmanlmhc](https://jocelynskillmanlmhc.substack.com/)"
)