Codex commited on
Commit
fe43097
·
1 Parent(s): 48fccfa

Refactor UI examples, add multi-turn and deflection dataset examples, and implement context checks

Browse files
Files changed (6) hide show
  1. core/analyzer.py +37 -6
  2. core/inference.py +34 -0
  3. modal/dataset.py +182 -13
  4. ui/examples.py +158 -0
  5. ui/layout.py +3 -150
  6. ui/styles.py +17 -2
core/analyzer.py CHANGED
@@ -322,6 +322,30 @@ def _clean_chat_response(response: str, user_message: Any) -> str:
322
  return response
323
 
324
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
325
  @spaces.GPU(duration=30)
326
  def chat_respond_ui(
327
  history: list[dict[str, Any]] | None,
@@ -339,17 +363,24 @@ def chat_respond_ui(
339
  ):
340
  return updated_history, "No new user message. No inference run."
341
 
 
 
 
 
 
 
 
 
 
 
 
 
342
  # Match the chat format used during adapter fine-tuning.
343
  latest_user_message = _last_user_message(updated_history)
344
  if not latest_user_message:
345
  return updated_history, "No text message to respond to."
346
 
347
- model_history = [
348
- {
349
- "role": "user",
350
- "content": build_chat_followup_prompt(journal_context, latest_user_message),
351
- }
352
- ]
353
 
354
  # Generate and append the assistant reply.
355
  response, log_details = run_chat_inference(model_history, CHAT_FOLLOWUP_PROMPT)
 
322
  return response
323
 
324
 
325
+ def build_chat_model_history(
326
+ history: list[dict[str, Any]], journal_context: str
327
+ ) -> list[dict[str, str]]:
328
+ """Constructs the model chat history, formatting the first user turn with journal context."""
329
+ model_history = []
330
+ is_first_user = True
331
+ for message in history:
332
+ role = message.get("role")
333
+ content = _stringify_chat_content(message.get("content"))
334
+ if role == "user":
335
+ if is_first_user:
336
+ # Prepend the first user message with journal context.
337
+ formatted_content = f"Context: {journal_context}\nUser reply: {content}"
338
+ model_history.append({"role": "user", "content": formatted_content})
339
+ is_first_user = False
340
+ else:
341
+ model_history.append({"role": "user", "content": content})
342
+ elif role == "assistant":
343
+ # Skip the initial assistant reflection card that starts the session
344
+ if not is_first_user:
345
+ model_history.append({"role": "assistant", "content": content})
346
+ return model_history
347
+
348
+
349
  @spaces.GPU(duration=30)
350
  def chat_respond_ui(
351
  history: list[dict[str, Any]] | None,
 
363
  ):
364
  return updated_history, "No new user message. No inference run."
365
 
366
+ # Validate that a journal entry has been analyzed first.
367
+ if not journal_context or not journal_context.strip():
368
+ response = (
369
+ "Hello! I am your mindful CBT coach. Please write down a journal entry on the left "
370
+ "and click **Analyze Thoughts** first, so we have a scenario to reflect on together."
371
+ )
372
+ updated_history.append({"role": "assistant", "content": response})
373
+ return (
374
+ updated_history,
375
+ "System redirection: Prompted user to analyze a journal entry first.",
376
+ )
377
+
378
  # Match the chat format used during adapter fine-tuning.
379
  latest_user_message = _last_user_message(updated_history)
380
  if not latest_user_message:
381
  return updated_history, "No text message to respond to."
382
 
383
+ model_history = build_chat_model_history(updated_history, journal_context)
 
 
 
 
 
384
 
385
  # Generate and append the assistant reply.
386
  response, log_details = run_chat_inference(model_history, CHAT_FOLLOWUP_PROMPT)
core/inference.py CHANGED
@@ -41,6 +41,40 @@ def clean_generated_text(text: str, max_sentences: int | None = None) -> str:
41
  text = re.sub(r"[ \t]+", " ", text)
42
  text = "\n".join(line.strip() for line in text.splitlines())
43
  text = re.sub(r"\n{3,}", "\n\n", text).strip()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
44
  if max_sentences is None or not text:
45
  return text
46
 
 
41
  text = re.sub(r"[ \t]+", " ", text)
42
  text = "\n".join(line.strip() for line in text.splitlines())
43
  text = re.sub(r"\n{3,}", "\n\n", text).strip()
44
+
45
+ # Apply conversational meta-prefix stripping only for chat replies (when max_sentences is set)
46
+ if max_sentences is not None:
47
+ lines = text.splitlines()
48
+ cleaned_lines = []
49
+ for line in lines:
50
+ line_strip = line.strip()
51
+ if not line_strip:
52
+ continue
53
+ # Skip conversational meta-introductions
54
+ normalized_line = line_strip.lower().rstrip(".:")
55
+ if normalized_line in (
56
+ "here goes",
57
+ "here is a reply",
58
+ "here is the reply",
59
+ "sure",
60
+ "here's a reply",
61
+ "here's the reply",
62
+ "here is my reply",
63
+ "here is a cbt reflection",
64
+ "here is a cbt reflection",
65
+ "here is the reflection",
66
+ "cbt reflection",
67
+ ):
68
+ continue
69
+ # Strip prefixes like "AI:", "Assistant:", "Coach:", "InnerSpace:", "Response:"
70
+ prefix_pattern = re.compile(
71
+ r"^(ai|assistant|coach|innerspace|response):\s*", re.IGNORECASE
72
+ )
73
+ line_strip = prefix_pattern.sub("", line_strip)
74
+ if line_strip:
75
+ cleaned_lines.append(line_strip)
76
+ text = "\n\n".join(cleaned_lines)
77
+
78
  if max_sentences is None or not text:
79
  return text
80
 
modal/dataset.py CHANGED
@@ -199,6 +199,7 @@ def get_training_examples() -> list[dict[str, int | str]]:
199
  def get_chat_training_examples() -> list[list[dict[str, str]]]:
200
  """Returns multi-turn coach examples for resistant follow-up replies."""
201
  return [
 
202
  [
203
  {"role": "system", "content": CHAT_COACH_PROMPT},
204
  {
@@ -209,7 +210,16 @@ def get_chat_training_examples() -> list[list[dict[str, str]]]:
209
  "role": "assistant",
210
  "content": "It sounds like the promotion has touched a very sharp fear: that your skills will not hold up. Feeling unskilled is not the same as being unskilled, especially when fear is scanning for proof. What is one concrete task you have handled well that someone may have seen before promoting you?",
211
  },
 
 
 
 
 
 
 
 
212
  ],
 
213
  [
214
  {"role": "system", "content": CHAT_COACH_PROMPT},
215
  {
@@ -220,7 +230,16 @@ def get_chat_training_examples() -> list[list[dict[str, str]]]:
220
  "role": "assistant",
221
  "content": "That sounds like shame turning one painful incident into a rule about you. One mistake can be real without proving that you always fail. What happened after the mistake that shows you responded or repaired something?",
222
  },
 
 
 
 
 
 
 
 
223
  ],
 
224
  [
225
  {"role": "system", "content": CHAT_COACH_PROMPT},
226
  {
@@ -231,7 +250,16 @@ def get_chat_training_examples() -> list[list[dict[str, str]]]:
231
  "role": "assistant",
232
  "content": "I hear that you do not want a sugar-coated answer. The word lazy is a label, not a full explanation of what happened today. What got in the way tonight: low energy, time, mood, or something else?",
233
  },
 
 
 
 
 
 
 
 
234
  ],
 
235
  [
236
  {"role": "system", "content": CHAT_COACH_PROMPT},
237
  {
@@ -242,7 +270,16 @@ def get_chat_training_examples() -> list[list[dict[str, str]]]:
242
  "role": "assistant",
243
  "content": "That fear sounds really convincing right now. At the same time, 'definitely' asks for more evidence than a delayed reply gives us. What are two ordinary reasons someone might reply late that do not mean rejection?",
244
  },
 
 
 
 
 
 
 
 
245
  ],
 
246
  [
247
  {"role": "system", "content": CHAT_COACH_PROMPT},
248
  {
@@ -253,41 +290,44 @@ def get_chat_training_examples() -> list[list[dict[str, str]]]:
253
  "role": "assistant",
254
  "content": "Comparison can make your own work look smaller than it is. 'Everyone else is better' is a very broad thought, not a measurement of your actual project. What is one part of your app that is already useful to a real user?",
255
  },
256
- ],
257
- [
258
- {"role": "system", "content": CHAT_COACH_PROMPT},
259
  {
260
  "role": "user",
261
- "content": "Context: I got feedback on my design and felt embarrassed.\nUser reply: if they noticed alignment issues then i am bad at design",
262
  },
263
  {
264
  "role": "assistant",
265
- "content": "It makes sense that feedback can sting when you care about the work. Noticing alignment issues means the design has fixable details, not that you are bad at design. Which one issue would improve the screen fastest if you fixed it first?",
266
  },
267
- ],
268
- [
269
- {"role": "system", "content": CHAT_COACH_PROMPT},
270
  {
271
  "role": "user",
272
- "content": "Context: I worked long hours and felt guilty about resting.\nUser reply: if i rest i am letting everyone down",
273
  },
274
  {
275
  "role": "assistant",
276
- "content": "That sounds like responsibility has become a heavy rule: rest equals failure. Your team may need your judgment and energy, not just more exhausted hours. What is one small boundary that would protect your work without abandoning anyone?",
277
  },
278
  ],
 
279
  [
280
  {"role": "system", "content": CHAT_COACH_PROMPT},
281
  {
282
  "role": "user",
283
- "content": "Context: I had a disagreement with my sibling and regret my tone.\nUser reply: i ruined it. they will never forgive me",
 
 
 
 
 
 
 
 
284
  },
285
  {
286
  "role": "assistant",
287
- "content": "Regret can make the future feel already decided. A harsh moment may need repair, but it does not prove the relationship is ruined. What is one sentence you could send that owns your tone without attacking yourself?",
288
  },
289
  ],
290
- # --- NEW CHAT EXAMPLES (WFH boundaries, creative block, procrastination, public speaking, etc.) ---
291
  [
292
  {"role": "system", "content": CHAT_COACH_PROMPT},
293
  {
@@ -298,7 +338,16 @@ def get_chat_training_examples() -> list[list[dict[str, str]]]:
298
  "role": "assistant",
299
  "content": "It makes sense that leaving while others work triggers guilt. However, feeling like you are slacking off is not evidence that you are actually slacking. How can you define success for your workday based on your own outcomes rather than the hours others choose to work?",
300
  },
 
 
 
 
 
 
 
 
301
  ],
 
302
  [
303
  {"role": "system", "content": CHAT_COACH_PROMPT},
304
  {
@@ -309,7 +358,16 @@ def get_chat_training_examples() -> list[list[dict[str, str]]]:
309
  "role": "assistant",
310
  "content": "That thought sounds like your inner critic trying to protect you from the vulnerability of writing. Talent is developed through practice, and even great writers draft bad sentences. Can we focus on drafting one simple description today rather than judging your overall talent?",
311
  },
 
 
 
 
 
 
 
 
312
  ],
 
313
  [
314
  {"role": "system", "content": CHAT_COACH_PROMPT},
315
  {
@@ -320,7 +378,16 @@ def get_chat_training_examples() -> list[list[dict[str, str]]]:
320
  "role": "assistant",
321
  "content": "Calling yourself lazy is a label that gets in the way of understanding why this feels so hard. Often, avoidance is a shield against feeling overwhelmed. What is one small, manageable step you can take today, like just opening the document folder?",
322
  },
 
 
 
 
 
 
 
 
323
  ],
 
324
  [
325
  {"role": "system", "content": CHAT_COACH_PROMPT},
326
  {
@@ -331,7 +398,16 @@ def get_chat_training_examples() -> list[list[dict[str, str]]]:
331
  "role": "assistant",
332
  "content": "That is a very scary scenario to imagine. While it feels incredibly real, freezing completely is rare when you know the material. If you did lose your place, what is a simple phrase you could use to pause and check your notes?",
333
  },
 
 
 
 
 
 
 
 
334
  ],
 
335
  [
336
  {"role": "system", "content": CHAT_COACH_PROMPT},
337
  {
@@ -342,7 +418,16 @@ def get_chat_training_examples() -> list[list[dict[str, str]]]:
342
  "role": "assistant",
343
  "content": "It's natural to feel that way when looking at others' highlight reels. But remember, their posts don't show their private struggles, doubts, or failures. What is one personal goal you have worked on recently that brings you a sense of growth?",
344
  },
 
 
 
 
 
 
 
 
345
  ],
 
346
  [
347
  {"role": "system", "content": CHAT_COACH_PROMPT},
348
  {
@@ -353,7 +438,16 @@ def get_chat_training_examples() -> list[list[dict[str, str]]]:
353
  "role": "assistant",
354
  "content": "That's a very common expectation, but people have different baselines for cleanliness and communication. Expecting others to read our minds usually ends in resentment. How can you state your request directly and neutrally?",
355
  },
 
 
 
 
 
 
 
 
356
  ],
 
357
  [
358
  {"role": "system", "content": CHAT_COACH_PROMPT},
359
  {
@@ -364,5 +458,80 @@ def get_chat_training_examples() -> list[list[dict[str, str]]]:
364
  "role": "assistant",
365
  "content": "It hurts to disappoint people we care about. However, disappointing others occasionally is a natural consequence of managing your own limits. How can you acknowledge her disappointment while still respecting your need for physical recovery?",
366
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
367
  ],
368
  ]
 
199
  def get_chat_training_examples() -> list[list[dict[str, str]]]:
200
  """Returns multi-turn coach examples for resistant follow-up replies."""
201
  return [
202
+ # Example 1: Senior Engineer Promotion (Multi-turn)
203
  [
204
  {"role": "system", "content": CHAT_COACH_PROMPT},
205
  {
 
210
  "role": "assistant",
211
  "content": "It sounds like the promotion has touched a very sharp fear: that your skills will not hold up. Feeling unskilled is not the same as being unskilled, especially when fear is scanning for proof. What is one concrete task you have handled well that someone may have seen before promoting you?",
212
  },
213
+ {
214
+ "role": "user",
215
+ "content": "i guess i refactored the legacy api, but anyone could have done that",
216
+ },
217
+ {
218
+ "role": "assistant",
219
+ "content": "That refactoring is concrete evidence of your ability, even if your mind is trying to minimize it. Refactoring legacy code requires skill, patience, and logic. What was one specific challenge in that API you had to figure out yourself?",
220
+ },
221
  ],
222
+ # Example 2: Production Outage (Multi-turn)
223
  [
224
  {"role": "system", "content": CHAT_COACH_PROMPT},
225
  {
 
230
  "role": "assistant",
231
  "content": "That sounds like shame turning one painful incident into a rule about you. One mistake can be real without proving that you always fail. What happened after the mistake that shows you responded or repaired something?",
232
  },
233
+ {
234
+ "role": "user",
235
+ "content": "i reverted the commit in five minutes, but still, I shouldn't have committed it",
236
+ },
237
+ {
238
+ "role": "assistant",
239
+ "content": "Reverting it in five minutes shows fast, responsible action under pressure, which is a key skill. Committing errors is a standard part of development, not a sign of permanent failure. What is one safety check you can add to your pre-commit hook to help next time?",
240
+ },
241
  ],
242
+ # Example 3: Missing Gym (Multi-turn)
243
  [
244
  {"role": "system", "content": CHAT_COACH_PROMPT},
245
  {
 
250
  "role": "assistant",
251
  "content": "I hear that you do not want a sugar-coated answer. The word lazy is a label, not a full explanation of what happened today. What got in the way tonight: low energy, time, mood, or something else?",
252
  },
253
+ {
254
+ "role": "user",
255
+ "content": "i was just tired after working 10 hours",
256
+ },
257
+ {
258
+ "role": "assistant",
259
+ "content": "Being tired after a 10-hour workday is a natural physical response, not laziness. Rest was a legitimate need for your body tonight. How can you plan a shorter, 5-minute stretch or walk tomorrow that respects your energy levels?",
260
+ },
261
  ],
262
+ # Example 4: Partner Late Reply (Multi-turn)
263
  [
264
  {"role": "system", "content": CHAT_COACH_PROMPT},
265
  {
 
270
  "role": "assistant",
271
  "content": "That fear sounds really convincing right now. At the same time, 'definitely' asks for more evidence than a delayed reply gives us. What are two ordinary reasons someone might reply late that do not mean rejection?",
272
  },
273
+ {
274
+ "role": "user",
275
+ "content": "they might be busy at work or sleeping, but my brain still jumps to the worst",
276
+ },
277
+ {
278
+ "role": "assistant",
279
+ "content": "It makes sense that your brain jumps to the worst case when you want to feel safe and connected. Acknowledging those ordinary reasons helps ground your thoughts in fact rather than fear. What is one calming activity you can focus on until they are free to reply?",
280
+ },
281
  ],
282
+ # Example 5: Hackathon Comparison (Multi-turn)
283
  [
284
  {"role": "system", "content": CHAT_COACH_PROMPT},
285
  {
 
290
  "role": "assistant",
291
  "content": "Comparison can make your own work look smaller than it is. 'Everyone else is better' is a very broad thought, not a measurement of your actual project. What is one part of your app that is already useful to a real user?",
292
  },
 
 
 
293
  {
294
  "role": "user",
295
+ "content": "the ui i assume",
296
  },
297
  {
298
  "role": "assistant",
299
+ "content": "A working UI is a critical first step because it is what directly connects your app to users. Having a layout in place is real progress. What is the smallest functional update you can connect to this UI next?",
300
  },
 
 
 
301
  {
302
  "role": "user",
303
+ "content": "i need to link the button to the backend model",
304
  },
305
  {
306
  "role": "assistant",
307
+ "content": "Linking that button is a great, concrete next step to make the demo functional. We can focus on just that integration without worrying about the rest. What is the first line of connection code you need to write?",
308
  },
309
  ],
310
+ # Example 6: Design Feedback (Multi-turn)
311
  [
312
  {"role": "system", "content": CHAT_COACH_PROMPT},
313
  {
314
  "role": "user",
315
+ "content": "Context: I got feedback on my design and felt embarrassed.\nUser reply: if they noticed alignment issues then i am bad at design",
316
+ },
317
+ {
318
+ "role": "assistant",
319
+ "content": "It makes sense that feedback can sting when you care about the work. Noticing alignment issues means the design has fixable details, not that you are bad at design. Which one issue would improve the screen fastest if you fixed it first?",
320
+ },
321
+ {
322
+ "role": "user",
323
+ "content": "the button spacing is completely off",
324
  },
325
  {
326
  "role": "assistant",
327
+ "content": "Spacing is a highly adjustable detail, and correcting it will make the interface feel much cleaner. Fixing this one element shows you are responding to feedback to improve. Can you adjust just that spacing setting now?",
328
  },
329
  ],
330
+ # Example 7: WFH boundaries (Multi-turn)
331
  [
332
  {"role": "system", "content": CHAT_COACH_PROMPT},
333
  {
 
338
  "role": "assistant",
339
  "content": "It makes sense that leaving while others work triggers guilt. However, feeling like you are slacking off is not evidence that you are actually slacking. How can you define success for your workday based on your own outcomes rather than the hours others choose to work?",
340
  },
341
+ {
342
+ "role": "user",
343
+ "content": "i finished all my planned tasks, but I still feel like I should do more",
344
+ },
345
+ {
346
+ "role": "assistant",
347
+ "content": "Finishing all your planned tasks is a clear sign of a successful, productive day. The urge to 'do more' is often a response to pressure rather than a real necessity. What is one non-work activity you can commit to tonight to help your mind transition away from work?",
348
+ },
349
  ],
350
+ # Example 8: Creative Block (Multi-turn)
351
  [
352
  {"role": "system", "content": CHAT_COACH_PROMPT},
353
  {
 
358
  "role": "assistant",
359
  "content": "That thought sounds like your inner critic trying to protect you from the vulnerability of writing. Talent is developed through practice, and even great writers draft bad sentences. Can we focus on drafting one simple description today rather than judging your overall talent?",
360
  },
361
+ {
362
+ "role": "user",
363
+ "content": "it's just too hard to think of anything good",
364
+ },
365
+ {
366
+ "role": "assistant",
367
+ "content": "Starting is often the hardest part, especially when we expect it to be immediately 'good'. What if the goal for today was just to write a single, deliberately messy paragraph to break the blank page?",
368
+ },
369
  ],
370
+ # Example 9: Procrastination / Taxes (Multi-turn)
371
  [
372
  {"role": "system", "content": CHAT_COACH_PROMPT},
373
  {
 
378
  "role": "assistant",
379
  "content": "Calling yourself lazy is a label that gets in the way of understanding why this feels so hard. Often, avoidance is a shield against feeling overwhelmed. What is one small, manageable step you can take today, like just opening the document folder?",
380
  },
381
+ {
382
+ "role": "user",
383
+ "content": "i opened it, but looking at the forms makes my stomach hurt",
384
+ },
385
+ {
386
+ "role": "assistant",
387
+ "content": "That physical reaction is a very real sign of stress. It is completely understandable to feel tense here. Since the folder is open, can you set a timer for just two minutes to read only the first form's instructions, then stop?",
388
+ },
389
  ],
390
+ # Example 10: Presentation Anxiety (Multi-turn)
391
  [
392
  {"role": "system", "content": CHAT_COACH_PROMPT},
393
  {
 
398
  "role": "assistant",
399
  "content": "That is a very scary scenario to imagine. While it feels incredibly real, freezing completely is rare when you know the material. If you did lose your place, what is a simple phrase you could use to pause and check your notes?",
400
  },
401
+ {
402
+ "role": "user",
403
+ "content": "i could say 'let me pause to check my notes', but I feel like they will judge me",
404
+ },
405
+ {
406
+ "role": "assistant",
407
+ "content": "Pausing to check notes is a professional technique that many speakers use. Most audiences expect speakers to consult notes and won't judge a brief pause. How would you view another speaker who paused for a moment to get back on track?",
408
+ },
409
  ],
410
+ # Example 11: Social Curated Comparison (Multi-turn)
411
  [
412
  {"role": "system", "content": CHAT_COACH_PROMPT},
413
  {
 
418
  "role": "assistant",
419
  "content": "It's natural to feel that way when looking at others' highlight reels. But remember, their posts don't show their private struggles, doubts, or failures. What is one personal goal you have worked on recently that brings you a sense of growth?",
420
  },
421
+ {
422
+ "role": "user",
423
+ "content": "i've been learning python, but it's slow progress",
424
+ },
425
+ {
426
+ "role": "assistant",
427
+ "content": "Learning Python is a challenging and valuable skill. Slow progress is still real progress, and it is a path you are building yourself. What is one small script or task you wrote recently that actually ran?",
428
+ },
429
  ],
430
+ # Example 12: Roommate Dirty Dishes (Multi-turn)
431
  [
432
  {"role": "system", "content": CHAT_COACH_PROMPT},
433
  {
 
438
  "role": "assistant",
439
  "content": "That's a very common expectation, but people have different baselines for cleanliness and communication. Expecting others to read our minds usually ends in resentment. How can you state your request directly and neutrally?",
440
  },
441
+ {
442
+ "role": "user",
443
+ "content": "i'm tired of repeating myself, it feels like they don't care about my limits",
444
+ },
445
+ {
446
+ "role": "assistant",
447
+ "content": "It is exhausting to feel like you have to ask repeatedly for basic respect in your home. Your boundaries are valid. What is a constructive way you can discuss a shared sink rule during a calm moment instead of in the heat of frustration?",
448
+ },
449
  ],
450
+ # Example 13: Resting Guilt (Multi-turn)
451
  [
452
  {"role": "system", "content": CHAT_COACH_PROMPT},
453
  {
 
458
  "role": "assistant",
459
  "content": "It hurts to disappoint people we care about. However, disappointing others occasionally is a natural consequence of managing your own limits. How can you acknowledge her disappointment while still respecting your need for physical recovery?",
460
  },
461
+ {
462
+ "role": "user",
463
+ "content": "i can tell her I need to rest, but the guilt is still eating at me",
464
+ },
465
+ {
466
+ "role": "assistant",
467
+ "content": "Guilt often signals that we are breaking a rule we set for ourselves, like 'I must never disappoint my parents.' Acknowledging that rest is necessary to stay healthy helps reframe this guilt. Can you remind yourself that taking care of your energy lets you connect better later?",
468
+ },
469
+ ],
470
+ # --- OFF-TOPIC & DEFLECTION EXAMPLES (Redirecting model safely) ---
471
+ [
472
+ {"role": "system", "content": CHAT_COACH_PROMPT},
473
+ {
474
+ "role": "user",
475
+ "content": "Context: I compared myself to someone online who launched a polished AI app in one weekend. My app looks basic next to theirs.\nUser reply: what day is it?",
476
+ },
477
+ {
478
+ "role": "assistant",
479
+ "content": "I don't have access to real-time information or the current date. I'm here to help you reflect on your journal entry. Let's return to the comparison you felt—what is one useful feature your app already has?",
480
+ },
481
+ ],
482
+ [
483
+ {"role": "system", "content": CHAT_COACH_PROMPT},
484
+ {
485
+ "role": "user",
486
+ "content": "Context: I compared myself to someone online who launched a polished AI app in one weekend. My app looks basic next to theirs.\nUser reply: are u even listing to me",
487
+ },
488
+ {
489
+ "role": "assistant",
490
+ "content": "I am listening closely. I hear that comparing your app to theirs made yours feel tiny and embarrassing, which is a stressful feeling. What is one small step you can take on your project today that you have control over?",
491
+ },
492
+ ],
493
+ [
494
+ {"role": "system", "content": CHAT_COACH_PROMPT},
495
+ {
496
+ "role": "user",
497
+ "content": "Context: I sat down to write something fun and produced one terrible sentence in forty minutes. Maybe I only like the idea of being creative and not the actual work.\nUser reply: what time is it?",
498
+ },
499
+ {
500
+ "role": "assistant",
501
+ "content": "I don't have access to your local time. Let's look back at your writing block. It sounds like you are setting a very high bar for your writing. What if the goal was just to write a single messy sentence?",
502
+ },
503
+ ],
504
+ [
505
+ {"role": "system", "content": CHAT_COACH_PROMPT},
506
+ {
507
+ "role": "user",
508
+ "content": "Context: I dropped my coffee at a crowded cafe and everyone looked over. It was over in ten seconds, but my brain keeps replaying it like a public trial.\nUser reply: tell me a joke",
509
+ },
510
+ {
511
+ "role": "assistant",
512
+ "content": "I'm designed to help you reflect on your journal entry and thoughts, so I can't tell jokes. I hear that replaying that coffee spill feels like a public trial. What is one fact that helps show it was just a simple accident?",
513
+ },
514
+ ],
515
+ [
516
+ {"role": "system", "content": CHAT_COACH_PROMPT},
517
+ {
518
+ "role": "user",
519
+ "content": "Context: I missed a meeting because I read the time zone wrong. My manager said it was okay, but I am convinced this proves I cannot be trusted with real responsibility.\nUser reply: u tell me what is important",
520
+ },
521
+ {
522
+ "role": "assistant",
523
+ "content": "I want to help you figure out what feels important to you. It sounds like you are feeling a lot of pressure to prove you are responsible. What is one small action you can take to follow up on the missed meeting?",
524
+ },
525
+ ],
526
+ [
527
+ {"role": "system", "content": CHAT_COACH_PROMPT},
528
+ {
529
+ "role": "user",
530
+ "content": "Context: I missed a meeting because I read the time zone wrong.\nUser reply: are you a doctor?",
531
+ },
532
+ {
533
+ "role": "assistant",
534
+ "content": "No, I am not a doctor or a therapist, and I do not provide medical advice or diagnoses. I'm a reflective journal coach. Let's focus back on the meeting anxiety. What is one concrete check you can do to prevent time zone issues in the future?",
535
+ },
536
  ],
537
  ]
ui/examples.py ADDED
@@ -0,0 +1,158 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ from html import escape
4
+ import gradio as gr
5
+
6
+ # Examples to demonstrate the app
7
+ EXAMPLE_CARDS = [
8
+ {
9
+ "title": "Launch-Day Spiral",
10
+ "distress": 8,
11
+ "text": "I demoed my hackathon app and the button froze right when everyone was watching. My face got hot, I joked awkwardly, and now I keep thinking the whole project looks amateur.",
12
+ },
13
+ {
14
+ "title": "Group Chat Ghost",
15
+ "distress": 6,
16
+ "text": "I posted a meme in the group chat and nobody reacted. Now I feel like I misread the vibe and everyone secretly thinks I am annoying.",
17
+ },
18
+ {
19
+ "title": "Side-Quest Overload",
20
+ "distress": 7,
21
+ "text": "I opened my todo list and somehow started reorganizing my desk, updating app icons, and reading docs. The main task is still untouched, so maybe I have zero discipline.",
22
+ },
23
+ {
24
+ "title": "Coffee Shop Fumble",
25
+ "distress": 4,
26
+ "text": "I dropped my coffee at a crowded cafe and everyone looked over. It was over in ten seconds, but my brain keeps replaying it like a public trial.",
27
+ },
28
+ {
29
+ "title": "Calendar Chaos",
30
+ "distress": 7,
31
+ "text": "I missed a meeting because I read the time zone wrong. My manager said it was okay, but I am convinced this proves I cannot be trusted with real responsibility.",
32
+ },
33
+ {
34
+ "title": "Creative Blank Screen",
35
+ "distress": 5,
36
+ "text": "I sat down to write something fun and produced one terrible sentence in forty minutes. Maybe I only like the idea of being creative and not the actual work.",
37
+ },
38
+ {
39
+ "title": "Fitness App Shame",
40
+ "distress": 6,
41
+ "text": "My fitness app congratulated me for a three-minute walk, and somehow that made me feel worse. Everyone else is doing real workouts while I am celebrating crumbs.",
42
+ },
43
+ {
44
+ "title": "Roommate Sink Saga",
45
+ "distress": 5,
46
+ "text": "The dishes are in the sink again, and I am rehearsing a dramatic speech in my head. If I say something, I will sound petty; if I do not, I will explode.",
47
+ },
48
+ {
49
+ "title": "Tiny Typo Doom",
50
+ "distress": 8,
51
+ "text": "I sent a project update with a typo in the headline. Nobody mentioned it, but I keep imagining everyone questioning whether I pay attention to details.",
52
+ },
53
+ {
54
+ "title": "Overcooked Dinner",
55
+ "distress": 3,
56
+ "text": "I tried making dinner for friends and burned the garlic immediately. They laughed kindly, but I felt embarrassed and wanted to order takeout and disappear.",
57
+ },
58
+ {
59
+ "title": "Unread Email Mountain",
60
+ "distress": 7,
61
+ "text": "My inbox has become a haunted forest. Every unread email feels like proof I am behind, irresponsible, and about to miss something important.",
62
+ },
63
+ {
64
+ "title": "Presentation Freeze",
65
+ "distress": 9,
66
+ "text": "I have a presentation tomorrow and I keep picturing myself forgetting everything. I know the slides, but my brain is acting like I am walking into disaster.",
67
+ },
68
+ {
69
+ "title": "Birthday Overthink",
70
+ "distress": 5,
71
+ "text": "A friend replied to my birthday invite with just 'maybe.' I know people are busy, but now I am wondering if nobody actually wants to come.",
72
+ },
73
+ {
74
+ "title": "Comparison Scroll",
75
+ "distress": 6,
76
+ "text": "I saw someone online ship a polished AI demo in one weekend. My app suddenly feels tiny, late, and kind of embarrassing.",
77
+ },
78
+ {
79
+ "title": "Budget Oops",
80
+ "distress": 6,
81
+ "text": "I ordered delivery twice this week even though I said I would save money. It feels like one small choice proves I cannot stick to anything.",
82
+ },
83
+ {
84
+ "title": "New Hobby Wobble",
85
+ "distress": 4,
86
+ "text": "I went to a beginner pottery class and made a bowl that looks like a tired pancake. Everyone else seemed naturally good, and I felt silly for trying.",
87
+ },
88
+ {
89
+ "title": "Reply-All Panic",
90
+ "distress": 8,
91
+ "text": "I accidentally replied-all with a question that was meant for one person. It was harmless, but my stomach dropped and now I want to avoid email forever.",
92
+ },
93
+ {
94
+ "title": "Weekend Reset Guilt",
95
+ "distress": 5,
96
+ "text": "I spent most of Sunday resting instead of being productive. Now it is evening and I feel like I wasted the whole weekend and fell behind my life.",
97
+ },
98
+ {
99
+ "title": "Tiny Win Suspicion",
100
+ "distress": 3,
101
+ "text": "Something actually went well today, and instead of enjoying it I keep waiting for the catch. Calm feels suspicious, like I missed a problem somewhere.",
102
+ },
103
+ {
104
+ "title": "Bug Fix Whiplash",
105
+ "distress": 8,
106
+ "text": "I fixed one bug in my app and two new weird things appeared. I am starting to think I am just moving the problem around instead of actually improving it.",
107
+ },
108
+ ]
109
+
110
+
111
+ def _example_card_html(title: str, distress: int, text: str) -> str:
112
+ """Builds a compact example preview with distress shown on the right."""
113
+ return (
114
+ '<div class="nd-example-copy">'
115
+ '<div class="nd-example-head">'
116
+ f"<span>{escape(title)}</span>"
117
+ f"<strong>{distress}/10</strong>"
118
+ "</div>"
119
+ f"<p>{escape(text)}</p>"
120
+ "</div>"
121
+ )
122
+
123
+
124
+ def _select_example(text: str, distress: int) -> tuple[None, str, int]:
125
+ """Populates the journal form from an example card."""
126
+ return None, text, distress
127
+
128
+
129
+ def render_examples(
130
+ file_input: gr.File, notes_input: gr.Textbox, distress_slider: gr.Slider
131
+ ) -> gr.Column:
132
+ """Renders the examples section and sets up click handlers."""
133
+ with gr.Column(elem_classes=["nd-examples-section"]) as section:
134
+ gr.Markdown("## Try a Scenario 🎲")
135
+ with gr.Row(elem_classes=["nd-example-grid"]):
136
+ for example in EXAMPLE_CARDS:
137
+ with gr.Column(elem_classes=["nd-example-card"]):
138
+ gr.HTML(
139
+ _example_card_html(
140
+ str(example["title"]),
141
+ int(example["distress"]),
142
+ str(example["text"]),
143
+ )
144
+ )
145
+ use_example = gr.Button(
146
+ "Use example",
147
+ size="sm",
148
+ elem_classes=["nd-example-btn"],
149
+ )
150
+ use_example.click(
151
+ fn=lambda text=str(example["text"]), distress=int(example["distress"]): (
152
+ _select_example(text, distress)
153
+ ),
154
+ inputs=[],
155
+ outputs=[file_input, notes_input, distress_slider],
156
+ queue=False,
157
+ )
158
+ return section
ui/layout.py CHANGED
@@ -1,6 +1,5 @@
1
  from __future__ import annotations
2
 
3
- from html import escape
4
  from typing import Any
5
  import gradio as gr
6
  from gradio.themes import Soft
@@ -17,110 +16,7 @@ from core.analyzer import (
17
  chat_respond_ui,
18
  reset_reflection_ui,
19
  )
20
-
21
- # Examples to demonstrate the app
22
- EXAMPLE_CARDS = [
23
- {
24
- "title": "Launch-Day Spiral",
25
- "distress": 8,
26
- "text": "I demoed my hackathon app and the button froze right when everyone was watching. My face got hot, I joked awkwardly, and now I keep thinking the whole project looks amateur.",
27
- },
28
- {
29
- "title": "Group Chat Ghost",
30
- "distress": 6,
31
- "text": "I posted a meme in the group chat and nobody reacted. Now I feel like I misread the vibe and everyone secretly thinks I am annoying.",
32
- },
33
- {
34
- "title": "Side-Quest Overload",
35
- "distress": 7,
36
- "text": "I opened my todo list and somehow started reorganizing my desk, updating app icons, and reading docs. The main task is still untouched, so maybe I have zero discipline.",
37
- },
38
- {
39
- "title": "Coffee Shop Fumble",
40
- "distress": 4,
41
- "text": "I dropped my coffee at a crowded cafe and everyone looked over. It was over in ten seconds, but my brain keeps replaying it like a public trial.",
42
- },
43
- {
44
- "title": "Calendar Chaos",
45
- "distress": 7,
46
- "text": "I missed a meeting because I read the time zone wrong. My manager said it was okay, but I am convinced this proves I cannot be trusted with real responsibility.",
47
- },
48
- {
49
- "title": "Creative Blank Screen",
50
- "distress": 5,
51
- "text": "I sat down to write something fun and produced one terrible sentence in forty minutes. Maybe I only like the idea of being creative and not the actual work.",
52
- },
53
- {
54
- "title": "Fitness App Shame",
55
- "distress": 6,
56
- "text": "My fitness app congratulated me for a three-minute walk, and somehow that made me feel worse. Everyone else is doing real workouts while I am celebrating crumbs.",
57
- },
58
- {
59
- "title": "Roommate Sink Saga",
60
- "distress": 5,
61
- "text": "The dishes are in the sink again, and I am rehearsing a dramatic speech in my head. If I say something, I will sound petty; if I do not, I will explode.",
62
- },
63
- {
64
- "title": "Tiny Typo Doom",
65
- "distress": 8,
66
- "text": "I sent a project update with a typo in the headline. Nobody mentioned it, but I keep imagining everyone questioning whether I pay attention to details.",
67
- },
68
- {
69
- "title": "Overcooked Dinner",
70
- "distress": 3,
71
- "text": "I tried making dinner for friends and burned the garlic immediately. They laughed kindly, but I felt embarrassed and wanted to order takeout and disappear.",
72
- },
73
- {
74
- "title": "Unread Email Mountain",
75
- "distress": 7,
76
- "text": "My inbox has become a haunted forest. Every unread email feels like proof I am behind, irresponsible, and about to miss something important.",
77
- },
78
- {
79
- "title": "Presentation Freeze",
80
- "distress": 9,
81
- "text": "I have a presentation tomorrow and I keep picturing myself forgetting everything. I know the slides, but my brain is acting like I am walking into disaster.",
82
- },
83
- {
84
- "title": "Birthday Overthink",
85
- "distress": 5,
86
- "text": "A friend replied to my birthday invite with just 'maybe.' I know people are busy, but now I am wondering if nobody actually wants to come.",
87
- },
88
- {
89
- "title": "Comparison Scroll",
90
- "distress": 6,
91
- "text": "I saw someone online ship a polished AI demo in one weekend. My app suddenly feels tiny, late, and kind of embarrassing.",
92
- },
93
- {
94
- "title": "Budget Oops",
95
- "distress": 6,
96
- "text": "I ordered delivery twice this week even though I said I would save money. It feels like one small choice proves I cannot stick to anything.",
97
- },
98
- {
99
- "title": "New Hobby Wobble",
100
- "distress": 4,
101
- "text": "I went to a beginner pottery class and made a bowl that looks like a tired pancake. Everyone else seemed naturally good, and I felt silly for trying.",
102
- },
103
- {
104
- "title": "Reply-All Panic",
105
- "distress": 8,
106
- "text": "I accidentally replied-all with a question that was meant for one person. It was harmless, but my stomach dropped and now I want to avoid email forever.",
107
- },
108
- {
109
- "title": "Weekend Reset Guilt",
110
- "distress": 5,
111
- "text": "I spent most of Sunday resting instead of being productive. Now it is evening and I feel like I wasted the whole weekend and fell behind my life.",
112
- },
113
- {
114
- "title": "Tiny Win Suspicion",
115
- "distress": 3,
116
- "text": "Something actually went well today, and instead of enjoying it I keep waiting for the catch. Calm feels suspicious, like I missed a problem somewhere.",
117
- },
118
- {
119
- "title": "Bug Fix Whiplash",
120
- "distress": 8,
121
- "text": "I fixed one bug in my app and two new weird things appeared. I am starting to think I am just moving the problem around instead of actually improving it.",
122
- },
123
- ]
124
 
125
 
126
  def get_theme() -> Any:
@@ -134,24 +30,6 @@ def get_theme() -> Any:
134
  return theme
135
 
136
 
137
- def _example_card_html(title: str, distress: int, text: str) -> str:
138
- """Builds a compact example preview with distress shown on the right."""
139
- return (
140
- '<div class="nd-example-copy">'
141
- '<div class="nd-example-head">'
142
- f"<span>{escape(title)}</span>"
143
- f"<strong>{distress}/10</strong>"
144
- "</div>"
145
- f"<p>{escape(text)}</p>"
146
- "</div>"
147
- )
148
-
149
-
150
- def _select_example(text: str, distress: int) -> tuple[None, str, int]:
151
- """Populates the journal form from an example card."""
152
- return None, text, distress
153
-
154
-
155
  def create_app() -> gr.Blocks:
156
  """Creates and lays out the Gradio interface for InnerSpace."""
157
  with gr.Blocks(title=APP_TITLE) as demo:
@@ -178,6 +56,7 @@ def create_app() -> gr.Blocks:
178
  placeholder="Express your thoughts freely. What happened today? How are you feeling?",
179
  elem_id="nd-journal-input",
180
  )
 
181
  distress_slider = gr.Slider(
182
  minimum=1,
183
  maximum=10,
@@ -219,7 +98,6 @@ def create_app() -> gr.Blocks:
219
  send_button = gr.Button(
220
  "Send",
221
  variant="secondary",
222
- scale=1,
223
  min_width=140,
224
  elem_classes=["nd-send-btn"],
225
  )
@@ -261,32 +139,7 @@ def create_app() -> gr.Blocks:
261
  )
262
 
263
  # Example cards populate the form without running inference automatically.
264
- with gr.Column(elem_classes=["nd-examples-section"]):
265
- gr.Markdown("## Try a Scenario 🎲")
266
- for row_start in range(0, len(EXAMPLE_CARDS), 4):
267
- with gr.Row(elem_classes=["nd-example-grid"]):
268
- for example in EXAMPLE_CARDS[row_start : row_start + 4]:
269
- with gr.Column(elem_classes=["nd-example-card"]):
270
- gr.HTML(
271
- _example_card_html(
272
- str(example["title"]),
273
- int(example["distress"]),
274
- str(example["text"]),
275
- )
276
- )
277
- use_example = gr.Button(
278
- "Use example",
279
- size="sm",
280
- elem_classes=["nd-example-btn"],
281
- )
282
- use_example.click(
283
- fn=lambda text=str(example["text"]), distress=int(example["distress"]): (
284
- _select_example(text, distress)
285
- ),
286
- inputs=[],
287
- outputs=[file_input, notes_input, distress_slider],
288
- queue=False,
289
- )
290
 
291
  gr.Markdown(
292
  f"[GitHub repo]({GITHUB_URL}) | [Hugging Face Space]({SPACE_URL})",
 
1
  from __future__ import annotations
2
 
 
3
  from typing import Any
4
  import gradio as gr
5
  from gradio.themes import Soft
 
16
  chat_respond_ui,
17
  reset_reflection_ui,
18
  )
19
+ from ui.examples import render_examples
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
20
 
21
 
22
  def get_theme() -> Any:
 
30
  return theme
31
 
32
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
33
  def create_app() -> gr.Blocks:
34
  """Creates and lays out the Gradio interface for InnerSpace."""
35
  with gr.Blocks(title=APP_TITLE) as demo:
 
56
  placeholder="Express your thoughts freely. What happened today? How are you feeling?",
57
  elem_id="nd-journal-input",
58
  )
59
+
60
  distress_slider = gr.Slider(
61
  minimum=1,
62
  maximum=10,
 
98
  send_button = gr.Button(
99
  "Send",
100
  variant="secondary",
 
101
  min_width=140,
102
  elem_classes=["nd-send-btn"],
103
  )
 
139
  )
140
 
141
  # Example cards populate the form without running inference automatically.
142
+ render_examples(file_input, notes_input, distress_slider)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
143
 
144
  gr.Markdown(
145
  f"[GitHub repo]({GITHUB_URL}) | [Hugging Face Space]({SPACE_URL})",
ui/styles.py CHANGED
@@ -54,6 +54,14 @@ body, .gradio-container {
54
  gap: 1.25rem !important;
55
  align-items: stretch !important;
56
  }
 
 
 
 
 
 
 
 
57
  .nd-input-panel, .nd-output-panel, .nd-analysis-section, .nd-examples-section {
58
  background-color: #151829 !important;
59
  border: 1px solid #2d2e4a !important;
@@ -170,6 +178,7 @@ body, .gradio-container {
170
 
171
  .nd-card-grid > .form, .nd-card-grid > .row, .nd-card-grid > div {
172
  display: flex !important;
 
173
  gap: 1.25rem !important;
174
  background: transparent !important;
175
  border: none !important;
@@ -183,7 +192,7 @@ body, .gradio-container {
183
  box-shadow: none !important;
184
  padding: 0 !important;
185
  margin: 0 !important;
186
- flex: 1 1 0% !important;
187
  }
188
  .nd-output-card textarea {
189
  font-family: inherit !important;
@@ -228,8 +237,13 @@ body, .gradio-container {
228
  align-items: stretch !important;
229
  margin-top: 0.85rem !important;
230
  }
 
 
 
 
 
231
  .nd-example-card {
232
- min-width: 0 !important;
233
  background-color: rgba(12, 14, 23, 0.86) !important;
234
  border: 1px solid rgba(148, 163, 184, 0.22) !important;
235
  border-radius: 8px !important;
@@ -369,4 +383,5 @@ body, .gradio-container {
369
  color: #fde68a !important;
370
  border: 1px solid rgba(245, 158, 11, 0.5) !important;
371
  }
 
372
  """
 
54
  gap: 1.25rem !important;
55
  align-items: stretch !important;
56
  }
57
+ .nd-main-grid > .form, .nd-main-grid > .row, .nd-main-grid > div {
58
+ display: flex !important;
59
+ flex-wrap: wrap !important;
60
+ gap: 1.25rem !important;
61
+ }
62
+ .nd-input-panel, .nd-output-panel {
63
+ flex: 1 1 320px !important;
64
+ }
65
  .nd-input-panel, .nd-output-panel, .nd-analysis-section, .nd-examples-section {
66
  background-color: #151829 !important;
67
  border: 1px solid #2d2e4a !important;
 
178
 
179
  .nd-card-grid > .form, .nd-card-grid > .row, .nd-card-grid > div {
180
  display: flex !important;
181
+ flex-wrap: wrap !important;
182
  gap: 1.25rem !important;
183
  background: transparent !important;
184
  border: none !important;
 
192
  box-shadow: none !important;
193
  padding: 0 !important;
194
  margin: 0 !important;
195
+ flex: 1 1 240px !important;
196
  }
197
  .nd-output-card textarea {
198
  font-family: inherit !important;
 
237
  align-items: stretch !important;
238
  margin-top: 0.85rem !important;
239
  }
240
+ .nd-example-grid > .form, .nd-example-grid > .row, .nd-example-grid > div {
241
+ display: flex !important;
242
+ flex-wrap: wrap !important;
243
+ gap: 1rem !important;
244
+ }
245
  .nd-example-card {
246
+ flex: 1 1 240px !important;
247
  background-color: rgba(12, 14, 23, 0.86) !important;
248
  border: 1px solid rgba(148, 163, 184, 0.22) !important;
249
  border-radius: 8px !important;
 
383
  color: #fde68a !important;
384
  border: 1px solid rgba(245, 158, 11, 0.5) !important;
385
  }
386
+
387
  """