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;
}
}