feat: 全功能前后端联调完成,47/47 测试通过

前端:
- 新增 DietCapturePage 独立拍照识别页
- 5种消息卡片类型完整实现(数据确认/用药/饮食/报告/快捷选项)
- 任务卡片区:异常警告+数据摘要+自动折叠
- 侧滑抽屉:历史对话列表+对话管理
- 运动计划:进度卡片+创建计划+每日打卡
- 报告页:拍照/相册/PDF上传+分析
- 面板按钮补全血氧/体重录入
- UI 升级:紫色主题+动画+气泡样式
- 全部迁移 Riverpod 3.x API

后端:
- 新增 _UpdateMessageTypeAndMetadata,Tool Calling 自动映射消息类型
- SSE answer 事件携带 type 字段
- 提示词优化(移除"阿福",语气规则归位)
- 运动计划支持 AI 创建和打卡

测试:
- 新增 full_e2e_test.py 全流程测试(认证/数据CRUD/6个Agent对话/VLM/报告)
This commit is contained in:
MingNian
2026-06-02 20:31:22 +08:00
parent 498708e568
commit c6395ea9b4
12 changed files with 2631 additions and 126 deletions

View File

@@ -21,8 +21,7 @@ public sealed class PromptManager
};
private const string DefaultPrompt = """
AI "阿福"
怀
AI健康管家
1.
@@ -34,6 +33,7 @@ public sealed class PromptManager
-
-
- /
- 怀
""";
private const string ConsultationPrompt = """

View File

@@ -107,6 +107,8 @@ public static class AiChatEndpoints
var maxIterations = 5;
var fullResponse = "";
var completedNormally = false;
var messageType = "text";
var metadata = new Dictionary<string, object>();
for (int i = 0; i < maxIterations; i++)
{
@@ -129,7 +131,7 @@ public static class AiChatEndpoints
if (!string.IsNullOrEmpty(content))
{
fullResponse += content;
await SseWriteAsync(http, new { action = "answer", data = content }, ct);
await SseWriteAsync(http, new { action = "answer", data = content, type = messageType }, ct);
}
}
catch (JsonException) { /* 跳过解析失败的 chunk */ }
@@ -160,6 +162,8 @@ public static class AiChatEndpoints
}
await SseWriteAsync(http, new { action = "tool_result", tool = tc.Function.Name, data = toolResult }, ct);
_UpdateMessageTypeAndMetadata(tc.Function.Name, toolResult, ref messageType, ref metadata);
messages.Add(new ChatMessage { Role = "tool", Content = JsonSerializer.Serialize(toolResult, JsonOpts), ToolCallId = tc.Id });
}
}
@@ -597,6 +601,40 @@ public static class AiChatEndpoints
}
};
/// <summary>根据工具调用结果更新消息类型和元数据</summary>
private static void _UpdateMessageTypeAndMetadata(string toolName, object toolResult, ref string messageType, ref Dictionary<string, object> metadata)
{
switch (toolName)
{
case "record_health_data":
messageType = "data_confirm";
if (toolResult is IDictionary<string, object> resultDict)
{
if (resultDict.TryGetValue("type", out var type))
metadata["type"] = type.ToString();
if (resultDict.TryGetValue("success", out var success) && success is bool b && b)
metadata["success"] = true;
}
break;
case "manage_medication":
messageType = "medication_confirm";
if (toolResult is IDictionary<string, object> medDict)
{
if (medDict.TryGetValue("name", out var name))
metadata["name"] = name.ToString();
if (medDict.TryGetValue("dosage", out var dosage))
metadata["dosage"] = dosage.ToString();
}
break;
case "estimate_food_text":
messageType = "diet_analysis";
break;
case "analyze_report":
messageType = "report_analysis";
break;
}
}
/// <summary>压缩图片到合理大小供 VLM API 使用</summary>
private static void CompressImage(string inputPath, string outputPath, int maxWidth, long quality)
{