jostlebot commited on
Commit
9c6a9ef
·
1 Parent(s): 63a30cb

Remove buggy Chatbot component - use Markdown display instead

Browse files
Files changed (1) hide show
  1. app.py +253 -977
app.py CHANGED
@@ -1,5 +1,6 @@
1
  """
2
  NVC Deep Practice - A Multi-Stage Nonviolent Communication Learning Space
 
3
  """
4
 
5
  import gradio as gr
@@ -23,546 +24,218 @@ print(f"=== STARTUP: API key configured: {API_KEY_CONFIGURED} ===")
23
 
24
  NEEDS_LIST = {
25
  "Emotional Needs": [
26
- "acceptance", "accountability", "acknowledgement", "affection",
27
- "appreciation", "attention", "autonomy", "belonging", "care",
28
- "consideration", "consistency", "dependability", "dignity",
29
- "emotional attunement", "freedom", "inclusion", "nurturance",
30
- "privacy", "protection", "reassurance", "respect", "security",
31
- "self-connection", "self-worth", "shared reality", "space",
32
- "to be heard", "to be known", "to be seen", "to matter",
33
- "trust", "understanding"
34
  ],
35
  "Relational Needs": [
36
- "awareness", "clarity", "closeness", "collaboration",
37
- "communication", "community", "companionship", "compassion",
38
- "connection", "emotional intimacy", "empathy", "friendship",
39
- "harmony", "help", "honesty", "integrity", "kindness",
40
- "listening", "love", "openness", "reconciliation",
41
- "shared values", "support", "synergy"
42
  ],
43
  "Value Needs": [
44
- "authenticity", "celebration", "choice", "courage", "curiosity",
45
- "creativity", "emotional integration", "empowerment", "equality",
46
- "fulfillment", "forgiveness", "gratefulness", "growth", "healing",
47
- "hope", "inspiration", "justice", "learning", "meaning",
48
- "mourning", "presence", "purpose", "reciprocity", "resilience",
49
- "resourcefulness", "self-advocacy", "self-expression", "solidarity"
50
  ],
51
  "Lifestyle Needs": [
52
- "adaptability", "beauty", "balance", "competence", "ease",
53
- "effectiveness", "fun", "financial wellbeing", "flexibility",
54
- "grace", "health", "joy", "movement", "nature", "novelty",
55
- "order", "passion", "peace", "play", "predictability", "rest",
56
- "safety", "self-care", "sexuality", "spontaneity", "structure",
57
- "vitality"
58
  ]
59
  }
60
 
61
  FEELINGS_CONNECTED = [
62
- "affectionate", "amazed", "animated", "appreciative", "calm", "centered",
63
- "clear-headed", "comfortable", "compassionate", "confident", "content",
64
- "curious", "delighted", "eager", "ecstatic", "elated", "empowered",
65
- "encouraged", "energetic", "engaged", "enlivened", "enthralled",
66
- "enthusiastic", "equanimous", "excited", "exhilarated", "expectant",
67
- "exuberant", "fascinated", "focused", "friendly", "fulfilled", "grateful",
68
- "happy", "hopeful", "inspired", "interested", "intrigued", "invigorated",
69
- "joyful", "loving", "mellow", "moved", "open", "optimistic", "passionate",
70
- "peaceful", "pleased", "proud", "radiant", "refreshed", "rejuvenated",
71
- "relaxed", "relieved", "rested", "safe", "satisfied", "secure", "tender",
72
- "thankful", "thrilled", "touched", "trusting", "vulnerable", "warm"
73
  ]
74
 
75
  FEELINGS_WANTING = [
76
- "afraid", "agitated", "alarmed", "angry", "annoyed", "anxious", "apathetic",
77
- "ashamed", "baffled", "bewildered", "bored", "confused", "depleted",
78
- "depressed", "despair", "devastated", "disappointed", "discouraged",
79
- "disgusted", "distant", "distracted", "distressed", "disturbed", "dread",
80
- "embarrassed", "enraged", "envious", "exasperated", "exhausted", "frustrated",
81
- "furious", "grief", "grumpy", "guarded", "guilty", "heartbroken",
82
- "heavy-hearted", "helpless", "hesitant", "hopeless", "horrified", "hurt",
83
- "impatient", "insecure", "irritated", "jealous", "lonely", "lost",
84
- "miserable", "mistrusting", "nervous", "nostalgic", "numb", "overwhelmed",
85
- "panicked", "perplexed", "rattled", "regretful", "resentful", "restless",
86
- "sad", "scared", "scattered", "self-conscious", "sensitive", "shocked",
87
- "stressed", "suspicious", "tense", "terrified", "tired", "torn", "troubled",
88
- "turmoil", "uncomfortable", "uneasy", "unhappy", "unsettled", "upset",
89
- "vulnerable", "withdrawn", "worried"
90
- ]
91
-
92
- FEELINGS_ADVOCACY = [
93
- "abandoned", "abused", "attacked", "belittled", "betrayed", "blamed",
94
- "bullied", "coerced", "controlled", "criticized", "demeaned", "discriminated",
95
- "dismissed", "disrespected", "excluded", "humiliated", "ignored", "insulted",
96
- "invisible", "judged", "manipulated", "misunderstood", "neglected",
97
- "patronized", "put down", "pressured", "provoked", "rejected", "shamed",
98
- "targeted", "unappreciated", "unheard", "used", "violated", "wronged"
99
  ]
100
 
101
  # ============ SYSTEM PROMPTS ============
102
 
103
  BASE_TONE = """
104
- Infused with the exquisite warmth of Sarah Peyton and Tara Brach -
105
- resonant empathy meeting radical acceptance, holding space with
106
- the tenderness of complete belonging.
107
 
108
  CRITICAL LANGUAGE RULES:
109
- - NEVER use first person ("I", "I'm", "I sense", "I hear", "I wonder", etc.)
110
  - DO use second person warmly ("you", "your")
111
  - Use impersonal constructions: "What's here is...", "There's...", "It sounds like..."
112
- - Ask "you" questions: "Are you feeling...?", "Does this resonate with you?"
113
  """
114
 
115
  STAGE_PROMPTS = {
116
  1: f"""
117
- Embody the presence of an ideal secure attachment figure - warm,
118
- steady, deeply attuned. Hold space with the quality of exquisite
119
- attunement, as if the most loving relational presence is witnessing
120
- what's alive here.
121
-
122
  {BASE_TONE}
123
 
124
- This is NVC training in the lineage of Marshall Rosenberg and
125
- Sarah Peyton, but delivered with the warmth of secure attachment -
126
- the kind of presence that sees, honors, and holds.
127
-
128
  The raw message has been shared. Offer:
129
  1. Warm acknowledgment of what's present
130
  2. Reflection of the emotional energy sensed
131
- 3. NO judgment or correction - just tender presence
132
- 4. Deep honoring of what's being expressed
133
 
134
- TONE: Like the most attuned caregiver reflecting back what they sense,
135
- with complete acceptance and warmth. Gentle, unhurried, fully present.
136
- The quality of "what's here is completely seen and welcome."
137
-
138
- Keep it brief (2-3 sentences) but saturated with warmth and presence.
139
  """,
140
 
141
  2: f"""
142
- Offer empathic reflection using NVC feelings vocabulary.
143
-
144
  {BASE_TONE}
145
 
146
- FEELINGS VOCABULARY - Use these rich distinctions:
147
-
148
- When connected to needs: {', '.join(FEELINGS_CONNECTED[:20])}... and more.
149
-
150
- When wanting more connection to needs: {', '.join(FEELINGS_WANTING[:20])}... and more.
151
-
152
- When advocacy/healing is needed: {', '.join(FEELINGS_ADVOCACY[:15])}... and more.
153
-
154
- SARAH PEYTON RESONANCE with TARA BRACH WARMTH:
155
- - Warm, gentle, curious tone - like the most attuned presence
156
- - Multiple feeling guesses (honor complexity and depth)
157
- - Phrases like "Are you feeling...?" "Is there...?" "Could it be...?" "Might you be...?"
158
- - Go for depth and nuance, not surface emotions
159
- - Name layers (e.g., "frustrated, and underneath that, perhaps hurt?")
160
- - Hold space with complete presence
161
- - Radical acceptance - whatever's here is completely welcome
162
 
163
- Offer 2-4 feeling guesses that honor the complexity and depth.
164
- Use specific, nuanced feelings vocabulary.
165
 
166
- End with gentle invitation: "Does any of this land for you?" or "What resonates?"
 
167
  """,
168
 
169
  3: f"""
170
- Guide a brief somatic check-in with the tenderness of an ideal
171
- caregiver helping someone connect to their body's wisdom.
172
-
173
  {BASE_TONE}
174
 
175
- EMBODIMENT PRINCIPLES:
176
- - Somatic awareness creates integration
177
- - The body holds wisdom about needs
178
- - Slowing down to feel creates space for choice
179
- - This is an invitation, not a demand - gentle, spacious
180
- - Complete safety in whatever arises (or doesn't)
181
-
182
- TONE: Like the most patient, loving presence guiding someone
183
- to reconnect with themselves. No rush. Complete safety.
184
- The quality of "whatever's here is completely welcome."
185
 
186
- Offer 2-3 gentle questions:
187
- - "Where might you be feeling this in your body right now?"
188
- - "What's the quality of sensation there?" (tight, warm, heavy, buzzing...)
189
- - "What does your body want to do with this energy?"
190
-
191
- Keep it brief (2-3 sentences) and completely non-pressuring.
192
-
193
- End with: "And if your body's not speaking clearly right now,
194
- that's completely okay too. Sometimes the body needs more time
195
- and safety to share what it knows."
196
  """,
197
 
198
  4: f"""
199
- Deliver the foundational teaching: feelings communicate needs.
200
- This is the heart of NVC - feelings are messengers.
201
-
202
  {BASE_TONE}
203
 
204
- CORE TEACHING TO DELIVER:
205
-
206
- Feelings are messengers. They're not the problem - they're information
207
- about what's needed.
208
-
209
- When feelings are pleasant (grateful, peaceful, joyful), they're saying:
210
- "Needs are being met. This is nourishing."
211
-
212
- When feelings are uncomfortable (frustrated, lonely, scared), they're saying:
213
- "There's a need here wanting attention."
214
-
215
- Marshall Rosenberg: "Feelings are the voice of our needs. They tell us
216
- whether our needs are being met or not."
217
 
218
- This changes everything. Instead of pushing feelings away or getting
219
- lost in them, we can listen. "What is this feeling telling me about
220
- what I need?"
221
 
222
- Anger points to needs like respect, autonomy, fairness.
223
- Sadness points to needs like connection, understanding, mourning.
224
- Fear points to needs like safety, predictability, reassurance.
225
- Loneliness points to needs like belonging, companionship, to be seen.
226
 
227
- The practice: Feel the feeling, then get curious about the need.
228
-
229
- PEDAGOGICAL APPROACH:
230
- - Keep it clear and brief (4-5 sentences)
231
- - Use Marshall Rosenberg quote
232
- - Give concrete examples connecting feelings to needs
233
- - End with invitation to discover their own needs
234
-
235
- TONE: Teaching with warmth and reverence. The quality of "this is
236
- such important wisdom - feelings as messengers, not problems."
237
-
238
- After delivering this teaching, transition warmly: "So... what needs
239
- might be calling for attention in what you're feeling?"
240
  """,
241
 
242
  5: f"""
243
- Guide the identification of universal needs with the warmth of
244
- someone helping another discover their own inner truth.
245
-
246
  {BASE_TONE}
247
 
248
- Based on the feelings identified earlier, offer gentle guesses about
249
- which needs might be present:
250
-
251
- If feeling frustrated/angry -> might need: respect, autonomy, understanding,
252
- to be heard, fairness, choice
253
-
254
- If feeling hurt/sad -> might need: care, empathy, connection, to matter,
255
- emotional intimacy, reassurance
256
-
257
- If feeling scared/anxious -> might need: safety, security, predictability,
258
- reassurance, protection, trust
259
 
260
- If feeling lonely/disconnected -> might need: belonging, companionship,
261
- closeness, to be seen, to be known, connection
262
 
263
- If feeling dismissed/invisible -> might need: acknowledgement, to be heard,
264
- to be seen, respect, dignity, to matter
265
-
266
- If feeling exhausted/depleted -> might need: rest, support, care, ease,
267
- balance, nurturance
268
-
269
- Offer 2-4 need guesses warmly, then invite them to explore:
270
- "These are guesses - what resonates? And there's a rich list below to
271
- explore what's most true for you."
272
-
273
- TEACH THE UNIVERSALITY OF NEEDS:
274
-
275
- Needs are universal - every human being shares these needs.
276
- They're what's essential for thriving and flourishing.
277
-
278
- Needs are NOT strategies. A need is universal (connection). A strategy
279
- is specific (wanting this particular person to call).
280
-
281
- Example:
282
- Need = understanding, to be known
283
- Strategy = "I need you to listen to me for an hour"
284
-
285
- Marshall Rosenberg: "When we express our needs rather than our
286
- interpretations of others' behavior, we increase the likelihood
287
- of meeting those needs."
288
-
289
- TONE: Gentle guidance with assistive support. The quality of
290
- "here's some help finding your way, and trust your own knowing."
291
  """,
292
 
293
  6: f"""
294
- The needs have been identified. Offer deep resonance in Sarah Peyton's
295
- and Tara Brach's style.
296
-
297
  {BASE_TONE}
298
 
299
- RESONANCE PRINCIPLES:
300
  - Acknowledge the beauty and legitimacy of these needs
301
- - Reflect back the universality ("Of course there's longing for...")
302
  - Create warmth and self-compassion
303
- - Brief but touching (2-3 sentences)
304
- - The quality of "this need is so deeply human and completely welcome"
305
-
306
- Example: "Of course there's longing for understanding - to be truly
307
- seen and known. That's such a tender, human need. Can you feel how
308
- natural it is to want that?"
309
-
310
- Use the specific needs identified. Make it personal and saturated with warmth.
311
- This is a moment of deep honoring and belonging.
312
 
313
- TONE: Pure resonance and acceptance. The quality of "these needs are
314
- beautiful and completely legitimate - of course this matters to you."
315
  """,
316
 
317
  7: f"""
318
- Based on the feelings and context, deliver a relevant micro-lesson
319
- with warmth and honoring.
320
-
321
  {BASE_TONE}
322
 
323
- AVAILABLE LESSONS (choose the most relevant):
324
-
325
- ANGER FAMILY: Anger is a messenger - it arrives to say a boundary has been
326
- crossed or a need isn't met. It's protective energy with wisdom in it.
327
- Marshall Rosenberg: "At the core of all anger is a need that is not being fulfilled."
328
-
329
- I-LANGUAGE: Notice the difference:
330
- "You never listen!" triggers defensiveness.
331
- "I feel unheard because I need understanding" creates an opening.
332
- Marshall Rosenberg: "When we express our needs rather than our interpretations
333
- of others' behavior, we increase the likelihood of meeting those needs."
334
 
335
- FEELINGS VS THOUGHTS: This distinction matters:
336
- NOT feelings: "I feel like you don't care" (thought), "I feel ignored" (interpretation)
337
- REAL feelings: "I feel hurt", "I feel sad", "I feel scared"
338
- Feelings are one-word descriptions of the inner landscape.
339
-
340
- VULNERABILITY: What's underneath the anger or frustration? Often there's
341
- something more tender - hurt, fear, longing, sadness. It takes courage
342
- to let that softer layer be seen.
343
-
344
- ADVOCACY FEELINGS: Some feelings point to experiences needing acknowledgment:
345
- abandoned, betrayed, dismissed, ignored, judged, rejected...
346
- These name real experiences of harm AND usually have a more vulnerable
347
- feeling underneath (hurt, scared, heartbroken).
348
-
349
- PEDAGOGICAL APPROACH:
350
- - Keep it brief (3-4 sentences max)
351
- - Include a Marshall Rosenberg quote when applicable
352
- - Connect to the specific situation with tenderness
353
- - End with an invitation or gentle insight
354
-
355
- TONE: Like a wise, loving teacher who sees capacity and offers
356
- wisdom with complete faith in unfolding.
357
  """,
358
 
359
  8: f"""
360
- Deliver micro-lesson on relational fields and early patterning.
361
-
362
  {BASE_TONE}
363
 
364
- KEY CONCEPTS TO WEAVE IN:
365
- 1. Interpersonal neurobiology - we're wired for connection
366
- 2. Early attachment patterns create default responses
367
- 3. Unintegrated emotional charge in relational fields
368
- 4. Neuroplasticity - we can create new patterns through practice
369
- 5. Reactivity vs. responsiveness
370
-
371
- TEACHING APPROACH:
372
- - Make it personal to their situation
373
- - Normalize the difficulty (not their fault)
374
- - Inspire hope (change is possible)
375
- - Keep to 4-5 sentences
376
- - Deep compassion for the old patterns
377
-
378
- Example frame: "The reactivity that's here isn't a character flaw -
379
- it's old wiring from early relationships. When needs didn't get met
380
- as a child, the nervous system learned to protect. The beautiful news:
381
- your brain is neuroplastic. Each time you practice pausing and naming
382
- your needs, you're creating new neural pathways."
383
-
384
- TONE: Tender psychoeducation. The quality of "this makes complete sense
385
- given where you've been, and there's so much possibility ahead."
386
  """,
387
 
388
  9: f"""
389
- Help craft an NVC request with warmth and clarity.
390
-
391
  {BASE_TONE}
392
 
393
- MARSHALL ROSENBERG ON REQUESTS:
394
- "Requests are the third component of NVC. We express what we are
395
- requesting rather than what we are NOT requesting."
396
-
397
- "A request is a specific action that we would like the other person
398
- to take. It's important that we make requests that are doable,
399
- concrete, and positive."
400
 
401
  REQUEST CRITERIA:
402
  1. Specific (not vague)
403
  2. Doable (concrete action)
404
- 3. Positive language (what you want, not what you don't want)
405
- 4. Present moment or near future
406
- 5. Honors other's autonomy (genuine request, not demand)
407
-
408
- DEMANDS VS REQUESTS:
409
- Demand: Blame/punishment if they say no
410
- Request: Empathy for their needs if they say no
411
-
412
- Guide: "What specific, doable action would meet your need for [their identified need]?"
413
 
414
- Offer 1-2 example requests based on their needs, then invite them to craft their own.
415
-
416
- TONE: Encouraging, clear, with deep trust in their capacity to ask
417
- for what they need. The quality of "you deserve to have your needs met."
418
  """,
419
 
420
  10: f"""
421
- Deliver micro-lesson on autonomy and receiving "no."
422
-
423
  {BASE_TONE}
424
 
425
- MARSHALL ROSENBERG QUOTES TO USE:
426
-
427
- "NVC's most important use may be in developing self-compassion."
428
 
429
- "We are never angry because of what someone else did. We can identify
430
- the other person's behavior as a stimulus, but it is important to
431
- establish a clear separation between stimulus and cause."
432
 
433
- "When we hear the other person's feelings and needs, we recognize
434
- our common humanity."
435
 
436
- TEACHING POINTS:
437
- - True requests allow for "no"
438
- - "No" protects their needs (just as valid as yours)
439
- - The practice: Get curious about their needs if they decline
440
- - This is how mutuality works - both people's needs matter
441
-
442
- Frame: "If you're not willing to hear 'no,' it's a demand, not a
443
- request. The practice is: Can you care about their needs even when
444
- they don't meet your strategy?"
445
-
446
- Keep it compassionate but clear. This is crucial NVC teaching.
447
-
448
- TONE: Tender but firm. The quality of "this is where real love lives -
449
- in honoring each other's autonomy."
450
  """,
451
 
452
  11: f"""
453
- Invite rewriting of the original message with encouragement and structure.
454
-
455
  {BASE_TONE}
456
 
457
- SCAFFOLDING PROMPT:
458
- "Now, with awareness of your feelings, needs, and request,
459
- would you like to try rewriting your message? Here's a structure
460
- if it's helpful:
461
-
462
- - When [observation - concrete, specific]
463
- - I feel [feeling word]
464
- - Because I need [universal need]
465
- - Would you be willing to [specific, doable request]?
466
-
467
- Or use whatever language feels authentic. The structure is training
468
- wheels - connection is the goal."
469
-
470
- Provide encouragement and remind: this is practice, not perfection.
471
 
472
- Show them their original message for reference.
 
 
 
 
473
 
474
- TONE: Warm encouragement. The quality of "you've got this, and whatever
475
- emerges will be a step forward."
476
  """,
477
 
478
  12: f"""
479
- Provide warm, constructive feedback on the revised message.
480
-
481
  {BASE_TONE}
482
 
483
- FEEDBACK APPROACH:
484
- 1. Name what's working (specific praise)
485
- 2. Offer one growth edge (gently)
486
- 3. Reflect on the shift from original to revised
487
- 4. Invite somatic reflection: "How does it feel in your body to express it this way?"
488
-
489
- GROWTH EDGES TO WATCH FOR:
490
- - Faux feelings ("I feel like you..." "I feel that...")
491
- - Needs confused with strategies
492
- - Requests as demands
493
- - Blame language sneaking in
494
- - Vagueness (not specific/doable)
495
 
496
  Keep feedback ratio 3:1 positive to constructive.
497
- Always end with encouragement and invitation to notice body/energy.
498
-
499
- TONE: Celebratory of effort, gentle with growth edges. The quality
500
- of "look at this beautiful step you're taking."
501
  """,
502
 
503
  13: f"""
504
- Close the practice session with integration and honoring.
505
-
506
  {BASE_TONE}
507
 
508
- INTEGRATION ELEMENTS:
509
  1. Honor the work done
510
- 2. Name key learning (ask: "What's one thing you'll take with you?")
511
- 3. Connect to neuroplasticity ("Each practice strengthens new pathways")
512
- 4. Offer encouragement for real-world application
513
- 5. Remind: progress over perfection
514
-
515
- CLOSING FRAME:
516
- Marshall Rosenberg: "The more we empathize with the other party,
517
- the safer we feel."
518
-
519
- Invite noticing how this practice shows up in the next real conversation.
520
- Neuroplasticity requires repetition - each attempt rewires relational capacity.
521
-
522
- TONE: Warm, honoring, hopeful. Vulnerable work has been done and deserves
523
- deep acknowledgment. The quality of "you showed up for yourself today,
524
- and that matters so much."
525
 
526
- End with an invitation to start a new practice if they'd like.
527
  """
528
  }
529
 
530
  STAGE_NAMES = {
531
- 1: "Raw Input",
532
  2: "Feeling Reflection",
533
- 3: "Somatic Check-In",
534
- 4: "Feelings Communicate Needs",
535
- 5: "Needs Identification",
536
  6: "Needs Resonance",
537
- 7: "Feelings Lesson",
538
- 8: "Relational Field Lesson",
539
- 9: "Request Formulation",
540
- 10: "Autonomy Lesson",
541
- 11: "Rewrite Practice",
542
- 12: "Feedback & Reflection",
543
- 13: "Integration & Close"
544
- }
545
-
546
- STAGE_FOCUS = {
547
- 1: "Emotional awareness",
548
- 2: "Feelings vocabulary",
549
- 3: "Embodiment",
550
- 4: "Feelings as messengers",
551
- 5: "Needs literacy",
552
- 6: "Self-compassion",
553
- 7: "Conceptual understanding",
554
- 8: "Systemic awareness",
555
- 9: "Clear asking",
556
- 10: "Mutuality",
557
- 11: "Application",
558
- 12: "Integration",
559
- 13: "Completion"
560
  }
561
 
562
  # ============ CLAUDE INTEGRATION ============
563
 
564
  def get_client():
565
- """Initialize Anthropic client - try multiple possible secret names"""
566
  api_key = (
567
  os.environ.get("ANTHROPIC_API_KEY") or
568
  os.environ.get("anthropic_key") or
@@ -573,626 +246,229 @@ def get_client():
573
  return None
574
  return anthropic.Anthropic(api_key=api_key)
575
 
576
- def call_claude(system_prompt, messages, context=""):
577
- """Call Claude API with given prompts"""
578
  client = get_client()
579
 
580
  if client is None:
581
- return None # Return None to indicate API not available
582
 
583
- if context:
584
- user_content = f"{context}\n\nUser message: {messages[-1]['content']}" if messages else context
585
- else:
586
- user_content = messages[-1]['content'] if messages else ""
587
 
588
  response = client.messages.create(
589
  model="claude-sonnet-4-20250514",
590
  max_tokens=1500,
591
  system=system_prompt,
592
- messages=[{"role": "user", "content": user_content}]
593
  )
594
 
595
  return response.content[0].text
596
 
597
- # ============ STAGE PROCESSING ============
598
 
599
- API_ERROR_MESSAGE = """**API Configuration Required**
 
 
 
600
 
601
- To use this NVC practice tool, the Space owner needs to add an Anthropic API key:
 
 
 
 
 
 
 
602
 
603
- 1. Go to **Settings** (gear icon) in this Space
604
- 2. Click **Repository secrets**
605
- 3. Add a new secret named `ANTHROPIC_API_KEY`
606
- 4. Paste your API key from [console.anthropic.com](https://console.anthropic.com)
607
- 5. **Restart the Space** (Factory reboot)
608
 
609
- If you're the Space owner and have already added the key, try restarting the Space."""
610
-
611
- def process_stage(user_input, stage, session_data, somatic_enabled):
612
- """Process current stage and return response"""
613
 
 
614
  if stage == 3 and not somatic_enabled:
615
- return "", 4, session_data, "Skipping somatic check-in..."
 
 
 
 
 
 
 
 
 
 
616
 
617
- context = build_context(stage, session_data)
618
  system_prompt = STAGE_PROMPTS.get(stage, STAGE_PROMPTS[1])
619
 
620
  try:
621
- response = call_claude(
622
- system_prompt,
623
- [{"role": "user", "content": user_input}],
624
- context
625
- )
626
 
627
- # Check if API key was not configured
628
  if response is None:
629
- return "", stage, session_data, API_ERROR_MESSAGE
630
-
631
- except anthropic.AuthenticationError:
632
- return "", stage, session_data, "**Invalid API Key** - The API key appears to be invalid. Please check that you've entered a valid Anthropic API key in the Space secrets."
633
- except anthropic.RateLimitError:
634
- return "", stage, session_data, "**Rate Limited** - Too many requests. Please wait a moment and try again."
635
- except anthropic.APIConnectionError:
636
- return "", stage, session_data, "**Connection Error** - Unable to connect to the Anthropic API. Please try again in a moment."
637
- except Exception as e:
638
- error_type = type(e).__name__
639
- return "", stage, session_data, f"**Error** ({error_type}): {str(e)}"
640
 
641
- session_data = update_session_data(stage, user_input, session_data)
642
- next_stage = stage + 1 if stage < 13 else 1
 
 
643
 
644
- return response, next_stage, session_data, None
 
 
 
 
 
 
645
 
646
- def build_context(stage, session_data):
647
- """Build context string for Claude based on stage"""
648
- context_parts = []
 
 
649
 
650
- if session_data.get("original_message"):
651
- context_parts.append(f"Original message: \"{session_data['original_message']}\"")
652
 
653
- if session_data.get("feelings") and stage > 2:
654
- context_parts.append(f"Feelings identified: {', '.join(session_data['feelings'])}")
 
 
 
 
 
 
 
 
 
 
 
655
 
656
- if session_data.get("body_sensations") and stage > 3:
657
- context_parts.append(f"Body sensations: {session_data['body_sensations']}")
 
 
 
 
 
 
 
 
 
658
 
659
- if session_data.get("needs") and stage > 5:
660
- context_parts.append(f"Needs identified: {', '.join(session_data['needs'])}")
 
 
661
 
662
- if session_data.get("request") and stage > 9:
663
- context_parts.append(f"Request formulated: {session_data['request']}")
664
 
665
- if session_data.get("revised_message") and stage > 11:
666
- context_parts.append(f"Revised message: \"{session_data['revised_message']}\"")
 
 
667
 
668
- return "\n".join(context_parts)
 
 
 
669
 
670
- def update_session_data(stage, user_input, session_data):
671
- """Update session data based on completed stage"""
672
- if stage == 1:
673
- session_data["original_message"] = user_input
674
- elif stage == 2:
675
- session_data["feelings"] = session_data.get("feelings", [])
676
- elif stage == 3:
677
- session_data["body_sensations"] = user_input
678
- elif stage == 9:
679
- session_data["request"] = user_input
680
- elif stage == 11:
681
- session_data["revised_message"] = user_input
682
-
683
- return session_data
684
-
685
- # ============ GRADIO INTERFACE ============
686
-
687
- def create_app():
688
- """Create the Gradio interface with custom color palette"""
689
-
690
- custom_css = """
691
- :root {
692
- --warm-gold: #B8941E;
693
- --olive-yellow: #A8A832;
694
- --deep-forest: #3D5932;
695
- --sage-green: #6B8070;
696
- --soft-cream: #F8F6F0;
697
- --warm-beige: #EAE6DC;
698
- }
699
-
700
- .gradio-container {
701
- font-family: 'Inter', 'Segoe UI', system-ui, sans-serif !important;
702
- background: var(--soft-cream) !important;
703
- }
704
-
705
- .main-header {
706
- background: linear-gradient(135deg, var(--warm-gold) 0%, #C9A52E 100%);
707
- padding: 2.5rem 2rem;
708
- border-radius: 16px;
709
- margin-bottom: 2rem;
710
- border: none;
711
- box-shadow: 0 4px 20px rgba(184, 148, 30, 0.2);
712
- }
713
-
714
- .main-header h1 {
715
- color: white;
716
- font-size: 2.2rem;
717
- font-weight: 600;
718
- margin-bottom: 0.5rem;
719
- letter-spacing: -0.02em;
720
- text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
721
- }
722
-
723
- .main-header p {
724
- color: rgba(255, 255, 255, 0.95);
725
- font-size: 1.05rem;
726
- line-height: 1.6;
727
- margin-top: 0.5rem;
728
- }
729
-
730
- .intro-text {
731
- color: var(--deep-forest);
732
- font-size: 1.05rem;
733
- line-height: 1.7;
734
- padding: 1.25rem 1.5rem;
735
- background: white;
736
- border-radius: 12px;
737
- border-left: 4px solid var(--sage-green);
738
- margin-bottom: 1.5rem;
739
- box-shadow: 0 2px 8px rgba(61, 89, 50, 0.08);
740
- }
741
-
742
- #chatbot {
743
- border: 2px solid var(--sage-green) !important;
744
- border-radius: 16px !important;
745
- background: white !important;
746
- box-shadow: 0 4px 16px rgba(61, 89, 50, 0.1) !important;
747
- }
748
-
749
- .message.user {
750
- background: linear-gradient(135deg, var(--olive-yellow) 0%, #B8B840 100%) !important;
751
- color: white !important;
752
- border: none !important;
753
- border-radius: 18px 18px 4px 18px !important;
754
- padding: 0.85rem 1.1rem !important;
755
- margin: 0.5rem 0 !important;
756
- box-shadow: 0 2px 8px rgba(168, 168, 50, 0.2) !important;
757
- }
758
-
759
- .message.bot {
760
- background: linear-gradient(135deg, var(--sage-green) 0%, #7A9080 100%) !important;
761
- color: white !important;
762
- border: none !important;
763
- border-radius: 18px 18px 18px 4px !important;
764
- padding: 0.85rem 1.1rem !important;
765
- margin: 0.5rem 0 !important;
766
- box-shadow: 0 2px 8px rgba(107, 128, 112, 0.2) !important;
767
- }
768
-
769
- textarea {
770
- border: 2px solid var(--sage-green) !important;
771
- border-radius: 12px !important;
772
- padding: 1rem !important;
773
- font-size: 1rem !important;
774
- background: white !important;
775
- transition: all 0.3s ease !important;
776
- color: var(--deep-forest) !important;
777
- }
778
-
779
- textarea:focus {
780
- border-color: var(--deep-forest) !important;
781
- box-shadow: 0 0 0 3px rgba(61, 89, 50, 0.1) !important;
782
- outline: none !important;
783
- }
784
-
785
- textarea::placeholder {
786
- color: var(--sage-green) !important;
787
- opacity: 0.7 !important;
788
- }
789
-
790
- .primary-button button {
791
- background: linear-gradient(135deg, var(--deep-forest) 0%, #2D4426 100%) !important;
792
- color: white !important;
793
- border: none !important;
794
- border-radius: 10px !important;
795
- padding: 0.85rem 1.75rem !important;
796
- font-weight: 600 !important;
797
- font-size: 1rem !important;
798
- transition: all 0.3s ease !important;
799
- box-shadow: 0 3px 12px rgba(61, 89, 50, 0.3) !important;
800
- }
801
-
802
- .primary-button button:hover {
803
- background: linear-gradient(135deg, #2D4426 0%, #1D3416 100%) !important;
804
- box-shadow: 0 5px 16px rgba(61, 89, 50, 0.4) !important;
805
- transform: translateY(-2px) !important;
806
- }
807
-
808
- .secondary-button button {
809
- background: linear-gradient(135deg, var(--sage-green) 0%, #7A9080 100%) !important;
810
- color: white !important;
811
- border: none !important;
812
- border-radius: 10px !important;
813
- padding: 0.85rem 1.5rem !important;
814
- font-weight: 500 !important;
815
- transition: all 0.3s ease !important;
816
- box-shadow: 0 2px 8px rgba(107, 128, 112, 0.2) !important;
817
- }
818
-
819
- .secondary-button button:hover {
820
- background: linear-gradient(135deg, #7A9080 0%, #8AA090 100%) !important;
821
- box-shadow: 0 4px 12px rgba(107, 128, 112, 0.3) !important;
822
- transform: translateY(-1px) !important;
823
- }
824
-
825
- .sidebar-section {
826
- background: white;
827
- border-radius: 12px;
828
- padding: 1.25rem;
829
- margin-bottom: 1.25rem;
830
- border: 2px solid var(--warm-beige);
831
- box-shadow: 0 2px 8px rgba(61, 89, 50, 0.06);
832
- }
833
-
834
- .sidebar-section h3 {
835
- color: var(--deep-forest);
836
- font-size: 1.1rem;
837
- font-weight: 600;
838
- margin-bottom: 0.75rem;
839
- padding-bottom: 0.5rem;
840
- border-bottom: 2px solid var(--olive-yellow);
841
- }
842
-
843
- .focus-display input {
844
- background: linear-gradient(135deg, var(--warm-gold) 0%, #C9A52E 100%) !important;
845
- border: none !important;
846
- border-radius: 10px !important;
847
- padding: 1rem !important;
848
- font-size: 1.05rem !important;
849
- font-weight: 600 !important;
850
- color: white !important;
851
- text-align: center !important;
852
- box-shadow: 0 3px 12px rgba(184, 148, 30, 0.25) !important;
853
- }
854
-
855
- input[type="checkbox"] {
856
- accent-color: var(--deep-forest) !important;
857
- }
858
-
859
- .checkbox-label {
860
- color: var(--deep-forest) !important;
861
- font-weight: 500 !important;
862
- }
863
-
864
- label {
865
- color: var(--deep-forest) !important;
866
- font-weight: 500 !important;
867
- }
868
-
869
- .card-shadow {
870
- box-shadow: 0 3px 15px rgba(61, 89, 50, 0.1);
871
- }
872
- """
873
-
874
- with gr.Blocks(css=custom_css, theme=gr.themes.Soft(
875
- primary_hue="green",
876
- secondary_hue="amber",
877
- neutral_hue="stone",
878
- font=["Inter", "system-ui", "sans-serif"]
879
- )) as demo:
880
-
881
- # State variables
882
- current_stage = gr.State(value=1)
883
- session_data = gr.State(value={})
884
- selected_needs = gr.State(value=[])
885
-
886
- # Header
887
- gr.HTML("""
888
- <div class="main-header card-shadow">
889
- <h1>NVC Deep Practice</h1>
890
- <p><strong>Learn <a href="https://www.cnvc.org/" target="_blank" style="color: white; text-decoration: underline;">Nonviolent Communication</a> through embodied practice</strong></p>
891
- </div>
892
- """)
893
 
894
- # Show warning if API key not configured
895
- if not API_KEY_CONFIGURED:
896
- gr.HTML("""
897
- <div style="background: #FEF3C7; border: 2px solid #F59E0B; border-radius: 12px; padding: 1rem 1.5rem; margin-bottom: 1.5rem;">
898
- <p style="color: #92400E; margin: 0; font-weight: 500;">
899
- <strong>Setup Required:</strong> This Space needs an Anthropic API key to function.
900
- If you're the owner, go to Settings > Repository secrets > Add <code>ANTHROPIC_API_KEY</code>, then restart the Space.
901
- </p>
902
- </div>
903
- """)
904
 
905
- gr.HTML("""
906
- <div class="intro-text">
907
- Write a raw message you want to send someone. This practice will guide you through
908
- understanding your feelings, needs, and how to express them with clarity and compassion.
909
- </div>
910
  """)
911
 
912
- with gr.Row():
913
- # Main chat area
914
- with gr.Column(scale=2):
915
- chatbot = gr.Chatbot(
916
- label="Practice Session",
917
- height=500,
918
- show_label=True,
919
- elem_id="chatbot",
920
- bubble_full_width=False,
921
- type="messages"
922
- )
923
-
924
- # Needs selector (hidden until stage 5)
925
- with gr.Accordion("Select Your Needs", open=False, visible=False) as needs_accordion:
926
- gr.Markdown("""
927
- **Select 1-3 needs that resonate most deeply.**
928
- Trust what arises - there's no wrong answer.
929
- """)
930
-
931
- with gr.Row():
932
- with gr.Column():
933
- emotional_needs = gr.CheckboxGroup(
934
- choices=NEEDS_LIST["Emotional Needs"],
935
- label="Emotional Needs (inner security, worth, safety)",
936
- interactive=True
937
- )
938
- with gr.Column():
939
- relational_needs = gr.CheckboxGroup(
940
- choices=NEEDS_LIST["Relational Needs"],
941
- label="Relational Needs (connection, mutuality)",
942
- interactive=True
943
- )
944
-
945
- with gr.Row():
946
- with gr.Column():
947
- value_needs = gr.CheckboxGroup(
948
- choices=NEEDS_LIST["Value Needs"],
949
- label="Value Needs (meaning, growth, contribution)",
950
- interactive=True
951
- )
952
- with gr.Column():
953
- lifestyle_needs = gr.CheckboxGroup(
954
- choices=NEEDS_LIST["Lifestyle Needs"],
955
- label="Lifestyle Needs (wellbeing, vitality)",
956
- interactive=True
957
- )
958
-
959
- selected_display = gr.Textbox(
960
- label="Your Selected Needs",
961
- interactive=False,
962
- placeholder="Selected needs will appear here..."
963
- )
964
-
965
- confirm_needs_btn = gr.Button("Confirm My Needs", variant="primary")
966
-
967
- msg = gr.Textbox(
968
- label="Your Message",
969
- placeholder="What would you like to say? Share what's alive in you...",
970
- lines=3,
971
- show_label=False
972
- )
973
 
 
 
 
 
 
 
974
  with gr.Row():
975
- submit_btn = gr.Button(
976
- "Continue Practice",
977
- variant="primary",
978
- elem_classes=["primary-button"],
979
- scale=2
980
- )
981
- reset_btn = gr.Button(
982
- "Start Fresh",
983
- elem_classes=["secondary-button"],
984
- scale=1
985
- )
986
-
987
- # Sidebar
988
- with gr.Column(scale=1):
989
- gr.HTML("""
990
- <div class="sidebar-section card-shadow">
991
- <h3>Practice Settings</h3>
992
- </div>
993
- """)
994
-
995
- somatic_check = gr.Checkbox(
996
- label="Include body check-ins",
997
- value=True,
998
- info="Pause to notice body sensations",
999
- elem_classes=["checkbox-label"]
1000
- )
1001
-
1002
- gr.HTML("""
1003
- <div class="sidebar-section card-shadow" style="margin-top: 1.5rem;">
1004
- <h3>Current Focus</h3>
1005
- <p style="color: #6B8070; font-size: 0.9rem; margin-bottom: 0.75rem;">
1006
- Track your learning journey
1007
- </p>
1008
- </div>
1009
- """)
1010
-
1011
- focus_display = gr.Textbox(
1012
- value="Emotional awareness",
1013
- interactive=False,
1014
- elem_classes=["focus-display"],
1015
- show_label=False
1016
- )
1017
-
1018
- stage_display = gr.Textbox(
1019
- label="Stage",
1020
- value="1/13: Raw Input",
1021
- interactive=False
1022
- )
1023
-
1024
- gr.HTML("""
1025
- <div class="sidebar-section card-shadow" style="margin-top: 1.5rem;">
1026
- <h3>About This Practice</h3>
1027
- <p style="color: #3D5932; font-size: 0.9rem; line-height: 1.6;">
1028
- This practice uses AI to guide you through the NVC process with warmth
1029
- and attunement. Each stage builds on the last, helping you discover
1030
- what's truly alive in you and how to express it with clarity.
1031
- </p>
1032
- </div>
1033
- """)
1034
-
1035
- # Event handlers
1036
- def handle_submit(message, history, stage, session, somatic, sel_needs):
1037
- """Handle message submission"""
1038
- if not message.strip():
1039
- message = "continue"
1040
-
1041
- if stage == 5 and not sel_needs:
1042
- bot_response = "Please take a moment to select your needs from the list above, then click 'Confirm My Needs' to continue."
1043
- history = history + [
1044
- {"role": "user", "content": message},
1045
- {"role": "assistant", "content": bot_response}
1046
- ]
1047
- return (
1048
- history, "", stage, session, sel_needs,
1049
- f"{stage}/13: {STAGE_NAMES[stage]}",
1050
- STAGE_FOCUS[stage],
1051
- gr.update(visible=True)
1052
- )
1053
-
1054
- if stage == 5 and sel_needs:
1055
- session["needs"] = sel_needs
1056
- message = f"I've identified these needs: {', '.join(sel_needs)}"
1057
-
1058
- response, next_stage, updated_session, error = process_stage(
1059
- message, stage, session, somatic
1060
  )
1061
 
1062
- if error:
1063
- history = history + [
1064
- {"role": "user", "content": message},
1065
- {"role": "assistant", "content": error}
1066
- ]
1067
- return (
1068
- history, "", stage, session, sel_needs,
1069
- f"{stage}/13: {STAGE_NAMES[stage]}",
1070
- STAGE_FOCUS[stage],
1071
- gr.update(visible=(stage == 5))
1072
- )
1073
-
1074
- if message != "continue":
1075
- history = history + [
1076
- {"role": "user", "content": message},
1077
- {"role": "assistant", "content": response}
1078
- ]
1079
- else:
1080
- history = history + [{"role": "assistant", "content": response}]
1081
-
1082
- show_needs = (next_stage == 5)
1083
-
1084
- if stage == 13:
1085
- next_stage = 1
1086
- updated_session = {}
1087
- sel_needs = []
1088
-
1089
- return (
1090
- history,
1091
- "",
1092
- next_stage,
1093
- updated_session,
1094
- sel_needs,
1095
- f"{next_stage}/13: {STAGE_NAMES[next_stage]}",
1096
- STAGE_FOCUS[next_stage],
1097
- gr.update(visible=show_needs)
1098
- )
1099
 
1100
- def handle_reset():
1101
- """Reset the practice session"""
1102
- return (
1103
- [],
1104
- "",
1105
- 1,
1106
- {},
1107
- [],
1108
- "1/13: Raw Input",
1109
- "Emotional awareness",
1110
- gr.update(visible=False),
1111
- [], [], [], []
1112
- )
1113
 
1114
- def update_selected_needs(emotional, relational, value, lifestyle):
1115
- """Update the selected needs display"""
1116
- all_selected = emotional + relational + value + lifestyle
1117
- return ", ".join(all_selected) if all_selected else "No needs selected yet"
1118
-
1119
- def confirm_needs(emotional, relational, value, lifestyle, history, stage, session):
1120
- """Confirm selected needs and continue"""
1121
- all_selected = emotional + relational + value + lifestyle
1122
-
1123
- if not all_selected:
1124
- return (
1125
- all_selected,
1126
- history + [{"role": "assistant", "content": "Please select at least one need before continuing."}],
1127
- stage,
1128
- session
1129
- )
1130
-
1131
- session["needs"] = all_selected
1132
-
1133
- response, next_stage, updated_session, error = process_stage(
1134
- f"I've identified these needs: {', '.join(all_selected)}",
1135
- stage,
1136
- session,
1137
- True
1138
  )
1139
 
1140
- history = history + [
1141
- {"role": "user", "content": f"Selected needs: {', '.join(all_selected)}"},
1142
- {"role": "assistant", "content": response}
1143
- ]
1144
-
1145
- return all_selected, history, next_stage, updated_session
1146
-
1147
- # Wire up events
1148
- submit_btn.click(
1149
- fn=handle_submit,
1150
- inputs=[msg, chatbot, current_stage, session_data, somatic_check, selected_needs],
1151
- outputs=[
1152
- chatbot, msg, current_stage, session_data, selected_needs,
1153
- stage_display, focus_display, needs_accordion
1154
- ]
1155
- )
1156
 
1157
- msg.submit(
1158
- fn=handle_submit,
1159
- inputs=[msg, chatbot, current_stage, session_data, somatic_check, selected_needs],
1160
- outputs=[
1161
- chatbot, msg, current_stage, session_data, selected_needs,
1162
- stage_display, focus_display, needs_accordion
1163
- ]
1164
- )
1165
 
1166
- reset_btn.click(
1167
- fn=handle_reset,
1168
- outputs=[
1169
- chatbot, msg, current_stage, session_data, selected_needs,
1170
- stage_display, focus_display, needs_accordion,
1171
- emotional_needs, relational_needs, value_needs, lifestyle_needs
1172
- ]
1173
  )
1174
 
1175
- for checkbox in [emotional_needs, relational_needs, value_needs, lifestyle_needs]:
1176
- checkbox.change(
1177
- fn=update_selected_needs,
1178
- inputs=[emotional_needs, relational_needs, value_needs, lifestyle_needs],
1179
- outputs=[selected_display]
1180
- )
1181
-
1182
- confirm_needs_btn.click(
1183
- fn=confirm_needs,
1184
- inputs=[emotional_needs, relational_needs, value_needs, lifestyle_needs, chatbot, current_stage, session_data],
1185
- outputs=[selected_needs, chatbot, current_stage, session_data]
1186
- ).then(
1187
- fn=lambda s: (f"{s}/13: {STAGE_NAMES[s]}", STAGE_FOCUS[s], gr.update(visible=False)),
1188
- inputs=[current_stage],
1189
- outputs=[stage_display, focus_display, needs_accordion]
1190
- )
1191
 
1192
- return demo
 
 
 
 
1193
 
1194
- # ============ LAUNCH ============
 
 
 
 
 
 
1195
 
1196
  if __name__ == "__main__":
1197
- demo = create_app()
1198
  demo.launch()
 
1
  """
2
  NVC Deep Practice - A Multi-Stage Nonviolent Communication Learning Space
3
+ Simplified version without Chatbot component to avoid Gradio bugs
4
  """
5
 
6
  import gradio as gr
 
24
 
25
  NEEDS_LIST = {
26
  "Emotional Needs": [
27
+ "acceptance", "acknowledgement", "affection", "appreciation",
28
+ "autonomy", "belonging", "care", "respect", "security",
29
+ "to be heard", "to be seen", "to matter", "trust", "understanding"
 
 
 
 
 
30
  ],
31
  "Relational Needs": [
32
+ "closeness", "collaboration", "communication", "community",
33
+ "companionship", "connection", "empathy", "honesty",
34
+ "kindness", "love", "support"
 
 
 
35
  ],
36
  "Value Needs": [
37
+ "authenticity", "creativity", "growth", "healing", "hope",
38
+ "learning", "meaning", "purpose", "self-expression"
 
 
 
 
39
  ],
40
  "Lifestyle Needs": [
41
+ "balance", "ease", "fun", "health", "peace", "play",
42
+ "rest", "safety", "vitality"
 
 
 
 
43
  ]
44
  }
45
 
46
  FEELINGS_CONNECTED = [
47
+ "grateful", "peaceful", "joyful", "content", "hopeful",
48
+ "confident", "curious", "excited", "relieved", "touched"
 
 
 
 
 
 
 
 
 
49
  ]
50
 
51
  FEELINGS_WANTING = [
52
+ "frustrated", "anxious", "sad", "hurt", "disappointed",
53
+ "overwhelmed", "confused", "lonely", "scared", "angry"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
54
  ]
55
 
56
  # ============ SYSTEM PROMPTS ============
57
 
58
  BASE_TONE = """
59
+ Infused with the warmth of Sarah Peyton and Tara Brach - resonant empathy meeting radical acceptance.
 
 
60
 
61
  CRITICAL LANGUAGE RULES:
62
+ - NEVER use first person ("I", "I'm", "I sense", "I hear")
63
  - DO use second person warmly ("you", "your")
64
  - Use impersonal constructions: "What's here is...", "There's...", "It sounds like..."
 
65
  """
66
 
67
  STAGE_PROMPTS = {
68
  1: f"""
 
 
 
 
 
69
  {BASE_TONE}
70
 
 
 
 
 
71
  The raw message has been shared. Offer:
72
  1. Warm acknowledgment of what's present
73
  2. Reflection of the emotional energy sensed
74
+ 3. NO judgment - just tender presence
 
75
 
76
+ Keep it brief (2-3 sentences) but saturated with warmth.
 
 
 
 
77
  """,
78
 
79
  2: f"""
 
 
80
  {BASE_TONE}
81
 
82
+ Offer empathic reflection using NVC feelings vocabulary.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
83
 
84
+ FEELINGS when connected: {', '.join(FEELINGS_CONNECTED)}
85
+ FEELINGS when wanting: {', '.join(FEELINGS_WANTING)}
86
 
87
+ Offer 2-4 feeling guesses with phrases like "Are you feeling...?" "Could it be...?"
88
+ End with: "What resonates?"
89
  """,
90
 
91
  3: f"""
 
 
 
92
  {BASE_TONE}
93
 
94
+ Guide a brief somatic check-in:
95
+ - "Where might you be feeling this in your body?"
96
+ - "What's the quality of sensation there?"
 
 
 
 
 
 
 
97
 
98
+ Keep it brief and non-pressuring. End with permission to skip if the body isn't speaking clearly.
 
 
 
 
 
 
 
 
 
99
  """,
100
 
101
  4: f"""
 
 
 
102
  {BASE_TONE}
103
 
104
+ TEACH: Feelings are messengers - they communicate needs.
 
 
 
 
 
 
 
 
 
 
 
 
105
 
106
+ When feelings are pleasant, needs are being met.
107
+ When feelings are uncomfortable, there's a need wanting attention.
 
108
 
109
+ Marshall Rosenberg: "Feelings are the voice of our needs."
 
 
 
110
 
111
+ Keep it to 4-5 sentences. End with: "What needs might be calling for attention?"
 
 
 
 
 
 
 
 
 
 
 
 
112
  """,
113
 
114
  5: f"""
 
 
 
115
  {BASE_TONE}
116
 
117
+ Based on the feelings, offer gentle guesses about needs present.
 
 
 
 
 
 
 
 
 
 
118
 
119
+ Needs are universal - every human shares them. They're NOT strategies.
 
120
 
121
+ Offer 2-4 need guesses warmly. Invite them to explore: "What resonates?"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
122
  """,
123
 
124
  6: f"""
 
 
 
125
  {BASE_TONE}
126
 
127
+ The needs have been identified. Offer deep resonance:
128
  - Acknowledge the beauty and legitimacy of these needs
129
+ - "Of course there's longing for..."
130
  - Create warmth and self-compassion
 
 
 
 
 
 
 
 
 
131
 
132
+ Keep it to 2-3 sentences, saturated with acceptance.
 
133
  """,
134
 
135
  7: f"""
 
 
 
136
  {BASE_TONE}
137
 
138
+ Deliver a relevant micro-lesson based on context:
139
+ - ANGER: A messenger that a boundary was crossed
140
+ - I-LANGUAGE: "I feel..." instead of "You never..."
141
+ - VULNERABILITY: What's underneath the anger?
 
 
 
 
 
 
 
142
 
143
+ Keep it brief (3-4 sentences) with a Marshall Rosenberg quote if applicable.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
144
  """,
145
 
146
  8: f"""
 
 
147
  {BASE_TONE}
148
 
149
+ Teach about relational patterns:
150
+ - We're wired for connection
151
+ - Early patterns create default responses
152
+ - Neuroplasticity means we can create new patterns
153
+
154
+ Keep to 4-5 sentences. Normalize the difficulty, inspire hope.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
155
  """,
156
 
157
  9: f"""
 
 
158
  {BASE_TONE}
159
 
160
+ Help craft an NVC request:
 
 
 
 
 
 
161
 
162
  REQUEST CRITERIA:
163
  1. Specific (not vague)
164
  2. Doable (concrete action)
165
+ 3. Positive language (what you want, not don't want)
166
+ 4. Honors other's autonomy
 
 
 
 
 
 
 
167
 
168
+ Guide: "What specific, doable action would meet your need?"
 
 
 
169
  """,
170
 
171
  10: f"""
 
 
172
  {BASE_TONE}
173
 
174
+ Teach about autonomy and receiving "no":
 
 
175
 
176
+ True requests allow for "no." If you're not willing to hear "no," it's a demand.
 
 
177
 
178
+ "No" protects their needs - just as valid as yours.
 
179
 
180
+ Keep it compassionate but clear.
 
 
 
 
 
 
 
 
 
 
 
 
 
181
  """,
182
 
183
  11: f"""
 
 
184
  {BASE_TONE}
185
 
186
+ Invite rewriting the original message:
 
 
 
 
 
 
 
 
 
 
 
 
 
187
 
188
+ Structure if helpful:
189
+ - When [observation]
190
+ - I feel [feeling]
191
+ - Because I need [need]
192
+ - Would you be willing to [request]?
193
 
194
+ Remind: This is practice, not perfection.
 
195
  """,
196
 
197
  12: f"""
 
 
198
  {BASE_TONE}
199
 
200
+ Provide warm feedback on the revised message:
201
+ 1. Name what's working
202
+ 2. Offer one gentle growth edge
203
+ 3. Invite: "How does it feel to express it this way?"
 
 
 
 
 
 
 
 
204
 
205
  Keep feedback ratio 3:1 positive to constructive.
 
 
 
 
206
  """,
207
 
208
  13: f"""
 
 
209
  {BASE_TONE}
210
 
211
+ Close the practice with integration:
212
  1. Honor the work done
213
+ 2. Ask: "What's one thing you'll take with you?"
214
+ 3. Remind: Each practice strengthens new neural pathways
 
 
 
 
 
 
 
 
 
 
 
 
 
215
 
216
+ End warmly with invitation to practice again.
217
  """
218
  }
219
 
220
  STAGE_NAMES = {
221
+ 1: "Share Your Message",
222
  2: "Feeling Reflection",
223
+ 3: "Body Check-In",
224
+ 4: "Feelings as Messengers",
225
+ 5: "Identify Needs",
226
  6: "Needs Resonance",
227
+ 7: "Deeper Understanding",
228
+ 8: "Relational Patterns",
229
+ 9: "Make a Request",
230
+ 10: "Honoring Autonomy",
231
+ 11: "Rewrite Your Message",
232
+ 12: "Reflection",
233
+ 13: "Integration"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
234
  }
235
 
236
  # ============ CLAUDE INTEGRATION ============
237
 
238
  def get_client():
 
239
  api_key = (
240
  os.environ.get("ANTHROPIC_API_KEY") or
241
  os.environ.get("anthropic_key") or
 
246
  return None
247
  return anthropic.Anthropic(api_key=api_key)
248
 
249
+ def call_claude(system_prompt, user_message, context=""):
 
250
  client = get_client()
251
 
252
  if client is None:
253
+ return None
254
 
255
+ full_message = f"{context}\n\nUser: {user_message}" if context else user_message
 
 
 
256
 
257
  response = client.messages.create(
258
  model="claude-sonnet-4-20250514",
259
  max_tokens=1500,
260
  system=system_prompt,
261
+ messages=[{"role": "user", "content": full_message}]
262
  )
263
 
264
  return response.content[0].text
265
 
266
+ # ============ MAIN APP ============
267
 
268
+ def format_history(history):
269
+ """Format conversation history as markdown"""
270
+ if not history:
271
+ return "*Start by sharing a message you'd like to communicate more skillfully...*"
272
 
273
+ output = ""
274
+ for entry in history:
275
+ if entry.get("user"):
276
+ output += f"**You:** {entry['user']}\n\n"
277
+ if entry.get("assistant"):
278
+ output += f"**Guide:** {entry['assistant']}\n\n"
279
+ output += "---\n\n"
280
+ return output
281
 
282
+ def process_message(user_input, history, stage, session_data, somatic_enabled, selected_needs_str):
283
+ """Process user message and return updated state"""
 
 
 
284
 
285
+ if not user_input.strip():
286
+ user_input = "continue"
 
 
287
 
288
+ # Skip somatic if disabled
289
  if stage == 3 and not somatic_enabled:
290
+ stage = 4
291
+
292
+ # Build context
293
+ context_parts = []
294
+ if session_data.get("original_message"):
295
+ context_parts.append(f"Original message: \"{session_data['original_message']}\"")
296
+ if session_data.get("feelings"):
297
+ context_parts.append(f"Feelings: {session_data['feelings']}")
298
+ if session_data.get("needs"):
299
+ context_parts.append(f"Needs: {session_data['needs']}")
300
+ context = "\n".join(context_parts)
301
 
302
+ # Get response from Claude
303
  system_prompt = STAGE_PROMPTS.get(stage, STAGE_PROMPTS[1])
304
 
305
  try:
306
+ response = call_claude(system_prompt, user_input, context)
 
 
 
 
307
 
 
308
  if response is None:
309
+ error_msg = "**Setup Required:** Please add ANTHROPIC_API_KEY in Space Settings > Repository secrets, then restart."
310
+ history.append({"user": user_input, "assistant": error_msg})
311
+ return format_history(history), "", history, stage, session_data, f"Stage {stage}: {STAGE_NAMES[stage]}"
 
 
 
 
 
 
 
 
312
 
313
+ except Exception as e:
314
+ error_msg = f"**Error:** {str(e)}"
315
+ history.append({"user": user_input, "assistant": error_msg})
316
+ return format_history(history), "", history, stage, session_data, f"Stage {stage}: {STAGE_NAMES[stage]}"
317
 
318
+ # Update session data
319
+ if stage == 1:
320
+ session_data["original_message"] = user_input
321
+ elif stage == 2:
322
+ session_data["feelings"] = user_input
323
+ elif stage == 5 and selected_needs_str:
324
+ session_data["needs"] = selected_needs_str
325
 
326
+ # Add to history
327
+ if user_input != "continue":
328
+ history.append({"user": user_input, "assistant": response})
329
+ else:
330
+ history.append({"assistant": response})
331
 
332
+ # Advance stage
333
+ next_stage = stage + 1 if stage < 13 else 1
334
 
335
+ # Reset if completed
336
+ if stage == 13:
337
+ next_stage = 1
338
+ session_data = {}
339
+
340
+ return (
341
+ format_history(history),
342
+ "",
343
+ history,
344
+ next_stage,
345
+ session_data,
346
+ f"Stage {next_stage}/13: {STAGE_NAMES[next_stage]}"
347
+ )
348
 
349
+ def reset_session():
350
+ """Reset everything"""
351
+ return (
352
+ "*Start by sharing a message you'd like to communicate more skillfully...*",
353
+ "",
354
+ [],
355
+ 1,
356
+ {},
357
+ "Stage 1/13: Share Your Message",
358
+ [], [], [], []
359
+ )
360
 
361
+ def update_needs_display(e1, e2, e3, e4):
362
+ """Combine selected needs"""
363
+ all_needs = e1 + e2 + e3 + e4
364
+ return ", ".join(all_needs) if all_needs else ""
365
 
366
+ # ============ BUILD INTERFACE ============
 
367
 
368
+ with gr.Blocks(
369
+ title="NVC Deep Practice",
370
+ theme=gr.themes.Soft(primary_hue="green", secondary_hue="amber")
371
+ ) as demo:
372
 
373
+ # State
374
+ history_state = gr.State([])
375
+ stage_state = gr.State(1)
376
+ session_state = gr.State({})
377
 
378
+ # Header
379
+ gr.Markdown("""
380
+ # NVC Deep Practice
381
+ **Learn Nonviolent Communication through guided practice**
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
382
 
383
+ Share a message you want to communicate, and this guide will help you explore
384
+ your feelings, needs, and how to express yourself with clarity and compassion.
385
+ """)
 
 
 
 
 
 
 
386
 
387
+ if not API_KEY_CONFIGURED:
388
+ gr.Markdown("""
389
+ > **Setup Required:** Add `ANTHROPIC_API_KEY` in Space Settings > Repository secrets, then restart.
 
 
390
  """)
391
 
392
+ with gr.Row():
393
+ with gr.Column(scale=2):
394
+ # Conversation display
395
+ conversation = gr.Markdown(
396
+ value="*Start by sharing a message you'd like to communicate more skillfully...*",
397
+ label="Conversation"
398
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
399
 
400
+ # Needs selector (collapsible)
401
+ with gr.Accordion("Select Your Needs (for Stage 5)", open=False):
402
+ gr.Markdown("**Select needs that resonate:**")
403
+ with gr.Row():
404
+ needs1 = gr.CheckboxGroup(NEEDS_LIST["Emotional Needs"], label="Emotional")
405
+ needs2 = gr.CheckboxGroup(NEEDS_LIST["Relational Needs"], label="Relational")
406
  with gr.Row():
407
+ needs3 = gr.CheckboxGroup(NEEDS_LIST["Value Needs"], label="Values")
408
+ needs4 = gr.CheckboxGroup(NEEDS_LIST["Lifestyle Needs"], label="Lifestyle")
409
+ selected_needs = gr.Textbox(label="Selected Needs", interactive=False)
410
+
411
+ # Input
412
+ user_input = gr.Textbox(
413
+ label="Your Response",
414
+ placeholder="Type your message or click Continue...",
415
+ lines=3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
416
  )
417
 
418
+ with gr.Row():
419
+ submit_btn = gr.Button("Continue", variant="primary")
420
+ reset_btn = gr.Button("Start Over")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
421
 
422
+ with gr.Column(scale=1):
423
+ gr.Markdown("### Settings")
424
+ somatic_check = gr.Checkbox(label="Include body check-ins", value=True)
 
 
 
 
 
 
 
 
 
 
425
 
426
+ gr.Markdown("### Progress")
427
+ stage_display = gr.Textbox(
428
+ value="Stage 1/13: Share Your Message",
429
+ label="Current Stage",
430
+ interactive=False
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
431
  )
432
 
433
+ gr.Markdown("""
434
+ ### About NVC
435
+ Nonviolent Communication helps us:
436
+ - Express feelings honestly
437
+ - Identify universal needs
438
+ - Make clear requests
439
+ - Connect with compassion
 
 
 
 
 
 
 
 
 
440
 
441
+ [Learn more at CNVC.org](https://www.cnvc.org/)
442
+ """)
 
 
 
 
 
 
443
 
444
+ # Update needs display
445
+ for cb in [needs1, needs2, needs3, needs4]:
446
+ cb.change(
447
+ update_needs_display,
448
+ [needs1, needs2, needs3, needs4],
449
+ selected_needs
 
450
  )
451
 
452
+ # Submit handler
453
+ submit_btn.click(
454
+ process_message,
455
+ [user_input, history_state, stage_state, session_state, somatic_check, selected_needs],
456
+ [conversation, user_input, history_state, stage_state, session_state, stage_display]
457
+ )
 
 
 
 
 
 
 
 
 
 
458
 
459
+ user_input.submit(
460
+ process_message,
461
+ [user_input, history_state, stage_state, session_state, somatic_check, selected_needs],
462
+ [conversation, user_input, history_state, stage_state, session_state, stage_display]
463
+ )
464
 
465
+ # Reset handler
466
+ reset_btn.click(
467
+ reset_session,
468
+ [],
469
+ [conversation, user_input, history_state, stage_state, session_state, stage_display,
470
+ needs1, needs2, needs3, needs4]
471
+ )
472
 
473
  if __name__ == "__main__":
 
474
  demo.launch()