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(); final settingsProvider = context.watch(); final chatProvider = context.watch(); 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()], ), ), ], ), ); } }