| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147 |
- import 'package:flutter/material.dart';
- import 'package:flutter_riverpod/flutter_riverpod.dart';
- import 'package:go_router/go_router.dart';
- import '../../../../services/analysis_providers.dart';
- import '../../../shared/presentation/widgets/async_value_view.dart';
- import '../../../shared/presentation/widgets/lab_section_scaffold.dart';
- class ExperimentPage extends ConsumerWidget {
- const ExperimentPage({super.key});
- @override
- Widget build(BuildContext context, WidgetRef ref) {
- final theme = Theme.of(context);
- final snapshot = ref.watch(selectedSessionSnapshotProvider);
- return LabSectionScaffold(
- eyebrow: 'Experiment',
- title: 'Follow the AI analyst through each validation step.',
- description:
- 'This page visualizes hypotheses, validation chains, score changes, and structured failure reasons across the experiment timeline.',
- children: [
- AsyncValueView(
- value: snapshot,
- loadingMessage: 'Loading experiment timeline...',
- data: (session) {
- if (session == null) {
- return const _EmptySessionCard();
- }
- return Column(
- children: [
- Card(
- child: Padding(
- padding: const EdgeInsets.all(20),
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Text('Hypotheses', style: theme.textTheme.titleLarge),
- const SizedBox(height: 12),
- ...session.hypotheses.map(
- (hypothesis) => Padding(
- padding: const EdgeInsets.only(bottom: 14),
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Text(
- '${hypothesis.label} | ${hypothesis.confidence.toStringAsFixed(2)}',
- style: theme.textTheme.titleMedium,
- ),
- const SizedBox(height: 4),
- Text(
- hypothesis.rationale,
- style: theme.textTheme.bodyMedium,
- ),
- const SizedBox(height: 6),
- Wrap(
- spacing: 8,
- runSpacing: 8,
- children: hypothesis.relatedSignalProfiles
- .map((item) => Chip(label: Text(item)))
- .toList(),
- ),
- ],
- ),
- ),
- ),
- ],
- ),
- ),
- ),
- const SizedBox(height: 16),
- ...session.experiments.map(
- (experiment) => Card(
- child: Padding(
- padding: const EdgeInsets.all(20),
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Text(
- experiment.title,
- style: theme.textTheme.titleLarge,
- ),
- const SizedBox(height: 8),
- Text(
- 'Status: ${experiment.status} | Score: ${experiment.score.total.toStringAsFixed(2)}',
- style: theme.textTheme.bodyMedium,
- ),
- if (experiment.hypothesisId != null) ...[
- const SizedBox(height: 6),
- Text(
- 'Hypothesis: ${experiment.hypothesisId}',
- style: theme.textTheme.bodyMedium,
- ),
- ],
- const SizedBox(height: 12),
- ...experiment.pipelineSummary.map(
- (node) => Padding(
- padding: const EdgeInsets.only(bottom: 4),
- child: Text('- $node'),
- ),
- ),
- if (experiment.score.notes.isNotEmpty) ...[
- const SizedBox(height: 12),
- ...experiment.score.notes.map(
- (note) => Text('Note: $note'),
- ),
- ],
- if (experiment.failureReasons.isNotEmpty) ...[
- const SizedBox(height: 12),
- Text(
- 'Failure reasons: ${experiment.failureReasons.join(', ')}',
- style: theme.textTheme.bodyMedium,
- ),
- ],
- ],
- ),
- ),
- ),
- ),
- const SizedBox(height: 8),
- FilledButton(
- onPressed: () => context.goNamed('conclusion'),
- child: const Text('Review Conclusion'),
- ),
- ],
- );
- },
- ),
- ],
- );
- }
- }
- class _EmptySessionCard extends StatelessWidget {
- const _EmptySessionCard();
- @override
- Widget build(BuildContext context) {
- return const Card(
- child: Padding(
- padding: EdgeInsets.all(20),
- child: Text('No session selected yet.'),
- ),
- );
- }
- }
|