feat: 问诊对话+建档引导+报告+日历+趋势页重写+视觉统一

- 问诊对话页: 新建consultation_provider, 完整AI分身聊天UI
- 首次建档引导: OnboardingPrompt+ManageArchiveTool+前端卡片
- 报告模块: POST端点 + report_agent_handler接VLM
- 健康日历: GET /api/calendar端点 + 前端接API
- 趋势页重写: 删除Mock数据 + 真实API + 手动录入
- 饮食保存: submit按钮接后端API
- 侧边栏: 健康概览横排 + 统一紫色配色
- 运动计划: 修复JSON反序列化(record→手动解析)
- VLM: prompt优化 + 模型切qwen3-vl-plus + 1280px压缩
- UI颜色: 加深主色 8B9CF7→6C5CE7
- 个人信息页精简 + 健康档案可编辑重设计
- 保洁: 删除无用agent_bar + 删除意见反馈/关于我们
- 新增AI提示词文档
This commit is contained in:
MingNian
2026-06-04 13:49:43 +08:00
parent c2399b952f
commit 0e0e8ce72b
29 changed files with 1621 additions and 3361 deletions

View File

@@ -61,4 +61,73 @@ public static class CommonAgentHandler
archive.Allergies, archive.DietRestrictions, archive.ChronicDiseases, archive.FamilyHistory,
};
}
public static readonly ToolDefinition ManageArchiveTool = new()
{
Function = new()
{
Name = "manage_archive", Description = "管理用户健康档案",
Parameters = new
{
type = "object",
properties = new
{
action = new { type = "string", description = "update_diagnosis/update_surgery/update_allergies/update_chronic_diseases/update_diet_restrictions/query" },
diagnosis = new { type = "string" },
surgery_type = new { type = "string" },
surgery_date = new { type = "string" },
allergies = new { type = "array", items = new { type = "string" } },
chronic_diseases = new { type = "array", items = new { type = "string" } },
diet_restrictions = new { type = "array", items = new { type = "string" } },
},
required = new[] { "action" }
}
}
};
public static async Task<object> ExecuteManageArchive(AppDbContext db, Guid userId, JsonElement args)
{
var action = args.TryGetProperty("action", out var a) ? a.GetString()! : "query";
var archive = await db.HealthArchives.FirstOrDefaultAsync(x => x.UserId == userId);
if (archive == null)
{
archive = new HealthArchive { Id = Guid.NewGuid(), UserId = userId };
db.HealthArchives.Add(archive);
}
switch (action)
{
case "update_diagnosis":
archive.Diagnosis = args.TryGetProperty("diagnosis", out var d) ? d.GetString() : archive.Diagnosis;
break;
case "update_surgery":
archive.SurgeryType = args.TryGetProperty("surgery_type", out var st) ? st.GetString() : archive.SurgeryType;
if (args.TryGetProperty("surgery_date", out var sd))
archive.SurgeryDate = DateOnly.TryParse(sd.GetString(), out var date) ? date : archive.SurgeryDate;
break;
case "update_allergies":
if (args.TryGetProperty("allergies", out var al) && al.ValueKind == JsonValueKind.Array)
archive.Allergies = [.. al.EnumerateArray().Select(x => x.GetString()!)];
break;
case "update_chronic_diseases":
if (args.TryGetProperty("chronic_diseases", out var cd) && cd.ValueKind == JsonValueKind.Array)
archive.ChronicDiseases = [.. cd.EnumerateArray().Select(x => x.GetString()!)];
break;
case "update_diet_restrictions":
if (args.TryGetProperty("diet_restrictions", out var dr) && dr.ValueKind == JsonValueKind.Array)
archive.DietRestrictions = [.. dr.EnumerateArray().Select(x => x.GetString()!)];
break;
default: // query
return new
{
found = true, archive.Diagnosis, archive.SurgeryType,
SurgeryDate = archive.SurgeryDate?.ToString("yyyy-MM-dd"),
archive.Allergies, archive.DietRestrictions, archive.ChronicDiseases, archive.FamilyHistory,
};
}
archive.UpdatedAt = DateTime.UtcNow;
await db.SaveChangesAsync();
return new { success = true };
}
}

View File

@@ -20,4 +20,39 @@ public static class ReportAgentHandler
_ => Task.FromResult<object>(new { success = false, message = $"未知工具: {toolName}" })
};
}
public static async Task<object> AnalyzeReport(AppDbContext db, Guid userId, JsonElement args, VisionClient visionClient)
{
var imageUrl = args.TryGetProperty("image_url", out var u) ? u.GetString()! : "";
if (string.IsNullOrEmpty(imageUrl))
return new { success = false, message = "缺少报告图片" };
var prompt = """
JSON格式返回
{
"reportType": "报告类型(血常规/生化全项/心电图/彩超/出院小结/其他)",
"indicators": [
{"name":"指标名","value":"数值","unit":"单位","range":"参考范围","status":"normal/high/low"}
],
"summary": "初步分析摘要",
"needsDoctorReview": true/false
}
JSON
""";
var response = await visionClient.VisionAsync(prompt, [imageUrl], userText: "请分析这份检查报告");
var content = response.Choices?.FirstOrDefault()?.Message?.Content ?? "{}";
// 保存到报告记录
var report = await db.Reports
.OrderByDescending(r => r.CreatedAt)
.FirstOrDefaultAsync(r => r.UserId == userId && r.Status == ReportStatus.PendingDoctor);
if (report != null)
{
report.AiSummary = content;
report.AiIndicators = content;
}
return new { success = true, analysis = content };
}
}

View File

@@ -136,6 +136,7 @@ public sealed class VisionClient(HttpClient http, IConfiguration config)
throw new HttpRequestException($"VLM API {response.StatusCode}: {errorBody}");
}
var body = await response.Content.ReadAsStringAsync(ct);
Console.WriteLine($"[VLM RAW] Model={_model}, BodyLen={body.Length}, Body={body[..Math.Min(body.Length, 500)]}");
return JsonSerializer.Deserialize<ChatCompletionResponse>(body, _jsonOptions)!;
}
}

View File

@@ -17,6 +17,7 @@ public sealed class PromptManager
AgentType.Medication => MedicationPrompt,
AgentType.Report => ReportPrompt,
AgentType.Exercise => ExercisePrompt,
AgentType.Onboarding => OnboardingPrompt,
_ => DefaultPrompt
};
@@ -115,4 +116,24 @@ public sealed class PromptManager
4.
5.
""";
private const string OnboardingPrompt = """
1//尿/
2PCI支架植入///
3////
4/尿//
5///
- 2-4
- manage_archive
- "跳过"/"以后再说"/"再说"/"不用了" "好的,随时可以跟我说'完善档案'来继续"
-
- 怀
-
""";
}