feat(l10n): Add silence duration settings for speech-to-text

This commit is contained in:
cogwheel0
2025-11-05 00:48:20 +05:30
parent 1bb2cbae25
commit 3424af60f9
6 changed files with 94 additions and 31 deletions

View File

@@ -689,43 +689,66 @@ class TextToSpeechService {
} }
List<String> _splitForTts(String text) { List<String> _splitForTts(String text) {
// Normalize whitespace // Mirrors OpenWebUI's extractSentencesForAudio implementation
final normalized = text.replaceAll(RegExp(r"\s+"), ' ').trim(); // See: src/lib/utils/index.ts lines 953-970, 907-928
if (normalized.isEmpty) return const [];
// Split on sentence-ending punctuation while keeping the delimiter // 1. Preserve code blocks (replace with placeholders)
final parts = <String>[]; final codeBlocks = <String>[];
final sentenceRegex = RegExp(r"(.+?[\.!?]+)(\s+|\$)"); var processed = text;
int index = 0; var codeBlockIndex = 0;
for (final match in sentenceRegex.allMatches('$normalized ')) {
final s = match.group(1) ?? '';
if (s.trim().isNotEmpty) parts.add(s.trim());
index = match.end;
}
if (index < normalized.length) {
final tail = normalized.substring(index).trim();
if (tail.isNotEmpty) parts.add(tail);
}
// Fallback to length-based splits for very long segments // Match triple backticks code blocks
const maxLen = 300; final codeBlockRegex = RegExp(r'```[\s\S]*?```', multiLine: true);
final chunks = <String>[]; processed = processed.replaceAllMapped(codeBlockRegex, (match) {
for (final p in parts.isEmpty ? [normalized] : parts) { final placeholder = '\u0000$codeBlockIndex\u0000';
if (p.length <= maxLen) { codeBlocks.add(match.group(0)!);
chunks.add(p); codeBlockIndex++;
return placeholder;
});
// 2. Split on sentence-ending punctuation: .!?
// OpenWebUI uses: /(?<=[.!?])\s+/
final sentences = processed
.split(RegExp(r'(?<=[.!?])\s+'))
.map((s) => s.trim())
.where((s) => s.isNotEmpty)
.toList();
// 3. Restore code blocks from placeholders
final restoredSentences = sentences
.map((sentence) {
return sentence.replaceAllMapped(RegExp(r'\u0000(\d+)\u0000'), (
match,
) {
final idx = int.parse(match.group(1)!);
return idx < codeBlocks.length ? codeBlocks[idx] : '';
});
})
.where((s) => s.isNotEmpty)
.toList();
// 4. Merge short sentences (< 4 words OR < 50 chars)
// OpenWebUI logic from extractSentencesForAudio
final mergedChunks = <String>[];
for (final sentence in restoredSentences) {
if (mergedChunks.isEmpty) {
mergedChunks.add(sentence);
} else { } else {
// Try splitting on commas/spaces final lastIndex = mergedChunks.length - 1;
var remaining = p; final previousText = mergedChunks[lastIndex];
while (remaining.length > maxLen) { final wordCount = previousText.split(RegExp(r'\s+')).length;
int cut = remaining.lastIndexOf(RegExp(r",\s|\s"), maxLen); final charCount = previousText.length;
cut = cut <= 0 ? maxLen : cut;
chunks.add(remaining.substring(0, cut).trim()); // Merge if previous chunk is too short
remaining = remaining.substring(cut).trim(); if (wordCount < 4 || charCount < 50) {
mergedChunks[lastIndex] = '$previousText $sentence';
} else {
mergedChunks.add(sentence);
} }
if (remaining.isNotEmpty) chunks.add(remaining);
} }
} }
return chunks;
return mergedChunks.isEmpty ? [text.trim()] : mergedChunks;
} }
Future<void> _configurePreferredVoice() async { Future<void> _configurePreferredVoice() async {

View File

@@ -1844,6 +1844,18 @@ abstract class AppLocalizations {
/// **'Connect to a server with transcription enabled to use this option.'** /// **'Connect to a server with transcription enabled to use this option.'**
String get sttServerUnavailableWarning; String get sttServerUnavailableWarning;
/// Label for the silence duration setting in server speech-to-text.
///
/// In en, this message translates to:
/// **'Silence Duration'**
String get sttSilenceDuration;
/// Description for the silence duration slider in server speech-to-text settings.
///
/// In en, this message translates to:
/// **'Time to wait after silence before auto-stopping recording'**
String get sttSilenceDurationDescription;
/// Label for selecting the text-to-speech engine. /// Label for selecting the text-to-speech engine.
/// ///
/// In en, this message translates to: /// In en, this message translates to:

View File

@@ -966,6 +966,13 @@ class AppLocalizationsDe extends AppLocalizations {
String get sttServerUnavailableWarning => String get sttServerUnavailableWarning =>
'Verbinde dich mit einem Server mit aktivierter Transkription, um diese Option zu nutzen.'; 'Verbinde dich mit einem Server mit aktivierter Transkription, um diese Option zu nutzen.';
@override
String get sttSilenceDuration => 'Stille-Dauer';
@override
String get sttSilenceDurationDescription =>
'Zeit nach Stille warten, bevor die Aufnahme automatisch gestoppt wird';
@override @override
String get ttsEngineLabel => 'Engine'; String get ttsEngineLabel => 'Engine';

View File

@@ -960,6 +960,13 @@ class AppLocalizationsEn extends AppLocalizations {
String get sttServerUnavailableWarning => String get sttServerUnavailableWarning =>
'Connect to a server with transcription enabled to use this option.'; 'Connect to a server with transcription enabled to use this option.';
@override
String get sttSilenceDuration => 'Silence Duration';
@override
String get sttSilenceDurationDescription =>
'Time to wait after silence before auto-stopping recording';
@override @override
String get ttsEngineLabel => 'Engine'; String get ttsEngineLabel => 'Engine';

View File

@@ -975,6 +975,13 @@ class AppLocalizationsFr extends AppLocalizations {
String get sttServerUnavailableWarning => String get sttServerUnavailableWarning =>
'Connectez-vous à un serveur avec la transcription activée pour utiliser cette option.'; 'Connectez-vous à un serveur avec la transcription activée pour utiliser cette option.';
@override
String get sttSilenceDuration => 'Durée du silence';
@override
String get sttSilenceDurationDescription =>
'Temps d\'attente après le silence avant d\'arrêter automatiquement l\'enregistrement';
@override @override
String get ttsEngineLabel => 'Moteur'; String get ttsEngineLabel => 'Moteur';

View File

@@ -964,6 +964,13 @@ class AppLocalizationsIt extends AppLocalizations {
String get sttServerUnavailableWarning => String get sttServerUnavailableWarning =>
'Collegati a un server con la trascrizione abilitata per usare questa opzione.'; 'Collegati a un server con la trascrizione abilitata per usare questa opzione.';
@override
String get sttSilenceDuration => 'Durata del silenzio';
@override
String get sttSilenceDurationDescription =>
'Tempo di attesa dopo il silenzio prima di fermare automaticamente la registrazione';
@override @override
String get ttsEngineLabel => 'Motore'; String get ttsEngineLabel => 'Motore';