feat: 饮食记录逻辑重构 + 通知管理分隔线修复 + 多页面 UI 调整

- 饮食: 新增 DietCommentaryPolicy + diet_record_logic, 饮食记录逻辑抽取复用
- 通知管理: 分隔线改为仿通知中心样式, 跳过图标区域只留文字下方
- 侧边栏: 对话记录操作栏浮层修复 + 常用功能间距收紧
- 智能体: 欢迎卡片去 400ms 延迟 + 胶囊去阴影
- 后端: AI 会话仓库新增方法 + 饮食评论策略 + 健康记录规则调整
- 其他: api_client IP 适配 + 多页面 UI 微调 + 新增测试
This commit is contained in:
MingNian
2026-07-12 22:49:38 +08:00
parent d82e006cf4
commit 335a3e6440
33 changed files with 2056 additions and 1283 deletions

View File

@@ -48,6 +48,7 @@ public interface IAiConversationRepository
Task<IReadOnlyList<ConversationMessage>> GetRecentMessagesAsync(Guid conversationId, int limit, CancellationToken ct);
Task AddConversationAsync(Conversation conversation, CancellationToken ct);
Task AddMessageAsync(ConversationMessage message, CancellationToken ct);
Task<int> DeleteLegacyDietCommentaryArtifactsAsync(Guid userId, string promptPrefix, CancellationToken ct);
void Delete(Conversation conversation);
Task SaveChangesAsync(CancellationToken ct);
}

View File

@@ -55,6 +55,10 @@ public sealed class AiConversationService(IAiConversationRepository conversation
public async Task<IReadOnlyList<AiConversationDto>> ListAsync(Guid userId, CancellationToken ct)
{
await _conversations.DeleteLegacyDietCommentaryArtifactsAsync(
userId,
DietCommentaryPolicy.LegacyPromptPrefix,
ct);
var conversations = await _conversations.ListAsync(userId, ct);
return conversations.Take(HistoryConversationLimit).Select(ToDto).ToList();
}

View File

@@ -0,0 +1,35 @@
using Health.Domain.Entities;
using Health.Domain.Enums;
namespace Health.Application.AI;
public sealed record DietCommentaryFood(string Name, string Portion, int Calories);
public static class DietCommentaryPolicy
{
public const string LegacyPromptPrefix = "饮食记录页需要展示本餐建议。";
public const string SystemPrompt = """
App 23
1222使 Markdown
""";
public static string BuildFoodDescription(IEnumerable<DietCommentaryFood> foods) =>
string.Join("、", foods.Select(food =>
$"{Normalize(food.Name, 40)}{Normalize(food.Portion, 30)}{Math.Clamp(food.Calories, 0, 10000)}千卡)"));
public static bool IsLegacyArtifact(IEnumerable<ConversationMessage> messages)
{
var userMessages = messages.Where(message => message.Role == MessageRole.User).ToList();
return userMessages.Count > 0 &&
userMessages.All(message => message.Content.TrimStart().StartsWith(LegacyPromptPrefix, StringComparison.Ordinal));
}
private static string Normalize(string value, int maxLength)
{
var normalized = string.Join(' ', value.Trim().Split((char[]?)null, StringSplitOptions.RemoveEmptyEntries));
return normalized.Length > maxLength ? normalized[..maxLength] : normalized;
}
}

View File

@@ -9,7 +9,7 @@ public static class HealthRecordRules
{
HealthMetricType.BloodPressure => request.Systolic >= 140 || request.Diastolic >= 90 || request.Systolic <= 89 || request.Diastolic <= 59,
HealthMetricType.HeartRate => request.Value > 100 || request.Value < 60,
HealthMetricType.Glucose => request.Value >= 7.0m || request.Value <= 3.8m,
HealthMetricType.Glucose => request.Value > 6.1m || request.Value < 3.9m,
HealthMetricType.SpO2 => request.Value <= 94,
HealthMetricType.Weight => false,
_ => false
@@ -58,4 +58,3 @@ public static class HealthRecordRules
throw new ValidationException($"{field}应在 {min}~{max} {unit} 之间,当前值 {value} 不合理");
}
}

View File

@@ -101,6 +101,7 @@ public sealed class HealthRecordService(
latest.Diastolic,
latest.Value,
latest.Unit,
latest.IsAbnormal,
latest.RecordedAt
};
}
@@ -140,7 +141,7 @@ public sealed class HealthRecordService(
("心率偏高提醒", $"本次心率为 {record.Value:0.#} 次/分,高于参考范围。建议安静休息后复测。", "warning", "heart_rate"),
HealthMetricType.HeartRate =>
("心率偏低提醒", $"本次心率为 {record.Value:0.#} 次/分,低于参考范围。如伴明显不适,请及时就医。", "warning", "heart_rate"),
HealthMetricType.Glucose when record.Value >= 7.0m =>
HealthMetricType.Glucose when record.Value > 6.1m =>
("血糖偏高提醒", $"本次血糖为 {record.Value:0.#} mmol/L高于参考范围。请结合测量时段并按计划复测。", "warning", "glucose"),
HealthMetricType.Glucose =>
("血糖偏低提醒", $"本次血糖为 {record.Value:0.#} mmol/L低于参考范围。请及时关注身体状况。", "critical", "glucose"),