feat(l10n): Add silence duration settings for speech-to-text
This commit is contained in:
@@ -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 {
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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';
|
||||||
|
|
||||||
|
|||||||
@@ -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';
|
||||||
|
|
||||||
|
|||||||
@@ -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';
|
||||||
|
|
||||||
|
|||||||
@@ -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';
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user