From 8080e66437447240bc045675cd49bf66efbf51a6 Mon Sep 17 00:00:00 2001 From: crosswk Date: Wed, 15 Jul 2026 10:24:44 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BC=98=E5=8C=96=E5=8D=83=E9=97=AE?= =?UTF-8?q?=E6=B5=81=E5=BC=8F=E8=AF=AD=E9=9F=B3=E8=AF=86=E5=88=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 修复千问流式 WebSocket 连接异常 - 修正千问流式的 App ID 配置显示 - 按实时协议聚合最终识别结果 --- CHANGELOG.md | 8 +++ .../src/providers/asr_qwen_realtime.rs | 58 +++++++++++++------ .../src/features/settings/CloudAPISection.tsx | 4 +- 3 files changed, 50 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d42fe36..dca664f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,14 @@ --- +## [0.0.9] - 2026-07-15 + +### 优化 +- 优化云 API 语音识别配置:App ID 仅在豆包 ASR 下显示,千问 ASR(含流式模式)只需填写百炼 API Key。 +- 优化千问 ASR 流式连接与结果聚合,避免实时连接异常后回退到录完再识别。 + +--- + ## [0.0.8] - 2026-07-09 ### 优化 diff --git a/client/src-tauri/src/providers/asr_qwen_realtime.rs b/client/src-tauri/src/providers/asr_qwen_realtime.rs index 7e811fb..d92d4f0 100644 --- a/client/src-tauri/src/providers/asr_qwen_realtime.rs +++ b/client/src-tauri/src/providers/asr_qwen_realtime.rs @@ -6,6 +6,9 @@ use futures_util::{SinkExt, StreamExt}; use std::sync::Arc; use tokio::sync::Mutex; use tokio_tungstenite::tungstenite; +use tungstenite::client::IntoClientRequest; +use tungstenite::http::header::{AUTHORIZATION, USER_AGENT}; +use tungstenite::http::HeaderValue; const MODEL: &str = "qwen3-asr-flash-realtime-2026-02-10"; @@ -31,21 +34,29 @@ pub async fn qwen_stream_open(config: AsrProviderConfig, hotwords: Option(&text) { let event_type = data.get("type").and_then(|t| t.as_str()).unwrap_or(""); if event_type == "session.updated" { + session_updated = true; + crate::commands::system::write_log_line( + "[RUST] [qwen_stream] session updated", + ); break; } if event_type == "error" { @@ -99,6 +115,9 @@ pub async fn qwen_stream_open(config: AsrProviderConfig, hotwords: Option Result { // 等待 session.finished,收集转录结果 let mut final_text = String::new(); + let mut completed_count = 0usize; while let Some(msg) = ws.next().await { let msg = msg.map_err(|e| format!("接收结果失败: {}", e))?; @@ -159,6 +179,7 @@ pub async fn qwen_stream_finish() -> Result { "conversation.item.input_audio_transcription.completed" => { if let Some(transcript) = data.get("transcript").and_then(|t| t.as_str()) { if !transcript.is_empty() { + completed_count += 1; if !final_text.is_empty() { final_text.push_str(","); } @@ -167,12 +188,7 @@ pub async fn qwen_stream_finish() -> Result { } } "session.finished" => { - // 优先用 session.finished 里的 transcript - if let Some(transcript) = data.get("transcript").and_then(|t| t.as_str()) { - if !transcript.is_empty() { - final_text = transcript.to_string(); - } - } + // 最终文本由 completed 事件逐段提供;session.finished 仅表示会话结束。 break; } "error" => { @@ -190,6 +206,12 @@ pub async fn qwen_stream_finish() -> Result { } } + crate::commands::system::write_log_line(&format!( + "[RUST] [qwen_stream] finished completedSegments={} utf8Len={} utf16Len={}", + completed_count, + final_text.len(), + final_text.encode_utf16().count() + )); let _ = ws.close(None).await; *session = None; diff --git a/client/src/features/settings/CloudAPISection.tsx b/client/src/features/settings/CloudAPISection.tsx index 74c5a96..176cc4e 100644 --- a/client/src/features/settings/CloudAPISection.tsx +++ b/client/src/features/settings/CloudAPISection.tsx @@ -230,8 +230,8 @@ export default function CloudAPISection() { ))} - {/* 豆包:App ID 在 API Key 前面(千问/Omni/MiMo 不需要 App ID) */} - {asrProvider !== 'qwen' && asrProvider !== 'mimo' && !asrProvider.startsWith('qwen_omni') && ( + {/* 只有豆包需要 App ID;千问(含流式/Omni)和 MiMo 只需要 API Key */} + {asrProvider === 'doubao_v2' && (