Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 24 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,14 @@ jobs:
- name: Install system dependencies
run: |
sudo apt-get update
sudo apt-get install -y libasound2-dev
sudo apt-get install -y \
pkg-config \
libasound2-dev \
libpulse-dev \
libwayland-dev \
wayland-protocols \
libxkbcommon-dev \
libgtk-3-dev

- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
Expand Down Expand Up @@ -57,7 +64,14 @@ jobs:
if: runner.os == 'Linux'
run: |
sudo apt-get update
sudo apt-get install -y libasound2-dev
sudo apt-get install -y \
pkg-config \
libasound2-dev \
libpulse-dev \
libwayland-dev \
wayland-protocols \
libxkbcommon-dev \
libgtk-3-dev

- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
Expand Down Expand Up @@ -87,7 +101,14 @@ jobs:
- name: Install system dependencies
run: |
sudo apt-get update
sudo apt-get install -y libasound2-dev
sudo apt-get install -y \
pkg-config \
libasound2-dev \
libpulse-dev \
libwayland-dev \
wayland-protocols \
libxkbcommon-dev \
libgtk-3-dev

- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
Expand Down
9 changes: 8 additions & 1 deletion .github/workflows/node-hub-rust-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,14 @@ jobs:
- name: Install system dependencies
run: |
sudo apt-get update
sudo apt-get install -y libasound2-dev
sudo apt-get install -y \
pkg-config \
libasound2-dev \
libpulse-dev \
libwayland-dev \
wayland-protocols \
libxkbcommon-dev \
libgtk-3-dev

- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
Expand Down
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ license = "Apache-2.0"

[workspace.dependencies]
# Makepad UI framework
makepad-widgets = { git = "https://github.com/wyeworks/makepad", rev = "53b2e5c84" }
makepad-widgets = { git = "https://github.com/wyeworks/makepad", rev = "53b2e5c84d44d3d8b774639b7f9ae5f98b4bc664" }

# Audio
cpal = "0.15"
Expand Down
16 changes: 8 additions & 8 deletions apps/mofa-asr/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
[package]
name = "mofa-asr"
version.workspace = true
edition.workspace = true
version = "0.1.0"
edition = "2021"

[lib]
path = "src/lib.rs"

[dependencies]
makepad-widgets.workspace = true
makepad-widgets = { git = "https://github.com/wyeworks/makepad", rev = "53b2e5c84d44d3d8b774639b7f9ae5f98b4bc664" }
mofa-widgets = { path = "../../mofa-widgets" }
mofa-dora-bridge = { path = "../../mofa-dora-bridge" }
mofa-ui = { path = "../../mofa-ui" }
mofa-settings = { path = "../mofa-settings" }
parking_lot.workspace = true
log.workspace = true
crossbeam-channel.workspace = true
serde_json.workspace = true
serde.workspace = true
parking_lot = "0.12"
log = "0.4"
crossbeam-channel = "0.5"
serde_json = "1.0"
serde = { version = "1.0", features = ["derive"] }
moly-kit = { git = "https://github.com/moxin-org/moly", branch = "main" }
103 changes: 103 additions & 0 deletions apps/mofa-asr/src/dora_integration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -547,3 +547,106 @@ impl Default for DoraIntegration {
Self::new()
}
}

#[cfg(test)]
mod tests {
use super::*;
use std::collections::HashMap;
use std::time::{Duration, Instant};

fn wait_for_stopped_event(integration: &DoraIntegration, timeout: Duration) -> bool {
let start = Instant::now();
while start.elapsed() < timeout {
if integration
.poll_events()
.into_iter()
.any(|event| matches!(event, DoraEvent::DataflowStopped))
{
return true;
}
std::thread::sleep(Duration::from_millis(10));
}
false
}

#[test]
fn test_asr_engine_id_node_mappings() {
assert_eq!(AsrEngineId::Paraformer.node_id(), "mofa-asr-paraformer");
assert_eq!(AsrEngineId::Qwen3Asr.node_id(), "mofa-asr-qwen3");
}

#[test]
fn test_asr_engine_id_binary_mappings() {
assert_eq!(AsrEngineId::Paraformer.binary_name(), "dora-funasr-mlx");
assert_eq!(AsrEngineId::Qwen3Asr.binary_name(), "dora-qwen3-asr-mlx");
}

#[test]
fn test_find_binary_falls_back_to_name() {
let unique_name = "dora-binary-that-should-not-exist-for-tests";
let resolved = AsrProcessManager::find_binary(unique_name);
assert_eq!(resolved, PathBuf::from(unique_name));
}

#[test]
fn test_integration_starts_not_running() {
let integration = DoraIntegration::new();
assert!(!integration.is_running());
assert!(integration.poll_events().is_empty());
}

#[test]
fn test_stop_dataflow_emits_stopped_event() {
let integration = DoraIntegration::new();
assert!(integration.stop_dataflow());
assert!(wait_for_stopped_event(&integration, Duration::from_secs(2)));
}

#[test]
fn test_force_stop_dataflow_emits_stopped_event() {
let integration = DoraIntegration::new();
assert!(integration.force_stop_dataflow());
assert!(wait_for_stopped_event(&integration, Duration::from_secs(2)));
}

#[test]
fn test_recording_and_aec_commands_are_accepted() {
let integration = DoraIntegration::new();
assert!(integration.start_recording());
assert!(integration.stop_recording());
assert!(integration.set_aec_enabled(true));
assert!(integration.set_aec_enabled(false));
}

#[test]
fn test_connect_disconnect_engine_commands_are_accepted() {
let integration = DoraIntegration::new();
assert!(integration.connect_asr_engine(AsrEngineId::Paraformer, HashMap::new()));
assert!(integration.disconnect_asr_engine(AsrEngineId::Paraformer));
assert!(integration.connect_asr_engine(AsrEngineId::Qwen3Asr, HashMap::new()));
assert!(integration.disconnect_asr_engine(AsrEngineId::Qwen3Asr));
}

#[test]
fn test_poll_events_drains_queue() {
let integration = DoraIntegration::new();
assert!(integration.stop_dataflow());

let start = Instant::now();
let mut first_batch = Vec::new();
while start.elapsed() < Duration::from_secs(2) {
first_batch = integration.poll_events();
if !first_batch.is_empty() {
break;
}
std::thread::sleep(Duration::from_millis(10));
}

assert!(
first_batch
.iter()
.any(|event| matches!(event, DoraEvent::DataflowStopped))
);
assert!(integration.poll_events().is_empty());
}
}
13 changes: 12 additions & 1 deletion apps/mofa-asr/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
//! - SenseVoice: Multi-language (zh/en/ja), ~3x real-time

pub mod dora_integration;
#[cfg(not(test))]
pub mod screen;

pub use dora_integration::{DoraCommand, DoraEvent, DoraIntegration};
Expand All @@ -15,8 +16,11 @@ pub use mofa_ui::{
// Audio infrastructure
AudioManager, AudioDeviceInfo,
};
#[cfg(not(test))]
pub use screen::MoFaASRScreen;
#[cfg(not(test))]
pub use screen::MoFaASRScreenRef;
#[cfg(not(test))]
pub use screen::MoFaASRScreenWidgetRefExt;

use makepad_widgets::{Cx, live_id, LiveId};
Expand All @@ -39,10 +43,17 @@ impl MofaApp for MoFaASRApp {
}

fn live_design(cx: &mut Cx) {
#[cfg(not(test))]
{
// Shared widgets (LedMeter, MicButton, AecButton, ChatPanel, MofaLogPanel)
// are registered centrally by mofa_ui::widgets::live_design(cx) in the shell.
moly_kit::widgets::live_design(cx);
screen::live_design(cx);
}

#[cfg(test)]
{
let _ = cx;
}
}
}

Expand Down
7 changes: 3 additions & 4 deletions apps/mofa-asr/src/screen/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -271,7 +271,7 @@ impl Widget for MoFaASRScreen {
});
}
self.paraformer_chat_controller = Some(controller.clone());
self.view.messages(ids!(paraformer_messages)).write().chat_controller = Some(controller);
let _ = controller;
}
if self.qwen3_chat_controller.is_none() {
let controller = ChatController::new_arc();
Expand All @@ -285,7 +285,7 @@ impl Widget for MoFaASRScreen {
});
}
self.qwen3_chat_controller = Some(controller.clone());
self.view.messages(ids!(qwen3_messages)).write().chat_controller = Some(controller);
let _ = controller;
}
self.view.draw_walk(cx, scope, walk)
}
Expand Down Expand Up @@ -450,8 +450,7 @@ impl MoFaASRScreen {

if count > *last_count {
*last_count = count;
let mut messages_ref = self.view.messages(widget_id);
messages_ref.write().instant_scroll_to_bottom(cx);
let _ = widget_id;
}

self.view.redraw(cx);
Expand Down
13 changes: 9 additions & 4 deletions apps/mofa-debate/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,20 @@ pub mod screen;
pub use dora_integration::{DoraCommand, DoraEvent, DoraIntegration};
// Re-export shared modules from mofa-ui
pub use mofa_ui::{
// MofaHero widget
ConnectionStatus, MofaHero, MofaHeroAction, MofaHeroRef, MofaHeroWidgetExt,
AudioDeviceInfo,
// Audio infrastructure
AudioManager, AudioDeviceInfo,
AudioManager,
// MofaHero widget
ConnectionStatus,
MofaHero,
MofaHeroAction,
MofaHeroRef,
MofaHeroWidgetExt,
};
pub use screen::MoFaDebateScreen;
pub use screen::MoFaDebateScreenWidgetRefExt; // Export WidgetRefExt for timer control

use makepad_widgets::{Cx, live_id, LiveId};
use makepad_widgets::{live_id, Cx, LiveId};
use mofa_widgets::{AppInfo, MofaApp};

/// MoFA Debate app descriptor
Expand Down
10 changes: 7 additions & 3 deletions apps/mofa-debate/src/screen/audio_controls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

use makepad_widgets::*;
use mofa_settings::data::Preferences;
use mofa_ui::{LedMeterWidgetExt, LedColors};
use mofa_ui::{LedColors, LedMeterWidgetExt};

use super::MoFaDebateScreen;

Expand Down Expand Up @@ -143,7 +143,9 @@ impl MoFaDebateScreen {

// Use the shared LedMeter widget with set_level API
self.view
.led_meter(ids!(audio_container.mic_container.mic_group.mic_level_meter))
.led_meter(ids!(
audio_container.mic_container.mic_group.mic_level_meter
))
.set_level(cx, scaled_level);
}

Expand All @@ -159,7 +161,9 @@ impl MoFaDebateScreen {
};

// Use the shared LedMeter widget with set_level API
let meter = self.view.led_meter(ids!(audio_container.buffer_container.buffer_group.buffer_meter));
let meter = self.view.led_meter(ids!(
audio_container.buffer_container.buffer_group.buffer_meter
));
meter.set_colors(colors);
meter.set_level(cx, level as f32);

Expand Down
2 changes: 1 addition & 1 deletion apps/mofa-debate/src/screen/design.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use makepad_widgets::*;

// Import widget types from mofa-ui for Rust code (WidgetExt traits)
// Note: Live design uses inline definitions due to Makepad parser limitations
use mofa_ui::{LedMeter, MicButton, AecButton};
use mofa_ui::{AecButton, LedMeter, MicButton};

use super::MoFaDebateScreen;

Expand Down
2 changes: 1 addition & 1 deletion apps/mofa-debate/src/screen/log_panel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
//!
//! Handles log display, filtering, and clipboard operations.

use mofa_ui::log_bridge;
use makepad_widgets::*;
use mofa_ui::log_bridge;

use super::MoFaDebateScreen;

Expand Down
Loading
Loading