diff --git a/src/channel/client.rs b/src/channel/client.rs index 24dcae0..0184262 100644 --- a/src/channel/client.rs +++ b/src/channel/client.rs @@ -335,15 +335,14 @@ impl WeChatClient { .map_err(|e| format!("解析发送响应失败: {}", e))?; // 校验业务返回码(存在 ret 字段且非 0 才算失败) - if let Some(ret) = resp.get("ret").and_then(|v| v.as_i64()) { - if ret != 0 { + if let Some(ret) = resp.get("ret").and_then(|v| v.as_i64()) + && ret != 0 { let errmsg = resp.get("errmsg").and_then(|v| v.as_str()); return Err(format!( "发送消息业务失败 ret={} errmsg={:?}", ret, errmsg )); } - } Ok(client_id) } diff --git a/src/channel/types.rs b/src/channel/types.rs index 0583a82..9a7703e 100644 --- a/src/channel/types.rs +++ b/src/channel/types.rs @@ -118,6 +118,7 @@ pub struct RefMessage { /// 消息条目(支持 text/image/voice/file/video) #[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Default)] pub struct MessageItem { #[serde(rename = "type", skip_serializing_if = "Option::is_none")] pub item_type: Option, @@ -165,27 +166,11 @@ impl MessageItem { } } -impl Default for MessageItem { - fn default() -> Self { - Self { - item_type: None, - create_time_ms: None, - update_time_ms: None, - is_completed: None, - msg_id: None, - ref_msg: None, - text_item: None, - image_item: None, - voice_item: None, - file_item: None, - video_item: None, - } - } -} // ─── 微信消息 ─── #[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Default)] pub struct WeixinMessage { #[serde(skip_serializing_if = "Option::is_none")] pub seq: Option, @@ -217,26 +202,6 @@ pub struct WeixinMessage { pub context_token: Option, } -impl Default for WeixinMessage { - fn default() -> Self { - Self { - seq: None, - message_id: None, - from_user_id: None, - to_user_id: None, - client_id: None, - create_time_ms: None, - update_time_ms: None, - delete_time_ms: None, - session_id: None, - group_id: None, - msg_type: None, - message_state: None, - item_list: None, - context_token: None, - } - } -} impl WeixinMessage { #[allow(dead_code)] diff --git a/src/core/pipeline.rs b/src/core/pipeline.rs index 684b575..ccb8f50 100644 --- a/src/core/pipeline.rs +++ b/src/core/pipeline.rs @@ -61,8 +61,8 @@ impl PipelineContext { info!("收到消息 from={}: {}", from, text); // 入库:收到的消息 - if let Some(db) = &self.database { - if let Err(e) = crate::db::models::insert_chat_record( + if let Some(db) = &self.database + && let Err(e) = crate::db::models::insert_chat_record( db.pool(), "inbound", from, @@ -76,15 +76,14 @@ impl PipelineContext { { error!("保存聊天记录失败: {}", e); } - } // Echo 模式 if echo { match self.client.send_text(from, text, ctx_token).await { Ok(sent_id) => { info!("回显成功 msg_id={}", sent_id); - if let Some(db) = &self.database { - if let Err(e) = crate::db::models::insert_chat_record( + if let Some(db) = &self.database + && let Err(e) = crate::db::models::insert_chat_record( db.pool(), "outbound", from, @@ -98,7 +97,6 @@ impl PipelineContext { { error!("保存聊天记录失败: {}", e); } - } } Err(e) => error!("回显失败: {}", e), } @@ -117,8 +115,8 @@ impl PipelineContext { } // 用户切换 + LLM 回复(全局会话锁保证串行) - if enable_llm { - if let Some(conv) = &conversation { + if enable_llm + && let Some(conv) = &conversation { let conv = conv.clone(); let client = self.client.clone(); let database = self.database.clone(); @@ -181,8 +179,8 @@ impl PipelineContext { { Ok(sent_id) => { info!("LLM 回复成功 msg_id={}", sent_id); - if let Some(db) = database { - if let Err(e) = crate::db::models::insert_chat_record( + if let Some(db) = database + && let Err(e) = crate::db::models::insert_chat_record( db.pool(), "outbound", &from_owned, @@ -196,13 +194,11 @@ impl PipelineContext { { error!("保存聊天记录失败: {}", e); } - } } Err(e) => error!("发送 LLM 回复失败: {}", e), } }); } - } } } Err(e) => { @@ -266,8 +262,8 @@ async fn store_usage( provider: &str, u: &Usage, ) { - if let Some(db) = db { - if let Err(e) = crate::db::models::insert_llm_usage( + if let Some(db) = db + && let Err(e) = crate::db::models::insert_llm_usage( db.pool(), user_id, model, @@ -281,5 +277,4 @@ async fn store_usage( { tracing::error!("存储 LLM 用量失败: {}", e); } - } } diff --git a/src/core/summary.rs b/src/core/summary.rs index ec58cb0..f1e91c5 100644 --- a/src/core/summary.rs +++ b/src/core/summary.rs @@ -106,8 +106,8 @@ pub async fn trigger_idle_summary( /// 生成摘要:优先使用 LLM,不可用时回退到简单截断 async fn generate_summary(messages: &[Message], summarizer: Option<&Summarizer>) -> String { - if let Some(summarizer) = summarizer { - if messages.len() >= 3 { + if let Some(summarizer) = summarizer + && messages.len() >= 3 { let prompt = build_summary_prompt(messages); match summarizer(prompt).await { Ok(text) if !text.is_empty() => { @@ -117,7 +117,6 @@ async fn generate_summary(messages: &[Message], summarizer: Option<&Summarizer>) _ => tracing::warn!("LLM 摘要失败,回退到简单截断"), } } - } summarize_messages(messages) } diff --git a/src/daemon.rs b/src/daemon.rs index 672a2c1..cf6bf60 100644 --- a/src/daemon.rs +++ b/src/daemon.rs @@ -67,7 +67,7 @@ impl MessageQueue { continue; } // 检查是否有待处理消息 - if self.pending.get(&uid).map_or(true, |q| q.is_empty()) { + if self.pending.get(&uid).is_none_or(|q| q.is_empty()) { continue; } self.active.insert(uid.clone(), true); @@ -101,7 +101,7 @@ impl MessageQueue { fn has_pending(&self, user_id: &str) -> bool { self.pending .get(user_id) - .map_or(false, |q| !q.is_empty()) + .is_some_and(|q| !q.is_empty()) } } @@ -249,8 +249,8 @@ pub async fn run( info!("收到消息 from={}: {}", from, text); // 入库:收到的消息 - if let Some(db) = &shared.db { - if let Err(e) = crate::db::models::insert_chat_record( + if let Some(db) = &shared.db + && let Err(e) = crate::db::models::insert_chat_record( db.pool(), "inbound", from, @@ -264,7 +264,6 @@ pub async fn run( { error!("保存聊天记录失败: {}", e); } - } // 审批回复检查 if let Some((skill_name, decision)) = shared.approval.handle_reply(from, text).await { @@ -500,8 +499,8 @@ async fn handle_worker( cache_miss_tokens, user_id: uid, } => { - if let Some(db) = &shared.db { - if let Err(e) = crate::db::models::insert_llm_usage( + if let Some(db) = &shared.db + && let Err(e) = crate::db::models::insert_llm_usage( db.pool(), &uid, &model, @@ -515,7 +514,6 @@ async fn handle_worker( { error!("存储 LLM 用量失败: {}", e); } - } } OutputFrame::Bye => break, }, @@ -536,8 +534,8 @@ async fn handle_worker( Ok(sent_id) => { info!("回复成功 user={} msg_id={}", user_id, sent_id); // 入库:发送的回复 - if let Some(db) = &shared.db { - if let Err(e) = crate::db::models::insert_chat_record( + if let Some(db) = &shared.db + && let Err(e) = crate::db::models::insert_chat_record( db.pool(), "outbound", &user_id, @@ -551,7 +549,6 @@ async fn handle_worker( { error!("保存聊天记录失败: {}", e); } - } } Err(e) => error!("发送回复失败: {}", e), } @@ -564,8 +561,8 @@ async fn handle_worker( { Ok(sent_id) => { info!("回复成功 (chunk) user={} msg_id={}", user_id, sent_id); - if let Some(db) = &shared.db { - if let Err(e) = crate::db::models::insert_chat_record( + if let Some(db) = &shared.db + && let Err(e) = crate::db::models::insert_chat_record( db.pool(), "outbound", &user_id, @@ -579,7 +576,6 @@ async fn handle_worker( { error!("保存聊天记录失败: {}", e); } - } } Err(e) => error!("发送回复失败: {}", e), } diff --git a/src/llm/conversation.rs b/src/llm/conversation.rs index 35a06ac..9f95b27 100644 --- a/src/llm/conversation.rs +++ b/src/llm/conversation.rs @@ -171,8 +171,8 @@ impl Conversation { } } - if let Some(calls) = tool_calls { - if !calls.is_empty() { + if let Some(calls) = tool_calls + && !calls.is_empty() { used_tools = true; tracing::info!( "🔧 LLM 请求 {} 个工具: {}", @@ -208,7 +208,6 @@ impl Conversation { } continue; } - } // 无工具调用:记录回复,检查是否需要溢出摘要 if !full_text.is_empty() { @@ -219,7 +218,7 @@ impl Conversation { { let s = self.session.lock().await; let recent = s.recent_messages(); - let estimated: u32 = recent.iter().map(|m| ctx_builder::estimate_tokens(m)).sum(); + let estimated: u32 = recent.iter().map(ctx_builder::estimate_tokens).sum(); if estimated > s.token_budget { drop(s); summary_builder::trigger_overflow_summary( diff --git a/src/llm/deepseek.rs b/src/llm/deepseek.rs index e503ad0..18a6b43 100644 --- a/src/llm/deepseek.rs +++ b/src/llm/deepseek.rs @@ -167,8 +167,8 @@ async fn stream_deepseek( } // 检查最后的 buffer - if !buf.trim().is_empty() { - if let Some(parsed) = parse_chat_chunk(buf.trim()) { + if !buf.trim().is_empty() + && let Some(parsed) = parse_chat_chunk(buf.trim()) { match parsed { ParsedChunk::Text(t) => full_text.push_str(&t), ParsedChunk::ToolCallDelta { @@ -195,7 +195,6 @@ async fn stream_deepseek( _ => {} } } - } // 构建 tool_calls let tool_calls: Option> = if tool_call_builders.is_empty() { diff --git a/src/llm/provider.rs b/src/llm/provider.rs index 74cecca..9b01c49 100644 --- a/src/llm/provider.rs +++ b/src/llm/provider.rs @@ -110,16 +110,16 @@ pub(crate) fn parse_chat_chunk(line: &str) -> Option { }; // 提取 usage(可能在最后一个 chunk) - if let Some(ref usage) = parsed.usage { - if usage.total_tokens > 0 { + if let Some(ref usage) = parsed.usage + && usage.total_tokens > 0 { return Some(ParsedChunk::Usage(usage.clone())); } - } - for choice in parsed.choices { + // SSE 流式响应中每个 chunk 只包含一个 choice 的 delta + if let Some(choice) = parsed.choices.into_iter().next() { // 工具调用 delta - if let Some(tool_calls) = &choice.delta.tool_calls { - for tc in tool_calls { + if let Some(tool_calls) = &choice.delta.tool_calls + && let Some(tc) = tool_calls.first() { let idx = tc.index.unwrap_or(0); let args = tc .function @@ -134,23 +134,19 @@ pub(crate) fn parse_chat_chunk(line: &str) -> Option { arguments: args, }); } - } - if let Some(reasoning) = &choice.delta.reasoning_content { - if !reasoning.is_empty() { + if let Some(reasoning) = &choice.delta.reasoning_content + && !reasoning.is_empty() { return Some(ParsedChunk::Reasoning(reasoning.clone())); } - } - if let Some(content) = &choice.delta.content { - if !content.is_empty() { + if let Some(content) = &choice.delta.content + && !content.is_empty() { return Some(ParsedChunk::Text(content.clone())); } - } - if let Some(reason) = &choice.finish_reason { - if !reason.is_empty() { + if let Some(reason) = &choice.finish_reason + && !reason.is_empty() { return Some(ParsedChunk::FinishReason(reason.clone())); } - } } None diff --git a/src/logger.rs b/src/logger.rs index 6398855..80493d0 100644 --- a/src/logger.rs +++ b/src/logger.rs @@ -11,8 +11,8 @@ pub fn init_logger(log_dir: Option<&str>, with_file: bool) { .with_target(false) .with_ansi(true); - if with_file { - if let Some(dir) = log_dir { + if with_file + && let Some(dir) = log_dir { let dir_path = PathBuf::from(dir); std::fs::create_dir_all(&dir_path).ok(); @@ -29,7 +29,6 @@ pub fn init_logger(log_dir: Option<&str>, with_file: bool) { .init(); return; } - } tracing_subscriber::registry() .with(env_filter) diff --git a/src/main.rs b/src/main.rs index 08d7db6..46a9bc7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -276,7 +276,6 @@ async fn cmd_listen( .send_text(&uid, &msg, None) .await .map(|_| ()) - .map_err(|e| e) }) }) }; @@ -477,8 +476,8 @@ async fn cmd_send( println!("✅ 消息已发送, msg_id={}", msg_id); // 入库:发送的消息 - if let Some(db) = database { - if let Err(e) = db::models::insert_chat_record( + if let Some(db) = database + && let Err(e) = db::models::insert_chat_record( db.pool(), "outbound", to, @@ -492,7 +491,6 @@ async fn cmd_send( { error!("保存聊天记录失败: {}", e); } - } } Err(e) => error!("发送失败: {}", e), } diff --git a/src/tools/approval.rs b/src/tools/approval.rs index a1598a4..4dec7dd 100644 --- a/src/tools/approval.rs +++ b/src/tools/approval.rs @@ -201,12 +201,11 @@ impl ApprovalManager { let mut map = self.pending.lock().await; for (uid, i, _) in expired.iter().rev() { - if let Some(entries) = map.get_mut(uid) { - if *i < entries.len() { + if let Some(entries) = map.get_mut(uid) + && *i < entries.len() { let entry = entries.remove(*i); let _ = entry.sender.send(ApprovalDecision::Expired); } - } } map.retain(|_, v| !v.is_empty()); } diff --git a/src/tools/builtins/fetch_page.rs b/src/tools/builtins/fetch_page.rs index 09b9de8..d45d750 100644 --- a/src/tools/builtins/fetch_page.rs +++ b/src/tools/builtins/fetch_page.rs @@ -136,20 +136,18 @@ fn extract_content(document: &Html, domain: &str) -> String { let config = load_config(); if let Some(selectors) = config.get(domain) { for sel_str in selectors { - if let Some(text) = try_selector(document, sel_str) { - if text.len() > 50 { + if let Some(text) = try_selector(document, sel_str) + && text.len() > 50 { return text; } - } } } // 2. 先用可读性算法 - if let Some(text) = readability_extract(document) { - if text.len() > 50 { + if let Some(text) = readability_extract(document) + && text.len() > 50 { return text; } - } // 3. 回退到 body if let Some(text) = try_selector(document, "body") { @@ -239,7 +237,7 @@ fn score_element(el: &ElementRef) -> f64 { score += commas * 3.0; // 句号/问号/感叹号 → 完整句子 - let sentences = text.matches(|c| c == '.' || c == '。' || c == '?' || c == '?' || c == '!' || c == '!').count() as f64; + let sentences = text.matches(['.', '。', '?', '?', '!', '!']).count() as f64; score += sentences * 2.0; // 段落数(

标签) @@ -280,11 +278,10 @@ fn extract_clean_text(el: &ElementRef) -> String { for child in el.descendants() { // 跳过噪声元素 - if let Some(child_el) = ElementRef::wrap(child) { - if is_noise(&child_el) { + if let Some(child_el) = ElementRef::wrap(child) + && is_noise(&child_el) { continue; } - } if let Some(t) = child.value().as_text() { let t = t.trim(); @@ -311,12 +308,11 @@ fn extract_clean_text(el: &ElementRef) -> String { // ─── 辅助 ─── fn extract_title(document: &Html) -> String { - if let Ok(sel) = Selector::parse("title") { - if let Some(el) = document.select(&sel).next() { + if let Ok(sel) = Selector::parse("title") + && let Some(el) = document.select(&sel).next() { let t: String = el.text().collect::>().join(" ").trim().to_string(); if !t.is_empty() { return t; } } - } "无标题".to_string() } diff --git a/src/tools/builtins/subagent.rs b/src/tools/builtins/subagent.rs index 2994c19..df68dc1 100644 --- a/src/tools/builtins/subagent.rs +++ b/src/tools/builtins/subagent.rs @@ -197,11 +197,10 @@ async fn run_subagent(prompt: &str, timeout_secs: u64) -> Result /// 查找 pi 可执行文件路径 fn find_pi_exe() -> Result { // 1. 检查环境变量 PI_EXE - if let Ok(path) = std::env::var("PI_EXE") { - if std::path::Path::new(&path).exists() { + if let Ok(path) = std::env::var("PI_EXE") + && std::path::Path::new(&path).exists() { return Ok(path); } - } // 2. 在 PATH 中搜索 if let Ok(path_var) = std::env::var("PATH") { diff --git a/src/tools/builtins/weather.rs b/src/tools/builtins/weather.rs index 0265d2e..8a0a9f7 100644 --- a/src/tools/builtins/weather.rs +++ b/src/tools/builtins/weather.rs @@ -474,11 +474,9 @@ fn extract_days_from_prompt(prompt: &str) -> u32 { .rev() .collect::() .into() - { - if let Ok(n) = num_str.parse::() { + && let Ok(n) = num_str.parse::() { return n.min(30).max(1); } - } } } 3 // 默认 @@ -601,7 +599,7 @@ pub async fn execute(params: serde_json::Value) -> SkillResult { if let Some(arr) = obj.get_mut(*key).and_then(|v| v.as_array_mut()) { for item in arr.iter_mut() { if let Some(m) = item.as_object_mut() { - m.retain(|_, v| !v.is_null() && v.as_str().map_or(true, |s| !s.is_empty())); + m.retain(|_, v| !v.is_null() && v.as_str().is_none_or(|s| !s.is_empty())); } } } diff --git a/src/tools/builtins/web_search.rs b/src/tools/builtins/web_search.rs index e56ed70..7d08b15 100644 --- a/src/tools/builtins/web_search.rs +++ b/src/tools/builtins/web_search.rs @@ -305,11 +305,10 @@ pub async fn execute(params: serde_json::Value) -> SkillResult { let mut output = String::new(); // AI 摘要 - if let Some(ref answer) = data.answer { - if !answer.is_empty() { + if let Some(ref answer) = data.answer + && !answer.is_empty() { output.push_str(&format!("📝 {}\n\n", answer)); } - } // 搜索结果 if data.results.is_empty() { diff --git a/src/tools/executor.rs b/src/tools/executor.rs index 8f09676..8759ade 100644 --- a/src/tools/executor.rs +++ b/src/tools/executor.rs @@ -243,8 +243,8 @@ impl ToolExecutorBuilder { if target_name == "manage_memos" { let cp: serde_json::Value = serde_json::from_str(&target_args).unwrap_or_default(); - if cp.get("action").and_then(|v| v.as_str()) == Some("add") { - if let Some(content) = + if cp.get("action").and_then(|v| v.as_str()) == Some("add") + && let Some(content) = cp.get("content").and_then(|v| v.as_str()) { if let Some(ref new_mems) = new_memories { @@ -254,7 +254,6 @@ impl ToolExecutorBuilder { store.write_for(&effective_user, content).await; } } - } } return Ok(result.output); } @@ -317,10 +316,8 @@ async fn listen_approve( "⚠️ 操作确认\n\n技能:{}\n\n确认码:{}\n\n回复确认码以继续\n回复 0 或「取消」以取消\n(60秒内有效,最多3次尝试)", tool_name, code ); - if let Some(ref send) = send_wechat { - if let Err(e) = send(user_id, &msg).await { - return Err(e); - } + if let Some(send) = send_wechat { + send(user_id, &msg).await? } // listen 模式使用较短超时(60s),避免长时间持有全局会话锁 diff --git a/src/tools/mod.rs b/src/tools/mod.rs index 73817a6..07fa3d1 100644 --- a/src/tools/mod.rs +++ b/src/tools/mod.rs @@ -17,11 +17,10 @@ pub fn unpack_call_params(cp: &serde_json::Value) -> serde_json::Value { if let Some(prompt) = cp.get("prompt") { match prompt { serde_json::Value::String(s) => { - if let Ok(v) = serde_json::from_str::(s) { - if let Some(obj) = v.as_object() { + if let Ok(v) = serde_json::from_str::(s) + && let Some(obj) = v.as_object() { params.extend(obj.clone()); } - } } serde_json::Value::Object(obj) => { params.extend(obj.clone()); @@ -323,7 +322,7 @@ fn format_spec(lines: &mut Vec, spec: &SkillSpec) { .parameters .get("properties") .and_then(|v| v.as_object()) - .map_or(true, |o| o.is_empty()) + .is_none_or(|o| o.is_empty()) { lines.push(format!( " 参数: {}", diff --git a/src/worker.rs b/src/worker.rs index 764764f..571f982 100644 --- a/src/worker.rs +++ b/src/worker.rs @@ -103,8 +103,7 @@ pub async fn run(sock_path: &str) -> Result<(), String> { // 6b. 若审批已通过且带有参数,直接执行工具并注入结果 if let (Some(tool_name), Some(tool_args)) = (&task.approved_tool, &task.approved_tool_args) - { - if !tool_name.is_empty() && !tool_args.is_empty() { + && !tool_name.is_empty() && !tool_args.is_empty() { info!("审批通过,直接执行工具: {} args={:.80}", tool_name, tool_args); let tool_id = format!("approved_{}", uuid::Uuid::new_v4()); // 手动执行工具 @@ -131,7 +130,6 @@ pub async fn run(sock_path: &str) -> Result<(), String> { s.add_tool_result(&tool_id, tool_name, &result_text); } } - } // 7. 执行 LLM 对话(带审批检测) let (reply, _used_tools, usage) = conv