feat: 添加饮食文字AI识别功能与代码文档
- 饮食页面新增"文字AI识别"按钮,支持输入文字描述后自动识别卡路里 - 修复 database.py 中 time 模块导入冲突问题 - 新增 CODEMAPS.md 代码结构文档 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -5037,7 +5037,8 @@ def get_meal_page_html() -> str:
|
||||
</select>
|
||||
|
||||
<label for="meal-description">食物描述</label>
|
||||
<textarea id="meal-description" name="description" rows="3"></textarea>
|
||||
<textarea id="meal-description" name="description" rows="3" placeholder="例如:一碗米饭、两个鸡蛋、一份青菜"></textarea>
|
||||
<button type="button" id="textRecognizeBtn" onclick="recognizeFoodFromText()" style="margin-top:8px; background:#10b981;">文字AI识别</button>
|
||||
|
||||
<label for="meal-calories">卡路里(可选,留空自动估算)</label>
|
||||
<input type="number" id="meal-calories" name="calories" min="0" inputmode="numeric">
|
||||
@@ -5075,6 +5076,7 @@ def get_meal_page_html() -> str:
|
||||
// 重置识别状态
|
||||
document.getElementById('recognizeBtn').style.display = 'none';
|
||||
document.getElementById('recognizeStatus').style.display = 'none';
|
||||
document.getElementById('textRecognizeBtn').disabled = false;
|
||||
}
|
||||
// ESC 键关闭 Modal
|
||||
document.addEventListener('keydown', (e) => {
|
||||
@@ -5161,6 +5163,73 @@ def get_meal_page_html() -> str:
|
||||
}
|
||||
}
|
||||
|
||||
async function recognizeFoodFromText() {
|
||||
const descriptionInput = document.getElementById('meal-description');
|
||||
const statusDiv = document.getElementById('recognizeStatus');
|
||||
const textRecognizeBtn = document.getElementById('textRecognizeBtn');
|
||||
|
||||
const text = descriptionInput.value.trim();
|
||||
if (!text) {
|
||||
alert('请先输入食物描述');
|
||||
return;
|
||||
}
|
||||
|
||||
// 显示识别中状态
|
||||
statusDiv.style.display = 'block';
|
||||
statusDiv.style.background = '#e3f2fd';
|
||||
statusDiv.style.color = '#1976d2';
|
||||
statusDiv.innerHTML = '🤖 正在识别食物,请稍候...';
|
||||
textRecognizeBtn.disabled = true;
|
||||
|
||||
try {
|
||||
const formData = new FormData();
|
||||
formData.append('text', text);
|
||||
formData.append('provider', 'qwen');
|
||||
|
||||
const res = await fetch('/api/meal/recognize', {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
});
|
||||
|
||||
const result = await res.json();
|
||||
|
||||
if (res.ok && result.success) {
|
||||
// 显示成功状态
|
||||
statusDiv.style.background = '#e8f5e9';
|
||||
statusDiv.style.color = '#2e7d32';
|
||||
statusDiv.innerHTML = `✅ 识别成功!<br>食物:${result.description}<br>卡路里:${result.total_calories} 卡`;
|
||||
|
||||
// 自动填充表单(更新描述为标准化的描述)
|
||||
const form = document.getElementById('addMealForm');
|
||||
if (result.description) {
|
||||
form.querySelector('[name="description"]').value = result.description;
|
||||
}
|
||||
form.querySelector('[name="calories"]').value = result.total_calories || '';
|
||||
if (result.total_protein) {
|
||||
form.querySelector('[name="protein"]').value = result.total_protein.toFixed(1);
|
||||
}
|
||||
if (result.total_carbs) {
|
||||
form.querySelector('[name="carbs"]').value = result.total_carbs.toFixed(1);
|
||||
}
|
||||
if (result.total_fat) {
|
||||
form.querySelector('[name="fat"]').value = result.total_fat.toFixed(1);
|
||||
}
|
||||
} else {
|
||||
// 显示错误状态
|
||||
statusDiv.style.background = '#ffebee';
|
||||
statusDiv.style.color = '#c62828';
|
||||
statusDiv.innerHTML = `❌ 识别失败:${result.detail || '未知错误'}`;
|
||||
}
|
||||
} catch (error) {
|
||||
// 显示错误状态
|
||||
statusDiv.style.background = '#ffebee';
|
||||
statusDiv.style.color = '#c62828';
|
||||
statusDiv.innerHTML = `❌ 识别失败:${error.message}`;
|
||||
} finally {
|
||||
textRecognizeBtn.disabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
function formatPhotoUrl(path) {
|
||||
if (!path) return null;
|
||||
const marker = '/.vitals/photos/';
|
||||
|
||||
Reference in New Issue
Block a user