Files

88 lines
2.7 KiB
Dart
Raw Permalink Normal View History

2026-10-16 14:53:37 +02:00
import '../models/article.dart';
import '../models/briefing.dart';
import '../models/source.dart';
2026-10-16 14:53:37 +02:00
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<String> summarizeArticle(Article article, String text, SourceLanguage language);
Future<DailyBriefing> createBriefing(List<Article> articles, SourceLanguage language);
}
/// Shared prompt material and response mapping for all summarizers.
class BriefingInput {
BriefingInput(List<Article> 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<String, Article> 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<Map<String, dynamic>> 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<String, dynamic> json, String generatedBy) {
final topics = (json['topics'] as List? ?? []).map((t) {
final topic = t as Map<String, dynamic>;
final ids = (topic['article_ids'] as List? ?? []).cast<String>();
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';