style: convert dream customs copy to english

#11
README.md CHANGED
@@ -20,7 +20,7 @@ tags:
20
  - dream-journal
21
  ---
22
 
23
- # Dream Customs / 梦境海关
24
 
25
  A Build Small Hackathon Gradio app that helps users form a playful alliance with last night's dream.
26
 
 
20
  - dream-journal
21
  ---
22
 
23
+ # Dream Customs
24
 
25
  A Build Small Hackathon Gradio app that helps users form a playful alliance with last night's dream.
26
 
dream_customs/models.py CHANGED
@@ -10,10 +10,6 @@ from dream_customs.prompts import visual_clue_prompt
10
  from dream_customs.schema import PactCard
11
 
12
 
13
- def _contains_cjk(text: str) -> bool:
14
- return bool(re.search(r"[\u4e00-\u9fff]", text))
15
-
16
-
17
  class FakeVisionClient:
18
  def extract_clues(self, image_path: Optional[str]) -> List[str]:
19
  if not image_path:
@@ -30,47 +26,26 @@ class FakeASRClient:
30
 
31
  class FakeTextClient:
32
  def generate_negotiation(self, prompt: str) -> Dict[str, Any]:
33
- if _contains_cjk(prompt):
34
- return {
35
- "visitor_name": "迟到的电梯",
36
- "questions": [
37
- "这个梦醒来后最明显的感觉是什么:紧张、疲惫、好奇,还是别的?",
38
- "今天有没有一件现实里的小事,你希望先轻一点开始?",
39
- "你想让我给你偏生活建议,还是偏 5 分钟怪趣任务?",
40
- ],
41
- "tone_note": "这个来访者也许不是敌人,而是在提醒你把开始和完成分开。",
42
- }
43
  return {
44
  "visitor_name": "Late Elevator",
45
  "questions": [
46
- "Was the elevator blocking you, or buying you a slower morning?",
47
- "If it lets you carry one unfinished thing into today, which one is smallest?",
48
- "Would you trade it a five-minute action for a release stamp?",
49
  ],
50
- "tone_note": "This visitor may be asking you to separate starting from finishing.",
51
  }
52
 
53
  def generate_pact(self, prompt: str) -> PactCard:
54
- if _contains_cjk(prompt):
55
- return PactCard(
56
- visitor_name="迟到的电梯",
57
- permit_id="DC-DEMO-014",
58
- contraband=["未申报的焦虑", "融化的按钮", "一小袋没来得及开始的事"],
59
- risk_level="橙色:需要被安置,但不需要被害怕",
60
- alliance_reading="这个梦也许在提醒你,今天先把启动一件事和完成一件事分开。",
61
- practical_suggestion="今天先把最重要的一件事写成 10 分钟能开始的版本,只要求打开它,不要求完成。",
62
- weird_task="在纸上画一个很小的电梯按钮,给今天的最小任务按一下,然后做 5 分钟。",
63
- bedtime_release="今日电梯已停靠,未完成事项明日再报关。",
64
- )
65
  return PactCard(
66
  visitor_name="Late Elevator",
67
  permit_id="DC-DEMO-014",
68
- contraband=["unfiled anxiety", "melted buttons", "one pouch of unstarted tasks"],
69
- risk_level="orange: needs placement, not fear",
70
- alliance_reading="This visitor asks you to separate starting from finishing.",
71
- practical_suggestion="Open one small task ten minutes early. You only need to start it.",
72
- weird_task="Write the elevator a one-sentence apology note.",
73
- bedtime_release="Today the elevator has docked; unfinished floors report tomorrow.",
74
  )
75
 
76
 
 
10
  from dream_customs.schema import PactCard
11
 
12
 
 
 
 
 
13
  class FakeVisionClient:
14
  def extract_clues(self, image_path: Optional[str]) -> List[str]:
15
  if not image_path:
 
26
 
27
  class FakeTextClient:
28
  def generate_negotiation(self, prompt: str) -> Dict[str, Any]:
 
 
 
 
 
 
 
 
 
 
29
  return {
30
  "visitor_name": "Late Elevator",
31
  "questions": [
32
+ "What feeling was strongest when you woke up: pressure, tiredness, curiosity, or something else?",
33
+ "Is there one real-life thing you would like to make easier today?",
34
+ "Would you rather receive a practical life tip, a tiny odd task, or both?",
35
  ],
36
+ "tone_note": "This visitor may be asking for a smaller start, not a perfect finish.",
37
  }
38
 
39
  def generate_pact(self, prompt: str) -> PactCard:
 
 
 
 
 
 
 
 
 
 
 
40
  return PactCard(
41
  visitor_name="Late Elevator",
42
  permit_id="DC-DEMO-014",
43
+ contraband=["unfiled pressure", "melted buttons", "a pocket of unstarted tasks"],
44
+ risk_level="orange: worth placing gently, not fearing",
45
+ alliance_reading="This dream may be pointing to the pressure of starting and finishing at the same time.",
46
+ practical_suggestion="Choose one task and define only its first 10 minutes. Open it, then pause for water.",
47
+ weird_task="Draw a tiny elevator button on paper, press it once, and work for five minutes.",
48
+ bedtime_release="The elevator has docked for tonight. Unfinished floors can report tomorrow.",
49
  )
50
 
51
 
dream_customs/pipeline.py CHANGED
@@ -63,32 +63,34 @@ def _looks_unclear_or_dream_literal(text: str) -> bool:
63
  "香薰皮",
64
  "果酱",
65
  "Dreamer",
 
 
 
 
 
66
  ]
67
  return any(term in clean for term in dream_literals)
68
 
69
 
70
  def _safe_practical_suggestion(intake: DreamIntake) -> str:
71
- if _contains_cjk(intake.merged_text()):
72
- mood = intake.mood.strip()
73
- if mood in {"焦虑", "迷雾", "累"}:
74
- return "今天先做一件能稳住身体的小事:喝水、吃点东西,把最重要的一件事写成 10 分钟能开始的版本。"
75
- return "今天给自己留一个低风险开头:先整理桌面或日程 5 分钟,再只开始一件最小的任务。"
 
76
  return "Do one low-risk stabilizing thing today: drink water, eat something, and write the most important task as a 10-minute first step."
77
 
78
 
79
  def _safe_weird_task(intake: DreamIntake) -> str:
80
- if _contains_cjk(intake.merged_text()):
81
- return "把今天最小的任务写在纸上,旁边画一个很小的通行章,然后只做 5 分钟。"
82
  return "Write your smallest task on paper, draw a tiny clearance stamp beside it, and work on it for just five minutes."
83
 
84
 
85
  def _polish_card_for_daily_use(card: PactCard, intake: DreamIntake, answers: str) -> PactCard:
86
  polished = card.model_copy(deep=True)
87
  merged = "\n".join([intake.merged_text(), answers or ""])
88
- chinese = _contains_cjk(merged)
89
-
90
- if chinese and (not polished.visitor_name.strip() or re.fullmatch(r"[A-Za-z\s_-]+", polished.visitor_name.strip())):
91
- polished.visitor_name = "昨夜来访者"
92
 
93
  if _looks_unclear_or_dream_literal(polished.practical_suggestion):
94
  polished.practical_suggestion = _safe_practical_suggestion(intake)
@@ -98,11 +100,13 @@ def _polish_card_for_daily_use(card: PactCard, intake: DreamIntake, answers: str
98
  elif len((polished.weird_task or "").strip()) < 8:
99
  polished.weird_task = _safe_weird_task(intake)
100
 
101
- if chinese:
102
- if len((polished.alliance_reading or "").strip()) < 12 or "联盟成员" in polished.alliance_reading:
103
- polished.alliance_reading = "这个梦可以先当作昨晚情绪留下的一点信号,不需要急着解释,今天先照顾好现实里的节奏。"
104
- if polished.risk_level.strip() in {"低", "中", "高"}:
105
- polished.risk_level = f"{polished.risk_level.strip()}:适合温和处理,不需要把它当成预兆。"
 
 
106
 
107
  if not needs_escalation(merged):
108
  polished.safety_note = ""
@@ -157,10 +161,6 @@ def create_session() -> CustomsSession:
157
  )
158
 
159
 
160
- def _contains_cjk(text: str) -> bool:
161
- return bool(re.search(r"[\u4e00-\u9fff]", text))
162
-
163
-
164
  def _append_text(existing: str, new_text: str) -> str:
165
  new_text = new_text.strip()
166
  if not new_text:
@@ -315,10 +315,7 @@ def ask_questions(session: CustomsSession, text_client, force_another: bool = Fa
315
  questions = [question for question in negotiation.get("questions", []) if question]
316
  fresh_questions = [question for question in questions if question not in next_session.question_history]
317
  if force_another and not fresh_questions:
318
- if _contains_cjk(next_session.intake.merged_text()):
319
- fresh_questions = ["如果海关只批准一个更小的动作,你希望今天先放行哪一件事?"]
320
- else:
321
- fresh_questions = ["If customs approves one smaller action today, which one should it release first?"]
322
  if not fresh_questions:
323
  fresh_questions = questions[:3]
324
 
@@ -353,10 +350,7 @@ def answer_question(session: CustomsSession, answer: str) -> CustomsSession:
353
 
354
  def skip_question(session: CustomsSession) -> CustomsSession:
355
  next_session = session.model_copy(deep=True)
356
- if _contains_cjk(next_session.intake.merged_text()):
357
- skip_text = "用户选择跳过这一轮问题。"
358
- else:
359
- skip_text = "The user chose to skip this question."
360
  next_session.answer_history.append(skip_text)
361
  next_session.phase = "negotiating"
362
  next_session.events.append(_event("user", "Question skipped", skip_text, status="skipped"))
@@ -392,29 +386,20 @@ def draft_pact(session: CustomsSession, text_client) -> CustomsSession:
392
  def _apply_revision_hint(card: PactCard, revision_request: str) -> PactCard:
393
  request = revision_request.lower()
394
  revised = card.model_copy(deep=True)
395
- cjk = _contains_cjk(" ".join([revision_request, card.visitor_name, card.alliance_reading]))
396
  if any(term in request for term in ["strange", "weird", "怪", "更奇怪", "更怪"]):
397
  revised.weird_task = (
398
- "把今天最小的任务写在纸上,给它盖一个看不见的放行章。"
399
- if cjk
400
- else "Write the smallest task on paper and stamp it with an invisible release mark."
401
  )
402
  elif any(term in request for term in ["gentle", "softer", "温和", "轻一点"]):
403
  revised.risk_level = (
404
- "浅橙色:先安置它,不急着解释它"
405
- if cjk
406
- else "soft orange: place it gently before interpreting it"
407
  )
408
  revised.practical_suggestion = (
409
- "今天先选一个不需要立刻完成的小开头,做 5 分钟就停。"
410
- if cjk
411
- else "Choose one start that does not need finishing today. Stop after five minutes."
412
  )
413
  elif revision_request.strip():
414
  revised.practical_suggestion = (
415
- f"{revised.practical_suggestion}(按你的修订请求:{revision_request.strip()}"
416
- if cjk
417
- else f"{revised.practical_suggestion} Revision note: {revision_request.strip()}"
418
  )
419
  return revised
420
 
 
63
  "香薰皮",
64
  "果酱",
65
  "Dreamer",
66
+ "dream content has no medical value",
67
+ "release stamp",
68
+ "customs stamp",
69
+ "clearance stamp",
70
+ "magical",
71
  ]
72
  return any(term in clean for term in dream_literals)
73
 
74
 
75
  def _safe_practical_suggestion(intake: DreamIntake) -> str:
76
+ mood = intake.mood.strip().lower()
77
+ if mood in {"uneasy", "foggy", "tired", "焦虑", "迷雾", "累"}:
78
+ return (
79
+ "Start with one body-level reset today: drink water, eat something simple, "
80
+ "then write the most important task as a 10-minute first step."
81
+ )
82
  return "Do one low-risk stabilizing thing today: drink water, eat something, and write the most important task as a 10-minute first step."
83
 
84
 
85
  def _safe_weird_task(intake: DreamIntake) -> str:
 
 
86
  return "Write your smallest task on paper, draw a tiny clearance stamp beside it, and work on it for just five minutes."
87
 
88
 
89
  def _polish_card_for_daily_use(card: PactCard, intake: DreamIntake, answers: str) -> PactCard:
90
  polished = card.model_copy(deep=True)
91
  merged = "\n".join([intake.merged_text(), answers or ""])
92
+ if not polished.visitor_name.strip() or polished.visitor_name.strip().lower() == "dreamer":
93
+ polished.visitor_name = "Night Visitor"
 
 
94
 
95
  if _looks_unclear_or_dream_literal(polished.practical_suggestion):
96
  polished.practical_suggestion = _safe_practical_suggestion(intake)
 
100
  elif len((polished.weird_task or "").strip()) < 8:
101
  polished.weird_task = _safe_weird_task(intake)
102
 
103
+ if len((polished.alliance_reading or "").strip()) < 12 or "联盟成员" in polished.alliance_reading:
104
+ polished.alliance_reading = (
105
+ "You can treat this as a small signal from last night's feelings, not a prophecy. "
106
+ "Today, protect a realistic pace."
107
+ )
108
+ if polished.risk_level.strip() in {"低", "中", "高", "low", "medium", "high"}:
109
+ polished.risk_level = "medium: handle gently, without treating it as a warning sign"
110
 
111
  if not needs_escalation(merged):
112
  polished.safety_note = ""
 
161
  )
162
 
163
 
 
 
 
 
164
  def _append_text(existing: str, new_text: str) -> str:
165
  new_text = new_text.strip()
166
  if not new_text:
 
315
  questions = [question for question in negotiation.get("questions", []) if question]
316
  fresh_questions = [question for question in questions if question not in next_session.question_history]
317
  if force_another and not fresh_questions:
318
+ fresh_questions = ["If today only needs one smaller first step, what should that step be?"]
 
 
 
319
  if not fresh_questions:
320
  fresh_questions = questions[:3]
321
 
 
350
 
351
  def skip_question(session: CustomsSession) -> CustomsSession:
352
  next_session = session.model_copy(deep=True)
353
+ skip_text = "The user chose to skip this question."
 
 
 
354
  next_session.answer_history.append(skip_text)
355
  next_session.phase = "negotiating"
356
  next_session.events.append(_event("user", "Question skipped", skip_text, status="skipped"))
 
386
  def _apply_revision_hint(card: PactCard, revision_request: str) -> PactCard:
387
  request = revision_request.lower()
388
  revised = card.model_copy(deep=True)
 
389
  if any(term in request for term in ["strange", "weird", "怪", "更奇怪", "更怪"]):
390
  revised.weird_task = (
391
+ "Write the smallest task on paper and stamp it with an invisible release mark."
 
 
392
  )
393
  elif any(term in request for term in ["gentle", "softer", "温和", "轻一点"]):
394
  revised.risk_level = (
395
+ "soft orange: place it gently before interpreting it"
 
 
396
  )
397
  revised.practical_suggestion = (
398
+ "Choose one start that does not need finishing today. Stop after five minutes."
 
 
399
  )
400
  elif revision_request.strip():
401
  revised.practical_suggestion = (
402
+ f"{revised.practical_suggestion} Revision note: {revision_request.strip()}"
 
 
403
  )
404
  return revised
405
 
dream_customs/prompts.py CHANGED
@@ -62,8 +62,9 @@ Give:
62
  - practical_suggestion: one safe, concrete life tip for today, such as hydration, eating, writing one task down, taking a short walk, reducing one task, checking the calendar, or asking for help. It must not be mystical or dream-literal.
63
  - weird_task: one harmless, playful, slightly odd thing doable in 5 minutes. It can be imaginative, but it must be understandable.
64
  - safety_note: empty string unless the user mentions self-harm, harming others, severe distress, severe insomnia, panic, or inability to function.
65
- Use warm, non-clinical language. If the user wrote in Chinese, answer in Chinese.
66
- Avoid generic English names like "Dreamer" when answering Chinese.
 
67
 
68
  Dream intake:
69
  {intake.merged_text()}
@@ -85,6 +86,7 @@ Do not diagnose. Do not make the dream sound certain or frightening.
85
  Keep practical_suggestion safe, concrete, and useful for daily life.
86
  Keep weird_task playful but understandable and separate from the practical suggestion.
87
  Use safety_note only for severe distress or safety risk; otherwise return an empty string.
 
88
 
89
  Dream intake:
90
  {intake.merged_text()}
 
62
  - practical_suggestion: one safe, concrete life tip for today, such as hydration, eating, writing one task down, taking a short walk, reducing one task, checking the calendar, or asking for help. It must not be mystical or dream-literal.
63
  - weird_task: one harmless, playful, slightly odd thing doable in 5 minutes. It can be imaginative, but it must be understandable.
64
  - safety_note: empty string unless the user mentions self-harm, harming others, severe distress, severe insomnia, panic, or inability to function.
65
+ Use warm, non-clinical language.
66
+ Avoid generic names like "Dreamer"; name the dream visitor with a short, vivid phrase.
67
+ Write the final card in polished English for an English-language demo, even if the dream fragment contains another language.
68
 
69
  Dream intake:
70
  {intake.merged_text()}
 
86
  Keep practical_suggestion safe, concrete, and useful for daily life.
87
  Keep weird_task playful but understandable and separate from the practical suggestion.
88
  Use safety_note only for severe distress or safety risk; otherwise return an empty string.
89
+ Write the revised card in polished English for an English-language demo, even if the dream fragment contains another language.
90
 
91
  Dream intake:
92
  {intake.merged_text()}
dream_customs/render.py CHANGED
@@ -33,7 +33,7 @@ def render_status_bar(session: CustomsSession, text_backend: str = "demo", visio
33
  <div class="dc-brand-lockup">
34
  <span class="dc-brand-mark">DC</span>
35
  <div>
36
- <h1>Dream Customs / 梦境海关</h1>
37
  <p>Turn last night's strange visitor into one gentle pact for the day: a safe life tip, a small weird task, and a bedtime release.</p>
38
  </div>
39
  </div>
 
33
  <div class="dc-brand-lockup">
34
  <span class="dc-brand-mark">DC</span>
35
  <div>
36
+ <h1>Dream Customs</h1>
37
  <p>Turn last night's strange visitor into one gentle pact for the day: a safe life tip, a small weird task, and a bedtime release.</p>
38
  </div>
39
  </div>
dream_customs/ui/actions.py CHANGED
@@ -42,20 +42,20 @@ def _trim_to_one_visible_question(session: CustomsSession, previous_count: int)
42
 
43
 
44
  def _card_plain_text(card: PactCard) -> str:
45
- contraband = "".join(card.contraband)
46
  parts = [
47
- "今日通行证",
48
- f"来访者:{card.visitor_name}",
49
- f"入境编号:{card.permit_id}",
50
- f"携带情绪违禁品:{contraband}",
51
- f"风险等级:{card.risk_level}",
52
- f"结盟解读:{card.alliance_reading}",
53
- f"今日生活建议:{card.practical_suggestion}",
54
- f"5 分钟怪趣任务:{card.weird_task}",
55
- f"睡前放行仪式:{card.bedtime_release}",
56
  ]
57
  if card.safety_note:
58
- parts.append(f"安全提示:{card.safety_note}")
59
  return "\n".join(parts)
60
 
61
 
@@ -63,7 +63,7 @@ def _render_today_pass(card: PactCard) -> str:
63
  contraband = "".join(f"<li>{escape(item)}</li>" for item in card.contraband)
64
  safety = (
65
  "<section class='dc-pass-safety'>"
66
- "<strong>必要时的安全提示</strong>"
67
  f"<p>{escape(card.safety_note)}</p>"
68
  "</section>"
69
  if card.safety_note
@@ -72,33 +72,33 @@ def _render_today_pass(card: PactCard) -> str:
72
  return f"""
73
  <article class="dc-pass-card">
74
  <div class="dc-pass-topline">
75
- <span>今日通行证</span>
76
  <span>{escape(card.permit_id)}</span>
77
  </div>
78
  <h2>{escape(card.visitor_name)}</h2>
79
  <p class="dc-pass-risk">{escape(card.risk_level)}</p>
80
  <section>
81
- <h3>可能代表的情绪</h3>
82
  <p>{escape(card.alliance_reading)}</p>
83
  </section>
84
  <section>
85
- <h3>今天的生活小 tip</h3>
86
  <p>{escape(card.practical_suggestion)}</p>
87
  </section>
88
  <section>
89
- <h3>5 分钟怪趣任务</h3>
90
  <p>{escape(card.weird_task)}</p>
91
  </section>
92
  <section>
93
- <h3>携带情绪违禁品</h3>
94
  <ul>{contraband}</ul>
95
  </section>
96
  <section>
97
- <h3>睡前放行词</h3>
98
  <p>{escape(card.bedtime_release)}</p>
99
  </section>
100
  {safety}
101
- <div class="dc-pass-seal">SEALED / 已放行</div>
102
  </article>
103
  """.strip()
104
 
@@ -116,7 +116,7 @@ def _view_payload(session: CustomsSession, text_backend: str, vision_backend: st
116
  "phase": session.phase,
117
  "question": _questions(session)[0] if _questions(session) else "",
118
  "questions": _questions(session),
119
- "card_title": "今日通行证" if card else "",
120
  "card_text": _card_plain_text(card) if card else "",
121
  "card_html": _render_today_pass(card) if card else "",
122
  "error": error,
@@ -135,12 +135,12 @@ def _view(session: CustomsSession, text_backend: str, vision_backend: str, **set
135
 
136
  def _notice_for_status(status: str, error: str = "") -> str:
137
  if status == "error":
138
- return error or "海关还没收到梦的碎片。"
139
  if status == "question":
140
- return "这一步是可选补充:说一点醒来后的真实感受,或者直接跳过。"
141
  if status == "card":
142
- return "今日通行证已盖章。它是一个温柔的行动提示,不是诊断。"
143
- return "写一句、几句,或贴一段梦;文字路径永远可用。"
144
 
145
 
146
  def initial_mobile_state(
 
42
 
43
 
44
  def _card_plain_text(card: PactCard) -> str:
45
+ contraband = ", ".join(card.contraband)
46
  parts = [
47
+ "Today's Clearance Pass",
48
+ f"Visitor: {card.visitor_name}",
49
+ f"Permit ID: {card.permit_id}",
50
+ f"Emotional contraband: {contraband}",
51
+ f"Risk level: {card.risk_level}",
52
+ f"Alliance reading: {card.alliance_reading}",
53
+ f"Life tip for today: {card.practical_suggestion}",
54
+ f"5-minute odd task: {card.weird_task}",
55
+ f"Bedtime release: {card.bedtime_release}",
56
  ]
57
  if card.safety_note:
58
+ parts.append(f"Support note: {card.safety_note}")
59
  return "\n".join(parts)
60
 
61
 
 
63
  contraband = "".join(f"<li>{escape(item)}</li>" for item in card.contraband)
64
  safety = (
65
  "<section class='dc-pass-safety'>"
66
+ "<strong>Support note, if needed</strong>"
67
  f"<p>{escape(card.safety_note)}</p>"
68
  "</section>"
69
  if card.safety_note
 
72
  return f"""
73
  <article class="dc-pass-card">
74
  <div class="dc-pass-topline">
75
+ <span>Today's Clearance Pass</span>
76
  <span>{escape(card.permit_id)}</span>
77
  </div>
78
  <h2>{escape(card.visitor_name)}</h2>
79
  <p class="dc-pass-risk">{escape(card.risk_level)}</p>
80
  <section>
81
+ <h3>What it may be carrying</h3>
82
  <p>{escape(card.alliance_reading)}</p>
83
  </section>
84
  <section>
85
+ <h3>Life tip for today</h3>
86
  <p>{escape(card.practical_suggestion)}</p>
87
  </section>
88
  <section>
89
+ <h3>5-minute odd task</h3>
90
  <p>{escape(card.weird_task)}</p>
91
  </section>
92
  <section>
93
+ <h3>Emotional contraband</h3>
94
  <ul>{contraband}</ul>
95
  </section>
96
  <section>
97
+ <h3>Bedtime release</h3>
98
  <p>{escape(card.bedtime_release)}</p>
99
  </section>
100
  {safety}
101
+ <div class="dc-pass-seal">SEALED / CLEARED</div>
102
  </article>
103
  """.strip()
104
 
 
116
  "phase": session.phase,
117
  "question": _questions(session)[0] if _questions(session) else "",
118
  "questions": _questions(session),
119
+ "card_title": "Today's Clearance Pass" if card else "",
120
  "card_text": _card_plain_text(card) if card else "",
121
  "card_html": _render_today_pass(card) if card else "",
122
  "error": error,
 
135
 
136
  def _notice_for_status(status: str, error: str = "") -> str:
137
  if status == "error":
138
+ return error or "Dream Customs has not received a fragment yet."
139
  if status == "question":
140
+ return "Optional check-in: add one grounded detail, or skip straight to the pass."
141
  if status == "card":
142
+ return "Today's pass is sealed. Treat it as a gentle action cue, not a diagnosis."
143
+ return "Write a sentence, a few lines, or paste a dream fragment. Text always works."
144
 
145
 
146
  def initial_mobile_state(
dream_customs/ui/app.py CHANGED
@@ -86,7 +86,7 @@ VOICE_JS = r"""
86
  button.addEventListener("click", async () => {
87
  const Recognition = window.SpeechRecognition || window.webkitSpeechRecognition;
88
  if (!Recognition) {
89
- setStatus("当前浏览器不支持直接语音输入,可先手动输入,ASR 通道待接入。", "error");
90
  textarea.focus();
91
  return;
92
  }
@@ -97,12 +97,12 @@ VOICE_JS = r"""
97
  stream.getTracks().forEach((track) => track.stop());
98
  }
99
  } catch (error) {
100
- setStatus("没有获得麦克风权限,请允许浏览器录音后再试。", "error");
101
  return;
102
  }
103
 
104
  const recognition = new Recognition();
105
- recognition.lang = "zh-CN";
106
  recognition.interimResults = true;
107
  recognition.continuous = false;
108
  recognition.maxAlternatives = 1;
@@ -110,7 +110,7 @@ VOICE_JS = r"""
110
  let latestTranscript = "";
111
 
112
  recognition.onstart = () => {
113
- setStatus("正在听,把梦说出来。", "listening");
114
  };
115
 
116
  recognition.onresult = (event) => {
@@ -119,23 +119,23 @@ VOICE_JS = r"""
119
  .join("")
120
  .trim();
121
  if (latestTranscript) {
122
- setStatus(`正在记录:${latestTranscript}`, "listening");
123
  }
124
  };
125
 
126
  recognition.onerror = (event) => {
127
  const message = event.error === "not-allowed"
128
- ? "麦克风权限被拒绝,请允许录音后再试。"
129
- : "这次没有听清,可以再点一次话筒。";
130
  setStatus(message, "error");
131
  };
132
 
133
  recognition.onend = () => {
134
  if (latestTranscript) {
135
  appendTranscript(latestTranscript);
136
- setStatus("已写入梦境碎片。", "done");
137
  } else if (button.dataset.mode === "listening") {
138
- setStatus("没有检测到语音,可以再点一次话筒。", "idle");
139
  }
140
  };
141
 
@@ -154,7 +154,7 @@ def _load_view(view_json: str) -> dict:
154
  try:
155
  return json.loads(view_json or "{}")
156
  except json.JSONDecodeError:
157
- return {"status": "error", "error": "界面状态解析失败,请重新申报。"}
158
 
159
 
160
  def _notice_html(view: dict) -> str:
@@ -166,17 +166,17 @@ def _notice_html(view: dict) -> str:
166
  def _question_markdown(view: dict) -> str:
167
  question = escape(view.get("question") or "")
168
  optional_question = (
169
- f"<p class='dc-question-original'><span>可选问题</span>{question}</p>"
170
  if question
171
  else ""
172
  )
173
  return f"""
174
  <div class="dc-question-card">
175
  <span class="dc-question-kicker">Optional check-in</span>
176
- <h2>还想补充一点吗?</h2>
177
- <p>如果你愿意,可以写下醒来后最想处理的一件现实小事,或者告诉我这个梦让你身体有什么感觉。</p>
178
  {optional_question}
179
- <p class="dc-question-note">不想补充也没关系,直接跳过就能生成今日通行证。</p>
180
  </div>
181
  """.strip()
182
 
@@ -299,7 +299,7 @@ def build_demo() -> gr.Blocks:
299
  initial_state, initial_view = initial_mobile_state()
300
  initial = _load_view(initial_view)
301
 
302
- with gr.Blocks(css=CSS, js=VOICE_JS, title="梦境海关") as demo:
303
  session_state = gr.State(initial_state)
304
  view_state = gr.State(initial_view)
305
 
@@ -346,7 +346,7 @@ def build_demo() -> gr.Blocks:
346
  """.strip()
347
  )
348
  dream_text = gr.Textbox(
349
- label="写下梦的碎片",
350
  placeholder=DREAM_PLACEHOLDER,
351
  lines=12,
352
  value="",
@@ -355,10 +355,10 @@ def build_demo() -> gr.Blocks:
355
  gr.HTML(
356
  """
357
  <div class="dc-mic-control">
358
- <button type="button" class="dc-mic-button" aria-label="点击话筒开始语音输入">
359
  <span class="dc-mic-glyph" aria-hidden="true"></span>
360
  </button>
361
- <div class="dc-mic-status" aria-live="polite">点击话筒开始语音输入</div>
362
  </div>
363
  """.strip()
364
  )
@@ -369,32 +369,32 @@ def build_demo() -> gr.Blocks:
369
  """.strip()
370
  )
371
  with gr.Row(elem_classes=["dc-submit-row"]):
372
- example_button = gr.Button("清空并试一个例子 →", variant="secondary")
373
- submit_button = gr.Button("盖章生成今日通行证 →", variant="primary")
374
- with gr.Accordion("附加材料", open=False, elem_classes=["dc-attachment-drawer"]):
375
- image_input = gr.Image(label="图片线索", type="filepath", height=160)
376
 
377
  with gr.Group(visible=False, elem_classes=["dc-stage", "dc-question"]) as question_group:
378
  question_markdown = gr.HTML(_question_markdown(initial))
379
  answer_text = gr.Textbox(
380
- label="你的补充",
381
  placeholder=ANSWER_PLACEHOLDER,
382
  lines=4,
383
  value="",
384
  )
385
  with gr.Row(elem_classes=["dc-question-actions"]):
386
- answer_button = gr.Button("用这条补充生成卡片", variant="primary")
387
- skip_button = gr.Button("跳过,直接生成", variant="secondary")
388
 
389
  with gr.Group(visible=False, elem_classes=["dc-stage", "dc-card"]) as card_group:
390
  card_html = gr.HTML("")
391
  with gr.Row(elem_classes=["dc-actions"]):
392
- gentle_button = gr.Button("再温柔一点", variant="secondary")
393
- weird_button = gr.Button("更怪一点", variant="secondary")
394
- copy_button = gr.Button("复制文本", variant="secondary")
395
- reset_button = gr.Button("重新申报", variant="secondary")
396
  card_text = gr.Textbox(
397
- label="可复制文本",
398
  value="",
399
  lines=8,
400
  show_copy_button=True,
@@ -410,7 +410,7 @@ def build_demo() -> gr.Blocks:
410
  </div>
411
  """.strip()
412
  )
413
- mood = gr.Dropdown(label="醒来后的感觉", choices=MOOD_OPTIONS, value=DEFAULT_MOOD)
414
  gr.HTML(
415
  """
416
  <div class="dc-side-stamp">
@@ -420,112 +420,112 @@ def build_demo() -> gr.Blocks:
420
  </div>
421
  """.strip()
422
  )
423
- with gr.Accordion("运行设置", open=True, elem_classes=["dc-dev"]):
424
  gr.HTML(
425
  """
426
  <div class="dc-dev-help">
427
- <strong>给调试用,普通体验不用改。</strong>
428
- <span>自动模式会优先使用 Space 已配置的后端;没有端点时会安全回落到演示数据。</span>
429
  </div>
430
  """.strip()
431
  )
432
  text_backend = gr.Dropdown(
433
- label="文字生成",
434
  choices=[
435
- ("自动:Space 已配置模型", "model"),
436
- ("演示:稳定假数据", "demo"),
437
- ("Modal/API:私有端点", "modal"),
438
- ("本地 Ollama", "ollama"),
439
  ],
440
  value=DEFAULT_TEXT_BACKEND,
441
  )
442
  vision_backend = gr.Dropdown(
443
- label="图片理解",
444
  choices=[
445
- ("自动:Space 已配置视觉模型", "model"),
446
- ("演示:不调用图片模型", "demo"),
447
- ("Modal/API:私有端点", "modal"),
448
- ("本地 Ollama", "ollama"),
449
  ],
450
  value=DEFAULT_VISION_BACKEND,
451
  )
452
  asr_backend = gr.Dropdown(
453
- label="语音输入",
454
  choices=[
455
- ("浏览器直接转写(当前)", "demo"),
456
- ("Modal ASR 端点(待接入)", "modal"),
457
- ("Hugging Face ASR 端点(待接入)", "huggingface"),
458
  ],
459
  value="demo",
460
  )
461
- with gr.Accordion("高级端点", open=False, elem_classes=["dc-dev-advanced"]):
462
- text_endpoint = gr.Textbox(label="文字 Endpoint", value="")
463
- vision_endpoint = gr.Textbox(label="图片 Endpoint", value="")
464
  asr_endpoint = gr.Textbox(label="ASR Endpoint", value="")
465
  hosted_token = gr.Textbox(label="Hosted Token", value="", type="password")
466
- text_model = gr.Textbox(label="文字模型", value=DEFAULT_TEXT_MODEL)
467
- vision_model = gr.Textbox(label="图片模型", value=DEFAULT_VISION_MODEL)
468
  ollama_url = gr.Textbox(label="Ollama URL", value="http://localhost:11434")
469
  text_timeout_seconds = gr.Number(
470
- label="文字超时秒",
471
  value=DEFAULT_HOSTED_TIMEOUT_SECONDS,
472
  precision=1,
473
  )
474
  vision_timeout_seconds = gr.Number(
475
- label="图片超时秒",
476
  value=DEFAULT_HOSTED_TIMEOUT_SECONDS,
477
  precision=1,
478
  )
479
  asr_timeout_seconds = gr.Number(
480
- label="ASR 超时秒",
481
  value=DEFAULT_ASR_TIMEOUT_SECONDS,
482
  precision=1,
483
  )
484
  text_latency_budget_ms = gr.Number(
485
- label="Modal 文字延迟预算 ms",
486
  value=DEFAULT_TEXT_LATENCY_BUDGET_MS,
487
  precision=0,
488
  )
489
  vision_latency_budget_ms = gr.Number(
490
- label="Modal 图片延迟预算 ms",
491
  value=DEFAULT_VISION_LATENCY_BUDGET_MS,
492
  precision=0,
493
  )
494
  asr_latency_budget_ms = gr.Number(
495
- label="ASR 延迟预算 ms",
496
  value=DEFAULT_ASR_LATENCY_BUDGET_MS,
497
  precision=0,
498
  )
499
  text_temperature = gr.Slider(
500
- label="文字温度",
501
  minimum=0,
502
  maximum=0.7,
503
  step=0.05,
504
  value=DEFAULT_TEXT_TEMPERATURE,
505
  )
506
  vision_temperature = gr.Slider(
507
- label="图片温度",
508
  minimum=0,
509
  maximum=0.7,
510
  step=0.05,
511
  value=DEFAULT_VISION_TEMPERATURE,
512
  )
513
  text_max_tokens = gr.Slider(
514
- label="文字 max tokens",
515
  minimum=64,
516
  maximum=1200,
517
  step=1,
518
  value=DEFAULT_TEXT_MAX_TOKENS,
519
  )
520
  vision_max_tokens = gr.Slider(
521
- label="图片 max tokens",
522
  minimum=64,
523
  maximum=800,
524
  step=1,
525
  value=DEFAULT_VISION_MAX_TOKENS,
526
  )
527
  debug_json = gr.Code(
528
- label="当前状态",
529
  value=json.dumps(initial.get("debug", {}), ensure_ascii=False, indent=2),
530
  language="json",
531
  visible=False,
@@ -581,12 +581,12 @@ def build_demo() -> gr.Blocks:
581
  )
582
  gentle_button.click(
583
  _revise,
584
- inputs=[session_state, gr.State("再温柔一点"), text_backend, vision_backend] + settings_inputs,
585
  outputs=outputs,
586
  )
587
  weird_button.click(
588
  _revise,
589
- inputs=[session_state, gr.State("更怪一点"), text_backend, vision_backend] + settings_inputs,
590
  outputs=outputs,
591
  )
592
  copy_button.click(lambda text: text, inputs=card_text, outputs=card_text)
 
86
  button.addEventListener("click", async () => {
87
  const Recognition = window.SpeechRecognition || window.webkitSpeechRecognition;
88
  if (!Recognition) {
89
+ setStatus("This browser cannot transcribe speech directly yet. You can still type the dream fragment.", "error");
90
  textarea.focus();
91
  return;
92
  }
 
97
  stream.getTracks().forEach((track) => track.stop());
98
  }
99
  } catch (error) {
100
+ setStatus("Microphone permission was not granted. Allow recording in the browser, then try again.", "error");
101
  return;
102
  }
103
 
104
  const recognition = new Recognition();
105
+ recognition.lang = "en-US";
106
  recognition.interimResults = true;
107
  recognition.continuous = false;
108
  recognition.maxAlternatives = 1;
 
110
  let latestTranscript = "";
111
 
112
  recognition.onstart = () => {
113
+ setStatus("Listening. Speak the dream fragment when you are ready.", "listening");
114
  };
115
 
116
  recognition.onresult = (event) => {
 
119
  .join("")
120
  .trim();
121
  if (latestTranscript) {
122
+ setStatus(`Recording: ${latestTranscript}`, "listening");
123
  }
124
  };
125
 
126
  recognition.onerror = (event) => {
127
  const message = event.error === "not-allowed"
128
+ ? "Microphone permission was denied. Allow recording, then try again."
129
+ : "I did not catch that clearly. Tap the mic once more if you want to retry.";
130
  setStatus(message, "error");
131
  };
132
 
133
  recognition.onend = () => {
134
  if (latestTranscript) {
135
  appendTranscript(latestTranscript);
136
+ setStatus("Added to the dream fragment.", "done");
137
  } else if (button.dataset.mode === "listening") {
138
+ setStatus("No speech detected. Tap the mic once more if you want to retry.", "idle");
139
  }
140
  };
141
 
 
154
  try:
155
  return json.loads(view_json or "{}")
156
  except json.JSONDecodeError:
157
+ return {"status": "error", "error": "The interface state could not be read. Please start a new declaration."}
158
 
159
 
160
  def _notice_html(view: dict) -> str:
 
166
  def _question_markdown(view: dict) -> str:
167
  question = escape(view.get("question") or "")
168
  optional_question = (
169
+ f"<p class='dc-question-original'><span>Optional question</span>{question}</p>"
170
  if question
171
  else ""
172
  )
173
  return f"""
174
  <div class="dc-question-card">
175
  <span class="dc-question-kicker">Optional check-in</span>
176
+ <h2>Anything worth adding?</h2>
177
+ <p>If it helps, name one real-world thing you want to make easier today, or describe how the dream left your body feeling.</p>
178
  {optional_question}
179
+ <p class="dc-question-note">This step is optional. You can skip it and create the pass now.</p>
180
  </div>
181
  """.strip()
182
 
 
299
  initial_state, initial_view = initial_mobile_state()
300
  initial = _load_view(initial_view)
301
 
302
+ with gr.Blocks(css=CSS, js=VOICE_JS, title="Dream Customs") as demo:
303
  session_state = gr.State(initial_state)
304
  view_state = gr.State(initial_view)
305
 
 
346
  """.strip()
347
  )
348
  dream_text = gr.Textbox(
349
+ label="Dream declaration",
350
  placeholder=DREAM_PLACEHOLDER,
351
  lines=12,
352
  value="",
 
355
  gr.HTML(
356
  """
357
  <div class="dc-mic-control">
358
+ <button type="button" class="dc-mic-button" aria-label="Tap the microphone to dictate">
359
  <span class="dc-mic-glyph" aria-hidden="true"></span>
360
  </button>
361
+ <div class="dc-mic-status" aria-live="polite">Tap the microphone to dictate</div>
362
  </div>
363
  """.strip()
364
  )
 
369
  """.strip()
370
  )
371
  with gr.Row(elem_classes=["dc-submit-row"]):
372
+ example_button = gr.Button("Try the sample →", variant="secondary")
373
+ submit_button = gr.Button("Issue today's pass →", variant="primary")
374
+ with gr.Accordion("Additional material", open=False, elem_classes=["dc-attachment-drawer"]):
375
+ image_input = gr.Image(label="Image clue", type="filepath", height=160)
376
 
377
  with gr.Group(visible=False, elem_classes=["dc-stage", "dc-question"]) as question_group:
378
  question_markdown = gr.HTML(_question_markdown(initial))
379
  answer_text = gr.Textbox(
380
+ label="Your optional note",
381
  placeholder=ANSWER_PLACEHOLDER,
382
  lines=4,
383
  value="",
384
  )
385
  with gr.Row(elem_classes=["dc-question-actions"]):
386
+ answer_button = gr.Button("Use this note", variant="primary")
387
+ skip_button = gr.Button("Skip and create pass", variant="secondary")
388
 
389
  with gr.Group(visible=False, elem_classes=["dc-stage", "dc-card"]) as card_group:
390
  card_html = gr.HTML("")
391
  with gr.Row(elem_classes=["dc-actions"]):
392
+ gentle_button = gr.Button("Softer", variant="secondary")
393
+ weird_button = gr.Button("Stranger", variant="secondary")
394
+ copy_button = gr.Button("Copy text", variant="secondary")
395
+ reset_button = gr.Button("New declaration", variant="secondary")
396
  card_text = gr.Textbox(
397
+ label="Copy-ready text",
398
  value="",
399
  lines=8,
400
  show_copy_button=True,
 
410
  </div>
411
  """.strip()
412
  )
413
+ mood = gr.Dropdown(label="After-waking feeling", choices=MOOD_OPTIONS, value=DEFAULT_MOOD)
414
  gr.HTML(
415
  """
416
  <div class="dc-side-stamp">
 
420
  </div>
421
  """.strip()
422
  )
423
+ with gr.Accordion("Runtime settings", open=True, elem_classes=["dc-dev"]):
424
  gr.HTML(
425
  """
426
  <div class="dc-dev-help">
427
+ <strong>For debugging only. Most people can leave this alone.</strong>
428
+ <span>Auto mode uses the backend configured for this Space, then safely falls back to demo data when no endpoint is available.</span>
429
  </div>
430
  """.strip()
431
  )
432
  text_backend = gr.Dropdown(
433
+ label="Text generation",
434
  choices=[
435
+ ("Auto: configured Space model", "model"),
436
+ ("Demo: stable sample data", "demo"),
437
+ ("Modal/API: private endpoint", "modal"),
438
+ ("Local Ollama", "ollama"),
439
  ],
440
  value=DEFAULT_TEXT_BACKEND,
441
  )
442
  vision_backend = gr.Dropdown(
443
+ label="Image understanding",
444
  choices=[
445
+ ("Auto: configured vision model", "model"),
446
+ ("Demo: skip image model", "demo"),
447
+ ("Modal/API: private endpoint", "modal"),
448
+ ("Local Ollama", "ollama"),
449
  ],
450
  value=DEFAULT_VISION_BACKEND,
451
  )
452
  asr_backend = gr.Dropdown(
453
+ label="Voice input",
454
  choices=[
455
+ ("Browser dictation now", "demo"),
456
+ ("Modal ASR endpoint, planned", "modal"),
457
+ ("Hugging Face ASR endpoint, planned", "huggingface"),
458
  ],
459
  value="demo",
460
  )
461
+ with gr.Accordion("Advanced endpoints", open=False, elem_classes=["dc-dev-advanced"]):
462
+ text_endpoint = gr.Textbox(label="Text endpoint", value="")
463
+ vision_endpoint = gr.Textbox(label="Image endpoint", value="")
464
  asr_endpoint = gr.Textbox(label="ASR Endpoint", value="")
465
  hosted_token = gr.Textbox(label="Hosted Token", value="", type="password")
466
+ text_model = gr.Textbox(label="Text model", value=DEFAULT_TEXT_MODEL)
467
+ vision_model = gr.Textbox(label="Image model", value=DEFAULT_VISION_MODEL)
468
  ollama_url = gr.Textbox(label="Ollama URL", value="http://localhost:11434")
469
  text_timeout_seconds = gr.Number(
470
+ label="Text timeout, seconds",
471
  value=DEFAULT_HOSTED_TIMEOUT_SECONDS,
472
  precision=1,
473
  )
474
  vision_timeout_seconds = gr.Number(
475
+ label="Image timeout, seconds",
476
  value=DEFAULT_HOSTED_TIMEOUT_SECONDS,
477
  precision=1,
478
  )
479
  asr_timeout_seconds = gr.Number(
480
+ label="ASR timeout, seconds",
481
  value=DEFAULT_ASR_TIMEOUT_SECONDS,
482
  precision=1,
483
  )
484
  text_latency_budget_ms = gr.Number(
485
+ label="Modal text latency budget, ms",
486
  value=DEFAULT_TEXT_LATENCY_BUDGET_MS,
487
  precision=0,
488
  )
489
  vision_latency_budget_ms = gr.Number(
490
+ label="Modal image latency budget, ms",
491
  value=DEFAULT_VISION_LATENCY_BUDGET_MS,
492
  precision=0,
493
  )
494
  asr_latency_budget_ms = gr.Number(
495
+ label="ASR latency budget, ms",
496
  value=DEFAULT_ASR_LATENCY_BUDGET_MS,
497
  precision=0,
498
  )
499
  text_temperature = gr.Slider(
500
+ label="Text temperature",
501
  minimum=0,
502
  maximum=0.7,
503
  step=0.05,
504
  value=DEFAULT_TEXT_TEMPERATURE,
505
  )
506
  vision_temperature = gr.Slider(
507
+ label="Image temperature",
508
  minimum=0,
509
  maximum=0.7,
510
  step=0.05,
511
  value=DEFAULT_VISION_TEMPERATURE,
512
  )
513
  text_max_tokens = gr.Slider(
514
+ label="Text max tokens",
515
  minimum=64,
516
  maximum=1200,
517
  step=1,
518
  value=DEFAULT_TEXT_MAX_TOKENS,
519
  )
520
  vision_max_tokens = gr.Slider(
521
+ label="Image max tokens",
522
  minimum=64,
523
  maximum=800,
524
  step=1,
525
  value=DEFAULT_VISION_MAX_TOKENS,
526
  )
527
  debug_json = gr.Code(
528
+ label="Current state",
529
  value=json.dumps(initial.get("debug", {}), ensure_ascii=False, indent=2),
530
  language="json",
531
  visible=False,
 
581
  )
582
  gentle_button.click(
583
  _revise,
584
+ inputs=[session_state, gr.State("softer"), text_backend, vision_backend] + settings_inputs,
585
  outputs=outputs,
586
  )
587
  weird_button.click(
588
  _revise,
589
+ inputs=[session_state, gr.State("stranger"), text_backend, vision_backend] + settings_inputs,
590
  outputs=outputs,
591
  )
592
  copy_button.click(lambda text: text, inputs=card_text, outputs=card_text)
dream_customs/ui/copy.py CHANGED
@@ -1,11 +1,11 @@
1
- APP_TITLE = "梦境海关"
2
- APP_SUBTITLE = "把昨晚的梦丢进来,我帮你换成今天的一张小通行证。"
3
 
4
- DREAM_PLACEHOLDER = "例如:我梦见电梯一直不到,按钮像蜡一样融化。"
5
- ANSWER_PLACEHOLDER = "可以只回一句,也可以留空后直接跳过。"
6
 
7
- EXAMPLE_DREAM = "我梦见自己一直赶不上电梯,按钮像蜡一样融化,楼层数字停在 14"
8
- EXAMPLE_MOOD = "焦虑"
9
 
10
- DEFAULT_MOOD = "平静"
11
- MOOD_OPTIONS = ["迷雾", "焦虑", "好奇", "", "平静"]
 
1
+ APP_TITLE = "Dream Customs"
2
+ APP_SUBTITLE = "Turn last night's strange fragment into one gentle clearance pass for today."
3
 
4
+ DREAM_PLACEHOLDER = "For example: I kept missing an elevator, and the buttons melted like wax."
5
+ ANSWER_PLACEHOLDER = "Add one sentence if it helps, or leave this blank and skip ahead."
6
 
7
+ EXAMPLE_DREAM = "I kept missing an elevator. The buttons melted like wax, and the floor number froze at 14."
8
+ EXAMPLE_MOOD = "Uneasy"
9
 
10
+ DEFAULT_MOOD = "Calm"
11
+ MOOD_OPTIONS = ["Foggy", "Uneasy", "Curious", "Tired", "Calm"]
dream_customs/ui/styles.py CHANGED
@@ -804,6 +804,11 @@ button.secondary {
804
  min-height: 130px !important;
805
  }
806
 
 
 
 
 
 
807
  .dc-dev {
808
  background: rgba(255, 249, 238, 0.7) !important;
809
  border: 1px solid rgba(189, 168, 143, 0.58) !important;
 
804
  min-height: 130px !important;
805
  }
806
 
807
+ footer.svelte-1rjryqp,
808
+ a.built-with[href*="gradio.app"] {
809
+ display: none !important;
810
+ }
811
+
812
  .dc-dev {
813
  background: rgba(255, 249, 238, 0.7) !important;
814
  border: 1px solid rgba(189, 168, 143, 0.58) !important;
tests/test_app_logic.py CHANGED
@@ -17,16 +17,16 @@ from dream_customs.models import HostedASRClient, HostedMiniCPMTextClient, Hoste
17
 
18
  def test_run_customs_once_generates_demo_outputs():
19
  negotiation, pact_text, html, debug_json = run_customs_once(
20
- dream_text="我梦见迟到的电梯。",
21
  mood="foggy",
22
- answers="我想先完成一件小事。",
23
  )
24
  debug = json.loads(debug_json)
25
  assert "Visitor:" in negotiation
26
  assert "Dream visitor:" in pact_text
27
  assert "Today's Pact" in html
28
  assert debug["status"] == "ok"
29
- assert debug["intake"]["dream_text"] == "我梦见迟到的电梯。"
30
 
31
 
32
  def test_run_customs_once_requires_one_modality():
 
17
 
18
  def test_run_customs_once_generates_demo_outputs():
19
  negotiation, pact_text, html, debug_json = run_customs_once(
20
+ dream_text="I dreamed of a late elevator.",
21
  mood="foggy",
22
+ answers="I want to start one small thing first.",
23
  )
24
  debug = json.loads(debug_json)
25
  assert "Visitor:" in negotiation
26
  assert "Dream visitor:" in pact_text
27
  assert "Today's Pact" in html
28
  assert debug["status"] == "ok"
29
+ assert debug["intake"]["dream_text"] == "I dreamed of a late elevator."
30
 
31
 
32
  def test_run_customs_once_requires_one_modality():
tests/test_pipeline.py CHANGED
@@ -88,8 +88,8 @@ def test_generate_pact_polishes_unclear_model_output_into_daily_tip():
88
  intake = build_intake(dream_text="我梦见电梯按钮融化,数字停在 14。", mood="焦虑")
89
  card, html = generate_pact(intake, "", UnclearTextClient())
90
 
91
- assert card.visitor_name == "昨夜来访者"
92
- assert "喝水" in card.practical_suggestion
93
  assert "电梯运行" not in card.practical_suggestion
94
  assert card.safety_note == ""
95
  assert "Life tip" in html
@@ -98,7 +98,7 @@ def test_generate_pact_polishes_unclear_model_output_into_daily_tip():
98
  def test_add_evidence_updates_session_with_text_image_audio_and_mood():
99
  session = add_evidence(
100
  create_session(),
101
- dream_text="我梦见电梯按钮融化。",
102
  image_path="demo.png",
103
  audio_path="demo.wav",
104
  mood="anxious",
@@ -106,7 +106,7 @@ def test_add_evidence_updates_session_with_text_image_audio_and_mood():
106
  asr_client=FakeASRClient(),
107
  )
108
  assert session.phase == "declaring"
109
- assert "电梯" in session.intake.dream_text
110
  assert "blue hallway" in session.intake.visual_clues
111
  assert "The buttons melted" in session.intake.voice_transcript
112
  assert session.intake.mood == "anxious"
 
88
  intake = build_intake(dream_text="我梦见电梯按钮融化,数字停在 14。", mood="焦虑")
89
  card, html = generate_pact(intake, "", UnclearTextClient())
90
 
91
+ assert card.visitor_name == "Night Visitor"
92
+ assert "drink water" in card.practical_suggestion
93
  assert "电梯运行" not in card.practical_suggestion
94
  assert card.safety_note == ""
95
  assert "Life tip" in html
 
98
  def test_add_evidence_updates_session_with_text_image_audio_and_mood():
99
  session = add_evidence(
100
  create_session(),
101
+ dream_text="I dreamed the elevator buttons melted.",
102
  image_path="demo.png",
103
  audio_path="demo.wav",
104
  mood="anxious",
 
106
  asr_client=FakeASRClient(),
107
  )
108
  assert session.phase == "declaring"
109
+ assert "elevator" in session.intake.dream_text
110
  assert "blue hallway" in session.intake.visual_clues
111
  assert "The buttons melted" in session.intake.voice_transcript
112
  assert session.intake.mood == "anxious"
tests/test_ui_actions.py CHANGED
@@ -48,8 +48,8 @@ def test_zerogpu_probe_is_importable_without_local_gpu():
48
 
49
  def test_mobile_mvp_submit_then_skip_auto_seals_pact():
50
  state, view_json = submit_dream_action(
51
- dream_text="我梦见迟到的电梯。",
52
- mood="焦虑",
53
  text_backend="demo",
54
  vision_backend="demo",
55
  )
@@ -65,23 +65,28 @@ def test_mobile_mvp_submit_then_skip_auto_seals_pact():
65
 
66
  assert view["status"] == "card"
67
  assert view["phase"] == "sealed"
68
- assert "今日通行证" in view["card_title"]
69
  assert f"DREAM{date.today():%Y%m%d}-014" in view["card_text"]
70
  assert "DC-DEMO-014" not in view["card_text"]
71
- assert "迟到的电梯" in view["card_html"]
72
 
73
 
74
  def test_mobile_mvp_answer_to_card_auto_seals_pact():
75
  state, _view_json = submit_dream_action(
76
- dream_text="我梦见按钮融化,电梯一直不到。",
77
- mood="迷雾",
78
  text_backend="demo",
79
  vision_backend="demo",
80
  )
81
 
82
- state, view_json = answer_to_card_action(state, "它可能是在让我慢一点。", text_backend="demo", vision_backend="demo")
 
 
 
 
 
83
  view = json.loads(view_json)
84
 
85
  assert view["status"] == "card"
86
  assert view["phase"] == "sealed"
87
- assert "它可能是在让我慢一点。" in view["debug"]["session"]["answer_history"]
 
48
 
49
  def test_mobile_mvp_submit_then_skip_auto_seals_pact():
50
  state, view_json = submit_dream_action(
51
+ dream_text="I dreamed of a late elevator.",
52
+ mood="Uneasy",
53
  text_backend="demo",
54
  vision_backend="demo",
55
  )
 
65
 
66
  assert view["status"] == "card"
67
  assert view["phase"] == "sealed"
68
+ assert "Today's Clearance Pass" in view["card_title"]
69
  assert f"DREAM{date.today():%Y%m%d}-014" in view["card_text"]
70
  assert "DC-DEMO-014" not in view["card_text"]
71
+ assert "Late Elevator" in view["card_html"]
72
 
73
 
74
  def test_mobile_mvp_answer_to_card_auto_seals_pact():
75
  state, _view_json = submit_dream_action(
76
+ dream_text="I dreamed the elevator buttons melted and the elevator never came.",
77
+ mood="Foggy",
78
  text_backend="demo",
79
  vision_backend="demo",
80
  )
81
 
82
+ state, view_json = answer_to_card_action(
83
+ state,
84
+ "It may be asking me to slow down.",
85
+ text_backend="demo",
86
+ vision_backend="demo",
87
+ )
88
  view = json.loads(view_json)
89
 
90
  assert view["status"] == "card"
91
  assert view["phase"] == "sealed"
92
+ assert "It may be asking me to slow down." in view["debug"]["session"]["answer_history"]