The-Agency/lib/ui/widgets/common/footer_bar.dart

192 lines
5.7 KiB
Dart

import "package:flutter/widgets.dart" hide Tooltip;
import "package:google_fonts/google_fonts.dart";
import "package:shadcn_flutter/shadcn_flutter.dart" hide Row, Expanded;
import "package:provider/provider.dart";
import "../../providers/chat_provider.dart";
import "../../providers/cost_provider.dart";
import "../../providers/settings_provider.dart";
import "ana_text.dart";
String _fmtTokens(int n) {
final s = n.toString();
final buf = StringBuffer();
for (var i = 0; i < s.length; i++) {
if (i > 0 && (s.length - i) % 3 == 0) buf.write(",");
buf.write(s[i]);
}
return buf.toString();
}
class FooterBar extends StatelessWidget {
const FooterBar({super.key});
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final mutedFg = theme.colorScheme.mutedForeground;
final borderColor = theme.colorScheme.border;
final bg = theme.colorScheme.background;
final costProvider = context.watch<CostProvider>();
final settingsProvider = context.watch<SettingsProvider>();
final chatProvider = context.watch<ChatProvider>();
final model = settingsProvider.settings.model ?? "unknown";
final costUsd = costProvider.getTotalCostUsd();
final cost = "\$${costUsd.toStringAsFixed(4)}";
final inputToks = costProvider.getTotalInputTokens();
final outputToks = costProvider.getTotalOutputTokens();
final isLoading = chatProvider.isLoading;
final isCompacting = chatProvider.isCompacting;
final contextTokens = chatProvider.contextTokens;
final warningState = chatProvider.tokenWarningState;
final textStyle = GoogleFonts.ibmPlexMono(
fontSize: 11,
height: 1,
fontWeight: FontWeight.w600,
color: mutedFg,
);
Widget divider() => const Padding(
padding: EdgeInsets.symmetric(horizontal: 5),
child: SizedBox(height: 12, child: VerticalDivider(width: 1)),
);
Widget copyrightBlock() {
return AnaText(
"© 2026 IMBENJI.NET LTD - The Agency",
style: textStyle,
);
}
Widget statusBlock() {
final label = isCompacting
? "compacting..."
: isLoading
? "running..."
: "idle";
final labelColor = isCompacting
? theme.colorScheme.primary.withAlpha(180)
: isLoading
? theme.colorScheme.primary
: mutedFg;
return Row(
mainAxisSize: MainAxisSize.min,
children: [
AnaText(label, style: textStyle.copyWith(color: labelColor)),
divider(),
AnaText(model.split("/").last, style: textStyle),
],
);
}
Color? _contextWarningColor() {
if (warningState == null) return null;
if (warningState.isAboveErrorThreshold) return const Color(0xFFEF4444); // red
if (warningState.isAboveWarningThreshold) return const Color(0xFFF59E0B); // amber
return null;
}
Widget statsBlock() {
final warnColor = _contextWarningColor();
return Row(
mainAxisSize: MainAxisSize.min,
children: [
// compact button — shown when we're near the threshold
if (warningState != null && warningState.isAboveWarningThreshold && !isLoading && !isCompacting) ...[
GestureDetector(
onTap: () => chatProvider.runCompact(),
child: MouseRegion(
cursor: SystemMouseCursors.click,
child: AnaText(
"compact",
style: textStyle.copyWith(
color: warnColor ?? mutedFg,
decoration: TextDecoration.underline,
decorationColor: warnColor ?? mutedFg,
),
),
),
),
divider(),
],
if (contextTokens > 0) ...[
Tooltip(
tooltip: (_) => TooltipContainer(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 8),
child: AnaText(
warningState != null
? "${warningState.percentLeft}% context remaining"
: _fmtTokens(contextTokens),
style: GoogleFonts.ibmPlexMono(fontSize: 11, height: 1.2),
),
),
child: AnaText(
_fmtTokens(contextTokens),
style: textStyle.copyWith(color: warnColor ?? mutedFg),
),
),
AnaText(" tokens", style: textStyle.copyWith(color: warnColor ?? mutedFg)),
divider(),
],
Tooltip(
tooltip: (_) => TooltipContainer(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 8),
child: AnaText(
"In: $inputToks\nOut: $outputToks",
style: GoogleFonts.ibmPlexMono(fontSize: 11, height: 1.2),
),
),
child: AnaText(cost, style: textStyle),
),
],
);
}
return Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
width: double.infinity,
decoration: BoxDecoration(
color: bg,
border: Border(top: BorderSide(color: borderColor, width: 1)),
),
child: Row(
children: [
Expanded(child: Row(children: [copyrightBlock()])),
Expanded(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [statusBlock()],
),
),
Expanded(
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [statsBlock()],
),
),
],
),
);
}
}