Spaces:
Runtime error
Runtime error
| 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 在修改代码后,若涉及接口、数据格式、配置的变更,必须同步更新本文件。不得随意简化、删除结构性内容。 |