157 lines
4.4 KiB
Dart
157 lines
4.4 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
import 'package:mam_core/mam_core.dart';
|
|
|
|
import 'src/providers/session_providers.dart';
|
|
import 'src/theme/app_theme.dart';
|
|
import 'src/widgets/detail_pane.dart';
|
|
import 'src/widgets/session_table.dart';
|
|
import 'src/widgets/stale_banner.dart';
|
|
|
|
void main() {
|
|
runApp(const ProviderScope(child: MamDesktopApp()));
|
|
}
|
|
|
|
class MamDesktopApp extends StatelessWidget {
|
|
const MamDesktopApp({super.key});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return MaterialApp(
|
|
title: 'multi-agent-mux',
|
|
debugShowCheckedModeBanner: false,
|
|
theme: buildAppTheme(),
|
|
home: const DashboardScreen(),
|
|
);
|
|
}
|
|
}
|
|
|
|
class DashboardScreen extends ConsumerWidget {
|
|
const DashboardScreen({super.key});
|
|
|
|
@override
|
|
Widget build(BuildContext context, WidgetRef ref) {
|
|
final pollAsync = ref.watch(sessionsPollProvider);
|
|
|
|
return Scaffold(
|
|
body: DecoratedBox(
|
|
decoration: const BoxDecoration(gradient: backgroundGradient),
|
|
child: SafeArea(
|
|
child: pollAsync.when(
|
|
data: (poll) => _DashboardBody(poll: poll),
|
|
loading: () => const Center(child: CircularProgressIndicator()),
|
|
error: (error, stackTrace) => _ErrorScreen(error: error),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class _ErrorScreen extends StatelessWidget {
|
|
final Object error;
|
|
const _ErrorScreen({required this.error});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Center(
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(32),
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
const Icon(Icons.error_outline, color: AppColors.danger, size: 40),
|
|
const SizedBox(height: 12),
|
|
Text(
|
|
'Failed to start status polling:\n$error',
|
|
textAlign: TextAlign.center,
|
|
style: const TextStyle(color: AppColors.danger),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class _DashboardBody extends ConsumerWidget {
|
|
final SessionsPoll poll;
|
|
const _DashboardBody({required this.poll});
|
|
|
|
@override
|
|
Widget build(BuildContext context, WidgetRef ref) {
|
|
final selectedName = ref.watch(selectedSessionNameProvider);
|
|
final sessions = poll.snapshot?.sessions ?? const <SessionRow>[];
|
|
final matches = sessions.where((s) => s.name == selectedName);
|
|
final selectedRow = matches.isEmpty ? null : matches.first;
|
|
|
|
return Column(
|
|
children: [
|
|
_TopBar(poll: poll),
|
|
StaleBanner(poll: poll),
|
|
Expanded(
|
|
child: Row(
|
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
children: [
|
|
Expanded(
|
|
flex: 3,
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(16),
|
|
child: SessionTable(
|
|
sessions: sessions,
|
|
selectedName: selectedName,
|
|
onSelect: (name) =>
|
|
ref.read(selectedSessionNameProvider.notifier).state =
|
|
name,
|
|
),
|
|
),
|
|
),
|
|
SizedBox(width: 380, child: DetailPane(session: selectedRow)),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
}
|
|
|
|
class _TopBar extends StatelessWidget {
|
|
final SessionsPoll poll;
|
|
const _TopBar({required this.poll});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final count = poll.snapshot?.sessions.length ?? 0;
|
|
return Padding(
|
|
padding: const EdgeInsets.fromLTRB(20, 16, 20, 0),
|
|
child: Row(
|
|
children: [
|
|
const Text(
|
|
'multi-agent-mux',
|
|
style: TextStyle(
|
|
fontSize: 20,
|
|
fontWeight: FontWeight.w700,
|
|
color: AppColors.textPrimary,
|
|
),
|
|
),
|
|
const SizedBox(width: 12),
|
|
Container(
|
|
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
|
|
decoration: BoxDecoration(
|
|
color: AppColors.accent.withValues(alpha: 0.18),
|
|
borderRadius: BorderRadius.circular(999),
|
|
),
|
|
child: Text(
|
|
'$count session${count == 1 ? '' : 's'}',
|
|
style: const TextStyle(
|
|
color: AppColors.accent2,
|
|
fontWeight: FontWeight.w600,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|