feat: AI categorisation fallback, unsubscribe confidence scoring, one-line summaries

Heuristic classifier falls back to AI only when it can't determine a category;
unsubscribe confidence blends method reliability with an AI safety opinion and
surfaces it in the Unsubscribe Manager; emails get an on-demand AI one-line
summary in the detail pane. All AI calls degrade gracefully when AI is disabled
or the provider errors.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
cesnimda
2026-06-30 22:21:10 +02:00
parent 0163165beb
commit 64f835f719
14 changed files with 949 additions and 10 deletions
@@ -55,6 +55,21 @@ public interface IAiService
{
bool IsEnabled { get; }
Task<AiClassificationDto> ClassifyAsync(Guid userId, Guid emailId, CancellationToken ct = default);
/// <summary>
/// Lightweight classification used as a sync-time fallback when the
/// heuristic rule set doesn't match anything specific. Takes raw fields
/// directly (no DB lookup) since the email entity may not be persisted
/// yet during sync.
/// </summary>
Task<EmailCategory> ClassifyFallbackAsync(string? subject, string senderAddress, string? snippet, CancellationToken ct = default);
/// <summary>
/// AI opinion (0-1) on how safe it is to unsubscribe from a sender, given
/// the sender address and a recent subject line. Returns 0.5 (neutral) if
/// AI is disabled or the call fails - callers blend this with a heuristic.
/// </summary>
Task<double> ScoreUnsubscribeConfidenceAsync(string senderAddress, string? recentSubject, int emailCount, CancellationToken ct = default);
/// <summary>One-line plain-language summary of a single email, for list/preview UIs.</summary>
Task<string> SummarizeEmailAsync(string? subject, string? snippet, string? bodyText, CancellationToken ct = default);
Task<InboxSummaryDto> SummarizeInboxAsync(Guid userId, CancellationToken ct = default);
Task<IReadOnlyList<AiCleanupSuggestionDto>> SuggestCleanupAsync(Guid userId, CancellationToken ct = default);
Task<GeneratedQueryDto> GenerateQueryAsync(Guid userId, string naturalLanguage, CancellationToken ct = default);