sate_ai 0.1.1 copy "sate_ai: ^0.1.1" to clipboard
sate_ai: ^0.1.1 copied to clipboard

A fault injection framework for testing on-device AI models in Flutter. Simulate memory pressure, malformed inputs, and other failure modes to validate model reliability before deployment. Catch OOM c [...]

example/lib/main.dart

// ignore_for_file: public_member_api_docs
import 'package:flutter/material.dart';
import 'package:sate_ai/sate_ai.dart';

void main() => runApp(const SateAIApp());

class SateAIApp extends StatelessWidget {
  const SateAIApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'SATE AI Demo',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(
          seedColor: const Color(0xFF6C63FF),
          brightness: Brightness.dark,
        ),
        useMaterial3: true,
        fontFamily: 'GoogleSans',
      ),
      home: const StressDashboard(),
    );
  }
}

// ---------------------------------------------------------------------------
// Dashboard screen
// ---------------------------------------------------------------------------

class StressDashboard extends StatefulWidget {
  const StressDashboard({super.key});

  @override
  State<StressDashboard> createState() => _StressDashboardState();
}

class _StressDashboardState extends State<StressDashboard>
    with TickerProviderStateMixin {
  StressReport? _report;
  bool _running = false;
  String _status = 'Ready';
  final List<String> _log = [];
  late AnimationController _pulseCtrl;

  @override
  void initState() {
    super.initState();
    _pulseCtrl = AnimationController(
      vsync: this,
      duration: const Duration(milliseconds: 900),
    )..repeat(reverse: true);
  }

  @override
  void dispose() {
    _pulseCtrl.dispose();
    super.dispose();
  }

  Future<void> _runStressTest() async {
    setState(() {
      _running = true;
      _report = null;
      _log.clear();
      _status = 'Initialising model…';
    });

    final model = MockAdapter(
      modelId: 'demo-llm-v1',
      inferenceDelay: const Duration(milliseconds: 200),
    );

    _appendLog('🤖  Model: ${model.modelId}');
    _appendLog('🧪  Starting stress test…');

    setState(() => _status = 'Injecting memory pressure…');
    _appendLog('⚙️   Running MemoryPressureInjector (100 MB)');

    setState(() => _status = 'Injecting malformed inputs…');
    _appendLog('⚙️   Running MalformedInputInjector');

    final report = await SateAI.stress(
      model: model,
      injectors: [
        MemoryPressureInjector(model: model, limitMb: 100),
        const MalformedInputInjector(),
      ],
      timeout: const Duration(seconds: 15),
    );

    for (final result in report.results) {
      final icon = result.passed ? '✅' : '❌';
      _appendLog(
        '$icon  ${result.injectorType.displayName}: '
        '${result.inferenceTime?.inMilliseconds ?? "N/A"} ms',
      );
    }

    setState(() {
      _running = false;
      _report = report;
      _status = report.passed ? '✅ All tests passed!' : '❌ Failures detected';
    });
  }

  void _appendLog(String line) => setState(() => _log.add(line));

  @override
  Widget build(BuildContext context) {
    final cs = Theme.of(context).colorScheme;

    return Scaffold(
      backgroundColor: const Color(0xFF0F0F1A),
      appBar: AppBar(
        backgroundColor: Colors.transparent,
        elevation: 0,
        title: Row(
          children: [
            Container(
              padding: const EdgeInsets.all(8),
              decoration: BoxDecoration(
                gradient: const LinearGradient(
                  colors: [Color(0xFF6C63FF), Color(0xFF48CAE4)],
                ),
                borderRadius: BorderRadius.circular(10),
              ),
              child: const Icon(Icons.science, color: Colors.white, size: 20),
            ),
            const SizedBox(width: 12),
            const Text(
              'SATE AI',
              style: TextStyle(
                color: Colors.white,
                fontWeight: FontWeight.w700,
                fontSize: 22,
                letterSpacing: 1.2,
              ),
            ),
          ],
        ),
      ),
      body: Padding(
        padding: const EdgeInsets.all(20),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            _buildStatusCard(cs),
            const SizedBox(height: 16),
            _buildRunButton(cs),
            const SizedBox(height: 16),
            if (_report != null) ...[
              _buildSummaryCard(_report!, cs),
              const SizedBox(height: 16),
            ],
            Expanded(child: _buildLogCard(cs)),
          ],
        ),
      ),
    );
  }

  Widget _buildStatusCard(ColorScheme cs) {
    final color = _running
        ? const Color(0xFF48CAE4)
        : (_report?.passed ?? true)
            ? const Color(0xFF06D6A0)
            : const Color(0xFFEF476F);

    return AnimatedBuilder(
      animation: _pulseCtrl,
      builder: (context, child) {
        return Container(
          padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 14),
          decoration: BoxDecoration(
            color: const Color(0xFF1A1A2E),
            borderRadius: BorderRadius.circular(16),
            border: Border.all(
              color: _running
                  ? color.withAlpha(
                      ((_pulseCtrl.value * 180 + 75).round()),
                    )
                  : color.withAlpha(80),
              width: 1.5,
            ),
            boxShadow: [
              BoxShadow(
                color: color.withAlpha(_running ? 60 : 20),
                blurRadius: 20,
                spreadRadius: 2,
              ),
            ],
          ),
          child: Row(
            children: [
              _running
                  ? SizedBox(
                      width: 20,
                      height: 20,
                      child: CircularProgressIndicator(
                        strokeWidth: 2,
                        color: color,
                      ),
                    )
                  : Icon(
                      _report == null
                          ? Icons.pending_outlined
                          : (_report!.passed
                              ? Icons.check_circle
                              : Icons.error),
                      color: color,
                      size: 22,
                    ),
              const SizedBox(width: 12),
              Expanded(
                child: Text(
                  _status,
                  style: TextStyle(
                    color: color,
                    fontWeight: FontWeight.w600,
                    fontSize: 15,
                  ),
                ),
              ),
            ],
          ),
        );
      },
    );
  }

  Widget _buildRunButton(ColorScheme cs) {
    return GestureDetector(
      onTap: _running ? null : _runStressTest,
      child: AnimatedContainer(
        duration: const Duration(milliseconds: 200),
        padding: const EdgeInsets.symmetric(vertical: 16),
        decoration: BoxDecoration(
          gradient: _running
              ? const LinearGradient(
                  colors: [Color(0xFF3A3A5C), Color(0xFF2A2A4A)],
                )
              : const LinearGradient(
                  colors: [Color(0xFF6C63FF), Color(0xFF48CAE4)],
                  begin: Alignment.centerLeft,
                  end: Alignment.centerRight,
                ),
          borderRadius: BorderRadius.circular(16),
          boxShadow: _running
              ? []
              : [
                  const BoxShadow(
                    color: Color(0x556C63FF),
                    blurRadius: 20,
                    offset: Offset(0, 6),
                  ),
                ],
        ),
        child: Center(
          child: Row(
            mainAxisSize: MainAxisSize.min,
            children: [
              Icon(
                _running ? Icons.hourglass_top : Icons.play_arrow_rounded,
                color: Colors.white,
              ),
              const SizedBox(width: 8),
              Text(
                _running ? 'Running…' : 'Run Stress Test',
                style: const TextStyle(
                  color: Colors.white,
                  fontWeight: FontWeight.w700,
                  fontSize: 16,
                  letterSpacing: 0.5,
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }

  Widget _buildSummaryCard(StressReport report, ColorScheme cs) {
    return Container(
      padding: const EdgeInsets.all(18),
      decoration: BoxDecoration(
        color: const Color(0xFF1A1A2E),
        borderRadius: BorderRadius.circular(16),
        border: Border.all(color: Colors.white12),
      ),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          const Text(
            'Summary',
            style: TextStyle(
              color: Colors.white70,
              fontSize: 12,
              fontWeight: FontWeight.w600,
              letterSpacing: 1,
            ),
          ),
          const SizedBox(height: 12),
          Row(
            mainAxisAlignment: MainAxisAlignment.spaceAround,
            children: [
              _StatChip(
                label: 'Tests',
                value: '${report.totalTests}',
                color: const Color(0xFF48CAE4),
              ),
              _StatChip(
                label: 'Passed',
                value: '${report.passCount}',
                color: const Color(0xFF06D6A0),
              ),
              _StatChip(
                label: 'Failed',
                value: '${report.failureCount}',
                color: const Color(0xFFEF476F),
              ),
              _StatChip(
                label: 'Duration',
                value: '${report.totalDuration.inMilliseconds}ms',
                color: const Color(0xFFFFD166),
              ),
            ],
          ),
        ],
      ),
    );
  }

  Widget _buildLogCard(ColorScheme cs) {
    return Container(
      decoration: BoxDecoration(
        color: const Color(0xFF0D0D1A),
        borderRadius: BorderRadius.circular(16),
        border: Border.all(color: Colors.white10),
      ),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          const Padding(
            padding: EdgeInsets.fromLTRB(16, 12, 16, 4),
            child: Text(
              'LOG',
              style: TextStyle(
                color: Colors.white38,
                fontSize: 11,
                fontWeight: FontWeight.w700,
                letterSpacing: 2,
              ),
            ),
          ),
          const Divider(color: Colors.white10, height: 1),
          Expanded(
            child: _log.isEmpty
                ? const Center(
                    child: Text(
                      'Hit "Run Stress Test" to begin',
                      style: TextStyle(color: Colors.white24, fontSize: 13),
                    ),
                  )
                : ListView.builder(
                    padding: const EdgeInsets.symmetric(
                      vertical: 8,
                      horizontal: 16,
                    ),
                    itemCount: _log.length,
                    itemBuilder: (ctx, i) => Padding(
                      padding: const EdgeInsets.symmetric(vertical: 3),
                      child: Text(
                        _log[i],
                        style: const TextStyle(
                          color: Color(0xFFB0B8D8),
                          fontFamily: 'monospace',
                          fontSize: 13,
                        ),
                      ),
                    ),
                  ),
          ),
        ],
      ),
    );
  }
}

class _StatChip extends StatelessWidget {
  const _StatChip({
    required this.label,
    required this.value,
    required this.color,
  });

  final String label;
  final String value;
  final Color color;

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Text(
          value,
          style: TextStyle(
            color: color,
            fontSize: 22,
            fontWeight: FontWeight.w800,
          ),
        ),
        const SizedBox(height: 4),
        Text(
          label,
          style: const TextStyle(
            color: Colors.white38,
            fontSize: 11,
            fontWeight: FontWeight.w600,
            letterSpacing: 0.5,
          ),
        ),
      ],
    );
  }
}
0
likes
0
points
0
downloads

Publisher

unverified uploader

Weekly Downloads

A fault injection framework for testing on-device AI models in Flutter. Simulate memory pressure, malformed inputs, and other failure modes to validate model reliability before deployment. Catch OOM crashes, input validation failures, and degradation scenarios in CI/CD pipelines.

Repository (GitHub)
View/report issues

Topics

#testing #ai #on-device #llm #fault-injection

License

unknown (license)

Dependencies

flutter, onnxruntime

More

Packages that depend on sate_ai