Update Morning Question Desk experience
#38
by ADJCJH - opened
- PRODUCT.md +2 -2
- README.md +10 -8
- docs/handoff.md +1 -1
- docs/prd.md +3 -3
- docs/reports/2026-06-12-dream-qa-fix-report.html +720 -0
- docs/smoke/2026-06-12-morning-question-desk-smoke.md +39 -0
- docs/smoke/2026-06-13-morning-question-desk-anchor-chips-smoke.md +46 -0
- docs/spec.md +4 -4
- dream_customs/app_logic.py +1 -1
- dream_customs/pipeline.py +9 -0
- dream_customs/render.py +31 -22
- dream_customs/ui/actions.py +6 -6
- dream_customs/ui/app.py +88 -9
- dream_customs/ui/copy.py +75 -34
- dream_customs/ui/styles.py +273 -0
- tests/test_pipeline.py +14 -0
- tests/test_render.py +11 -6
- tests/test_ui_actions.py +35 -5
PRODUCT.md
CHANGED
|
@@ -10,11 +10,11 @@ Dream QA / 梦境问答台 serves people who wake up with vivid, strange, or uns
|
|
| 10 |
|
| 11 |
## Product Purpose
|
| 12 |
|
| 13 |
-
The product helps a user unpack a dream through a guided, step-by-step Q&A. It accepts text, image, and voice fragments, turns them into one structured dream intake, asks gentle follow-up
|
| 14 |
|
| 15 |
The hackathon-facing experience is English-first. A visible `English / 中文` language control keeps the Chinese experience available without making judges start from a Chinese-only first screen.
|
| 16 |
|
| 17 |
-
Success means the user receives something more useful than a one-shot interpretation: one clear today tip first, an optional tiny action, and then a non-certain explanation grounded in concrete dream details and the user's answer. The
|
| 18 |
|
| 19 |
## Brand Personality
|
| 20 |
|
|
|
|
| 10 |
|
| 11 |
## Product Purpose
|
| 12 |
|
| 13 |
+
The product helps a user unpack a dream through a guided, step-by-step Q&A. It accepts text, image, and voice fragments, turns them into one structured dream intake, asks one gentle follow-up question, drafts a grounded interpretation, and ends with a screenshot-friendly Morning Ticket whose first payload is one Today Tip.
|
| 14 |
|
| 15 |
The hackathon-facing experience is English-first. A visible `English / 中文` language control keeps the Chinese experience available without making judges start from a Chinese-only first screen.
|
| 16 |
|
| 17 |
+
Success means the user receives something more useful than a one-shot interpretation: one clear today tip first, an optional tiny 5-minute action, and then a non-certain explanation grounded in concrete dream details and the user's answer. The ticket can be practical, playful, or simply caring, but it must feel connected to the dream.
|
| 18 |
|
| 19 |
## Brand Personality
|
| 20 |
|
README.md
CHANGED
|
@@ -20,17 +20,19 @@ tags:
|
|
| 20 |
- dream-journal
|
| 21 |
---
|
| 22 |
|
| 23 |
-
# Dream QA /
|
| 24 |
|
| 25 |
-
A Build Small Hackathon Gradio app that
|
| 26 |
|
| 27 |
-
The Hugging Face Space may still be named `Dream Customs` for continuity, but the current product direction is Dream QA: record a dream, answer or skip gentle
|
| 28 |
|
| 29 |
The public hackathon demo is English-first for international judges. A visible in-app language toggle keeps the Chinese experience available as `中文`.
|
| 30 |
|
| 31 |
## Concept
|
| 32 |
|
| 33 |
-
Dream QA accepts dream fragments by text, image, or voice. It turns those fragments into a shared dream intake, asks
|
|
|
|
|
|
|
| 34 |
|
| 35 |
This is not a therapy, diagnosis, or prophecy product.
|
| 36 |
|
|
@@ -46,10 +48,10 @@ This is not a therapy, diagnosis, or prophecy product.
|
|
| 46 |
## User Flow
|
| 47 |
|
| 48 |
1. Enter a dream by text, image, voice, or a mix.
|
| 49 |
-
2.
|
| 50 |
-
3. Answer or skip one
|
| 51 |
-
4. Receive one Today Tip tied to concrete dream details and the user's answer.
|
| 52 |
-
5.
|
| 53 |
|
| 54 |
## Language
|
| 55 |
|
|
|
|
| 20 |
- dream-journal
|
| 21 |
---
|
| 22 |
|
| 23 |
+
# Dream QA / The Morning Question Desk
|
| 24 |
|
| 25 |
+
A Build Small Hackathon Gradio app that turns a fresh dream into one grounded Morning Ticket.
|
| 26 |
|
| 27 |
+
The Hugging Face Space may still be named `Dream Customs` for continuity, but the current product direction is Dream QA / 清晨问讯室: record a dream, answer or skip one gentle question, and receive a screenshot-friendly ticket with one `Today Tip`.
|
| 28 |
|
| 29 |
The public hackathon demo is English-first for international judges. A visible in-app language toggle keeps the Chinese experience available as `中文`.
|
| 30 |
|
| 31 |
## Concept
|
| 32 |
|
| 33 |
+
Dream QA accepts dream fragments by text, image, or voice. It turns those fragments into a shared dream intake, extracts up to three concrete dream anchors, asks one grounded follow-up question, and returns a Morning Ticket: the Today Tip first, a tiny 5-minute action, and a short non-certain supporting reflection.
|
| 34 |
+
|
| 35 |
+
The fastest demo path is the elevator story: `elevator -> floor 14 -> melting buttons -> overdue email -> write only the first sentence`.
|
| 36 |
|
| 37 |
This is not a therapy, diagnosis, or prophecy product.
|
| 38 |
|
|
|
|
| 48 |
## User Flow
|
| 49 |
|
| 50 |
1. Enter a dream by text, image, voice, or a mix.
|
| 51 |
+
2. Let the app surface the dream anchors.
|
| 52 |
+
3. Answer or skip one follow-up question.
|
| 53 |
+
4. Receive a Morning Ticket with one Today Tip tied to concrete dream details and the user's answer.
|
| 54 |
+
5. Open the small-model note if the judge wants to see how MiniCPM-V and MiniCPM5-1B split the work.
|
| 55 |
|
| 56 |
## Language
|
| 57 |
|
docs/handoff.md
CHANGED
|
@@ -49,7 +49,7 @@ Build a guided dream interpretation app, not a diagnosis app, not a fortune-tell
|
|
| 49 |
|
| 50 |
Final positioning:
|
| 51 |
|
| 52 |
-
> Dream QA helps you record a dream, answer or skip one gentle question, and leave with one grounded Today Tip.
|
| 53 |
|
| 54 |
2026-06-09 tone contract:
|
| 55 |
|
|
|
|
| 49 |
|
| 50 |
Final positioning:
|
| 51 |
|
| 52 |
+
> Dream QA helps you record a dream, answer or skip one gentle question, and leave with one grounded Morning Ticket whose first payload is a Today Tip.
|
| 53 |
|
| 54 |
2026-06-09 tone contract:
|
| 55 |
|
docs/prd.md
CHANGED
|
@@ -10,7 +10,7 @@ Users who wake up from vivid dreams may feel unsettled, curious, or mentally clu
|
|
| 10 |
|
| 11 |
### Proposed Solution
|
| 12 |
|
| 13 |
-
Dream QA is a Gradio app that accepts voice, image, and text dream fragments, normalizes them into a structured dream intake, asks gentle follow-up
|
| 14 |
|
| 15 |
The repository and Space may continue to use `Dream Customs` as the deployed project name, but the product experience should no longer be built around permits, contraband, or sealed pact cards.
|
| 16 |
|
|
@@ -92,13 +92,13 @@ Acceptance criteria:
|
|
| 92 |
- Skip path still generates a final tip.
|
| 93 |
- Questions avoid clinical or diagnostic phrasing.
|
| 94 |
|
| 95 |
-
#### Story 5:
|
| 96 |
|
| 97 |
As a user, I want a clear final result so that I leave with one small thing for today.
|
| 98 |
|
| 99 |
Acceptance criteria:
|
| 100 |
|
| 101 |
-
- Result includes dream summary, main question, today tip, optional tiny action, supporting interpretation, optional caring note, and safety note when needed.
|
| 102 |
- Result presents the Today Tip before supporting interpretation.
|
| 103 |
- Result is readable on mobile and desktop.
|
| 104 |
- Result avoids medical, prophetic, or deterministic framing.
|
|
|
|
| 10 |
|
| 11 |
### Proposed Solution
|
| 12 |
|
| 13 |
+
Dream QA is a Gradio app that accepts voice, image, and text dream fragments, normalizes them into a structured dream intake, asks one gentle follow-up question, drafts a grounded interpretation, and ends with a Morning Ticket whose first payload is one Today Tip.
|
| 14 |
|
| 15 |
The repository and Space may continue to use `Dream Customs` as the deployed project name, but the product experience should no longer be built around permits, contraband, or sealed pact cards.
|
| 16 |
|
|
|
|
| 92 |
- Skip path still generates a final tip.
|
| 93 |
- Questions avoid clinical or diagnostic phrasing.
|
| 94 |
|
| 95 |
+
#### Story 5: Morning Ticket
|
| 96 |
|
| 97 |
As a user, I want a clear final result so that I leave with one small thing for today.
|
| 98 |
|
| 99 |
Acceptance criteria:
|
| 100 |
|
| 101 |
+
- Result includes dream summary, main question, today tip, optional tiny 5-minute action, supporting interpretation, optional caring note, and safety note when needed.
|
| 102 |
- Result presents the Today Tip before supporting interpretation.
|
| 103 |
- Result is readable on mobile and desktop.
|
| 104 |
- Result avoids medical, prophetic, or deterministic framing.
|
docs/reports/2026-06-12-dream-qa-fix-report.html
ADDED
|
@@ -0,0 +1,720 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!doctype html>
|
| 2 |
+
<html lang="zh-CN">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="utf-8">
|
| 5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
| 6 |
+
<title>Dream QA 本轮修复回顾 - 2026-06-12</title>
|
| 7 |
+
<style>
|
| 8 |
+
:root {
|
| 9 |
+
--bg: #f6f1e9;
|
| 10 |
+
--paper: #fffaf1;
|
| 11 |
+
--paper-strong: #fff3df;
|
| 12 |
+
--ink: #183137;
|
| 13 |
+
--muted: #66787b;
|
| 14 |
+
--line: #d9cbb8;
|
| 15 |
+
--teal: #0b5963;
|
| 16 |
+
--teal-soft: #dcecea;
|
| 17 |
+
--coral: #bd5545;
|
| 18 |
+
--coral-soft: #f7ddd4;
|
| 19 |
+
--sage: #e7eadb;
|
| 20 |
+
--shadow: 0 22px 60px rgba(24, 49, 55, 0.13);
|
| 21 |
+
}
|
| 22 |
+
|
| 23 |
+
* {
|
| 24 |
+
box-sizing: border-box;
|
| 25 |
+
}
|
| 26 |
+
|
| 27 |
+
html {
|
| 28 |
+
color: var(--ink);
|
| 29 |
+
font-family: "Avenir Next", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", system-ui, sans-serif;
|
| 30 |
+
line-height: 1.65;
|
| 31 |
+
background:
|
| 32 |
+
linear-gradient(90deg, rgba(11, 89, 99, 0.04) 1px, transparent 1px),
|
| 33 |
+
linear-gradient(0deg, rgba(189, 85, 69, 0.035) 1px, transparent 1px),
|
| 34 |
+
var(--bg);
|
| 35 |
+
background-size: 76px 76px, 76px 76px, auto;
|
| 36 |
+
}
|
| 37 |
+
|
| 38 |
+
body {
|
| 39 |
+
margin: 0;
|
| 40 |
+
}
|
| 41 |
+
|
| 42 |
+
a {
|
| 43 |
+
color: var(--teal);
|
| 44 |
+
font-weight: 760;
|
| 45 |
+
text-decoration-thickness: 2px;
|
| 46 |
+
text-underline-offset: 3px;
|
| 47 |
+
}
|
| 48 |
+
|
| 49 |
+
code {
|
| 50 |
+
background: rgba(11, 89, 99, 0.08);
|
| 51 |
+
border: 1px solid rgba(11, 89, 99, 0.12);
|
| 52 |
+
border-radius: 5px;
|
| 53 |
+
color: #113e46;
|
| 54 |
+
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
| 55 |
+
font-size: 0.92em;
|
| 56 |
+
padding: 0.08em 0.34em;
|
| 57 |
+
}
|
| 58 |
+
|
| 59 |
+
.page {
|
| 60 |
+
margin: 0 auto;
|
| 61 |
+
max-width: 1180px;
|
| 62 |
+
padding: 28px clamp(16px, 4vw, 44px) 56px;
|
| 63 |
+
}
|
| 64 |
+
|
| 65 |
+
.hero {
|
| 66 |
+
background:
|
| 67 |
+
linear-gradient(135deg, rgba(11, 89, 99, 0.97), rgba(24, 49, 55, 0.96)),
|
| 68 |
+
var(--teal);
|
| 69 |
+
border: 1px solid rgba(24, 49, 55, 0.2);
|
| 70 |
+
border-radius: 10px 10px 0 0;
|
| 71 |
+
box-shadow: var(--shadow);
|
| 72 |
+
color: #fffaf1;
|
| 73 |
+
padding: clamp(24px, 4vw, 44px);
|
| 74 |
+
position: relative;
|
| 75 |
+
overflow: hidden;
|
| 76 |
+
}
|
| 77 |
+
|
| 78 |
+
.hero::after {
|
| 79 |
+
border: 1px solid rgba(255, 250, 241, 0.45);
|
| 80 |
+
border-radius: 999px;
|
| 81 |
+
content: "DREAM QA REVIEW";
|
| 82 |
+
font-family: Georgia, "Times New Roman", serif;
|
| 83 |
+
font-size: 0.74rem;
|
| 84 |
+
font-weight: 700;
|
| 85 |
+
height: 92px;
|
| 86 |
+
letter-spacing: 0;
|
| 87 |
+
line-height: 92px;
|
| 88 |
+
opacity: 0.55;
|
| 89 |
+
position: absolute;
|
| 90 |
+
right: 28px;
|
| 91 |
+
text-align: center;
|
| 92 |
+
top: 24px;
|
| 93 |
+
transform: rotate(8deg);
|
| 94 |
+
width: 92px;
|
| 95 |
+
}
|
| 96 |
+
|
| 97 |
+
.kicker {
|
| 98 |
+
color: #f7d7cc;
|
| 99 |
+
font-size: 0.82rem;
|
| 100 |
+
font-weight: 820;
|
| 101 |
+
margin: 0 0 12px;
|
| 102 |
+
text-transform: uppercase;
|
| 103 |
+
}
|
| 104 |
+
|
| 105 |
+
h1,
|
| 106 |
+
h2,
|
| 107 |
+
h3 {
|
| 108 |
+
letter-spacing: 0;
|
| 109 |
+
line-height: 1.2;
|
| 110 |
+
}
|
| 111 |
+
|
| 112 |
+
h1 {
|
| 113 |
+
font-family: Georgia, "Times New Roman", serif;
|
| 114 |
+
font-size: clamp(2.15rem, 6vw, 4.2rem);
|
| 115 |
+
margin: 0;
|
| 116 |
+
max-width: 820px;
|
| 117 |
+
}
|
| 118 |
+
|
| 119 |
+
.hero p {
|
| 120 |
+
color: rgba(255, 250, 241, 0.86);
|
| 121 |
+
font-size: 1.05rem;
|
| 122 |
+
margin: 18px 0 0;
|
| 123 |
+
max-width: 760px;
|
| 124 |
+
}
|
| 125 |
+
|
| 126 |
+
.summary-bar {
|
| 127 |
+
background: var(--paper);
|
| 128 |
+
border: 1px solid var(--line);
|
| 129 |
+
border-top: 0;
|
| 130 |
+
box-shadow: var(--shadow);
|
| 131 |
+
display: grid;
|
| 132 |
+
gap: 14px;
|
| 133 |
+
grid-template-columns: repeat(4, minmax(0, 1fr));
|
| 134 |
+
padding: 18px clamp(18px, 4vw, 34px);
|
| 135 |
+
}
|
| 136 |
+
|
| 137 |
+
.summary-item {
|
| 138 |
+
border-left: 3px solid var(--teal);
|
| 139 |
+
padding-left: 12px;
|
| 140 |
+
}
|
| 141 |
+
|
| 142 |
+
.summary-item span {
|
| 143 |
+
color: var(--muted);
|
| 144 |
+
display: block;
|
| 145 |
+
font-size: 0.78rem;
|
| 146 |
+
font-weight: 760;
|
| 147 |
+
text-transform: uppercase;
|
| 148 |
+
}
|
| 149 |
+
|
| 150 |
+
.summary-item strong {
|
| 151 |
+
display: block;
|
| 152 |
+
font-size: 1rem;
|
| 153 |
+
margin-top: 3px;
|
| 154 |
+
}
|
| 155 |
+
|
| 156 |
+
main {
|
| 157 |
+
background: rgba(255, 250, 241, 0.88);
|
| 158 |
+
border: 1px solid var(--line);
|
| 159 |
+
border-top: 0;
|
| 160 |
+
box-shadow: var(--shadow);
|
| 161 |
+
padding: clamp(22px, 4vw, 40px);
|
| 162 |
+
}
|
| 163 |
+
|
| 164 |
+
section {
|
| 165 |
+
margin-top: 38px;
|
| 166 |
+
}
|
| 167 |
+
|
| 168 |
+
section:first-child {
|
| 169 |
+
margin-top: 0;
|
| 170 |
+
}
|
| 171 |
+
|
| 172 |
+
h2 {
|
| 173 |
+
align-items: center;
|
| 174 |
+
color: var(--ink);
|
| 175 |
+
display: flex;
|
| 176 |
+
font-family: Georgia, "Times New Roman", serif;
|
| 177 |
+
font-size: clamp(1.45rem, 3vw, 2rem);
|
| 178 |
+
gap: 12px;
|
| 179 |
+
margin: 0 0 18px;
|
| 180 |
+
}
|
| 181 |
+
|
| 182 |
+
h2::before {
|
| 183 |
+
background: var(--teal);
|
| 184 |
+
border-radius: 999px;
|
| 185 |
+
content: "";
|
| 186 |
+
display: inline-block;
|
| 187 |
+
height: 12px;
|
| 188 |
+
width: 12px;
|
| 189 |
+
}
|
| 190 |
+
|
| 191 |
+
h3 {
|
| 192 |
+
color: #173f47;
|
| 193 |
+
font-size: 1.05rem;
|
| 194 |
+
margin: 0 0 8px;
|
| 195 |
+
}
|
| 196 |
+
|
| 197 |
+
p {
|
| 198 |
+
margin: 0 0 14px;
|
| 199 |
+
}
|
| 200 |
+
|
| 201 |
+
.lead {
|
| 202 |
+
color: #304f55;
|
| 203 |
+
font-size: 1.06rem;
|
| 204 |
+
max-width: 860px;
|
| 205 |
+
}
|
| 206 |
+
|
| 207 |
+
.grid {
|
| 208 |
+
display: grid;
|
| 209 |
+
gap: 16px;
|
| 210 |
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
| 211 |
+
}
|
| 212 |
+
|
| 213 |
+
.card {
|
| 214 |
+
background: rgba(255, 255, 255, 0.55);
|
| 215 |
+
border: 1px solid var(--line);
|
| 216 |
+
border-radius: 8px;
|
| 217 |
+
padding: 18px;
|
| 218 |
+
}
|
| 219 |
+
|
| 220 |
+
.card.warning {
|
| 221 |
+
background: linear-gradient(180deg, #fff8ee, var(--coral-soft));
|
| 222 |
+
border-color: #e7b8aa;
|
| 223 |
+
}
|
| 224 |
+
|
| 225 |
+
.tag-list {
|
| 226 |
+
display: flex;
|
| 227 |
+
flex-wrap: wrap;
|
| 228 |
+
gap: 8px;
|
| 229 |
+
margin: 12px 0 0;
|
| 230 |
+
padding: 0;
|
| 231 |
+
}
|
| 232 |
+
|
| 233 |
+
.tag-list li {
|
| 234 |
+
background: var(--teal-soft);
|
| 235 |
+
border: 1px solid rgba(11, 89, 99, 0.16);
|
| 236 |
+
border-radius: 999px;
|
| 237 |
+
color: #17494f;
|
| 238 |
+
font-size: 0.88rem;
|
| 239 |
+
font-weight: 720;
|
| 240 |
+
list-style: none;
|
| 241 |
+
padding: 5px 10px;
|
| 242 |
+
}
|
| 243 |
+
|
| 244 |
+
.fix-list {
|
| 245 |
+
counter-reset: fixes;
|
| 246 |
+
display: grid;
|
| 247 |
+
gap: 14px;
|
| 248 |
+
margin: 0;
|
| 249 |
+
padding: 0;
|
| 250 |
+
}
|
| 251 |
+
|
| 252 |
+
.fix-list > li {
|
| 253 |
+
background: var(--paper);
|
| 254 |
+
border: 1px solid var(--line);
|
| 255 |
+
border-radius: 8px;
|
| 256 |
+
display: grid;
|
| 257 |
+
gap: 10px;
|
| 258 |
+
grid-template-columns: 42px minmax(0, 1fr);
|
| 259 |
+
list-style: none;
|
| 260 |
+
padding: 16px;
|
| 261 |
+
}
|
| 262 |
+
|
| 263 |
+
.fix-list > li::before {
|
| 264 |
+
align-items: center;
|
| 265 |
+
background: var(--teal);
|
| 266 |
+
border-radius: 999px;
|
| 267 |
+
color: #fffaf1;
|
| 268 |
+
content: counter(fixes);
|
| 269 |
+
counter-increment: fixes;
|
| 270 |
+
display: inline-flex;
|
| 271 |
+
font-weight: 820;
|
| 272 |
+
height: 32px;
|
| 273 |
+
justify-content: center;
|
| 274 |
+
margin-top: 2px;
|
| 275 |
+
width: 32px;
|
| 276 |
+
}
|
| 277 |
+
|
| 278 |
+
.fix-list ul,
|
| 279 |
+
.check-list,
|
| 280 |
+
.commit-list {
|
| 281 |
+
margin: 8px 0 0;
|
| 282 |
+
padding-left: 1.1rem;
|
| 283 |
+
}
|
| 284 |
+
|
| 285 |
+
.fix-list li li,
|
| 286 |
+
.check-list li,
|
| 287 |
+
.commit-list li {
|
| 288 |
+
margin: 5px 0;
|
| 289 |
+
}
|
| 290 |
+
|
| 291 |
+
.matrix {
|
| 292 |
+
border-collapse: collapse;
|
| 293 |
+
font-size: 0.96rem;
|
| 294 |
+
width: 100%;
|
| 295 |
+
}
|
| 296 |
+
|
| 297 |
+
.matrix th,
|
| 298 |
+
.matrix td {
|
| 299 |
+
border: 1px solid var(--line);
|
| 300 |
+
padding: 11px 12px;
|
| 301 |
+
text-align: left;
|
| 302 |
+
vertical-align: top;
|
| 303 |
+
}
|
| 304 |
+
|
| 305 |
+
.matrix th {
|
| 306 |
+
background: var(--sage);
|
| 307 |
+
color: #253f43;
|
| 308 |
+
font-size: 0.82rem;
|
| 309 |
+
text-transform: uppercase;
|
| 310 |
+
}
|
| 311 |
+
|
| 312 |
+
.status {
|
| 313 |
+
border-radius: 999px;
|
| 314 |
+
display: inline-block;
|
| 315 |
+
font-size: 0.82rem;
|
| 316 |
+
font-weight: 820;
|
| 317 |
+
padding: 4px 9px;
|
| 318 |
+
white-space: nowrap;
|
| 319 |
+
}
|
| 320 |
+
|
| 321 |
+
.status.done {
|
| 322 |
+
background: #dcecea;
|
| 323 |
+
color: #17494f;
|
| 324 |
+
}
|
| 325 |
+
|
| 326 |
+
.status.open {
|
| 327 |
+
background: #fff0cf;
|
| 328 |
+
color: #704d00;
|
| 329 |
+
}
|
| 330 |
+
|
| 331 |
+
.callout {
|
| 332 |
+
background: linear-gradient(90deg, rgba(11, 89, 99, 0.12), rgba(11, 89, 99, 0.04));
|
| 333 |
+
border: 1px solid rgba(11, 89, 99, 0.2);
|
| 334 |
+
border-left: 5px solid var(--teal);
|
| 335 |
+
border-radius: 8px;
|
| 336 |
+
padding: 16px 18px;
|
| 337 |
+
}
|
| 338 |
+
|
| 339 |
+
.timeline {
|
| 340 |
+
border-left: 2px solid var(--line);
|
| 341 |
+
margin: 0;
|
| 342 |
+
padding: 0 0 0 22px;
|
| 343 |
+
}
|
| 344 |
+
|
| 345 |
+
.timeline li {
|
| 346 |
+
list-style: none;
|
| 347 |
+
margin: 0 0 16px;
|
| 348 |
+
position: relative;
|
| 349 |
+
}
|
| 350 |
+
|
| 351 |
+
.timeline li::before {
|
| 352 |
+
background: var(--coral);
|
| 353 |
+
border: 3px solid var(--paper);
|
| 354 |
+
border-radius: 999px;
|
| 355 |
+
content: "";
|
| 356 |
+
height: 12px;
|
| 357 |
+
left: -30px;
|
| 358 |
+
position: absolute;
|
| 359 |
+
top: 6px;
|
| 360 |
+
width: 12px;
|
| 361 |
+
}
|
| 362 |
+
|
| 363 |
+
footer {
|
| 364 |
+
color: var(--muted);
|
| 365 |
+
font-size: 0.9rem;
|
| 366 |
+
padding: 20px 4px 0;
|
| 367 |
+
text-align: center;
|
| 368 |
+
}
|
| 369 |
+
|
| 370 |
+
@media (max-width: 820px) {
|
| 371 |
+
.summary-bar,
|
| 372 |
+
.grid {
|
| 373 |
+
grid-template-columns: 1fr;
|
| 374 |
+
}
|
| 375 |
+
|
| 376 |
+
.hero::after {
|
| 377 |
+
display: none;
|
| 378 |
+
}
|
| 379 |
+
|
| 380 |
+
.fix-list > li {
|
| 381 |
+
grid-template-columns: 1fr;
|
| 382 |
+
}
|
| 383 |
+
|
| 384 |
+
.matrix {
|
| 385 |
+
display: block;
|
| 386 |
+
overflow-x: auto;
|
| 387 |
+
}
|
| 388 |
+
}
|
| 389 |
+
|
| 390 |
+
@media print {
|
| 391 |
+
html {
|
| 392 |
+
background: #fff;
|
| 393 |
+
}
|
| 394 |
+
|
| 395 |
+
.page {
|
| 396 |
+
max-width: none;
|
| 397 |
+
padding: 0;
|
| 398 |
+
}
|
| 399 |
+
|
| 400 |
+
.hero,
|
| 401 |
+
.summary-bar,
|
| 402 |
+
main {
|
| 403 |
+
box-shadow: none;
|
| 404 |
+
}
|
| 405 |
+
|
| 406 |
+
a {
|
| 407 |
+
color: var(--ink);
|
| 408 |
+
}
|
| 409 |
+
}
|
| 410 |
+
</style>
|
| 411 |
+
</head>
|
| 412 |
+
<body>
|
| 413 |
+
<div class="page">
|
| 414 |
+
<header class="hero">
|
| 415 |
+
<p class="kicker">Build Small Hackathon - Dream QA</p>
|
| 416 |
+
<h1>本轮修复回顾</h1>
|
| 417 |
+
<p>这份文档记录了从用户视角评估本地 Gradio app 后发现的问题、对应修复、验证结果,以及 Hugging Face Space PR 状态。它适合回看决策脉络,也适合给后续接手的人快速对齐。</p>
|
| 418 |
+
</header>
|
| 419 |
+
|
| 420 |
+
<aside class="summary-bar" aria-label="本轮状态摘要">
|
| 421 |
+
<div class="summary-item">
|
| 422 |
+
<span>GitHub branch</span>
|
| 423 |
+
<strong><code>bugfix/emotion-led-dream-qa</code></strong>
|
| 424 |
+
</div>
|
| 425 |
+
<div class="summary-item">
|
| 426 |
+
<span>Latest GitHub commit</span>
|
| 427 |
+
<strong><code>a2c28e0</code></strong>
|
| 428 |
+
</div>
|
| 429 |
+
<div class="summary-item">
|
| 430 |
+
<span>HF Space PR</span>
|
| 431 |
+
<strong><a href="https://huggingface.co/spaces/build-small-hackathon/dream-customs/discussions/36">Discussion #36</a></strong>
|
| 432 |
+
</div>
|
| 433 |
+
<div class="summary-item">
|
| 434 |
+
<span>Verification</span>
|
| 435 |
+
<strong>147 tests + Chrome smoke passed</strong>
|
| 436 |
+
</div>
|
| 437 |
+
</aside>
|
| 438 |
+
|
| 439 |
+
<main>
|
| 440 |
+
<section>
|
| 441 |
+
<h2>一句话结论</h2>
|
| 442 |
+
<p class="lead">本轮不是单纯改样式,而是把 Dream QA 从“看起来能跑”推进到“用户真实点击时更稳定、更贴梦境、更可交付”。重点修了三类问题:梦境细节保真、交互反馈与复制体验、真实 Chrome 下 Gradio 事件运行时错误。</p>
|
| 443 |
+
<div class="callout">
|
| 444 |
+
<strong>当前状态:</strong>本地 Chrome 完整用户流已通过;GitHub 分支已推送;Hugging Face Space PR 已创建但未合并到 live Space main。
|
| 445 |
+
</div>
|
| 446 |
+
</section>
|
| 447 |
+
|
| 448 |
+
<section>
|
| 449 |
+
<h2>最初评价维度</h2>
|
| 450 |
+
<div class="grid">
|
| 451 |
+
<article class="card">
|
| 452 |
+
<h3>1. 创意实现完整度</h3>
|
| 453 |
+
<p>核心创意是循序渐进的梦境问答:先记录梦境,再问一个温和追问,最后给一个引用梦里具体细节的 Today Tip。本轮重点检查它是否真的按这个故事线工作,而不是输出泛泛建议。</p>
|
| 454 |
+
<ul class="tag-list">
|
| 455 |
+
<li>梦境锚点</li>
|
| 456 |
+
<li>追问链路</li>
|
| 457 |
+
<li>Today Tip</li>
|
| 458 |
+
</ul>
|
| 459 |
+
</article>
|
| 460 |
+
<article class="card">
|
| 461 |
+
<h3>2. 前端 UI/UX 与交互逻辑</h3>
|
| 462 |
+
<p>重点看用户能不能明确知道下一步、等待时有没有反馈、错误是否贴近输入区、结果是否能复制,以及“再追问”和“换角度”是否语义清楚。</p>
|
| 463 |
+
<ul class="tag-list">
|
| 464 |
+
<li>加载反馈</li>
|
| 465 |
+
<li>内联错误</li>
|
| 466 |
+
<li>复制结果</li>
|
| 467 |
+
<li>按钮语义</li>
|
| 468 |
+
</ul>
|
| 469 |
+
</article>
|
| 470 |
+
<article class="card">
|
| 471 |
+
<h3>3. 后端与 Modal endpoint 动态响应</h3>
|
| 472 |
+
<p>重点看后端是否能根据用户输入和追问回答动态调整输出,而不是沿用模板;同时检查真实点击是否触发 Gradio/endpoint 运行时错误。</p>
|
| 473 |
+
<ul class="tag-list">
|
| 474 |
+
<li>回答感知</li>
|
| 475 |
+
<li>schema guard</li>
|
| 476 |
+
<li>真实运行态</li>
|
| 477 |
+
</ul>
|
| 478 |
+
</article>
|
| 479 |
+
<article class="card">
|
| 480 |
+
<h3>4. 补充验收方向</h3>
|
| 481 |
+
<p>我额外补了可回归的工程验收:自动测试、Today Tip 质量脚本、Chrome 真实点击、剪贴板、HF Space PR 状态拆分。</p>
|
| 482 |
+
<ul class="tag-list">
|
| 483 |
+
<li>pytest</li>
|
| 484 |
+
<li>Chrome smoke</li>
|
| 485 |
+
<li>HF Space PR</li>
|
| 486 |
+
</ul>
|
| 487 |
+
</article>
|
| 488 |
+
</div>
|
| 489 |
+
</section>
|
| 490 |
+
|
| 491 |
+
<section>
|
| 492 |
+
<h2>发现的问题与修复</h2>
|
| 493 |
+
<ol class="fix-list">
|
| 494 |
+
<li>
|
| 495 |
+
<div>
|
| 496 |
+
<h3>梦境锚点会被示例污染</h3>
|
| 497 |
+
<p><strong>问题:</strong>用户只说了办公楼、14 层、电梯口、邮件、小台灯时,系统有时会把历史示例里的“融化按钮”带进结果,破坏可信度。</p>
|
| 498 |
+
<p><strong>修复:</strong>扩展中文锚点提取,增加 unsupported melted detail 清理逻辑,让 <code>stuck_elevator</code> 相关提示只使用真实输入里出现过的细节。</p>
|
| 499 |
+
<ul>
|
| 500 |
+
<li>新增测试覆盖:未输入“融化”时不得出现;用户确实输入“融化按钮”时要保留。</li>
|
| 501 |
+
<li>代表提交:<code>7e05b4c Fix Dream QA anchor fidelity</code></li>
|
| 502 |
+
</ul>
|
| 503 |
+
</div>
|
| 504 |
+
</li>
|
| 505 |
+
<li>
|
| 506 |
+
<div>
|
| 507 |
+
<h3>Today Tip 没有优先吸收用户回答</h3>
|
| 508 |
+
<p><strong>问题:</strong>用户在追问回答里给出具体行动,例如“今天先写邮件第一句话”,结果仍可能偏向情绪模板。</p>
|
| 509 |
+
<p><strong>修复:</strong>在 <code>_polish_today_tip</code> 中增加 answer-aware shaping:当回答里包含邮件、草稿、演示等明确任务时,让 interpretation、today_tip、tiny_action 优先围绕这个具体行动生成。</p>
|
| 510 |
+
<ul>
|
| 511 |
+
<li>Chrome 验证结果:最终 Tip 明确引用“邮件”和“第一句话”。</li>
|
| 512 |
+
<li>代表提交:<code>8b45257 Prioritize concrete answer tips</code></li>
|
| 513 |
+
</ul>
|
| 514 |
+
</div>
|
| 515 |
+
</li>
|
| 516 |
+
<li>
|
| 517 |
+
<div>
|
| 518 |
+
<h3>长等待时用户不知道系统是否在工作</h3>
|
| 519 |
+
<p><strong>问题:</strong>点击继续或发送回答后,如果后端较慢,页面缺少即时反馈,用户容易误以为卡住。</p>
|
| 520 |
+
<p><strong>修复:</strong>增加 processing notice 与 processing note。提交梦境时显示“正在整理梦境线索”,生成 Tip 时显示“正在检查梦境锚点、你的回答和安全边界”。</p>
|
| 521 |
+
<ul>
|
| 522 |
+
<li>后续 Chrome 验收发现 event-level JS 会触发 Gradio Number 输入错误,因此最终改成 DOM listener,不再挂在 Gradio <code>.click(js=...)</code> 上。</li>
|
| 523 |
+
<li>代表提交:<code>f84f819</code>, <code>ed9b284</code>, <code>a2c28e0</code></li>
|
| 524 |
+
</ul>
|
| 525 |
+
</div>
|
| 526 |
+
</li>
|
| 527 |
+
<li>
|
| 528 |
+
<div>
|
| 529 |
+
<h3>复制结果按钮不是真正复制</h3>
|
| 530 |
+
<p><strong>问题:</strong>用户生成 Today Tip 后,需要方便复制结果回看或分享。</p>
|
| 531 |
+
<p><strong>修复:</strong>增加 <code>navigator.clipboard.writeText</code> 路径,并保留浏览器阻止复制时的可恢复提示。</p>
|
| 532 |
+
<ul>
|
| 533 |
+
<li>Chrome 验证结果:剪贴板文本长度 1299,包含 Today Tip 与“邮件”。</li>
|
| 534 |
+
<li>代表提交:<code>6fb9df2 Make result copy button use clipboard</code></li>
|
| 535 |
+
</ul>
|
| 536 |
+
</div>
|
| 537 |
+
</li>
|
| 538 |
+
<li>
|
| 539 |
+
<div>
|
| 540 |
+
<h3>“换个角度”语义不够明确,追问片段可能截断</h3>
|
| 541 |
+
<p><strong>问题:</strong>按钮文案容易让人误解为重写结果,不是重新追问;中文回答片段还可能截成“今天可。”这种不自然片段。</p>
|
| 542 |
+
<p><strong>修复:</strong>按钮改为“再追问一下”和“换个追问角度”;中文 answer snippet 截断长度放宽,并尽量在句末截断。</p>
|
| 543 |
+
<ul>
|
| 544 |
+
<li>Chrome 验证结果:新追问保留完整“今天可以先写第一句话,不一定马上发出去”。</li>
|
| 545 |
+
<li>代表提交:<code>11bc374 Clarify another-angle questions</code></li>
|
| 546 |
+
</ul>
|
| 547 |
+
</div>
|
| 548 |
+
</li>
|
| 549 |
+
<li>
|
| 550 |
+
<div>
|
| 551 |
+
<h3>空输入错误只在顶部提示,不够贴近输入区</h3>
|
| 552 |
+
<p><strong>问题:</strong>用户不输入任何梦境就点继续时,错误只靠顶部 notice,不够明确,也不利于辅助技术感知。</p>
|
| 553 |
+
<p><strong>修复:</strong>在输入区下方增加内联错误提示,并使用 <code>role="alert"</code>。</p>
|
| 554 |
+
<ul>
|
| 555 |
+
<li>Chrome 验证结果:空输入后出现 “Dream note needs at least one sentence, image, or voice clue before Continue.”</li>
|
| 556 |
+
<li>代表提交:<code>510df9b Show inline dream input errors</code></li>
|
| 557 |
+
</ul>
|
| 558 |
+
</div>
|
| 559 |
+
</li>
|
| 560 |
+
<li>
|
| 561 |
+
<div>
|
| 562 |
+
<h3>真实 Chrome 下“发送回答”会崩溃</h3>
|
| 563 |
+
<p><strong>问题:</strong>Chrome 真实点击“发送回答”时,Gradio 抛出 <code>type str doesn't define __round__ method</code>。根因是 processing feedback 挂在 event-level JS 上后,Number 输入在真实浏览器路径中以字符串进入 <code>gr.Number.preprocess</code>。</p>
|
| 564 |
+
<p><strong>修复:</strong>尝试数值 coercion 后仍复现,最终改成更稳的方案:processing feedback 使用 <code>Blocks(js=VOICE_JS)</code> 里的 class-based DOM listener,Gradio 事件本身不再带 processing JS,从根上避免污染事件输入。</p>
|
| 565 |
+
<ul>
|
| 566 |
+
<li>Chrome 复测结果:回答提交成功生成 Today Tip,无 <code>__round__</code> 错误。</li>
|
| 567 |
+
<li>代表提交:<code>a5fd37d</code>, <code>fd4afbc</code>, <code>ed9b284</code></li>
|
| 568 |
+
</ul>
|
| 569 |
+
</div>
|
| 570 |
+
</li>
|
| 571 |
+
<li>
|
| 572 |
+
<div>
|
| 573 |
+
<h3>追问按钮的处理中提示语义不准</h3>
|
| 574 |
+
<p><strong>问题:</strong>点“换个追问角度”时,processing 文案仍然说“正在把追问回答整理进今日小 Tips”,与实际行为不一致。</p>
|
| 575 |
+
<p><strong>修复:</strong>把追问类按钮从 Tip processing 分离出来,显示“正在换一个追问角度”和“正在保留你的回答和梦境锚点,只重新生成一个更贴近当下的追问”。</p>
|
| 576 |
+
<ul>
|
| 577 |
+
<li>Chrome 验证结果:换角度时出现正确 processing 文案,并成功回到追问态。</li>
|
| 578 |
+
<li>代表提交:<code>a2c28e0 Clarify question processing feedback</code></li>
|
| 579 |
+
</ul>
|
| 580 |
+
</div>
|
| 581 |
+
</li>
|
| 582 |
+
</ol>
|
| 583 |
+
</section>
|
| 584 |
+
|
| 585 |
+
<section>
|
| 586 |
+
<h2>最终 Chrome 验收记录</h2>
|
| 587 |
+
<table class="matrix">
|
| 588 |
+
<thead>
|
| 589 |
+
<tr>
|
| 590 |
+
<th>用户动作</th>
|
| 591 |
+
<th>验收点</th>
|
| 592 |
+
<th>结果</th>
|
| 593 |
+
</tr>
|
| 594 |
+
</thead>
|
| 595 |
+
<tbody>
|
| 596 |
+
<tr>
|
| 597 |
+
<td>空输入后点击 Continue</td>
|
| 598 |
+
<td>输入区下方出现内联错误,带 <code>role="alert"</code></td>
|
| 599 |
+
<td><span class="status done">通过</span></td>
|
| 600 |
+
</tr>
|
| 601 |
+
<tr>
|
| 602 |
+
<td>切中文并输入办公楼、电梯口、邮件梦境</td>
|
| 603 |
+
<td>点击后立即出现中文 processing 文案</td>
|
| 604 |
+
<td><span class="status done">通过</span></td>
|
| 605 |
+
</tr>
|
| 606 |
+
<tr>
|
| 607 |
+
<td>等待生成追问</td>
|
| 608 |
+
<td>追问抓住办公楼、邮件、电梯口,不出现未输入的“融化按钮”</td>
|
| 609 |
+
<td><span class="status done">通过</span></td>
|
| 610 |
+
</tr>
|
| 611 |
+
<tr>
|
| 612 |
+
<td>回答“先写邮件第一句话”</td>
|
| 613 |
+
<td>Today Tip 引用邮件��第一句话,且不要求立刻发出</td>
|
| 614 |
+
<td><span class="status done">通过</span></td>
|
| 615 |
+
</tr>
|
| 616 |
+
<tr>
|
| 617 |
+
<td>点击复制结果</td>
|
| 618 |
+
<td>剪贴板中有完整结果,包含 Today Tip 与邮件线索</td>
|
| 619 |
+
<td><span class="status done">通过</span></td>
|
| 620 |
+
</tr>
|
| 621 |
+
<tr>
|
| 622 |
+
<td>点击换个追问角度</td>
|
| 623 |
+
<td>processing 文案语义正确,新追问不截断为“今天可。”</td>
|
| 624 |
+
<td><span class="status done">通过</span></td>
|
| 625 |
+
</tr>
|
| 626 |
+
</tbody>
|
| 627 |
+
</table>
|
| 628 |
+
</section>
|
| 629 |
+
|
| 630 |
+
<section>
|
| 631 |
+
<h2>测试与质量门</h2>
|
| 632 |
+
<div class="grid">
|
| 633 |
+
<article class="card">
|
| 634 |
+
<h3>自动化测试</h3>
|
| 635 |
+
<ul class="check-list">
|
| 636 |
+
<li><code>.venv/bin/python -m pytest -q</code> - 147 passed</li>
|
| 637 |
+
<li><code>.venv/bin/python scripts/evaluate_today_tip_quality.py</code> - 11 cases passed</li>
|
| 638 |
+
<li><code>git diff --check</code> - passed</li>
|
| 639 |
+
</ul>
|
| 640 |
+
</article>
|
| 641 |
+
<article class="card">
|
| 642 |
+
<h3>真实运行检查</h3>
|
| 643 |
+
<ul class="check-list">
|
| 644 |
+
<li>本地 Gradio app 使用当前提交启动在 <code>localhost:7860</code></li>
|
| 645 |
+
<li>通过 Chrome 插件真实点击完整用户流</li>
|
| 646 |
+
<li>检查剪贴板、DOM 文案、运行时错误、梦境锚点污染</li>
|
| 647 |
+
</ul>
|
| 648 |
+
</article>
|
| 649 |
+
</div>
|
| 650 |
+
</section>
|
| 651 |
+
|
| 652 |
+
<section>
|
| 653 |
+
<h2>提交记录</h2>
|
| 654 |
+
<p>本轮按“每完成一项 todo 就 commit/push”的节奏推进。核心提交如下:</p>
|
| 655 |
+
<ul class="commit-list">
|
| 656 |
+
<li><code>7e05b4c</code> Fix Dream QA anchor fidelity</li>
|
| 657 |
+
<li><code>8b45257</code> Prioritize concrete answer tips</li>
|
| 658 |
+
<li><code>f84f819</code> Improve Dream QA processing feedback</li>
|
| 659 |
+
<li><code>6fb9df2</code> Make result copy button use clipboard</li>
|
| 660 |
+
<li><code>11bc374</code> Clarify another-angle questions</li>
|
| 661 |
+
<li><code>510df9b</code> Show inline dream input errors</li>
|
| 662 |
+
<li><code>a5fd37d</code> Preserve numeric settings in processing JS</li>
|
| 663 |
+
<li><code>fd4afbc</code> Harden processing JS number coercion</li>
|
| 664 |
+
<li><code>ed9b284</code> Move processing feedback off Gradio event JS</li>
|
| 665 |
+
<li><code>a2c28e0</code> Clarify question processing feedback</li>
|
| 666 |
+
</ul>
|
| 667 |
+
</section>
|
| 668 |
+
|
| 669 |
+
<section>
|
| 670 |
+
<h2>Hugging Face Space 状态</h2>
|
| 671 |
+
<div class="grid">
|
| 672 |
+
<article class="card">
|
| 673 |
+
<h3>已完成</h3>
|
| 674 |
+
<p>已经从干净的 <code>HEAD</code> archive 创建 Hugging Face Space PR,避免把本地既有脏文件和未跟踪目录上传进去。</p>
|
| 675 |
+
<ul class="check-list">
|
| 676 |
+
<li>PR: <a href="https://huggingface.co/spaces/build-small-hackathon/dream-customs/discussions/36">dream-customs discussions/36</a></li>
|
| 677 |
+
<li>PR ref: <code>refs/pr/36</code></li>
|
| 678 |
+
<li>PR commit: <code>76c31a370aba5ca6663310c304c7f08882d73959</code></li>
|
| 679 |
+
</ul>
|
| 680 |
+
</article>
|
| 681 |
+
<article class="card warning">
|
| 682 |
+
<h3>未合并到 live main</h3>
|
| 683 |
+
<p>本轮只创建了 Space PR,没有改 live Space main。创建 PR 时:</p>
|
| 684 |
+
<ul class="check-list">
|
| 685 |
+
<li><code>space/main</code>: <code>2ce45081165b96e747c8b406269905e5d46ac211</code></li>
|
| 686 |
+
<li><code>refs/pr/36</code>: <code>76c31a370aba5ca6663310c304c7f08882d73959</code></li>
|
| 687 |
+
</ul>
|
| 688 |
+
</article>
|
| 689 |
+
</div>
|
| 690 |
+
</section>
|
| 691 |
+
|
| 692 |
+
<section>
|
| 693 |
+
<h2>回看时重点看什么</h2>
|
| 694 |
+
<ul class="timeline">
|
| 695 |
+
<li>
|
| 696 |
+
<h3>用户信任感来自“锚点真实”</h3>
|
| 697 |
+
<p>Dream QA 最怕凭空编细节。本轮最重要的产品修复就是让输出只引用用户真的提供过的梦境线索。</p>
|
| 698 |
+
</li>
|
| 699 |
+
<li>
|
| 700 |
+
<h3>Today Tip 必须跟用户回答发生关系</h3>
|
| 701 |
+
<p>用户回答追问后,结果应该明显变得更具体。本轮让“先写邮件第一句话”这种回答进入可见建议。</p>
|
| 702 |
+
</li>
|
| 703 |
+
<li>
|
| 704 |
+
<h3>真实点击比静态测试更能暴露问题</h3>
|
| 705 |
+
<p><code>type str doesn't define __round__ method</code> 是 Chrome 真实点击才稳定暴露的问题。最终修法不是继续包补丁,而是绕开 event-level JS 对 Gradio inputs 的干扰。</p>
|
| 706 |
+
</li>
|
| 707 |
+
<li>
|
| 708 |
+
<h3>Space PR 和 live Space 要分开看</h3>
|
| 709 |
+
<p>GitHub 分支已推,HF Space PR 已创建,但 live Space main 没有被本轮修改。后续如果合并 PR,需要再做 live <code>/config</code> 和浏览器 smoke。</p>
|
| 710 |
+
</li>
|
| 711 |
+
</ul>
|
| 712 |
+
</section>
|
| 713 |
+
</main>
|
| 714 |
+
|
| 715 |
+
<footer>
|
| 716 |
+
Generated for Dream QA local fix review on 2026-06-12. This file is static and self-contained.
|
| 717 |
+
</footer>
|
| 718 |
+
</div>
|
| 719 |
+
</body>
|
| 720 |
+
</html>
|
docs/smoke/2026-06-12-morning-question-desk-smoke.md
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Morning Question Desk Smoke - 2026-06-12
|
| 2 |
+
|
| 3 |
+
Goal: verify the Dream QA shell now behaves like `The Morning Question Desk` instead of a generic dream interpreter or legacy customs form.
|
| 4 |
+
|
| 5 |
+
## Static Gates
|
| 6 |
+
|
| 7 |
+
```bash
|
| 8 |
+
.venv/bin/python -m pytest -q
|
| 9 |
+
.venv/bin/python scripts/evaluate_today_tip_quality.py
|
| 10 |
+
.venv/bin/python scripts/evaluate_demo_quality.py
|
| 11 |
+
```
|
| 12 |
+
|
| 13 |
+
Results:
|
| 14 |
+
|
| 15 |
+
- `152 passed`
|
| 16 |
+
- Today Tip quality eval: `11/11`, `passes: true`
|
| 17 |
+
- Demo quality eval: `10/10`, `passes: true`
|
| 18 |
+
|
| 19 |
+
## Chrome Smoke
|
| 20 |
+
|
| 21 |
+
Local server:
|
| 22 |
+
|
| 23 |
+
```bash
|
| 24 |
+
GRADIO_SERVER_PORT=7864 .venv/bin/python app.py
|
| 25 |
+
```
|
| 26 |
+
|
| 27 |
+
Chrome observations on `http://127.0.0.1:7864`:
|
| 28 |
+
|
| 29 |
+
- Hero shows `Dream QA / The Morning Question Desk`.
|
| 30 |
+
- Main question shows `What did the dream leave you asking?`.
|
| 31 |
+
- Stepper is `Record -> One Question -> Today Tip`.
|
| 32 |
+
- Three real demo chips are visible: `elevator`, `floor 14`, `melting buttons`.
|
| 33 |
+
- Clicking `melting buttons` fills the dream note with the floor-14 overdue-email demo.
|
| 34 |
+
- Completing the demo produces a `Morning Ticket`.
|
| 35 |
+
- The ticket renders anchor chips: `elevator`, `melted button`, `floor 14`.
|
| 36 |
+
- Ticket sections include `Today Tip`, `Tiny 5-minute action`, `Supporting reflection`, and collapsed `How this was made small`.
|
| 37 |
+
- Visible page text did not include legacy `permit`, `contraband`, or `sealed pact`.
|
| 38 |
+
|
| 39 |
+
Known note: Chrome console still reports a Gradio warning, `Too many arguments provided for the endpoint`, after the chip click. The chip output, textarea state, and downstream ticket flow work correctly, and `/config` shows each chip event has one language input and two outputs.
|
docs/smoke/2026-06-13-morning-question-desk-anchor-chips-smoke.md
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Morning Question Desk Anchor Chips Smoke - 2026-06-13
|
| 2 |
+
|
| 3 |
+
Goal: verify the Dream QA 90-second judge path shows visible dream anchors before the single follow-up question, then still produces a grounded Morning Ticket.
|
| 4 |
+
|
| 5 |
+
## Static and automated gates
|
| 6 |
+
|
| 7 |
+
- Pull check: `git pull --ff-only` -> `Already up to date.`
|
| 8 |
+
- Pytest: `.venv/bin/python -m pytest -q` -> `154 passed, 6 warnings`
|
| 9 |
+
- Today Tip quality eval: `.venv/bin/python scripts/evaluate_today_tip_quality.py` -> `11/11`, `passes: true`
|
| 10 |
+
- Demo quality eval: `.venv/bin/python scripts/evaluate_demo_quality.py` -> `10/10`, `passes: true`
|
| 11 |
+
|
| 12 |
+
## Local runtime
|
| 13 |
+
|
| 14 |
+
- Command: `GRADIO_SERVER_PORT=7864 .venv/bin/python app.py`
|
| 15 |
+
- URL: `http://127.0.0.1:7864`
|
| 16 |
+
- Default runtime config still reports Modal text / vision / ASR endpoints configured.
|
| 17 |
+
|
| 18 |
+
## Chrome path
|
| 19 |
+
|
| 20 |
+
1. Opened the local app in Chrome.
|
| 21 |
+
2. Verified hero copy:
|
| 22 |
+
- `Dream QA / The Morning Question Desk`
|
| 23 |
+
- `What did the dream leave you asking?`
|
| 24 |
+
- `Record -> One Question -> Today Tip`
|
| 25 |
+
3. Clicked demo chip `melting buttons`.
|
| 26 |
+
4. Verified the dream note contains `floor 14` and `overdue email`.
|
| 27 |
+
5. Clicked `Ask one question`.
|
| 28 |
+
6. Verified One Question stage shows visible anchor chips before the desk question:
|
| 29 |
+
- `elevator`
|
| 30 |
+
- `floor 14`
|
| 31 |
+
- `melted button`
|
| 32 |
+
7. Answered: `It reminds me of an overdue email. I only need the first sentence.`
|
| 33 |
+
8. Verified the final result:
|
| 34 |
+
- Shows `Morning Ticket`
|
| 35 |
+
- Shows `Today Tip`
|
| 36 |
+
- Shows `Because your dream kept returning to`
|
| 37 |
+
- Shows `Tiny 5-minute action`
|
| 38 |
+
- Shows collapsed `How this was made small`
|
| 39 |
+
- Mentions `overdue email`
|
| 40 |
+
- Mentions `first sentence`
|
| 41 |
+
- Ticket anchors render as `elevator`, `melted button`, `floor 14`
|
| 42 |
+
- Visible page text does not include legacy `permit`, `contraband`, or `sealed pact`
|
| 43 |
+
|
| 44 |
+
## Product note
|
| 45 |
+
|
| 46 |
+
The earlier One Question stage had enough textual grounding, but the anchor strip was not visible because the ask-state view did not pass `qa_state.dream_anchors` forward. The ask-state view now carries those anchors, and the English anchor extractor avoids the malformed `for floor` chip on the elevator demo.
|
docs/spec.md
CHANGED
|
@@ -4,7 +4,7 @@ Last updated: 2026-06-08
|
|
| 4 |
|
| 5 |
## 1. One-Line Concept
|
| 6 |
|
| 7 |
-
Dream QA helps users unpack last night's dream step by step: they record a dream with text, image, or voice; the system asks one grounded follow-up question; then it gives a non-diagnostic, non-fatalistic
|
| 8 |
|
| 9 |
The public hackathon demo is English-first because the event is international. Chinese remains available through the in-app `English / 中文` toggle.
|
| 10 |
|
|
@@ -168,7 +168,7 @@ Responsibilities:
|
|
| 168 |
8. User answers, skips, adds detail, or asks for another angle.
|
| 169 |
9. MiniCPM5-1B drafts a grounded interpretation.
|
| 170 |
10. MiniCPM5-1B generates a final `TodayTipCard`.
|
| 171 |
-
11. Gradio renders a mobile-readable
|
| 172 |
|
| 173 |
## 9. Output Contract
|
| 174 |
|
|
@@ -177,7 +177,7 @@ The final result must include:
|
|
| 177 |
- Dream summary.
|
| 178 |
- Main question.
|
| 179 |
- One primary today tip.
|
| 180 |
-
- Optional tiny action.
|
| 181 |
- Supporting grounded interpretation.
|
| 182 |
- Optional caring note.
|
| 183 |
- Safety note if the input indicates severe distress.
|
|
@@ -232,7 +232,7 @@ Content:
|
|
| 232 |
Content:
|
| 233 |
|
| 234 |
- One primary today tip.
|
| 235 |
-
- Optional tiny action.
|
| 236 |
- Optional caring note.
|
| 237 |
|
| 238 |
### Screen 0: Language
|
|
|
|
| 4 |
|
| 5 |
## 1. One-Line Concept
|
| 6 |
|
| 7 |
+
Dream QA helps users unpack last night's dream step by step: they record a dream with text, image, or voice; the system asks one grounded follow-up question; then it gives a non-diagnostic, non-fatalistic Morning Ticket with one gentle Today Tip first.
|
| 8 |
|
| 9 |
The public hackathon demo is English-first because the event is international. Chinese remains available through the in-app `English / 中文` toggle.
|
| 10 |
|
|
|
|
| 168 |
8. User answers, skips, adds detail, or asks for another angle.
|
| 169 |
9. MiniCPM5-1B drafts a grounded interpretation.
|
| 170 |
10. MiniCPM5-1B generates a final `TodayTipCard`.
|
| 171 |
+
11. Gradio renders a mobile-readable Morning Ticket and plain text.
|
| 172 |
|
| 173 |
## 9. Output Contract
|
| 174 |
|
|
|
|
| 177 |
- Dream summary.
|
| 178 |
- Main question.
|
| 179 |
- One primary today tip.
|
| 180 |
+
- Optional tiny 5-minute action.
|
| 181 |
- Supporting grounded interpretation.
|
| 182 |
- Optional caring note.
|
| 183 |
- Safety note if the input indicates severe distress.
|
|
|
|
| 232 |
Content:
|
| 233 |
|
| 234 |
- One primary today tip.
|
| 235 |
+
- Optional tiny 5-minute action.
|
| 236 |
- Optional caring note.
|
| 237 |
|
| 238 |
### Screen 0: Language
|
dream_customs/app_logic.py
CHANGED
|
@@ -257,7 +257,7 @@ def _today_tip_plain_text(card) -> str:
|
|
| 257 |
f"Today Tip: {card.today_tip}",
|
| 258 |
]
|
| 259 |
if card.tiny_action:
|
| 260 |
-
lines.append(f"
|
| 261 |
if card.caring_note:
|
| 262 |
lines.append(f"Caring note: {card.caring_note}")
|
| 263 |
if card.safety_note:
|
|
|
|
| 257 |
f"Today Tip: {card.today_tip}",
|
| 258 |
]
|
| 259 |
if card.tiny_action:
|
| 260 |
+
lines.append(f"Tiny 5-minute action: {card.tiny_action}")
|
| 261 |
if card.caring_note:
|
| 262 |
lines.append(f"Caring note: {card.caring_note}")
|
| 263 |
if card.safety_note:
|
dream_customs/pipeline.py
CHANGED
|
@@ -87,6 +87,7 @@ _ANCHOR_STOPWORDS = {
|
|
| 87 |
"every",
|
| 88 |
"feeling",
|
| 89 |
"fragment",
|
|
|
|
| 90 |
"from",
|
| 91 |
"full",
|
| 92 |
"into",
|
|
@@ -485,6 +486,14 @@ def _extract_dream_anchors(intake: DreamIntake) -> List[str]:
|
|
| 485 |
candidates: List[str] = []
|
| 486 |
visual_candidates = _visual_anchor_candidates(intake)
|
| 487 |
candidates.extend(visual_candidates[:3])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 488 |
for marker in _ZH_ANCHOR_MARKERS:
|
| 489 |
if marker in raw_text:
|
| 490 |
candidates.append(marker)
|
|
|
|
| 87 |
"every",
|
| 88 |
"feeling",
|
| 89 |
"fragment",
|
| 90 |
+
"for",
|
| 91 |
"from",
|
| 92 |
"full",
|
| 93 |
"into",
|
|
|
|
| 486 |
candidates: List[str] = []
|
| 487 |
visual_candidates = _visual_anchor_candidates(intake)
|
| 488 |
candidates.extend(visual_candidates[:3])
|
| 489 |
+
if "elevator" in text:
|
| 490 |
+
candidates.append("elevator")
|
| 491 |
+
if re.search(r"\bfloor\s*14\b|\b14\b", text):
|
| 492 |
+
candidates.append("floor 14")
|
| 493 |
+
if "button" in text and re.search(r"\b(melt|melted|melting|wax)\b", text):
|
| 494 |
+
candidates.append("melted button")
|
| 495 |
+
if "overdue email" in text:
|
| 496 |
+
candidates.append("overdue email")
|
| 497 |
for marker in _ZH_ANCHOR_MARKERS:
|
| 498 |
if marker in raw_text:
|
| 499 |
candidates.append(marker)
|
dream_customs/render.py
CHANGED
|
@@ -25,32 +25,42 @@ def _phase_label(phase: str) -> str:
|
|
| 25 |
|
| 26 |
def render_today_tip_card(card: TodayTipCard, language: str = "en") -> str:
|
| 27 |
is_zh = language == "zh"
|
| 28 |
-
|
|
|
|
|
|
|
| 29 |
questions = "".join(f"<li>{escape(question)}</li>" for question in card.followup_questions)
|
| 30 |
answers = "".join(f"<li>{escape(answer)}</li>" for answer in card.user_answers)
|
| 31 |
labels = {
|
| 32 |
-
"page": "
|
| 33 |
-
"
|
|
|
|
| 34 |
if is_zh
|
| 35 |
-
else "
|
| 36 |
"interpretation": "补充解读" if is_zh else "Supporting reflection",
|
| 37 |
-
"today_tip": "今
|
| 38 |
-
"tiny_action": "
|
| 39 |
-
"anchors": "
|
|
|
|
| 40 |
"history": "追问记录" if is_zh else "Question record",
|
| 41 |
"safety_default": "这不是诊断,只是一个温和的今日参考。"
|
| 42 |
if is_zh
|
| 43 |
else "A gentle reflection for today, not a diagnosis or prophecy.",
|
| 44 |
"safety_prefix": "这不是诊断。" if is_zh else "This is not a diagnosis. ",
|
| 45 |
"highlight": "梦境细节" if is_zh else "dream detail",
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 46 |
}
|
| 47 |
tiny_action = (
|
| 48 |
-
f"<section class='dqa-
|
| 49 |
if card.tiny_action
|
| 50 |
else ""
|
| 51 |
)
|
| 52 |
caring_note = (
|
| 53 |
-
f"<div class='dqa-care-note'>
|
| 54 |
if card.caring_note
|
| 55 |
else ""
|
| 56 |
)
|
|
@@ -68,28 +78,27 @@ def render_today_tip_card(card: TodayTipCard, language: str = "en") -> str:
|
|
| 68 |
<section class="dqa-tip-page" aria-label="{labels['page']}">
|
| 69 |
<div class="dqa-tip-hero">
|
| 70 |
<div>
|
| 71 |
-
<div class="dqa-sun">☼</div>
|
| 72 |
<h2>{labels['page']}</h2>
|
| 73 |
<p>{labels['thanks']}</p>
|
| 74 |
</div>
|
| 75 |
</div>
|
| 76 |
-
<
|
| 77 |
-
|
| 78 |
-
<div>
|
|
|
|
| 79 |
<h3>{labels['today_tip']}</h3>
|
| 80 |
<p>{escape(card.today_tip)}</p>
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
{tiny_action}
|
| 85 |
-
<article class="dqa-result-card is-interpretation">
|
| 86 |
-
<div class="dqa-card-icon">☘</div>
|
| 87 |
-
<div>
|
| 88 |
<h3>{labels['interpretation']}</h3>
|
| 89 |
<p>{escape(card.interpretation)}</p>
|
| 90 |
-
|
| 91 |
-
</div>
|
| 92 |
</article>
|
|
|
|
|
|
|
|
|
|
|
|
|
| 93 |
{qa_history}
|
| 94 |
{safety}
|
| 95 |
{caring_note}
|
|
|
|
| 25 |
|
| 26 |
def render_today_tip_card(card: TodayTipCard, language: str = "en") -> str:
|
| 27 |
is_zh = language == "zh"
|
| 28 |
+
anchor_items = [anchor for anchor in card.dream_anchors[:3] if anchor.strip()]
|
| 29 |
+
anchors = " ".join(escape(anchor) for anchor in anchor_items)
|
| 30 |
+
anchor_chips = "".join(f"<span>{escape(anchor)}</span>" for anchor in anchor_items)
|
| 31 |
questions = "".join(f"<li>{escape(question)}</li>" for question in card.followup_questions)
|
| 32 |
answers = "".join(f"<li>{escape(answer)}</li>" for answer in card.user_answers)
|
| 33 |
labels = {
|
| 34 |
+
"page": "清晨小票" if is_zh else "Morning Ticket",
|
| 35 |
+
"ticket": "今日小 Tips" if is_zh else "Today Tip",
|
| 36 |
+
"thanks": "这不是诊断,也不是预言;它只把梦境锚点整理成今天可以带走的一张小纸条。"
|
| 37 |
if is_zh
|
| 38 |
+
else "Not a diagnosis, not a prophecy. Just one small note for today, grounded in the dream.",
|
| 39 |
"interpretation": "补充解读" if is_zh else "Supporting reflection",
|
| 40 |
+
"today_tip": "今日小 Tips" if is_zh else "Today Tip",
|
| 41 |
+
"tiny_action": "5 分钟小行动" if is_zh else "Tiny 5-minute action",
|
| 42 |
+
"anchors": "梦境锚点" if is_zh else "Dream anchors",
|
| 43 |
+
"because": "因为这个梦一直回到:" if is_zh else "Because your dream kept returning to:",
|
| 44 |
"history": "追问记录" if is_zh else "Question record",
|
| 45 |
"safety_default": "这不是诊断,只是一个温和的今日参考。"
|
| 46 |
if is_zh
|
| 47 |
else "A gentle reflection for today, not a diagnosis or prophecy.",
|
| 48 |
"safety_prefix": "这不是诊断。" if is_zh else "This is not a diagnosis. ",
|
| 49 |
"highlight": "梦境细节" if is_zh else "dream detail",
|
| 50 |
+
"small_models": "小模型如何参与" if is_zh else "How this was made small",
|
| 51 |
+
"small_models_body": (
|
| 52 |
+
"MiniCPM-V 负责读取草图、便签和图片线索;MiniCPM5-1B 负责追问、解读草稿和今日小 Tips。"
|
| 53 |
+
if is_zh
|
| 54 |
+
else "MiniCPM-V reads sketches, notes, and image clues; MiniCPM5-1B writes the follow-up question, reflection, and ticket."
|
| 55 |
+
),
|
| 56 |
}
|
| 57 |
tiny_action = (
|
| 58 |
+
f"<section class='dqa-ticket-row'><h3>{labels['tiny_action']}</h3><p>{escape(card.tiny_action)}</p></section>"
|
| 59 |
if card.tiny_action
|
| 60 |
else ""
|
| 61 |
)
|
| 62 |
caring_note = (
|
| 63 |
+
f"<div class='dqa-care-note'><strong>{'关心一句' if is_zh else 'Care note'}</strong>{escape(card.caring_note)}</div>"
|
| 64 |
if card.caring_note
|
| 65 |
else ""
|
| 66 |
)
|
|
|
|
| 78 |
<section class="dqa-tip-page" aria-label="{labels['page']}">
|
| 79 |
<div class="dqa-tip-hero">
|
| 80 |
<div>
|
|
|
|
| 81 |
<h2>{labels['page']}</h2>
|
| 82 |
<p>{labels['thanks']}</p>
|
| 83 |
</div>
|
| 84 |
</div>
|
| 85 |
+
<div class="dqa-anchor-chips" aria-label="{labels['anchors']}">{anchor_chips}</div>
|
| 86 |
+
<article class="dqa-morning-ticket">
|
| 87 |
+
<div class="dqa-ticket-topline"><span>{labels['because']}</span><strong>{anchors or labels['highlight']}</strong></div>
|
| 88 |
+
<section class="dqa-ticket-row is-primary-tip">
|
| 89 |
<h3>{labels['today_tip']}</h3>
|
| 90 |
<p>{escape(card.today_tip)}</p>
|
| 91 |
+
</section>
|
| 92 |
+
{tiny_action}
|
| 93 |
+
<section class="dqa-ticket-row is-interpretation">
|
|
|
|
|
|
|
|
|
|
|
|
|
| 94 |
<h3>{labels['interpretation']}</h3>
|
| 95 |
<p>{escape(card.interpretation)}</p>
|
| 96 |
+
</section>
|
|
|
|
| 97 |
</article>
|
| 98 |
+
<details class="dqa-small-model-note">
|
| 99 |
+
<summary>{labels['small_models']}</summary>
|
| 100 |
+
<p>{labels['small_models_body']}</p>
|
| 101 |
+
</details>
|
| 102 |
{qa_history}
|
| 103 |
{safety}
|
| 104 |
{caring_note}
|
dream_customs/ui/actions.py
CHANGED
|
@@ -86,7 +86,7 @@ def _card_plain_text(card: TodayTipCard, language: str) -> str:
|
|
| 86 |
text += f"\n\n结构化结果 JSON:\n{structured}"
|
| 87 |
return text
|
| 88 |
lines = [
|
| 89 |
-
"
|
| 90 |
f"Dream summary: {card.dream_summary}",
|
| 91 |
f"Question: {card.main_question}",
|
| 92 |
f"Dream anchors: {', '.join(card.dream_anchors)}",
|
|
@@ -96,14 +96,14 @@ def _card_plain_text(card: TodayTipCard, language: str) -> str:
|
|
| 96 |
f"Today Tip: {card.today_tip}",
|
| 97 |
]
|
| 98 |
if card.tiny_action:
|
| 99 |
-
lines.append(f"
|
| 100 |
if card.caring_note:
|
| 101 |
lines.append(f"Caring note: {card.caring_note}")
|
| 102 |
if card.safety_note:
|
| 103 |
lines.append(f"Safety note: {card.safety_note}")
|
| 104 |
if card.followup_questions and card.user_answers:
|
| 105 |
lines.append("Reasoning trail: Your follow-up answer shaped the interpretation and Today Tip.")
|
| 106 |
-
lines.append("
|
| 107 |
lines.extend(["", "Structured result JSON:", structured])
|
| 108 |
return "\n".join(lines)
|
| 109 |
|
|
@@ -151,9 +151,9 @@ def _view_payload(
|
|
| 151 |
"card_html": _render_today_pass(card, language) if card else "",
|
| 152 |
"error": error,
|
| 153 |
"notice": _notice_for_status(status, error, language),
|
| 154 |
-
"dream_summary":
|
| 155 |
-
"main_question":
|
| 156 |
-
"dream_anchors":
|
| 157 |
"followup_questions": list(session.question_history),
|
| 158 |
"user_answers": list(session.answer_history),
|
| 159 |
"interpretation": "",
|
|
|
|
| 86 |
text += f"\n\n结构化结果 JSON:\n{structured}"
|
| 87 |
return text
|
| 88 |
lines = [
|
| 89 |
+
"Morning Ticket",
|
| 90 |
f"Dream summary: {card.dream_summary}",
|
| 91 |
f"Question: {card.main_question}",
|
| 92 |
f"Dream anchors: {', '.join(card.dream_anchors)}",
|
|
|
|
| 96 |
f"Today Tip: {card.today_tip}",
|
| 97 |
]
|
| 98 |
if card.tiny_action:
|
| 99 |
+
lines.append(f"Tiny 5-minute action: {card.tiny_action}")
|
| 100 |
if card.caring_note:
|
| 101 |
lines.append(f"Caring note: {card.caring_note}")
|
| 102 |
if card.safety_note:
|
| 103 |
lines.append(f"Safety note: {card.safety_note}")
|
| 104 |
if card.followup_questions and card.user_answers:
|
| 105 |
lines.append("Reasoning trail: Your follow-up answer shaped the interpretation and Today Tip.")
|
| 106 |
+
lines.append("Small-model note: text via MiniCPM5-1B route; visual clues via MiniCPM-V-4.6 route.")
|
| 107 |
lines.extend(["", "Structured result JSON:", structured])
|
| 108 |
return "\n".join(lines)
|
| 109 |
|
|
|
|
| 151 |
"card_html": _render_today_pass(card, language) if card else "",
|
| 152 |
"error": error,
|
| 153 |
"notice": _notice_for_status(status, error, language),
|
| 154 |
+
"dream_summary": session.qa_state.dream_summary,
|
| 155 |
+
"main_question": session.qa_state.main_question,
|
| 156 |
+
"dream_anchors": list(session.qa_state.dream_anchors),
|
| 157 |
"followup_questions": list(session.question_history),
|
| 158 |
"user_answers": list(session.answer_history),
|
| 159 |
"interpretation": "",
|
dream_customs/ui/app.py
CHANGED
|
@@ -42,6 +42,7 @@ from dream_customs.ui.copy import (
|
|
| 42 |
default_mood_for,
|
| 43 |
mood_options_for,
|
| 44 |
normalize_language,
|
|
|
|
| 45 |
EXAMPLE_DREAMS,
|
| 46 |
EXAMPLE_MOODS,
|
| 47 |
)
|
|
@@ -427,6 +428,14 @@ def _notice_html(view: dict) -> str:
|
|
| 427 |
def _question_markdown(view: dict, language: str = DEFAULT_LANGUAGE) -> str:
|
| 428 |
copy = copy_for(language)
|
| 429 |
question = escape(view.get("question") or "")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 430 |
optional_question = (
|
| 431 |
f"<p class='dc-question-original'><span>{copy['question_speaker']}</span>{question}</p>"
|
| 432 |
if question
|
|
@@ -437,6 +446,7 @@ def _question_markdown(view: dict, language: str = DEFAULT_LANGUAGE) -> str:
|
|
| 437 |
<span class="dc-question-kicker">{copy['question_kicker']}</span>
|
| 438 |
<h2>{copy['question_title']}</h2>
|
| 439 |
<p>{copy['question_body']}</p>
|
|
|
|
| 440 |
{optional_question}
|
| 441 |
<p class="dc-question-note">{copy['question_note']}</p>
|
| 442 |
</div>
|
|
@@ -650,7 +660,7 @@ def _agent_dream_qa(dream_text: str, mood: str = "", answer: str = "", language:
|
|
| 650 |
|
| 651 |
|
| 652 |
def _active_step_for_status(status: str) -> int:
|
| 653 |
-
return {"record": 1, "error": 1, "ask": 2, "drafting":
|
| 654 |
|
| 655 |
|
| 656 |
def _hero_html(language: str = DEFAULT_LANGUAGE, status: str = "record") -> str:
|
|
@@ -679,12 +689,14 @@ def _hero_html(language: str = DEFAULT_LANGUAGE, status: str = "record") -> str:
|
|
| 679 |
<div class="dc-menu-mark" aria-hidden="true"><span></span><span></span><span></span></div>
|
| 680 |
<div class="dc-brand-lockup">
|
| 681 |
<div>
|
| 682 |
-
<
|
|
|
|
| 683 |
<p class="dc-brand-subtitle">{copy['subtitle']}</p>
|
| 684 |
</div>
|
| 685 |
</div>
|
| 686 |
-
<div class="dc-sun-mark" aria-hidden="true">
|
| 687 |
</div>
|
|
|
|
| 688 |
<div class="dc-stepper" aria-label="Dream QA steps">
|
| 689 |
{''.join(step_html)}
|
| 690 |
</div>
|
|
@@ -701,6 +713,15 @@ def _section_title_html(number: int, text: str) -> str:
|
|
| 701 |
""".strip()
|
| 702 |
|
| 703 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 704 |
def _mic_html(language: str = DEFAULT_LANGUAGE) -> str:
|
| 705 |
copy = copy_for(language)
|
| 706 |
return f"""
|
|
@@ -771,6 +792,26 @@ def _side_stamp_html(language: str = DEFAULT_LANGUAGE) -> str:
|
|
| 771 |
""".strip()
|
| 772 |
|
| 773 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 774 |
def _dev_help_html(language: str = DEFAULT_LANGUAGE) -> str:
|
| 775 |
return f"""
|
| 776 |
<div class="dc-dev-help">
|
|
@@ -834,13 +875,29 @@ def build_demo() -> gr.Blocks:
|
|
| 834 |
)
|
| 835 |
_make_media_api_info_client_safe(audio_input)
|
| 836 |
field_tip_html = gr.HTML(_field_tip_html(DEFAULT_LANGUAGE))
|
|
|
|
| 837 |
with gr.Row(elem_classes=["dc-submit-row"]):
|
| 838 |
-
example_button = gr.Button(initial_copy["example_button"], variant="secondary")
|
| 839 |
submit_button = gr.Button(
|
| 840 |
initial_copy["submit_button"],
|
| 841 |
variant="primary",
|
| 842 |
elem_classes=["dc-submit-button"],
|
| 843 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 844 |
processing_html = gr.HTML(_processing_html(DEFAULT_LANGUAGE))
|
| 845 |
|
| 846 |
with gr.Group(visible=False, elem_classes=["dc-stage", "dc-question"]) as question_group:
|
|
@@ -1092,11 +1149,27 @@ def build_demo() -> gr.Blocks:
|
|
| 1092 |
show_api=False,
|
| 1093 |
)
|
| 1094 |
|
| 1095 |
-
|
| 1096 |
-
|
| 1097 |
-
|
| 1098 |
-
|
| 1099 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1100 |
|
| 1101 |
def _language_ui(selected_language):
|
| 1102 |
selected_language = normalize_language(selected_language)
|
|
@@ -1111,7 +1184,10 @@ def build_demo() -> gr.Blocks:
|
|
| 1111 |
_attachment_html(selected_language),
|
| 1112 |
gr.update(label=copy["image_label"]),
|
| 1113 |
_field_tip_html(selected_language),
|
|
|
|
| 1114 |
gr.update(value=copy["example_button"]),
|
|
|
|
|
|
|
| 1115 |
gr.update(value=copy["submit_button"]),
|
| 1116 |
_processing_html(selected_language),
|
| 1117 |
_question_markdown({"question": ""}, selected_language),
|
|
@@ -1145,7 +1221,10 @@ def build_demo() -> gr.Blocks:
|
|
| 1145 |
attachment_html,
|
| 1146 |
image_input,
|
| 1147 |
field_tip_html,
|
|
|
|
| 1148 |
example_button,
|
|
|
|
|
|
|
| 1149 |
submit_button,
|
| 1150 |
processing_html,
|
| 1151 |
question_markdown,
|
|
|
|
| 42 |
default_mood_for,
|
| 43 |
mood_options_for,
|
| 44 |
normalize_language,
|
| 45 |
+
EXAMPLE_CHIPS,
|
| 46 |
EXAMPLE_DREAMS,
|
| 47 |
EXAMPLE_MOODS,
|
| 48 |
)
|
|
|
|
| 428 |
def _question_markdown(view: dict, language: str = DEFAULT_LANGUAGE) -> str:
|
| 429 |
copy = copy_for(language)
|
| 430 |
question = escape(view.get("question") or "")
|
| 431 |
+
anchor_items = [str(anchor).strip() for anchor in view.get("dream_anchors", [])[:3] if str(anchor).strip()]
|
| 432 |
+
anchor_label = "梦境锚点" if language == "zh" else "Dream anchors"
|
| 433 |
+
anchor_chips = "".join(f"<span>{escape(anchor)}</span>" for anchor in anchor_items)
|
| 434 |
+
anchor_strip = (
|
| 435 |
+
f"<div class='dc-question-anchor-strip' aria-label='{anchor_label}'>{anchor_chips}</div>"
|
| 436 |
+
if anchor_chips
|
| 437 |
+
else ""
|
| 438 |
+
)
|
| 439 |
optional_question = (
|
| 440 |
f"<p class='dc-question-original'><span>{copy['question_speaker']}</span>{question}</p>"
|
| 441 |
if question
|
|
|
|
| 446 |
<span class="dc-question-kicker">{copy['question_kicker']}</span>
|
| 447 |
<h2>{copy['question_title']}</h2>
|
| 448 |
<p>{copy['question_body']}</p>
|
| 449 |
+
{anchor_strip}
|
| 450 |
{optional_question}
|
| 451 |
<p class="dc-question-note">{copy['question_note']}</p>
|
| 452 |
</div>
|
|
|
|
| 660 |
|
| 661 |
|
| 662 |
def _active_step_for_status(status: str) -> int:
|
| 663 |
+
return {"record": 1, "error": 1, "ask": 2, "drafting": 2, "tip": 3}.get(status, 1)
|
| 664 |
|
| 665 |
|
| 666 |
def _hero_html(language: str = DEFAULT_LANGUAGE, status: str = "record") -> str:
|
|
|
|
| 689 |
<div class="dc-menu-mark" aria-hidden="true"><span></span><span></span><span></span></div>
|
| 690 |
<div class="dc-brand-lockup">
|
| 691 |
<div>
|
| 692 |
+
<p class="dc-hero-kicker">{copy['hero_kicker']}</p>
|
| 693 |
+
<h1>{copy['hero_title']}</h1>
|
| 694 |
<p class="dc-brand-subtitle">{copy['subtitle']}</p>
|
| 695 |
</div>
|
| 696 |
</div>
|
| 697 |
+
<div class="dc-sun-mark" aria-hidden="true"></div>
|
| 698 |
</div>
|
| 699 |
+
<p class="dc-hero-body">{copy['hero_body']}</p>
|
| 700 |
<div class="dc-stepper" aria-label="Dream QA steps">
|
| 701 |
{''.join(step_html)}
|
| 702 |
</div>
|
|
|
|
| 713 |
""".strip()
|
| 714 |
|
| 715 |
|
| 716 |
+
def _demo_chip_intro_html(language: str = DEFAULT_LANGUAGE) -> str:
|
| 717 |
+
message = (
|
| 718 |
+
"Or tap the 90-second demo trail:"
|
| 719 |
+
if normalize_language(language) == "en"
|
| 720 |
+
else "也可以点一个 90 秒演示线索:"
|
| 721 |
+
)
|
| 722 |
+
return f'<p class="dc-demo-chip-intro">{escape(message)}</p>'
|
| 723 |
+
|
| 724 |
+
|
| 725 |
def _mic_html(language: str = DEFAULT_LANGUAGE) -> str:
|
| 726 |
copy = copy_for(language)
|
| 727 |
return f"""
|
|
|
|
| 792 |
""".strip()
|
| 793 |
|
| 794 |
|
| 795 |
+
def _example_chip(selected_language: str, chip_key: str):
|
| 796 |
+
selected_language = normalize_language(selected_language)
|
| 797 |
+
chip = EXAMPLE_CHIPS[selected_language].get(chip_key)
|
| 798 |
+
if chip:
|
| 799 |
+
return chip
|
| 800 |
+
return EXAMPLE_DREAMS[selected_language], EXAMPLE_MOODS[selected_language]
|
| 801 |
+
|
| 802 |
+
|
| 803 |
+
def _example_elevator(selected_language: str):
|
| 804 |
+
return _example_chip(selected_language, "elevator")
|
| 805 |
+
|
| 806 |
+
|
| 807 |
+
def _example_floor14(selected_language: str):
|
| 808 |
+
return _example_chip(selected_language, "floor14")
|
| 809 |
+
|
| 810 |
+
|
| 811 |
+
def _example_melting(selected_language: str):
|
| 812 |
+
return _example_chip(selected_language, "melting")
|
| 813 |
+
|
| 814 |
+
|
| 815 |
def _dev_help_html(language: str = DEFAULT_LANGUAGE) -> str:
|
| 816 |
return f"""
|
| 817 |
<div class="dc-dev-help">
|
|
|
|
| 875 |
)
|
| 876 |
_make_media_api_info_client_safe(audio_input)
|
| 877 |
field_tip_html = gr.HTML(_field_tip_html(DEFAULT_LANGUAGE))
|
| 878 |
+
demo_chip_intro = gr.HTML(_demo_chip_intro_html(DEFAULT_LANGUAGE))
|
| 879 |
with gr.Row(elem_classes=["dc-submit-row"]):
|
|
|
|
| 880 |
submit_button = gr.Button(
|
| 881 |
initial_copy["submit_button"],
|
| 882 |
variant="primary",
|
| 883 |
elem_classes=["dc-submit-button"],
|
| 884 |
)
|
| 885 |
+
with gr.Row(elem_classes=["dc-demo-chip-row"]):
|
| 886 |
+
example_button = gr.Button(
|
| 887 |
+
initial_copy["example_button"],
|
| 888 |
+
variant="secondary",
|
| 889 |
+
elem_classes=["dc-demo-chip"],
|
| 890 |
+
)
|
| 891 |
+
example_button_2 = gr.Button(
|
| 892 |
+
initial_copy["example_button_2"],
|
| 893 |
+
variant="secondary",
|
| 894 |
+
elem_classes=["dc-demo-chip"],
|
| 895 |
+
)
|
| 896 |
+
example_button_3 = gr.Button(
|
| 897 |
+
initial_copy["example_button_3"],
|
| 898 |
+
variant="secondary",
|
| 899 |
+
elem_classes=["dc-demo-chip"],
|
| 900 |
+
)
|
| 901 |
processing_html = gr.HTML(_processing_html(DEFAULT_LANGUAGE))
|
| 902 |
|
| 903 |
with gr.Group(visible=False, elem_classes=["dc-stage", "dc-question"]) as question_group:
|
|
|
|
| 1149 |
show_api=False,
|
| 1150 |
)
|
| 1151 |
|
| 1152 |
+
example_button.click(
|
| 1153 |
+
_example_elevator,
|
| 1154 |
+
inputs=[language],
|
| 1155 |
+
outputs=[dream_text, mood],
|
| 1156 |
+
api_name=False,
|
| 1157 |
+
show_api=False,
|
| 1158 |
+
)
|
| 1159 |
+
example_button_2.click(
|
| 1160 |
+
_example_floor14,
|
| 1161 |
+
inputs=[language],
|
| 1162 |
+
outputs=[dream_text, mood],
|
| 1163 |
+
api_name=False,
|
| 1164 |
+
show_api=False,
|
| 1165 |
+
)
|
| 1166 |
+
example_button_3.click(
|
| 1167 |
+
_example_melting,
|
| 1168 |
+
inputs=[language],
|
| 1169 |
+
outputs=[dream_text, mood],
|
| 1170 |
+
api_name=False,
|
| 1171 |
+
show_api=False,
|
| 1172 |
+
)
|
| 1173 |
|
| 1174 |
def _language_ui(selected_language):
|
| 1175 |
selected_language = normalize_language(selected_language)
|
|
|
|
| 1184 |
_attachment_html(selected_language),
|
| 1185 |
gr.update(label=copy["image_label"]),
|
| 1186 |
_field_tip_html(selected_language),
|
| 1187 |
+
_demo_chip_intro_html(selected_language),
|
| 1188 |
gr.update(value=copy["example_button"]),
|
| 1189 |
+
gr.update(value=copy["example_button_2"]),
|
| 1190 |
+
gr.update(value=copy["example_button_3"]),
|
| 1191 |
gr.update(value=copy["submit_button"]),
|
| 1192 |
_processing_html(selected_language),
|
| 1193 |
_question_markdown({"question": ""}, selected_language),
|
|
|
|
| 1221 |
attachment_html,
|
| 1222 |
image_input,
|
| 1223 |
field_tip_html,
|
| 1224 |
+
demo_chip_intro,
|
| 1225 |
example_button,
|
| 1226 |
+
example_button_2,
|
| 1227 |
+
example_button_3,
|
| 1228 |
submit_button,
|
| 1229 |
processing_html,
|
| 1230 |
question_markdown,
|
dream_customs/ui/copy.py
CHANGED
|
@@ -8,15 +8,18 @@ LANGUAGE_OPTIONS = [
|
|
| 8 |
APP_COPY = {
|
| 9 |
"en": {
|
| 10 |
"title": "Dream QA",
|
| 11 |
-
"
|
|
|
|
|
|
|
|
|
|
| 12 |
"brand_subtitle": "Dream Customs",
|
| 13 |
-
"steps": ["Record", "Question", "
|
| 14 |
-
"notice_record": "Write
|
| 15 |
-
"notice_ask": "Answer this question, or skip it and
|
| 16 |
-
"notice_tip": "Your
|
| 17 |
"notice_error": "Dream QA needs a dream fragment before it can continue.",
|
| 18 |
"dream_label": "Dream note",
|
| 19 |
-
"dream_placeholder": "Write
|
| 20 |
"mic_idle": "Tap the microphone to dictate",
|
| 21 |
"mic_unsupported": "This browser cannot transcribe voice here. You can still type the dream.",
|
| 22 |
"mic_permission": "Microphone permission was not granted. Allow recording and try again.",
|
|
@@ -25,20 +28,22 @@ APP_COPY = {
|
|
| 25 |
"mic_empty": "No speech detected. Tap again if you want to retry.",
|
| 26 |
"voice_label": "Voice note",
|
| 27 |
"voice_help": "Record or upload a short voice note. It is transcribed by the ASR adapter when you continue.",
|
| 28 |
-
"field_tip": "
|
| 29 |
-
"example_button": "
|
| 30 |
-
"
|
|
|
|
|
|
|
| 31 |
"processing_note": (
|
| 32 |
-
"After submission,
|
| 33 |
),
|
| 34 |
"image_accordion": "+",
|
| 35 |
"image_label": "Image clue",
|
| 36 |
"image_upload": "Upload image",
|
| 37 |
"image_paste": "Paste from Clipboard",
|
| 38 |
"question_kicker": "Question",
|
| 39 |
-
"question_title": "
|
| 40 |
-
"question_body": "Answer in one or two lines, or skip and
|
| 41 |
-
"question_speaker": "
|
| 42 |
"question_note": "This step makes the tip more specific. It is not diagnosis.",
|
| 43 |
"answer_label": "Your answer",
|
| 44 |
"answer_placeholder": "Write one answer, or leave it blank and skip.",
|
|
@@ -51,9 +56,9 @@ APP_COPY = {
|
|
| 51 |
"copy_label": "Copyable result",
|
| 52 |
"side_title": "Waking mood",
|
| 53 |
"mood_label": "Mood",
|
| 54 |
-
"side_stamp_label": "
|
| 55 |
-
"side_stamp_title": "
|
| 56 |
-
"side_stamp_body": "
|
| 57 |
"language_label": "Language",
|
| 58 |
"runtime_help": "Advanced controls for demos and development. Most visitors can leave these unchanged.",
|
| 59 |
"debug_title": "Debug",
|
|
@@ -64,15 +69,18 @@ APP_COPY = {
|
|
| 64 |
},
|
| 65 |
"zh": {
|
| 66 |
"title": "梦境问答台",
|
| 67 |
-
"
|
|
|
|
|
|
|
|
|
|
| 68 |
"brand_subtitle": "Dream Customs",
|
| 69 |
-
"steps": ["记录", "追问", "
|
| 70 |
-
"notice_record": "写一
|
| 71 |
-
"notice_ask": "
|
| 72 |
-
"notice_tip": "
|
| 73 |
"notice_error": "梦境问答台还没有收到片段。",
|
| 74 |
"dream_label": "梦境记录",
|
| 75 |
-
"dream_placeholder": "
|
| 76 |
"mic_idle": "点击麦克风录音",
|
| 77 |
"mic_unsupported": "这个浏览器暂时不能直接转写语音,你仍然可以手动输入梦境。",
|
| 78 |
"mic_permission": "没有获得麦克风权限。允许浏览器录音后可以再试一次。",
|
|
@@ -81,20 +89,22 @@ APP_COPY = {
|
|
| 81 |
"mic_empty": "没有检测到语音。想重试的话,再点一次麦克风。",
|
| 82 |
"voice_label": "语音片段",
|
| 83 |
"voice_help": "可以录音或上传一小段语音。点击继续后,ASR 适配器会先转写它。",
|
| 84 |
-
"field_tip": "
|
| 85 |
-
"example_button": "
|
| 86 |
-
"
|
|
|
|
|
|
|
| 87 |
"processing_note": (
|
| 88 |
-
"提交后,梦境
|
| 89 |
),
|
| 90 |
"image_accordion": "+",
|
| 91 |
"image_label": "图片线索",
|
| 92 |
"image_upload": "上传图片",
|
| 93 |
"image_paste": "从剪贴板粘贴",
|
| 94 |
"question_kicker": "追问",
|
| 95 |
-
"question_title": "
|
| 96 |
-
"question_body": "回答一两句就好;也可以跳过,直接
|
| 97 |
-
"question_speaker": "
|
| 98 |
"question_note": "这个步骤是为了让最终建议更贴近你的梦,不是问诊。",
|
| 99 |
"answer_label": "你的回答",
|
| 100 |
"answer_placeholder": "写一句回答,或留空后选择跳过。",
|
|
@@ -107,9 +117,9 @@ APP_COPY = {
|
|
| 107 |
"copy_label": "可复制结果",
|
| 108 |
"side_title": "醒来后的心情",
|
| 109 |
"mood_label": "心情",
|
| 110 |
-
"side_stamp_label": "
|
| 111 |
-
"side_stamp_title": "
|
| 112 |
-
"side_stamp_body": "
|
| 113 |
"language_label": "语言",
|
| 114 |
"runtime_help": "高级演示与开发控制。普通体验保持默认即可。",
|
| 115 |
"debug_title": "调试",
|
|
@@ -121,12 +131,43 @@ APP_COPY = {
|
|
| 121 |
}
|
| 122 |
|
| 123 |
EXAMPLE_DREAMS = {
|
| 124 |
-
"en": "I dreamed I
|
| 125 |
-
"zh": "我梦到
|
| 126 |
}
|
| 127 |
|
| 128 |
EXAMPLE_MOODS = {"en": "Uneasy", "zh": "焦虑"}
|
| 129 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 130 |
MOOD_OPTIONS_BY_LANGUAGE = {
|
| 131 |
"en": ["Neutral", "Uneasy", "Foggy", "Curious", "Tired", "A little amused"],
|
| 132 |
"zh": ["一般", "焦虑", "迷糊", "好奇", "疲惫", "有点好笑"],
|
|
|
|
| 8 |
APP_COPY = {
|
| 9 |
"en": {
|
| 10 |
"title": "Dream QA",
|
| 11 |
+
"hero_kicker": "Dream QA / The Morning Question Desk",
|
| 12 |
+
"hero_title": "What did the dream leave you asking?",
|
| 13 |
+
"subtitle": "Record the dream, answer one gentle question, and leave with a grounded Morning Ticket.",
|
| 14 |
+
"hero_body": "A small-model dream desk for the first few minutes after waking. It gathers concrete details, asks before interpreting, and writes one note for today.",
|
| 15 |
"brand_subtitle": "Dream Customs",
|
| 16 |
+
"steps": ["Record", "One Question", "Today Tip"],
|
| 17 |
+
"notice_record": "Write one dream fragment, then use a demo chip if you want the 90-second judge path.",
|
| 18 |
+
"notice_ask": "Answer this one question, or skip it and turn the existing clues into a Morning Ticket.",
|
| 19 |
+
"notice_tip": "Your Morning Ticket is ready. Treat it as gentle reflection, not diagnosis or prophecy.",
|
| 20 |
"notice_error": "Dream QA needs a dream fragment before it can continue.",
|
| 21 |
"dream_label": "Dream note",
|
| 22 |
+
"dream_placeholder": "Write the dream while it is still foggy...\nExample: I kept missing an elevator. The button for floor 14 melted like wax.",
|
| 23 |
"mic_idle": "Tap the microphone to dictate",
|
| 24 |
"mic_unsupported": "This browser cannot transcribe voice here. You can still type the dream.",
|
| 25 |
"mic_permission": "Microphone permission was not granted. Allow recording and try again.",
|
|
|
|
| 28 |
"mic_empty": "No speech detected. Tap again if you want to retry.",
|
| 29 |
"voice_label": "Voice note",
|
| 30 |
"voice_help": "Record or upload a short voice note. It is transcribed by the ASR adapter when you continue.",
|
| 31 |
+
"field_tip": "The desk looks for three anchors: a place, an object, and the question the dream left behind.",
|
| 32 |
+
"example_button": "elevator",
|
| 33 |
+
"example_button_2": "floor 14",
|
| 34 |
+
"example_button_3": "melting buttons",
|
| 35 |
+
"submit_button": "Ask one question",
|
| 36 |
"processing_note": (
|
| 37 |
+
"After submission, the desk extracts dream anchors, asks one grounded question, then writes a Morning Ticket with one Today Tip."
|
| 38 |
),
|
| 39 |
"image_accordion": "+",
|
| 40 |
"image_label": "Image clue",
|
| 41 |
"image_upload": "Upload image",
|
| 42 |
"image_paste": "Paste from Clipboard",
|
| 43 |
"question_kicker": "Question",
|
| 44 |
+
"question_title": "One question before the ticket",
|
| 45 |
+
"question_body": "Answer in one or two lines, or skip and let the existing anchors carry the ticket.",
|
| 46 |
+
"question_speaker": "Morning Question Desk",
|
| 47 |
"question_note": "This step makes the tip more specific. It is not diagnosis.",
|
| 48 |
"answer_label": "Your answer",
|
| 49 |
"answer_placeholder": "Write one answer, or leave it blank and skip.",
|
|
|
|
| 56 |
"copy_label": "Copyable result",
|
| 57 |
"side_title": "Waking mood",
|
| 58 |
"mood_label": "Mood",
|
| 59 |
+
"side_stamp_label": "90-second demo",
|
| 60 |
+
"side_stamp_title": "Elevator, floor 14, first sentence",
|
| 61 |
+
"side_stamp_body": "The strongest path ends with a tiny action tied to a real overdue email.",
|
| 62 |
"language_label": "Language",
|
| 63 |
"runtime_help": "Advanced controls for demos and development. Most visitors can leave these unchanged.",
|
| 64 |
"debug_title": "Debug",
|
|
|
|
| 69 |
},
|
| 70 |
"zh": {
|
| 71 |
"title": "梦境问答台",
|
| 72 |
+
"hero_kicker": "梦境问答台 / 清���问讯室",
|
| 73 |
+
"hero_title": "这个梦,醒来后把什么问题留给了你?",
|
| 74 |
+
"subtitle": "记录梦境,回答一个温和追问,最后拿到一张贴着梦境细节的清晨小票。",
|
| 75 |
+
"hero_body": "一个适合刚醒来几分钟使用的小模型问讯台:先捡起具体细节,再问一个好问题,最后只给今天的一张小纸条。",
|
| 76 |
"brand_subtitle": "Dream Customs",
|
| 77 |
+
"steps": ["记录", "一个追问", "清晨小票"],
|
| 78 |
+
"notice_record": "写一个梦境片段;想走 90 秒演示路径,也可以直接点示例 chip。",
|
| 79 |
+
"notice_ask": "回答这一个追问,或跳过,用已有梦境线索生成清晨小票。",
|
| 80 |
+
"notice_tip": "清晨小票已生成。把它当作温和参考,不是诊断或预言。",
|
| 81 |
"notice_error": "梦境问答台还没有收到片段。",
|
| 82 |
"dream_label": "梦境记录",
|
| 83 |
+
"dream_placeholder": "趁梦还带着雾气,先写下来...\n例如:我一直赶不上电梯,14 楼按钮像蜡一样融化。",
|
| 84 |
"mic_idle": "点击麦克风录音",
|
| 85 |
"mic_unsupported": "这个浏览器暂时不能直接转写语音,你仍然可以手动输入梦境。",
|
| 86 |
"mic_permission": "没有获得麦克风权限。允许浏览器录音后可以再试一次。",
|
|
|
|
| 89 |
"mic_empty": "没有检测到语音。想重试的话,再点一次麦克风。",
|
| 90 |
"voice_label": "语音片段",
|
| 91 |
"voice_help": "可以录音或上传一小段语音。点击继续后,ASR 适配器会先转写它。",
|
| 92 |
+
"field_tip": "问讯室会优先寻找三个锚点:地点、物件、以及梦醒后留下的问题。",
|
| 93 |
+
"example_button": "电梯",
|
| 94 |
+
"example_button_2": "14 楼",
|
| 95 |
+
"example_button_3": "融化按钮",
|
| 96 |
+
"submit_button": "问一个问题",
|
| 97 |
"processing_note": (
|
| 98 |
+
"提交后,问讯室会提取梦境锚点,问一个关键问题,再写成一张清晨小票。"
|
| 99 |
),
|
| 100 |
"image_accordion": "+",
|
| 101 |
"image_label": "图片线索",
|
| 102 |
"image_upload": "上传图片",
|
| 103 |
"image_paste": "从剪贴板粘贴",
|
| 104 |
"question_kicker": "追问",
|
| 105 |
+
"question_title": "出票前,只问这一个问题",
|
| 106 |
+
"question_body": "回答一两句就好;也可以跳过,让已有锚点直接生成小票。",
|
| 107 |
+
"question_speaker": "清晨问讯室",
|
| 108 |
"question_note": "这个步骤是为了让最终建议更贴近你的梦,不是问诊。",
|
| 109 |
"answer_label": "你的回答",
|
| 110 |
"answer_placeholder": "写一句回答,或留空后选择跳过。",
|
|
|
|
| 117 |
"copy_label": "可复制结果",
|
| 118 |
"side_title": "醒来后的心情",
|
| 119 |
"mood_label": "心情",
|
| 120 |
+
"side_stamp_label": "90 秒演示",
|
| 121 |
+
"side_stamp_title": "电梯、14 楼、第一句话",
|
| 122 |
+
"side_stamp_body": "最稳的演示路径会把梦境锚点落到一封迟迟没写的邮件。",
|
| 123 |
"language_label": "语言",
|
| 124 |
"runtime_help": "高级演示与开发控制。普通体验保持默认即可。",
|
| 125 |
"debug_title": "调试",
|
|
|
|
| 131 |
}
|
| 132 |
|
| 133 |
EXAMPLE_DREAMS = {
|
| 134 |
+
"en": "I dreamed I kept missing an elevator. The button for floor 14 melted like wax. I woke up anxious because it reminded me of an overdue email.",
|
| 135 |
+
"zh": "我梦到自己一直赶不上电梯,14 楼按钮像蜡一样融化。醒来有点焦虑,因为它让我想到一封迟迟没写的邮件。",
|
| 136 |
}
|
| 137 |
|
| 138 |
EXAMPLE_MOODS = {"en": "Uneasy", "zh": "焦虑"}
|
| 139 |
|
| 140 |
+
EXAMPLE_CHIPS = {
|
| 141 |
+
"en": {
|
| 142 |
+
"elevator": (
|
| 143 |
+
"I dreamed I kept missing an elevator. The doors opened, closed, and left me behind.",
|
| 144 |
+
"Uneasy",
|
| 145 |
+
),
|
| 146 |
+
"floor14": (
|
| 147 |
+
"I dreamed I kept missing an elevator, and the floor number stayed on 14.",
|
| 148 |
+
"Uneasy",
|
| 149 |
+
),
|
| 150 |
+
"melting": (
|
| 151 |
+
"I dreamed I kept missing an elevator. The button for floor 14 melted like wax. I woke up anxious because it reminded me of an overdue email.",
|
| 152 |
+
"Uneasy",
|
| 153 |
+
),
|
| 154 |
+
},
|
| 155 |
+
"zh": {
|
| 156 |
+
"elevator": (
|
| 157 |
+
"我梦到自己一直赶不上电梯,门开了又关,好像总是把我留在外面。",
|
| 158 |
+
"焦虑",
|
| 159 |
+
),
|
| 160 |
+
"floor14": (
|
| 161 |
+
"我梦到自己一直赶不上电梯,楼层数字一直停在 14。",
|
| 162 |
+
"焦虑",
|
| 163 |
+
),
|
| 164 |
+
"melting": (
|
| 165 |
+
"我梦到自己一直赶不上电梯,14 楼按钮像蜡一样融化。醒来有点焦虑,因为它让我想到一封迟迟没写的邮件。",
|
| 166 |
+
"焦虑",
|
| 167 |
+
),
|
| 168 |
+
},
|
| 169 |
+
}
|
| 170 |
+
|
| 171 |
MOOD_OPTIONS_BY_LANGUAGE = {
|
| 172 |
"en": ["Neutral", "Uneasy", "Foggy", "Curious", "Tired", "A little amused"],
|
| 173 |
"zh": ["一般", "焦虑", "迷糊", "好奇", "疲惫", "有点好笑"],
|
dream_customs/ui/styles.py
CHANGED
|
@@ -1199,6 +1199,260 @@ a.built-with[href*="gradio.app"] {
|
|
| 1199 |
}
|
| 1200 |
"""
|
| 1201 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1202 |
CSS += """
|
| 1203 |
:root {
|
| 1204 |
--dqa-mist: #f3f8f9;
|
|
@@ -1469,6 +1723,25 @@ button.secondary {
|
|
| 1469 |
border-left: 4px solid var(--dqa-amber);
|
| 1470 |
}
|
| 1471 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1472 |
.dc-notice {
|
| 1473 |
background: rgba(233, 243, 231, 0.9);
|
| 1474 |
border: 1px solid rgba(79, 138, 88, 0.18);
|
|
|
|
| 1199 |
}
|
| 1200 |
"""
|
| 1201 |
|
| 1202 |
+
CSS += """
|
| 1203 |
+
.dc-hero {
|
| 1204 |
+
align-content: end;
|
| 1205 |
+
display: grid;
|
| 1206 |
+
gap: 18px;
|
| 1207 |
+
min-height: clamp(330px, 40vw, 500px);
|
| 1208 |
+
}
|
| 1209 |
+
|
| 1210 |
+
.dc-hero-kicker {
|
| 1211 |
+
color: var(--dqa-sage-deep);
|
| 1212 |
+
font-size: clamp(0.84rem, 1.4vw, 1rem);
|
| 1213 |
+
font-weight: 760;
|
| 1214 |
+
letter-spacing: 0;
|
| 1215 |
+
margin: 0 0 10px;
|
| 1216 |
+
}
|
| 1217 |
+
|
| 1218 |
+
.dc-hero h1 {
|
| 1219 |
+
max-width: 960px;
|
| 1220 |
+
}
|
| 1221 |
+
|
| 1222 |
+
.dc-brand-subtitle {
|
| 1223 |
+
margin-left: auto;
|
| 1224 |
+
margin-right: auto;
|
| 1225 |
+
max-width: 760px;
|
| 1226 |
+
}
|
| 1227 |
+
|
| 1228 |
+
.dc-hero-body {
|
| 1229 |
+
color: #4f626c;
|
| 1230 |
+
font-size: clamp(1rem, 1.8vw, 1.22rem);
|
| 1231 |
+
line-height: 1.65;
|
| 1232 |
+
margin: 0 auto;
|
| 1233 |
+
max-width: 740px;
|
| 1234 |
+
text-align: center;
|
| 1235 |
+
}
|
| 1236 |
+
|
| 1237 |
+
.dc-sun-mark {
|
| 1238 |
+
aspect-ratio: 1;
|
| 1239 |
+
background:
|
| 1240 |
+
linear-gradient(90deg, transparent 47%, rgba(239, 139, 112, 0.55) 48% 52%, transparent 53%),
|
| 1241 |
+
linear-gradient(0deg, transparent 47%, rgba(239, 139, 112, 0.55) 48% 52%, transparent 53%),
|
| 1242 |
+
radial-gradient(circle, rgba(240, 181, 61, 0.7) 0 34%, transparent 35%);
|
| 1243 |
+
border: 1px solid rgba(239, 139, 112, 0.32);
|
| 1244 |
+
border-radius: 999px;
|
| 1245 |
+
height: 58px;
|
| 1246 |
+
justify-self: end;
|
| 1247 |
+
width: 58px;
|
| 1248 |
+
}
|
| 1249 |
+
|
| 1250 |
+
.dc-demo-chip-intro {
|
| 1251 |
+
color: var(--dqa-muted);
|
| 1252 |
+
font-size: 0.92rem;
|
| 1253 |
+
font-weight: 650;
|
| 1254 |
+
margin: 14px 0 0;
|
| 1255 |
+
}
|
| 1256 |
+
|
| 1257 |
+
.dc-submit-row {
|
| 1258 |
+
align-items: stretch !important;
|
| 1259 |
+
grid-template-columns: minmax(0, 1fr) minmax(0, 1.2fr) !important;
|
| 1260 |
+
}
|
| 1261 |
+
|
| 1262 |
+
.dc-demo-chip-row {
|
| 1263 |
+
align-content: stretch !important;
|
| 1264 |
+
display: grid !important;
|
| 1265 |
+
gap: 10px !important;
|
| 1266 |
+
grid-template-columns: repeat(3, minmax(0, 1fr)) !important;
|
| 1267 |
+
min-width: 0;
|
| 1268 |
+
}
|
| 1269 |
+
|
| 1270 |
+
.dc-demo-chip-row .dc-demo-chip button {
|
| 1271 |
+
background: #ffffff !important;
|
| 1272 |
+
border: 1px solid rgba(79, 138, 88, 0.28) !important;
|
| 1273 |
+
border-radius: 999px !important;
|
| 1274 |
+
box-shadow: none !important;
|
| 1275 |
+
color: var(--dqa-sage-deep) !important;
|
| 1276 |
+
font-size: 0.95rem !important;
|
| 1277 |
+
min-height: 52px !important;
|
| 1278 |
+
padding: 10px 12px !important;
|
| 1279 |
+
transition: background 180ms ease, border-color 180ms ease, transform 180ms ease;
|
| 1280 |
+
}
|
| 1281 |
+
|
| 1282 |
+
.dc-demo-chip-row .dc-demo-chip button:hover,
|
| 1283 |
+
.dc-demo-chip-row .dc-demo-chip button:focus-visible {
|
| 1284 |
+
background: var(--dqa-sage-soft) !important;
|
| 1285 |
+
border-color: rgba(79, 138, 88, 0.52) !important;
|
| 1286 |
+
transform: translateY(-1px);
|
| 1287 |
+
}
|
| 1288 |
+
|
| 1289 |
+
.dqa-tip-page {
|
| 1290 |
+
background: #fffdf8;
|
| 1291 |
+
border: 1px solid var(--dqa-line);
|
| 1292 |
+
box-shadow: 0 24px 60px rgba(45, 56, 42, 0.14);
|
| 1293 |
+
overflow: hidden;
|
| 1294 |
+
}
|
| 1295 |
+
|
| 1296 |
+
.dqa-tip-hero {
|
| 1297 |
+
background:
|
| 1298 |
+
linear-gradient(90deg, rgba(255, 253, 248, 0.94), rgba(255, 253, 248, 0.68)),
|
| 1299 |
+
url("https://images.unsplash.com/photo-1494438639946-1ebd1d20bf85?auto=format&fit=crop&w=1200&q=80") center / cover;
|
| 1300 |
+
min-height: 150px;
|
| 1301 |
+
}
|
| 1302 |
+
|
| 1303 |
+
.dqa-tip-hero h2 {
|
| 1304 |
+
font-size: clamp(2rem, 5vw, 3.4rem);
|
| 1305 |
+
}
|
| 1306 |
+
|
| 1307 |
+
.dqa-anchor-chips {
|
| 1308 |
+
display: flex;
|
| 1309 |
+
flex-wrap: wrap;
|
| 1310 |
+
gap: 10px;
|
| 1311 |
+
margin: 22px 42px 0;
|
| 1312 |
+
}
|
| 1313 |
+
|
| 1314 |
+
.dqa-anchor-chips span {
|
| 1315 |
+
background: var(--dqa-sage-soft);
|
| 1316 |
+
border: 1px solid rgba(79, 138, 88, 0.26);
|
| 1317 |
+
border-radius: 999px;
|
| 1318 |
+
color: var(--dqa-sage-deep);
|
| 1319 |
+
font-size: 0.92rem;
|
| 1320 |
+
font-weight: 720;
|
| 1321 |
+
padding: 8px 12px;
|
| 1322 |
+
}
|
| 1323 |
+
|
| 1324 |
+
.dqa-morning-ticket {
|
| 1325 |
+
background:
|
| 1326 |
+
linear-gradient(#fffdf8 0 0) padding-box,
|
| 1327 |
+
repeating-linear-gradient(90deg, #ead8aa 0 16px, transparent 16px 28px) border-box;
|
| 1328 |
+
border: 1px solid transparent;
|
| 1329 |
+
border-radius: 10px;
|
| 1330 |
+
box-shadow: var(--dqa-shadow);
|
| 1331 |
+
margin: 0 42px;
|
| 1332 |
+
overflow: hidden;
|
| 1333 |
+
}
|
| 1334 |
+
|
| 1335 |
+
.dqa-ticket-topline {
|
| 1336 |
+
align-items: center;
|
| 1337 |
+
background: #f8f1df;
|
| 1338 |
+
border-bottom: 1px dashed #dfc98e;
|
| 1339 |
+
color: #6c5a37;
|
| 1340 |
+
display: flex;
|
| 1341 |
+
flex-wrap: wrap;
|
| 1342 |
+
gap: 8px 12px;
|
| 1343 |
+
justify-content: space-between;
|
| 1344 |
+
padding: 14px 18px;
|
| 1345 |
+
}
|
| 1346 |
+
|
| 1347 |
+
.dqa-ticket-topline span {
|
| 1348 |
+
font-size: 0.82rem;
|
| 1349 |
+
font-weight: 760;
|
| 1350 |
+
}
|
| 1351 |
+
|
| 1352 |
+
.dqa-ticket-topline strong {
|
| 1353 |
+
color: var(--dqa-sage-deep);
|
| 1354 |
+
font-size: 0.96rem;
|
| 1355 |
+
line-height: 1.4;
|
| 1356 |
+
}
|
| 1357 |
+
|
| 1358 |
+
.dqa-ticket-row {
|
| 1359 |
+
border-bottom: 1px dashed #ead8aa;
|
| 1360 |
+
padding: clamp(18px, 3vw, 30px);
|
| 1361 |
+
}
|
| 1362 |
+
|
| 1363 |
+
.dqa-ticket-row:last-child {
|
| 1364 |
+
border-bottom: 0;
|
| 1365 |
+
}
|
| 1366 |
+
|
| 1367 |
+
.dqa-ticket-row h3 {
|
| 1368 |
+
color: var(--dqa-ink) !important;
|
| 1369 |
+
font-size: clamp(1.1rem, 2.6vw, 1.65rem);
|
| 1370 |
+
margin: 0 0 10px;
|
| 1371 |
+
}
|
| 1372 |
+
|
| 1373 |
+
.dqa-ticket-row p {
|
| 1374 |
+
color: var(--dqa-ink) !important;
|
| 1375 |
+
font-size: 1.05rem;
|
| 1376 |
+
line-height: 1.72;
|
| 1377 |
+
margin: 0;
|
| 1378 |
+
}
|
| 1379 |
+
|
| 1380 |
+
.dqa-ticket-row.is-primary-tip {
|
| 1381 |
+
background: linear-gradient(90deg, rgba(233, 243, 231, 0.9), rgba(255, 247, 217, 0.92));
|
| 1382 |
+
}
|
| 1383 |
+
|
| 1384 |
+
.dqa-ticket-row.is-primary-tip h3 {
|
| 1385 |
+
font-size: clamp(1.7rem, 4vw, 2.45rem);
|
| 1386 |
+
}
|
| 1387 |
+
|
| 1388 |
+
.dqa-small-model-note {
|
| 1389 |
+
background: #f7fbf4;
|
| 1390 |
+
border: 1px solid rgba(79, 138, 88, 0.2);
|
| 1391 |
+
border-radius: 12px;
|
| 1392 |
+
color: var(--dqa-ink);
|
| 1393 |
+
margin: 0 42px;
|
| 1394 |
+
padding: 14px 18px;
|
| 1395 |
+
}
|
| 1396 |
+
|
| 1397 |
+
.dqa-small-model-note summary {
|
| 1398 |
+
color: var(--dqa-sage-deep);
|
| 1399 |
+
cursor: pointer;
|
| 1400 |
+
font-weight: 760;
|
| 1401 |
+
}
|
| 1402 |
+
|
| 1403 |
+
.dqa-small-model-note p {
|
| 1404 |
+
color: var(--dqa-muted);
|
| 1405 |
+
line-height: 1.6;
|
| 1406 |
+
margin: 10px 0 0;
|
| 1407 |
+
}
|
| 1408 |
+
|
| 1409 |
+
.dqa-care-note {
|
| 1410 |
+
display: grid;
|
| 1411 |
+
gap: 4px;
|
| 1412 |
+
}
|
| 1413 |
+
|
| 1414 |
+
.dqa-care-note strong {
|
| 1415 |
+
color: var(--dqa-sage-deep);
|
| 1416 |
+
}
|
| 1417 |
+
|
| 1418 |
+
@media (max-width: 900px) {
|
| 1419 |
+
.dc-submit-row,
|
| 1420 |
+
.dc-demo-chip-row {
|
| 1421 |
+
grid-template-columns: 1fr !important;
|
| 1422 |
+
}
|
| 1423 |
+
|
| 1424 |
+
.dc-sun-mark {
|
| 1425 |
+
display: none;
|
| 1426 |
+
}
|
| 1427 |
+
}
|
| 1428 |
+
|
| 1429 |
+
@media (max-width: 640px) {
|
| 1430 |
+
.dc-hero {
|
| 1431 |
+
min-height: 360px;
|
| 1432 |
+
}
|
| 1433 |
+
|
| 1434 |
+
.dc-hero-body {
|
| 1435 |
+
text-align: left;
|
| 1436 |
+
}
|
| 1437 |
+
|
| 1438 |
+
.dc-brand-lockup {
|
| 1439 |
+
text-align: left;
|
| 1440 |
+
}
|
| 1441 |
+
|
| 1442 |
+
.dc-brand-subtitle {
|
| 1443 |
+
margin-left: 0;
|
| 1444 |
+
margin-right: 0;
|
| 1445 |
+
}
|
| 1446 |
+
|
| 1447 |
+
.dqa-anchor-chips,
|
| 1448 |
+
.dqa-morning-ticket,
|
| 1449 |
+
.dqa-small-model-note {
|
| 1450 |
+
margin-left: 18px;
|
| 1451 |
+
margin-right: 18px;
|
| 1452 |
+
}
|
| 1453 |
+
}
|
| 1454 |
+
"""
|
| 1455 |
+
|
| 1456 |
CSS += """
|
| 1457 |
:root {
|
| 1458 |
--dqa-mist: #f3f8f9;
|
|
|
|
| 1723 |
border-left: 4px solid var(--dqa-amber);
|
| 1724 |
}
|
| 1725 |
|
| 1726 |
+
.dc-question-anchor-strip {
|
| 1727 |
+
display: flex;
|
| 1728 |
+
flex-wrap: wrap;
|
| 1729 |
+
gap: 8px;
|
| 1730 |
+
margin: 16px 0 4px;
|
| 1731 |
+
}
|
| 1732 |
+
|
| 1733 |
+
.dc-question-anchor-strip span {
|
| 1734 |
+
background: rgba(255, 255, 255, 0.82);
|
| 1735 |
+
border: 1px solid rgba(152, 125, 69, 0.24);
|
| 1736 |
+
border-radius: 999px;
|
| 1737 |
+
box-shadow: 0 8px 18px rgba(88, 72, 40, 0.08);
|
| 1738 |
+
color: var(--dqa-ink);
|
| 1739 |
+
font-size: 0.88rem;
|
| 1740 |
+
font-weight: 750;
|
| 1741 |
+
line-height: 1.2;
|
| 1742 |
+
padding: 8px 12px;
|
| 1743 |
+
}
|
| 1744 |
+
|
| 1745 |
.dc-notice {
|
| 1746 |
background: rgba(233, 243, 231, 0.9);
|
| 1747 |
border: 1px solid rgba(79, 138, 88, 0.18);
|
tests/test_pipeline.py
CHANGED
|
@@ -79,6 +79,20 @@ def test_ask_questions_grounds_generic_question_in_dream_detail():
|
|
| 79 |
assert "customs window" in session.question_history[0].lower() or "wet paper" in session.question_history[0].lower()
|
| 80 |
|
| 81 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 82 |
def test_generate_pact_returns_card_and_html():
|
| 83 |
intake = build_intake(dream_text="I missed an elevator.", mood="anxious")
|
| 84 |
card, html = generate_pact(intake, "I want a small start.", FakeTextClient())
|
|
|
|
| 79 |
assert "customs window" in session.question_history[0].lower() or "wet paper" in session.question_history[0].lower()
|
| 80 |
|
| 81 |
|
| 82 |
+
def test_elevator_floor14_demo_anchors_are_clean_for_question_stage():
|
| 83 |
+
intake = build_intake(
|
| 84 |
+
dream_text=(
|
| 85 |
+
"I dreamed I kept missing an elevator. The button for floor 14 melted like wax. "
|
| 86 |
+
"I woke up anxious because it reminded me of an overdue email."
|
| 87 |
+
),
|
| 88 |
+
mood="Uneasy",
|
| 89 |
+
)
|
| 90 |
+
state = build_qa_state(intake, language="en")
|
| 91 |
+
|
| 92 |
+
assert state.dream_anchors[:3] == ["elevator", "floor 14", "melted button"]
|
| 93 |
+
assert "for floor" not in state.dream_anchors
|
| 94 |
+
|
| 95 |
+
|
| 96 |
def test_generate_pact_returns_card_and_html():
|
| 97 |
intake = build_intake(dream_text="I missed an elevator.", mood="anxious")
|
| 98 |
card, html = generate_pact(intake, "I want a small start.", FakeTextClient())
|
tests/test_render.py
CHANGED
|
@@ -95,10 +95,15 @@ def test_today_tip_card_prioritizes_tip_before_interpretation():
|
|
| 95 |
)
|
| 96 |
)
|
| 97 |
|
| 98 |
-
assert html.index("Today
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 99 |
|
| 100 |
|
| 101 |
-
def
|
| 102 |
card = TodayTipCard(
|
| 103 |
dream_summary="你梦见办公楼、电梯和一封迟迟没发出去的邮件。",
|
| 104 |
main_question="为什么我总是卡在电梯口?",
|
|
@@ -114,7 +119,7 @@ def test_today_tip_card_renames_tiny_action_to_weird_little_thing():
|
|
| 114 |
zh_html = render_today_tip_card(card, language="zh")
|
| 115 |
en_html = render_today_tip_card(card, language="en")
|
| 116 |
|
| 117 |
-
assert "
|
| 118 |
-
assert "
|
| 119 |
-
assert "
|
| 120 |
-
assert "
|
|
|
|
| 95 |
)
|
| 96 |
)
|
| 97 |
|
| 98 |
+
assert html.index("Today Tip") < html.index("Maybe this dream is pointing to")
|
| 99 |
+
assert "Morning Ticket" in html
|
| 100 |
+
assert "Because your dream kept returning to" in html
|
| 101 |
+
assert "dqa-anchor-chips" in html
|
| 102 |
+
assert "floor 14" in html
|
| 103 |
+
assert "How this was made small" in html
|
| 104 |
|
| 105 |
|
| 106 |
+
def test_today_tip_card_renames_tiny_action_to_five_minute_action():
|
| 107 |
card = TodayTipCard(
|
| 108 |
dream_summary="你梦见办公楼、电梯和一封迟迟没发出去的邮件。",
|
| 109 |
main_question="为什么我总是卡在电梯口?",
|
|
|
|
| 119 |
zh_html = render_today_tip_card(card, language="zh")
|
| 120 |
en_html = render_today_tip_card(card, language="en")
|
| 121 |
|
| 122 |
+
assert "5 分钟小行动" in zh_html
|
| 123 |
+
assert "古怪的小事" not in zh_html
|
| 124 |
+
assert "Tiny 5-minute action" in en_html
|
| 125 |
+
assert "Weird little thing" not in en_html
|
tests/test_ui_actions.py
CHANGED
|
@@ -91,19 +91,32 @@ def test_hero_stepper_tracks_app_status():
|
|
| 91 |
assert '<span class="dc-step is-complete"><strong>1</strong>' in ask_html
|
| 92 |
assert '<i class="dc-stepper-line is-complete" aria-hidden="true"></i>' in ask_html
|
| 93 |
assert '<span class="dc-step is-active" aria-current="step"><strong>2</strong>' in ask_html
|
| 94 |
-
assert '<span class="dc-step is-complete"><strong>
|
| 95 |
-
assert '<span class="dc-step is-active" aria-current="step"><strong>
|
| 96 |
-
assert tip_html.count("dc-stepper-line is-complete") ==
|
|
|
|
| 97 |
|
| 98 |
|
| 99 |
def test_hero_subtitle_explains_app_instead_of_legacy_name():
|
| 100 |
hero_html = ui_app._hero_html()
|
| 101 |
|
| 102 |
-
assert "
|
| 103 |
-
assert "
|
|
|
|
| 104 |
assert "Dream Customs" not in hero_html
|
| 105 |
|
| 106 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 107 |
def test_side_panel_dropdown_hover_stays_readable():
|
| 108 |
assert '.dc-side-panel .dc-section-title strong' in CSS
|
| 109 |
assert '.gradio-container [role="option"]:hover' in CSS
|
|
@@ -152,6 +165,7 @@ def test_processing_note_is_story_copy_not_backend_jargon():
|
|
| 152 |
|
| 153 |
assert "grounded question" in lowered
|
| 154 |
assert "today tip" in lowered
|
|
|
|
| 155 |
assert "model routes" not in lowered
|
| 156 |
assert "fallback" not in lowered
|
| 157 |
assert "token" not in lowered
|
|
@@ -159,6 +173,21 @@ def test_processing_note_is_story_copy_not_backend_jargon():
|
|
| 159 |
assert "debug" not in lowered
|
| 160 |
|
| 161 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 162 |
def test_long_running_buttons_show_processing_feedback():
|
| 163 |
source = inspect.getsource(ui_app.build_demo)
|
| 164 |
app_source = inspect.getsource(ui_app)
|
|
@@ -246,6 +275,7 @@ def test_mobile_mvp_submit_then_skip_generates_today_tip():
|
|
| 246 |
assert view["status"] == "ask"
|
| 247 |
assert view["question"]
|
| 248 |
assert len(view["questions"]) == 1
|
|
|
|
| 249 |
assert "DC-DEMO-014" not in view["card_text"]
|
| 250 |
|
| 251 |
state, view_json = skip_to_card_action(state)
|
|
|
|
| 91 |
assert '<span class="dc-step is-complete"><strong>1</strong>' in ask_html
|
| 92 |
assert '<i class="dc-stepper-line is-complete" aria-hidden="true"></i>' in ask_html
|
| 93 |
assert '<span class="dc-step is-active" aria-current="step"><strong>2</strong>' in ask_html
|
| 94 |
+
assert '<span class="dc-step is-complete"><strong>2</strong>' in tip_html
|
| 95 |
+
assert '<span class="dc-step is-active" aria-current="step"><strong>3</strong>' in tip_html
|
| 96 |
+
assert tip_html.count("dc-stepper-line is-complete") == 2
|
| 97 |
+
assert "One Question" in tip_html
|
| 98 |
|
| 99 |
|
| 100 |
def test_hero_subtitle_explains_app_instead_of_legacy_name():
|
| 101 |
hero_html = ui_app._hero_html()
|
| 102 |
|
| 103 |
+
assert "The Morning Question Desk" in hero_html
|
| 104 |
+
assert "What did the dream leave you asking?" in hero_html
|
| 105 |
+
assert "grounded Morning Ticket" in hero_html
|
| 106 |
assert "Dream Customs" not in hero_html
|
| 107 |
|
| 108 |
|
| 109 |
+
def test_composer_has_three_real_demo_chips():
|
| 110 |
+
source = inspect.getsource(ui_app.build_demo)
|
| 111 |
+
|
| 112 |
+
assert 'elem_classes=["dc-demo-chip-row"]' in source
|
| 113 |
+
assert source.count('elem_classes=["dc-demo-chip"]') == 3
|
| 114 |
+
assert "_example_elevator" in source
|
| 115 |
+
assert "_example_floor14" in source
|
| 116 |
+
assert "_example_melting" in source
|
| 117 |
+
assert ".dc-demo-chip-row" in CSS
|
| 118 |
+
|
| 119 |
+
|
| 120 |
def test_side_panel_dropdown_hover_stays_readable():
|
| 121 |
assert '.dc-side-panel .dc-section-title strong' in CSS
|
| 122 |
assert '.gradio-container [role="option"]:hover' in CSS
|
|
|
|
| 165 |
|
| 166 |
assert "grounded question" in lowered
|
| 167 |
assert "today tip" in lowered
|
| 168 |
+
assert "morning ticket" in lowered
|
| 169 |
assert "model routes" not in lowered
|
| 170 |
assert "fallback" not in lowered
|
| 171 |
assert "token" not in lowered
|
|
|
|
| 173 |
assert "debug" not in lowered
|
| 174 |
|
| 175 |
|
| 176 |
+
def test_question_stage_shows_anchor_chips_before_the_question():
|
| 177 |
+
html = ui_app._question_markdown(
|
| 178 |
+
{
|
| 179 |
+
"question": "When floor 14 shows up beside the overdue email, what would make opening it feel smaller?",
|
| 180 |
+
"dream_anchors": ["elevator", "floor 14", "melting button"],
|
| 181 |
+
},
|
| 182 |
+
"en",
|
| 183 |
+
)
|
| 184 |
+
|
| 185 |
+
assert "dc-question-anchor-strip" in html
|
| 186 |
+
assert html.index("elevator") < html.index("Morning Question Desk")
|
| 187 |
+
assert "floor 14" in html
|
| 188 |
+
assert "melting button" in html
|
| 189 |
+
|
| 190 |
+
|
| 191 |
def test_long_running_buttons_show_processing_feedback():
|
| 192 |
source = inspect.getsource(ui_app.build_demo)
|
| 193 |
app_source = inspect.getsource(ui_app)
|
|
|
|
| 275 |
assert view["status"] == "ask"
|
| 276 |
assert view["question"]
|
| 277 |
assert len(view["questions"]) == 1
|
| 278 |
+
assert view["dream_anchors"]
|
| 279 |
assert "DC-DEMO-014" not in view["card_text"]
|
| 280 |
|
| 281 |
state, view_json = skip_to_card_action(state)
|