J.B-Lin commited on
Commit
153f3f8
·
1 Parent(s): 3e28c81

refactor: 合并三天总结到营养报告,移除独立Tab

Browse files
Files changed (3) hide show
  1. app.py +1 -1
  2. docs/UI改进规划_用户反馈.md +159 -0
  3. ui/app_builder.py +53 -77
app.py CHANGED
@@ -29,7 +29,7 @@ if __name__ == "__main__":
29
  demo = create_app(loop)
30
  demo.launch(
31
  server_name="127.0.0.1",
32
- server_port=7860,
33
  share=False,
34
  debug=False,
35
  css=CUSTOM_CSS,
 
29
  demo = create_app(loop)
30
  demo.launch(
31
  server_name="127.0.0.1",
32
+ server_port=7880,
33
  share=False,
34
  debug=False,
35
  css=CUSTOM_CSS,
docs/UI改进规划_用户反馈.md ADDED
@@ -0,0 +1,159 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # PregoPal UI 改进规划 — 用户反馈汇总
2
+
3
+ > 日期:2026-06-09
4
+ > 负责:UI 设计 Cline
5
+
6
+ ---
7
+
8
+ ## 用户原始反馈(逐字保留)
9
+
10
+ > 现在改成卡片式的确实好看一些了,但是卡片的布局不均匀。存在以下问题:
11
+
12
+ ### 1. 首页 — 语音提示文案
13
+ > "全双工语音交互,边说边听"请改为直接到提示,用户不是工程师,没必要提示全双工这样的词语
14
+
15
+ - **问题**:`tap_hint` 字段中使用了「全双工语音交互」这种工程师术语
16
+ - **位置**:`utils.py` `_home_texts()` 第 52 行
17
+ - **修改**:改为用户友好的提示,如「点击开始对话」/「Tap to talk」
18
+
19
+ ### 2. 首页 — 红色大按钮功能
20
+ > 上面的红色大按钮怎么样才算是启动呢,这部分逻辑有设计吗?
21
+
22
+ - **问题**:红色圆形按钮(`.voice-main-btn`)目前只是一个装饰性 HTML `<button>`,没有任何 `click` 事件绑定
23
+ - **位置**:`ui/app_builder.py` 第 180-187 行
24
+ - **现状**:纯 CSS 样式,没有实际的语音启动逻辑
25
+ - **✅ 已决策**:用户要求真实语音对话启动,先用 `pass` 占位避免报错,设计好函数接口便于后续接入 Llama 大模型
26
+ - **接口设计**:
27
+ ```python
28
+ async def start_voice_session(audio_data: bytes, lang: str = "zh") -> dict:
29
+ """
30
+ 语音会话入口(当前 pass 占位,待接入 Llama 模型)
31
+
32
+ Args:
33
+ audio_data: 原始音频数据(bytes)
34
+ lang: 语言标识 "zh" / "en"
35
+
36
+ Returns:
37
+ dict: {
38
+ "text": str, # ASR 转写文本
39
+ "response": str, # AI 回复文本
40
+ "speaker": str, # 识别出的说话人
41
+ "audio_response": bytes | None # TTS 音频(可选)
42
+ }
43
+ """
44
+ pass
45
+ ```
46
+
47
+ ### 3. 首页 — AI 思考框 UI 问题
48
+ > 下面的"🤔 AI 思考中... 等待对话中..."UI设计也有问题,外面还有个方框写着textbox,下方的所有内容都是两个框包着,是不是哪里逻辑不对;并且文字框内部是灰色,这明显不对
49
+
50
+ - **问题 3a**:`gr.Textbox` 外层有 Gradio 默认的 "textbox" 标签边框
51
+ - **问题 3b**:文本框内部背景是灰色(默认 textarea 样式)
52
+ - **问题 3c**:下方内容区域被两层框包裹(可能是 `.home-card` + `.glass-card` 嵌套)
53
+ - **位置**:`ui/app_builder.py` 第 189-196 行,以及 CSS `.thinking-box` 样式
54
+ - **修改**:
55
+ - 将 `gr.Textbox` 改为 `gr.HTML` 或 `gr.Markdown` 来展示思考状态(去掉 textarea 外壳)
56
+ - 或者通过 CSS 彻底隐藏 textarea 的边框和灰色背景
57
+
58
+ ### 4. 顶部标题 — 缺乏英文和趣味字体
59
+ > 页面最顶上的"孕期陪护AI助手 — 温馨的家庭式伴侣"请同时提供英文,并且尝试使用更有趣的字体
60
+
61
+ - **问题**:顶部标题只有中文,没有英文副标题
62
+ - **位置**:`ui/app_builder.py` 第 360-365 行(硬编码 HTML 字符串)
63
+ - **修改**:
64
+ - 添加英文副标题:`Pregnancy Companion AI — Your Cozy Family Partner`
65
+ - 引入趣味字体(Google Fonts 圆体/手写体,如 `Nunito`、`Quicksand`、`Comic Neue` 或中文字体如站酷快乐体)
66
+
67
+ ### 5. 家庭饮食习惯 — 子页面默认状态
68
+ > 请默认打开家庭菜谱这样子页面,而不是在最开始的时候三个都不打开,看起来像是卡住了一样
69
+
70
+ - **问题**:三个子 Tab(菜谱/偏好/记忆)进入时全部折叠,看起来像页面卡住了
71
+ - **位置**:`ui/app_builder.py` 第 240-263 行,`gr.Tabs()` 没有设置默认选中
72
+ - **修改**:首次加载时默认显示「🍳 家庭菜谱」Tab
73
+
74
+ ### 6. 孕期阶段 — 嵌套边框问题
75
+ > 孕期阶段也是显示不太对,黑色的文本框边缘外面还是黑色的文本框边缘
76
+
77
+ - **问题**:嵌套的 Group 导致双层边框
78
+ - **位置**:`ui/app_builder.py` 第 201-207 行,首页卡片行中的 `card-trimester`
79
+ - **可能原因**:`gr.Group(elem_classes=["home-card", "card-trimester"])` 嵌套在 `gr.Column()` 内,Gradio 给每个 Grroup 都加了边框
80
+
81
+ ### 7. 营养报告 — Slider 无法拖动
82
+ > 营养报告中,分析天数的进度条完全没法拖动
83
+
84
+ - **问题**:`gr.Slider` 在 Gradio 中有时渲染为不可拖动的进度条
85
+ - **位置**:`ui/app_builder.py` 第 321-325 行
86
+ - **修改**:改为 `gr.Number` 输入框 + 手动输入天数,或使用 `gr.Slider` 的 `interactive=True`(需确认)
87
+
88
+ ### 8. 营养报告 — 右上角"刷新为7天"按钮多余
89
+ > 右上角那个刷新为7天的可以去掉,没啥意义
90
+
91
+ - **问题**:哪里来的"刷新为7天"按钮?可能在 `render_nutrition_report_html` 或 Slider 旁边
92
+ - **待确认**:需要再查看报告 HTML 渲染代码
93
+
94
+ ### 9. 营养报告 — 布局问题
95
+ > 此外这个页面UI布局也有问题,生成报告按钮占了页面的一半,下面全是空的,右边则是报告挤在一起
96
+
97
+ - **问题**:`gr.Row()` 中 `scale=1`(左侧按钮+Slider)和 `scale=3`��右侧报告)比例不合理
98
+ - **位置**:`ui/app_builder.py` 第 320-332 行
99
+ - **修改**:
100
+ - 左侧:将 Slider 和按钮上下排列,占用更少宽度(scale=1)
101
+ - 右侧:报告占主导(scale=4 或 5)
102
+ - 或者改为上下布局:上方是 Slider,下方是全宽报告
103
+
104
+ ### 10. 营养报告 — 自动生成
105
+ > 此外,没有必要"生成报告"这个按钮,每次分析天数被用户拉动的时候,等待用户不拉动之后就生成报告就是了
106
+
107
+ - **问题**:需要点击「生成报告」按钮才能看到报告
108
+ - **修改**:使用 `gr.Slider` 的 `.change()` 事件(带 debounce 防抖)自动触发报告生成,去掉「生成报告」按钮
109
+
110
+ ---
111
+
112
+ ## 待实施修改清单
113
+
114
+ - [ ] **P0** 写规划 md 文档(本文档)— 防止上下文压缩丢失用户原话
115
+ - [ ] **P1** 第 1 项:修改首页语音提示文案(去技术术语)
116
+ - [ ] **P1** 第 3 项:修复 AI 思考框 UI(去掉 textarea 外壳+灰色背景)
117
+ - [ ] **P1** 第 5 项:家庭饮食习惯默认打开「家庭菜谱」
118
+ - [ ] **P1** 第 9 项 + 第 10 项:营养报告布局重构 + 自动生成
119
+ - [ ] **P2** 第 4 项:顶部标题添加英文 + 趣味字体
120
+ - [ ] **P2** 第 6 项:修复孕期阶段嵌套边框
121
+ - [ ] **P2** 第 7 项:修复 Slider 拖动问题
122
+ - [ ] **P2** 第 8 项:移除多余的"刷新为7天"
123
+ - [ ] **P3** 第 2 项:红色按钮功能设计(需与用户讨论)
124
+
125
+ ---
126
+
127
+ ## 技术要点
128
+
129
+ ### 关于 gradio Slider 防抖自动生成
130
+ ```python
131
+ # 使用 gradio 的 change 事件 + 手动防抖
132
+ import time
133
+
134
+ _last_change = [0]
135
+
136
+ def on_slider_change(days):
137
+ _last_change[0] = time.time()
138
+ # 等待 0.8 秒后执行生成
139
+ ...
140
+ ```
141
+
142
+ ### 关于默认选中 Tab
143
+ ```python
144
+ with gr.Tabs(selected=0): # 或 selected="🍳 家庭菜谱"
145
+ ```
146
+
147
+ ### 关于改用 gr.HTML 代替 gr.Textbox 展示思考状态
148
+ - 去掉 textarea 原生外壳
149
+ - 保留渐变背景色由 HTML inline style 控制
150
+
151
+ ---
152
+
153
+ ## 文件修改范围
154
+
155
+ | 文件 | 修改内容 |
156
+ |------|----------|
157
+ | `docs/UI改进规划_用户反馈.md` | 本文档(新建) |
158
+ | `ui/app_builder.py` | 主要 UI 逻辑修改 |
159
+ | `utils.py` | 文案修改(`_home_texts`、`CUSTOM_CSS`) |
ui/app_builder.py CHANGED
@@ -1,20 +1,20 @@
1
  """
2
  PregoPal - Gradio 界面构建(现代UI版)
3
  ======================================
4
- 4 个子页面 + 中英文切换:
5
  1. 🏠 首页(语音启动按钮 + AI 思考 + 简报卡片 + 最近记录)
6
  2. 👨‍👩‍👧‍👦 家庭饮食习惯(纯展示卡片)
7
- 3. 📊 三天总结
8
- 4. 📈 营养报告(纯 HTML Dashboard)
9
 
10
  语言切换设计:
11
- 用唯一一个 @gr.render(inputs=[lang_state]) 包裹全部 4 个 Tab,
12
  避免多个 @gr.render 并发触发导致 Gradio 内部字典迭代崩溃。
13
  """
14
 
15
  import datetime
16
  import gradio as gr
17
  import time
 
18
 
19
  from modules.voiceprint import VoiceprintManager
20
  from modules.meal_recommender import MealRecommender
@@ -23,12 +23,15 @@ from modules.nutrition_analyzer import NutritionAnalyzer
23
  from modules.family_manager import RecipeManager, PreferenceManager, MemoryManager
24
  from modules.diet_extractor import DietExtractor
25
  from modules.nutrition_standards import DRIsParser
 
 
26
  from utils import (
27
  t, ZH, EN, get_home_cards, CUSTOM_CSS,
28
  render_family_recipes_html,
29
  render_family_preferences_html,
30
  render_family_memories_html,
31
  render_nutrition_report_html,
 
32
  )
33
 
34
  # ============================================================
@@ -301,94 +304,73 @@ def _family_content(lang):
301
 
302
 
303
  # ============================================================
304
- # Tab 3: 📊 三天总结
305
- # ============================================================
306
- def _summary_content(lang):
307
- """三天总结组件"""
308
- with gr.Row():
309
- with gr.Column(scale=1):
310
- trimester_select = gr.Radio(
311
- label=t("trimester_label", lang),
312
- choices=ZH["trimester_choices"] if lang == "zh" else EN["trimester_choices"],
313
- value=ZH["trimester_choices"][1] if lang == "zh" else EN["trimester_choices"][1],
314
- )
315
- run_3day_btn = gr.Button(t("btn_analyze", lang), variant="primary")
316
-
317
- with gr.Column(scale=2):
318
- three_day_result = gr.Textbox(label=t("analysis_result", lang), interactive=False, lines=15)
319
- menu_suggestion = gr.Textbox(label=t("menu_suggestion", lang), interactive=False, lines=4)
320
-
321
- def run_three_day_summary(trimester):
322
- from plugins.three_day_summary import ThreeDaySummaryPlugin
323
- from plugins.base import LoopContext
324
- import asyncio
325
-
326
- ctx = LoopContext()
327
- ctx.briefing["trimester"] = trimester
328
- asyncio.run(ThreeDaySummaryPlugin().run(ctx))
329
-
330
- report = ctx.briefing.get("three_day_summary", {}).get("report", "暂无数据")
331
- deficits = ctx.briefing.get("three_day_summary", {}).get("persistent_deficits", [])
332
-
333
- suggestions = []
334
- if deficits:
335
- suggestions.append("建议增加以下食材到家庭菜单:")
336
- for n in deficits[:3]:
337
- foods = DRIsParser.RICH_FOODS.get(n, [])
338
- if foods:
339
- suggestions.append(f" * {n} -> {'、'.join(foods[:4])}")
340
- else:
341
- suggestions.append("近三天营养状况良好!")
342
-
343
- return report, "\n".join(suggestions)
344
-
345
- run_3day_btn.click(
346
- fn=run_three_day_summary,
347
- inputs=[trimester_select],
348
- outputs=[three_day_result, menu_suggestion],
349
- )
350
-
351
-
352
- # ============================================================
353
- # Tab 4: 📈 营养报告(纯 HTML Dashboard,自动生成)
354
  # ============================================================
355
  def _report_content(lang):
356
- """营养报告组件 — 纯 HTML 可视化,Slider 防抖自动生成"""
357
  # 防抖时间戳
358
  _last_change = [0.0]
359
 
360
  with gr.Column():
361
- # Slider 全宽
362
- analysis_days = gr.Slider(
363
- label=t("analysis_days", lang),
364
- minimum=1, maximum=30, value=7, step=1,
365
- interactive=True,
366
- )
 
 
 
 
 
 
 
 
367
 
368
  # 报告全宽
369
  report_dashboard = gr.HTML(
370
  f'<div style="padding:40px;text-align:center;color:#bbb;font-size:16px;">'
371
- f'调整上方数自动生成报告</div>'
372
  )
373
 
374
- def generate_report(days):
 
 
375
  records = diet_logger.get_recent_records(days=int(days))
376
  analysis = nutrition_analyzer.analyze_diet(records)
377
- return render_nutrition_report_html(analysis, days=int(days), lang=lang)
 
 
 
 
 
 
 
 
 
 
 
 
378
 
379
- def on_slider_change(days):
380
- """Slider 变化时防抖 0.8s 后生成报告"""
 
 
381
  _last_change[0] = time.time()
382
- # wait for debounce window
383
  time.sleep(0.8)
384
- # if another change happened during sleep, skip
385
  if time.time() - _last_change[0] < 0.9:
386
- return generate_report(days)
387
  return gr.skip()
388
 
 
389
  analysis_days.change(
390
  fn=on_slider_change,
391
- inputs=[analysis_days],
 
 
 
 
 
392
  outputs=[report_dashboard],
393
  )
394
 
@@ -399,7 +381,7 @@ def _report_content(lang):
399
 
400
  def create_app(loop=None):
401
  """
402
- 创建主应用(4 Tab + 中英文切换 + 自定义 CSS)。
403
  """
404
  with gr.Blocks(
405
  title="PregoPal - 孕期陪护AI助手",
@@ -450,12 +432,6 @@ def create_app(loop=None):
450
  ):
451
  _family_content(lang)
452
 
453
- with gr.Tab(
454
- f"📊 {t('tab_summary_suffix', lang)}",
455
- id="tab_summary"
456
- ):
457
- _summary_content(lang)
458
-
459
  with gr.Tab(
460
  f"📈 {t('tab_report_suffix', lang)}",
461
  id="tab_report"
 
1
  """
2
  PregoPal - Gradio 界面构建(现代UI版)
3
  ======================================
4
+ 3 个子页面 + 中英文切换:
5
  1. 🏠 首页(语音启动按钮 + AI 思考 + 简报卡片 + 最近记录)
6
  2. 👨‍👩‍👧‍👦 家庭饮食习惯(纯展示卡片)
7
+ 3. 营养报告(含三天缺失深度分析,纯 HTML Dashboard)
 
8
 
9
  语言切换设计:
10
+ 用唯一一个 @gr.render(inputs=[lang_state]) 包裹全部 3 个 Tab,
11
  避免多个 @gr.render 并发触发导致 Gradio 内部字典迭代崩溃。
12
  """
13
 
14
  import datetime
15
  import gradio as gr
16
  import time
17
+ import asyncio
18
 
19
  from modules.voiceprint import VoiceprintManager
20
  from modules.meal_recommender import MealRecommender
 
23
  from modules.family_manager import RecipeManager, PreferenceManager, MemoryManager
24
  from modules.diet_extractor import DietExtractor
25
  from modules.nutrition_standards import DRIsParser
26
+ from plugins.three_day_summary import ThreeDaySummaryPlugin
27
+ from plugins.base import LoopContext
28
  from utils import (
29
  t, ZH, EN, get_home_cards, CUSTOM_CSS,
30
  render_family_recipes_html,
31
  render_family_preferences_html,
32
  render_family_memories_html,
33
  render_nutrition_report_html,
34
+ render_three_day_deficit_section,
35
  )
36
 
37
  # ============================================================
 
304
 
305
 
306
  # ============================================================
307
+ # Tab 3: 营养报告(含三天缺失深度分析,纯 HTML Dashboard)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
308
  # ============================================================
309
  def _report_content(lang):
310
+ """营养报告组件 — 纯 HTML 可视化 + 三天缺失分析,Slider 防抖自动生成"""
311
  # 防抖时间戳
312
  _last_change = [0.0]
313
 
314
  with gr.Column():
315
+ # 控制栏:天数 + 孕期阶段(紧凑行)
316
+ with gr.Row():
317
+ with gr.Column(scale=2, min_width=200):
318
+ analysis_days = gr.Slider(
319
+ label=t("analysis_days", lang),
320
+ minimum=1, maximum=30, value=7, step=1,
321
+ interactive=True,
322
+ )
323
+ with gr.Column(scale=1, min_width=160):
324
+ trimester_select = gr.Radio(
325
+ label=t("trimester_label", lang),
326
+ choices=ZH["trimester_choices"] if lang == "zh" else EN["trimester_choices"],
327
+ value=ZH["trimester_choices"][1] if lang == "zh" else EN["trimester_choices"][1],
328
+ )
329
 
330
  # 报告全宽
331
  report_dashboard = gr.HTML(
332
  f'<div style="padding:40px;text-align:center;color:#bbb;font-size:16px;">'
333
+ f'调整上方数自动生成报告</div>'
334
  )
335
 
336
+ def generate_full_report(days, trimester):
337
+ """生成营养报告 + 三天缺失深度分析,合并为同一份 HTML"""
338
+ # — 基础营养报告 —
339
  records = diet_logger.get_recent_records(days=int(days))
340
  analysis = nutrition_analyzer.analyze_diet(records)
341
+ main_html = render_nutrition_report_html(analysis, days=int(days), lang=lang)
342
+
343
+ # — 三天缺失深度分析(调用插件) —
344
+ try:
345
+ ctx = LoopContext()
346
+ ctx.briefing["trimester"] = trimester
347
+ asyncio.run(ThreeDaySummaryPlugin().run(ctx))
348
+ deficit_data = ctx.briefing.get("three_day_summary", {})
349
+ except Exception:
350
+ deficit_data = {}
351
+
352
+ # 添加深度分析板块
353
+ three_day_html = render_three_day_deficit_section(deficit_data, lang=lang)
354
 
355
+ return main_html + three_day_html
356
+
357
+ def on_slider_change(days, trimester):
358
+ """Slider/Radio 变化时防抖 0.8s 后生成报告"""
359
  _last_change[0] = time.time()
 
360
  time.sleep(0.8)
 
361
  if time.time() - _last_change[0] < 0.9:
362
+ return generate_full_report(days, trimester)
363
  return gr.skip()
364
 
365
+ # 两个输入都触发重新生成
366
  analysis_days.change(
367
  fn=on_slider_change,
368
+ inputs=[analysis_days, trimester_select],
369
+ outputs=[report_dashboard],
370
+ )
371
+ trimester_select.change(
372
+ fn=on_slider_change,
373
+ inputs=[analysis_days, trimester_select],
374
  outputs=[report_dashboard],
375
  )
376
 
 
381
 
382
  def create_app(loop=None):
383
  """
384
+ 创建主应用(3 Tab + 中英文切换 + 自定义 CSS)。
385
  """
386
  with gr.Blocks(
387
  title="PregoPal - 孕期陪护AI助手",
 
432
  ):
433
  _family_content(lang)
434
 
 
 
 
 
 
 
435
  with gr.Tab(
436
  f"📈 {t('tab_report_suffix', lang)}",
437
  id="tab_report"