118 lines
5.0 KiB
Dart
118 lines
5.0 KiB
Dart
import 'package:flutter/material.dart';
|
|
import '../../../core/theme/app_theme.dart';
|
|
import '../../../core/widgets/glass_container.dart';
|
|
import '../../../core/widgets/status_badge.dart';
|
|
import '../models/news_article_model.dart';
|
|
|
|
class FinbertSentimentTab extends StatelessWidget {
|
|
final NewsArticleModel article;
|
|
|
|
const FinbertSentimentTab({super.key, required this.article});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final finbert = article.finbertResult;
|
|
final double compoundScore = finbert?.score ?? article.sentimentScore;
|
|
final double confidenceScore = article.confidence;
|
|
final String label = (finbert?.label ?? article.sentiment).toUpperCase();
|
|
|
|
final double posRatio = finbert?.positiveProbability ?? (label == 'POSITIVE' ? 0.8 : 0.1);
|
|
final double neuRatio = finbert?.neutralProbability ?? (label == 'NEUTRAL' ? 0.8 : 0.1);
|
|
final double negRatio = finbert?.negativeProbability ?? (label == 'NEGATIVE' ? 0.8 : 0.1);
|
|
|
|
final String aiText = finbert?.summarySnippet ?? (article.summary.isNotEmpty ? article.summary : 'FinBERT Sentiment-Analyse verarbeitet.');
|
|
|
|
return SingleChildScrollView(
|
|
padding: const EdgeInsets.all(16),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
Row(
|
|
children: [
|
|
Icon(Icons.psychology, color: AppTheme.accentCyan, size: 24),
|
|
const SizedBox(width: 8),
|
|
const Text('FinBERT NLP Modell', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16, color: Colors.white)),
|
|
],
|
|
),
|
|
StatusBadge.sentiment(label.isNotEmpty ? label : 'NEUTRAL', score: compoundScore),
|
|
],
|
|
),
|
|
const SizedBox(height: 16),
|
|
GlassContainer(
|
|
padding: const EdgeInsets.all(16),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
const Text('Sentiment Verteilung (Wahrscheinlichkeiten):', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 13, color: Colors.white)),
|
|
const SizedBox(height: 12),
|
|
_buildProbBar('Positiv', posRatio, AppTheme.primaryEmerald),
|
|
const SizedBox(height: 8),
|
|
_buildProbBar('Neutral', neuRatio, Colors.amber),
|
|
const SizedBox(height: 8),
|
|
_buildProbBar('Negativ', negRatio, AppTheme.accentRed),
|
|
],
|
|
),
|
|
),
|
|
const SizedBox(height: 16),
|
|
GlassContainer(
|
|
padding: const EdgeInsets.all(16),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
const Text('Konfidenz-Score:', style: TextStyle(color: Colors.white70, fontSize: 13)),
|
|
Text('${(confidenceScore * 100).toStringAsFixed(1)}%', style: TextStyle(color: AppTheme.accentCyan, fontWeight: FontWeight.bold, fontSize: 14)),
|
|
],
|
|
),
|
|
const SizedBox(height: 8),
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
const Text('Compound Sentiment-Wert:', style: TextStyle(color: Colors.white70, fontSize: 13)),
|
|
Text(compoundScore.toStringAsFixed(2), style: TextStyle(color: compoundScore >= 0 ? AppTheme.primaryEmerald : AppTheme.accentRed, fontWeight: FontWeight.bold, fontSize: 14)),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
const SizedBox(height: 16),
|
|
const Text('KI-Zusammenfassung & Relevanz:', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 14, color: Colors.white)),
|
|
const SizedBox(height: 8),
|
|
Text(aiText, style: const TextStyle(color: Colors.white70, fontSize: 13, height: 1.4)),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildProbBar(String label, double ratio, Color color) {
|
|
final pct = (ratio * 100).clamp(0.0, 100.0);
|
|
return Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
Text(label, style: TextStyle(color: AppTheme.textMuted, fontSize: 12)),
|
|
Text('${pct.toStringAsFixed(1)}%', style: TextStyle(color: color, fontWeight: FontWeight.bold, fontSize: 12)),
|
|
],
|
|
),
|
|
const SizedBox(height: 4),
|
|
ClipRRect(
|
|
borderRadius: BorderRadius.circular(4),
|
|
child: LinearProgressIndicator(
|
|
value: ratio.clamp(0.0, 1.0),
|
|
backgroundColor: Colors.white.withValues(alpha: 0.08),
|
|
valueColor: AlwaysStoppedAnimation<Color>(color),
|
|
minHeight: 6,
|
|
),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
}
|