From 70bfd7231a9c4b0f19118b4a8509afe63bf1e5bb Mon Sep 17 00:00:00 2001 From: cyrus Date: Fri, 27 Mar 2026 18:09:39 +0530 Subject: [PATCH] Refactor error handling and deduplicate error events Updated error handling in chat commands to reduce redundant event emissions by logging errors instead. Enhanced error reporting for HTTP client and inference response failures. Added frontend logic to prevent duplicate error messages in the UI. --- skills | 2 +- src-tauri/Cargo.lock | 2 +- src-tauri/src/commands/chat.rs | 90 +++++++++++++++++++++++++--------- src/pages/Conversations.tsx | 14 ++++++ 4 files changed, 82 insertions(+), 26 deletions(-) diff --git a/skills b/skills index 0685a6af4..e856b8e75 160000 --- a/skills +++ b/skills @@ -1 +1 @@ -Subproject commit 0685a6af4ef3ddea4fb26364dd72ea574f307314 +Subproject commit e856b8e756ad7ea86f36dbd1697542e76a8d3571 diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index bb512679f..9b02b47de 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -4,7 +4,7 @@ version = 4 [[package]] name = "OpenHuman" -version = "0.49.12" +version = "0.49.15" dependencies = [ "aes-gcm", "android_logger", diff --git a/src-tauri/src/commands/chat.rs b/src-tauri/src/commands/chat.rs index 2d0919d0a..54fb36668 100644 --- a/src-tauri/src/commands/chat.rs +++ b/src-tauri/src/commands/chat.rs @@ -436,15 +436,9 @@ pub async fn chat_send( chat_state_arc.remove(&thread_id_clone); if let Err(e) = result { - let _ = app_clone.emit( - "chat:error", - ChatErrorEvent { - thread_id: thread_id_clone, - message: e, - error_type: "inference".to_string(), - round: None, - }, - ); + // chat_send_inner already emits chat:error for known error paths. + // Only log here to avoid duplicate error events. + log::error!("[chat] chat_send_inner failed: {e}"); } }); @@ -501,15 +495,9 @@ pub async fn chat_send( chat_state_arc.remove(&thread_id_clone); if let Err(e) = result { - let _ = app_clone.emit( - "chat:error", - ChatErrorEvent { - thread_id: thread_id_clone, - message: e, - error_type: "inference".to_string(), - round: None, - }, - ); + // chat_send_mobile already emits chat:error for known error paths. + // Only log here to avoid duplicate error events. + log::error!("[chat] chat_send_mobile failed: {e}"); } }); @@ -546,7 +534,16 @@ async fn chat_send_inner( let client = reqwest::Client::builder() .use_rustls_tls() .build() - .map_err(|e| format!("Failed to build HTTP client: {}", e))?; + .map_err(|e| { + let msg = format!("Failed to build HTTP client: {}", e); + let _ = app.emit("chat:error", ChatErrorEvent { + thread_id: thread_id.to_string(), + message: msg.clone(), + error_type: "network".to_string(), + round: None, + }); + msg + })?; // ── Step 1: Load AI context ───────────────────────────────────────── let openclaw_context = load_openclaw_context(app); @@ -775,7 +772,16 @@ async fn chat_send_inner( let completion: ChatCompletionResponse = response .json() .await - .map_err(|e| format!("Failed to parse inference response: {}", e))?; + .map_err(|e| { + let msg = format!("Failed to parse inference response: {}", e); + let _ = app.emit("chat:error", ChatErrorEvent { + thread_id: thread_id.to_string(), + message: msg.clone(), + error_type: "inference".to_string(), + round: Some(round), + }); + msg + })?; // Accumulate token usage if let Some(ref usage) = completion.usage { @@ -786,7 +792,16 @@ async fn chat_send_inner( let choice = completion .choices .first() - .ok_or_else(|| "No choices in inference response".to_string())?; + .ok_or_else(|| { + let msg = "No choices in inference response".to_string(); + let _ = app.emit("chat:error", ChatErrorEvent { + thread_id: thread_id.to_string(), + message: msg.clone(), + error_type: "inference".to_string(), + round: Some(round), + }); + msg + })?; log::info!( "[chat] Round {} — finish_reason={:?}, tool_calls={}", @@ -1002,7 +1017,16 @@ async fn chat_send_mobile( let client = reqwest::Client::builder() .use_rustls_tls() .build() - .map_err(|e| format!("Failed to build HTTP client: {}", e))?; + .map_err(|e| { + let msg = format!("Failed to build HTTP client: {}", e); + let _ = app.emit("chat:error", ChatErrorEvent { + thread_id: thread_id.to_string(), + message: msg.clone(), + error_type: "network".to_string(), + round: None, + }); + msg + })?; // ── Step 1: Load AI context ───────────────────────────────────────── let openclaw_context = load_openclaw_context(app); @@ -1146,7 +1170,16 @@ async fn chat_send_mobile( let completion: ChatCompletionResponse = response .json() .await - .map_err(|e| format!("Failed to parse inference response: {}", e))?; + .map_err(|e| { + let msg = format!("Failed to parse inference response: {}", e); + let _ = app.emit("chat:error", ChatErrorEvent { + thread_id: thread_id.to_string(), + message: msg.clone(), + error_type: "inference".to_string(), + round: None, + }); + msg + })?; let (total_input_tokens, total_output_tokens) = completion .usage @@ -1156,7 +1189,16 @@ async fn chat_send_mobile( let choice = completion .choices .first() - .ok_or_else(|| "No choices in inference response".to_string())?; + .ok_or_else(|| { + let msg = "No choices in inference response".to_string(); + let _ = app.emit("chat:error", ChatErrorEvent { + thread_id: thread_id.to_string(), + message: msg.clone(), + error_type: "inference".to_string(), + round: None, + }); + msg + })?; let full_response = choice.message.content.clone().unwrap_or_default(); diff --git a/src/pages/Conversations.tsx b/src/pages/Conversations.tsx index 877322287..5c90e0cd5 100644 --- a/src/pages/Conversations.tsx +++ b/src/pages/Conversations.tsx @@ -293,6 +293,20 @@ const Conversations = () => { }); if (event.error_type !== 'cancelled') { + // Deduplicate: skip if the last message is already an error + const currentState = store.getState() as { + thread: { messagesByThreadId: Record }; + }; + const threadMessages = + currentState.thread.messagesByThreadId[event.thread_id] || []; + const lastMsg = threadMessages[threadMessages.length - 1]; + if ( + lastMsg?.sender === 'agent' && + lastMsg?.content === 'Something went wrong — please try again.' + ) { + return; + } + dispatch( addInferenceResponse({ content: 'Something went wrong — please try again.',