Initial commit: Tagesbyte prototype
Flutter app that collects daily tech news from German and English RSS/Atom feeds, with a reader view, AI article summaries and daily briefings (Claude API or n8n webhook), paywall/sponsored filtering and ad/consent blocking. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
commit
f12fb1dabb
159 files changed
+8738
No files matched your search
@@ -0,0 +1,88 @@
|
||||
import '../models/article.dart';
|
||||
import '../models/briefing.dart';
|
||||
import '../models/news_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<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';
|
||||
Reference in new issue
Block a user