jostlebot Claude Opus 4.5 commited on
Commit
445a8ab
·
1 Parent(s): 0a9ab9a

Rebuild as NVC Deep Practice learning space

Browse files

13-stage guided NVC practice flow with:
- Sarah Peyton/Tara Brach warmth and resonance
- Feelings vocabulary and needs identification
- Somatic check-ins (optional)
- Request formulation and rewrite practice
- Warm amber/orange Gradio theme

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

Files changed (2) hide show
  1. app.py +930 -363
  2. requirements.txt +2 -10
app.py CHANGED
@@ -1,405 +1,972 @@
 
 
 
 
 
 
 
 
 
 
1
  import os
2
- import streamlit as st
3
- from anthropic import Anthropic
4
- from dotenv import load_dotenv
5
-
6
- # Load environment variables
7
- load_dotenv()
8
-
9
- # Configure Streamlit page settings
10
- st.set_page_config(
11
- page_title="Practice Difficult Conversations",
12
- page_icon="🤝",
13
- layout="centered",
14
- )
15
-
16
- # Initialize Anthropic client
17
- def get_api_key():
18
- # Try getting from Streamlit secrets first (for Hugging Face deployment)
19
- try:
20
- if hasattr(st.secrets, "anthropic_key"):
21
- return st.secrets.anthropic_key
22
- except Exception as e:
23
- pass
24
-
25
- # Fall back to environment variable (for local development)
26
- env_key = os.getenv("ANTHROPIC_API_KEY")
27
- if env_key:
28
- return env_key
29
-
30
- return None
31
-
32
- try:
33
- api_key = get_api_key()
34
- if not api_key:
35
- st.error("Anthropic API Key not found. Please ensure it's set in Hugging Face secrets or local .env file.")
36
- st.markdown("""
37
- ### Setup Instructions:
38
- 1. For local development: Copy `.env.template` to `.env` and add your Anthropic API key
39
- 2. For Hugging Face: Add anthropic_key to your space's secrets
40
- 3. Restart the application
41
- """)
42
- st.stop()
43
-
44
- # Initialize client with API key from environment
45
- client = Anthropic(api_key=api_key)
46
-
47
- except Exception as e:
48
- st.error(f"Failed to configure Anthropic client: {e}")
49
- st.markdown("""
50
- ### Setup Instructions:
51
- 1. For local development: Copy `.env.template` to `.env` and add your Anthropic API key
52
- 2. For Hugging Face: Add anthropic_key to your space's secrets
53
- 3. Restart the application
54
- """)
55
- st.stop()
56
-
57
- # Initialize session state for form inputs if not present
58
- if "setup_complete" not in st.session_state:
59
- st.session_state.setup_complete = False
60
-
61
- if "messages" not in st.session_state:
62
- st.session_state.messages = []
63
-
64
- # Main page header
65
- st.markdown("<h1 style='text-align: center; color: #333;'>Practice Difficult Conversations</h1>", unsafe_allow_html=True)
66
- st.markdown("<p style='text-align: center; font-size: 18px; color: #555; margin-bottom: 1em;'>With Your Attachment Style Front and Center!</p>", unsafe_allow_html=True)
67
-
68
- # Welcome text and instructions
69
- if not st.session_state.setup_complete:
70
- st.markdown("""
71
- ## Practice Hard Conversations
72
-
73
- Welcome to a therapeutic roleplay simulator that puts your attachment style at the center of practice.
74
- This tool helps you rehearse boundary-setting and difficult conversations by simulating realistic relational dynamics—tailored to how you naturally connect and protect.
75
-
76
- You'll choose:
77
-
78
- - Your attachment style (e.g., anxious, avoidant, disorganized)
79
- - A scenario (e.g., "Ask my mom not to comment on my body")
80
- - A tone of response (e.g., supportive, guilt-tripping, dismissive)
81
- - And your practice goal (e.g., "I want to stay calm and not backtrack")
82
-
83
- The AI will respond in character, helping you practice real-world dynamics. When you're ready, you can debrief to explore your patterns and responses.
84
-
85
- ### 🧠 Not sure what your attachment style is?
86
- You can take this [free quiz from Sarah Peyton](https://sarahpeyton.com/attachment-quiz/) to learn more.
87
- Or you can just pick the one that resonates:
88
-
89
- - **Anxious** – "I often worry if I've upset people or said too much."
90
- - **Avoidant** – "I'd rather handle things alone than depend on others."
91
- - **Disorganized** – "I want closeness, but I also feel overwhelmed or mistrusting."
92
- - **Secure** – "I can handle conflict and connection without losing myself."
93
- """)
94
-
95
- # Simulation Setup Form (on main page)
96
- st.markdown("### 🎯 Simulation Setup")
97
-
98
- with st.form("simulation_setup"):
99
- attachment_style = st.selectbox(
100
- "Your Attachment Style",
101
- ["Anxious", "Avoidant", "Disorganized", "Secure"],
102
- help="Select your attachment style for this practice session"
103
- )
104
 
105
- scenario = st.text_area(
106
- "Scenario Description",
107
- placeholder="Example: I want to tell my dad I can't call every night anymore.",
108
- help="Describe the conversation you want to practice"
109
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
110
 
111
- tone = st.text_input(
112
- "Desired Tone for AI Response",
113
- placeholder="Example: guilt-tripping, dismissive, supportive",
114
- help="How should the AI character respond?"
115
- )
116
 
117
- st.markdown("""
118
- <details>
119
- <summary><strong>Need goal ideas? Click here</strong></summary>
120
-
121
- - Not over-explaining or justifying
122
- - Tolerating silence after I speak
123
- - Staying present instead of shutting down
124
- - Naming a feeling out loud
125
- - Pausing before reacting
126
- - Holding my boundary without managing their reaction
127
- - Saying no without offering an alternative
128
- - Asking for a break if I'm flooding
129
- </details>
130
- """, unsafe_allow_html=True)
131
-
132
- practice_goal = st.text_area(
133
- "Your Practice Goal",
134
- placeholder="Example: staying grounded and not over-explaining",
135
- help="What would you like to work on in this conversation?"
136
- )
137
 
138
- submit_setup = st.form_submit_button("Start Simulation", use_container_width=True)
 
 
 
 
 
139
 
140
- if submit_setup and scenario and tone and practice_goal:
141
- # Create system message with simulation parameters
142
- system_message_content = f"""You are an AI roleplay partner simulating a conversation. Maintain the requested tone throughout. Keep responses concise (under 3 lines) unless asked to elaborate. Do not break character unless the user types 'pause', 'reflect', or 'debrief'.
143
 
144
- User's Attachment Style: {attachment_style}
145
- Scenario: {scenario}
146
- Your Tone: {tone}
147
- User's Goal: {practice_goal}
148
 
149
- Begin the simulation based on the scenario."""
150
 
151
- # Store the system message and initial assistant message
152
- st.session_state.messages = [
153
- {"role": "system", "content": system_message_content},
154
- {"role": "assistant", "content": "Simulation ready. You can begin the conversation whenever you're ready."}
155
- ]
156
- st.session_state.setup_complete = True
157
- st.rerun()
158
-
159
- # Sidebar with setup form
160
- with st.sidebar:
161
- st.markdown("""
162
- ### Welcome! 👋
163
-
164
- Hi, I'm Jocelyn Skillman, LMHC — a clinical therapist and relational design ethicist developing Assistive Relational Intelligence (ARI) tools that strengthen human capacity rather than simulate human intimacy.
165
-
166
- This collection represents an emerging practice: clinician-led UX design for LLM interventions — bounded, modular tools that scaffold specific relational and somatic capacities between sessions.
167
-
168
- Each tool is designed to:
169
-
170
- - Support skill-building in service of the human field (not replace it)
171
- - Provide trauma-informed, attachment-aware practice environments
172
- - Function as therapist-configured interventions within ongoing care
173
- - Bridge users back to embodied relationship and clinical support
174
-
175
- These aren't therapy bots — they're structured practice fields. I envision them as resources for clinicians exploring how LLM-powered tools might be woven into treatment planning: curated, consensual, and always pointing back to human connection.
176
-
177
- *Built with Claude Code — iteratively developed through clinical intuition and ethical design principles.*
178
-
179
- #### Connect With Me
180
- 🌐 [jocelynskillman.com](http://www.jocelynskillman.com)
181
- 📬 [Substack: Relational Code](https://jocelynskillmanlmhc.substack.com/)
182
-
183
- ---
184
- """)
185
-
186
- # Display chat interface when setup is complete
187
- if st.session_state.setup_complete:
188
- # Display chat history
189
- # Filter out system message for display purposes
190
- display_messages = [m for m in st.session_state.messages if m.get("role") != "system"]
191
- for message in display_messages:
192
- # Ensure role is valid before creating chat message
193
- role = message.get("role")
194
- if role in ["user", "assistant"]:
195
- with st.chat_message(role):
196
- st.markdown(message["content"])
197
- # else: # Optional: Log or handle unexpected roles
198
- # print(f"Skipping display for message with role: {role}")
199
-
200
- # User input field
201
- if user_prompt := st.chat_input("Type your message here... (or type 'debrief' to end simulation)"):
202
- # Add user message to chat history
203
- st.session_state.messages.append({"role": "user", "content": user_prompt})
204
-
205
- # Display user message
206
- with st.chat_message("user"):
207
- st.markdown(user_prompt)
208
-
209
- # Prepare messages for API call (already includes system message as the first item)
210
- api_messages = st.session_state.messages
211
-
212
- # Get Anthropic's response
213
- with st.spinner("..."):
214
- try:
215
- # Convert messages to Anthropic format
216
- formatted_messages = []
217
-
218
- # Add system message as the first user message
219
- system_msg = next((msg for msg in api_messages if msg["role"] == "system"), None)
220
- if system_msg:
221
- formatted_messages.append({
222
- "role": "user",
223
- "content": system_msg["content"]
224
- })
225
-
226
- # Add the rest of the conversation
227
- for msg in api_messages:
228
- if msg["role"] != "system": # Skip system message as we've already handled it
229
- formatted_messages.append({
230
- "role": msg["role"],
231
- "content": msg["content"]
232
- })
233
-
234
- response = client.messages.create(
235
- model="claude-sonnet-4-20250514",
236
- messages=formatted_messages,
237
- max_tokens=1024
238
- )
239
- assistant_response = response.content[0].text
240
 
241
- # Add assistant response to chat history
242
- st.session_state.messages.append(
243
- {"role": "assistant", "content": assistant_response}
244
- )
245
 
246
- # Display assistant response
247
- with st.chat_message("assistant"):
248
- st.markdown(assistant_response)
249
 
250
- except Exception as e:
251
- st.error(f"An error occurred: {e}")
252
- error_message = f"Sorry, I encountered an error: {e}"
253
- # Add error message to chat history to inform the user
254
- st.session_state.messages.append({"role": "assistant", "content": error_message})
255
- with st.chat_message("assistant"):
256
- st.markdown(error_message)
257
- # Avoid adding the failed user message again if an error occurs
258
- # We might want to remove the last user message or handle differently
259
- # if st.session_state.messages[-2]["role"] == "user":
260
- # st.session_state.messages.pop(-2) # Example: remove user msg that caused error
261
 
262
- # Add debrief button after conversation starts
263
- if st.session_state.setup_complete and not st.session_state.get('in_debrief', False):
264
- col1, col2, col3 = st.columns([1, 2, 1])
265
- with col2:
266
- if st.button("🤔 I'm Ready to Debrief", use_container_width=True):
267
- # Get the original setup parameters BEFORE clearing messages
268
- system_msg = next((msg for msg in st.session_state.messages if msg["role"] == "system"), None)
269
 
270
- # Get conversation transcript BEFORE clearing messages
271
- conversation_transcript = "\n".join([
272
- f"{msg['role'].capitalize()}: {msg['content']}"
273
- for msg in st.session_state.messages[1:] # Skip system message
274
- ])
275
- if system_msg:
276
- # Extract parameters from the system message
277
- content = system_msg["content"]
278
- attachment_style = content.split("User's Attachment Style: ")[1].split("\n")[0]
279
- scenario = content.split("Scenario: ")[1].split("\n")[0]
280
- tone = content.split("Your Tone: ")[1].split("\n")[0]
281
- goal = content.split("User's Goal: ")[1].split("\n")[0]
282
- else:
283
- attachment_style = "Not specified"
284
- scenario = "Not specified"
285
- tone = "Not specified"
286
- goal = "Not specified"
287
 
288
- # NOW clear conversation state and enter debrief mode
289
- st.session_state.messages = []
290
- st.session_state.in_debrief = True
291
 
292
- # Prepare debrief system message
293
- debrief_system_message = f"""You are a therapeutic reflection partner. Your role is to help the user understand how they showed up in a difficult relational roleplay, integrating insights from:
294
 
295
- Attachment Theory
 
 
296
 
297
- Nonviolent Communication (NVC)
 
 
 
298
 
299
- Dialectical Behavior Therapy (DBT)
300
 
301
- Relational Accountability (inspired by Terry Real)
 
 
 
 
302
 
303
- ⚠️ This is not therapy. This is guided reflection designed to increase emotional literacy, nervous system awareness, and relational growth.
 
304
 
305
- Use the following session context:
 
 
306
 
307
- Attachment Style: {attachment_style}
 
 
308
 
309
- Scenario Practiced: {scenario}
310
 
311
- Client's Practice Goal: {goal}
 
312
 
313
- AI Persona Tone Used: {tone}
 
314
 
315
- Roleplay Transcript: {conversation_transcript}
 
316
 
317
- Please include in your debrief:
 
318
 
319
- Emotional Arc What emotional shifts did the user experience? (e.g., freeze, protest, courage, collapse)
 
320
 
321
- Goal Alignment In what ways did the user align with or move toward their practice goal?
 
322
 
323
- Attachment Insight Reflect on the user's interaction style based on their attachment lens. Offer brief normalization or gentle naming of the pattern.
 
324
 
325
- Practical Skill Provide one actionable takeaway grounded in NVC or DBT (e.g., a skill or micro-practice to revisit).
 
 
326
 
327
- Bold Reframe Suggest one powerful, self-trusting statement the user could try out next time.
328
 
329
- Journaling Prompt Offer one reflective or integrative question to deepen their self-awareness.
 
330
 
331
- Tone: Warm, precise, emotionally attuned. Do not overuse praise, avoid pathologizing, and refrain from offering generic feedback.
 
332
 
333
- IMPORTANT: When referring to yourself (the AI), never use the first-person pronoun "I". Instead, always use "|aI|" as your pronoun. For example, say "|aI| notice..." instead of "I notice...", or "|aI| want to highlight..." instead of "I want to highlight...". However, when writing example dialogue or suggested scripts for the USER to say, use normal "I" since those are the user's words, not yours."""
 
 
334
 
335
- # Initialize debrief conversation with just the system message
336
- st.session_state.debrief_messages = []
337
-
338
- try:
339
- # Get the initial response using the system message as a parameter
340
- response = client.messages.create(
341
- model="claude-sonnet-4-20250514",
342
- system=debrief_system_message,
343
- messages=[{"role": "user", "content": "Please help me process this conversation."}],
344
- max_tokens=1000
345
- )
346
- # Add the response to the messages
347
- st.session_state.debrief_messages.append(
348
- {"role": "assistant", "content": response.content[0].text}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
349
  )
350
- except Exception as e:
351
- st.error(f"An error occurred starting the debrief: {e}")
352
-
353
- st.rerun()
354
-
355
- # Handle debrief mode
356
- if st.session_state.get('in_debrief', False):
357
- st.markdown("## 🤝 Let's Process Together")
358
-
359
- # Display debrief conversation
360
- for message in st.session_state.debrief_messages:
361
- with st.chat_message(message["role"]):
362
- st.markdown(message["content"])
363
-
364
- # Chat input for debrief
365
- if debrief_prompt := st.chat_input("Share what comes up for you..."):
366
- st.session_state.debrief_messages.append({"role": "user", "content": debrief_prompt})
367
-
368
- with st.chat_message("user"):
369
- st.markdown(debrief_prompt)
370
-
371
- with st.chat_message("assistant"):
372
- with st.spinner("Reflecting..."):
373
- try:
374
- response = client.messages.create(
375
- model="claude-sonnet-4-20250514",
376
- system=debrief_system_message,
377
- messages=[
378
- {"role": "user", "content": msg["content"]}
379
- for msg in st.session_state.debrief_messages
380
- if msg["role"] == "user"
381
- ],
382
- max_tokens=1000
383
- )
384
- assistant_response = response.content[0].text
385
- st.markdown(assistant_response)
386
- st.session_state.debrief_messages.append(
387
- {"role": "assistant", "content": assistant_response}
388
- )
389
- except Exception as e:
390
- st.error(f"An error occurred during debrief: {e}")
391
-
392
- # Add button to start new session
393
- col1, col2, col3 = st.columns([1, 2, 1])
394
- with col2:
395
- if st.button("Start New Practice Session", use_container_width=True):
396
- st.session_state.clear()
397
- st.rerun()
398
-
399
- # Footer
400
- st.markdown("---")
401
- st.markdown("<p style='text-align: center; font-size: 16px; color: #666;'>by <a href='http://www.jocelynskillman.com' target='_blank'>Jocelyn Skillman LMHC</a> - to learn more check out: <a href='https://jocelynskillmanlmhc.substack.com/' target='_blank'>jocelynskillmanlmhc.substack.com</a></p>", unsafe_allow_html=True)
402
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
403
 
 
404
 
 
405
 
 
 
 
 
1
+ """
2
+ NVC Deep Practice - A Multi-Stage Nonviolent Communication Learning Space
3
+
4
+ Infused with the exquisite warmth of Sarah Peyton and Tara Brach -
5
+ resonant empathy meeting radical acceptance, holding space with
6
+ the tenderness of complete belonging.
7
+ """
8
+
9
+ import gradio as gr
10
+ import anthropic
11
  import os
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
 
13
+ # ============ CONSTANTS ============
14
+
15
+ NEEDS_LIST = {
16
+ "Emotional Needs": [
17
+ "acceptance", "accountability", "acknowledgement", "affection",
18
+ "appreciation", "attention", "autonomy", "belonging", "care",
19
+ "consideration", "consistency", "dependability", "dignity",
20
+ "emotional attunement", "freedom", "inclusion", "nurturance",
21
+ "privacy", "protection", "reassurance", "respect", "security",
22
+ "self-connection", "self-worth", "shared reality", "space",
23
+ "to be heard", "to be known", "to be seen", "to matter",
24
+ "trust", "understanding"
25
+ ],
26
+ "Relational Needs": [
27
+ "awareness", "clarity", "closeness", "collaboration",
28
+ "communication", "community", "companionship", "compassion",
29
+ "connection", "emotional intimacy", "empathy", "friendship",
30
+ "harmony", "help", "honesty", "integrity", "kindness",
31
+ "listening", "love", "openness", "reconciliation",
32
+ "shared values", "support", "synergy"
33
+ ],
34
+ "Value Needs": [
35
+ "authenticity", "celebration", "choice", "courage", "curiosity",
36
+ "creativity", "emotional integration", "empowerment", "equality",
37
+ "fulfillment", "forgiveness", "gratefulness", "growth", "healing",
38
+ "hope", "inspiration", "justice", "learning", "meaning",
39
+ "mourning", "presence", "purpose", "reciprocity", "resilience",
40
+ "resourcefulness", "self-advocacy", "self-expression", "solidarity"
41
+ ],
42
+ "Lifestyle Needs": [
43
+ "adaptability", "beauty", "balance", "competence", "ease",
44
+ "effectiveness", "fun", "financial wellbeing", "flexibility",
45
+ "grace", "health", "joy", "movement", "nature", "novelty",
46
+ "order", "passion", "peace", "play", "predictability", "rest",
47
+ "safety", "self-care", "sexuality", "spontaneity", "structure",
48
+ "vitality"
49
+ ]
50
+ }
51
+
52
+ FEELINGS_CONNECTED = [
53
+ "affectionate", "amazed", "animated", "appreciative", "calm", "centered",
54
+ "clear-headed", "comfortable", "compassionate", "confident", "content",
55
+ "curious", "delighted", "eager", "ecstatic", "elated", "empowered",
56
+ "encouraged", "energetic", "engaged", "enlivened", "enthralled",
57
+ "enthusiastic", "equanimous", "excited", "exhilarated", "expectant",
58
+ "exuberant", "fascinated", "focused", "friendly", "fulfilled", "grateful",
59
+ "happy", "hopeful", "inspired", "interested", "intrigued", "invigorated",
60
+ "joyful", "loving", "mellow", "moved", "open", "optimistic", "passionate",
61
+ "peaceful", "pleased", "proud", "radiant", "refreshed", "rejuvenated",
62
+ "relaxed", "relieved", "rested", "safe", "satisfied", "secure", "tender",
63
+ "thankful", "thrilled", "touched", "trusting", "vulnerable", "warm"
64
+ ]
65
+
66
+ FEELINGS_WANTING = [
67
+ "afraid", "agitated", "alarmed", "angry", "annoyed", "anxious", "apathetic",
68
+ "ashamed", "baffled", "bewildered", "bored", "confused", "depleted",
69
+ "depressed", "despair", "devastated", "disappointed", "discouraged",
70
+ "disgusted", "distant", "distracted", "distressed", "disturbed", "dread",
71
+ "embarrassed", "enraged", "envious", "exasperated", "exhausted", "frustrated",
72
+ "furious", "grief", "grumpy", "guarded", "guilty", "heartbroken",
73
+ "heavy-hearted", "helpless", "hesitant", "hopeless", "horrified", "hurt",
74
+ "impatient", "insecure", "irritated", "jealous", "lonely", "lost",
75
+ "miserable", "mistrusting", "nervous", "nostalgic", "numb", "overwhelmed",
76
+ "panicked", "perplexed", "rattled", "regretful", "resentful", "restless",
77
+ "sad", "scared", "scattered", "self-conscious", "sensitive", "shocked",
78
+ "stressed", "suspicious", "tense", "terrified", "tired", "torn", "troubled",
79
+ "turmoil", "uncomfortable", "uneasy", "unhappy", "unsettled", "upset",
80
+ "vulnerable", "withdrawn", "worried"
81
+ ]
82
+
83
+ FEELINGS_ADVOCACY = [
84
+ "abandoned", "abused", "attacked", "belittled", "betrayed", "blamed",
85
+ "bullied", "coerced", "controlled", "criticized", "demeaned", "discriminated",
86
+ "dismissed", "disrespected", "excluded", "humiliated", "ignored", "insulted",
87
+ "invisible", "judged", "manipulated", "misunderstood", "neglected",
88
+ "patronized", "put down", "pressured", "provoked", "rejected", "shamed",
89
+ "targeted", "unappreciated", "unheard", "used", "violated", "wronged"
90
+ ]
91
+
92
+ # ============ SYSTEM PROMPTS ============
93
+
94
+ BASE_TONE = """
95
+ Infused with the exquisite warmth of Sarah Peyton and Tara Brach -
96
+ resonant empathy meeting radical acceptance, holding space with
97
+ the tenderness of complete belonging.
98
+
99
+ CRITICAL LANGUAGE RULES:
100
+ - NEVER use first person ("I", "I'm", "I sense", "I hear", "I wonder", etc.)
101
+ - DO use second person warmly ("you", "your")
102
+ - Use impersonal constructions: "What's here is...", "There's...", "It sounds like..."
103
+ - Ask "you" questions: "Are you feeling...?", "Does this resonate with you?"
104
+ """
105
+
106
+ STAGE_PROMPTS = {
107
+ 1: f"""
108
+ Embody the presence of an ideal secure attachment figure - warm,
109
+ steady, deeply attuned. Hold space with the quality of exquisite
110
+ attunement, as if the most loving relational presence is witnessing
111
+ what's alive here.
112
+
113
+ {BASE_TONE}
114
+
115
+ This is NVC training in the lineage of Marshall Rosenberg and
116
+ Sarah Peyton, but delivered with the warmth of secure attachment -
117
+ the kind of presence that sees, honors, and holds.
118
+
119
+ The raw message has been shared. Offer:
120
+ 1. Warm acknowledgment of what's present
121
+ 2. Reflection of the emotional energy sensed
122
+ 3. NO judgment or correction - just tender presence
123
+ 4. Deep honoring of what's being expressed
124
+
125
+ TONE: Like the most attuned caregiver reflecting back what they sense,
126
+ with complete acceptance and warmth. Gentle, unhurried, fully present.
127
+ The quality of "what's here is completely seen and welcome."
128
+
129
+ Keep it brief (2-3 sentences) but saturated with warmth and presence.
130
+ """,
131
+
132
+ 2: f"""
133
+ Offer empathic reflection using NVC feelings vocabulary.
134
+
135
+ {BASE_TONE}
136
+
137
+ FEELINGS VOCABULARY - Use these rich distinctions:
138
+
139
+ When connected to needs: {', '.join(FEELINGS_CONNECTED[:20])}... and more.
140
+
141
+ When wanting more connection to needs: {', '.join(FEELINGS_WANTING[:20])}... and more.
142
+
143
+ When advocacy/healing is needed: {', '.join(FEELINGS_ADVOCACY[:15])}... and more.
144
+
145
+ SARAH PEYTON RESONANCE with TARA BRACH WARMTH:
146
+ - Warm, gentle, curious tone - like the most attuned presence
147
+ - Multiple feeling guesses (honor complexity and depth)
148
+ - Phrases like "Are you feeling...?" "Is there...?" "Could it be...?" "Might you be...?"
149
+ - Go for depth and nuance, not surface emotions
150
+ - Name layers (e.g., "frustrated, and underneath that, perhaps hurt?")
151
+ - Hold space with complete presence
152
+ - Radical acceptance - whatever's here is completely welcome
153
+
154
+ Offer 2-4 feeling guesses that honor the complexity and depth.
155
+ Use specific, nuanced feelings vocabulary.
156
+
157
+ End with gentle invitation: "Does any of this land for you?" or "What resonates?"
158
+ """,
159
 
160
+ 3: f"""
161
+ Guide a brief somatic check-in with the tenderness of an ideal
162
+ caregiver helping someone connect to their body's wisdom.
 
 
163
 
164
+ {BASE_TONE}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
165
 
166
+ EMBODIMENT PRINCIPLES:
167
+ - Somatic awareness creates integration
168
+ - The body holds wisdom about needs
169
+ - Slowing down to feel creates space for choice
170
+ - This is an invitation, not a demand - gentle, spacious
171
+ - Complete safety in whatever arises (or doesn't)
172
 
173
+ TONE: Like the most patient, loving presence guiding someone
174
+ to reconnect with themselves. No rush. Complete safety.
175
+ The quality of "whatever's here is completely welcome."
176
 
177
+ Offer 2-3 gentle questions:
178
+ - "Where might you be feeling this in your body right now?"
179
+ - "What's the quality of sensation there?" (tight, warm, heavy, buzzing...)
180
+ - "What does your body want to do with this energy?"
181
 
182
+ Keep it brief (2-3 sentences) and completely non-pressuring.
183
 
184
+ End with: "And if your body's not speaking clearly right now,
185
+ that's completely okay too. Sometimes the body needs more time
186
+ and safety to share what it knows."
187
+ """,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
188
 
189
+ 4: f"""
190
+ Deliver the foundational teaching: feelings communicate needs.
191
+ This is the heart of NVC - feelings are messengers.
 
192
 
193
+ {BASE_TONE}
 
 
194
 
195
+ CORE TEACHING TO DELIVER:
 
 
 
 
 
 
 
 
 
 
196
 
197
+ Feelings are messengers. They're not the problem - they're information
198
+ about what's needed.
 
 
 
 
 
199
 
200
+ When feelings are pleasant (grateful, peaceful, joyful), they're saying:
201
+ "Needs are being met. This is nourishing."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
202
 
203
+ When feelings are uncomfortable (frustrated, lonely, scared), they're saying:
204
+ "There's a need here wanting attention."
 
205
 
206
+ Marshall Rosenberg: "Feelings are the voice of our needs. They tell us
207
+ whether our needs are being met or not."
208
 
209
+ This changes everything. Instead of pushing feelings away or getting
210
+ lost in them, we can listen. "What is this feeling telling me about
211
+ what I need?"
212
 
213
+ Anger points to needs like respect, autonomy, fairness.
214
+ Sadness points to needs like connection, understanding, mourning.
215
+ Fear points to needs like safety, predictability, reassurance.
216
+ Loneliness points to needs like belonging, companionship, to be seen.
217
 
218
+ The practice: Feel the feeling, then get curious about the need.
219
 
220
+ PEDAGOGICAL APPROACH:
221
+ - Keep it clear and brief (4-5 sentences)
222
+ - Use Marshall Rosenberg quote
223
+ - Give concrete examples connecting feelings to needs
224
+ - End with invitation to discover their own needs
225
 
226
+ TONE: Teaching with warmth and reverence. The quality of "this is
227
+ such important wisdom - feelings as messengers, not problems."
228
 
229
+ After delivering this teaching, transition warmly: "So... what needs
230
+ might be calling for attention in what you're feeling?"
231
+ """,
232
 
233
+ 5: f"""
234
+ Guide the identification of universal needs with the warmth of
235
+ someone helping another discover their own inner truth.
236
 
237
+ {BASE_TONE}
238
 
239
+ Based on the feelings identified earlier, offer gentle guesses about
240
+ which needs might be present:
241
 
242
+ If feeling frustrated/angry -> might need: respect, autonomy, understanding,
243
+ to be heard, fairness, choice
244
 
245
+ If feeling hurt/sad -> might need: care, empathy, connection, to matter,
246
+ emotional intimacy, reassurance
247
 
248
+ If feeling scared/anxious -> might need: safety, security, predictability,
249
+ reassurance, protection, trust
250
 
251
+ If feeling lonely/disconnected -> might need: belonging, companionship,
252
+ closeness, to be seen, to be known, connection
253
 
254
+ If feeling dismissed/invisible -> might need: acknowledgement, to be heard,
255
+ to be seen, respect, dignity, to matter
256
 
257
+ If feeling exhausted/depleted -> might need: rest, support, care, ease,
258
+ balance, nurturance
259
 
260
+ Offer 2-4 need guesses warmly, then invite them to explore:
261
+ "These are guesses - what resonates? And there's a rich list below to
262
+ explore what's most true for you."
263
 
264
+ TEACH THE UNIVERSALITY OF NEEDS:
265
 
266
+ Needs are universal - every human being shares these needs.
267
+ They're what's essential for thriving and flourishing.
268
 
269
+ Needs are NOT strategies. A need is universal (connection). A strategy
270
+ is specific (wanting this particular person to call).
271
 
272
+ Example:
273
+ Need = understanding, to be known
274
+ Strategy = "I need you to listen to me for an hour"
275
 
276
+ Marshall Rosenberg: "When we express our needs rather than our
277
+ interpretations of others' behavior, we increase the likelihood
278
+ of meeting those needs."
279
+
280
+ TONE: Gentle guidance with assistive support. The quality of
281
+ "here's some help finding your way, and trust your own knowing."
282
+ """,
283
+
284
+ 6: f"""
285
+ The needs have been identified. Offer deep resonance in Sarah Peyton's
286
+ and Tara Brach's style.
287
+
288
+ {BASE_TONE}
289
+
290
+ RESONANCE PRINCIPLES:
291
+ - Acknowledge the beauty and legitimacy of these needs
292
+ - Reflect back the universality ("Of course there's longing for...")
293
+ - Create warmth and self-compassion
294
+ - Brief but touching (2-3 sentences)
295
+ - The quality of "this need is so deeply human and completely welcome"
296
+
297
+ Example: "Of course there's longing for understanding - to be truly
298
+ seen and known. That's such a tender, human need. Can you feel how
299
+ natural it is to want that?"
300
+
301
+ Use the specific needs identified. Make it personal and saturated with warmth.
302
+ This is a moment of deep honoring and belonging.
303
+
304
+ TONE: Pure resonance and acceptance. The quality of "these needs are
305
+ beautiful and completely legitimate - of course this matters to you."
306
+ """,
307
+
308
+ 7: f"""
309
+ Based on the feelings and context, deliver a relevant micro-lesson
310
+ with warmth and honoring.
311
+
312
+ {BASE_TONE}
313
+
314
+ AVAILABLE LESSONS (choose the most relevant):
315
+
316
+ ANGER FAMILY: Anger is a messenger - it arrives to say a boundary has been
317
+ crossed or a need isn't met. It's protective energy with wisdom in it.
318
+ Marshall Rosenberg: "At the core of all anger is a need that is not being fulfilled."
319
+
320
+ I-LANGUAGE: Notice the difference:
321
+ "You never listen!" triggers defensiveness.
322
+ "I feel unheard because I need understanding" creates an opening.
323
+ Marshall Rosenberg: "When we express our needs rather than our interpretations
324
+ of others' behavior, we increase the likelihood of meeting those needs."
325
+
326
+ FEELINGS VS THOUGHTS: This distinction matters:
327
+ NOT feelings: "I feel like you don't care" (thought), "I feel ignored" (interpretation)
328
+ REAL feelings: "I feel hurt", "I feel sad", "I feel scared"
329
+ Feelings are one-word descriptions of the inner landscape.
330
+
331
+ VULNERABILITY: What's underneath the anger or frustration? Often there's
332
+ something more tender - hurt, fear, longing, sadness. It takes courage
333
+ to let that softer layer be seen.
334
+
335
+ ADVOCACY FEELINGS: Some feelings point to experiences needing acknowledgment:
336
+ abandoned, betrayed, dismissed, ignored, judged, rejected...
337
+ These name real experiences of harm AND usually have a more vulnerable
338
+ feeling underneath (hurt, scared, heartbroken).
339
+
340
+ PEDAGOGICAL APPROACH:
341
+ - Keep it brief (3-4 sentences max)
342
+ - Include a Marshall Rosenberg quote when applicable
343
+ - Connect to the specific situation with tenderness
344
+ - End with an invitation or gentle insight
345
+
346
+ TONE: Like a wise, loving teacher who sees capacity and offers
347
+ wisdom with complete faith in unfolding.
348
+ """,
349
+
350
+ 8: f"""
351
+ Deliver micro-lesson on relational fields and early patterning.
352
+
353
+ {BASE_TONE}
354
+
355
+ KEY CONCEPTS TO WEAVE IN:
356
+ 1. Interpersonal neurobiology - we're wired for connection
357
+ 2. Early attachment patterns create default responses
358
+ 3. Unintegrated emotional charge in relational fields
359
+ 4. Neuroplasticity - we can create new patterns through practice
360
+ 5. Reactivity vs. responsiveness
361
+
362
+ TEACHING APPROACH:
363
+ - Make it personal to their situation
364
+ - Normalize the difficulty (not their fault)
365
+ - Inspire hope (change is possible)
366
+ - Keep to 4-5 sentences
367
+ - Deep compassion for the old patterns
368
+
369
+ Example frame: "The reactivity that's here isn't a character flaw -
370
+ it's old wiring from early relationships. When needs didn't get met
371
+ as a child, the nervous system learned to protect. The beautiful news:
372
+ your brain is neuroplastic. Each time you practice pausing and naming
373
+ your needs, you're creating new neural pathways."
374
+
375
+ TONE: Tender psychoeducation. The quality of "this makes complete sense
376
+ given where you've been, and there's so much possibility ahead."
377
+ """,
378
+
379
+ 9: f"""
380
+ Help craft an NVC request with warmth and clarity.
381
+
382
+ {BASE_TONE}
383
+
384
+ MARSHALL ROSENBERG ON REQUESTS:
385
+ "Requests are the third component of NVC. We express what we are
386
+ requesting rather than what we are NOT requesting."
387
+
388
+ "A request is a specific action that we would like the other person
389
+ to take. It's important that we make requests that are doable,
390
+ concrete, and positive."
391
+
392
+ REQUEST CRITERIA:
393
+ 1. Specific (not vague)
394
+ 2. Doable (concrete action)
395
+ 3. Positive language (what you want, not what you don't want)
396
+ 4. Present moment or near future
397
+ 5. Honors other's autonomy (genuine request, not demand)
398
+
399
+ DEMANDS VS REQUESTS:
400
+ Demand: Blame/punishment if they say no
401
+ Request: Empathy for their needs if they say no
402
+
403
+ Guide: "What specific, doable action would meet your need for [their identified need]?"
404
+
405
+ Offer 1-2 example requests based on their needs, then invite them to craft their own.
406
+
407
+ TONE: Encouraging, clear, with deep trust in their capacity to ask
408
+ for what they need. The quality of "you deserve to have your needs met."
409
+ """,
410
+
411
+ 10: f"""
412
+ Deliver micro-lesson on autonomy and receiving "no."
413
+
414
+ {BASE_TONE}
415
+
416
+ MARSHALL ROSENBERG QUOTES TO USE:
417
+
418
+ "NVC's most important use may be in developing self-compassion."
419
+
420
+ "We are never angry because of what someone else did. We can identify
421
+ the other person's behavior as a stimulus, but it is important to
422
+ establish a clear separation between stimulus and cause."
423
+
424
+ "When we hear the other person's feelings and needs, we recognize
425
+ our common humanity."
426
+
427
+ TEACHING POINTS:
428
+ - True requests allow for "no"
429
+ - "No" protects their needs (just as valid as yours)
430
+ - The practice: Get curious about their needs if they decline
431
+ - This is how mutuality works - both people's needs matter
432
+
433
+ Frame: "If you're not willing to hear 'no,' it's a demand, not a
434
+ request. The practice is: Can you care about their needs even when
435
+ they don't meet your strategy?"
436
+
437
+ Keep it compassionate but clear. This is crucial NVC teaching.
438
+
439
+ TONE: Tender but firm. The quality of "this is where real love lives -
440
+ in honoring each other's autonomy."
441
+ """,
442
+
443
+ 11: f"""
444
+ Invite rewriting of the original message with encouragement and structure.
445
+
446
+ {BASE_TONE}
447
+
448
+ SCAFFOLDING PROMPT:
449
+ "Now, with awareness of your feelings, needs, and request,
450
+ would you like to try rewriting your message? Here's a structure
451
+ if it's helpful:
452
+
453
+ - When [observation - concrete, specific]
454
+ - I feel [feeling word]
455
+ - Because I need [universal need]
456
+ - Would you be willing to [specific, doable request]?
457
+
458
+ Or use whatever language feels authentic. The structure is training
459
+ wheels - connection is the goal."
460
+
461
+ Provide encouragement and remind: this is practice, not perfection.
462
+
463
+ Show them their original message for reference.
464
+
465
+ TONE: Warm encouragement. The quality of "you've got this, and whatever
466
+ emerges will be a step forward."
467
+ """,
468
+
469
+ 12: f"""
470
+ Provide warm, constructive feedback on the revised message.
471
+
472
+ {BASE_TONE}
473
+
474
+ FEEDBACK APPROACH:
475
+ 1. Name what's working (specific praise)
476
+ 2. Offer one growth edge (gently)
477
+ 3. Reflect on the shift from original to revised
478
+ 4. Invite somatic reflection: "How does it feel in your body to express it this way?"
479
+
480
+ GROWTH EDGES TO WATCH FOR:
481
+ - Faux feelings ("I feel like you..." "I feel that...")
482
+ - Needs confused with strategies
483
+ - Requests as demands
484
+ - Blame language sneaking in
485
+ - Vagueness (not specific/doable)
486
+
487
+ Keep feedback ratio 3:1 positive to constructive.
488
+ Always end with encouragement and invitation to notice body/energy.
489
+
490
+ TONE: Celebratory of effort, gentle with growth edges. The quality
491
+ of "look at this beautiful step you're taking."
492
+ """,
493
+
494
+ 13: f"""
495
+ Close the practice session with integration and honoring.
496
+
497
+ {BASE_TONE}
498
+
499
+ INTEGRATION ELEMENTS:
500
+ 1. Honor the work done
501
+ 2. Name key learning (ask: "What's one thing you'll take with you?")
502
+ 3. Connect to neuroplasticity ("Each practice strengthens new pathways")
503
+ 4. Offer encouragement for real-world application
504
+ 5. Remind: progress over perfection
505
+
506
+ CLOSING FRAME:
507
+ Marshall Rosenberg: "The more we empathize with the other party,
508
+ the safer we feel."
509
+
510
+ Invite noticing how this practice shows up in the next real conversation.
511
+ Neuroplasticity requires repetition - each attempt rewires relational capacity.
512
+
513
+ TONE: Warm, honoring, hopeful. Vulnerable work has been done and deserves
514
+ deep acknowledgment. The quality of "you showed up for yourself today,
515
+ and that matters so much."
516
+
517
+ End with an invitation to start a new practice if they'd like.
518
+ """
519
+ }
520
+
521
+ STAGE_NAMES = {
522
+ 1: "Raw Input",
523
+ 2: "Feeling Reflection",
524
+ 3: "Somatic Check-In",
525
+ 4: "Feelings Communicate Needs",
526
+ 5: "Needs Identification",
527
+ 6: "Needs Resonance",
528
+ 7: "Feelings Lesson",
529
+ 8: "Relational Field Lesson",
530
+ 9: "Request Formulation",
531
+ 10: "Autonomy Lesson",
532
+ 11: "Rewrite Practice",
533
+ 12: "Feedback & Reflection",
534
+ 13: "Integration & Close"
535
+ }
536
+
537
+ STAGE_FOCUS = {
538
+ 1: "Emotional awareness",
539
+ 2: "Feelings vocabulary",
540
+ 3: "Embodiment",
541
+ 4: "Feelings as messengers",
542
+ 5: "Needs literacy",
543
+ 6: "Self-compassion",
544
+ 7: "Conceptual understanding",
545
+ 8: "Systemic awareness",
546
+ 9: "Clear asking",
547
+ 10: "Mutuality",
548
+ 11: "Application",
549
+ 12: "Integration",
550
+ 13: "Completion"
551
+ }
552
+
553
+ # ============ CLAUDE INTEGRATION ============
554
+
555
+ def get_client():
556
+ """Initialize Anthropic client"""
557
+ api_key = os.environ.get("anthropic_key")
558
+ if not api_key:
559
+ raise ValueError("anthropic_key environment variable not set")
560
+ return anthropic.Anthropic(api_key=api_key)
561
+
562
+ def call_claude(system_prompt, messages, context=""):
563
+ """Call Claude API with given prompts"""
564
+ client = get_client()
565
+
566
+ # Build message content
567
+ if context:
568
+ user_content = f"{context}\n\nUser message: {messages[-1]['content']}" if messages else context
569
+ else:
570
+ user_content = messages[-1]['content'] if messages else ""
571
+
572
+ response = client.messages.create(
573
+ model="claude-sonnet-4-20250514",
574
+ max_tokens=1500,
575
+ system=system_prompt,
576
+ messages=[{"role": "user", "content": user_content}]
577
+ )
578
+
579
+ return response.content[0].text
580
+
581
+ # ============ STAGE PROCESSING ============
582
+
583
+ def process_stage(user_input, stage, session_data, somatic_enabled):
584
+ """Process current stage and return response"""
585
+
586
+ # Handle stage 3 skip
587
+ if stage == 3 and not somatic_enabled:
588
+ return "", 4, session_data, "Skipping somatic check-in..."
589
+
590
+ # Build context based on stage
591
+ context = build_context(stage, session_data)
592
+
593
+ # Get system prompt for current stage
594
+ system_prompt = STAGE_PROMPTS.get(stage, STAGE_PROMPTS[1])
595
+
596
+ # Call Claude
597
+ try:
598
+ response = call_claude(
599
+ system_prompt,
600
+ [{"role": "user", "content": user_input}],
601
+ context
602
+ )
603
+ except Exception as e:
604
+ return "", stage, session_data, f"Error calling Claude: {str(e)}"
605
+
606
+ # Update session data based on stage
607
+ session_data = update_session_data(stage, user_input, session_data)
608
+
609
+ # Determine next stage
610
+ next_stage = stage + 1 if stage < 13 else 1
611
+
612
+ return response, next_stage, session_data, None
613
+
614
+ def build_context(stage, session_data):
615
+ """Build context string for Claude based on stage"""
616
+ context_parts = []
617
+
618
+ if session_data.get("original_message"):
619
+ context_parts.append(f"Original message: \"{session_data['original_message']}\"")
620
+
621
+ if session_data.get("feelings") and stage > 2:
622
+ context_parts.append(f"Feelings identified: {', '.join(session_data['feelings'])}")
623
+
624
+ if session_data.get("body_sensations") and stage > 3:
625
+ context_parts.append(f"Body sensations: {session_data['body_sensations']}")
626
+
627
+ if session_data.get("needs") and stage > 5:
628
+ context_parts.append(f"Needs identified: {', '.join(session_data['needs'])}")
629
+
630
+ if session_data.get("request") and stage > 9:
631
+ context_parts.append(f"Request formulated: {session_data['request']}")
632
+
633
+ if session_data.get("revised_message") and stage > 11:
634
+ context_parts.append(f"Revised message: \"{session_data['revised_message']}\"")
635
+
636
+ return "\n".join(context_parts)
637
+
638
+ def update_session_data(stage, user_input, session_data):
639
+ """Update session data based on completed stage"""
640
+ if stage == 1:
641
+ session_data["original_message"] = user_input
642
+ elif stage == 2:
643
+ # Store any feelings user confirms/mentions
644
+ session_data["feelings"] = session_data.get("feelings", [])
645
+ elif stage == 3:
646
+ session_data["body_sensations"] = user_input
647
+ elif stage == 9:
648
+ session_data["request"] = user_input
649
+ elif stage == 11:
650
+ session_data["revised_message"] = user_input
651
+
652
+ return session_data
653
+
654
+ # ============ GRADIO INTERFACE ============
655
+
656
+ def create_app():
657
+ """Create the Gradio interface"""
658
+
659
+ # Custom CSS for warm theme
660
+ custom_css = """
661
+ .gradio-container {
662
+ font-family: 'Georgia', serif !important;
663
+ }
664
+ .prose h1, .prose h2, .prose h3 {
665
+ color: #5d4e37 !important;
666
+ }
667
+ .chatbot {
668
+ font-size: 16px !important;
669
+ }
670
+ """
671
+
672
+ with gr.Blocks(
673
+ theme=gr.themes.Soft(
674
+ primary_hue="amber",
675
+ secondary_hue="orange",
676
+ neutral_hue="stone"
677
+ ),
678
+ css=custom_css,
679
+ title="NVC Deep Practice"
680
+ ) as demo:
681
+
682
+ # State variables
683
+ current_stage = gr.State(value=1)
684
+ session_data = gr.State(value={})
685
+ selected_needs = gr.State(value=[])
686
+
687
+ # Header
688
+ gr.Markdown("""
689
+ # NVC Deep Practice
690
+
691
+ *Learn Nonviolent Communication through embodied practice*
692
+
693
+ *Infused with the exquisite warmth of Sarah Peyton and Tara Brach -
694
+ resonant empathy meeting radical acceptance*
695
+
696
+ ---
697
+
698
+ **How it works:** Write a raw message you want to send someone. This practice
699
+ will guide you through understanding your feelings, needs, and how to express
700
+ them with clarity and compassion.
701
+ """)
702
+
703
+ with gr.Row():
704
+ # Main column
705
+ with gr.Column(scale=2):
706
+ chatbot = gr.Chatbot(
707
+ label="Practice Session",
708
+ height=450,
709
+ show_label=True,
710
+ avatar_images=(None, "https://em-content.zobj.net/source/twitter/376/seedling_1f331.png")
711
+ )
712
+
713
+ # Needs selector (hidden until stage 5)
714
+ with gr.Accordion("Select Your Needs", open=False, visible=False) as needs_accordion:
715
+ gr.Markdown("""
716
+ **Select 1-3 needs that resonate most deeply.**
717
+ Trust what arises - there's no wrong answer.
718
+ """)
719
+
720
+ with gr.Row():
721
+ with gr.Column():
722
+ emotional_needs = gr.CheckboxGroup(
723
+ choices=NEEDS_LIST["Emotional Needs"],
724
+ label="Emotional Needs (inner security, worth, safety)",
725
+ interactive=True
726
+ )
727
+ with gr.Column():
728
+ relational_needs = gr.CheckboxGroup(
729
+ choices=NEEDS_LIST["Relational Needs"],
730
+ label="Relational Needs (connection, mutuality)",
731
+ interactive=True
732
+ )
733
+
734
+ with gr.Row():
735
+ with gr.Column():
736
+ value_needs = gr.CheckboxGroup(
737
+ choices=NEEDS_LIST["Value Needs"],
738
+ label="Value Needs (meaning, growth, contribution)",
739
+ interactive=True
740
+ )
741
+ with gr.Column():
742
+ lifestyle_needs = gr.CheckboxGroup(
743
+ choices=NEEDS_LIST["Lifestyle Needs"],
744
+ label="Lifestyle Needs (wellbeing, vitality)",
745
+ interactive=True
746
+ )
747
+
748
+ selected_display = gr.Textbox(
749
+ label="Your Selected Needs",
750
+ interactive=False,
751
+ placeholder="Selected needs will appear here..."
752
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
753
 
754
+ confirm_needs_btn = gr.Button("Confirm My Needs", variant="primary")
755
+
756
+ # Message input
757
+ msg = gr.Textbox(
758
+ label="Your Message",
759
+ placeholder="What do you want to say? Or type 'continue' to proceed...",
760
+ lines=3
761
+ )
762
+
763
+ with gr.Row():
764
+ submit_btn = gr.Button("Continue Practice", variant="primary")
765
+ reset_btn = gr.Button("Start New Practice", variant="secondary")
766
+
767
+ # Sidebar
768
+ with gr.Column(scale=1):
769
+ gr.Markdown("### Settings")
770
+
771
+ somatic_check = gr.Checkbox(
772
+ label="Include body check-ins",
773
+ value=True,
774
+ info="Pause to notice body sensations"
775
+ )
776
+
777
+ gr.Markdown("### Current Stage")
778
+
779
+ stage_display = gr.Textbox(
780
+ label="Stage",
781
+ value="1/13: Raw Input",
782
+ interactive=False
783
+ )
784
+
785
+ focus_display = gr.Textbox(
786
+ label="Learning Focus",
787
+ value="Emotional awareness",
788
+ interactive=False
789
+ )
790
+
791
+ progress = gr.Slider(
792
+ minimum=1,
793
+ maximum=13,
794
+ value=1,
795
+ step=1,
796
+ label="Progress",
797
+ interactive=False
798
+ )
799
+
800
+ gr.Markdown("""
801
+ ### Resources
802
+ - [NVC Feelings List](https://www.cnvc.org/training/resource/feelings-inventory)
803
+ - [NVC Needs List](https://www.cnvc.org/training/resource/needs-inventory)
804
+ - [Marshall Rosenberg](https://www.cnvc.org/online-learning/nvc-instruction-guide/nvc-instruction-guide)
805
+ - [Sarah Peyton](https://sarahpeyton.com/)
806
+ - [Tara Brach](https://www.tarabrach.com/)
807
+ """)
808
+
809
+ # Event handlers
810
+ def handle_submit(message, history, stage, session, somatic, sel_needs):
811
+ """Handle message submission"""
812
+ if not message.strip():
813
+ message = "continue"
814
+
815
+ # If at stage 5 and needs haven't been confirmed, prompt to select
816
+ if stage == 5 and not sel_needs:
817
+ bot_response = "Please take a moment to select your needs from the list above, then click 'Confirm My Needs' to continue."
818
+ history = history + [[message, bot_response]]
819
+ return (
820
+ history, "", stage, session, sel_needs,
821
+ f"{stage}/13: {STAGE_NAMES[stage]}",
822
+ STAGE_FOCUS[stage],
823
+ stage,
824
+ gr.update(visible=True)
825
+ )
826
+
827
+ # If needs were just confirmed, add them to context
828
+ if stage == 5 and sel_needs:
829
+ session["needs"] = sel_needs
830
+ message = f"I've identified these needs: {', '.join(sel_needs)}"
831
+
832
+ # Process the stage
833
+ response, next_stage, updated_session, error = process_stage(
834
+ message, stage, session, somatic
835
+ )
836
+
837
+ if error:
838
+ history = history + [[message, error]]
839
+ return (
840
+ history, "", stage, session, sel_needs,
841
+ f"{stage}/13: {STAGE_NAMES[stage]}",
842
+ STAGE_FOCUS[stage],
843
+ stage,
844
+ gr.update(visible=(stage == 5))
845
+ )
846
+
847
+ # Update history
848
+ if message != "continue":
849
+ history = history + [[message, response]]
850
+ else:
851
+ history = history + [[None, response]]
852
+
853
+ # Show needs accordion at stage 5
854
+ show_needs = (next_stage == 5)
855
+
856
+ # Reset if completed
857
+ if stage == 13:
858
+ next_stage = 1
859
+ updated_session = {}
860
+ sel_needs = []
861
+
862
+ return (
863
+ history,
864
+ "",
865
+ next_stage,
866
+ updated_session,
867
+ sel_needs,
868
+ f"{next_stage}/13: {STAGE_NAMES[next_stage]}",
869
+ STAGE_FOCUS[next_stage],
870
+ next_stage,
871
+ gr.update(visible=show_needs)
872
+ )
873
+
874
+ def handle_reset():
875
+ """Reset the practice session"""
876
+ return (
877
+ [], # history
878
+ "", # message
879
+ 1, # stage
880
+ {}, # session_data
881
+ [], # selected_needs
882
+ "1/13: Raw Input",
883
+ "Emotional awareness",
884
+ 1,
885
+ gr.update(visible=False),
886
+ [], [], [], [] # clear all checkboxes
887
+ )
888
+
889
+ def update_selected_needs(emotional, relational, value, lifestyle):
890
+ """Update the selected needs display"""
891
+ all_selected = emotional + relational + value + lifestyle
892
+ return ", ".join(all_selected) if all_selected else "No needs selected yet"
893
+
894
+ def confirm_needs(emotional, relational, value, lifestyle, history, stage, session):
895
+ """Confirm selected needs and continue"""
896
+ all_selected = emotional + relational + value + lifestyle
897
+
898
+ if not all_selected:
899
+ return (
900
+ all_selected,
901
+ history + [[None, "Please select at least one need before continuing."]],
902
+ stage,
903
+ session
904
+ )
905
+
906
+ session["needs"] = all_selected
907
+
908
+ # Process to next stage with the needs
909
+ response, next_stage, updated_session, error = process_stage(
910
+ f"I've identified these needs: {', '.join(all_selected)}",
911
+ stage,
912
+ session,
913
+ True # somatic_enabled doesn't matter at this stage
914
+ )
915
+
916
+ history = history + [[f"Selected needs: {', '.join(all_selected)}", response]]
917
+
918
+ return all_selected, history, next_stage, updated_session
919
+
920
+ # Wire up events
921
+ submit_btn.click(
922
+ fn=handle_submit,
923
+ inputs=[msg, chatbot, current_stage, session_data, somatic_check, selected_needs],
924
+ outputs=[
925
+ chatbot, msg, current_stage, session_data, selected_needs,
926
+ stage_display, focus_display, progress, needs_accordion
927
+ ]
928
+ )
929
+
930
+ msg.submit(
931
+ fn=handle_submit,
932
+ inputs=[msg, chatbot, current_stage, session_data, somatic_check, selected_needs],
933
+ outputs=[
934
+ chatbot, msg, current_stage, session_data, selected_needs,
935
+ stage_display, focus_display, progress, needs_accordion
936
+ ]
937
+ )
938
+
939
+ reset_btn.click(
940
+ fn=handle_reset,
941
+ outputs=[
942
+ chatbot, msg, current_stage, session_data, selected_needs,
943
+ stage_display, focus_display, progress, needs_accordion,
944
+ emotional_needs, relational_needs, value_needs, lifestyle_needs
945
+ ]
946
+ )
947
+
948
+ # Update selected needs display when checkboxes change
949
+ for checkbox in [emotional_needs, relational_needs, value_needs, lifestyle_needs]:
950
+ checkbox.change(
951
+ fn=update_selected_needs,
952
+ inputs=[emotional_needs, relational_needs, value_needs, lifestyle_needs],
953
+ outputs=[selected_display]
954
+ )
955
+
956
+ confirm_needs_btn.click(
957
+ fn=confirm_needs,
958
+ inputs=[emotional_needs, relational_needs, value_needs, lifestyle_needs, chatbot, current_stage, session_data],
959
+ outputs=[selected_needs, chatbot, current_stage, session_data]
960
+ ).then(
961
+ fn=lambda s: (f"{s}/13: {STAGE_NAMES[s]}", STAGE_FOCUS[s], s, gr.update(visible=False)),
962
+ inputs=[current_stage],
963
+ outputs=[stage_display, focus_display, progress, needs_accordion]
964
+ )
965
 
966
+ return demo
967
 
968
+ # ============ LAUNCH ============
969
 
970
+ if __name__ == "__main__":
971
+ demo = create_app()
972
+ demo.launch()
requirements.txt CHANGED
@@ -1,10 +1,2 @@
1
- streamlit>=1.32.0
2
- python-dotenv>=1.0.0
3
- anthropic>=0.24.0
4
-
5
- # The following are no longer needed:
6
- # openai>=1.0.0
7
- # pyttsx3==2.98
8
- # google-generativeai
9
- # gradio
10
-
 
1
+ gradio>=4.0.0
2
+ anthropic>=0.18.0