Spaces:
Runtime error
Runtime error
merge: 解决合并冲突
Browse files- README.md +91 -18
- ui/app_builder.py +4 -8
- utils.py +83 -80
README.md
CHANGED
|
@@ -39,14 +39,14 @@ PregoPal 是一款面向孕期家庭的 AI 陪护工具,支持:
|
|
| 39 |
| 🍽️ 今日菜品推荐(营养+家庭能力) | 基线可用 | `modules/meal_recommender.py` |
|
| 40 |
| 📝 饮食记录储存(JSON + Markdown) | 已实现 | `modules/diet_logger.py` + `modules/diet_extractor.py` |
|
| 41 |
| 📊 营养分析与可视化报告 | 基线可用 | `modules/nutrition_analyzer.py` + `modules/nutrition_standards.py` |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 42 |
|
| 43 |
-
**技术栈**: Python 3.13 · Gradio 6.16 · MiniCPM-o 4.5 · llama-cpp-python (CUDA) · Modal T4 · Matplotlib · Pandas
|
| 44 |
|
| 45 |
-
**模型部署**: MiniCPM-o-4_5 GGUF (Q4_K_M) on Modal (T4 GPU, $0.50/hr)
|
| 46 |
-
- API 端点: `https://andrew-jiabin--prego-pal-minicpm-serve.modal.run`
|
| 47 |
-
- 集成方式: `core/model_loader.py` ←HTTP→ Modal API (OpenAI 兼容)
|
| 48 |
-
- 模型数据: Modal Volume `minicpm-o-4_5-models`(持久化,仅需上传一次)
|
| 49 |
-
- 详见: `modal_deploy/README.md`
|
| 50 |
|
| 51 |
---
|
| 52 |
|
|
@@ -54,7 +54,7 @@ PregoPal 是一款面向孕期家庭的 AI 陪护工具,支持:
|
|
| 54 |
|
| 55 |
```
|
| 56 |
app.py ← Gradio 薄入口
|
| 57 |
-
├── ui/app_builder.py ← 前端 Tab 布局(
|
| 58 |
├── loop.py ← 每日自动分析循环(状态机)
|
| 59 |
│ ├── plugins/base.py ← 插件基类(LoopPlugin, PluginRegistry, LoopContext)
|
| 60 |
│ ├── plugins/family_quiz.py ← 家庭问卷插件(菜谱/体重检查)
|
|
@@ -63,7 +63,7 @@ app.py ← Gradio 薄入口
|
|
| 63 |
│ ├── plugins/family_memory.py← 家庭记忆处理
|
| 64 |
│ ├── plugins/dri_analysis.py ← DRIs 营养对比分析
|
| 65 |
│ ├── plugins/briefing_generator.py ← 今日简报生成
|
| 66 |
-
│ ├── plugins/three_day_summary.py ←
|
| 67 |
│ └── plugins/preset_writer.py ← 预设/缓存写入
|
| 68 |
├── modules/ ← 核心业务逻辑(纯函数/无状态)
|
| 69 |
│ ├── voiceprint.py ← 声纹识别
|
|
@@ -79,7 +79,10 @@ app.py ← Gradio 薄入口
|
|
| 79 |
│ ├── vision_processor.py ← 视觉处理
|
| 80 |
│ └── conversation_manager.py ← 对话管理
|
| 81 |
├── config.py ← 全局配置(路径/常量/数据模板)
|
| 82 |
-
├── utils.py ← 工具函数(CSS/翻译/HTML渲染/
|
|
|
|
|
|
|
|
|
|
| 83 |
└── data/ ← 持久化存储
|
| 84 |
├── diet_logs.json ← 结构化饮食记录 JSON
|
| 85 |
├── nutrition_db.json ← 营养数据库 JSON
|
|
@@ -430,7 +433,7 @@ class LoopPlugin(ABC):
|
|
| 430 |
| `FamilyMemoryPlugin` | `SUMMARIZE` | `plugins/family_memory.py` | 提取家庭记忆 |
|
| 431 |
| `DRIAnalysisPlugin` | `ANALYZE` | `plugins/dri_analysis.py` | DRIs 营养对比 |
|
| 432 |
| `BriefingGeneratorPlugin` | `BRIEF` | `plugins/briefing_generator.py` | 汇总生成今日简报 |
|
| 433 |
-
| `ThreeDaySummaryPlugin` | `THREE_DAY` | `plugins/three_day_summary.py` | 三天
|
| 434 |
| `PresetWriterPlugin` | `CONSOLIDATE` | `plugins/preset_writer.py` | 写入预设/缓存 |
|
| 435 |
|
| 436 |
**插件向 `ctx.briefing` 写入的 key**(`BriefingGeneratorPlugin` 最终消费):
|
|
@@ -446,6 +449,7 @@ ctx.briefing["dri_analysis"] # dict {"focus_nutrients","summary"}
|
|
| 446 |
ctx.briefing["recommended_foods"] # list[str]
|
| 447 |
ctx.briefing["family_memory"] # dict
|
| 448 |
ctx.briefing["thinking_keywords"] # str
|
|
|
|
| 449 |
```
|
| 450 |
|
| 451 |
---
|
|
@@ -486,11 +490,15 @@ ctx.briefing["thinking_keywords"] # str
|
|
| 486 |
|
| 487 |
```
|
| 488 |
Cline A: Modules 强化(营养分析对接 DRIs、菜谱推荐 AI 化)
|
| 489 |
-
Cline B: UI 界面优化(Gradio 前端增强、报告模板美化)
|
| 490 |
Cline C: 声纹升级(Whisper encoder 替换频谱特征)
|
| 491 |
Cline D: 数据处理与插件增强(diet_extractor fallback、新插件)
|
|
|
|
| 492 |
```
|
| 493 |
|
|
|
|
|
|
|
|
|
|
| 494 |
### 7.2 关键约定
|
| 495 |
|
| 496 |
1. **改接口前先 grep**:用 `search_files` 搜索方法名找到所有调用方
|
|
@@ -545,28 +553,37 @@ chore: 配置/依赖/路径调整
|
|
| 545 |
|
| 546 |
### 8.1 页面结构
|
| 547 |
|
| 548 |
-
|
| 549 |
|
| 550 |
| Tab | ID | 功能 |
|
| 551 |
|-----|----|------|
|
| 552 |
| 🏠 首页 | `tab_home` | 语音按钮 + AI 思考 + 信息卡片 + 最近记录 |
|
| 553 |
| 👨👩👧👦 家庭 | `tab_family` | 家庭菜谱/偏好/记事 HTML 卡片展示 |
|
| 554 |
-
|
|
| 555 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 556 |
|
| 557 |
### 8.2 设计风格
|
| 558 |
|
| 559 |
- **Glassmorphism 毛玻璃**:`backdrop-filter: blur(20px)` 半透明卡片
|
| 560 |
- **粉色暖调主题**:主色 `#E91E63`、渐变 `#FF6B9D → #FFD6E0`
|
| 561 |
-
- **纯 HTML 渲染**:家庭信息卡、营养报告均使用 HTML + CSS,无需生成图片
|
| 562 |
- **中英文切换**:`lang_state` 驱动唯一 `@gr.render`
|
|
|
|
|
|
|
|
|
|
|
|
|
| 563 |
|
| 564 |
### 8.3 关键文件
|
| 565 |
|
| 566 |
| 文件 | 职责 |
|
| 567 |
|------|------|
|
| 568 |
-
| `ui/app_builder.py` | Gradio 界面构建(
|
| 569 |
-
| `utils.py` | CSS 定义 + 翻译字典 + HTML 渲染函数(家庭卡片 + 营养仪表盘) |
|
|
|
|
| 570 |
|
| 571 |
---
|
| 572 |
|
|
@@ -594,8 +611,64 @@ Model to be Used: MiniCPM-o 4.5
|
|
| 594 |
|
| 595 |
核心能力层(`core/` 目录)提供模型加载、语音处理、视觉处理、对话管理的底层能力封装。详见各文件 docstring。
|
| 596 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 597 |
---
|
| 598 |
|
| 599 |
-
*最后更新: 2026-06-
|
| 600 |
|
| 601 |
> **⚠️ 重要提醒**:本 README 是所有并行 Cline 的协作基础,任�� Cline 在修改代码后,若涉及接口、数据格式、配置的变更,必须同步更新本文件。不得随意简化、删除结构性内容。
|
|
|
|
| 39 |
| 🍽️ 今日菜品推荐(营养+家庭能力) | 基线可用 | `modules/meal_recommender.py` |
|
| 40 |
| 📝 饮食记录储存(JSON + Markdown) | 已实现 | `modules/diet_logger.py` + `modules/diet_extractor.py` |
|
| 41 |
| 📊 营养分析与可视化报告 | 基线可用 | `modules/nutrition_analyzer.py` + `modules/nutrition_standards.py` |
|
| 42 |
+
| 🤖 多模态大模型后端(MiniCPM-o 4.5) | ✅ 已部署 Modal | `modal_deploy/deploy.py` + `modal_deploy/client.py` |
|
| 43 |
+
|
| 44 |
+
**技术栈**: Python 3.13 · Gradio 6.16 · MiniCPM-o 4.5 · Modal · Matplotlib · Pandas
|
| 45 |
+
|
| 46 |
+
**大模型部署状态**:✅ 已完成,详见 [第11节](#11-多模态大模型部署-modal)
|
| 47 |
+
|
| 48 |
|
|
|
|
| 49 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 50 |
|
| 51 |
---
|
| 52 |
|
|
|
|
| 54 |
|
| 55 |
```
|
| 56 |
app.py ← Gradio 薄入口
|
| 57 |
+
├── ui/app_builder.py ← 前端 Tab 布局(3 Tab: 首页/家庭/营养报告)
|
| 58 |
├── loop.py ← 每日自动分析循环(状态机)
|
| 59 |
│ ├── plugins/base.py ← 插件基类(LoopPlugin, PluginRegistry, LoopContext)
|
| 60 |
│ ├── plugins/family_quiz.py ← 家庭问卷插件(菜谱/体重检查)
|
|
|
|
| 63 |
│ ├── plugins/family_memory.py← 家庭记忆处理
|
| 64 |
│ ├── plugins/dri_analysis.py ← DRIs 营养对比分析
|
| 65 |
│ ├── plugins/briefing_generator.py ← 今日简报生成
|
| 66 |
+
│ ├── plugins/three_day_summary.py ← 三天深度缺失分析(数据由营养报告Tab调用)
|
| 67 |
│ └── plugins/preset_writer.py ← 预设/缓存写入
|
| 68 |
├── modules/ ← 核心业务逻辑(纯函数/无状态)
|
| 69 |
│ ├── voiceprint.py ← 声纹识别
|
|
|
|
| 79 |
│ ├── vision_processor.py ← 视觉处理
|
| 80 |
│ └── conversation_manager.py ← 对话管理
|
| 81 |
├── config.py ← 全局配置(路径/常量/数据模板)
|
| 82 |
+
├── utils.py ← 工具函数(CSS/翻译/HTML渲染/营养报告+三天缺失分析)
|
| 83 |
+
├── modal_deploy/ ← Modal 部署层
|
| 84 |
+
│ ├── deploy.py ← Modal ASGI 部署入口(llama-server → FastAPI)
|
| 85 |
+
│ └── client.py ← Python API 客户端(chat / chat_with_image / embed)
|
| 86 |
└── data/ ← 持久化存储
|
| 87 |
├── diet_logs.json ← 结构化饮食记录 JSON
|
| 88 |
├── nutrition_db.json ← 营养数据库 JSON
|
|
|
|
| 433 |
| `FamilyMemoryPlugin` | `SUMMARIZE` | `plugins/family_memory.py` | 提取家庭记忆 |
|
| 434 |
| `DRIAnalysisPlugin` | `ANALYZE` | `plugins/dri_analysis.py` | DRIs 营养对比 |
|
| 435 |
| `BriefingGeneratorPlugin` | `BRIEF` | `plugins/briefing_generator.py` | 汇总生成今日简报 |
|
| 436 |
+
| `ThreeDaySummaryPlugin` | `THREE_DAY` | `plugins/three_day_summary.py` | 三天深度缺失分析(被营养报告Tab调用) |
|
| 437 |
| `PresetWriterPlugin` | `CONSOLIDATE` | `plugins/preset_writer.py` | 写入预设/缓存 |
|
| 438 |
|
| 439 |
**插件向 `ctx.briefing` 写入的 key**(`BriefingGeneratorPlugin` 最终消费):
|
|
|
|
| 449 |
ctx.briefing["recommended_foods"] # list[str]
|
| 450 |
ctx.briefing["family_memory"] # dict
|
| 451 |
ctx.briefing["thinking_keywords"] # str
|
| 452 |
+
ctx.briefing["three_day_summary"] # dict(由报告Tab选择性地读取)
|
| 453 |
```
|
| 454 |
|
| 455 |
---
|
|
|
|
| 490 |
|
| 491 |
```
|
| 492 |
Cline A: Modules 强化(营养分析对接 DRIs、菜谱推荐 AI 化)
|
| 493 |
+
Cline B: UI 界面优化(Gradio 前端增强、报告模板美化) ← 当前 Cline
|
| 494 |
Cline C: 声纹升级(Whisper encoder 替换频谱特征)
|
| 495 |
Cline D: 数据处理与插件增强(diet_extractor fallback、新插件)
|
| 496 |
+
Cline E: 大模型集成(对接 Modal API,实现全双工语音对话) ← 新增(部署已完成)
|
| 497 |
```
|
| 498 |
|
| 499 |
+
> 注:MiniCPM-o 4.5 已在 Modal 完成部署,API 客户端 `modal_deploy/client.py` 已就绪。
|
| 500 |
+
> 接下来可并行推进:A) 前端接入大模型 B) 营养分析对接 DRIs C) 声纹升级。
|
| 501 |
+
|
| 502 |
### 7.2 关键约定
|
| 503 |
|
| 504 |
1. **改接口前先 grep**:用 `search_files` 搜索方法名找到所有调用方
|
|
|
|
| 553 |
|
| 554 |
### 8.1 页面结构
|
| 555 |
|
| 556 |
+
3 Tab 布局,使用 **唯一一个 `@gr.render`** 包裹全部 Tab(避免 Gradio 内部字典迭代崩溃):
|
| 557 |
|
| 558 |
| Tab | ID | 功能 |
|
| 559 |
|-----|----|------|
|
| 560 |
| 🏠 首页 | `tab_home` | 语音按钮 + AI 思考 + 信息卡片 + 最近记录 |
|
| 561 |
| 👨👩👧👦 家庭 | `tab_family` | 家庭菜谱/偏好/记事 HTML 卡片展示 |
|
| 562 |
+
| 📈 营养报告 | `tab_report` | 纯 HTML 营养仪表盘 + 三天缺失深度分析(已合并) |
|
| 563 |
+
|
| 564 |
+
> **架构变更说明**(v2.0):
|
| 565 |
+
> - 原「三天总结」Tab **已合并到营养报告 Tab** 中,作为一个统一下沉展示区块
|
| 566 |
+
> - 营养报告使用 Slider 防抖自动生成(0.8s),包含基础营养评分 + 三天缺失深度分析
|
| 567 |
+
> - 三天缺失分析页面使用与营养报告一致的设计语言(渐变 Header + 紧凑标签 + 结构化 Summary)
|
| 568 |
|
| 569 |
### 8.2 设计风格
|
| 570 |
|
| 571 |
- **Glassmorphism 毛玻璃**:`backdrop-filter: blur(20px)` 半透明卡片
|
| 572 |
- **粉色暖调主题**:主色 `#E91E63`、渐变 `#FF6B9D → #FFD6E0`
|
| 573 |
+
- **纯 HTML 渲染**:家庭信息卡、营养报告、三天缺失分析均使用 HTML + CSS,无需生成图片
|
| 574 |
- **中英文切换**:`lang_state` 驱动唯一 `@gr.render`
|
| 575 |
+
- **三天缺失分析**:
|
| 576 |
+
- 持续不足营养素采用**紧凑标签式**显示(pill 标签,一行多个)
|
| 577 |
+
- 食材补充建议区域采用 **1:2 扩大布局**(右侧占 2/3 宽度,每行 6 个食物标签)
|
| 578 |
+
- 总结部分采用**结构化 UI 卡片**(红色/橙色/绿色 左侧边框,按严重程度分类显示)
|
| 579 |
|
| 580 |
### 8.3 关键文件
|
| 581 |
|
| 582 |
| 文件 | 职责 |
|
| 583 |
|------|------|
|
| 584 |
+
| `ui/app_builder.py` | Gradio 界面构建(3 Tab + 语言切换 + 报告自动生成) |
|
| 585 |
+
| `utils.py` | CSS 定义 + 翻译字典 + HTML 渲染函数(家庭卡片 + 营养仪表盘 + 三天缺失分析) |
|
| 586 |
+
| `plugins/three_day_summary.py` | 三天缺失分析插件(数据层,被报告 Tab 异步调用) |
|
| 587 |
|
| 588 |
---
|
| 589 |
|
|
|
|
| 611 |
|
| 612 |
核心能力层(`core/` 目录)提供模型加载、语音处理、视觉处理、对话管理的底层能力封装。详见各文件 docstring。
|
| 613 |
|
| 614 |
+
**部署状态**: ✅ 已完成 Modal 部署(见第11节)
|
| 615 |
+
|
| 616 |
+
---
|
| 617 |
+
|
| 618 |
+
## 11. 多模态大模型部署 (Modal)
|
| 619 |
+
|
| 620 |
+
### 11.1 部署状态
|
| 621 |
+
|
| 622 |
+
| 项目 | 状态 |
|
| 623 |
+
|------|------|
|
| 624 |
+
| API URL | `https://andrew-jiabin--prego-pal-minicpm-serve.modal.run` |
|
| 625 |
+
| Modal App | `prego-pal-minicpm` |
|
| 626 |
+
| GPU | T4 (16GB) |
|
| 627 |
+
| 模型 | MiniCPM-o 4.5 Q4_K_M (~12GB) + vision/audio/TTS 投影层 |
|
| 628 |
+
| 冷启动 | ~2-8 分钟(首次请求触发容器启动 + 模型加载) |
|
| 629 |
+
| 闲置缩容 | 300s 无请求自动缩容 |
|
| 630 |
+
| 并发 | `max_inputs=10` |
|
| 631 |
+
|
| 632 |
+
### 11.2 API 端点
|
| 633 |
+
|
| 634 |
+
| 端点 | 方法 | 功能 |
|
| 635 |
+
|------|------|------|
|
| 636 |
+
| `/v1/chat/completions` | POST | OpenAI 兼容聊天(文本) |
|
| 637 |
+
| `/v1/embeddings` | POST | 文本嵌入 |
|
| 638 |
+
| `/v1/models` | GET | 模型列表 |
|
| 639 |
+
| `/v1/multimodal/chat` | POST | 多模态(图片+文本) |
|
| 640 |
+
| `/health` | GET | 健康检查 |
|
| 641 |
+
| `/` | GET | 服务信息 |
|
| 642 |
+
|
| 643 |
+
### 11.3 Python 客户端
|
| 644 |
+
|
| 645 |
+
```python
|
| 646 |
+
from modal_deploy.client import MiniCPMClient
|
| 647 |
+
|
| 648 |
+
client = MiniCPMClient(base_url="https://andrew-jiabin--prego-pal-minicpm-serve.modal.run")
|
| 649 |
+
|
| 650 |
+
# 文本对话
|
| 651 |
+
response = client.ask("今天孕妇可以吃什么?")
|
| 652 |
+
|
| 653 |
+
# 多模态(图片理解)
|
| 654 |
+
response = client.describe_image(image_base64_str)
|
| 655 |
+
|
| 656 |
+
# 嵌入向量
|
| 657 |
+
embeddings = client.embed(["孕期饮食建议", "番茄牛腩"])
|
| 658 |
+
```
|
| 659 |
+
|
| 660 |
+
详见 `modal_deploy/client.py` 完整文档。
|
| 661 |
+
|
| 662 |
+
### 11.4 部署文件
|
| 663 |
+
|
| 664 |
+
| 文件 | 说明 |
|
| 665 |
+
|------|------|
|
| 666 |
+
| `modal_deploy/deploy.py` | Modal ASGI 部署入口(llama-server → FastAPI 包装) |
|
| 667 |
+
| `modal_deploy/client.py` | Python API 客户端(chat / chat_with_image / embed) |
|
| 668 |
+
| `docs/部署经验_Modal_MiniCPM-o.md` | 详细部署经验文档(踩坑记录、成本估算、后续优化) |
|
| 669 |
+
|
| 670 |
---
|
| 671 |
|
| 672 |
+
*最后更新: 2026-06-10 | 由 PregoPal Cline 团队维护*
|
| 673 |
|
| 674 |
> **⚠️ 重要提醒**:本 README 是所有并行 Cline 的协作基础,任�� Cline 在修改代码后,若涉及接口、数据格式、配置的变更,必须同步更新本文件。不得随意简化、删除结构性内容。
|
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 |
-
"""生成营养报告
|
| 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 |
-
|
| 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 |
-
|
| 106 |
-
"
|
|
|
|
| 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 |
-
|
| 172 |
-
"
|
|
|
|
| 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
|
| 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
|
| 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:
|
| 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 |
-
<
|
|
|
|
| 670 |
|
| 671 |
-
<!--
|
| 672 |
-
<div style="background:linear-gradient(135deg,#
|
| 673 |
<div style="display:flex;justify-content:space-between;align-items:center;">
|
| 674 |
<div>
|
| 675 |
-
<div style="font-size:20px;font-weight:700;color:#
|
| 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:#
|
| 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 |
# ============================================================
|