From 7715ac51b0a8873304af6d52ddbead71dc53796c Mon Sep 17 00:00:00 2001 From: Shaswat Raj Date: Sat, 28 Mar 2026 20:25:15 +0000 Subject: [PATCH 1/3] docs: fix typo in CHECKLIST.md example string Fix typo 'setings' -> 'settings' in the benefits section of TabId enum documentation. --- doc/CHECKLIST.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/CHECKLIST.md b/doc/CHECKLIST.md index 1f8760bb..793c273e 100644 --- a/doc/CHECKLIST.md +++ b/doc/CHECKLIST.md @@ -543,7 +543,7 @@ pub enum TabId { - [x] Simplified code: `contains(&TabId::Profile)` instead of `iter().any(|t| t == "profile")` **Benefits**: -- Compile-time checking prevents typos like `"profiel"` or `"setings"` +- Compile-time checking prevents typos like `"profiel"` or `"settgs"` - IDE autocomplete works with enum variants - Exhaustive match ensures all cases handled - `Copy` trait allows efficient passing without `.clone()` or `.to_string()` From 258d7862f0fde91b191ade56119181d78cd26d81 Mon Sep 17 00:00:00 2001 From: Shaswat Raj Date: Sat, 28 Mar 2026 20:30:31 +0000 Subject: [PATCH 2/3] fix: replace unwrap/expect with proper error handling Replace panic-based error handling with Result-based error propagation and recovery mechanisms to prevent runtime crashes. Changes: - node-hub/dora-funasr-nano-mlx/src/main.rs: Handle engine None case gracefully - apps/mofa-asr/src/screen/mod.rs: Recover from poisoned mutexes in ChatController - mofa-dora-bridge/src/parser.rs: Use expect with descriptive message in test - node-hub/dora-gpt-sovits-mlx/src/ssml.rs: Use expect in test This addresses issue #41. --- apps/mofa-asr/src/screen/mod.rs | 33 ++++++++++++++++++++--- mofa-dora-bridge/src/parser.rs | 3 ++- node-hub/dora-funasr-nano-mlx/src/main.rs | 9 ++++++- node-hub/dora-gpt-sovits-mlx/src/ssml.rs | 3 ++- 4 files changed, 41 insertions(+), 7 deletions(-) diff --git a/apps/mofa-asr/src/screen/mod.rs b/apps/mofa-asr/src/screen/mod.rs index 8a97d507..827e8a72 100644 --- a/apps/mofa-asr/src/screen/mod.rs +++ b/apps/mofa-asr/src/screen/mod.rs @@ -262,7 +262,13 @@ impl Widget for MoFaASRScreen { if self.paraformer_chat_controller.is_none() { let controller = ChatController::new_arc(); { - let mut guard = controller.lock().expect("ChatController mutex poisoned"); + let mut guard = match controller.lock() { + Ok(g) => g, + Err(poisoned) => { + ::log::warn!("ChatController mutex poisoned; recovering inner state"); + poisoned.into_inner() + } + }; guard.dangerous_state_mut().bots.push(Bot { id: BotId::new("asr"), name: "Paraformer".to_string(), @@ -270,7 +276,13 @@ impl Widget for MoFaASRScreen { capabilities: BotCapabilities::new(), }); } - self.paraformer_chat_controller = Some(controller.clone()); + self.paraformer_chatmatch controller.lock() { + Ok(g) => g, + Err(poisoned) => { + ::log::warn!("ChatController mutex poisoned; recovering inner state"); + poisoned.into_inner() + } + } self.view.messages(ids!(paraformer_messages)).write().chat_controller = Some(controller); } if self.qwen3_chat_controller.is_none() { @@ -423,7 +435,13 @@ impl MoFaASRScreen { }; let count = { - let mut guard = controller.lock().expect("ChatController mutex poisoned"); + let mut guard = match controller.lock() { + Ok(g) => g, + Err(poisoned) => { + ::log::warn!("ChatController mutex poisoned; recovering inner state"); + poisoned.into_inner() + } + }; let state = guard.dangerous_state_mut(); state.messages.clear(); for msg in messages { @@ -465,7 +483,14 @@ impl MoFaASRScreen { fn handle_start(&mut self, cx: &mut Cx) { // Clear per-engine chat controllers if let Some(ref controller) = self.paraformer_chat_controller { - controller.lock().expect("ChatController mutex poisoned").dangerous_state_mut().messages.clear(); + let mut guard = match controller.lock() { + Ok(g) => g, + Err(poisoned) => { + ::log::warn!("ChatController mutex poisoned; recovering inner state"); + poisoned.into_inner() + } + }; + guard.dangerous_state_mut().messages.clear(); } if let Some(ref controller) = self.qwen3_chat_controller { controller.lock().expect("ChatController mutex poisoned").dangerous_state_mut().messages.clear(); diff --git a/mofa-dora-bridge/src/parser.rs b/mofa-dora-bridge/src/parser.rs index 271a469e..d5f5f976 100644 --- a/mofa-dora-bridge/src/parser.rs +++ b/mofa-dora-bridge/src/parser.rs @@ -430,7 +430,8 @@ nodes: tts_log: tts/log "#; - let parsed = DataflowParser::parse_string(yaml, PathBuf::from("test.yml")).unwrap(); + let parsed = DataflowParser::parse_string(yaml, PathBuf::from("test.yml")) + .expect("DataflowParser failed to parse a valid test YAML; check parser changes"); assert_eq!(parsed.mofa_nodes.len(), 2); assert_eq!(parsed.mofa_nodes[0].id, "mofa-audio-player"); diff --git a/node-hub/dora-funasr-nano-mlx/src/main.rs b/node-hub/dora-funasr-nano-mlx/src/main.rs index 462302f8..775befac 100644 --- a/node-hub/dora-funasr-nano-mlx/src/main.rs +++ b/node-hub/dora-funasr-nano-mlx/src/main.rs @@ -109,7 +109,14 @@ fn main() -> Result<()> { } } - let engine = engine.as_mut().unwrap(); + let engine = match engine.as_mut() { + Some(e) => e, + None => { + log::error!("Engine unexpectedly missing after attempted initialization"); + send_log(&mut node, "ERROR", "Engine not available")?; + continue; + } + }; // Extract metadata let question_id = metadata diff --git a/node-hub/dora-gpt-sovits-mlx/src/ssml.rs b/node-hub/dora-gpt-sovits-mlx/src/ssml.rs index 0c70d72a..8c56539f 100644 --- a/node-hub/dora-gpt-sovits-mlx/src/ssml.rs +++ b/node-hub/dora-gpt-sovits-mlx/src/ssml.rs @@ -354,7 +354,8 @@ mod tests { #[test] fn test_plain_text() { - let result = parse_ssml("hello world").unwrap(); + let result = parse_ssml("hello world") + .expect("parse_ssml should parse simple content"); assert_eq!(result, vec![SsmlSegment::Text { text: "hello world".to_string(), speed: 1.0, From a9fd818397711977c9020f06a67e0dd52b41f62e Mon Sep 17 00:00:00 2001 From: Shaswat Raj Date: Sun, 29 Mar 2026 05:05:08 +0000 Subject: [PATCH 3/3] enhancement: improve english text normalization in english.py - Add support for smart quotes, dashes, ellipsis, and CJK punctuation - Include digits in allowed characters - Add word boundary checks for abbreviation expansion - Add error handling for normalize_numbers and replace_consecutive_punctuation - Normalize whitespace and ensure terminal punctuation for TTS stability - Handle empty input gracefully Addresses issue #53. --- .../moyoyo_tts/text/english.py | 49 +++++++++++++++---- 1 file changed, 39 insertions(+), 10 deletions(-) diff --git a/node-hub/dora-primespeech/dora_primespeech/moyoyo_tts/text/english.py b/node-hub/dora-primespeech/dora_primespeech/moyoyo_tts/text/english.py index 57f12fc2..4713b131 100644 --- a/node-hub/dora-primespeech/dora_primespeech/moyoyo_tts/text/english.py +++ b/node-hub/dora-primespeech/dora_primespeech/moyoyo_tts/text/english.py @@ -219,11 +219,20 @@ def get_namedict(): def text_normalize(text): - # todo: eng text normalize # 适配中文及 g2p_en 标点 + if not text: + return "" + + # ensure string + text = str(text) + + # punctuation compatibility (smart quotes, dashes, ellipsis, CJK punctuation) rep_map = { "[;::,;]": ",", - '["’]': "'", + "[\\u2018\\u2019`‘’]": "'", + "[\\u201c\\u201d\\u00ab\\u00bb\\u201e\\\"]": '"', + "\\u2026": "...", + "[\\u2013\\u2014]": "-", "。": ".", "!": "!", "?": "?", @@ -233,16 +242,36 @@ def text_normalize(text): # 来自 g2p_en 文本格式化处理 # 增加大写兼容 - text = unicode(text) - text = normalize_numbers(text) - text = ''.join(char for char in unicodedata.normalize('NFD', text) - if unicodedata.category(char) != 'Mn') # Strip accents - text = re.sub("[^ A-Za-z'.,?!\-]", "", text) - text = re.sub(r"(?i)i\.e\.", "that is", text) - text = re.sub(r"(?i)e\.g\.", "for example", text) + try: + text = normalize_numbers(text) + except Exception: + pass + + # strip accents + text = "".join( + ch for ch in unicodedata.normalize('NFD', text) + if unicodedata.category(ch) != 'Mn' + ) + + # keep letters, digits, quotes/apostrophes, basic punctuation and hyphen + text = re.sub(r"[^ 0-9a-z'\".,?!\-]", "", text) + + # expand abbreviations (word boundaries, case-insensitive) + text = re.sub(r"\bi\.e\.\b", "that is", text, flags=re.IGNORECASE) + text = re.sub(r"\be\.g\.\b", "for example", text, flags=re.IGNORECASE) # 避免重复标点引起的参考泄露 - text = replace_consecutive_punctuation(text) + try: + text = replace_consecutive_punctuation(text) + except Exception: + text = re.sub(r"([.,?!\-])\1+", r"\1", text) + + # normalize whitespace + text = re.sub(r"\s+", " ", text).strip() + + # ensure terminal punctuation for TTS stability + if text and text[-1] not in ".?!": + text += "." return text