import 'package:flutter/material.dart'; import 'dart:io'; import '../../../core/network/ai_api_service.dart'; /// AI口语评估页面 class AISpeakingPage extends StatefulWidget { const AISpeakingPage({Key? key}) : super(key: key); @override State createState() => _AISpeakingPageState(); } class _AISpeakingPageState extends State { final AIApiService _aiApiService = AIApiService(); bool _isRecording = false; bool _isLoading = false; File? _audioFile; Map? _evaluationResult; String? _error; String _selectedTask = 'pronunciation'; final List> _taskTypes = [ {'value': 'pronunciation', 'label': '发音评估'}, {'value': 'fluency', 'label': '流利度评估'}, {'value': 'conversation', 'label': '对话评估'}, {'value': 'presentation', 'label': '演讲评估'}, ]; Future _startRecording() async { // 这里应该集成录音功能 // 暂时模拟录音状态 setState(() { _isRecording = true; _error = null; }); // 模拟录音过程 await Future.delayed(const Duration(seconds: 2)); setState(() { _isRecording = false; // 模拟生成音频文件 _audioFile = File('/tmp/mock_audio.wav'); }); ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('录音完成,可以开始评估')), ); } Future _stopRecording() async { setState(() { _isRecording = false; }); } Future _submitForEvaluation() async { if (_audioFile == null) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('请先录制音频')), ); return; } setState(() { _isLoading = true; _error = null; _evaluationResult = null; }); try { final result = await _aiApiService.evaluateSpeaking( audioText: '模拟音频转文本结果', // 实际应该是音频转文本的结果 prompt: '请评估这段英语口语的$_selectedTask', ); setState(() { _evaluationResult = result; _isLoading = false; }); } catch (e) { setState(() { _error = e.toString(); _isLoading = false; }); } } void _clearRecording() { setState(() { _audioFile = null; _evaluationResult = null; _error = null; }); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('AI口语评估'), backgroundColor: Theme.of(context).primaryColor, foregroundColor: Colors.white, actions: [ IconButton( icon: const Icon(Icons.clear), onPressed: _clearRecording, tooltip: '清空录音', ), ], ), body: SingleChildScrollView( padding: const EdgeInsets.all(16.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // 任务类型选择 Card( child: Padding( padding: const EdgeInsets.all(16.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( '评估类型', style: TextStyle( fontSize: 16, fontWeight: FontWeight.bold, ), ), const SizedBox(height: 12), Wrap( spacing: 8, children: _taskTypes.map((type) { final isSelected = _selectedTask == type['value']; return ChoiceChip( label: Text(type['label']!), selected: isSelected, onSelected: (selected) { if (selected) { setState(() { _selectedTask = type['value']!; }); } }, ); }).toList(), ), ], ), ), ), const SizedBox(height: 16), // 录音控制区域 Card( child: Padding( padding: const EdgeInsets.all(16.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( '录音控制', style: TextStyle( fontSize: 16, fontWeight: FontWeight.bold, ), ), const SizedBox(height: 16), // 录音按钮 Center( child: Column( children: [ GestureDetector( onTap: _isRecording ? _stopRecording : _startRecording, child: Container( width: 120, height: 120, decoration: BoxDecoration( shape: BoxShape.circle, color: _isRecording ? Colors.red.shade400 : Theme.of(context).primaryColor, boxShadow: [ BoxShadow( color: (_isRecording ? Colors.red.shade400 : Theme.of(context).primaryColor) .withOpacity(0.3), spreadRadius: _isRecording ? 10 : 5, blurRadius: 20, ), ], ), child: Icon( _isRecording ? Icons.stop : Icons.mic, size: 50, color: Colors.white, ), ), ), const SizedBox(height: 16), Text( _isRecording ? '点击停止录音' : '点击开始录音', style: const TextStyle( fontSize: 16, fontWeight: FontWeight.bold, ), ), if (_audioFile != null) Container( margin: const EdgeInsets.only(top: 12), padding: const EdgeInsets.symmetric( horizontal: 16, vertical: 8, ), decoration: BoxDecoration( color: Colors.green.shade50, borderRadius: BorderRadius.circular(20), border: Border.all( color: Colors.green.shade200, ), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Icon( Icons.check_circle, color: Colors.green.shade600, size: 16, ), const SizedBox(width: 4), const Text( '录音完成', style: TextStyle(fontSize: 12), ), ], ), ), ], ), ), const SizedBox(height: 16), // 评估按钮 SizedBox( width: double.infinity, child: ElevatedButton( onPressed: (_audioFile != null && !_isLoading) ? _submitForEvaluation : null, style: ElevatedButton.styleFrom( backgroundColor: Theme.of(context).primaryColor, foregroundColor: Colors.white, padding: const EdgeInsets.symmetric(vertical: 16), ), child: _isLoading ? const Row( mainAxisAlignment: MainAxisAlignment.center, children: [ SizedBox( width: 20, height: 20, child: CircularProgressIndicator( strokeWidth: 2, valueColor: AlwaysStoppedAnimation(Colors.white), ), ), SizedBox(width: 8), Text('AI评估中...'), ], ) : const Text( '开始AI评估', style: TextStyle(fontSize: 16), ), ), ), ], ), ), ), const SizedBox(height: 16), // 错误显示 if (_error != null) Card( color: Colors.red.shade50, child: Padding( padding: const EdgeInsets.all(16.0), child: Row( children: [ Icon(Icons.error, color: Colors.red.shade700), const SizedBox(width: 8), Expanded( child: Text( _error!, style: TextStyle(color: Colors.red.shade700), ), ), ], ), ), ), // 评估结果显示 if (_evaluationResult != null) Card( child: Padding( padding: const EdgeInsets.all(16.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Icon( Icons.assessment, color: Colors.blue.shade600, ), const SizedBox(width: 8), const Text( 'AI评估结果', style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, ), ), ], ), const SizedBox(height: 16), // 总体评分 if (_evaluationResult!['overall_score'] != null) Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: Colors.blue.shade50, borderRadius: BorderRadius.circular(8), ), child: Row( children: [ const Icon(Icons.star, color: Colors.amber), const SizedBox(width: 8), Text( '总体评分: ${_evaluationResult!['overall_score']}/100', style: const TextStyle( fontSize: 16, fontWeight: FontWeight.bold, ), ), ], ), ), const SizedBox(height: 12), // 各项评分 if (_evaluationResult!['scores'] != null) Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( '详细评分:', style: TextStyle( fontSize: 16, fontWeight: FontWeight.bold, ), ), const SizedBox(height: 8), ...(_evaluationResult!['scores'] as Map) .entries .map( (entry) => Padding( padding: const EdgeInsets.symmetric(vertical: 4), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( _getScoreLabel(entry.key), style: const TextStyle(fontSize: 14), ), Container( padding: const EdgeInsets.symmetric( horizontal: 8, vertical: 2, ), decoration: BoxDecoration( color: _getScoreColor(entry.value), borderRadius: BorderRadius.circular(12), ), child: Text( '${entry.value}/100', style: const TextStyle( color: Colors.white, fontSize: 12, fontWeight: FontWeight.bold, ), ), ), ], ), ), ), ], ), // 改进建议 if (_evaluationResult!['suggestions'] != null) Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const SizedBox(height: 16), const Text( '改进建议:', style: TextStyle( fontSize: 16, fontWeight: FontWeight.bold, ), ), const SizedBox(height: 8), Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: Colors.green.shade50, borderRadius: BorderRadius.circular(8), border: Border.all( color: Colors.green.shade200, ), ), child: Text( _evaluationResult!['suggestions'].toString(), style: const TextStyle(fontSize: 14), ), ), ], ), ], ), ), ), ], ), ), ); } String _getScoreLabel(String key) { switch (key) { case 'pronunciation': return '发音准确度'; case 'fluency': return '流利度'; case 'grammar': return '语法正确性'; case 'vocabulary': return '词汇丰富度'; case 'coherence': return '连贯性'; default: return key; } } Color _getScoreColor(dynamic score) { final scoreValue = score is int ? score : int.tryParse(score.toString()) ?? 0; if (scoreValue >= 80) { return Colors.green; } else if (scoreValue >= 60) { return Colors.orange; } else { return Colors.red; } } }