fix(asr): route Apple Speech dictation locally (#741)

This commit is contained in:
Haoqian authored and GitHub committed 2026-06-24 12:29:54 +08:00
1 parent 95920a6b41
commit 0881338e2e
2 files changed
+94 -24

No files matched your search

@@ -71,6 +71,17 @@ pub(super) fn ensure_asr_credentials() -> Result<(), String> {
}
}
if crate::asr::local::is_apple_speech(&active_asr) {
#[cfg(not(target_os = "macos"))]
{
return Err("Apple Speech 当前仅支持 macOS".to_string());
}
#[cfg(target_os = "macos")]
{
return Ok(());
}
}
if crate::asr::local::foundry::is_foundry_local_whisper(&active_asr) {
#[cfg(not(target_os = "windows"))]
{
@@ -14,6 +14,24 @@ use super::*;
const HOTKEY_DEBOUNCE: std::time::Duration = std::time::Duration::from_millis(250);
const STREAMING_INSERT_FLUSH_INTERVAL: std::time::Duration = std::time::Duration::from_millis(12);
#[cfg(target_os = "macos")]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum MacosKeylessDictationProvider {
LocalQwen3,
AppleSpeech,
}
#[cfg(target_os = "macos")]
fn macos_keyless_dictation_provider(active_asr: &str) -> Option<MacosKeylessDictationProvider> {
if crate::asr::local::is_local_qwen3(active_asr) {
Some(MacosKeylessDictationProvider::LocalQwen3)
} else if crate::asr::local::is_apple_speech(active_asr) {
Some(MacosKeylessDictationProvider::AppleSpeech)
} else {
None
}
}
/// Less Computer 浮窗的 Tauri 事件名(前端 LessComputerPanel 订阅)。
const LESS_COMPUTER_EVENT: &str = "less-computer:event";
@@ -1215,33 +1233,58 @@ pub(super) async fn begin_session_as(
}
#[cfg(target_os = "macos")]
if crate::asr::local::is_local_qwen3(&active_asr) {
let local = match build_local_qwen3(inner).await {
Ok(l) => l,
Err(e) => {
log::error!("[coord] 本地 Qwen3-ASR 初始化失败: {e:#}");
emit_capsule(
if let Some(provider) = macos_keyless_dictation_provider(&active_asr) {
match provider {
MacosKeylessDictationProvider::LocalQwen3 => {
let local = match build_local_qwen3(inner).await {
Ok(l) => l,
Err(e) => {
log::error!("[coord] 本地 Qwen3-ASR 初始化失败: {e:#}");
emit_capsule(
inner,
CapsuleState::Error,
0.0,
0,
Some(format!("本地模型初始化失败: {e}")),
None,
);
restore_prepared_windows_ime_session(inner, current_session_id);
inner.state.lock().phase = SessionPhase::Idle;
schedule_capsule_idle(inner, CAPSULE_AUTO_HIDE_DELAY_MS);
return Err(format!("local ASR init failed: {e}"));
}
};
store_asr_for_session(
inner,
CapsuleState::Error,
0.0,
0,
Some(format!("本地模型初始化失败: {e}")),
None,
current_session_id,
ActiveAsr::Local(Arc::clone(&local)),
);
restore_prepared_windows_ime_session(inner, current_session_id);
inner.state.lock().phase = SessionPhase::Idle;
schedule_capsule_idle(inner, CAPSULE_AUTO_HIDE_DELAY_MS);
return Err(format!("local ASR init failed: {e}"));
let consumer: Arc<dyn crate::recorder::AudioConsumer> = local;
start_recorder_and_enter_listening(
inner,
current_session_id,
&active_asr,
consumer,
)
.await?;
}
};
store_asr_for_session(
inner,
current_session_id,
ActiveAsr::Local(Arc::clone(&local)),
);
let consumer: Arc<dyn crate::recorder::AudioConsumer> = local;
start_recorder_and_enter_listening(inner, current_session_id, &active_asr, consumer)
.await?;
MacosKeylessDictationProvider::AppleSpeech => {
let local = build_apple_speech();
store_asr_for_session(
inner,
current_session_id,
ActiveAsr::AppleSpeech(Arc::clone(&local)),
);
let consumer: Arc<dyn crate::recorder::AudioConsumer> = local;
start_recorder_and_enter_listening(
inner,
current_session_id,
&active_asr,
consumer,
)
.await?;
}
}
return Ok(());
}
@@ -2700,6 +2743,8 @@ mod tests {
finalize_polished_text, flush_streaming_insert_buffer_with, pcm_duration_ms,
pcm_from_wav_bytes, streaming_insert_eligible,
};
#[cfg(target_os = "macos")]
use super::{macos_keyless_dictation_provider, MacosKeylessDictationProvider};
use crate::types::{
ChineseScriptPreference, CorrectionRule, DictationSession, InsertStatus, PolishMode,
};
@@ -2796,6 +2841,20 @@ mod tests {
assert_eq!(session.has_audio_recording, Some(false));
}
#[cfg(target_os = "macos")]
#[test]
fn macos_keyless_dictation_provider_routes_apple_speech_locally() {
assert_eq!(
macos_keyless_dictation_provider(crate::asr::local::APPLE_SPEECH_PROVIDER_ID),
Some(MacosKeylessDictationProvider::AppleSpeech)
);
assert_eq!(
macos_keyless_dictation_provider(crate::asr::local::PROVIDER_ID),
Some(MacosKeylessDictationProvider::LocalQwen3)
);
assert_eq!(macos_keyless_dictation_provider("volcengine"), None);
}
#[test]
fn pcm_from_wav_strips_44_byte_header() {
// 自动静默重试从归档 wav 取 PCM:标准 16k/mono/16-bit 头固定 44 字节,PCM = 头之后全部。