jostlebot Claude Opus 4.5 commited on
Commit
aec1996
·
1 Parent(s): 137436b

Fix context + add psychoeducation + hide text during selection

Browse files

- Guide understands message is FOR someone else, not to AI
- Text input hidden during feelings/needs (just select + continue)
- Micro-lessons: feelings as messengers, needs as universal, autonomy
- Request stage teaches about true requests vs demands
- Warm resonance at each stage

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

Files changed (1) hide show
  1. app.py +113 -73
app.py CHANGED
@@ -22,29 +22,57 @@ API_KEY_CONFIGURED = bool(get_client())
22
  # ============ DATA ============
23
 
24
  FEELINGS = {
25
- "Sad": ["sad", "hurt", "disappointed", "lonely"],
26
- "Anxious": ["anxious", "worried", "overwhelmed", "scared"],
27
- "Frustrated": ["frustrated", "angry", "annoyed", "irritated"],
28
- "Tired": ["exhausted", "confused", "numb", "depleted"]
29
  }
30
 
31
  NEEDS = {
32
- "Connection": ["understanding", "to be heard", "belonging", "closeness"],
33
- "Autonomy": ["autonomy", "choice", "freedom", "space"],
34
- "Security": ["safety", "stability", "trust", "support"],
35
- "Meaning": ["purpose", "respect", "growth", "authenticity"]
36
  }
37
 
38
- # ============ PROMPTS (ultra-concise) ============
39
 
40
- BASE = "Warm guide. 1-2 sentences only. Use 'you' not 'I'."
 
 
41
 
42
  PROMPTS = {
43
- 1: f"{BASE} Brief acknowledgment of what was shared.",
44
- 2: f"{BASE} Name 2 feeling guesses. Say: 'Select below or type your own.'",
45
- 3: f"{BASE} Connect feelings to 2 possible needs. Say: 'Choose below.'",
46
- 4: f"{BASE} Ask: 'What specific action would help?' Keep it short.",
47
- 5: f"{BASE} One sentence celebrating their work. Invite another round."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
48
  }
49
 
50
  # ============ FUNCTIONS ============
@@ -53,10 +81,10 @@ def call_claude(prompt, message, context=""):
53
  client = get_client()
54
  if not client:
55
  return None
56
- msg = f"{context}\n{message}" if context else message
57
  resp = client.messages.create(
58
  model="claude-sonnet-4-20250514",
59
- max_tokens=150,
60
  system=prompt,
61
  messages=[{"role": "user", "content": msg}]
62
  )
@@ -67,69 +95,83 @@ def build_statement(data):
67
  lines = []
68
 
69
  if data.get("original"):
70
- orig = data['original'][:60] + "..." if len(data.get('original','')) > 60 else data['original']
71
- lines.append(f"*\"{orig}\"*")
 
72
 
73
- lines.append("")
74
  lines.append("**I feel** " + (data.get("feelings") or "___"))
75
  lines.append("**because I need** " + (data.get("needs") or "___"))
76
- lines.append("**Would you** " + (data.get("request") or "___") + "?")
77
 
78
  return "\n\n".join(lines)
79
 
80
  def process(user_input, stage, data, feelings_str, needs_str):
81
  """Process input and advance"""
82
 
83
- if not user_input.strip() and stage > 1:
84
- user_input = "continue"
85
-
86
- # Context for Claude
87
- context = ""
88
- if data.get("original"):
89
- context = f"Original: {data['original']}"
90
  if data.get("feelings"):
91
- context += f"\nFeelings: {data['feelings']}"
92
  if data.get("needs"):
93
- context += f"\nNeeds: {data['needs']}"
94
-
95
- # Get response
96
- try:
97
- response = call_claude(PROMPTS.get(stage, PROMPTS[1]), user_input, context)
98
- if not response:
99
- return ("*Add ANTHROPIC_API_KEY in Settings*", "", stage, data,
100
- build_statement(data), gr.update(visible=False), gr.update(visible=False))
101
- except:
102
- return ("*Error - try again*", "", stage, data,
103
- build_statement(data), gr.update(visible=False), gr.update(visible=False))
104
 
105
- # Update data
106
  if stage == 1:
 
107
  data["original"] = user_input
108
  elif stage == 2:
109
- data["feelings"] = feelings_str or user_input
 
110
  elif stage == 3:
111
- data["needs"] = needs_str or user_input
 
112
  elif stage == 4:
 
113
  data["request"] = user_input
 
 
 
 
 
 
 
 
 
 
 
 
 
 
114
 
115
  # Next stage
116
  next_stage = stage + 1 if stage < 5 else 1
117
  if stage == 5:
118
  data = {}
119
 
 
 
 
 
 
 
120
  return (
121
  response,
122
  "",
123
  next_stage,
124
  data,
125
  build_statement(data),
126
- gr.update(visible=(next_stage == 2)),
127
- gr.update(visible=(next_stage == 3))
 
128
  )
129
 
130
  def reset():
131
- return ("*What message do you want to transform?*", "", 1, {},
132
- build_statement({}), gr.update(visible=False), gr.update(visible=False),
 
 
133
  [], [], [], [], [], [], [], [])
134
 
135
  def combine_feelings(a, b, c, d):
@@ -140,55 +182,53 @@ def combine_needs(a, b, c, d):
140
 
141
  # ============ UI ============
142
 
143
- css = """
144
- .container { max-width: 800px; margin: auto; }
145
- .statement-box { background: #f8f9fa; padding: 1rem; border-radius: 8px; }
146
- """
147
-
148
- with gr.Blocks(title="NVC Practice", theme=gr.themes.Soft(), css=css) as demo:
149
 
150
  stage_state = gr.State(1)
151
  data_state = gr.State({})
152
 
153
  gr.Markdown("## NVC Translation Practice")
 
154
 
155
  if not API_KEY_CONFIGURED:
156
  gr.Markdown("*Add ANTHROPIC_API_KEY in Settings > Secrets*")
157
 
158
  # Guide response
159
- guide = gr.Markdown("*What message do you want to transform?*")
160
 
161
- # Feelings (hidden until stage 2)
162
- with gr.Accordion("Select feelings", open=True, visible=False) as feelings_box:
 
163
  with gr.Row():
164
- f1 = gr.CheckboxGroup(FEELINGS["Sad"], label="Sad", scale=1)
165
- f2 = gr.CheckboxGroup(FEELINGS["Anxious"], label="Anxious", scale=1)
166
  with gr.Row():
167
- f3 = gr.CheckboxGroup(FEELINGS["Frustrated"], label="Frustrated", scale=1)
168
- f4 = gr.CheckboxGroup(FEELINGS["Tired"], label="Tired", scale=1)
169
  feelings_txt = gr.Textbox(label="Selected", interactive=False)
170
 
171
- # Needs (hidden until stage 3)
172
- with gr.Accordion("Select needs", open=True, visible=False) as needs_box:
 
173
  with gr.Row():
174
- n1 = gr.CheckboxGroup(NEEDS["Connection"], label="Connection", scale=1)
175
- n2 = gr.CheckboxGroup(NEEDS["Autonomy"], label="Autonomy", scale=1)
176
  with gr.Row():
177
- n3 = gr.CheckboxGroup(NEEDS["Security"], label="Security", scale=1)
178
- n4 = gr.CheckboxGroup(NEEDS["Meaning"], label="Meaning", scale=1)
179
  needs_txt = gr.Textbox(label="Selected", interactive=False)
180
 
181
- # Input
182
- user_input = gr.Textbox(placeholder="Type here...", lines=2, show_label=False)
183
 
184
  with gr.Row():
185
  submit = gr.Button("Continue", variant="primary", scale=2)
186
- reset_btn = gr.Button("Reset", scale=1)
187
 
188
  # Building statement
189
  gr.Markdown("---")
190
  gr.Markdown("**Your translation:**")
191
- statement = gr.Markdown(build_statement({}), elem_classes="statement-box")
192
 
193
  # Wiring
194
  for cb in [f1, f2, f3, f4]:
@@ -199,16 +239,16 @@ with gr.Blocks(title="NVC Practice", theme=gr.themes.Soft(), css=css) as demo:
199
  submit.click(
200
  process,
201
  [user_input, stage_state, data_state, feelings_txt, needs_txt],
202
- [guide, user_input, stage_state, data_state, statement, feelings_box, needs_box]
203
  )
204
  user_input.submit(
205
  process,
206
  [user_input, stage_state, data_state, feelings_txt, needs_txt],
207
- [guide, user_input, stage_state, data_state, statement, feelings_box, needs_box]
208
  )
209
  reset_btn.click(
210
  reset, [],
211
- [guide, user_input, stage_state, data_state, statement, feelings_box, needs_box,
212
  f1, f2, f3, f4, n1, n2, n3, n4]
213
  )
214
 
 
22
  # ============ DATA ============
23
 
24
  FEELINGS = {
25
+ "Sad": ["sad", "hurt", "disappointed", "lonely", "discouraged"],
26
+ "Anxious": ["anxious", "worried", "overwhelmed", "scared", "insecure"],
27
+ "Frustrated": ["frustrated", "angry", "annoyed", "irritated", "resentful"],
28
+ "Tired": ["exhausted", "confused", "numb", "depleted", "disconnected"]
29
  }
30
 
31
  NEEDS = {
32
+ "Connection": ["understanding", "to be heard", "to be seen", "belonging", "closeness"],
33
+ "Autonomy": ["autonomy", "choice", "freedom", "independence", "space"],
34
+ "Security": ["safety", "stability", "trust", "support", "reassurance"],
35
+ "Meaning": ["purpose", "respect", "growth", "authenticity", "contribution"]
36
  }
37
 
38
+ # ============ PROMPTS ============
39
 
40
+ BASE = """You are a warm NVC guide helping someone translate a difficult message they want to say TO ANOTHER PERSON.
41
+ You are NOT the recipient of their message - you're helping them transform it.
42
+ Keep responses to 2-3 sentences. Use "you" not "I". Be warm but concise."""
43
 
44
  PROMPTS = {
45
+ 1: f"""{BASE}
46
+ The user shared a raw message they want to communicate to someone else.
47
+ Acknowledge what's alive for them with warmth. Something like:
48
+ "There's some real [energy/feeling] here about [topic]. Let's find the feelings underneath."
49
+ Then say: "What feelings come up when you think about this?" """,
50
+
51
+ 2: f"""{BASE}
52
+ The user selected their feelings. Offer brief resonance and a micro-lesson:
53
+ "[Feeling] makes so much sense here. Feelings are messengers - they point to needs wanting attention."
54
+ Then: "What needs might be underneath these feelings? Select below." """,
55
+
56
+ 3: f"""{BASE}
57
+ The user identified their needs. Offer warm resonance and teach:
58
+ "Of course there's longing for [need] - this is such a universal human need.
59
+ When we name our needs, we create space for connection rather than blame."
60
+ Then: "Now let's craft a request. What specific action might help meet this need?" """,
61
+
62
+ 4: f"""{BASE}
63
+ The user made a request. Evaluate it kindly and teach about requests:
64
+ - Is it specific and doable?
65
+ - Is it positive (what you want, not what you don't)?
66
+ - Does it honor the other person's autonomy to say no?
67
+
68
+ Offer brief feedback. Remind them: "A true request allows for 'no' - otherwise it's a demand.
69
+ The other person's autonomy matters as much as yours."
70
+
71
+ Show their complete translation and celebrate their work. """,
72
+
73
+ 5: f"""{BASE}
74
+ Close with brief warmth. Remind them each practice builds new neural pathways.
75
+ Invite them to practice again with a new message. """
76
  }
77
 
78
  # ============ FUNCTIONS ============
 
81
  client = get_client()
82
  if not client:
83
  return None
84
+ msg = f"{context}\n\n{message}" if context else message
85
  resp = client.messages.create(
86
  model="claude-sonnet-4-20250514",
87
+ max_tokens=250,
88
  system=prompt,
89
  messages=[{"role": "user", "content": msg}]
90
  )
 
95
  lines = []
96
 
97
  if data.get("original"):
98
+ orig = data['original'][:80] + "..." if len(data.get('original','')) > 80 else data['original']
99
+ lines.append(f"**Original:** *\"{orig}\"*")
100
+ lines.append("")
101
 
 
102
  lines.append("**I feel** " + (data.get("feelings") or "___"))
103
  lines.append("**because I need** " + (data.get("needs") or "___"))
104
+ lines.append("**Would you be willing to** " + (data.get("request") or "___") + "?")
105
 
106
  return "\n\n".join(lines)
107
 
108
  def process(user_input, stage, data, feelings_str, needs_str):
109
  """Process input and advance"""
110
 
111
+ # Build context
112
+ context = f"Their original message to transform: \"{data.get('original', user_input)}\""
 
 
 
 
 
113
  if data.get("feelings"):
114
+ context += f"\nFeelings identified: {data['feelings']}"
115
  if data.get("needs"):
116
+ context += f"\nNeeds identified: {data['needs']}"
117
+ if data.get("request"):
118
+ context += f"\nRequest: {data['request']}"
 
 
 
 
 
 
 
 
119
 
120
+ # Determine what to send based on stage
121
  if stage == 1:
122
+ msg = f"Raw message to transform: {user_input}"
123
  data["original"] = user_input
124
  elif stage == 2:
125
+ msg = f"Selected feelings: {feelings_str}"
126
+ data["feelings"] = feelings_str
127
  elif stage == 3:
128
+ msg = f"Selected needs: {needs_str}"
129
+ data["needs"] = needs_str
130
  elif stage == 4:
131
+ msg = f"Their request attempt: {user_input}"
132
  data["request"] = user_input
133
+ else:
134
+ msg = "continue"
135
+
136
+ # Get response
137
+ try:
138
+ response = call_claude(PROMPTS.get(stage, PROMPTS[1]), msg, context)
139
+ if not response:
140
+ return ("*Add ANTHROPIC_API_KEY in Settings*", "", stage, data,
141
+ build_statement(data), gr.update(visible=False), gr.update(visible=False),
142
+ gr.update(visible=True))
143
+ except Exception as e:
144
+ return (f"*Error - try again*", "", stage, data,
145
+ build_statement(data), gr.update(visible=False), gr.update(visible=False),
146
+ gr.update(visible=True))
147
 
148
  # Next stage
149
  next_stage = stage + 1 if stage < 5 else 1
150
  if stage == 5:
151
  data = {}
152
 
153
+ # Show/hide elements based on next stage
154
+ # Text input only at stage 1 (raw message) and stage 4 (request)
155
+ show_text = (next_stage == 1 or next_stage == 4)
156
+ show_feelings = (next_stage == 2)
157
+ show_needs = (next_stage == 3)
158
+
159
  return (
160
  response,
161
  "",
162
  next_stage,
163
  data,
164
  build_statement(data),
165
+ gr.update(visible=show_feelings),
166
+ gr.update(visible=show_needs),
167
+ gr.update(visible=show_text)
168
  )
169
 
170
  def reset():
171
+ return ("*What message do you want to transform? (This is something you want to say to someone.)*",
172
+ "", 1, {},
173
+ build_statement({}),
174
+ gr.update(visible=False), gr.update(visible=False), gr.update(visible=True),
175
  [], [], [], [], [], [], [], [])
176
 
177
  def combine_feelings(a, b, c, d):
 
182
 
183
  # ============ UI ============
184
 
185
+ with gr.Blocks(title="NVC Practice", theme=gr.themes.Soft()) as demo:
 
 
 
 
 
186
 
187
  stage_state = gr.State(1)
188
  data_state = gr.State({})
189
 
190
  gr.Markdown("## NVC Translation Practice")
191
+ gr.Markdown("*Transform a difficult message into one that connects*")
192
 
193
  if not API_KEY_CONFIGURED:
194
  gr.Markdown("*Add ANTHROPIC_API_KEY in Settings > Secrets*")
195
 
196
  # Guide response
197
+ guide = gr.Markdown("*What message do you want to transform? (This is something you want to say to someone.)*")
198
 
199
+ # Feelings selector (hidden until stage 2)
200
+ with gr.Accordion("Select your feelings", open=True, visible=False) as feelings_box:
201
+ gr.Markdown("*Which feelings resonate?*")
202
  with gr.Row():
203
+ f1 = gr.CheckboxGroup(FEELINGS["Sad"], label="Sad/Hurt")
204
+ f2 = gr.CheckboxGroup(FEELINGS["Anxious"], label="Anxious/Scared")
205
  with gr.Row():
206
+ f3 = gr.CheckboxGroup(FEELINGS["Frustrated"], label="Frustrated/Angry")
207
+ f4 = gr.CheckboxGroup(FEELINGS["Tired"], label="Tired/Confused")
208
  feelings_txt = gr.Textbox(label="Selected", interactive=False)
209
 
210
+ # Needs selector (hidden until stage 3)
211
+ with gr.Accordion("Select your needs", open=True, visible=False) as needs_box:
212
+ gr.Markdown("*What needs are underneath these feelings?*")
213
  with gr.Row():
214
+ n1 = gr.CheckboxGroup(NEEDS["Connection"], label="Connection")
215
+ n2 = gr.CheckboxGroup(NEEDS["Autonomy"], label="Autonomy")
216
  with gr.Row():
217
+ n3 = gr.CheckboxGroup(NEEDS["Security"], label="Security")
218
+ n4 = gr.CheckboxGroup(NEEDS["Meaning"], label="Meaning")
219
  needs_txt = gr.Textbox(label="Selected", interactive=False)
220
 
221
+ # Text input (only visible at stages 1 and 4)
222
+ user_input = gr.Textbox(placeholder="Type here...", lines=2, show_label=False, visible=True)
223
 
224
  with gr.Row():
225
  submit = gr.Button("Continue", variant="primary", scale=2)
226
+ reset_btn = gr.Button("Start Over", scale=1)
227
 
228
  # Building statement
229
  gr.Markdown("---")
230
  gr.Markdown("**Your translation:**")
231
+ statement = gr.Markdown(build_statement({}))
232
 
233
  # Wiring
234
  for cb in [f1, f2, f3, f4]:
 
239
  submit.click(
240
  process,
241
  [user_input, stage_state, data_state, feelings_txt, needs_txt],
242
+ [guide, user_input, stage_state, data_state, statement, feelings_box, needs_box, user_input]
243
  )
244
  user_input.submit(
245
  process,
246
  [user_input, stage_state, data_state, feelings_txt, needs_txt],
247
+ [guide, user_input, stage_state, data_state, statement, feelings_box, needs_box, user_input]
248
  )
249
  reset_btn.click(
250
  reset, [],
251
+ [guide, user_input, stage_state, data_state, statement, feelings_box, needs_box, user_input,
252
  f1, f2, f3, f4, n1, n2, n3, n4]
253
  )
254