diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 3d5f6975a..6a06d7878 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -3674,7 +3674,7 @@ dependencies = [ [[package]] name = "proxycast" -version = "0.25.0" +version = "0.26.0" dependencies = [ "anyhow", "arboard", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index f22a2b387..e68ba5183 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "proxycast" -version = "0.25.0" +version = "0.26.0" description = "AI API Proxy Desktop App" authors = ["you"] edition = "2021" diff --git a/src-tauri/src/agent/native_agent.rs b/src-tauri/src/agent/native_agent.rs index fee201654..4210e0f97 100644 --- a/src-tauri/src/agent/native_agent.rs +++ b/src-tauri/src/agent/native_agent.rs @@ -11,8 +11,9 @@ //! - 实现工具调用检测、执行和结果收集 //! - Requirements: 7.1, 7.2, 7.3, 7.4, 7.5, 7.6 -use crate::agent::tool_loop::{ToolCallResult, ToolLoopConfig, ToolLoopEngine, ToolLoopState}; -use crate::agent::tools::ToolRegistry; +#![allow(dead_code)] + +use crate::agent::tool_loop::{ToolCallResult, ToolLoopEngine, ToolLoopState}; use crate::agent::types::*; use crate::models::openai::{ ChatCompletionRequest, ChatCompletionResponse, ChatMessage, ContentPart as OpenAIContentPart, @@ -580,7 +581,7 @@ impl NativeAgent { let model = request.model.unwrap_or_else(|| self.config.model.clone()); let session_id = request.session_id.clone(); - debug!( + info!( "[NativeAgent] 发送流式聊天请求: model={}, session={:?}", model, session_id ); @@ -652,7 +653,10 @@ impl NativeAgent { for line in event.lines() { if let Some(data) = line.strip_prefix("data: ") { + debug!("[NativeAgent] SSE data: {}", data); let (text_delta, is_done, usage) = parser.parse_data(data); + debug!("[NativeAgent] 解析结果: text_delta={:?}, is_done={}, usage={:?}", + text_delta, is_done, usage); // 更新 usage if usage.is_some() { @@ -661,6 +665,7 @@ impl NativeAgent { // 发送文本增量 if let Some(text) = text_delta { + debug!("[NativeAgent] 发送 TextDelta: {}", text); let _ = tx.send(StreamEvent::TextDelta { text }).await; } diff --git a/src-tauri/src/agent/tools/bash.rs b/src-tauri/src/agent/tools/bash.rs index 8a9e92e10..20314b9ef 100644 --- a/src-tauri/src/agent/tools/bash.rs +++ b/src-tauri/src/agent/tools/bash.rs @@ -9,6 +9,8 @@ //! - 超时控制 //! - 防止交互的环境变量设置 +#![allow(dead_code)] + use super::registry::Tool; use super::security::SecurityManager; use super::types::{JsonSchema, PropertySchema, ToolDefinition, ToolError, ToolResult}; @@ -460,7 +462,7 @@ impl Tool for BashTool { // Requirements: 3.2 - THE Bash_Executor SHALL capture both stdout and stderr // Requirements: 3.5 - IF a command fails, THEN THE Bash_Executor SHALL return the exit code and error output if result.timed_out { - let output = format!( + let _output = format!( "命令执行超时({}秒)\n\n已捕获的输出:\n{}", timeout_secs, result.combined_output() diff --git a/src-tauri/src/agent/tools/edit_file.rs b/src-tauri/src/agent/tools/edit_file.rs index e7c626e1c..bbaa23469 100644 --- a/src-tauri/src/agent/tools/edit_file.rs +++ b/src-tauri/src/agent/tools/edit_file.rs @@ -8,6 +8,8 @@ //! - 多次出现检测(返回错误要求更多上下文) //! - 不存在检测(返回错误和指导) //! - Unified diff 支持 + +#![allow(dead_code)] //! - 历史栈和撤销功能 //! - 返回变更上下文片段 @@ -20,7 +22,7 @@ use std::collections::HashMap; use std::fs; use std::path::{Path, PathBuf}; use std::sync::Arc; -use tracing::{debug, info, warn}; +use tracing::{debug, info}; /// 上下文行数(显示变更前后的行数) const CONTEXT_LINES: usize = 3; @@ -339,7 +341,7 @@ fn find_occurrence_positions(content: &str, pattern: &str) -> Vec { } /// 获取指定位置的上下文片段 -fn get_context_snippet(content: &str, position: usize, match_len: usize) -> String { +fn get_context_snippet(content: &str, position: usize, _match_len: usize) -> String { let lines: Vec<&str> = content.lines().collect(); // 找到位置所在的行 diff --git a/src-tauri/src/agent/tools/read_file.rs b/src-tauri/src/agent/tools/read_file.rs index 22106a2fc..e84be2484 100644 --- a/src-tauri/src/agent/tools/read_file.rs +++ b/src-tauri/src/agent/tools/read_file.rs @@ -17,7 +17,7 @@ use async_trait::async_trait; use std::fs; use std::path::{Path, PathBuf}; use std::sync::Arc; -use tracing::{debug, info, warn}; +use tracing::{debug, info}; /// 大文件阈值(行数) const LARGE_FILE_THRESHOLD: usize = 500; diff --git a/src-tauri/src/agent/tools/write_file.rs b/src-tauri/src/agent/tools/write_file.rs index 12b83ffb6..f14d15c87 100644 --- a/src-tauri/src/agent/tools/write_file.rs +++ b/src-tauri/src/agent/tools/write_file.rs @@ -16,7 +16,7 @@ use async_trait::async_trait; use std::fs; use std::path::{Path, PathBuf}; use std::sync::Arc; -use tracing::{debug, info, warn}; +use tracing::{debug, info}; /// 文件写入工具 /// diff --git a/src-tauri/src/browser_interceptor/config.rs b/src-tauri/src/browser_interceptor/config.rs index 93d42e08a..1f5653554 100644 --- a/src-tauri/src/browser_interceptor/config.rs +++ b/src-tauri/src/browser_interceptor/config.rs @@ -1,6 +1,5 @@ use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; -use std::collections::HashMap; /// 拦截器状态 #[derive(Debug, Clone, Serialize, Deserialize)] diff --git a/src-tauri/src/browser_interceptor/notification_service.rs b/src-tauri/src/browser_interceptor/notification_service.rs index a35ddfe6e..16d3f7581 100644 --- a/src-tauri/src/browser_interceptor/notification_service.rs +++ b/src-tauri/src/browser_interceptor/notification_service.rs @@ -1,4 +1,4 @@ -use crate::browser_interceptor::{BrowserInterceptorError, InterceptedUrl, Result}; +use crate::browser_interceptor::{InterceptedUrl, Result}; use serde::{Deserialize, Serialize}; use std::time::Duration; diff --git a/src-tauri/src/browser_interceptor/platform/macos.rs b/src-tauri/src/browser_interceptor/platform/macos.rs index ff00e0597..2d8d0ab97 100644 --- a/src-tauri/src/browser_interceptor/platform/macos.rs +++ b/src-tauri/src/browser_interceptor/platform/macos.rs @@ -1,3 +1,5 @@ +#![allow(dead_code)] + use crate::browser_interceptor::{BrowserInterceptorError, InterceptedUrl, Result}; use once_cell::sync::Lazy; use std::process::Command; diff --git a/src-tauri/src/browser_interceptor/state_manager.rs b/src-tauri/src/browser_interceptor/state_manager.rs index 3334ea21a..71cfd1eb5 100644 --- a/src-tauri/src/browser_interceptor/state_manager.rs +++ b/src-tauri/src/browser_interceptor/state_manager.rs @@ -292,7 +292,7 @@ impl StateManager { /// 备份注册表项(Windows 特定) async fn backup_registry_keys(&self) -> Result> { - let mut backup = std::collections::HashMap::new(); + let backup = std::collections::HashMap::new(); #[cfg(target_os = "windows")] { @@ -411,11 +411,11 @@ impl StateManager { /// 恢复注册表项 async fn restore_registry_keys( &self, - backup: &std::collections::HashMap, + _backup: &std::collections::HashMap, ) -> Result<()> { #[cfg(target_os = "windows")] { - for (key_path, value) in backup { + for (key_path, value) in _backup { if let Err(e) = self.write_registry_value(key_path, "ProgId", value).await { tracing::error!("恢复注册表项失败 {}: {}", key_path, e); } diff --git a/src-tauri/src/commands/flow_monitor_cmd.rs b/src-tauri/src/commands/flow_monitor_cmd.rs index 613c142dc..3c21fb1ee 100644 --- a/src-tauri/src/commands/flow_monitor_cmd.rs +++ b/src-tauri/src/commands/flow_monitor_cmd.rs @@ -8,7 +8,6 @@ use serde::{Deserialize, Serialize}; use std::sync::Arc; use tauri::State; -use crate::flow_monitor::monitor::{NotificationConfig, NotificationSettings}; use crate::flow_monitor::{ get_filter_help, BatchOperation, BatchOperations, BatchResult, DiffConfig, ExportFormat, ExportOptions, FilterExpr, FilterParser, FlowAnnotations, FlowDiff, FlowDiffResult, @@ -1127,14 +1126,9 @@ pub async fn query_flows_with_expression( // 拦截器相关命令 // ============================================================================ -use crate::flow_monitor::{ - FlowInterceptor, InterceptConfig, InterceptEvent, InterceptedFlow, InterceptorError, - ModifiedData, TimeoutAction, -}; +use crate::flow_monitor::{FlowInterceptor, InterceptConfig, InterceptedFlow, ModifiedData}; -use crate::flow_monitor::{ - BatchReplayResult, FlowReplayer, ReplayConfig, ReplayResult, RequestModification, -}; +use crate::flow_monitor::{BatchReplayResult, FlowReplayer, ReplayConfig, ReplayResult}; /// 拦截器状态封装 pub struct FlowInterceptorState(pub Arc); @@ -2452,7 +2446,7 @@ pub async fn get_code_export_formats() -> Result, String> { // 书签管理命令 // ============================================================================ -use crate::flow_monitor::{BookmarkExport, BookmarkManager, FlowBookmark}; +use crate::flow_monitor::{BookmarkManager, FlowBookmark}; /// 书签管理器状态封装 pub struct BookmarkManagerState(pub Arc); @@ -2794,8 +2788,7 @@ pub async fn toggle_bookmark( // ============================================================================ use crate::flow_monitor::{ - Distribution, EnhancedStats, EnhancedStatsService, ReportFormat, StatsTimeRange, - TimeSeriesPoint, TrendData, + Distribution, EnhancedStats, EnhancedStatsService, ReportFormat, StatsTimeRange, TrendData, }; /// 增强统计服务状态封装 @@ -3217,7 +3210,7 @@ pub async fn batch_add_to_session( // 实时监控增强命令 // ============================================================================ -use crate::flow_monitor::{ThresholdCheckResult, ThresholdConfig}; +use crate::flow_monitor::ThresholdConfig; /// 阈值配置响应 #[derive(Debug, Clone, Serialize, Deserialize)] diff --git a/src-tauri/src/commands/native_agent_cmd.rs b/src-tauri/src/commands/native_agent_cmd.rs index 3340a6ed5..3076fe137 100644 --- a/src-tauri/src/commands/native_agent_cmd.rs +++ b/src-tauri/src/commands/native_agent_cmd.rs @@ -211,12 +211,19 @@ pub async fn native_agent_chat_stream( let stream_task = tokio::spawn(async move { agent.chat_stream(request, tx).await }); while let Some(event) = rx.recv().await { + tracing::debug!( + "[NativeAgent] 收到流式事件: {:?}, 发送到: {}", + event, + event_name_clone + ); if let Err(e) = app_handle.emit(&event_name_clone, &event) { tracing::error!("[NativeAgent] 发送事件失败: {}", e); break; } + tracing::debug!("[NativeAgent] 事件发送成功"); if matches!(event, StreamEvent::Done { .. } | StreamEvent::Error { .. }) { + tracing::info!("[NativeAgent] 流式响应完成"); break; } } diff --git a/src-tauri/src/commands/plugin_cmd.rs b/src-tauri/src/commands/plugin_cmd.rs index 61a50f617..ee824add8 100644 --- a/src-tauri/src/commands/plugin_cmd.rs +++ b/src-tauri/src/commands/plugin_cmd.rs @@ -9,6 +9,8 @@ //! //! _需求: 3.1, 3.2, 3.3_ +#![allow(dead_code)] + use crate::plugin::{PluginConfig, PluginInfo, PluginManager, PluginManifest}; use serde::{Deserialize, Serialize}; use std::path::Path; diff --git a/src-tauri/src/commands/provider_pool_cmd.rs b/src-tauri/src/commands/provider_pool_cmd.rs index f5f3a8ab5..af44eb6f7 100644 --- a/src-tauri/src/commands/provider_pool_cmd.rs +++ b/src-tauri/src/commands/provider_pool_cmd.rs @@ -1,5 +1,7 @@ //! Provider Pool Tauri 命令 +#![allow(dead_code)] + use crate::credential::CredentialSyncService; use crate::database::dao::provider_pool::ProviderPoolDao; use crate::database::DbConnection; diff --git a/src-tauri/src/commands/window_cmd.rs b/src-tauri/src/commands/window_cmd.rs index 6286c8603..54e69b68c 100644 --- a/src-tauri/src/commands/window_cmd.rs +++ b/src-tauri/src/commands/window_cmd.rs @@ -3,7 +3,7 @@ //! 提供窗口大小调整、位置控制等功能 use serde::{Deserialize, Serialize}; -use tauri::{AppHandle, Manager, PhysicalSize, Window}; +use tauri::{AppHandle, Manager, PhysicalSize}; /// 窗口大小预设 #[derive(Debug, Clone, Serialize, Deserialize)] diff --git a/src-tauri/src/config/hot_reload.rs b/src-tauri/src/config/hot_reload.rs index f95c02488..3ed26ddc1 100644 --- a/src-tauri/src/config/hot_reload.rs +++ b/src-tauri/src/config/hot_reload.rs @@ -3,6 +3,8 @@ //! 提供配置文件监控和热重载功能 //! - 使用 `notify` crate 监控配置文件变化 //! - 支持原子性配置更新 + +#![allow(dead_code)] //! - 失败时自动回滚到之前的配置 use super::types::{is_default_api_key, Config}; diff --git a/src-tauri/src/config/import.rs b/src-tauri/src/config/import.rs index e86ccd25e..a761eed42 100644 --- a/src-tauri/src/config/import.rs +++ b/src-tauri/src/config/import.rs @@ -3,6 +3,8 @@ //! 提供配置和凭证的统一导入功能,支持: //! - YAML 配置导入 //! - 完整导入包导入(配置 + 凭证 + OAuth Token 文件) + +#![allow(dead_code)] //! - 导入验证(格式、版本、脱敏状态) //! - 合并和替换模式 diff --git a/src-tauri/src/config/path_utils.rs b/src-tauri/src/config/path_utils.rs index f47502636..a85667086 100644 --- a/src-tauri/src/config/path_utils.rs +++ b/src-tauri/src/config/path_utils.rs @@ -2,6 +2,8 @@ //! //! 提供路径处理相关的工具函数,包括 tilde (~) 路径展开 +#![allow(dead_code)] + use std::path::{Path, PathBuf}; /// 展开路径中的 tilde (~) 为用户主目录 diff --git a/src-tauri/src/config/yaml.rs b/src-tauri/src/config/yaml.rs index 71b308ac7..321d0d4c8 100644 --- a/src-tauri/src/config/yaml.rs +++ b/src-tauri/src/config/yaml.rs @@ -3,6 +3,8 @@ //! 提供 YAML 配置的加载、保存和管理功能 //! 支持保留注释的配置保存 +#![allow(dead_code)] + use super::types::Config; use std::collections::HashMap; use std::path::{Path, PathBuf}; diff --git a/src-tauri/src/converter/openai_to_antigravity.rs b/src-tauri/src/converter/openai_to_antigravity.rs index 2ffc0c5e7..659516753 100644 --- a/src-tauri/src/converter/openai_to_antigravity.rs +++ b/src-tauri/src/converter/openai_to_antigravity.rs @@ -8,6 +8,8 @@ //! - 工具定义转换(parameters → parametersJsonSchema) //! - 安全设置自动附加 //! - 思维链配置(reasoning_effort) + +#![allow(dead_code)] //! //! ## 更新日志 //! - 2025-12-28: 修复请求格式,对齐 CLIProxyAPI 实现 diff --git a/src-tauri/src/converter/openai_to_cw.rs b/src-tauri/src/converter/openai_to_cw.rs index fd7f20fa3..5886c5325 100644 --- a/src-tauri/src/converter/openai_to_cw.rs +++ b/src-tauri/src/converter/openai_to_cw.rs @@ -5,6 +5,9 @@ //! # 更新日志 //! //! - 2025-12-27: 添加 web_search 工具支持,修复 Issue #49 + +#![allow(dead_code)] + use crate::models::codewhisperer::*; use crate::models::openai::*; use std::collections::HashMap; diff --git a/src-tauri/src/converter/protocol_selector.rs b/src-tauri/src/converter/protocol_selector.rs index 534948d12..15ea1b845 100644 --- a/src-tauri/src/converter/protocol_selector.rs +++ b/src-tauri/src/converter/protocol_selector.rs @@ -2,6 +2,8 @@ //! //! 根据源协议、目标 Provider 和请求特征,选择最优的协议转换路径。 +#![allow(dead_code)] + use crate::models::provider_pool_model::PoolProviderType; /// 协议类型 diff --git a/src-tauri/src/database/dao/installed_plugins.rs b/src-tauri/src/database/dao/installed_plugins.rs index 197d76052..d449ac0d8 100644 --- a/src-tauri/src/database/dao/installed_plugins.rs +++ b/src-tauri/src/database/dao/installed_plugins.rs @@ -3,6 +3,8 @@ //! 提供已安装插件的 CRUD 操作。 //! _需求: 1.2, 4.2_ +#![allow(dead_code)] + use chrono::{DateTime, Utc}; use rusqlite::{params, Connection, OptionalExtension}; use std::path::PathBuf; diff --git a/src-tauri/src/flow_monitor/file_store.rs b/src-tauri/src/flow_monitor/file_store.rs index 7898bb4b0..b74034977 100644 --- a/src-tauri/src/flow_monitor/file_store.rs +++ b/src-tauri/src/flow_monitor/file_store.rs @@ -578,7 +578,7 @@ impl FlowFileStore { return Ok(None); } - let mut flow: LLMFlow = serde_json::from_str(&line)?; + let flow: LLMFlow = serde_json::from_str(&line)?; Ok(Some(flow)) } diff --git a/src-tauri/src/flow_monitor/filter_parser.rs b/src-tauri/src/flow_monitor/filter_parser.rs index d16a4ce51..7100f7782 100644 --- a/src-tauri/src/flow_monitor/filter_parser.rs +++ b/src-tauri/src/flow_monitor/filter_parser.rs @@ -8,6 +8,8 @@ //! - `~p `: 提供商匹配 //! - `~s `: 状态匹配 (pending/streaming/completed/failed) //! - `~e`: 有错误 + +#![allow(dead_code)] //! - `~t`: 有工具调用 //! - `~k`: 有思维链 //! - `~starred`: 已收藏 @@ -382,7 +384,7 @@ impl<'a> Lexer<'a> { self.chars.next(); ComparisonOp::Eq } - Some(&(pos, c)) => { + Some(&(_pos, c)) => { return Err(FilterParseError::InvalidComparisonOp(c.to_string())); } None => { diff --git a/src-tauri/src/flow_monitor/interceptor.rs b/src-tauri/src/flow_monitor/interceptor.rs index d963dae51..51c7f1655 100644 --- a/src-tauri/src/flow_monitor/interceptor.rs +++ b/src-tauri/src/flow_monitor/interceptor.rs @@ -16,7 +16,7 @@ use std::sync::Arc; use tokio::sync::{broadcast, oneshot, RwLock}; use tokio::time::{timeout, Duration}; -use super::filter_parser::{FilterExpr, FilterParser}; +use super::filter_parser::FilterParser; use super::models::{LLMFlow, LLMRequest, LLMResponse}; // ============================================================================ diff --git a/src-tauri/src/flow_monitor/monitor.rs b/src-tauri/src/flow_monitor/monitor.rs index 402d5f068..174f6ab49 100644 --- a/src-tauri/src/flow_monitor/monitor.rs +++ b/src-tauri/src/flow_monitor/monitor.rs @@ -8,6 +8,8 @@ //! - 阈值检测(延迟、Token 使用量) //! - 请求速率计算 +#![allow(dead_code)] + use chrono::{DateTime, Duration, Utc}; use serde::{Deserialize, Serialize}; use std::collections::{HashMap, VecDeque}; diff --git a/src-tauri/src/flow_monitor/replayer.rs b/src-tauri/src/flow_monitor/replayer.rs index 3d2bcc798..3b27a4db4 100644 --- a/src-tauri/src/flow_monitor/replayer.rs +++ b/src-tauri/src/flow_monitor/replayer.rs @@ -582,7 +582,7 @@ impl FlowReplayer { credential_id: &Option, ) -> Result, ReplayerError> { // 如果没有指定凭证,尝试从凭证池选择 - let cred_id = if let Some(id) = credential_id { + let _cred_id = if let Some(id) = credential_id { id.clone() } else { // 尝试从凭证池选择 diff --git a/src-tauri/src/models/kiro_fingerprint.rs b/src-tauri/src/models/kiro_fingerprint.rs index 762faf1eb..afefc5bea 100644 --- a/src-tauri/src/models/kiro_fingerprint.rs +++ b/src-tauri/src/models/kiro_fingerprint.rs @@ -2,6 +2,8 @@ //! //! 为每个 Kiro 凭证存储独立的 Machine ID,实现多账号指纹隔离。 +#![allow(dead_code)] + use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; use std::collections::HashMap; diff --git a/src-tauri/src/models/mod.rs b/src-tauri/src/models/mod.rs index fb973a209..5600dadee 100644 --- a/src-tauri/src/models/mod.rs +++ b/src-tauri/src/models/mod.rs @@ -24,5 +24,3 @@ pub use provider_model::Provider; #[allow(unused_imports)] pub use provider_pool_model::*; pub use skill_model::{Skill, SkillMetadata, SkillRepo, SkillState, SkillStates}; - -pub use kiro_fingerprint::{KiroFingerprintBinding, KiroFingerprintStore, SwitchToLocalResult}; diff --git a/src-tauri/src/plugin/installer/installer.rs b/src-tauri/src/plugin/installer/installer.rs index 09666a3b1..752d3dbd1 100644 --- a/src-tauri/src/plugin/installer/installer.rs +++ b/src-tauri/src/plugin/installer/installer.rs @@ -19,7 +19,6 @@ use super::types::{ InstallError, InstallProgress, InstallSource, InstalledPlugin, PackageFormat, ProgressCallback, }; use super::validator::PackageValidator; -use crate::plugin::PluginManifest; /// 插件安装器 /// diff --git a/src-tauri/src/plugin/installer/types.rs b/src-tauri/src/plugin/installer/types.rs index a416bc024..72fb179e0 100644 --- a/src-tauri/src/plugin/installer/types.rs +++ b/src-tauri/src/plugin/installer/types.rs @@ -2,6 +2,8 @@ //! //! 定义安装相关的错误类型、进度类型和数据结构 +#![allow(dead_code)] + use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; use std::path::PathBuf; diff --git a/src-tauri/src/processor/steps/routing.rs b/src-tauri/src/processor/steps/routing.rs index 6e665df03..b86a830c1 100644 --- a/src-tauri/src/processor/steps/routing.rs +++ b/src-tauri/src/processor/steps/routing.rs @@ -2,6 +2,8 @@ //! //! 解析模型别名并选择 Provider +#![allow(dead_code)] + use super::traits::{PipelineStep, StepError}; use crate::processor::RequestContext; use crate::router::{ModelMapper, Router}; diff --git a/src-tauri/src/providers/antigravity.rs b/src-tauri/src/providers/antigravity.rs index 54186af81..9d1453b89 100644 --- a/src-tauri/src/providers/antigravity.rs +++ b/src-tauri/src/providers/antigravity.rs @@ -2,6 +2,8 @@ //! //! 支持 Gemini 3 Pro 等高级模型,通过 Google 内部 API 访问。 +#![allow(dead_code)] + use super::traits::{CredentialProvider, ProviderResult}; use async_trait::async_trait; use reqwest::Client; diff --git a/src-tauri/src/providers/claude_oauth.rs b/src-tauri/src/providers/claude_oauth.rs index ba0b5656f..e235339e2 100644 --- a/src-tauri/src/providers/claude_oauth.rs +++ b/src-tauri/src/providers/claude_oauth.rs @@ -8,6 +8,8 @@ //! 2. **Cookie 自动授权** - 使用 sessionKey 自动完成整个 OAuth 流程 //! 3. **Setup Token** - 只需推理权限,无 refresh_token //! + +#![allow(dead_code)] //! ## 主要功能 //! //! - Token 刷新和重试机制 diff --git a/src-tauri/src/providers/codex.rs b/src-tauri/src/providers/codex.rs index 848a2005f..ae80db018 100644 --- a/src-tauri/src/providers/codex.rs +++ b/src-tauri/src/providers/codex.rs @@ -3,6 +3,8 @@ //! Implements OAuth authentication flow for OpenAI Codex API. //! Supports PKCE (Proof Key for Code Exchange) for secure authentication. +#![allow(dead_code)] + use super::error::{ create_auth_error, create_config_error, create_token_refresh_error, ProviderError, }; diff --git a/src-tauri/src/providers/gemini.rs b/src-tauri/src/providers/gemini.rs index 79312691b..ea2f3067c 100644 --- a/src-tauri/src/providers/gemini.rs +++ b/src-tauri/src/providers/gemini.rs @@ -3,6 +3,8 @@ //! 实现 Google Gemini OAuth 认证流程,与 CLIProxyAPI 对齐。 //! 支持 Token 刷新、重试机制和统一凭证格式。 +#![allow(dead_code)] + use super::error::{ create_auth_error, create_config_error, create_token_refresh_error, ProviderError, }; diff --git a/src-tauri/src/providers/iflow.rs b/src-tauri/src/providers/iflow.rs index 260278f3b..7b3494452 100644 --- a/src-tauri/src/providers/iflow.rs +++ b/src-tauri/src/providers/iflow.rs @@ -3,6 +3,8 @@ //! 实现 iFlow OAuth 和 Cookie 认证流程,与 CLIProxyAPI 对齐。 //! 支持双重认证模式:OAuth Token 和导入的 Cookie。 +#![allow(dead_code)] + use super::error::{ create_auth_error, create_config_error, create_token_refresh_error, ProviderError, }; diff --git a/src-tauri/src/providers/kiro.rs b/src-tauri/src/providers/kiro.rs index 61e7a6d83..0c74e2b9b 100644 --- a/src-tauri/src/providers/kiro.rs +++ b/src-tauri/src/providers/kiro.rs @@ -1,4 +1,7 @@ //! Kiro/CodeWhisperer Provider + +#![allow(dead_code)] + use crate::converter::openai_to_cw::convert_openai_to_codewhisperer; use crate::models::openai::*; use crate::providers::traits::{CredentialProvider, ProviderResult}; diff --git a/src-tauri/src/providers/traits.rs b/src-tauri/src/providers/traits.rs index 7f8024f4b..70c922347 100644 --- a/src-tauri/src/providers/traits.rs +++ b/src-tauri/src/providers/traits.rs @@ -2,6 +2,8 @@ //! //! 统一的 Provider 接口,用于凭证管理和 Token 生命周期管理。 +#![allow(dead_code)] + use async_trait::async_trait; use std::error::Error; diff --git a/src-tauri/src/providers/vertex.rs b/src-tauri/src/providers/vertex.rs index 227bff9eb..52b8966a5 100644 --- a/src-tauri/src/providers/vertex.rs +++ b/src-tauri/src/providers/vertex.rs @@ -3,6 +3,8 @@ //! Provides API key authentication for Google Vertex AI models. //! Supports model alias mappings and load balancing across multiple credentials. +#![allow(dead_code)] + use crate::config::VertexApiKeyEntry; use reqwest::Client; use serde::{Deserialize, Serialize}; diff --git a/src-tauri/src/server/client_detector.rs b/src-tauri/src/server/client_detector.rs index c3dd3c01e..f26b709d3 100644 --- a/src-tauri/src/server/client_detector.rs +++ b/src-tauri/src/server/client_detector.rs @@ -2,6 +2,8 @@ //! //! 通过解析 HTTP 请求的 User-Agent 头来识别客户端类型。 +#![allow(dead_code)] + use serde::{Deserialize, Serialize}; /// 客户端类型枚举 @@ -130,7 +132,7 @@ impl std::fmt::Display for ClientType { /// # 返回 /// 选择的 Provider 名称 pub fn select_provider( - client_type: ClientType, + _client_type: ClientType, endpoint_provider: Option<&String>, default_provider: &str, ) -> String { diff --git a/src-tauri/src/server/handlers/api.rs b/src-tauri/src/server/handlers/api.rs index ec55b9123..8004412da 100644 --- a/src-tauri/src/server/handlers/api.rs +++ b/src-tauri/src/server/handlers/api.rs @@ -3,6 +3,8 @@ //! 处理 OpenAI 和 Anthropic 格式的 API 请求 //! //! # 流式传输支持 + +#![allow(dead_code)] //! //! 本模块支持真正的端到端流式传输: //! - 对于流式请求,使用 StreamManager 处理响应 diff --git a/src-tauri/src/server/handlers/kiro_credential.rs b/src-tauri/src/server/handlers/kiro_credential.rs index d7a411afc..5c90c73d5 100644 --- a/src-tauri/src/server/handlers/kiro_credential.rs +++ b/src-tauri/src/server/handlers/kiro_credential.rs @@ -121,7 +121,7 @@ pub async fn get_available_credentials( status_code: 503, })?; - let pool_service = &state.pool_service; + let _pool_service = &state.pool_service; let token_cache = &state.token_cache; // 获取所有kiro凭证 diff --git a/src-tauri/src/server/handlers/management.rs b/src-tauri/src/server/handlers/management.rs index 4e751e249..98803aac6 100644 --- a/src-tauri/src/server/handlers/management.rs +++ b/src-tauri/src/server/handlers/management.rs @@ -2,6 +2,8 @@ //! //! 提供服务器状态查询、凭证管理、配置管理等功能 +#![allow(dead_code)] + use axum::{extract::State, http::StatusCode, response::IntoResponse, Json}; use serde::{Deserialize, Serialize}; diff --git a/src-tauri/src/server/handlers/provider_calls.rs b/src-tauri/src/server/handlers/provider_calls.rs index 3c2eb77fc..b8028e32d 100644 --- a/src-tauri/src/server/handlers/provider_calls.rs +++ b/src-tauri/src/server/handlers/provider_calls.rs @@ -3,6 +3,8 @@ //! 根据凭证类型调用不同的 Provider API //! //! # 流式传输支持 + +#![allow(dead_code)] //! //! 本模块支持真正的端到端流式传输,通过以下组件实现: //! - `StreamManager`: 管理流式请求的生命周期 @@ -760,7 +762,7 @@ pub async fn call_provider_openai( state: &AppState, credential: &ProviderCredential, request: &ChatCompletionRequest, - flow_id: Option<&str>, + _flow_id: Option<&str>, ) -> Response { let _start_time = std::time::Instant::now(); match &credential.credential { @@ -823,6 +825,130 @@ pub async fn call_provider_openai( let _ = kiro.load_credentials_from_path(creds_file_path).await; // 使用缓存的 token 覆盖文件中的 token(缓存的 token 更新) kiro.credentials.access_token = Some(token); + + tracing::info!("[CALL_PROVIDER_OPENAI] request.stream = {}, model = {}", request.stream, request.model); + + // 检查是否为流式请求 + if request.stream { + // 流式请求处理 + tracing::info!("[OPENAI_STREAM] 处理流式请求, model={}", request.model); + match kiro.call_api_stream(request).await { + Ok(stream_response) => { + // 记录成功 + if let Some(db) = &state.db { + let _ = state.pool_service.mark_healthy(db, &credential.uuid, Some(&request.model)); + let _ = state.pool_service.record_usage(db, &credential.uuid); + } + + tracing::info!("[OPENAI_STREAM] 开始转换流式响应"); + + // 创建 StreamConverter 将 AWS Event Stream 转换为 OpenAI SSE + let converter = std::sync::Arc::new(tokio::sync::Mutex::new( + crate::streaming::converter::StreamConverter::with_model( + crate::streaming::converter::StreamFormat::AwsEventStream, + crate::streaming::converter::StreamFormat::OpenAiSse, + &request.model, + ), + )); + + // 创建转换流 + let converter_for_stream = converter.clone(); + let final_stream = async_stream::stream! { + use futures::StreamExt; + + let mut stream_response = stream_response; + + while let Some(chunk_result) = stream_response.next().await { + match chunk_result { + Ok(bytes) => { + tracing::debug!( + "[OPENAI_STREAM] 收到 {} 字节数据", + bytes.len() + ); + + // 转换 chunk + let sse_events = { + let mut converter_guard = converter_for_stream.lock().await; + converter_guard.convert(&bytes) + }; + + tracing::debug!( + "[OPENAI_STREAM] 生成 {} 个 SSE 事件", + sse_events.len() + ); + + // yield 每个 SSE 事件 + for sse_str in sse_events { + yield Ok::(sse_str); + } + } + Err(e) => { + tracing::error!("[OPENAI_STREAM] 流式传输错误: {}", e); + yield Err(e); + return; + } + } + } + + tracing::info!("[OPENAI_STREAM] 流结束,生成 finalize 事件"); + + // 流结束,生成结束事件 + let final_events = { + let mut converter_guard = converter_for_stream.lock().await; + converter_guard.finish() + }; + + tracing::info!("[OPENAI_STREAM] finalize 生成 {} 个事件", final_events.len()); + + for sse_str in final_events { + yield Ok::(sse_str); + } + }; + + tracing::info!("[OPENAI_STREAM] 构建 SSE 响应"); + + // 转换为 Body 流 + let body_stream = final_stream.map(|result| -> Result { + match result { + Ok(event) => Ok(axum::body::Bytes::from(event)), + Err(e) => Ok(axum::body::Bytes::from(e.to_sse_error())), + } + }); + + // 构建 SSE 响应 + return Response::builder() + .status(StatusCode::OK) + .header(header::CONTENT_TYPE, "text/event-stream") + .header(header::CACHE_CONTROL, "no-cache") + .header(header::CONNECTION, "keep-alive") + .header(header::TRANSFER_ENCODING, "chunked") + .header("X-Accel-Buffering", "no") + .body(Body::from_stream(body_stream)) + .unwrap_or_else(|_| { + ( + StatusCode::INTERNAL_SERVER_ERROR, + Json( + serde_json::json!({"error": {"message": "Failed to build streaming response"}}), + ), + ) + .into_response() + }); + } + Err(e) => { + // 记录请求错误 + if let Some(db) = &state.db { + let _ = state.pool_service.mark_unhealthy(db, &credential.uuid, Some(&e.to_string())); + } + return ( + StatusCode::INTERNAL_SERVER_ERROR, + Json(serde_json::json!({"error": {"message": e.to_string()}})), + ) + .into_response(); + } + } + } + + // 非流式请求处理 match kiro.call_api(request).await { Ok(resp) => { let status = resp.status(); @@ -967,6 +1093,53 @@ pub async fn call_provider_openai( } CredentialData::OpenAIKey { api_key, base_url } => { let openai = OpenAICustomProvider::with_config(api_key.clone(), base_url.clone()); + + tracing::info!("[OPENAI_KEY] request.stream = {}, model = {}", request.stream, request.model); + + // 检查是否为流式请求 + if request.stream { + tracing::info!("[OPENAI_KEY_STREAM] 处理流式请求, model={}", request.model); + match openai.call_api_stream(request).await { + Ok(stream_response) => { + tracing::info!("[OPENAI_KEY_STREAM] 开始直接转发 OpenAI SSE 流"); + + // OpenAI 提供商已经返回 OpenAI SSE 格式,直接转发 + let body_stream = stream_response.map(|result| -> Result { + match result { + Ok(bytes) => Ok(bytes), + Err(e) => Ok(axum::body::Bytes::from(e.to_sse_error())), + } + }); + + return Response::builder() + .status(StatusCode::OK) + .header(header::CONTENT_TYPE, "text/event-stream") + .header(header::CACHE_CONTROL, "no-cache") + .header(header::CONNECTION, "keep-alive") + .header(header::TRANSFER_ENCODING, "chunked") + .header("X-Accel-Buffering", "no") + .body(Body::from_stream(body_stream)) + .unwrap_or_else(|_| { + ( + StatusCode::INTERNAL_SERVER_ERROR, + Json( + serde_json::json!({"error": {"message": "Failed to build streaming response"}}), + ), + ) + .into_response() + }); + } + Err(e) => { + return ( + StatusCode::INTERNAL_SERVER_ERROR, + Json(serde_json::json!({"error": {"message": e.to_string()}})), + ) + .into_response(); + } + } + } + + // 非流式请求处理 match openai.call_api(request).await { Ok(resp) => { if resp.status().is_success() { @@ -1008,11 +1181,104 @@ pub async fn call_provider_openai( // 打印 Claude 代理 URL 用于调试 let actual_base_url = base_url.as_deref().unwrap_or("https://api.anthropic.com"); tracing::info!( - "[CLAUDE] 使用 Claude API 代理: base_url={} credential_uuid={}", + "[CLAUDE] 使用 Claude API 代理: base_url={} credential_uuid={} stream={}", actual_base_url, - &credential.uuid[..8] + &credential.uuid[..8], + request.stream ); let claude = ClaudeCustomProvider::with_config(api_key.clone(), base_url.clone()); + + // 检查是否为流式请求 + if request.stream { + tracing::info!("[CLAUDE_KEY_STREAM] 处理流式请求, model={}", request.model); + + match claude.call_api_stream(request).await { + Ok(stream_response) => { + tracing::info!("[CLAUDE_KEY_STREAM] 开始转换 Anthropic SSE 到 OpenAI SSE"); + + // 创建 StreamConverter 将 Anthropic SSE 转换为 OpenAI SSE + let converter = std::sync::Arc::new(tokio::sync::Mutex::new( + crate::streaming::converter::StreamConverter::with_model( + crate::streaming::converter::StreamFormat::AnthropicSse, + crate::streaming::converter::StreamFormat::OpenAiSse, + &request.model, + ), + )); + + let converter_for_stream = converter.clone(); + let final_stream = async_stream::stream! { + use futures::StreamExt; + + let mut stream_response = stream_response; + + while let Some(chunk_result) = stream_response.next().await { + match chunk_result { + Ok(bytes) => { + // 转换 Anthropic SSE 到 OpenAI SSE + let sse_events = { + let mut converter_guard = converter_for_stream.lock().await; + converter_guard.convert(&bytes) + }; + + for sse_str in sse_events { + yield Ok::(sse_str); + } + } + Err(e) => { + tracing::error!("[CLAUDE_KEY_STREAM] 流式传输错误: {}", e); + yield Err(e); + return; + } + } + } + + // 流结束,生成结束事件 + let final_events = { + let mut converter_guard = converter_for_stream.lock().await; + converter_guard.finish() + }; + + for sse_str in final_events { + yield Ok::(sse_str); + } + }; + + let body_stream = final_stream.map(|result| -> Result { + match result { + Ok(event) => Ok(axum::body::Bytes::from(event)), + Err(e) => Ok(axum::body::Bytes::from(e.to_sse_error())), + } + }); + + return Response::builder() + .status(StatusCode::OK) + .header(header::CONTENT_TYPE, "text/event-stream") + .header(header::CACHE_CONTROL, "no-cache") + .header(header::CONNECTION, "keep-alive") + .header(header::TRANSFER_ENCODING, "chunked") + .header("X-Accel-Buffering", "no") + .body(Body::from_stream(body_stream)) + .unwrap_or_else(|_| { + ( + StatusCode::INTERNAL_SERVER_ERROR, + Json( + serde_json::json!({"error": {"message": "Failed to build streaming response"}}), + ), + ) + .into_response() + }); + } + Err(e) => { + return ( + StatusCode::INTERNAL_SERVER_ERROR, + Json(serde_json::json!({"error": {"message": e.to_string()}})), + ) + .into_response(); + } + } + } + + // 非流式请求处理 match claude.call_openai_api(request).await { Ok(resp) => Json(resp).into_response(), Err(e) => ( diff --git a/src-tauri/src/services/backup_service.rs b/src-tauri/src/services/backup_service.rs index f79af51ae..01d3b430d 100644 --- a/src-tauri/src/services/backup_service.rs +++ b/src-tauri/src/services/backup_service.rs @@ -2,6 +2,8 @@ //! //! 提供数据库与配置备份的基础能力 +#![allow(dead_code)] + use crate::database::{get_db_path, DbConnection}; use chrono::{DateTime, Duration, Utc}; use rusqlite::DatabaseName; diff --git a/src-tauri/src/services/kiro_event_service.rs b/src-tauri/src/services/kiro_event_service.rs index 7b15d41f1..58ebd5236 100644 --- a/src-tauri/src/services/kiro_event_service.rs +++ b/src-tauri/src/services/kiro_event_service.rs @@ -24,7 +24,7 @@ pub struct KiroEventService { /// 缓存的凭证状态 #[derive(Debug, Clone, Serialize, Deserialize)] -struct CachedCredentialState { +pub struct CachedCredentialState { uuid: String, is_healthy: bool, is_disabled: bool, diff --git a/src-tauri/src/services/live_sync.rs b/src-tauri/src/services/live_sync.rs index fc3334587..365c8dec2 100644 --- a/src-tauri/src/services/live_sync.rs +++ b/src-tauri/src/services/live_sync.rs @@ -1,7 +1,7 @@ use crate::models::{AppType, Provider}; use serde_json::{json, Value}; use std::fs; -use std::io::{Read, Write}; +use std::io::Write; use std::path::PathBuf; /// ProxyCast 管理的环境变量块标记 diff --git a/src-tauri/src/services/machine_id_service.rs b/src-tauri/src/services/machine_id_service.rs index f9772f904..f8373afb2 100644 --- a/src-tauri/src/services/machine_id_service.rs +++ b/src-tauri/src/services/machine_id_service.rs @@ -1,8 +1,10 @@ +#![allow(dead_code)] + use crate::models::machine_id::*; use dirs; use serde_json; use std::fs; -use std::path::{Path, PathBuf}; +use std::path::PathBuf; use std::process::Command; use tracing; use uuid::Uuid; diff --git a/src-tauri/src/services/provider_pool_service.rs b/src-tauri/src/services/provider_pool_service.rs index 65e4244a8..30b5316cc 100644 --- a/src-tauri/src/services/provider_pool_service.rs +++ b/src-tauri/src/services/provider_pool_service.rs @@ -2,6 +2,8 @@ //! //! 提供凭证池的选择、健康检测、负载均衡等功能。 +#![allow(dead_code)] + use crate::database::dao::provider_pool::ProviderPoolDao; use crate::database::DbConnection; use crate::models::provider_pool_model::{ @@ -14,7 +16,7 @@ use crate::providers::kiro::KiroProvider; use chrono::Utc; use reqwest::Client; use std::collections::HashMap; -use std::sync::atomic::{AtomicUsize, Ordering}; +use std::sync::atomic::AtomicUsize; use std::time::Duration; /// 凭证池管理服务 diff --git a/src-tauri/src/services/token_cache_service.rs b/src-tauri/src/services/token_cache_service.rs index 9e2ae484d..655691251 100644 --- a/src-tauri/src/services/token_cache_service.rs +++ b/src-tauri/src/services/token_cache_service.rs @@ -3,6 +3,8 @@ //! 负责管理凭证池中 OAuth Token 的生命周期: //! - 从源文件加载初始 Token //! - 缓存刷新后的 Token 到数据库 + +#![allow(dead_code)] //! - 按需刷新即将过期的 Token //! - 处理 401/403 错误时的强制刷新 diff --git a/src-tauri/src/streaming/converter.rs b/src-tauri/src/streaming/converter.rs index c746335aa..de77c0fbf 100644 --- a/src-tauri/src/streaming/converter.rs +++ b/src-tauri/src/streaming/converter.rs @@ -3,6 +3,8 @@ //! 在不同流式格式之间转换,支持 AWS Event Stream、Anthropic SSE 和 OpenAI SSE。 //! //! # 需求覆盖 + +#![allow(dead_code)] //! //! - 需求 3.1: AWS Event Stream 到 Anthropic SSE 转换 //! - 需求 3.2: AWS Event Stream 到 OpenAI SSE 转换 diff --git a/src-tauri/src/telemetry/tokens.rs b/src-tauri/src/telemetry/tokens.rs index 9223ecf78..b0de7477d 100644 --- a/src-tauri/src/telemetry/tokens.rs +++ b/src-tauri/src/telemetry/tokens.rs @@ -2,6 +2,8 @@ //! //! 提供 Token 计数记录、估算和统计功能 +#![allow(dead_code)] + use crate::ProviderType; use chrono::{DateTime, Duration, Utc}; use parking_lot::RwLock; diff --git a/src-tauri/src/tray/sync.rs b/src-tauri/src/tray/sync.rs index b196ea434..86a7a8dc4 100644 --- a/src-tauri/src/tray/sync.rs +++ b/src-tauri/src/tray/sync.rs @@ -3,6 +3,8 @@ //! 提供托盘状态与应用状态的同步功能 //! //! # Requirements + +#![allow(dead_code)] //! - 7.1: API 服务器状态变化时在 1 秒内更新托盘图标 //! - 7.2: 凭证健康状态变化时在 1 秒内更新托盘图标 diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 898ce5b4d..2e081bfa7 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -1,7 +1,7 @@ { "$schema": "https://schema.tauri.app/config/2", "productName": "ProxyCast", - "version": "0.25.0", + "version": "0.26.0", "identifier": "com.proxycast.app", "build": { "beforeDevCommand": "npm run dev", diff --git a/src/components/agent/chat/hooks/useAgentChat.ts b/src/components/agent/chat/hooks/useAgentChat.ts index 3ecd8f5d9..b3eec096e 100644 --- a/src/components/agent/chat/hooks/useAgentChat.ts +++ b/src/components/agent/chat/hooks/useAgentChat.ts @@ -1,15 +1,18 @@ import { useState, useEffect } from "react"; import { toast } from "sonner"; +import { listen, type UnlistenFn } from "@tauri-apps/api/event"; import { startAgentProcess, stopAgentProcess, getAgentProcessStatus, createAgentSession, - sendAgentMessage, + sendAgentMessageStream, listAgentSessions, deleteAgentSession, + parseStreamEvent, type AgentProcessStatus, type SessionInfo, + type StreamEvent, } from "@/lib/api/agent"; import { Message, MessageImage, PROVIDER_CONFIG } from "../types"; @@ -156,7 +159,7 @@ export function useAgentChat() { }, [sessionId]); // Ensure an active session exists (internal helper) - const ensureSession = async (): Promise => { + const _ensureSession = async (): Promise => { // If we already have a session, we might want to continue using it. // However, check if we need to "re-initialize" if critical params changed? // User said: "选择模型后,不用和会话绑定". So we keep the session ID if it exists. @@ -236,45 +239,154 @@ export function useAgentChat() { setMessages((prev) => [...prev, userMsg, assistantMsg]); setIsSending(true); - try { - // 2. Ensure Session Exists (Seamless) - const activeSessionId = await ensureSession(); - if (!activeSessionId) throw new Error("Could not establish session"); + // 用于累积流式内容 + let accumulatedContent = ""; + let unlisten: UnlistenFn | null = null; - // 3. Send Message + try { + // 2. 创建唯一事件名称(流式 API 不需要 session) + const eventName = `agent_stream_${assistantMsgId}`; + + // 4. 设置事件监听器(流式接收) + console.log(`[AgentChat] 设置事件监听器: ${eventName}`); + unlisten = await listen(eventName, (event) => { + console.log("[AgentChat] 收到事件:", eventName, event.payload); + const data = parseStreamEvent(event.payload); + if (!data) { + console.warn("[AgentChat] 解析事件失败:", event.payload); + return; + } + console.log("[AgentChat] 解析后数据:", data); + + switch (data.type) { + case "text_delta": + // 累积文本并实时更新 UI + accumulatedContent += data.text; + setMessages((prev) => + prev.map((msg) => + msg.id === assistantMsgId + ? { + ...msg, + content: accumulatedContent, + thinkingContent: undefined, + } + : msg, + ), + ); + break; + + case "done": + // 完成,标记 isThinking 为 false + setMessages((prev) => + prev.map((msg) => + msg.id === assistantMsgId + ? { + ...msg, + isThinking: false, + content: accumulatedContent || "(No response)", + } + : msg, + ), + ); + setIsSending(false); + if (unlisten) { + unlisten(); + unlisten = null; + } + break; + + case "error": + // 错误处理 + toast.error(`响应错误: ${data.message}`); + setMessages((prev) => + prev.map((msg) => + msg.id === assistantMsgId + ? { + ...msg, + isThinking: false, + content: accumulatedContent || `错误: ${data.message}`, + } + : msg, + ), + ); + setIsSending(false); + if (unlisten) { + unlisten(); + unlisten = null; + } + break; + + case "tool_start": + // 工具开始执行 - 添加到工具调用列表 + console.log(`[Tool Start] ${data.tool_name} (${data.tool_id})`); + setMessages((prev) => + prev.map((msg) => + msg.id === assistantMsgId + ? { + ...msg, + toolCalls: [ + ...(msg.toolCalls || []), + { + id: data.tool_id, + name: data.tool_name, + status: "running" as const, + startTime: new Date(), + }, + ], + } + : msg, + ), + ); + break; + + case "tool_end": + // 工具执行完成 - 更新工具调用状态 + console.log(`[Tool End] ${data.tool_id}`); + setMessages((prev) => + prev.map((msg) => + msg.id === assistantMsgId + ? { + ...msg, + toolCalls: (msg.toolCalls || []).map((tc) => + tc.id === data.tool_id + ? { + ...tc, + status: data.result.success + ? ("completed" as const) + : ("failed" as const), + result: data.result, + endTime: new Date(), + } + : tc, + ), + } + : msg, + ), + ); + break; + } + }); + + // 5. 发送流式请求 const imagesToSend = images.length > 0 ? images.map((img) => ({ data: img.data, media_type: img.mediaType })) : undefined; - // Pass current model preference to override session default if supported - const response = await sendAgentMessage( + await sendAgentMessageStream( content, - activeSessionId, + eventName, model || undefined, imagesToSend, - webSearch, - thinking, - ); - - setMessages((prev) => - prev.map((msg) => - msg.id === assistantMsgId - ? { - ...msg, - content: response || "(No response)", - isThinking: false, - thinkingContent: undefined, - } - : msg, - ), ); } catch (error) { toast.error(`发送失败: ${error}`); // Remove the optimistic assistant message on failure setMessages((prev) => prev.filter((msg) => msg.id !== assistantMsgId)); - } finally { setIsSending(false); + if (unlisten) { + unlisten(); + } } }; diff --git a/src/lib/api/agent.ts b/src/lib/api/agent.ts index 465e5c6fd..9dbc6a937 100644 --- a/src/lib/api/agent.ts +++ b/src/lib/api/agent.ts @@ -248,7 +248,7 @@ export async function createAgentSession( } /** - * 发送消息到 Agent(支持连续对话) + * 发送消息到 Agent(支持连续对话)- 非流式版本 */ export async function sendAgentMessage( message: string, @@ -268,6 +268,35 @@ export async function sendAgentMessage( }); } +/** + * 发送消息到 Agent(流式版本) + * + * 通过 Tauri 事件接收响应流,需要配合 listen() 使用: + * @example + * ```typescript + * const unlisten = await listen(eventName, (event) => { + * const data = event.payload; + * if (data.type === "text_delta") { + * // 处理文本增量 + * } + * }); + * await sendAgentMessageStream(message, eventName); + * ``` + */ +export async function sendAgentMessageStream( + message: string, + eventName: string, + model?: string, + images?: ImageInput[], +): Promise { + return await invoke("native_agent_chat_stream", { + message, + eventName, + model, + images, + }); +} + /** * 获取会话列表 */ diff --git a/test_stream.py b/test_stream.py new file mode 100644 index 000000000..2ee6161f2 --- /dev/null +++ b/test_stream.py @@ -0,0 +1,63 @@ +#!/usr/bin/env python3 +import http.client +import json + +conn = http.client.HTTPConnection("127.0.0.1", 8999) + +headers = { + "Content-Type": "application/json", + "Authorization": "Bearer Proxycast-key11" +} + +data = { + "model": "claude-opus-4-5-20251101", + "messages": [{"role": "user", "content": "你好"}], + "stream": True +} + +print("发送流式请求...") +print(f"请求数据: {json.dumps(data, ensure_ascii=False)}\n") +conn.request("POST", "/v1/chat/completions", json.dumps(data), headers) + +response = conn.getresponse() +print(f"HTTP 状态码: {response.status}") +print(f"响应头: {dict(response.getheaders())}\n") + +print("开始接收流式数据:\n") +chunk_count = 0 +content_buffer = "" + +while True: + line = response.readline() + if not line: + break + + chunk_count += 1 + decoded = line.decode('utf-8').strip() + + if not decoded: + continue + + print(f"Chunk {chunk_count}: {decoded[:150]}...") + + if decoded.startswith('data: '): + json_str = decoded[6:] + if json_str == '[DONE]': + print(" → 流结束") + break + + try: + data_obj = json.loads(json_str) + if 'choices' in data_obj and data_obj['choices']: + delta = data_obj['choices'][0].get('delta', {}) + content = delta.get('content', '') + if content: + content_buffer += content + print(f" ✓ 内容: {content}") + except json.JSONDecodeError as e: + print(f" ✗ JSON 解析失败: {e}") + +print(f"\n总共接收 {chunk_count} 个数据块") +print(f"累积内容: {content_buffer}") + +conn.close()