J.B-Lin commited on
Commit
a4f0e44
·
1 Parent(s): 50bb88b

refactor: 将三天总结从框架层面合并到营养报告,统一UI设计

Browse files
Files changed (2) hide show
  1. ui/app_builder.py +4 -8
  2. utils.py +83 -80
ui/app_builder.py CHANGED
@@ -31,7 +31,6 @@ from utils import (
31
  render_family_preferences_html,
32
  render_family_memories_html,
33
  render_nutrition_report_html,
34
- render_three_day_deficit_section,
35
  )
36
 
37
  # ============================================================
@@ -334,13 +333,12 @@ def _report_content(lang):
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
@@ -349,10 +347,8 @@ def _report_content(lang):
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 后生成报告"""
 
31
  render_family_preferences_html,
32
  render_family_memories_html,
33
  render_nutrition_report_html,
 
34
  )
35
 
36
  # ============================================================
 
333
  )
334
 
335
  def generate_full_report(days, trimester):
336
+ """生成营养报告(含三天缺失深度分析,框架层面合并"""
337
  # — 基础营养报告 —
338
  records = diet_logger.get_recent_records(days=int(days))
339
  analysis = nutrition_analyzer.analyze_diet(records)
 
340
 
341
+ # — 三天缺失深度分析(调用插件,数据传入 render_nutrition_report_html 统一渲染) —
342
  try:
343
  ctx = LoopContext()
344
  ctx.briefing["trimester"] = trimester
 
347
  except Exception:
348
  deficit_data = {}
349
 
350
+ # 所有内容由 render_nutrition_report_html 统一渲染(框架层面合并)
351
+ return render_nutrition_report_html(analysis, days=int(days), lang=lang, deficit_data=deficit_data)
 
 
352
 
353
  def on_slider_change(days, trimester):
354
  """Slider/Radio 变化时防抖 0.8s 后生成报告"""
utils.py CHANGED
@@ -57,7 +57,6 @@ ZH = {
57
  "app_subtitle": "一个温馨的家庭式孕期AI伴侣",
58
  "tab_home_suffix": "首页",
59
  "tab_family_suffix": "家庭饮食习惯",
60
- "tab_summary_suffix": "三天总结",
61
  "tab_report_suffix": "营养报告",
62
  "chat_label": "💬 对话",
63
  "msg_placeholder": "例如:我今天中午吃了番茄牛腩",
@@ -102,20 +101,15 @@ ZH = {
102
  "report_diversity": "饮食多样性",
103
  "report_suggestions": "饮食建议",
104
  "report_days": "近{days}天",
105
- "three_day_title": "📊 三天深度营养分析",
106
- "three_day_subtitle": "基于近日饮食趋势的持续缺失追踪",
 
107
  "three_day_days": "分析了 {days} 天数据",
108
  "three_day_duration": "持续不足的营养素",
109
- "three_day_deficit": "缺失风险",
110
- "three_day_attention": "需关注",
111
- "three_day_good": "摄入达标",
112
  "three_day_suggestions": "🍽️ 食材补充建议",
113
- "three_day_summary": "📋 总结",
114
  "three_day_no_data": "近三天无饮食记录,跳过深度分析",
115
  "three_day_all_good": "✅ 各营养素摄入基本达标,继续保持!",
116
- "three_day_def_critical": "❌",
117
- "three_day_def_warning": "⚠️",
118
- "three_day_def_ok": "✅",
119
  }
120
 
121
  EN = {
@@ -123,7 +117,6 @@ EN = {
123
  "app_subtitle": "A cozy home-oriented AI companion for expectant mothers",
124
  "tab_home_suffix": "Home",
125
  "tab_family_suffix": "Family Diet",
126
- "tab_summary_suffix": "3-Day Summary",
127
  "tab_report_suffix": "Nutrition Report",
128
  "chat_label": "💬 Chat",
129
  "msg_placeholder": "e.g. I had tomato beef brisket for lunch today",
@@ -168,20 +161,15 @@ EN = {
168
  "report_diversity": "Diet Diversity",
169
  "report_suggestions": "Dietary Suggestions",
170
  "report_days": "Last {days} days",
171
- "three_day_title": "📊 3-Day Deep Analysis",
172
- "three_day_subtitle": "Continuous deficiency tracking based on recent diet trends",
 
173
  "three_day_days": "Analyzed {days} days of data",
174
  "three_day_duration": "Persistent Deficits",
175
- "three_day_deficit": "Deficit Risk",
176
- "three_day_attention": "Needs Attention",
177
- "three_day_good": "Adequate Intake",
178
  "three_day_suggestions": "🍽️ Food Suggestions",
179
- "three_day_summary": "📋 Summary",
180
  "three_day_no_data": "No diet records in the last 3 days, skipping deep analysis",
181
  "three_day_all_good": "✅ All nutrients adequate, keep it up!",
182
- "three_day_def_critical": "❌",
183
- "three_day_def_warning": "⚠️",
184
- "three_day_def_ok": "✅",
185
  }
186
 
187
 
@@ -335,11 +323,17 @@ def render_family_memories_html(lang="zh") -> str:
335
 
336
 
337
  # ============================================================
338
- # 营养报告 HTML
 
 
 
 
 
 
339
  # ============================================================
340
 
341
- def render_nutrition_report_html(analysis: dict, days: int, lang="zh") -> str:
342
- """渲染营养报告为现代 Dashboard HTML"""
343
  if not analysis or "error" in analysis:
344
  return f'<div style="padding:40px;text-align:center;color:#aaa;font-size:16px;">{analysis.get("error","暂无数据")}</div>'
345
 
@@ -405,6 +399,11 @@ def render_nutrition_report_html(analysis: dict, days: int, lang="zh") -> str:
405
 
406
  days_str = t("report_days", lang).format(days=days)
407
 
 
 
 
 
 
408
  return f"""
409
  <div style="font-family:system-ui,-apple-system,sans-serif;max-width:100%;">
410
  <!-- Header -->
@@ -459,49 +458,14 @@ def render_nutrition_report_html(analysis: dict, days: int, lang="zh") -> str:
459
  <div style="font-weight:600;font-size:14px;margin-bottom:6px;color:#E65100;">💡 {t("report_suggestions", lang)}</div>
460
  {sug_items if sug_items else '<div style="font-size:13px;color:#888;">暂无建议</div>'}
461
  </div>
462
- </div>"""
463
 
 
 
464
 
465
- # ============================================================
466
- # 营养素中英文对照表(用于三天总结双语展示)
467
- # ============================================================
468
-
469
- NUTRIENT_EN_NAMES = {
470
- "能量(MJ)": "Energy",
471
- "蛋白质(g)": "Protein",
472
- "钙(mg)": "Calcium",
473
- "铁(mg)": "Iron",
474
- "锌(mg)": "Zinc",
475
- "维生素A(μg RAE)": "Vitamin A",
476
- "维生素D(μg)": "Vitamin D",
477
- "维生素E(mg α-TE)": "Vitamin E",
478
- "维生素B1(mg)": "Vitamin B1",
479
- "维生素B2(mg)": "Vitamin B2",
480
- "叶酸(μg DFE)": "Folate",
481
- "碘(μg)": "Iodine",
482
- "镁(mg)": "Magnesium",
483
- "硒(μg)": "Selenium",
484
- "钾(mg)": "Potassium",
485
- "钠(mg)": "Sodium",
486
- "磷(mg)": "Phosphorus",
487
- "维生素C(mg)": "Vitamin C",
488
- "膳食纤维(g)": "Dietary Fiber",
489
- }
490
-
491
-
492
- def _nutrient_display(name_cn: str, lang: str) -> str:
493
- """返回营养素名的展示文本(中文模式显示中文+英文,英文模式显示英文)"""
494
- en_name = NUTRIENT_EN_NAMES.get(name_cn, name_cn)
495
- if lang == "zh":
496
- return f"{name_cn} / {en_name}"
497
- return en_name
498
-
499
 
500
- # ============================================================
501
- # 三天缺失深度分析 HTML(与营养报告统一视觉风格)
502
- # ============================================================
503
 
504
- def render_three_day_deficit_section(deficit_data: dict, lang="zh") -> str:
505
  """
506
  渲染三天缺失深度分析板块,与营养报告使用同一套现代UI视觉语言。
507
 
@@ -513,7 +477,7 @@ def render_three_day_deficit_section(deficit_data: dict, lang="zh") -> str:
513
  lang: 语言标识 "zh" / "en"
514
 
515
  Returns:
516
- str: 纯 HTML 字符串,可直接拼接到营养报告之后
517
  """
518
  if not deficit_data:
519
  return ""
@@ -532,7 +496,7 @@ def render_three_day_deficit_section(deficit_data: dict, lang="zh") -> str:
532
  {no_data_msg}
533
  </div>"""
534
 
535
- # ——— Persistent Deficits: 紧凑标签式展示(取代大卡片) ———
536
  deficit_tags = ""
537
  for nutrient in persistent_deficits:
538
  nd = _nutrient_display(nutrient, lang)
@@ -554,7 +518,7 @@ def render_three_day_deficit_section(deficit_data: dict, lang="zh") -> str:
554
  🎉 {all_good}
555
  </span>"""
556
 
557
- # ——— Food Suggestions: 扩大展示(使用2/3宽度,每行2个食物标签) ———
558
  from modules.nutrition_standards import DRIsParser
559
  suggestion_rows = ""
560
  for nutrient in persistent_deficits[:3]:
@@ -583,11 +547,10 @@ def render_three_day_deficit_section(deficit_data: dict, lang="zh") -> str:
583
  🎉 {t("three_day_all_good", lang)}
584
  </div>"""
585
 
586
- # ——— Summary: 覆盖折叠纯文本,解析后用结构化UI展示 ———
587
  summary_sections_html = ""
588
  if report_text:
589
  lines = report_text.strip().split("\n")
590
- # 分类行
591
  critical_items = []
592
  warning_items = []
593
  ok_items = []
@@ -662,31 +625,30 @@ def render_three_day_deficit_section(deficit_data: dict, lang="zh") -> str:
662
 
663
  days_label = t("three_day_days", lang).format(days=days_analyzed)
664
 
665
- # ——— 拼装最终 HTML ———
666
- # 布局: 左右比例 1:2 (deficit : suggestions)
667
- # summary 在下方单独一行,结构化展示
668
  return f"""
669
- <div style="margin-top:20px;font-family:system-ui,-apple-system,sans-serif;max-width:100%;">
 
670
 
671
- <!-- ★ Header: 与营养报告统一的渐变风格 -->
672
- <div style="background:linear-gradient(135deg,#E8F5E9,#F1F8E9);border-radius:18px;padding:20px 24px;margin-bottom:16px;">
673
  <div style="display:flex;justify-content:space-between;align-items:center;">
674
  <div>
675
- <div style="font-size:20px;font-weight:700;color:#1B5E20;">{t("three_day_title", lang)}</div>
676
  <div style="color:#888;font-size:13px;margin-top:4px;">{t("three_day_subtitle", lang)}</div>
677
  </div>
678
  <div style="text-align:right;">
679
- <div style="font-size:32px;font-weight:800;color:#4CAF50;">{days_analyzed}</div>
680
- <div style="font-size:12px;color:#888;">天</div>
681
  </div>
682
  </div>
683
  <div style="margin-top:8px;font-size:13px;color:#555;">{days_label}</div>
684
  </div>
685
 
686
- <!-- 双列布局 1:2 → 左侧:紧凑标签式缺失营养素 | 右侧:放大的食材建议 -->
687
  <div style="display:grid;grid-template-columns:1fr 2fr;gap:12px;margin-bottom:16px;">
688
 
689
- <!-- 左侧:持续不足的营养素 — 紧凑标签风格 -->
690
  <div style="background:#fff;border-radius:14px;padding:16px;
691
  box-shadow:0 1px 6px rgba(0,0,0,0.04);border:1px solid #f0f0f0;">
692
  <div style="font-weight:600;font-size:14px;margin-bottom:12px;color:#333;">
@@ -697,7 +659,7 @@ def render_three_day_deficit_section(deficit_data: dict, lang="zh") -> str:
697
  </div>
698
  </div>
699
 
700
- <!-- 右侧:食材补充建议 — 放大展示 -->
701
  <div style="background:linear-gradient(135deg,#FFF8E1,#FFFDE7);border-radius:14px;padding:16px 20px;
702
  border:1px solid #FFE082;">
703
  <div style="font-weight:600;font-size:15px;margin-bottom:12px;color:#E65100;">
@@ -708,7 +670,7 @@ def render_three_day_deficit_section(deficit_data: dict, lang="zh") -> str:
708
 
709
  </div>
710
 
711
- <!-- 总结部分:结构化UI展示(替代纯文本折叠) -->
712
  {f'''
713
  <div style="background:#fff;border-radius:14px;padding:16px 20px;
714
  box-shadow:0 1px 6px rgba(0,0,0,0.04);border:1px solid #f0f0f0;">
@@ -722,6 +684,47 @@ def render_three_day_deficit_section(deficit_data: dict, lang="zh") -> str:
722
  </div>"""
723
 
724
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
725
  # ============================================================
726
  # 自定义 CSS 样式
727
  # ============================================================
 
57
  "app_subtitle": "一个温馨的家庭式孕期AI伴侣",
58
  "tab_home_suffix": "首页",
59
  "tab_family_suffix": "家庭饮食习惯",
 
60
  "tab_report_suffix": "营养报告",
61
  "chat_label": "💬 对话",
62
  "msg_placeholder": "例如:我今天中午吃了番茄牛腩",
 
101
  "report_diversity": "饮食多样性",
102
  "report_suggestions": "饮食建议",
103
  "report_days": "近{days}天",
104
+ # 三天深度分析(已合并到营养报告中)
105
+ "three_day_title": "📊 天深度营养趋势",
106
+ "three_day_subtitle": "基于近三日饮食数据的持续缺失追踪",
107
  "three_day_days": "分析了 {days} 天数据",
108
  "three_day_duration": "持续不足的营养素",
 
 
 
109
  "three_day_suggestions": "🍽️ 食材补充建议",
110
+ "three_day_summary": "📋 趋势总结",
111
  "three_day_no_data": "近三天无饮食记录,跳过深度分析",
112
  "three_day_all_good": "✅ 各营养素摄入基本达标,继续保持!",
 
 
 
113
  }
114
 
115
  EN = {
 
117
  "app_subtitle": "A cozy home-oriented AI companion for expectant mothers",
118
  "tab_home_suffix": "Home",
119
  "tab_family_suffix": "Family Diet",
 
120
  "tab_report_suffix": "Nutrition Report",
121
  "chat_label": "💬 Chat",
122
  "msg_placeholder": "e.g. I had tomato beef brisket for lunch today",
 
161
  "report_diversity": "Diet Diversity",
162
  "report_suggestions": "Dietary Suggestions",
163
  "report_days": "Last {days} days",
164
+ # 三天深度分析(合并到营养报告中)
165
+ "three_day_title": "📊 3-Day Nutrient Trends",
166
+ "three_day_subtitle": "Continuous deficiency tracking based on recent diet data",
167
  "three_day_days": "Analyzed {days} days of data",
168
  "three_day_duration": "Persistent Deficits",
 
 
 
169
  "three_day_suggestions": "🍽️ Food Suggestions",
170
+ "three_day_summary": "📋 Trend Summary",
171
  "three_day_no_data": "No diet records in the last 3 days, skipping deep analysis",
172
  "three_day_all_good": "✅ All nutrients adequate, keep it up!",
 
 
 
173
  }
174
 
175
 
 
323
 
324
 
325
  # ============================================================
326
+ # 营养报告 HTML(含三天深度分析)
327
+ # ============================================================
328
+ # 统一的设计令牌(Design Tokens)
329
+ # Header: 粉色渐变 background:linear-gradient(135deg,#FCE4EC,#FFF0F2)
330
+ # 卡片: background:#fff; border-radius:14px; box-shadow + border
331
+ # 建议: background:linear-gradient(135deg,#FFF8E1,#FFFDE7); border:1px solid #FFE082
332
+ # 强调色: #880E4F (深粉红), #E91E63 (粉红), #E65100 (橙)
333
  # ============================================================
334
 
335
+ def render_nutrition_report_html(analysis: dict, days: int, lang="zh", deficit_data: dict = None) -> str:
336
+ """渲染营养报告为现代 Dashboard HTML(含三天深度趋势分析)"""
337
  if not analysis or "error" in analysis:
338
  return f'<div style="padding:40px;text-align:center;color:#aaa;font-size:16px;">{analysis.get("error","暂无数据")}</div>'
339
 
 
399
 
400
  days_str = t("report_days", lang).format(days=days)
401
 
402
+ # ============================================================
403
+ # 三天深度趋势分析(合并到营养报告内)
404
+ # ============================================================
405
+ three_day_html = _render_deficit_section(deficit_data, lang)
406
+
407
  return f"""
408
  <div style="font-family:system-ui,-apple-system,sans-serif;max-width:100%;">
409
  <!-- Header -->
 
458
  <div style="font-weight:600;font-size:14px;margin-bottom:6px;color:#E65100;">💡 {t("report_suggestions", lang)}</div>
459
  {sug_items if sug_items else '<div style="font-size:13px;color:#888;">暂无建议</div>'}
460
  </div>
 
461
 
462
+ <!-- ★ 三天深度趋势分析 — 使用与营养报告统一的UI设计 -->
463
+ {three_day_html}
464
 
465
+ </div>"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
466
 
 
 
 
467
 
468
+ def _render_deficit_section(deficit_data: dict, lang="zh") -> str:
469
  """
470
  渲染三天缺失深度分析板块,与营养报告使用同一套现代UI视觉语言。
471
 
 
477
  lang: 语言标识 "zh" / "en"
478
 
479
  Returns:
480
+ str: 纯 HTML 字符串,使用与营养报告统一的风格
481
  """
482
  if not deficit_data:
483
  return ""
 
496
  {no_data_msg}
497
  </div>"""
498
 
499
+ # ——— Persistent Deficits: 紧凑标签式展示 ———
500
  deficit_tags = ""
501
  for nutrient in persistent_deficits:
502
  nd = _nutrient_display(nutrient, lang)
 
518
  🎉 {all_good}
519
  </span>"""
520
 
521
+ # ——— Food Suggestions ———
522
  from modules.nutrition_standards import DRIsParser
523
  suggestion_rows = ""
524
  for nutrient in persistent_deficits[:3]:
 
547
  🎉 {t("three_day_all_good", lang)}
548
  </div>"""
549
 
550
+ # ——— Summary: 结构化UI展示 ———
551
  summary_sections_html = ""
552
  if report_text:
553
  lines = report_text.strip().split("\n")
 
554
  critical_items = []
555
  warning_items = []
556
  ok_items = []
 
625
 
626
  days_label = t("three_day_days", lang).format(days=days_analyzed)
627
 
628
+ # ——— 拼装最终 HTML(使用与营养报告统一的设计语言) ———
 
 
629
  return f"""
630
+ <!-- 三天深度趋势分析 — 使用与营养报告一致的粉色渐变主题 -->
631
+ <div style="margin-top:20px;">
632
 
633
+ <!-- 头部:与营养报告Header统一的粉色渐变 -->
634
+ <div style="background:linear-gradient(135deg,#FCE4EC,#FFF0F2);border-radius:18px;padding:20px 24px;margin-bottom:16px;">
635
  <div style="display:flex;justify-content:space-between;align-items:center;">
636
  <div>
637
+ <div style="font-size:20px;font-weight:700;color:#880E4F;">{t("three_day_title", lang)}</div>
638
  <div style="color:#888;font-size:13px;margin-top:4px;">{t("three_day_subtitle", lang)}</div>
639
  </div>
640
  <div style="text-align:right;">
641
+ <div style="font-size:32px;font-weight:800;color:#E91E63;">{days_analyzed}</div>
642
+ <div style="font-size:12px;color:#888;">{'' if lang == 'zh' else 'days'}</div>
643
  </div>
644
  </div>
645
  <div style="margin-top:8px;font-size:13px;color:#555;">{days_label}</div>
646
  </div>
647
 
648
+ <!-- 双列布局 1:2 → 左侧:缺失营养素标签 | 右侧:食材建议 -->
649
  <div style="display:grid;grid-template-columns:1fr 2fr;gap:12px;margin-bottom:16px;">
650
 
651
+ <!-- 左侧:持续不足的营养素 — 与报告一致的白色卡片 -->
652
  <div style="background:#fff;border-radius:14px;padding:16px;
653
  box-shadow:0 1px 6px rgba(0,0,0,0.04);border:1px solid #f0f0f0;">
654
  <div style="font-weight:600;font-size:14px;margin-bottom:12px;color:#333;">
 
659
  </div>
660
  </div>
661
 
662
+ <!-- 右侧:食材补充建议 — 与报告建议区一致的金色渐变 -->
663
  <div style="background:linear-gradient(135deg,#FFF8E1,#FFFDE7);border-radius:14px;padding:16px 20px;
664
  border:1px solid #FFE082;">
665
  <div style="font-weight:600;font-size:15px;margin-bottom:12px;color:#E65100;">
 
670
 
671
  </div>
672
 
673
+ <!-- 总结部分:结构化UI展示 -->
674
  {f'''
675
  <div style="background:#fff;border-radius:14px;padding:16px 20px;
676
  box-shadow:0 1px 6px rgba(0,0,0,0.04);border:1px solid #f0f0f0;">
 
684
  </div>"""
685
 
686
 
687
+ # 保留旧函数名作为代理(兼容外部调用),标记为 deprecated
688
+ def render_three_day_deficit_section(deficit_data: dict, lang="zh") -> str:
689
+ """已弃用:三天分析已合并到 render_nutrition_report_html 中。保留此函数保持兼容。"""
690
+ return _render_deficit_section(deficit_data, lang)
691
+
692
+
693
+ # ============================================================
694
+ # 营养素中英文对照表
695
+ # ============================================================
696
+
697
+ NUTRIENT_EN_NAMES = {
698
+ "能量(MJ)": "Energy",
699
+ "蛋白质(g)": "Protein",
700
+ "钙(mg)": "Calcium",
701
+ "铁(mg)": "Iron",
702
+ "锌(mg)": "Zinc",
703
+ "维生素A(μg RAE)": "Vitamin A",
704
+ "维生素D(μg)": "Vitamin D",
705
+ "维生素E(mg α-TE)": "Vitamin E",
706
+ "维生素B1(mg)": "Vitamin B1",
707
+ "维生素B2(mg)": "Vitamin B2",
708
+ "叶酸(μg DFE)": "Folate",
709
+ "碘(μg)": "Iodine",
710
+ "镁(mg)": "Magnesium",
711
+ "硒(μg)": "Selenium",
712
+ "钾(mg)": "Potassium",
713
+ "钠(mg)": "Sodium",
714
+ "磷(mg)": "Phosphorus",
715
+ "维生素C(mg)": "Vitamin C",
716
+ "膳食纤维(g)": "Dietary Fiber",
717
+ }
718
+
719
+
720
+ def _nutrient_display(name_cn: str, lang: str) -> str:
721
+ """返回营养素名的展示文本(中文模式显示中文+英文,英文模式显示英文)"""
722
+ en_name = NUTRIENT_EN_NAMES.get(name_cn, name_cn)
723
+ if lang == "zh":
724
+ return f"{name_cn} / {en_name}"
725
+ return en_name
726
+
727
+
728
  # ============================================================
729
  # 自定义 CSS 样式
730
  # ============================================================