Spaces:
Runtime error
Runtime error
File size: 27,777 Bytes
35f8467 50bb88b 35f8467 50bb88b 35f8467 8193cd7 35f8467 50bb88b 35f8467 50bb88b 35f8467 50bb88b 35f8467 50bb88b 35f8467 50bb88b 35f8467 50bb88b 35f8467 50bb88b 35f8467 50bb88b 35f8467 33aa89f 35f8467 33aa89f 35f8467 33aa89f 35f8467 33aa89f 35f8467 33aa89f 35f8467 33aa89f 35f8467 50bb88b 33aa89f 35f8467 50bb88b 35f8467 50bb88b 35f8467 50bb88b 35f8467 50bb88b 35f8467 33aa89f 35f8467 33aa89f 50bb88b 6cce707 50bb88b 35f8467 33aa89f 50bb88b 33aa89f 35f8467 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 | ---
title: PregoPal
emoji: 📈
colorFrom: pink
colorTo: green
sdk: gradio
sdk_version: 6.16.0
python_version: '3.13'
app_file: app.py
pinned: false
license: mit
short_description: Voice-based Pregnant Meal & Nutrition Tracker
---
# PregoPal — 孕期陪护 AI 助手
> 🔴 **【Cline 并行协作核心文档】**
>
> 本 README 是所有并行 Cline 同步接口规范、数据结构、协作约定的唯一全局文档。
> **任何 Cline 不得随意简化、删除或修改本 README 的结构性内容**(包括但不限于:模块接口签名、数据 Schema、协作流程、冲突策略)。
>
> 修改原则:
> 1. 改接口签名 → 必须同步更新对应章节
> 2. 新增模块 → 必须在架构图/模块表中添加条目
> 3. 新增配置 → 必须在 config 章节追加说明
> 4. 数据格式变更 → 更新 Schema 表
>
> 每次 commit 前请检查:本 README 是否仍反映项目最新状态。
---
## 1. 项目概述
PregoPal 是一款面向孕期家庭的 AI 陪护工具,支持:
| 功能 | 状态 | 负责模块 |
|------|------|----------|
| 🎤 声纹识别与家庭成员区分 | 基线可用 | `modules/voiceprint.py` |
| 🍽️ 今日菜品推荐(营养+家庭能力) | 基线可用 | `modules/meal_recommender.py` |
| 📝 饮食记录储存(JSON + Markdown) | 已实现 | `modules/diet_logger.py` + `modules/diet_extractor.py` |
| 📊 营养分析与可视化报告 | 基线可用 | `modules/nutrition_analyzer.py` + `modules/nutrition_standards.py` |
| 🤖 多模态大模型后端(MiniCPM-o 4.5) | ✅ 已部署 Modal | `modal_deploy/deploy.py` + `modal_deploy/client.py` |
**技术栈**: Python 3.13 · Gradio 6.16 · MiniCPM-o 4.5 · Modal · Matplotlib · Pandas
**大模型部署状态**:✅ 已完成,详见 [第11节](#11-多模态大模型部署-modal)
---
## 2. 架构总览
```
app.py ← Gradio 薄入口
├── ui/app_builder.py ← 前端 Tab 布局(3 Tab: 首页/家庭/营养报告)
├── loop.py ← 每日自动分析循环(状态机)
│ ├── plugins/base.py ← 插件基类(LoopPlugin, PluginRegistry, LoopContext)
│ ├── plugins/family_quiz.py ← 家庭问卷插件(菜谱/体重检查)
│ ├── plugins/diet_summary.py ← 昨日饮食总结
│ ├── plugins/weight_check.py ← 体重检查插件
│ ├── plugins/family_memory.py← 家庭记忆处理
│ ├── plugins/dri_analysis.py ← DRIs 营养对比分析
│ ├── plugins/briefing_generator.py ← 今日简报生成
│ ├── plugins/three_day_summary.py ← 三天深度缺失分析(数据由营养报告Tab调用)
│ └── plugins/preset_writer.py ← 预设/缓存写入
├── modules/ ← 核心业务逻辑(纯函数/无状态)
│ ├── voiceprint.py ← 声纹识别
│ ├── meal_recommender.py ← 菜品推荐
│ ├── diet_extractor.py ← AI 回复数据提取
│ ├── diet_logger.py ← 饮食记录存储
│ ├── family_manager.py ← 家庭信息管理
│ ├── nutrition_standards.py ← 中国官方营养标准
│ ├── nutrition_analyzer.py ← 营养分析与可视化
│ └── core/ ← 核心能力层
│ ├── model_loader.py ← MiniCPM-o 模型加载
│ ├── voice_processor.py ← 语音处理
│ ├── vision_processor.py ← 视觉处理
│ └── conversation_manager.py ← 对话管理
├── config.py ← 全局配置(路径/常量/数据模板)
├── utils.py ← 工具函数(CSS/翻译/HTML渲染/营养报告+三天缺失分析)
├── modal_deploy/ ← Modal 部署层
│ ├── deploy.py ← Modal ASGI 部署入口(llama-server → FastAPI)
│ └── client.py ← Python API 客户端(chat / chat_with_image / embed)
└── data/ ← 持久化存储
├── diet_logs.json ← 结构化饮食记录 JSON
├── nutrition_db.json ← 营养数据库 JSON
├── family.json ← 家庭成员声纹 JSON
├── family/ ← 家庭信息 Markdown(recipes/preferences/memory)
├── logs/ ← 每日饮食日志 Markdown
├── presets/ ← 预设/缓存 + .daily_status.json
├── reports/ ← 营养报告 Markdown
├── voices/ ← 声纹音频文件
└── nutrition/ ← 营养标准原始文档
```
---
## 3. 核心模块接口说明
> **约定**:模块间通过函数调用传递数据。所有返回 dict 的接口,其 key 约定在下方列出。
> **修改清单**:改任何模块的输入/输出签名时,必须同步更新本 README 对应条目。
### 3.1 声纹识别 — `modules/voiceprint.py`
类:`VoiceprintManager`
| 方法 | 输入 | 输出 | 说明 |
|------|------|------|------|
| `register_member(name, relation, audio_path)` | `name: str`, `relation: str`, `audio_path: str` (Path) | `(member_info: dict\|None, msg: str)` | 注册新成员声纹,返回 `{"id","name","relation","registered_at","audio_path","features"}` |
| `identify_speaker(audio_path)` | `audio_path: str` (Path) | `(member_info: dict\|None, msg: str)` | 识别说话人,需声纹库非空 |
| `get_members_list()` | — | `str` (格式化文本) | 获取已注册成员列表 |
| `delete_member(member_id)` | `member_id: str` | `str` (结果消息) | 删除指定成员 |
**依赖**:
- `VOICE_DIR`(`data/voices/`)从 `config.py` 导入
- `FAMILY_FILE`(`data/family.json`)— JSON 格式 `{"members": [...], "voiceprints": {...}}`
- `config.VOICEPRINT_SIMILARITY_THRESHOLD`(默认 0.7)
**内部数据结构**(`data/family.json`):
```json
{
"members": [
{
"id": "abc12345",
"name": "小红",
"relation": "孕妇",
"registered_at": "2026-06-09T07:00:00",
"audio_path": "data/voices/abc12345.wav",
"features": {"mean": 0.01, "std": 0.05, "max": 0.5, "min": -0.5, "zero_crossing_rate": 0.3, "energy": 0.001, "duration": 3.5}
}
],
"voiceprints": {
"abc12345": {"mean": 0.01, ...}
}
}
```
> ⚠️ **风险等级:中** — 当前使用频谱统计特征(baseline),后续升级 Whisper encoder 需保持 `register_member`/`identify_speaker` 签名不变。
---
### 3.2 菜品推荐 — `modules/meal_recommender.py`
类:`MealRecommender`
| 方法 | 输入 | 输出 | 说明 |
|------|------|------|------|
| `get_recommendation(preference, trimester, restrictions)` | `preference: str=""`, `trimester: str="孕中期"`, `restrictions: str=""` | `dict` | 返回今日推荐食谱 |
| `format_meal_plan(recommendation)` | `recommendation: dict`(上一个方法的返回值) | `str` | 格式化为可读文本 |
**输出 dict schema**(`get_recommendation` 返回):
```json
{
"date": "2026-06-09",
"trimester": "孕中期",
"preference": "想吃清淡的",
"focus": "补充蛋白质、钙、铁",
"meals": {
"早餐": "小米粥+包子+煮鸡蛋",
"午餐": "番茄牛腩+杂粮饭+凉拌黄瓜",
"晚餐": "蒸蛋羹+小米粥+炒青菜",
"加餐": "酸奶+坚果"
},
"tips": ["🌿 孕中期建议:...", "💡 建议每天饮水..."]
}
```
**依赖**:
- `config.MEAL_TEMPLATES`(4 餐次 × 4 选项的食谱模板 dict)
- `config.TRIMESTER_ADJUSTMENTS`(各孕期阶段的 focus/avoid 建议)
- `config.TRIMESTER_TIPS`(各孕期阶段的饮食提示字符串)
> ⚠️ **风险等级:中** — 当前为随机模板推荐。后续对接 AI 对话推荐时需保持 `get_recommendation` 签名,内部逻辑可任意替换。
---
### 3.3 AI 对话数据提取 — `modules/diet_extractor.py`
类:`DietExtractor`(全静态方法)
| 方法 | 输入 | 输出 | 说明 |
|------|------|------|------|
| `extract_all(text)` | `text: str` (AI 回复全文) | `dict` | 正则提取所有结构化数据 |
| `fallback_extract_diet(text)` | `text: str` | `dict\|None` | 关键词 fallback 提取饮食 |
| `fallback_extract_thinking(text)` | `text: str` | `dict\|None` | 关键词 fallback 提取思考 |
| `robust_extract(text)` | `text: str` | `dict` | 正则优先 → fallback 兜底 |
**`extract_all` 返回 dict schema**:
```json
{
"diets": [
{
"meals": {"早餐": "全麦面包+鸡蛋+牛奶", "午餐": "清蒸鱼", "晚餐": "", "加餐": ""},
"日期": "2026-06-09",
"记录人": "孕妇",
"备注": "孕妇吃了"
}
],
"recipes": [{"菜名": "清蒸鱼", "制作人": "丈夫", "难度": "简单", "食材": "鱼、姜", "备注": ""}],
"preferences": [{"人员": "孕妇", "类型": "偏好", "内容": "爱吃酸"}],
"weights": [{"日期": "2026-06-09", "体重": "65", "记录人": "孕妇自己"}],
"memories": [{"类型": "事件", "内容": "婆婆今天来家里"}],
"thinking": {"当前步骤": "记录饮食", "下一步": "分析营养"} // or None
}
```
**Markdown 提取标记格式**(AI 回复中需含):
```
[EXTRACT_DIET]...[/EXTRACT_DIET]
[EXTRACT_RECIPE]...[/EXTRACT_RECIPE]
[EXTRACT_PREFERENCE]...[/EXTRACT_PREFERENCE]
[EXTRACT_WEIGHT]...[/EXTRACT_WEIGHT]
[EXTRACT_MEMORY]...[/EXTRACT_MEMORY]
[THINKING]...[/THINKING]
```
**辅助函数**:
- `get_extract_prompt(date_str=None) -> str` — 返回含日期占位符的 System Prompt 模板
> ✅ **风险等级:低** — 接口稳定,已存在完整测试(`tests/test_diet_extractor.py`)。
---
### 3.4 饮食记录存储 — `modules/diet_logger.py`
类:`DietLogger`
| 方法 | 输入 | 输出 | 说明 |
|------|------|------|------|
| `add_record(member_name, member_relation, date, meals, mood, notes)` | `member_name: str`, `member_relation: str`, `date: str (ISO)`, `meals: dict`, `mood: str=""`, `notes: str=""` | `(record: dict, md_path: Path)` | 添加记录 → JSON + MD 双写 |
| `get_recent_records(days=7)` | `days: int` | `list[dict]` | 获取近 N 天记录 |
| `get_all_markdown_files()` | — | `list[Path]` | 所有 MD 日志文件 |
| `parse_diet_record(text) — static` | `text: str` | `dict\|None` | [DIET_RECORD] 标记解析(后续 AI 版本用) |
**`add_record` 返回的 record dict**:
```json
{
"id": "a1b2c3d4",
"member_name": "小红",
"member_relation": "孕妇",
"date": "2026-06-09",
"meals": {"早餐": "燕麦粥+坚果", "午餐": "清蒸鱼+米饭"},
"mood": "挺好",
"notes": "今天胃口不错",
"extensions": {},
"created_at": "2026-06-09T12:00:00"
}
```
**依赖**:
- `config.DIET_LOG_FILE`(`data/diet_logs.json`)— JSON 格式
- `config.LOGS_DIR`(`data/logs/`)— Markdown 日志目录
- `config.DIET_LOG_SCHEMA_VERSION`(当前 "1.0")
**Markdown 日志文件格式**(`data/logs/饮食日志_YYYY-MM-DD.md`):
```markdown
# 🥗 孕期饮食日志
## 📋 基本信息
- **日期**: 2026-06-09
- **记录人**: 孕妇 - 小红
- **记录时间**: 2026-06-09T12:00:00
## 🍽️ 今日饮食记录
### 早餐
- 燕麦粥+坚果
... (output truncated) ...
```
---
### 3.5 家庭信息管理 — `modules/family_manager.py`
类:`RecipeManager`, `PreferenceManager`, `MemoryManager`
| 方法 | 输入 | 输出 | 说明 |
|------|------|------|------|
| `RecipeManager.add_recipe(name, maker, difficulty, ingredients, notes)` | 字段见函数签名 | `str` (结果消息) | 添加菜谱 |
| `RecipeManager.load_all()` | — | `list[dict]` | 加载所有菜谱 |
| `PreferenceManager.add_preference(member, ptype, content)` | `member/type/content: str` | `str` | 添加偏好/忌口 |
| `PreferenceManager.load_all()` | — | `list[dict]` | 加载所有偏好 |
| `MemoryManager.add_memory(mtype, content)` | `mtype/content: str` | `str` | 添加家庭记事 |
| `MemoryManager.load_all()` | — | `list[dict]` | 加载所有记忆 |
**依赖**:
- `config.FAMILY_DIR`(`data/family/`)— 目录包含 `recipes.md` / `preferences.md` / `memory.md`
---
### 3.6 营养标准 — `modules/nutrition_standards.py`
类:`DRIsParser`(全静态方法)
| 方法 | 输入 | 输出 | 说明 |
|------|------|------|------|
| `get_dri_for_trimester(trimester)` | `trimester: str`(孕早期/中期/晚期) | `dict` | 获取对应孕期 DRIs 推荐值 |
| `calculate_bmi(weight, height)` | `weight: float (kg)`, `height: float (cm)` | `float` | BMI 计算 |
| `get_weight_gain_goal(pre_bmi, trimester)` | `pre_bmi: float`, `trimester: str` | `dict` | 体重增长建议 |
| `get_dietary_guidelines(trimester)` | `trimester: str` | `dict` | 膳食指南建议 |
| `assess_nutrient_intake(actual, dri)` | `actual: dict (mg)`, `dri: dict (mg)` | `dict` | 实际 vs 推荐对比 |
**输出示例**:
```python
DRIsParser.get_dri_for_trimester("孕中期")
# 返回:
{
"能量": {"推荐摄入量_kcal": 2100, ...},
"蛋白质": {"推荐摄入量_g": 70, ...},
"钙": {"推荐摄入量_mg": 1000, ...},
"铁": {"推荐摄入量_mg": 24, ...},
"叶酸": {"推荐摄入量_ug": 600, ...},
"维生素D": {"推荐摄入量_ug": 10, ...},
"DHA": {"适宜摄入量_mg": 200, ...}
}
```
> ✅ **风险等级:低** — 接口清晰。如需调整推荐值数值,修改 `DRIsParser` 内部常量即可。
---
### 3.7 营养分析与可视化 — `modules/nutrition_analyzer.py`
类:`NutritionAnalyzer`
| 方法 | 输入 | 输出 | 说明 |
|------|------|------|------|
| `analyze_diet(records)` | `records: list[dict]` (diet_logger 格式) | `dict` | 营养覆盖分析 |
| `generate_report_chart(analysis)` | `analysis: dict`(上一个方法返回) | `matplotlib.figure.Figure` | 4 面板可视化图表 |
| `generate_report_text(analysis)` | `analysis: dict` | `str` | 文本格式报告 |
| `export_report_markdown(analysis, filename)` | `analysis: dict`, `filename: str\|None` | `Path` | 导出 Markdown 报告 |
**`analyze_diet` 返回 dict schema**(最新版,含 `score` 综合评分):
```json
{
"score": 72,
"total_days": 7,
"total_records": 21,
"meal_counts": {"早餐": 5, "午餐": 6, "晚餐": 6, "加餐": 4},
"food_items": ["全麦面包", "鸡蛋", "牛奶", "清蒸鱼", ...],
"nutrition_coverage": {
"叶酸": {
"matched_foods": ["菠菜"],
"covered": true,
"recommended_foods": ["菠菜", "西兰花", "芦笋"],
"benefit": "预防胎儿神经管畸形",
"daily_recommend": "0.4",
"unit": "mg"
},
...
},
"diversity_score": 75,
"diversity_details": ["[OK] 早餐: 6/7天 (86%)", ...],
"suggestions": ["⚠️ 以下营养素摄入不足: 铁, 钙", ...]
}
```
**图表输出**(`generate_report_chart` — 保留 matplotlib 版本):
1. 左上:营养覆盖雷达图(最多 8 种营养素)
2. 右上:各餐次频率柱状图
3. 左下:饮食多样性评分环形图
4. 右下:营养建议文本框
**依赖**:
- `config.NUTRITION_DB_FILE`(`data/nutrition_db.json`)— 可自定义
- `config.DEFAULT_NUTRITION_DB`(10 种营养素的内置推荐)
- `config.REPORTS_DIR`(`data/reports/`)
- `utils.setup_chinese_font()` — matplotlib 中文字体
> 🔴 **风险等级:高(待改造)** — 当前使用通用 `DEFAULT_NUTRITION_DB` 做营养覆盖分析,**尚未对接 `modules/nutrition_standards.py` 的 DRIs 数据**。改造时需:
> 1. 将 `_calculate_nutrition_coverage` 中的 `self.nutrition_db` 替换为 `DRIsParser.get_dri_for_trimester()`
> 2. 保持 `analyze_diet(records) -> dict` 签名不变
> 3. 保持 `nutrition_coverage` 的 dict key 不变(nutrient name 为 key)
---
### 3.8 插件基类 — `plugins/base.py`
核心类型(供所有插件和 loop 使用):
```python
# 阶段枚举
class LoopStage(Enum):
FAMILY_QUIZ = "family_quiz"
SUMMARIZE = "summarize"
ANALYZE = "analyze"
BRIEF = "brief"
THREE_DAY = "three_day"
CONSOLIDATE = "consolidate"
# 上下文容器(插件间共享)
@dataclass
class LoopContext:
briefing: dict = {} # 累积的简报数据
weight_data: dict = {}
diet_records: list = []
family_recipes: list = [] # family_manager.RecipeManager.load_all() 格式
family_memory: dict = {} # family_manager.MemoryManager.load_all() 格式
analysis_results: dict = {}
errors: list = []
# 插件结果
@dataclass
class PluginResult:
success: bool = True
data: dict = {}
message: str = ""
# 插件基类
class LoopPlugin(ABC):
@abstractmethod
def stage(self) -> LoopStage: ...
@abstractmethod
def name(self) -> str: ...
@abstractmethod
async def run(self, ctx: LoopContext) -> PluginResult: ...
```
> ✅ **风险等级:低** — 核心架构稳定。新增插件:实现 `LoopPlugin`,在 `loop.py` 的 `_register_default_plugins` 中注册即可。
---
## 4. 插件管线一览
| 插件名 | 阶段 | 文件 | 职责 |
|--------|------|------|------|
| `FamilyRecipeQuizPlugin` | `FAMILY_QUIZ` | `plugins/family_quiz.py` | 检查是否需要询问家庭菜谱 |
| `WeightQuizPlugin` | `FAMILY_QUIZ` | `plugins/family_quiz.py` | 检查是否需要询问体重 |
| `DietSummaryPlugin` | `SUMMARIZE` | `plugins/diet_summary.py` | 读取昨日饮食日志 |
| `WeightCheckPlugin` | `SUMMARIZE` | `plugins/weight_check.py` | 体重变化分析 |
| `FamilyMemoryPlugin` | `SUMMARIZE` | `plugins/family_memory.py` | 提取家庭记忆 |
| `DRIAnalysisPlugin` | `ANALYZE` | `plugins/dri_analysis.py` | DRIs 营养对比 |
| `BriefingGeneratorPlugin` | `BRIEF` | `plugins/briefing_generator.py` | 汇总生成今日简报 |
| `ThreeDaySummaryPlugin` | `THREE_DAY` | `plugins/three_day_summary.py` | 三天深度缺失分析(被营养报告Tab调用) |
| `PresetWriterPlugin` | `CONSOLIDATE` | `plugins/preset_writer.py` | 写入预设/缓存 |
**插件向 `ctx.briefing` 写入的 key**(`BriefingGeneratorPlugin` 最终消费):
```
ctx.briefing["trimester"] # str
ctx.briefing["need_ask_weight"] # bool
ctx.briefing["weight_quiz_message"] # str
ctx.briefing["weight_evaluation"] # dict
ctx.briefing["need_ask_recipe"] # bool
ctx.briefing["recipe_quiz_message"] # str
ctx.briefing["yesterday_diet"] # dict {"status","summary","meal_count"}
ctx.briefing["dri_analysis"] # dict {"focus_nutrients","summary"}
ctx.briefing["recommended_foods"] # list[str]
ctx.briefing["family_memory"] # dict
ctx.briefing["thinking_keywords"] # str
ctx.briefing["three_day_summary"] # dict(由报告Tab选择性地读取)
```
---
## 5. 配置模块 — `config.py`
每个 Cline 如需新增全局常量,**追加到同类型区域末尾**,并在 commit message 中注明。
| 配置区 | 主要内容 | 修改风险 |
|--------|---------|----------|
| 目录路径 | `DATA_DIR`, `VOICE_DIR`, `LOGS_DIR`, `REPORTS_DIR`, `FAMILY_FILE`, `DIET_LOG_FILE`, `NUTRITION_DB_FILE` | ⚠️ 中 |
| 常量枚举 | `FAMILY_ROLES`, `TRIMESTERS` | ✅ 低 |
| 数据模板 | `DEFAULT_NUTRITION_DB` (10种营养素), `MEAL_TEMPLATES` (4×4), `TRIMESTER_ADJUSTMENTS`, `TRIMESTER_TIPS` | ⚠️ 中 |
| Schema 版本 | `DIET_LOG_SCHEMA_VERSION = "1.0"` | ⚠️(改版本号前需评估向后兼容) |
| 声纹阈值 | `VOICEPRINT_SIMILARITY_THRESHOLD = 0.7` | ✅ 低 |
---
## 6. 数据文件格式规范
| 文件 | 格式 | Schema | 读模块 | 写模块 |
|------|------|--------|--------|--------|
| `data/diet_logs.json` | JSON | `{"schema_version": "1.0", "records": [...]}` | `diet_logger`, `nutrition_analyzer` | `diet_logger` |
| `data/nutrition_db.json` | JSON | `{"营养素名": {"category", "daily_recommend_mg/g/mcg", "foods": [...], "benefit"}}` | `nutrition_analyzer` | `nutrition_analyzer` (首次初始化) |
| `data/family.json` | JSON | `{"members": [...], "voiceprints": {...}}` | `voiceprint` | `voiceprint` |
| `data/family/recipes.md` | Markdown | `### 菜名\n- **制作人**: ...` | `family_manager.RecipeManager` | `family_manager.RecipeManager` |
| `data/family/preferences.md` | Markdown | `### 成员:姓名\n- **偏好**: ...` | `family_manager.PreferenceManager` | `family_manager.PreferenceManager` |
| `data/family/memory.md` | Markdown | `## 重要事件\n- **日期**: ...` | `family_manager.MemoryManager` | `family_manager.MemoryManager` |
| `data/logs/饮食日志_*.md` | Markdown | 见 3.4 节 | `plugins/diet_summary` | `diet_logger` |
| `data/reports/营养报告_*.md` | Markdown | 见 3.7 节 | 用户 | `nutrition_analyzer` |
| `data/presets/.daily_status.json` | JSON | `{"YYYY-MM-DD": {"summary_done": bool, "day_ended": bool}}` | `loop.DailyStatus` | `loop.DailyStatus` |
---
## 7. 并行 Cline 协作规范
### 7.1 分工建议
```
Cline A: Modules 强化(营养分析对接 DRIs、菜谱推荐 AI 化)
Cline B: UI 界面优化(Gradio 前端增强、报告模板美化) ← 当前 Cline
Cline C: 声纹升级(Whisper encoder 替换频谱特征)
Cline D: 数据处理与插件增强(diet_extractor fallback、新插件)
Cline E: 大模型集成(对接 Modal API,实现全双工语音对话) ← 新增(部署已完成)
```
> 注:MiniCPM-o 4.5 已在 Modal 完成部署,API 客户端 `modal_deploy/client.py` 已就绪。
> 接下来可并行推进:A) 前端接入大模型 B) 营养分析对接 DRIs C) 声纹升级。
### 7.2 关键约定
1. **改接口前先 grep**:用 `search_files` 搜索方法名找到所有调用方
2. **config.py 修改需沟通**:新增常量追加到同类型区域,不删改已有常量名
3. **数据文件向后兼容**:新增字段优先用 `extensions: {}` 或新增可选 key,不删除已有 key
4. **测试保持通过**:`python tests/run_tests.py` 零失败
5. **README 同步更新**:接口签名变更 → 更新本 README 对应章节
### 7.3 Git 工作流(强制执行)
```bash
# 工作前
git pull --rebase origin main
# 开发中:每完成一个独立功能点就提交
git add -A
git commit -m "feat: 描述做了什么"
# 推送前再次 pull
git pull --rebase origin main
# 测试通过后推送
python tests/run_tests.py
git push origin main
```
### 7.4 Commit Message 规范
```
feat: 新功能/新模块/新接口
fix: 修复 bug
test: 添加或修改测试
refactor: 重构(不改功能)
docs: 文档更新(含本 README)
chore: 配置/依赖/路径调整
```
### 7.5 冲突处理策略
| 冲突类型 | 处理方式 |
|---------|---------|
| `config.py` 常量冲突 | 取并集,双方新增常量都保留 |
| 同函数不同实现 | 保留逻辑更完整的一方,另一方改动如果无冲突则合并到合适位置 |
| 数据文件(JSON/MD)冲突 | 保留两个版本共有的条目 + 各自独有条目(去重) |
| 测试文件冲突 | **取并集**:保留所有测试用例 |
| **本 README 冲突** | **取行数更长的一方**,手动整合 |
| `.gitignore` 冲突 | 取并集 |
---
## 8. UI 设计说明(当前 Cline B 负责)
### 8.1 页面结构
3 Tab 布局,使用 **唯一一个 `@gr.render`** 包裹全部 Tab(避免 Gradio 内部字典迭代崩溃):
| Tab | ID | 功能 |
|-----|----|------|
| 🏠 首页 | `tab_home` | 语音按钮 + AI 思考 + 信息卡片 + 最近记录 |
| 👨👩👧👦 家庭 | `tab_family` | 家庭菜谱/偏好/记事 HTML 卡片展示 |
| 📈 营养报告 | `tab_report` | 纯 HTML 营养仪表盘 + 三天缺失深度分析(已合并) |
> **架构变更说明**(v2.0):
> - 原「三天总结」Tab **已合并到营养报告 Tab** 中,作为一个统一下沉展示区块
> - 营养报告使用 Slider 防抖自动生成(0.8s),包含基础营养评分 + 三天缺失深度分析
> - 三天缺失分析页面使用与营养报告一致的设计语言(渐变 Header + 紧凑标签 + 结构化 Summary)
### 8.2 设计风格
- **Glassmorphism 毛玻璃**:`backdrop-filter: blur(20px)` 半透明卡片
- **粉色暖调主题**:主色 `#E91E63`、渐变 `#FF6B9D → #FFD6E0`
- **纯 HTML 渲染**:家庭信息卡、营养报告、三天缺失分析均使用 HTML + CSS,无需生成图片
- **中英文切换**:`lang_state` 驱动唯一 `@gr.render`
- **三天缺失分析**:
- 持续不足营养素采用**紧凑标签式**显示(pill 标签,一行多个)
- 食材补充建议区域采用 **1:2 扩大布局**(右侧占 2/3 宽度,每行 6 个食物标签)
- 总结部分采用**结构化 UI 卡片**(红色/橙色/绿色 左侧边框,按严重程度分类显示)
### 8.3 关键文件
| 文件 | 职责 |
|------|------|
| `ui/app_builder.py` | Gradio 界面构建(3 Tab + 语言切换 + 报告自动生成) |
| `utils.py` | CSS 定义 + 翻译字典 + HTML 渲染函数(家庭卡片 + 营养仪表盘 + 三天缺失分析) |
| `plugins/three_day_summary.py` | 三天缺失分析插件(数据层,被报告 Tab 异步调用) |
---
## 9. 测试
```bash
# 运行全部测试
python tests/run_tests.py
```
当前测试覆盖:
- `test_diet_extractor.py` — ✅ 通过(正则解析 + fallback 提取)
- `test_family_manager.py` — ✅ 通过(菜谱/偏好/记忆 CRUD)
- `test_nutrition_standards.py` — ✅ 通过(BMI/膳食指南/DRIs 数据)
- `test_loop.py` — 循环执行测试
- `test_plugins.py` — 插件集成测试
---
## 10. Model: MiniCPM-o 4.5
```
Model to be Used: MiniCPM-o 4.5
```
核心能力层(`core/` 目录)提供模型加载、语音处理、视觉处理、对话管理的底层能力封装。详见各文件 docstring。
**部署状态**: ✅ 已完成 Modal 部署(见第11节)
---
## 11. 多模态大模型部署 (Modal)
### 11.1 部署状态
| 项目 | 状态 |
|------|------|
| API URL | `https://andrew-jiabin--prego-pal-minicpm-serve.modal.run` |
| Modal App | `prego-pal-minicpm` |
| GPU | T4 (16GB) |
| 模型 | MiniCPM-o 4.5 Q4_K_M (~12GB) + vision/audio/TTS 投影层 |
| 冷启动 | ~2-8 分钟(首次请求触发容器启动 + 模型加载) |
| 闲置缩容 | 300s 无请求自动缩容 |
| 并发 | `max_inputs=10` |
### 11.2 API 端点
| 端点 | 方法 | 功能 |
|------|------|------|
| `/v1/chat/completions` | POST | OpenAI 兼容聊天(文本) |
| `/v1/embeddings` | POST | 文本嵌入 |
| `/v1/models` | GET | 模型列表 |
| `/v1/multimodal/chat` | POST | 多模态(图片+文本) |
| `/health` | GET | 健康检查 |
| `/` | GET | 服务信息 |
### 11.3 Python 客户端
```python
from modal_deploy.client import MiniCPMClient
client = MiniCPMClient(base_url="https://andrew-jiabin--prego-pal-minicpm-serve.modal.run")
# 文本对话
response = client.ask("今天孕妇可以吃什么?")
# 多模态(图片理解)
response = client.describe_image(image_base64_str)
# 嵌入向量
embeddings = client.embed(["孕期饮食建议", "番茄牛腩"])
```
详见 `modal_deploy/client.py` 完整文档。
### 11.4 部署文件
| 文件 | 说明 |
|------|------|
| `modal_deploy/deploy.py` | Modal ASGI 部署入口(llama-server → FastAPI 包装) |
| `modal_deploy/client.py` | Python API 客户端(chat / chat_with_image / embed) |
| `docs/部署经验_Modal_MiniCPM-o.md` | 详细部署经验文档(踩坑记录、成本估算、后续优化) |
---
*最后更新: 2026-06-10 | 由 PregoPal Cline 团队维护*
> **⚠️ 重要提醒**:本 README 是所有并行 Cline 的协作基础,任何 Cline 在修改代码后,若涉及接口、数据格式、配置的变更,必须同步更新本文件。不得随意简化、删除结构性内容。 |