J.B-Lin commited on
Commit
c48504c
·
1 Parent(s): ff5d4ed

refactor: simplify homepage + clean emoji from reports + fix fonts

Browse files
modules/nutrition_analyzer.py CHANGED
@@ -126,7 +126,7 @@ class NutritionAnalyzer:
126
  details = []
127
  for meal, count in meal_counts.items():
128
  rate = count / total_days if total_days > 0 else 0
129
- status = "" if rate >= 0.7 else ("⚠️" if rate >= 0.4 else "")
130
  details.append(f"{status} {meal}: {count}/{total_days}天 ({rate:.0%})")
131
 
132
  return {"score": score, "details": details}
@@ -138,18 +138,18 @@ class NutritionAnalyzer:
138
  # 检查未覆盖的营养素
139
  missing = [n for n, info in nutrition_coverage.items() if not info["covered"]]
140
  if missing:
141
- suggestions.append(f"⚠️ 以下营养素摄入不足: {', '.join(missing[:5])}")
142
  for n in missing[:3]:
143
  info = nutrition_coverage[n]
144
- suggestions.append(f" 💡 建议补充 {n}({info['benefit']}):可多吃 {', '.join(info['recommended_foods'][:3])}")
145
 
146
  if diversity["score"] < 60:
147
- suggestions.append("⚠️ 饮食多样性不足,建议增加食物种类")
148
  elif diversity["score"] >= 80:
149
- suggestions.append(" 饮食多样性良好,继续保持!")
150
 
151
- suggestions.append("💪 建议每天摄入12种以上食物,每周25种以上")
152
- suggestions.append("🥤 保证每天1.5-2L饮水")
153
 
154
  return suggestions
155
 
@@ -176,7 +176,7 @@ class NutritionAnalyzer:
176
  ax1.set_xticks(angles[:-1])
177
  ax1.set_xticklabels(nutrients, fontsize=9)
178
  ax1.set_ylim(0, 1.2)
179
- ax1.set_title('🥗 营养覆盖雷达图', pad=20, fontsize=13, fontweight='bold')
180
 
181
  # 2. 各餐次频率柱状图
182
  ax2 = fig.add_subplot(2, 2, 2)
@@ -184,7 +184,7 @@ class NutritionAnalyzer:
184
  counts = list(analysis["meal_counts"].values())
185
  colors = ['#FF9AA2', '#FFB7B2', '#FFDAC1', '#E2F0CB']
186
  bars = ax2.bar(meals, counts, color=colors, edgecolor='white', linewidth=1.5)
187
- ax2.set_title('🍽️ 各餐次记录频率', fontsize=13, fontweight='bold')
188
  ax2.set_ylabel('记录次数')
189
  for bar, count in zip(bars, counts):
190
  ax2.text(bar.get_x() + bar.get_width() / 2, bar.get_height() + 0.1,
@@ -198,16 +198,16 @@ class NutritionAnalyzer:
198
  wedgeprops={'width': 0.3, 'edgecolor': 'white'})
199
  ax3.text(0, 0, f'{score}', ha='center', va='center', fontsize=28, fontweight='bold')
200
  ax3.text(0, -0.15, '多样性评分', ha='center', va='center', fontsize=10, color='gray')
201
- ax3.set_title('📊 饮食多样性评分', fontsize=13, fontweight='bold')
202
 
203
  # 4. 建议文本
204
  ax4 = fig.add_subplot(2, 2, 4)
205
  ax4.axis('off')
206
  suggestions = analysis.get("suggestions", [])
207
  if suggestions:
208
- text = "📋 营养建议\n" + "\n".join(f" {s}" for s in suggestions[:6])
209
  else:
210
- text = " 营养状况良好!"
211
  ax4.text(0.05, 0.95, text, transform=ax4.transAxes,
212
  fontsize=10, verticalalignment='top',
213
  fontfamily='sans-serif',
@@ -223,29 +223,29 @@ class NutritionAnalyzer:
223
 
224
  lines = [
225
  "=" * 50,
226
- "📋 孕期营养分析报告",
227
  "=" * 50,
228
- f"📅 分析周期: {analysis['total_days']} 天",
229
- f"📝 记录总数: {analysis['total_records']} 条",
230
  "",
231
- "📊 饮食多样性评分: {}/100".format(analysis['diversity_score']['score']),
232
  ]
233
 
234
  lines.append("")
235
- lines.append("📈 各餐次记录情况:")
236
  for detail in analysis['diversity_score']['details']:
237
  lines.append(f" {detail}")
238
 
239
  lines.append("")
240
- lines.append("🥗 覆盖情况:")
241
  for nutrient, info in analysis['nutrition_coverage'].items():
242
- status = "" if info['covered'] else ""
243
  matched = ", ".join(info['matched_foods']) if info['matched_foods'] else "无"
244
  lines.append(f" {status} {nutrient}: 匹配食物 [{matched}]")
245
- lines.append(f" 💡 {info['benefit']}")
246
 
247
  lines.append("")
248
- lines.append("💡 改善建议:")
249
  for s in analysis.get("suggestions", []):
250
  lines.append(f" {s}")
251
 
@@ -262,14 +262,14 @@ class NutritionAnalyzer:
262
 
263
  md_path = REPORTS_DIR / filename
264
 
265
- content = f"""# 📋 孕期营养分析报告
266
 
267
- ## 📅 基本信息
268
  - **分析周期**: {analysis.get('total_days', 0)} 天
269
  - **记录总数**: {analysis.get('total_records', 0)} 条
270
  - **生成时间**: {datetime.datetime.now().strftime('%Y-%m-%d %H:%M')}
271
 
272
- ## 📊 饮食多样性评分
273
  **评分: {analysis.get('diversity_score', {}).get('score', 0)}/100**
274
 
275
  | 餐次 | 记录天数 | 覆盖率 |
@@ -281,18 +281,18 @@ class NutritionAnalyzer:
281
  content += f"| {parts[0]} | {parts[1]} |\n"
282
 
283
  content += """
284
- ## 🥗 营养覆盖分析
285
 
286
  | 营养素 | 状态 | 匹配食物 | 功效 |
287
  |--------|------|---------|------|
288
  """
289
  for nutrient, info in analysis.get('nutrition_coverage', {}).items():
290
- status = "" if info['covered'] else ""
291
  matched = ", ".join(info['matched_foods']) if info['matched_foods'] else "-"
292
  content += f"| {nutrient} | {status} | {matched} | {info['benefit']} |\n"
293
 
294
  content += """
295
- ## 💡 改善建议
296
 
297
  """
298
  for s in analysis.get('suggestions', []):
 
126
  details = []
127
  for meal, count in meal_counts.items():
128
  rate = count / total_days if total_days > 0 else 0
129
+ status = "[OK]" if rate >= 0.7 else ("[!]" if rate >= 0.4 else "[!!]")
130
  details.append(f"{status} {meal}: {count}/{total_days}天 ({rate:.0%})")
131
 
132
  return {"score": score, "details": details}
 
138
  # 检查未覆盖的营养素
139
  missing = [n for n, info in nutrition_coverage.items() if not info["covered"]]
140
  if missing:
141
+ suggestions.append(f"[!] 以下营养素摄入不足: {', '.join(missing[:5])}")
142
  for n in missing[:3]:
143
  info = nutrition_coverage[n]
144
+ suggestions.append(f" -> 建议补充 {n}({info['benefit']}):可多吃 {', '.join(info['recommended_foods'][:3])}")
145
 
146
  if diversity["score"] < 60:
147
+ suggestions.append("[!] 饮食多样性不足,建议增加食物种类")
148
  elif diversity["score"] >= 80:
149
+ suggestions.append("[OK] 饮食多样性良好,继续保持!")
150
 
151
+ suggestions.append(" 建议每天摄入12种以上食物,每周25种以上")
152
+ suggestions.append(" 保证每天1.5-2L饮水")
153
 
154
  return suggestions
155
 
 
176
  ax1.set_xticks(angles[:-1])
177
  ax1.set_xticklabels(nutrients, fontsize=9)
178
  ax1.set_ylim(0, 1.2)
179
+ ax1.set_title('营养覆盖雷达图', pad=20, fontsize=13, fontweight='bold')
180
 
181
  # 2. 各餐次频率柱状图
182
  ax2 = fig.add_subplot(2, 2, 2)
 
184
  counts = list(analysis["meal_counts"].values())
185
  colors = ['#FF9AA2', '#FFB7B2', '#FFDAC1', '#E2F0CB']
186
  bars = ax2.bar(meals, counts, color=colors, edgecolor='white', linewidth=1.5)
187
+ ax2.set_title('各餐次记录频率', fontsize=13, fontweight='bold')
188
  ax2.set_ylabel('记录次数')
189
  for bar, count in zip(bars, counts):
190
  ax2.text(bar.get_x() + bar.get_width() / 2, bar.get_height() + 0.1,
 
198
  wedgeprops={'width': 0.3, 'edgecolor': 'white'})
199
  ax3.text(0, 0, f'{score}', ha='center', va='center', fontsize=28, fontweight='bold')
200
  ax3.text(0, -0.15, '多样性评分', ha='center', va='center', fontsize=10, color='gray')
201
+ ax3.set_title('饮食多样性评分', fontsize=13, fontweight='bold')
202
 
203
  # 4. 建议文本
204
  ax4 = fig.add_subplot(2, 2, 4)
205
  ax4.axis('off')
206
  suggestions = analysis.get("suggestions", [])
207
  if suggestions:
208
+ text = "-- 营养建议 --\n" + "\n".join(f"* {s}" for s in suggestions[:6])
209
  else:
210
+ text = "[OK] 营养状况良好!"
211
  ax4.text(0.05, 0.95, text, transform=ax4.transAxes,
212
  fontsize=10, verticalalignment='top',
213
  fontfamily='sans-serif',
 
223
 
224
  lines = [
225
  "=" * 50,
226
+ "--- 孕期营养分析报告 ---",
227
  "=" * 50,
228
+ f"分析周期: {analysis['total_days']} 天",
229
+ f"记录总数: {analysis['total_records']} 条",
230
  "",
231
+ "饮食多样性评分: {}/100".format(analysis['diversity_score']['score']),
232
  ]
233
 
234
  lines.append("")
235
+ lines.append("各餐次记录情况:")
236
  for detail in analysis['diversity_score']['details']:
237
  lines.append(f" {detail}")
238
 
239
  lines.append("")
240
+ lines.append("营���覆盖情况:")
241
  for nutrient, info in analysis['nutrition_coverage'].items():
242
+ status = "[OK]" if info['covered'] else "[!!]"
243
  matched = ", ".join(info['matched_foods']) if info['matched_foods'] else "无"
244
  lines.append(f" {status} {nutrient}: 匹配食物 [{matched}]")
245
+ lines.append(f" -> {info['benefit']}")
246
 
247
  lines.append("")
248
+ lines.append("改善建议:")
249
  for s in analysis.get("suggestions", []):
250
  lines.append(f" {s}")
251
 
 
262
 
263
  md_path = REPORTS_DIR / filename
264
 
265
+ content = f"""# 孕期营养分析报告
266
 
267
+ ## 基本信息
268
  - **分析周期**: {analysis.get('total_days', 0)} 天
269
  - **记录总数**: {analysis.get('total_records', 0)} 条
270
  - **生成时间**: {datetime.datetime.now().strftime('%Y-%m-%d %H:%M')}
271
 
272
+ ## 饮食多样性评分
273
  **评分: {analysis.get('diversity_score', {}).get('score', 0)}/100**
274
 
275
  | 餐次 | 记录天数 | 覆盖率 |
 
281
  content += f"| {parts[0]} | {parts[1]} |\n"
282
 
283
  content += """
284
+ ## 营养覆盖分析
285
 
286
  | 营养素 | 状态 | 匹配食物 | 功效 |
287
  |--------|------|---------|------|
288
  """
289
  for nutrient, info in analysis.get('nutrition_coverage', {}).items():
290
+ status = "OK" if info['covered'] else "!!"
291
  matched = ", ".join(info['matched_foods']) if info['matched_foods'] else "-"
292
  content += f"| {nutrient} | {status} | {matched} | {info['benefit']} |\n"
293
 
294
  content += """
295
+ ## 改善建议
296
 
297
  """
298
  for s in analysis.get('suggestions', []):
ui/__pycache__/app_builder.cpython-311.pyc CHANGED
Binary files a/ui/__pycache__/app_builder.cpython-311.pyc and b/ui/__pycache__/app_builder.cpython-311.pyc differ
 
ui/app_builder.py CHANGED
@@ -34,97 +34,64 @@ nutrition_analyzer = NutritionAnalyzer()
34
  # Tab 1: 🏠 首页
35
  # ============================================================
36
  def _build_home_tab(loop, lang_state):
37
- """构建首页(语音启动页 + 报卡片 + AI 思考状态)"""
38
  lang = lang_state.value if hasattr(lang_state, 'value') else "zh"
39
 
40
- # 提取简报数据
41
- cards = get_home_cards(loop)
42
 
43
  # ========== 语言相关文本 ==========
44
  _home = {
45
  "zh": {
46
- "title": "# 🌸 欢迎来到 PregoPal",
47
- "subtitle": "### 你的孕期AI伴侣,时刻陪伴在你身边",
48
- "tap_speak": "🎙️ 点击说话",
49
- "tap_hint": "全双工语音交互,边边听",
50
- "trimester_label": "孕期阶段",
51
- "nutrition_focus": "营养关注",
52
- "today_diet": "昨日饮食",
53
- "family_recipe": "家庭菜谱",
54
- "weight_label": "体重管理",
55
- "thinking_label": "🤔 AI 思考中...",
56
- "thinking_placeholder": "等待对话中...",
57
- "meal_count_unit": "餐",
58
- "no_data": "暂无数据",
59
- "no_record": "暂无记录",
60
- "recipes_unit": "道家常菜",
61
- "no_recipe": "还没有菜谱",
62
  },
63
  "en": {
64
- "title": "# 🌸 Welcome to PregoPal",
65
- "subtitle": "### Your AI pregnancy companion, always by your side",
66
- "tap_speak": "🎙️ Tap to Speak",
67
- "tap_hint": "Full-duplex voice interaction",
68
- "trimester_label": "Trimester",
69
- "nutrition_focus": "Nutrition Focus",
70
- "today_diet": "Yesterday's Diet",
71
- "family_recipe": "Family Recipes",
72
- "weight_label": "Weight",
73
- "thinking_label": "🤔 AI Thinking...",
74
- "thinking_placeholder": "Waiting for conversation...",
75
- "meal_count_unit": " meals",
76
- "no_data": "No data",
77
- "no_record": "No records",
78
- "recipes_unit": " recipes",
79
- "no_recipe": "No recipes yet",
80
  }
81
  }
82
  T = _home[lang]
83
 
84
- # ========== 卡片数据渲染 ==========
85
-
86
- # 营养关注
87
- nutrition_display = "、".join(cards["focus_nutrients"][:5]) if cards["focus_nutrients"] else T["no_data"]
88
- if cards["recommended_foods"]:
89
- food_str = "、".join(cards["recommended_foods"][:5])
90
- nutrition_display += f"\n\n🍽️ 推荐:{food_str}"
91
-
92
- # 昨日饮食
93
- diet_display = cards["yesterday_summary"]
94
- if cards["meal_count"] > 0:
95
- diet_display = f"{diet_display}\n({cards['meal_count']}{T['meal_count_unit']})"
96
-
97
- # 家庭菜谱
98
- if cards["recipe_count"] > 0:
99
- recipe_display = f"{cards['recipe_count']}{T['recipes_unit']}:{'、'.join(cards['recipe_names'])}"
100
  else:
101
- recipe_display = T["no_recipe"]
102
-
103
- # 体重
104
- weight_display = cards["weight_status"]
105
- if cards["weight_trend"]:
106
- weight_display += f" | {cards['weight_trend']}"
107
 
108
- # 思考关键词
109
- thinking_text = cards["thinking_keywords"] if cards["thinking_keywords"] else T["thinking_placeholder"]
110
-
111
- # ========== 布局:上部语音按钮 + 下部卡片行 ==========
112
  with gr.Column(elem_classes=["home-container"]):
113
  # 标题
114
  gr.Markdown(T["title"])
115
  gr.Markdown(T["subtitle"])
116
 
117
- # 语音启动按钮(居中)
118
  with gr.Row():
119
  with gr.Column(scale=1):
120
  pass
121
  with gr.Column(scale=2):
122
  gr.HTML(f"""
123
- <div style="text-align: center; padding: 24px 0;">
124
  <button class="voice-main-btn" onclick="document.querySelectorAll('.tabs button')[1].click()">
125
  🎙️<br><span style="font-size:14px">{T['tap_speak']}</span>
126
  </button>
127
- <p style="margin-top: 12px; color: #999; font-size: 14px;">{T['tap_hint']}</p>
128
  </div>
129
  """)
130
  with gr.Column(scale=1):
@@ -132,45 +99,17 @@ def _build_home_tab(loop, lang_state):
132
 
133
  # AI 思考状态(实时显示)
134
  thinking_box = gr.Textbox(
135
- value=f"{T['thinking_label']}\n{thinking_text}",
136
  label="",
137
  interactive=False,
138
- lines=2,
139
  elem_classes=["thinking-box"],
140
  )
141
 
142
- # ========== 卡片行 ==========
143
- with gr.Row():
144
- # 孕期阶段卡片
145
- with gr.Column(scale=1):
146
- with gr.Group(elem_classes=["home-card", "card-trimester"]):
147
- gr.Markdown(f"### 📅 {T['trimester_label']}")
148
- gr.Markdown(f"**{cards['trimester']}**")
149
-
150
- # 营养关注卡片
151
- with gr.Column(scale=1):
152
- with gr.Group(elem_classes=["home-card", "card-nutrition"]):
153
- gr.Markdown(f"### 🥗 {T['nutrition_focus']}")
154
- gr.Markdown(nutrition_display)
155
-
156
- # 昨日饮食卡片
157
- with gr.Column(scale=1):
158
- with gr.Group(elem_classes=["home-card", "card-diet"]):
159
- gr.Markdown(f"### 🍽️ {T['today_diet']}")
160
- gr.Markdown(diet_display)
161
-
162
- with gr.Row():
163
- # 家庭菜谱卡片
164
- with gr.Column(scale=1):
165
- with gr.Group(elem_classes=["home-card", "card-recipe"]):
166
- gr.Markdown(f"### 🍳 {T['family_recipe']}")
167
- gr.Markdown(recipe_display)
168
-
169
- # 体重管理卡片
170
- with gr.Column(scale=1):
171
- with gr.Group(elem_classes=["home-card", "card-weight"]):
172
- gr.Markdown(f"### ⚖️ {T['weight_label']}")
173
- gr.Markdown(weight_display)
174
 
175
  return thinking_box
176
 
 
34
  # Tab 1: 🏠 首页
35
  # ============================================================
36
  def _build_home_tab(loop, lang_state):
37
+ """构建首页(简洁对话入口 + AI 思考状态)"""
38
  lang = lang_state.value if hasattr(lang_state, 'value') else "zh"
39
 
40
+ from modules.family_manager import MemoryManager
 
41
 
42
  # ========== 语言相关文本 ==========
43
  _home = {
44
  "zh": {
45
+ "title": "# 🌸 PregoPal",
46
+ "subtitle": "你的孕期AI伴侣,用对话记录每一天",
47
+ "tap_speak": "🎙️ 开始对话",
48
+ "tap_hint": "说说今天吃了什么、心情怎么样...",
49
+ "thinking_label": "🤔 PregoPal 在想...",
50
+ "thinking_placeholder": "等待你的对话开启...",
51
+ "recent_log_label": "📝 最近的饮食记录",
52
+ "no_record": "还没有记录,开始对话吧!",
 
 
 
 
 
 
 
 
53
  },
54
  "en": {
55
+ "title": "# 🌸 PregoPal",
56
+ "subtitle": "Your pregnancy AI companion just talk naturally",
57
+ "tap_speak": "🎙️ Start Talking",
58
+ "tap_hint": "Tell me what you ate, how you feel...",
59
+ "thinking_label": "🤔 PregoPal is thinking...",
60
+ "thinking_placeholder": "Waiting for you...",
61
+ "recent_log_label": "📝 Recent Logs",
62
+ "no_record": "No records yet. Start a conversation!",
 
 
 
 
 
 
 
 
63
  }
64
  }
65
  T = _home[lang]
66
 
67
+ # 获取最近饮食摘要
68
+ records = DietLogger().get_recent_records(days=3)
69
+ if records:
70
+ recent_lines = []
71
+ for r in records[-4:]:
72
+ meals_str = "、".join(f"{k}:{v}" for k, v in r.get("meals", {}).items())
73
+ recent_lines.append(f" {r['date']} {r.get('member_name','')} {meals_str}")
74
+ recent_display = "\n".join(recent_lines)
 
 
 
 
 
 
 
 
75
  else:
76
+ recent_display = T["no_record"]
 
 
 
 
 
77
 
78
+ # ========== 布局:极简首页 ==========
 
 
 
79
  with gr.Column(elem_classes=["home-container"]):
80
  # 标题
81
  gr.Markdown(T["title"])
82
  gr.Markdown(T["subtitle"])
83
 
84
+ # 语音启动按钮(居中大按钮
85
  with gr.Row():
86
  with gr.Column(scale=1):
87
  pass
88
  with gr.Column(scale=2):
89
  gr.HTML(f"""
90
+ <div style="text-align: center; padding: 32px 0;">
91
  <button class="voice-main-btn" onclick="document.querySelectorAll('.tabs button')[1].click()">
92
  🎙️<br><span style="font-size:14px">{T['tap_speak']}</span>
93
  </button>
94
+ <p style="margin-top: 16px; color: #888; font-size: 15px;">{T['tap_hint']}</p>
95
  </div>
96
  """)
97
  with gr.Column(scale=1):
 
99
 
100
  # AI 思考状态(实时显示)
101
  thinking_box = gr.Textbox(
102
+ value=f"{T['thinking_label']}\n{T['thinking_placeholder']}",
103
  label="",
104
  interactive=False,
105
+ lines=3,
106
  elem_classes=["thinking-box"],
107
  )
108
 
109
+ # 简短最近记录摘要
110
+ with gr.Group(elem_classes=["home-card"]):
111
+ gr.Markdown(f"### {T['recent_log_label']}")
112
+ gr.Markdown(recent_display)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
113
 
114
  return thinking_box
115