import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import '../models/ai_model.dart'; import '../providers/model_provider.dart'; import '../theme/app_colors.dart'; class ModelsScreen extends StatefulWidget { const ModelsScreen({super.key}); @override State createState() => _ModelsScreenState(); } class _ModelsScreenState extends State { @override void initState() { super.initState(); WidgetsBinding.instance.addPostFrameCallback((_) { context.read().refresh(); }); } @override Widget build(BuildContext context) { final provider = context.watch(); final snapshot = provider.snapshot; return Scaffold( appBar: AppBar( title: const Text('模型管理'), actions: [ IconButton( onPressed: provider.isLoading ? null : () => provider.refresh(), icon: const Icon(Icons.refresh_rounded), ), ], ), body: RefreshIndicator( onRefresh: provider.refresh, child: ListView( padding: const EdgeInsets.all(AppSpacing.md), children: [ if (provider.error != null) Padding( padding: const EdgeInsets.only(bottom: AppSpacing.sm), child: Text( provider.error!, style: const TextStyle(color: AppColors.error), ), ), if (provider.status != null) Padding( padding: const EdgeInsets.only(bottom: AppSpacing.sm), child: Text( provider.status!, style: const TextStyle(color: AppColors.textSecondary), ), ), if (snapshot != null) ...[ Card( child: Padding( padding: const EdgeInsets.all(AppSpacing.md), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( '存储 ${snapshot.usedLabel} / ${snapshot.quotaLabel}', style: Theme.of(context).textTheme.titleMedium, ), const SizedBox(height: 8), LinearProgressIndicator(value: snapshot.usageRatio), const SizedBox(height: 8), Text( '激活: LLM=${snapshot.active['llm'] ?? '-'} · ' 'ASR=${snapshot.active['asr'] ?? '-'} · ' 'TTS=${snapshot.active['tts'] ?? '-'}', style: Theme.of(context).textTheme.bodySmall?.copyWith( color: AppColors.textSecondary, ), ), ], ), ), ), const SizedBox(height: AppSpacing.md), ], if (provider.isLoading && snapshot == null) const Padding( padding: EdgeInsets.only(top: 48), child: Center(child: CircularProgressIndicator()), ) else if (snapshot == null || snapshot.models.isEmpty) const Padding( padding: EdgeInsets.only(top: 48), child: Center(child: Text('暂无模型清单(AI 插件可能未就绪)')), ) else ...snapshot.models.map((model) { final active = snapshot.isActive(model); return Card( margin: const EdgeInsets.only(bottom: AppSpacing.md), child: Padding( padding: const EdgeInsets.all(AppSpacing.md), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Expanded( child: Text( model.name, style: Theme.of(context).textTheme.titleMedium, ), ), if (active) Container( padding: const EdgeInsets.symmetric( horizontal: 8, vertical: 2, ), decoration: BoxDecoration( color: AppColors.success.withValues(alpha: 0.15), borderRadius: BorderRadius.circular(999), ), child: const Text( '使用中', style: TextStyle( color: AppColors.success, fontSize: 12, ), ), ), ], ), const SizedBox(height: 4), Text( '${model.kindLabel} · ${model.sizeLabel} · 内存 ${model.memoryLabel}', style: Theme.of(context).textTheme.bodySmall?.copyWith( color: AppColors.textSecondary, ), ), if (model.recommendedTier.isNotEmpty) Text( model.recommendedTier, style: Theme.of(context) .textTheme .bodySmall ?.copyWith(color: AppColors.textSecondary), ), if (!model.downloaded && model.downloadProgress > 0) Padding( padding: const EdgeInsets.only(top: 8), child: LinearProgressIndicator( value: model.downloadProgress / 100.0, ), ), const SizedBox(height: 10), Wrap( spacing: 8, runSpacing: 8, children: [ if (!model.downloaded) FilledButton.tonal( onPressed: provider.isActing ? null : () => provider.download(model.id), child: Text( model.downloadProgress > 0 && model.downloadProgress < 100 ? '下载中 ${model.downloadProgress}%' : '下载', ), ), if (model.downloaded && !active) FilledButton( onPressed: provider.isActing ? null : () => provider.switchTo( modelId: model.id, kind: model.kind, ), child: const Text('切换使用'), ), if (model.downloaded && !active) OutlinedButton( onPressed: provider.isActing ? null : () => _confirmDelete(context, model), child: const Text('删除'), ), ], ), ], ), ), ); }), ], ), ), ); } Future _confirmDelete(BuildContext context, AiModelInfo model) async { final ok = await showDialog( context: context, builder: (context) => AlertDialog( title: const Text('删除模型'), content: Text('确定删除 ${model.name}?此操作不可恢复。'), actions: [ TextButton( onPressed: () => Navigator.pop(context, false), child: const Text('取消'), ), FilledButton( onPressed: () => Navigator.pop(context, true), child: const Text('删除'), ), ], ), ); if (ok == true && context.mounted) { await context.read().delete(model.id); } } }