Compare commits
No commits in common. "508244fac38f51e1266c7dd6b86537acb8a3c5e8" and "aba8b44ab8fcf3bd97f584b1c5ecd60990a4ab5b" have entirely different histories.
508244fac3
...
aba8b44ab8
|
|
@ -40,10 +40,14 @@ class _SpeechRecognitionPageState extends State<SpeechRecognitionPage> {
|
|||
bool _isListening = false;
|
||||
String _currentText = '';
|
||||
String _errorMessage = '';
|
||||
List<String> _recognitionHistory = [];
|
||||
|
||||
/// 本次录音开始前的文本内容(用于实时追加)
|
||||
String _baseText = '';
|
||||
|
||||
// 录音相关
|
||||
final List<String> _realtimeResults = []; // 存储实时识别片段
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
|
@ -148,9 +152,11 @@ class _SpeechRecognitionPageState extends State<SpeechRecognitionPage> {
|
|||
);
|
||||
}
|
||||
|
||||
/// 清除内容
|
||||
void _clearContent() {
|
||||
/// 清除历史记录
|
||||
void _clearHistory() {
|
||||
setState(() {
|
||||
_recognitionHistory.clear();
|
||||
_realtimeResults.clear();
|
||||
_currentText = '';
|
||||
_baseText = ''; // 也清空基础文本
|
||||
_textController.clear();
|
||||
|
|
@ -167,8 +173,8 @@ class _SpeechRecognitionPageState extends State<SpeechRecognitionPage> {
|
|||
actions: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.clear_all),
|
||||
onPressed: _clearContent,
|
||||
tooltip: '清除内容',
|
||||
onPressed: _clearHistory,
|
||||
tooltip: '清除历史',
|
||||
),
|
||||
],
|
||||
),
|
||||
|
|
@ -183,6 +189,9 @@ class _SpeechRecognitionPageState extends State<SpeechRecognitionPage> {
|
|||
// 识别结果卡片
|
||||
_buildRecognitionCard(),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// 历史记录
|
||||
Expanded(child: _buildHistoryCard()),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
@ -387,6 +396,66 @@ class _SpeechRecognitionPageState extends State<SpeechRecognitionPage> {
|
|||
);
|
||||
}
|
||||
|
||||
/// 构建历史记录卡片
|
||||
Widget _buildHistoryCard() {
|
||||
return Card(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text(
|
||||
'识别历史',
|
||||
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Expanded(
|
||||
child: _recognitionHistory.isEmpty
|
||||
? Center(
|
||||
child: Text(
|
||||
'暂无识别历史',
|
||||
style: TextStyle(
|
||||
color: Colors.grey[600],
|
||||
fontStyle: FontStyle.italic,
|
||||
),
|
||||
),
|
||||
)
|
||||
: ListView.builder(
|
||||
itemCount: _recognitionHistory.length,
|
||||
itemBuilder: (context, index) {
|
||||
return Card(
|
||||
margin: const EdgeInsets.only(bottom: 8.0),
|
||||
child: ListTile(
|
||||
leading: CircleAvatar(
|
||||
backgroundColor: Theme.of(context).primaryColor,
|
||||
child: Text(
|
||||
'${index + 1}',
|
||||
style: const TextStyle(color: Colors.white),
|
||||
),
|
||||
),
|
||||
title: Text(_recognitionHistory[index]),
|
||||
trailing: IconButton(
|
||||
icon: const Icon(Icons.copy),
|
||||
onPressed: () {
|
||||
// TODO: 实现复制到剪贴板功能
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('已复制到剪贴板'),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// 构建浮动操作按钮
|
||||
Widget _buildFloatingActionButton() {
|
||||
if (!_isInitialized) {
|
||||
|
|
@ -439,6 +508,16 @@ class _SpeechRecognitionPageState extends State<SpeechRecognitionPage> {
|
|||
});
|
||||
|
||||
if (!isListening) {
|
||||
// 录音结束后,确认当前文本并保存到历史记录
|
||||
if (_currentText.isNotEmpty) {
|
||||
setState(() {
|
||||
_recognitionHistory.insert(0, _currentText);
|
||||
// 限制历史记录数量
|
||||
if (_recognitionHistory.length > 10) {
|
||||
_recognitionHistory.removeLast();
|
||||
}
|
||||
});
|
||||
}
|
||||
// 录音结束后,更新基础文本为下次录音做准备
|
||||
setState(() {
|
||||
_baseText = _textController.text; // 保存当前文本作为下次的基础
|
||||
|
|
@ -448,6 +527,7 @@ class _SpeechRecognitionPageState extends State<SpeechRecognitionPage> {
|
|||
// 开始录音时记录当前文本作为基础,清空当前识别文本
|
||||
setState(() {
|
||||
_baseText = _textController.text; // 记录录音开始前的文本
|
||||
_realtimeResults.clear();
|
||||
_currentText = ''; // 清空当前识别文本
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,14 +3,24 @@ class SpeechRecognitionResult {
|
|||
/// 识别出的文字内容
|
||||
final String recognizedWords;
|
||||
|
||||
/// 识别置信度(0.0 到 1.0)
|
||||
final double confidence;
|
||||
|
||||
/// 备选识别结果
|
||||
final List<String> alternatives;
|
||||
|
||||
const SpeechRecognitionResult({
|
||||
required this.recognizedWords,
|
||||
this.confidence = 0.0,
|
||||
this.alternatives = const [],
|
||||
});
|
||||
|
||||
/// 从 Map 创建 [SpeechRecognitionResult] 实例
|
||||
factory SpeechRecognitionResult.fromMap(Map<String, dynamic> map) {
|
||||
return SpeechRecognitionResult(
|
||||
recognizedWords: map['recognizedWords'] as String? ?? '',
|
||||
confidence: (map['confidence'] as num?)?.toDouble() ?? 0.0,
|
||||
alternatives: List<String>.from(map['alternatives'] as List? ?? []),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -18,23 +28,31 @@ class SpeechRecognitionResult {
|
|||
Map<String, dynamic> toMap() {
|
||||
return {
|
||||
'recognizedWords': recognizedWords,
|
||||
'confidence': confidence,
|
||||
'alternatives': alternatives,
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'SpeechRecognitionResult(recognizedWords: $recognizedWords)';
|
||||
return 'SpeechRecognitionResult(recognizedWords: $recognizedWords, '
|
||||
'confidence: $confidence, alternatives: $alternatives)';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if (identical(this, other)) return true;
|
||||
return other is SpeechRecognitionResult &&
|
||||
other.recognizedWords == recognizedWords;
|
||||
other.recognizedWords == recognizedWords &&
|
||||
other.confidence == confidence &&
|
||||
other.alternatives.length == alternatives.length &&
|
||||
other.alternatives.every((alt) => alternatives.contains(alt));
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
return recognizedWords.hashCode;
|
||||
return recognizedWords.hashCode ^
|
||||
confidence.hashCode ^
|
||||
alternatives.hashCode;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -221,7 +221,6 @@ class YxAsrService implements SpeechRecognitionService {
|
|||
bool _isStartingRecording = false; // 防抖保护:防止重复启动录音
|
||||
bool _isInitialized = false;
|
||||
String _currentModelPath = '';
|
||||
String _lastRecognizedText = ''; // 记录上次识别的文本,避免重复发送
|
||||
|
||||
// 识别速度配置
|
||||
RecognitionSpeed _recognitionSpeed = RecognitionSpeed.fast;
|
||||
|
|
@ -514,7 +513,6 @@ class YxAsrService implements SpeechRecognitionService {
|
|||
await _startAudioRecording(_sampleRate.hz);
|
||||
|
||||
_isListening = true;
|
||||
_lastRecognizedText = ''; // 重置上次识别的文本
|
||||
_statusController.add(true);
|
||||
|
||||
// 开始识别循环处理
|
||||
|
|
@ -742,18 +740,13 @@ class YxAsrService implements SpeechRecognitionService {
|
|||
final result = _recognizer!.getResult(_stream!);
|
||||
debugPrint('🔍 [YxAsr] 获取识别结果: "${result.text}"');
|
||||
|
||||
// 只有当识别结果不为空、启用了部分结果、且与上次结果不同时才发送
|
||||
if (result.text.isNotEmpty &&
|
||||
partialResults &&
|
||||
result.text != _lastRecognizedText) {
|
||||
if (result.text.isNotEmpty && partialResults) {
|
||||
debugPrint('🎤 [YxAsr] 发送实时识别结果: ${result.text}');
|
||||
_lastRecognizedText = result.text; // 更新最后识别的文本
|
||||
_sendResult(
|
||||
recognizedWords: result.text,
|
||||
confidence: 0.8,
|
||||
alternatives: [],
|
||||
);
|
||||
} else if (result.text.isNotEmpty &&
|
||||
result.text == _lastRecognizedText) {
|
||||
debugPrint('🔄 [YxAsr] 跳过重复识别结果: "${result.text}"');
|
||||
}
|
||||
|
||||
// 端点检测已禁用,由用户手动控制录音结束
|
||||
|
|
@ -769,10 +762,14 @@ class YxAsrService implements SpeechRecognitionService {
|
|||
/// 发送识别结果到结果流
|
||||
void _sendResult({
|
||||
required String recognizedWords,
|
||||
required double confidence,
|
||||
required List<String> alternatives,
|
||||
}) {
|
||||
debugPrint('📤 [YxAsr] 发送识别结果: "$recognizedWords"');
|
||||
final result = SpeechRecognitionResult(
|
||||
recognizedWords: recognizedWords,
|
||||
confidence: confidence,
|
||||
alternatives: alternatives,
|
||||
);
|
||||
_resultController.add(result);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue