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;
|
bool _isListening = false;
|
||||||
String _currentText = '';
|
String _currentText = '';
|
||||||
String _errorMessage = '';
|
String _errorMessage = '';
|
||||||
|
List<String> _recognitionHistory = [];
|
||||||
|
|
||||||
/// 本次录音开始前的文本内容(用于实时追加)
|
/// 本次录音开始前的文本内容(用于实时追加)
|
||||||
String _baseText = '';
|
String _baseText = '';
|
||||||
|
|
||||||
|
// 录音相关
|
||||||
|
final List<String> _realtimeResults = []; // 存储实时识别片段
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
|
@ -148,9 +152,11 @@ class _SpeechRecognitionPageState extends State<SpeechRecognitionPage> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 清除内容
|
/// 清除历史记录
|
||||||
void _clearContent() {
|
void _clearHistory() {
|
||||||
setState(() {
|
setState(() {
|
||||||
|
_recognitionHistory.clear();
|
||||||
|
_realtimeResults.clear();
|
||||||
_currentText = '';
|
_currentText = '';
|
||||||
_baseText = ''; // 也清空基础文本
|
_baseText = ''; // 也清空基础文本
|
||||||
_textController.clear();
|
_textController.clear();
|
||||||
|
|
@ -167,8 +173,8 @@ class _SpeechRecognitionPageState extends State<SpeechRecognitionPage> {
|
||||||
actions: [
|
actions: [
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(Icons.clear_all),
|
icon: const Icon(Icons.clear_all),
|
||||||
onPressed: _clearContent,
|
onPressed: _clearHistory,
|
||||||
tooltip: '清除内容',
|
tooltip: '清除历史',
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
@ -183,6 +189,9 @@ class _SpeechRecognitionPageState extends State<SpeechRecognitionPage> {
|
||||||
// 识别结果卡片
|
// 识别结果卡片
|
||||||
_buildRecognitionCard(),
|
_buildRecognitionCard(),
|
||||||
const SizedBox(height: 16),
|
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() {
|
Widget _buildFloatingActionButton() {
|
||||||
if (!_isInitialized) {
|
if (!_isInitialized) {
|
||||||
|
|
@ -439,6 +508,16 @@ class _SpeechRecognitionPageState extends State<SpeechRecognitionPage> {
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!isListening) {
|
if (!isListening) {
|
||||||
|
// 录音结束后,确认当前文本并保存到历史记录
|
||||||
|
if (_currentText.isNotEmpty) {
|
||||||
|
setState(() {
|
||||||
|
_recognitionHistory.insert(0, _currentText);
|
||||||
|
// 限制历史记录数量
|
||||||
|
if (_recognitionHistory.length > 10) {
|
||||||
|
_recognitionHistory.removeLast();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
// 录音结束后,更新基础文本为下次录音做准备
|
// 录音结束后,更新基础文本为下次录音做准备
|
||||||
setState(() {
|
setState(() {
|
||||||
_baseText = _textController.text; // 保存当前文本作为下次的基础
|
_baseText = _textController.text; // 保存当前文本作为下次的基础
|
||||||
|
|
@ -448,6 +527,7 @@ class _SpeechRecognitionPageState extends State<SpeechRecognitionPage> {
|
||||||
// 开始录音时记录当前文本作为基础,清空当前识别文本
|
// 开始录音时记录当前文本作为基础,清空当前识别文本
|
||||||
setState(() {
|
setState(() {
|
||||||
_baseText = _textController.text; // 记录录音开始前的文本
|
_baseText = _textController.text; // 记录录音开始前的文本
|
||||||
|
_realtimeResults.clear();
|
||||||
_currentText = ''; // 清空当前识别文本
|
_currentText = ''; // 清空当前识别文本
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,14 +3,24 @@ class SpeechRecognitionResult {
|
||||||
/// 识别出的文字内容
|
/// 识别出的文字内容
|
||||||
final String recognizedWords;
|
final String recognizedWords;
|
||||||
|
|
||||||
|
/// 识别置信度(0.0 到 1.0)
|
||||||
|
final double confidence;
|
||||||
|
|
||||||
|
/// 备选识别结果
|
||||||
|
final List<String> alternatives;
|
||||||
|
|
||||||
const SpeechRecognitionResult({
|
const SpeechRecognitionResult({
|
||||||
required this.recognizedWords,
|
required this.recognizedWords,
|
||||||
|
this.confidence = 0.0,
|
||||||
|
this.alternatives = const [],
|
||||||
});
|
});
|
||||||
|
|
||||||
/// 从 Map 创建 [SpeechRecognitionResult] 实例
|
/// 从 Map 创建 [SpeechRecognitionResult] 实例
|
||||||
factory SpeechRecognitionResult.fromMap(Map<String, dynamic> map) {
|
factory SpeechRecognitionResult.fromMap(Map<String, dynamic> map) {
|
||||||
return SpeechRecognitionResult(
|
return SpeechRecognitionResult(
|
||||||
recognizedWords: map['recognizedWords'] as String? ?? '',
|
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() {
|
Map<String, dynamic> toMap() {
|
||||||
return {
|
return {
|
||||||
'recognizedWords': recognizedWords,
|
'recognizedWords': recognizedWords,
|
||||||
|
'confidence': confidence,
|
||||||
|
'alternatives': alternatives,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'SpeechRecognitionResult(recognizedWords: $recognizedWords)';
|
return 'SpeechRecognitionResult(recognizedWords: $recognizedWords, '
|
||||||
|
'confidence: $confidence, alternatives: $alternatives)';
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) {
|
bool operator ==(Object other) {
|
||||||
if (identical(this, other)) return true;
|
if (identical(this, other)) return true;
|
||||||
return other is SpeechRecognitionResult &&
|
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
|
@override
|
||||||
int get hashCode {
|
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 _isStartingRecording = false; // 防抖保护:防止重复启动录音
|
||||||
bool _isInitialized = false;
|
bool _isInitialized = false;
|
||||||
String _currentModelPath = '';
|
String _currentModelPath = '';
|
||||||
String _lastRecognizedText = ''; // 记录上次识别的文本,避免重复发送
|
|
||||||
|
|
||||||
// 识别速度配置
|
// 识别速度配置
|
||||||
RecognitionSpeed _recognitionSpeed = RecognitionSpeed.fast;
|
RecognitionSpeed _recognitionSpeed = RecognitionSpeed.fast;
|
||||||
|
|
@ -514,7 +513,6 @@ class YxAsrService implements SpeechRecognitionService {
|
||||||
await _startAudioRecording(_sampleRate.hz);
|
await _startAudioRecording(_sampleRate.hz);
|
||||||
|
|
||||||
_isListening = true;
|
_isListening = true;
|
||||||
_lastRecognizedText = ''; // 重置上次识别的文本
|
|
||||||
_statusController.add(true);
|
_statusController.add(true);
|
||||||
|
|
||||||
// 开始识别循环处理
|
// 开始识别循环处理
|
||||||
|
|
@ -742,18 +740,13 @@ class YxAsrService implements SpeechRecognitionService {
|
||||||
final result = _recognizer!.getResult(_stream!);
|
final result = _recognizer!.getResult(_stream!);
|
||||||
debugPrint('🔍 [YxAsr] 获取识别结果: "${result.text}"');
|
debugPrint('🔍 [YxAsr] 获取识别结果: "${result.text}"');
|
||||||
|
|
||||||
// 只有当识别结果不为空、启用了部分结果、且与上次结果不同时才发送
|
if (result.text.isNotEmpty && partialResults) {
|
||||||
if (result.text.isNotEmpty &&
|
|
||||||
partialResults &&
|
|
||||||
result.text != _lastRecognizedText) {
|
|
||||||
debugPrint('🎤 [YxAsr] 发送实时识别结果: ${result.text}');
|
debugPrint('🎤 [YxAsr] 发送实时识别结果: ${result.text}');
|
||||||
_lastRecognizedText = result.text; // 更新最后识别的文本
|
|
||||||
_sendResult(
|
_sendResult(
|
||||||
recognizedWords: result.text,
|
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({
|
void _sendResult({
|
||||||
required String recognizedWords,
|
required String recognizedWords,
|
||||||
|
required double confidence,
|
||||||
|
required List<String> alternatives,
|
||||||
}) {
|
}) {
|
||||||
debugPrint('📤 [YxAsr] 发送识别结果: "$recognizedWords"');
|
debugPrint('📤 [YxAsr] 发送识别结果: "$recognizedWords"');
|
||||||
final result = SpeechRecognitionResult(
|
final result = SpeechRecognitionResult(
|
||||||
recognizedWords: recognizedWords,
|
recognizedWords: recognizedWords,
|
||||||
|
confidence: confidence,
|
||||||
|
alternatives: alternatives,
|
||||||
);
|
);
|
||||||
_resultController.add(result);
|
_resultController.add(result);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue