import '../models/article.dart'; import '../models/briefing.dart'; import '../models/source.dart'; class SummarizerException implements Exception { SummarizerException(this.message); final String message; @override String toString() => message; } /// Something that can turn articles into summaries. Implemented by the /// direct Claude API client and by the n8n webhook bridge. abstract class Summarizer { String get name; Future summarizeArticle(Article article, String text, SourceLanguage language); Future createBriefing(List
articles, SourceLanguage language); } /// Shared prompt material and response mapping for all summarizers. class BriefingInput { BriefingInput(List
articles) : byId = {for (var i = 0; i < articles.length; i++) 'a${i + 1}': articles[i]}; /// Short ids keep the prompt compact and are easy for the model to cite. final Map byId; static const excerptChars = 220; String toPromptList() { final buffer = StringBuffer(); byId.forEach((id, a) { final excerpt = a.excerpt.length > excerptChars ? '${a.excerpt.substring(0, excerptChars)}…' : a.excerpt; buffer.writeln('[$id] (${a.source.name}) ${a.title}' '${excerpt.isEmpty ? '' : ' — $excerpt'}'); }); return buffer.toString(); } List> toJsonList() => [ for (final e in byId.entries) { 'id': e.key, 'title': e.value.title, 'url': e.value.link, 'source': e.value.source.name, 'language': e.value.source.language.name, 'published': e.value.published?.toIso8601String(), 'excerpt': e.value.excerpt, } ]; /// Maps `{intro, topics: [{title, summary, article_ids}]}` onto a briefing. DailyBriefing toBriefing(Map json, String generatedBy) { final topics = (json['topics'] as List? ?? []).map((t) { final topic = t as Map; final ids = (topic['article_ids'] as List? ?? []).cast(); return BriefingTopic( title: topic['title'] as String? ?? '', summary: topic['summary'] as String? ?? '', sources: [ for (final id in ids) if (byId[id] != null) ArticleRef( title: byId[id]!.title, url: byId[id]!.link, sourceName: byId[id]!.source.name, ), ], ); }).toList(); return DailyBriefing( createdAt: DateTime.now(), intro: json['intro'] as String? ?? '', topics: topics, generatedBy: generatedBy, ); } } String languageName(SourceLanguage language) => language == SourceLanguage.de ? 'German' : 'English';