Spaces:
Runtime error
Runtime error
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 +27 -27
- ui/__pycache__/app_builder.cpython-311.pyc +0 -0
- ui/app_builder.py +37 -98
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 = "
|
| 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"
|
| 142 |
for n in missing[:3]:
|
| 143 |
info = nutrition_coverage[n]
|
| 144 |
-
suggestions.append(f"
|
| 145 |
|
| 146 |
if diversity["score"] < 60:
|
| 147 |
-
suggestions.append("
|
| 148 |
elif diversity["score"] >= 80:
|
| 149 |
-
suggestions.append("
|
| 150 |
|
| 151 |
-
suggestions.append("
|
| 152 |
-
suggestions.append("
|
| 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('
|
| 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('
|
| 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('
|
| 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 = "
|
| 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"
|
| 229 |
-
f"
|
| 230 |
"",
|
| 231 |
-
"
|
| 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 = "
|
| 243 |
matched = ", ".join(info['matched_foods']) if info['matched_foods'] else "无"
|
| 244 |
lines.append(f" {status} {nutrient}: 匹配食物 [{matched}]")
|
| 245 |
-
lines.append(f"
|
| 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 = "
|
| 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 |
-
"""构建首页(
|
| 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": "# 🌸
|
| 47 |
-
"subtitle": "
|
| 48 |
-
"tap_speak": "🎙️
|
| 49 |
-
"tap_hint": "
|
| 50 |
-
"
|
| 51 |
-
"
|
| 52 |
-
"
|
| 53 |
-
"
|
| 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": "# 🌸
|
| 65 |
-
"subtitle": "
|
| 66 |
-
"tap_speak": "🎙️
|
| 67 |
-
"tap_hint": "
|
| 68 |
-
"
|
| 69 |
-
"
|
| 70 |
-
"
|
| 71 |
-
"
|
| 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 |
-
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
|
| 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 |
-
|
| 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:
|
| 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:
|
| 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{
|
| 136 |
label="",
|
| 137 |
interactive=False,
|
| 138 |
-
lines=
|
| 139 |
elem_classes=["thinking-box"],
|
| 140 |
)
|
| 141 |
|
| 142 |
-
#
|
| 143 |
-
with gr.
|
| 144 |
-
#
|
| 145 |
-
|
| 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 |
|