refactor: 4层架构重构 + 饮食VLM接入 + 多项修复

- 后端: remaining_endpoints拆分为6个独立文件
- 后端: AI Agent Handler从ai_chat_endpoints抽取为7个独立处理器
- 后端: 食物识别prompt改为输出结构化JSON
- 前端: 饮食识别从Mock替换为真实VLM API调用
- 前端: 首页图片上传URL修复(/api/upload→/api/files/upload)
- 前端: 拍饮食按钮导航到独立DietCapturePage
- 前端: 删除无用agent_bar.dart
- 前端: 修复widget_test.dart过期属性名
- 前端: 恢复ServicePackageCard和详情页
- 新增6份实施文档(情况/问诊/报告/建档/日历/视觉统一)
This commit is contained in:
MingNian
2026-06-03 23:17:37 +08:00
parent 5bd0155e17
commit c2399b952f
33 changed files with 3311 additions and 660 deletions

View File

@@ -0,0 +1,70 @@
namespace Health.WebApi.Endpoints;
public static class ConsultationEndpoints
{
public static void MapConsultationEndpoints(this WebApplication app)
{
var group = app.MapGroup("/api").RequireAuthorization();
group.MapGet("/doctors", async (AppDbContext db) =>
{
var doctors = await db.Doctors.Where(d => d.IsActive).Select(d => new { d.Id, d.Name, d.Title, d.Department, d.Introduction }).ToListAsync();
return Results.Ok(new { code = 0, data = doctors, message = (string?)null });
});
group.MapGet("/consultations", async (HttpContext http, AppDbContext db) =>
{
var userId = GetUserId(http);
var consultations = await db.Consultations.Where(c => c.UserId == userId).OrderByDescending(c => c.CreatedAt).ToListAsync();
return Results.Ok(new { code = 0, data = consultations, message = (string?)null });
});
group.MapPost("/consultations", async (CreateConsultationRequest req, HttpContext http, AppDbContext db, CancellationToken ct) =>
{
var userId = GetUserId(http);
var consultation = new Consultation
{
Id = Guid.NewGuid(), UserId = userId, DoctorId = req.DoctorId,
Status = ConsultationStatus.AiTalking,
Month = DateTime.UtcNow.Year * 100 + DateTime.UtcNow.Month,
};
db.Consultations.Add(consultation);
await db.SaveChangesAsync(ct);
return Results.Ok(new { code = 0, data = new { consultation.Id }, message = (string?)null });
});
group.MapGet("/consultations/{id:guid}/messages", async (Guid id, string? after, HttpContext http, AppDbContext db, CancellationToken ct) =>
{
var userId = GetUserId(http);
var query = db.ConsultationMessages.Where(m => m.ConsultationId == id && m.Consultation.UserId == userId);
if (Guid.TryParse(after, out var afterId))
query = query.Where(m => m.Id.CompareTo(afterId) > 0);
var messages = await query.OrderBy(m => m.CreatedAt).Take(50).Select(m => new { m.Id, SenderType = m.SenderType.ToString(), m.SenderName, m.Content, m.CreatedAt }).ToListAsync(ct);
return Results.Ok(new { code = 0, data = messages, message = (string?)null });
});
group.MapPost("/consultations/{id:guid}/messages", async (Guid id, SendMessageRequest req, HttpContext http, AppDbContext db, CancellationToken ct) =>
{
var userId = GetUserId(http);
var msg = new ConsultationMessage { Id = Guid.NewGuid(), ConsultationId = id, SenderType = ConsultationSenderType.User, Content = req.Content, SenderName = null, CreatedAt = DateTime.UtcNow };
db.ConsultationMessages.Add(msg);
await db.SaveChangesAsync(ct);
return Results.Ok(new { code = 0, data = new { msg.Id }, message = (string?)null });
});
group.MapGet("/user/consultation-quota", async (HttpContext http, AppDbContext db) =>
{
var userId = GetUserId(http);
var now = DateTime.UtcNow;
var currentPeriod = now.Year * 100 + now.Month;
var used = await db.Consultations.CountAsync(c => c.UserId == userId && c.Month == currentPeriod);
return Results.Ok(new { code = 0, data = new { total = 3, used, remaining = 3 - used }, message = (string?)null });
});
}
private static Guid GetUserId(HttpContext http) =>
Guid.TryParse(http.User.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier)?.Value, out var id) ? id : Guid.Empty;
}
public sealed record CreateConsultationRequest(Guid DoctorId);
public sealed record SendMessageRequest(string Content);