release: v0.54.0 - 代码质量改进与 Clippy 修复

- 更新版本号到 0.54.0
- 修复所有 ESLint 警告
- 通过 cargo clippy --fix 自动修复大量代码风格问题
- 修复 format! 字符串内联变量
- 修复 &PathBuf 改为 &Path
- 使用 #[derive(Default)] 替代手动实现
- 修复 aster 依赖版本 (v0.7.1 -> v0.7.0)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
coso
2026-02-03 03:27:40 +08:00
co-authored by Claude Opus 4.5
parent 2b65f6ac89
commit 2ba5eaa4c8
231 changed files with 3542 additions and 3217 deletions
+2 -1
View File
@@ -1,7 +1,7 @@
{
"name": "proxycast",
"private": true,
"version": "0.53.0",
"version": "0.54.0",
"type": "module",
"repository": {
"type": "git",
@@ -62,6 +62,7 @@
"@xterm/xterm": "^6.0.0",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
"cmdk": "^1.1.1",
"date-fns": "^4.1.0",
"dayjs": "^1.11.19",
"fabric": "^5.5.2",
+16 -16
View File
@@ -202,7 +202,7 @@ checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
[[package]]
name = "aster"
version = "0.7.0"
version = "0.7.1"
dependencies = [
"ahash",
"anyhow",
@@ -2112,7 +2112,7 @@ dependencies = [
"dtoa-short",
"itoa",
"matches",
"phf 0.10.1",
"phf 0.8.0",
"proc-macro2",
"quote",
"smallvec",
@@ -2128,7 +2128,7 @@ dependencies = [
"cssparser-macros",
"dtoa-short",
"itoa",
"phf 0.11.3",
"phf 0.8.0",
"smallvec",
]
@@ -3988,7 +3988,7 @@ dependencies = [
"js-sys",
"log",
"wasm-bindgen",
"windows-core 0.57.0",
"windows-core 0.56.0",
]
[[package]]
@@ -5307,7 +5307,7 @@ version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff32365de1b6743cb203b710788263c44a03de03802daf96092f2da4fe6ba4d7"
dependencies = [
"proc-macro-crate 2.0.2",
"proc-macro-crate 1.3.1",
"proc-macro2",
"quote",
"syn 2.0.114",
@@ -6024,7 +6024,9 @@ version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12"
dependencies = [
"phf_macros 0.8.0",
"phf_shared 0.8.0",
"proc-macro-hack",
]
[[package]]
@@ -6033,9 +6035,7 @@ version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259"
dependencies = [
"phf_macros 0.10.0",
"phf_shared 0.10.0",
"proc-macro-hack",
]
[[package]]
@@ -6139,12 +6139,12 @@ dependencies = [
[[package]]
name = "phf_macros"
version = "0.10.0"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "58fdf3184dd560f160dd73922bea2d5cd6e8f064bf4b13110abd81b03697b4e0"
checksum = "7f6fde18ff429ffc8fe78e2bf7f8b7a5a5a6e2a8b58bc5a9ac69198bbda9189c"
dependencies = [
"phf_generator 0.10.0",
"phf_shared 0.10.0",
"phf_generator 0.8.0",
"phf_shared 0.8.0",
"proc-macro-hack",
"proc-macro2",
"quote",
@@ -6545,7 +6545,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a56d757972c98b346a9b766e3f02746cde6dd1cd1d1d563472929fdd74bec4d"
dependencies = [
"anyhow",
"itertools 0.14.0",
"itertools 0.12.1",
"proc-macro2",
"quote",
"syn 2.0.114",
@@ -6553,7 +6553,7 @@ dependencies = [
[[package]]
name = "proxycast"
version = "0.53.0"
version = "0.54.0"
dependencies = [
"anyhow",
"arboard",
@@ -6635,7 +6635,7 @@ dependencies = [
[[package]]
name = "proxycast-core"
version = "0.53.0"
version = "0.54.0"
dependencies = [
"chrono",
"dirs 5.0.1",
@@ -6651,7 +6651,7 @@ dependencies = [
[[package]]
name = "proxycast-infra"
version = "0.53.0"
version = "0.54.0"
dependencies = [
"chrono",
"dashmap 5.5.3",
@@ -7969,7 +7969,7 @@ version = "3.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b1fdf65dd6331831494dd616b30351c38e96e45921a27745cf98490458b90bb"
dependencies = [
"dirs 6.0.0",
"dirs 4.0.0",
]
[[package]]
+4 -4
View File
@@ -3,7 +3,7 @@ members = ["crates/*"]
resolver = "2"
[workspace.package]
version = "0.53.0"
version = "0.54.0"
edition = "2021"
authors = ["you"]
repository = "https://github.com/aiclientproxy/proxycast"
@@ -103,9 +103,9 @@ enigo = "0.3"
# Aster Agent Framework
# 开发时使用本地 aster-rust,CI/CD 使用远程 GitHub 仓库
# 本地开发: path = "../../../astercloud/aster-rust/crates/aster" (相对 src-tauri/)
# CI/CD: git = "https://github.com/astercloud/aster-rust", tag = "v0.7.0"
# CI/CD: git = "https://github.com/astercloud/aster-rust", tag = "v0.7.1"
# aster = { version = "0.5.1", path = "../../../astercloud/aster-rust/crates/aster" }
aster = { git = "https://github.com/astercloud/aster-rust", tag = "v0.7.0" }
aster = { git = "https://github.com/astercloud/aster-rust", tag = "v0.7.1" }
# Tauri
@@ -164,7 +164,7 @@ version = "2.4"
[package]
name = "proxycast"
version = "0.53.0"
version = "0.54.0"
description = "AI API Proxy Desktop App"
authors = ["you"]
edition = "2021"
+1 -1
View File
@@ -161,7 +161,7 @@ impl LogStore {
self.prune_old_logs(path);
}
fn prune_old_logs(&self, path: &PathBuf) {
fn prune_old_logs(&self, path: &std::path::Path) {
let Some(dir) = path.parent() else {
return;
};
@@ -38,8 +38,7 @@ impl KiroFingerprintStore {
.join("proxycast");
if !app_data_dir.exists() {
fs::create_dir_all(&app_data_dir)
.map_err(|e| format!("创建应用数据目录失败: {}", e))?;
fs::create_dir_all(&app_data_dir).map_err(|e| format!("创建应用数据目录失败: {e}"))?;
}
Ok(app_data_dir.join("kiro_fingerprints.json"))
@@ -54,18 +53,18 @@ impl KiroFingerprintStore {
}
let content =
fs::read_to_string(&path).map_err(|e| format!("读取指纹存储文件失败: {}", e))?;
fs::read_to_string(&path).map_err(|e| format!("读取指纹存储文件失败: {e}"))?;
serde_json::from_str(&content).map_err(|e| format!("解析指纹存储文件失败: {}", e))
serde_json::from_str(&content).map_err(|e| format!("解析指纹存储文件失败: {e}"))
}
/// 保存到文件
pub fn save(&self) -> Result<(), String> {
let path = Self::get_storage_path()?;
let content =
serde_json::to_string_pretty(self).map_err(|e| format!("序列化指纹存储失败: {}", e))?;
serde_json::to_string_pretty(self).map_err(|e| format!("序列化指纹存储失败: {e}"))?;
fs::write(&path, content).map_err(|e| format!("写入指纹存储文件失败: {}", e))
fs::write(&path, content).map_err(|e| format!("写入指纹存储文件失败: {e}"))
}
/// 获取凭证的指纹绑定
@@ -132,7 +131,7 @@ fn generate_stable_machine_id(
hasher.update(seed.as_bytes());
let result = hasher.finalize();
let hex = format!("{:x}", result);
let hex = format!("{result:x}");
format!(
"{}-{}-{}-{}-{}",
&hex[0..8],
@@ -86,7 +86,7 @@ impl std::str::FromStr for ModelStatus {
"beta" => Ok(Self::Beta),
"deprecated" => Ok(Self::Deprecated),
"legacy" => Ok(Self::Legacy),
_ => Err(format!("Unknown model status: {}", s)),
_ => Err(format!("Unknown model status: {s}")),
}
}
}
@@ -124,7 +124,7 @@ impl std::str::FromStr for ModelTier {
"mini" => Ok(Self::Mini),
"pro" => Ok(Self::Pro),
"max" => Ok(Self::Max),
_ => Err(format!("Unknown model tier: {}", s)),
_ => Err(format!("Unknown model tier: {s}")),
}
}
}
@@ -165,7 +165,7 @@ impl std::str::FromStr for ModelSource {
"models.dev" | "modelsdev" => Ok(Self::ModelsDev),
"local" => Ok(Self::Local),
"custom" => Ok(Self::Custom),
_ => Err(format!("Unknown model source: {}", s)),
_ => Err(format!("Unknown model source: {s}")),
}
}
}
@@ -300,7 +300,7 @@ impl UserModelPreference {
}
/// 模型同步状态
#[derive(Debug, Clone, Serialize, Deserialize)]
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct ModelSyncState {
pub last_sync_at: Option<i64>,
pub model_count: u32,
@@ -308,17 +308,6 @@ pub struct ModelSyncState {
pub last_error: Option<String>,
}
impl Default for ModelSyncState {
fn default() -> Self {
Self {
last_sync_at: None,
model_count: 0,
is_syncing: false,
last_error: None,
}
}
}
// Provider Alias 相关类型
/// 单个模型别名映射
@@ -56,7 +56,7 @@ impl RouteInfo {
"openai" => format!("/{}/v1/chat/completions", self.selector),
_ => return,
};
let url = format!("{}{}", base_url, path);
let url = format!("{base_url}{path}");
self.endpoints.push(RouteEndpoint {
path,
protocol: protocol.to_string(),
@@ -81,11 +81,10 @@ impl RouteInfo {
model,
format!(
r#"{{
"model": "{}",
"model": "{model}",
"max_tokens": 1024,
"messages": [{{"role": "user", "content": "Hello!"}}]
}}"#,
model
}}"#
),
)
}
@@ -101,10 +100,9 @@ impl RouteInfo {
model,
format!(
r#"{{
"model": "{}",
"model": "{model}",
"messages": [{{"role": "user", "content": "Hello!"}}]
}}"#,
model
}}"#
),
)
}
@@ -131,7 +131,7 @@ impl FailoverResult {
switched: true,
new_provider: Some(new_provider),
failure_type,
message: format!("已切换到 Provider: {}", new_provider),
message: format!("已切换到 Provider: {new_provider}"),
}
}
@@ -206,7 +206,7 @@ impl Failover {
if !should_switch {
return FailoverResult::not_switched(
failure_type,
&format!("不在 {:?} 故障时切换", failure_type),
&format!("不在 {failure_type:?} 故障时切换"),
);
}
@@ -347,7 +347,7 @@ impl FailoverManager {
if !should_switch {
return FailoverResult::not_switched(
failure_type,
&format!("不在 {:?} 故障时切换", failure_type),
&format!("不在 {failure_type:?} 故障时切换"),
);
}
@@ -92,21 +92,13 @@ impl std::fmt::Display for TimeoutError {
timeout_ms,
elapsed_ms,
} => {
write!(
f,
"请求超时: 配置 {}ms, 已耗时 {}ms",
timeout_ms, elapsed_ms
)
write!(f, "请求超时: 配置 {timeout_ms}ms, 已耗时 {elapsed_ms}ms")
}
TimeoutError::StreamIdleTimeout {
timeout_ms,
idle_ms,
} => {
write!(
f,
"流式响应空闲超时: 配置 {}ms, 空闲 {}ms",
timeout_ms, idle_ms
)
write!(f, "流式响应空闲超时: 配置 {timeout_ms}ms, 空闲 {idle_ms}ms")
}
TimeoutError::Cancelled => {
write!(f, "操作已取消")
+8 -10
View File
@@ -29,10 +29,10 @@ pub enum LoggerError {
impl std::fmt::Display for LoggerError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
LoggerError::Io(e) => write!(f, "IO 错误: {}", e),
LoggerError::Serialization(e) => write!(f, "序列化错误: {}", e),
LoggerError::NotFound(id) => write!(f, "日志未找到: {}", id),
LoggerError::DirectoryCreation(msg) => write!(f, "日志目录创建失败: {}", msg),
LoggerError::Io(e) => write!(f, "IO 错误: {e}"),
LoggerError::Serialization(e) => write!(f, "序列化错误: {e}"),
LoggerError::NotFound(id) => write!(f, "日志未找到: {id}"),
LoggerError::DirectoryCreation(msg) => write!(f, "日志目录创建失败: {msg}"),
}
}
}
@@ -107,7 +107,7 @@ impl RequestLogger {
// 创建日志目录
fs::create_dir_all(&log_dir).map_err(|e| {
LoggerError::DirectoryCreation(format!("无法创建日志目录 {:?}: {}", log_dir, e))
LoggerError::DirectoryCreation(format!("无法创建日志目录 {log_dir:?}: {e}"))
})?;
let logger = Self {
@@ -316,7 +316,7 @@ impl RequestLogger {
let mut file = OpenOptions::new().create(true).append(true).open(&path)?;
let json = serde_json::to_string(log)?;
writeln!(file, "{}", json)?;
writeln!(file, "{json}")?;
}
Ok(())
@@ -325,7 +325,7 @@ impl RequestLogger {
/// 如果需要则轮转日志文件
fn rotate_log_file_if_needed(&self) -> Result<(), LoggerError> {
let today = Utc::now().format("%Y-%m-%d").to_string();
let expected_file = self.log_dir.join(format!("requests_{}.jsonl", today));
let expected_file = self.log_dir.join(format!("requests_{today}.jsonl"));
let needs_rotation = {
let current = self.current_log_file.read();
@@ -370,9 +370,7 @@ impl RequestLogger {
fn find_next_log_file(&self, date: &str) -> Result<PathBuf, LoggerError> {
let mut index = 1;
loop {
let file = self
.log_dir
.join(format!("requests_{}_{}.jsonl", date, index));
let file = self.log_dir.join(format!("requests_{date}_{index}.jsonl"));
if !file.exists()
|| file
.metadata()
@@ -525,7 +525,7 @@ impl std::fmt::Display for TokenEstimatorError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
TokenEstimatorError::InitializationError(msg) => {
write!(f, "Token 估算器初始化失败: {}", msg)
write!(f, "Token 估算器初始化失败: {msg}")
}
}
}
@@ -84,8 +84,7 @@ impl AsrClient for OpenAIWhisperClient {
let status = response.status();
let body = response.text().await.unwrap_or_default();
return Err(VoiceError::AsrError(format!(
"OpenAI API 错误: {} - {}",
status, body
"OpenAI API 错误: {status} - {body}"
)));
}
@@ -104,7 +104,7 @@ impl XunfeiClient {
tracing::debug!("讯飞鉴权 - api_secret 长度: {}", self.api_secret.len());
// 构建签名原文
let signature_origin = format!("host: {}\ndate: {}\nGET {} HTTP/1.1", host, date, path);
let signature_origin = format!("host: {host}\ndate: {date}\nGET {path} HTTP/1.1");
tracing::debug!("讯飞鉴权 - signature_origin:\n{}", signature_origin);
// HMAC-SHA256 签名
@@ -292,7 +292,7 @@ impl AsrClient for XunfeiClient {
tracing::info!("正在连接讯飞 WebSocket...");
let (ws_stream, response) = connect_async(&url).await.map_err(|e| {
tracing::error!("讯飞 WebSocket 连接失败: {:?}", e);
VoiceError::NetworkError(format!("WebSocket 连接失败: {}", e))
VoiceError::NetworkError(format!("WebSocket 连接失败: {e}"))
})?;
tracing::info!(
@@ -406,7 +406,7 @@ impl AsrClient for XunfeiClient {
let json = match serde_json::to_string(&request) {
Ok(j) => j,
Err(e) => {
send_error = Some(VoiceError::AsrError(format!("序列化请求失败: {}", e)));
send_error = Some(VoiceError::AsrError(format!("序列化请求失败: {e}")));
break;
}
};
@@ -422,7 +422,7 @@ impl AsrClient for XunfeiClient {
}
Err(e) => {
tracing::error!("发送第 {} 帧失败: {}", i, e);
send_error = Some(VoiceError::NetworkError(format!("发送数据失败: {}", e)));
send_error = Some(VoiceError::NetworkError(format!("发送数据失败: {e}")));
break;
}
}
@@ -440,7 +440,7 @@ impl AsrClient for XunfeiClient {
match tokio::time::timeout(tokio::time::Duration::from_secs(30), receive_task).await {
Ok(Ok(responses)) => responses,
Ok(Err(e)) => {
return Err(VoiceError::AsrError(format!("接收任务失败: {}", e)));
return Err(VoiceError::AsrError(format!("接收任务失败: {e}")));
}
Err(_) => {
return Err(VoiceError::AsrError("等待识别结果超时".to_string()));
@@ -0,0 +1,7 @@
# Seeds for failure cases proptest has generated in the past. It is
# automatically read and these particular cases re-run before any
# novel cases are generated.
#
# It is recommended to check this file in to source control so that
# everyone who runs the test benefits from these saved cases.
cc 690e03d4ddd600c7175d123787d49a9afacf6f13642e9f7ae0e9e766a2ba93c2 # shrinks to provider = "qwen"
+91 -43
View File
@@ -4,11 +4,11 @@
//! 处理消息发送、事件流转换和会话管理
use crate::agent::aster_state::{AsterAgentState, SessionConfigBuilder};
use crate::database::dao::agent::AgentDao;
use crate::database::DbConnection;
use aster::conversation::message::Message;
use aster::session::SessionManager;
use chrono::Utc;
use futures::StreamExt;
use std::path::PathBuf;
use tauri::{AppHandle, Emitter};
/// Aster Agent 包装器
@@ -78,7 +78,7 @@ impl AsterAgentWrapper {
// 发送错误事件
let error_event =
crate::agent::event_converter::TauriAgentEvent::Error {
message: format!("Stream error: {}", e),
message: format!("Stream error: {e}"),
};
let _ = app.emit(&event_name, &error_event);
}
@@ -93,10 +93,10 @@ impl AsterAgentWrapper {
Err(e) => {
// 发送错误事件并返回错误
let error_event = crate::agent::event_converter::TauriAgentEvent::Error {
message: format!("Agent error: {}", e),
message: format!("Agent error: {e}"),
};
let _ = app.emit(&event_name, &error_event);
return Err(format!("Agent error: {}", e));
return Err(format!("Agent error: {e}"));
}
}
@@ -113,60 +113,74 @@ impl AsterAgentWrapper {
state.cancel_session(session_id).await
}
/// 创建新会话
pub async fn create_session(
working_dir: Option<PathBuf>,
name: Option<String>,
) -> Result<String, String> {
let dir = working_dir
.unwrap_or_else(|| std::env::current_dir().unwrap_or_else(|_| PathBuf::from(".")));
let session_name = name.unwrap_or_else(|| "New Session".to_string());
/// 创建新会话 - 使用 ProxyCast 数据库
pub fn create_session_sync(db: &DbConnection, name: Option<String>) -> Result<String, String> {
let conn = db.lock().map_err(|e| format!("数据库锁定失败: {e}"))?;
let session_name = name.unwrap_or_else(|| "新对话".to_string());
let session_id = uuid::Uuid::new_v4().to_string();
let now = Utc::now().to_rfc3339();
let session =
SessionManager::create_session(dir, session_name, aster::session::SessionType::User)
.await
.map_err(|e| format!("Failed to create session: {}", e))?;
let session = crate::agent::types::AgentSession {
id: session_id.clone(),
model: "agent:default".to_string(),
messages: Vec::new(),
system_prompt: None,
title: Some(session_name),
created_at: now.clone(),
updated_at: now,
};
Ok(session.id)
AgentDao::create_session(&conn, &session).map_err(|e| format!("创建会话失败: {e}"))?;
Ok(session_id)
}
/// 列出所有会话
pub async fn list_sessions() -> Result<Vec<SessionInfo>, String> {
let sessions = SessionManager::list_sessions()
.await
.map_err(|e| format!("Failed to list sessions: {}", e))?;
/// 列出所有会话 - 使用 ProxyCast 数据库
pub fn list_sessions_sync(db: &DbConnection) -> Result<Vec<SessionInfo>, String> {
let conn = db.lock().map_err(|e| format!("数据库锁定失败: {e}"))?;
let sessions =
AgentDao::list_sessions(&conn).map_err(|e| format!("获取会话列表失败: {e}"))?;
Ok(sessions
.into_iter()
.map(|s| SessionInfo {
id: s.id,
name: s.name,
created_at: s.created_at.timestamp(),
updated_at: s.updated_at.timestamp(),
name: s.title.unwrap_or_else(|| "未命名".to_string()),
created_at: chrono::DateTime::parse_from_rfc3339(&s.created_at)
.map(|dt| dt.timestamp())
.unwrap_or(0),
updated_at: chrono::DateTime::parse_from_rfc3339(&s.updated_at)
.map(|dt| dt.timestamp())
.unwrap_or(0),
})
.collect())
}
/// 获取会话详情
pub async fn get_session(session_id: &str) -> Result<SessionDetail, String> {
let session = SessionManager::get_session(session_id, true)
.await
.map_err(|e| format!("Failed to get session: {}", e))?;
/// 获取会话详情 - 使用 ProxyCast 数据库
pub fn get_session_sync(db: &DbConnection, session_id: &str) -> Result<SessionDetail, String> {
let conn = db.lock().map_err(|e| format!("数据库锁定失败: {e}"))?;
let session = AgentDao::get_session(&conn, session_id)
.map_err(|e| format!("获取会话失败: {e}"))?
.ok_or_else(|| format!("会话不存在: {session_id}"))?;
let messages =
AgentDao::get_messages(&conn, session_id).map_err(|e| format!("获取消息失败: {e}"))?;
Ok(SessionDetail {
id: session.id,
name: session.name,
created_at: session.created_at.timestamp(),
updated_at: session.updated_at.timestamp(),
messages: session
.conversation
.map(|c| {
c.messages()
.iter()
.map(|m| crate::agent::event_converter::convert_to_tauri_message(m))
.collect()
})
.unwrap_or_default(),
name: session.title.unwrap_or_else(|| "未命名".to_string()),
created_at: chrono::DateTime::parse_from_rfc3339(&session.created_at)
.map(|dt| dt.timestamp())
.unwrap_or(0),
updated_at: chrono::DateTime::parse_from_rfc3339(&session.updated_at)
.map(|dt| dt.timestamp())
.unwrap_or(0),
messages: messages
.into_iter()
.map(|m| convert_agent_message(&m))
.collect(),
})
}
}
@@ -190,6 +204,40 @@ pub struct SessionDetail {
pub messages: Vec<crate::agent::event_converter::TauriMessage>,
}
/// 将 AgentMessage 转换为 TauriMessage
fn convert_agent_message(
msg: &crate::agent::types::AgentMessage,
) -> crate::agent::event_converter::TauriMessage {
use crate::agent::event_converter::{TauriMessage, TauriMessageContent};
use crate::agent::types::MessageContent;
let content = match &msg.content {
MessageContent::Text(text) => vec![TauriMessageContent::Text { text: text.clone() }],
MessageContent::Parts(parts) => parts
.iter()
.filter_map(|p| {
if let crate::agent::types::ContentPart::Text { text } = p {
Some(TauriMessageContent::Text { text: text.clone() })
} else {
None
}
})
.collect(),
};
// 解析时间戳
let timestamp = chrono::DateTime::parse_from_rfc3339(&msg.timestamp)
.map(|dt| dt.timestamp())
.unwrap_or(0);
TauriMessage {
id: None,
role: msg.role.clone(),
content,
timestamp,
}
}
#[cfg(test)]
mod tests {
use super::*;
+46 -36
View File
@@ -89,10 +89,18 @@ impl AsterAgentState {
if agent_guard.is_none() {
// 创建 SessionStore
let session_store = Arc::new(ProxyCastSessionStore::new(db.clone()));
tracing::info!("[AsterAgent] 创建 ProxyCastSessionStore 成功");
// 创建 Agent 并注入 SessionStore
let agent = Agent::new().with_session_store(session_store);
// 验证 session_store 是否被正确设置
let has_store = agent.session_store().is_some();
tracing::info!(
"[AsterAgent] Agent 创建完成,session_store 已设置: {}",
has_store
);
// 使用异步方法设置 ProxyCast 专属身份
let identity = Self::create_proxycast_identity();
agent.set_identity(identity).await;
@@ -101,6 +109,8 @@ impl AsterAgentState {
tracing::info!(
"[AsterAgent] Agent 初始化成功,已注入 ProxyCastSessionStore 和 ProxyCast 身份"
);
} else {
tracing::debug!("[AsterAgent] Agent 已初始化,跳过");
}
Ok(())
}
@@ -159,12 +169,12 @@ impl AsterAgentState {
// 创建 ModelConfig
let model_config = ModelConfig::new(&config.model_name)
.map_err(|e| format!("创建 ModelConfig 失败: {}", e))?;
.map_err(|e| format!("创建 ModelConfig 失败: {e}"))?;
// 创建 Provider
let provider = aster::providers::create(&config.provider_name, model_config)
.await
.map_err(|e| format!("创建 Provider 失败: {}", e))?;
.map_err(|e| format!("创建 Provider 失败: {e}"))?;
// 更新 Agent 的 Provider
let agent_guard = self.agent.read().await;
@@ -172,7 +182,7 @@ impl AsterAgentState {
agent
.update_provider(provider, session_id)
.await
.map_err(|e| format!("更新 Provider 失败: {}", e))?;
.map_err(|e| format!("更新 Provider 失败: {e}"))?;
}
// 保存当前配置
@@ -212,12 +222,12 @@ impl AsterAgentState {
.credential_bridge
.select_and_configure(db, provider_type, model)
.await
.map_err(|e| format!("从凭证池选择凭证失败: {}", e))?;
.map_err(|e| format!("从凭证池选择凭证失败: {e}"))?;
// 创建 Provider
let provider = create_aster_provider(&aster_config)
.await
.map_err(|e| format!("创建 Provider 失败: {}", e))?;
.map_err(|e| format!("创建 Provider 失败: {e}"))?;
// 更新 Agent 的 Provider
let agent_guard = self.agent.read().await;
@@ -225,7 +235,7 @@ impl AsterAgentState {
agent
.update_provider(provider, session_id)
.await
.map_err(|e| format!("更新 Provider 失败: {}", e))?;
.map_err(|e| format!("更新 Provider 失败: {e}"))?;
}
// 保存当前配置
@@ -446,36 +456,6 @@ pub mod message_helpers {
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_aster_state_init() {
let state = AsterAgentState::new();
assert!(!state.is_initialized().await);
#[allow(deprecated)]
state.init_agent().await.unwrap();
assert!(state.is_initialized().await);
}
#[tokio::test]
async fn test_cancel_token() {
let state = AsterAgentState::new();
let session_id = "test-session";
let token = state.create_cancel_token(session_id).await;
assert!(!token.is_cancelled());
assert!(state.cancel_session(session_id).await);
assert!(token.is_cancelled());
state.remove_cancel_token(session_id).await;
assert!(!state.cancel_session(session_id).await);
}
}
// =============================================================================
// ProxyCast Agent 身份提示词
// =============================================================================
@@ -505,3 +485,33 @@ ProxyCast 是一个 AI 代理服务应用,帮助用户:
- 友好但不啰嗦,像经验丰富的技术伙伴
- 遇到问题时,先分析原因再提供方案
"#;
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_aster_state_init() {
let state = AsterAgentState::new();
assert!(!state.is_initialized().await);
#[allow(deprecated)]
state.init_agent().await.unwrap();
assert!(state.is_initialized().await);
}
#[tokio::test]
async fn test_cancel_token() {
let state = AsterAgentState::new();
let session_id = "test-session";
let token = state.create_cancel_token(session_id).await;
assert!(!token.is_cancelled());
assert!(state.cancel_session(session_id).await);
assert!(token.is_cancelled());
state.remove_cancel_token(session_id).await;
assert!(!state.cancel_session(session_id).await);
}
}
+20 -22
View File
@@ -35,11 +35,11 @@ pub enum CredentialBridgeError {
impl std::fmt::Display for CredentialBridgeError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::NoCredentials(msg) => write!(f, "没有可用凭证: {}", msg),
Self::UnsupportedCredentialType(msg) => write!(f, "不支持的凭证类型: {}", msg),
Self::ProviderCreationFailed(msg) => write!(f, "Provider 创建失败: {}", msg),
Self::TokenRefreshFailed(msg) => write!(f, "Token 刷新失败: {}", msg),
Self::DatabaseError(msg) => write!(f, "数据库错误: {}", msg),
Self::NoCredentials(msg) => write!(f, "没有可用凭证: {msg}"),
Self::UnsupportedCredentialType(msg) => write!(f, "不支持的凭证类型: {msg}"),
Self::ProviderCreationFailed(msg) => write!(f, "Provider 创建失败: {msg}"),
Self::TokenRefreshFailed(msg) => write!(f, "Token 刷新失败: {msg}"),
Self::DatabaseError(msg) => write!(f, "数据库错误: {msg}"),
}
}
}
@@ -112,11 +112,10 @@ impl CredentialBridge {
None,
)
.await
.map_err(|e| CredentialBridgeError::DatabaseError(e))?
.map_err(CredentialBridgeError::DatabaseError)?
.ok_or_else(|| {
CredentialBridgeError::NoCredentials(format!(
"没有找到 {} 类型的可用凭证",
provider_type
"没有找到 {provider_type} 类型的可用凭证"
))
})?;
@@ -229,7 +228,7 @@ impl CredentialBridge {
.load_credentials_from_path(creds_path)
.await
.map_err(|e| {
CredentialBridgeError::TokenRefreshFailed(format!("加载 Kiro 凭证失败: {}", e))
CredentialBridgeError::TokenRefreshFailed(format!("加载 Kiro 凭证失败: {e}"))
})?;
// 检查 token 是否过期,如果过期则刷新
@@ -238,14 +237,14 @@ impl CredentialBridge {
self.pool_service
.refresh_kiro_token(creds_path)
.await
.map_err(|e| CredentialBridgeError::TokenRefreshFailed(e))?;
.map_err(CredentialBridgeError::TokenRefreshFailed)?;
// 重新加载凭证
provider
.load_credentials_from_path(creds_path)
.await
.map_err(|e| {
CredentialBridgeError::TokenRefreshFailed(format!("重新加载凭证失败: {}", e))
CredentialBridgeError::TokenRefreshFailed(format!("重新加载凭证失败: {e}"))
})?;
}
@@ -257,12 +256,11 @@ impl CredentialBridge {
/// 获取通用 OAuth Token
async fn get_oauth_token(&self, creds_path: &str) -> Result<String, CredentialBridgeError> {
let content = std::fs::read_to_string(creds_path).map_err(|e| {
CredentialBridgeError::TokenRefreshFailed(format!("读取凭证文件失败: {}", e))
CredentialBridgeError::TokenRefreshFailed(format!("读取凭证文件失败: {e}"))
})?;
let creds: serde_json::Value = serde_json::from_str(&content).map_err(|e| {
CredentialBridgeError::TokenRefreshFailed(format!("解析凭证失败: {}", e))
})?;
let creds: serde_json::Value = serde_json::from_str(&content)
.map_err(|e| CredentialBridgeError::TokenRefreshFailed(format!("解析凭证失败: {e}")))?;
creds["access_token"]
.as_str()
@@ -281,11 +279,11 @@ impl CredentialBridge {
.load_credentials_from_path(creds_path)
.await
.map_err(|e| {
CredentialBridgeError::TokenRefreshFailed(format!("加载 Codex 凭证失败: {}", e))
CredentialBridgeError::TokenRefreshFailed(format!("加载 Codex 凭证失败: {e}"))
})?;
provider.ensure_valid_token().await.map_err(|e| {
CredentialBridgeError::TokenRefreshFailed(format!("获取 Codex token 失败: {}", e))
CredentialBridgeError::TokenRefreshFailed(format!("获取 Codex token 失败: {e}"))
})
}
@@ -293,7 +291,7 @@ impl CredentialBridge {
pub fn record_usage(&self, db: &DbConnection, uuid: &str) -> Result<(), CredentialBridgeError> {
self.pool_service
.record_usage(db, uuid)
.map_err(|e| CredentialBridgeError::DatabaseError(e))
.map_err(CredentialBridgeError::DatabaseError)
}
/// 标记凭证为健康
@@ -305,7 +303,7 @@ impl CredentialBridge {
) -> Result<(), CredentialBridgeError> {
self.pool_service
.mark_healthy(db, uuid, model)
.map_err(|e| CredentialBridgeError::DatabaseError(e))
.map_err(CredentialBridgeError::DatabaseError)
}
/// 标记凭证为不健康
@@ -317,7 +315,7 @@ impl CredentialBridge {
) -> Result<(), CredentialBridgeError> {
self.pool_service
.mark_unhealthy(db, uuid, error)
.map_err(|e| CredentialBridgeError::DatabaseError(e))
.map_err(CredentialBridgeError::DatabaseError)
}
}
@@ -332,14 +330,14 @@ pub async fn create_aster_provider(
// 创建 ModelConfig
let model_config = ModelConfig::new(&config.model_name).map_err(|e| {
CredentialBridgeError::ProviderCreationFailed(format!("创建 ModelConfig 失败: {}", e))
CredentialBridgeError::ProviderCreationFailed(format!("创建 ModelConfig 失败: {e}"))
})?;
// 创建 Provider
aster::providers::create(&config.provider_name, model_config)
.await
.map_err(|e| {
CredentialBridgeError::ProviderCreationFailed(format!("创建 Provider 失败: {}", e))
CredentialBridgeError::ProviderCreationFailed(format!("创建 Provider 失败: {e}"))
})
}
+3 -3
View File
@@ -205,7 +205,7 @@ fn convert_message(message: Message) -> Vec<TauriAgentEvent> {
}
Err(e) => {
events.push(TauriAgentEvent::Error {
message: format!("Invalid tool call: {}", e),
message: format!("Invalid tool call: {e}"),
});
}
},
@@ -302,7 +302,7 @@ fn convert_message(message: Message) -> Vec<TauriAgentEvent> {
}
Err(e) => {
events.push(TauriAgentEvent::Error {
message: format!("Invalid frontend tool call: {}", e),
message: format!("Invalid frontend tool call: {e}"),
});
}
},
@@ -320,7 +320,7 @@ pub fn convert_to_tauri_message(message: &Message) -> TauriMessage {
let content = message
.content
.iter()
.filter_map(|c| convert_message_content(c))
.filter_map(convert_message_content)
.collect();
TauriMessage {
+17 -18
View File
@@ -64,8 +64,8 @@ pub enum ConfigError {
impl std::fmt::Display for ConfigError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ConfigError::LoadFailed(e) => write!(f, "配置加载失败: {}", e),
ConfigError::SaveFailed(e) => write!(f, "配置保存失败: {}", e),
ConfigError::LoadFailed(e) => write!(f, "配置加载失败: {e}"),
ConfigError::SaveFailed(e) => write!(f, "配置保存失败: {e}"),
ConfigError::InvalidHost => {
write!(
f,
@@ -166,11 +166,10 @@ pub fn init_states(config: &Config) -> Result<AppStates, String> {
let logs: LogState = Arc::new(RwLock::new(logger::LogStore::with_config(&config.logging)));
// 数据库
let db = database::init_database().map_err(|e| format!("数据库初始化失败: {}", e))?;
let db = database::init_database().map_err(|e| format!("数据库初始化失败: {e}"))?;
// 服务状态
let skill_service =
SkillService::new().map_err(|e| format!("SkillService 初始化失败: {}", e))?;
let skill_service = SkillService::new().map_err(|e| format!("SkillService 初始化失败: {e}"))?;
let skill_service_state = SkillServiceState(Arc::new(skill_service));
let provider_pool_service = ProviderPoolService::new();
@@ -186,7 +185,7 @@ pub fn init_states(config: &Config) -> Result<AppStates, String> {
let token_cache_service_state = TokenCacheServiceState(Arc::new(token_cache_service));
let machine_id_service = crate::services::machine_id_service::MachineIdService::new()
.map_err(|e| format!("MachineIdService 初始化失败: {}", e))?;
.map_err(|e| format!("MachineIdService 初始化失败: {e}"))?;
let machine_id_service_state: MachineIdState = Arc::new(RwLock::new(machine_id_service));
let resilience_config_state = ResilienceConfigState::default();
@@ -259,7 +258,7 @@ pub fn init_states(config: &Config) -> Result<AppStates, String> {
// 初始化会话文件存储
let session_files_storage = crate::session_files::SessionFileStorage::new()
.map_err(|e| format!("SessionFileStorage 初始化失败: {}", e))?;
.map_err(|e| format!("SessionFileStorage 初始化失败: {e}"))?;
let session_files_state = SessionFilesState(std::sync::Mutex::new(session_files_storage));
// 初始化全局配置管理器
@@ -271,13 +270,13 @@ pub fn init_states(config: &Config) -> Result<AppStates, String> {
{
let conn = db.lock().expect("Failed to lock database");
database::dao::skills::SkillDao::init_default_skill_repos(&conn)
.map_err(|e| format!("初始化默认技能仓库失败: {}", e))?;
.map_err(|e| format!("初始化默认技能仓库失败: {e}"))?;
}
// 初始化上下文记忆服务
let context_memory_config = ContextMemoryConfig::default();
let context_memory_service = ContextMemoryService::new(context_memory_config)
.map_err(|e| format!("ContextMemoryService 初始化失败: {}", e))?;
.map_err(|e| format!("ContextMemoryService 初始化失败: {e}"))?;
let context_memory_service_arc = Arc::new(context_memory_service);
let context_memory_service_state =
ContextMemoryServiceState(context_memory_service_arc.clone());
@@ -335,7 +334,7 @@ pub fn init_states(config: &Config) -> Result<AppStates, String> {
/// 初始化插件安装器
fn init_plugin_installer() -> Result<PluginInstallerState, String> {
let db_path = database::get_db_path().map_err(|e| format!("获取数据库路径失败: {}", e))?;
let db_path = database::get_db_path().map_err(|e| format!("获取数据库路径失败: {e}"))?;
let plugins_dir = dirs::data_dir()
.unwrap_or_else(|| std::path::PathBuf::from("."))
.join("proxycast")
@@ -366,7 +365,7 @@ fn init_plugin_installer() -> Result<PluginInstallerState, String> {
fallback_temp_dir,
&db_path,
)
.map_err(|e| format!("后备插件安装器初始化失败: {}", e))?;
.map_err(|e| format!("后备插件安装器初始化失败: {e}"))?;
Ok(PluginInstallerState(Arc::new(RwLock::new(installer))))
}
}
@@ -398,7 +397,7 @@ fn init_telemetry(
};
let shared_logger = Arc::new(
telemetry::RequestLogger::new(log_rotation)
.map_err(|e| format!("RequestLogger 初始化失败: {}", e))?,
.map_err(|e| format!("RequestLogger 初始化失败: {e}"))?,
);
let telemetry_state = crate::commands::telemetry_cmd::TelemetryState::with_shared(
@@ -406,7 +405,7 @@ fn init_telemetry(
shared_tokens.clone(),
Some(shared_logger.clone()),
)
.map_err(|e| format!("TelemetryState 初始化失败: {}", e))?;
.map_err(|e| format!("TelemetryState 初始化失败: {e}"))?;
Ok((telemetry_state, shared_stats, shared_tokens, shared_logger))
}
@@ -483,22 +482,22 @@ fn init_flow_monitor(
));
let flow_replayer_state = FlowReplayerState(flow_replayer);
let db_path = database::get_db_path().map_err(|e| format!("获取数据库路径失败: {}", e))?;
let db_path = database::get_db_path().map_err(|e| format!("获取数据库路径失败: {e}"))?;
let session_manager = Arc::new(
SessionManager::new(db_path.clone())
.map_err(|e| format!("SessionManager 初始化失败: {}", e))?,
.map_err(|e| format!("SessionManager 初始化失败: {e}"))?,
);
let session_manager_state = SessionManagerState(session_manager.clone());
let quick_filter_manager = Arc::new(
QuickFilterManager::new(db_path.clone())
.map_err(|e| format!("QuickFilterManager 初始化失败: {}", e))?,
.map_err(|e| format!("QuickFilterManager 初始化失败: {e}"))?,
);
let quick_filter_manager_state = QuickFilterManagerState(quick_filter_manager);
let bookmark_manager = Arc::new(
BookmarkManager::new(db_path).map_err(|e| format!("BookmarkManager 初始化失败: {}", e))?,
BookmarkManager::new(db_path).map_err(|e| format!("BookmarkManager 初始化失败: {e}"))?,
);
let bookmark_manager_state = BookmarkManagerState(bookmark_manager);
@@ -519,7 +518,7 @@ fn init_flow_monitor(
let temp_dir = std::env::temp_dir().join("proxycast_flows");
let _ = std::fs::create_dir_all(&temp_dir);
let temp_store = FlowFileStore::new(temp_dir, rotation_config)
.map_err(|e| format!("临时 FlowFileStore 初始化失败: {}", e))?;
.map_err(|e| format!("临时 FlowFileStore 初始化失败: {e}"))?;
let query_service =
FlowQueryService::new(flow_monitor.memory_store(), Arc::new(temp_store));
FlowQueryServiceState(Arc::new(query_service))
+4 -10
View File
@@ -156,7 +156,7 @@ pub async fn set_endpoint_provider(
.endpoint_providers
.set_provider(&endpoint, provider.clone())
{
return Err(format!("未知的客户端类型: {}", endpoint));
return Err(format!("未知的客户端类型: {endpoint}"));
}
config::save_config(&s.config).map_err(|e| e.to_string())?;
@@ -181,10 +181,7 @@ pub async fn set_endpoint_provider(
let provider_display = provider.as_deref().unwrap_or("默认");
logs.write().await.add(
"info",
&format!(
"客户端 {} 的 Provider 已设置为: {}",
endpoint, provider_display
),
&format!("客户端 {endpoint} 的 Provider 已设置为: {provider_display}"),
);
tracing::info!(
@@ -274,10 +271,7 @@ pub async fn update_provider_env_vars(
// 未知类型,默认使用 ANTHROPIC_BASE_URL(因为大多数第三方 Provider 都是 Anthropic 兼容的)
logs.write().await.add(
"info",
&format!(
"Provider 类型 '{}' 使用默认 ANTHROPIC_BASE_URL",
provider_type
),
&format!("Provider 类型 '{provider_type}' 使用默认 ANTHROPIC_BASE_URL"),
);
let mut vars = vec![("ANTHROPIC_BASE_URL".to_string(), api_host.clone())];
if let Some(key) = api_key {
@@ -324,7 +318,7 @@ pub async fn update_provider_env_vars(
if let Err(e) = write_env_to_shell_config(&env_vars) {
logs.write()
.await
.add("warn", &format!("写入 shell 配置文件失败: {}", e));
.add("warn", &format!("写入 shell 配置文件失败: {e}"));
// 不中断流程
}
+7 -7
View File
@@ -30,7 +30,7 @@ pub fn run() {
Ok(cfg) => cfg,
Err(err) => {
tracing::error!("{}", err);
eprintln!("{}", err);
eprintln!("{err}");
return;
}
};
@@ -40,7 +40,7 @@ pub fn run() {
Ok(s) => s,
Err(err) => {
tracing::error!("应用状态初始化失败: {}", err);
eprintln!("应用状态初始化失败: {}", err);
eprintln!("应用状态初始化失败: {err}");
return;
}
};
@@ -403,7 +403,7 @@ pub fn run() {
Err(e) => {
tracing::error!("[Deep Link] 解析 URL 失败: {:?}", e);
// 发送错误事件到前端
let _ = app_handle_clone.emit("deep-link-error", &format!("{:?}", e));
let _ = app_handle_clone.emit("deep-link-error", &format!("{e:?}"));
}
}
}
@@ -439,7 +439,7 @@ pub fn run() {
}
Err(e) => {
tracing::error!("[Deep Link] 解析 URL 失败: {:?}", e);
let _ = app_handle_clone.emit("deep-link-error", &format!("{:?}", e));
let _ = app_handle_clone.emit("deep-link-error", &format!("{e:?}"));
}
}
}
@@ -485,7 +485,7 @@ pub fn run() {
"claude_oauth" => "Claude OAuth",
_ => &provider_overview.provider_type,
};
loaded_types.push(format!("{} ({} 个)", provider_name, count));
loaded_types.push(format!("{provider_name} ({count} 个)"));
}
}
@@ -503,7 +503,7 @@ pub fn run() {
Err(e) => {
logs.write()
.await
.add("warn", &format!("[启动] 获取凭证池信息失败: {}", e));
.add("warn", &format!("[启动] 获取凭证池信息失败: {e}"));
}
}
@@ -546,7 +546,7 @@ pub fn run() {
.await
.add("info", &format!("[启动] 服务器已启动: {host}:{port}"));
server_started = true;
server_address = format!("{}:{}", host, port);
server_address = format!("{host}:{port}");
}
Err(e) => {
logs.write()
+3 -3
View File
@@ -141,7 +141,7 @@ async fn start_server_async(
"iflow" => "iFlow",
_ => &provider_overview.provider_type,
};
loaded_types.push(format!("{} ({} 个)", provider_name, count));
loaded_types.push(format!("{provider_name} ({count} 个)"));
}
}
@@ -159,7 +159,7 @@ async fn start_server_async(
Err(e) => {
logs.write()
.await
.add("warn", &format!("[启动] 获取凭证池信息失败: {}", e));
.add("warn", &format!("[启动] 获取凭证池信息失败: {e}"));
}
}
@@ -203,7 +203,7 @@ async fn start_server_async(
.await
.add("info", &format!("[启动] 服务器已启动: {host}:{port}"));
server_started = true;
server_address = format!("{}:{}", host, port);
server_address = format!("{host}:{port}");
}
Err(e) => {
logs.write()
+2 -2
View File
@@ -163,10 +163,10 @@ mod tests {
#[test]
fn test_backend_error_display() {
let err = BackendError::new(BackendErrorKind::NetworkError, "connection refused");
assert_eq!(format!("{}", err), "NetworkError: connection refused");
assert_eq!(format!("{err}"), "NetworkError: connection refused");
let err = BackendError::with_status(BackendErrorKind::ServerError, "internal error", 500);
assert_eq!(format!("{}", err), "ServerError (500): internal error");
assert_eq!(format!("{err}"), "ServerError (500): internal error");
}
#[test]
+2 -25
View File
@@ -2,7 +2,7 @@ use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
/// 拦截器状态
#[derive(Debug, Clone, Serialize, Deserialize)]
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct InterceptorState {
pub enabled: bool,
pub active_hooks: Vec<String>,
@@ -11,18 +11,6 @@ pub struct InterceptorState {
pub can_restore: bool, // 是否可以恢复正常状态
}
impl Default for InterceptorState {
fn default() -> Self {
Self {
enabled: false,
active_hooks: Vec::new(),
intercepted_count: 0,
last_activity: None,
can_restore: false,
}
}
}
/// 被拦截的 URL 信息
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct InterceptedUrl {
@@ -50,7 +38,7 @@ impl InterceptedUrl {
}
/// 指纹浏览器配置
#[derive(Debug, Clone, Serialize, Deserialize)]
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct FingerprintBrowserConfig {
pub enabled: bool,
pub executable_path: String,
@@ -58,17 +46,6 @@ pub struct FingerprintBrowserConfig {
pub additional_args: Vec<String>,
}
impl Default for FingerprintBrowserConfig {
fn default() -> Self {
Self {
enabled: false,
executable_path: String::new(),
profile_path: String::new(),
additional_args: Vec::new(),
}
}
}
/// 恢复机制配置
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RecoveryConfig {
@@ -53,7 +53,7 @@ impl BrowserInterceptor {
tracing::info!("验证配置...");
config.validate().map_err(|e| {
tracing::error!("配置验证失败: {}", e);
BrowserInterceptorError::ConfigError(format!("配置验证失败: {}", e))
BrowserInterceptorError::ConfigError(format!("配置验证失败: {e}"))
})?;
drop(config); // 释放读锁
@@ -226,7 +226,7 @@ impl BrowserInterceptor {
// 验证新配置
new_config
.validate()
.map_err(|e| BrowserInterceptorError::ConfigError(format!("配置验证失败: {}", e)))?;
.map_err(|e| BrowserInterceptorError::ConfigError(format!("配置验证失败: {e}")))?;
let mut config = self.config.write().await;
*config = new_config;
@@ -375,16 +375,14 @@ impl BrowserInterceptor {
Ok(mut clipboard) => {
if let Err(e) = clipboard.set_text(text) {
return Err(BrowserInterceptorError::InterceptorError(format!(
"复制到剪贴板失败: {}",
e
"复制到剪贴板失败: {e}"
)));
}
tracing::info!("已复制到剪贴板: {}", text);
Ok(())
}
Err(e) => Err(BrowserInterceptorError::InterceptorError(format!(
"创建剪贴板实例失败: {}",
e
"创建剪贴板实例失败: {e}"
))),
}
}
@@ -446,8 +444,7 @@ impl BrowserInterceptor {
Ok(())
}
Err(e) => Err(BrowserInterceptorError::InterceptorError(format!(
"启动指纹浏览器失败: {}",
e
"启动指纹浏览器失败: {e}"
))),
}
}
+7 -7
View File
@@ -46,14 +46,14 @@ pub enum BrowserInterceptorError {
impl fmt::Display for BrowserInterceptorError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
BrowserInterceptorError::ConfigError(msg) => write!(f, "配置错误: {}", msg),
BrowserInterceptorError::InterceptorError(msg) => write!(f, "拦截器错误: {}", msg),
BrowserInterceptorError::StateError(msg) => write!(f, "状态管理错误: {}", msg),
BrowserInterceptorError::PlatformError(msg) => write!(f, "平台错误: {}", msg),
BrowserInterceptorError::NotificationError(msg) => write!(f, "通知错误: {}", msg),
BrowserInterceptorError::ConfigError(msg) => write!(f, "配置错误: {msg}"),
BrowserInterceptorError::InterceptorError(msg) => write!(f, "拦截器错误: {msg}"),
BrowserInterceptorError::StateError(msg) => write!(f, "状态管理错误: {msg}"),
BrowserInterceptorError::PlatformError(msg) => write!(f, "平台错误: {msg}"),
BrowserInterceptorError::NotificationError(msg) => write!(f, "通知错误: {msg}"),
BrowserInterceptorError::AlreadyRunning => write!(f, "拦截器已在运行"),
BrowserInterceptorError::UnsupportedPlatform(msg) => write!(f, "不支持的平台: {}", msg),
BrowserInterceptorError::IoError(msg) => write!(f, "IO错误: {}", msg),
BrowserInterceptorError::UnsupportedPlatform(msg) => write!(f, "不支持的平台: {msg}"),
BrowserInterceptorError::IoError(msg) => write!(f, "IO错误: {msg}"),
}
}
}
@@ -134,7 +134,7 @@ print("OK")
.args(["-e", swift_code])
.output()
.map_err(|e| {
BrowserInterceptorError::PlatformError(format!("执行 Swift 脚本失败: {}", e))
BrowserInterceptorError::PlatformError(format!("执行 Swift 脚本失败: {e}"))
})?;
if output.status.success() {
@@ -176,12 +176,11 @@ print("OK")
import Foundation
import CoreServices
let bundleId = "{}" as CFString
let bundleId = "{browser_id}" as CFString
LSSetDefaultHandlerForURLScheme("http" as CFString, bundleId)
LSSetDefaultHandlerForURLScheme("https" as CFString, bundleId)
print("OK")
"#,
browser_id
"#
);
let output = Command::new("swift").args(["-e", &swift_code]).output();
@@ -32,7 +32,7 @@ impl StateManager {
pub fn get_state(&self) -> Result<InterceptorState> {
self.state
.read()
.map_err(|e| BrowserInterceptorError::StateError(format!("读取状态失败: {}", e)))
.map_err(|e| BrowserInterceptorError::StateError(format!("读取状态失败: {e}")))
.map(|state| state.clone())
}
@@ -46,7 +46,7 @@ impl StateManager {
let mut state = self
.state
.write()
.map_err(|e| BrowserInterceptorError::StateError(format!("写入状态失败: {}", e)))?;
.map_err(|e| BrowserInterceptorError::StateError(format!("写入状态失败: {e}")))?;
state.enabled = true;
state.can_restore = true;
@@ -63,7 +63,7 @@ impl StateManager {
let mut state = self
.state
.write()
.map_err(|e| BrowserInterceptorError::StateError(format!("写入状态失败: {}", e)))?;
.map_err(|e| BrowserInterceptorError::StateError(format!("写入状态失败: {e}")))?;
state.enabled = false;
state.active_hooks.clear();
@@ -80,9 +80,10 @@ impl StateManager {
// 设置定时器
{
let mut timer = self.temporary_disable_timer.write().map_err(|e| {
BrowserInterceptorError::StateError(format!("设置定时器失败: {}", e))
})?;
let mut timer = self
.temporary_disable_timer
.write()
.map_err(|e| BrowserInterceptorError::StateError(format!("设置定时器失败: {e}")))?;
*timer = Some(Instant::now() + Duration::from_secs(duration_seconds));
}
@@ -113,7 +114,7 @@ impl StateManager {
let mut state = self
.state
.write()
.map_err(|e| BrowserInterceptorError::StateError(format!("写入状态失败: {}", e)))?;
.map_err(|e| BrowserInterceptorError::StateError(format!("写入状态失败: {e}")))?;
state.can_restore = false;
}
@@ -126,7 +127,7 @@ impl StateManager {
let mut state = self
.state
.write()
.map_err(|e| BrowserInterceptorError::StateError(format!("写入状态失败: {}", e)))?;
.map_err(|e| BrowserInterceptorError::StateError(format!("写入状态失败: {e}")))?;
state.intercepted_count += 1;
state.last_activity = Some(Utc::now());
@@ -139,7 +140,7 @@ impl StateManager {
let mut state = self
.state
.write()
.map_err(|e| BrowserInterceptorError::StateError(format!("写入状态失败: {}", e)))?;
.map_err(|e| BrowserInterceptorError::StateError(format!("写入状态失败: {e}")))?;
if !state.active_hooks.contains(&hook_name) {
state.active_hooks.push(hook_name);
@@ -153,7 +154,7 @@ impl StateManager {
let mut state = self
.state
.write()
.map_err(|e| BrowserInterceptorError::StateError(format!("写入状态失败: {}", e)))?;
.map_err(|e| BrowserInterceptorError::StateError(format!("写入状态失败: {e}")))?;
state.active_hooks.retain(|h| h != hook_name);
@@ -171,7 +172,7 @@ impl StateManager {
{
let mut backup = self.original_system_state.write().map_err(|e| {
BrowserInterceptorError::StateError(format!("备份系统状态失败: {}", e))
BrowserInterceptorError::StateError(format!("备份系统状态失败: {e}"))
})?;
*backup = Some(system_state);
}
@@ -184,7 +185,7 @@ impl StateManager {
async fn restore_system_state(&self) -> Result<()> {
let backup = {
let backup_guard = self.original_system_state.read().map_err(|e| {
BrowserInterceptorError::StateError(format!("读取备份状态失败: {}", e))
BrowserInterceptorError::StateError(format!("读取备份状态失败: {e}"))
})?;
backup_guard.clone()
};
@@ -50,7 +50,7 @@ impl UrlManager {
// 添加到当前拦截列表
{
let mut urls = self.intercepted_urls.write().map_err(|e| {
BrowserInterceptorError::StateError(format!("添加拦截 URL 失败: {}", e))
BrowserInterceptorError::StateError(format!("添加拦截 URL 失败: {e}"))
})?;
urls.insert(id.clone(), intercepted_url.clone());
}
@@ -58,7 +58,7 @@ impl UrlManager {
// 添加到历史记录
{
let mut history = self.history.write().map_err(|e| {
BrowserInterceptorError::StateError(format!("添加历史记录失败: {}", e))
BrowserInterceptorError::StateError(format!("添加历史记录失败: {e}"))
})?;
history.push(intercepted_url);
@@ -82,9 +82,10 @@ impl UrlManager {
/// 获取所有当前拦截的 URL
pub fn get_intercepted_urls(&self) -> Result<Vec<InterceptedUrl>> {
let urls = self.intercepted_urls.read().map_err(|e| {
BrowserInterceptorError::StateError(format!("读取拦截 URL 失败: {}", e))
})?;
let urls = self
.intercepted_urls
.read()
.map_err(|e| BrowserInterceptorError::StateError(format!("读取拦截 URL 失败: {e}")))?;
let mut result: Vec<InterceptedUrl> = urls.values().cloned().collect();
result.sort_by(|a, b| b.timestamp.cmp(&a.timestamp)); // 按时间倒序排列
@@ -94,18 +95,20 @@ impl UrlManager {
/// 获取指定 ID 的拦截 URL
pub fn get_intercepted_url(&self, id: &str) -> Result<Option<InterceptedUrl>> {
let urls = self.intercepted_urls.read().map_err(|e| {
BrowserInterceptorError::StateError(format!("读取拦截 URL 失败: {}", e))
})?;
let urls = self
.intercepted_urls
.read()
.map_err(|e| BrowserInterceptorError::StateError(format!("读取拦截 URL 失败: {e}")))?;
Ok(urls.get(id).cloned())
}
/// 标记 URL 为已复制
pub fn mark_as_copied(&self, id: &str) -> Result<()> {
let mut urls = self.intercepted_urls.write().map_err(|e| {
BrowserInterceptorError::StateError(format!("更新拦截 URL 失败: {}", e))
})?;
let mut urls = self
.intercepted_urls
.write()
.map_err(|e| BrowserInterceptorError::StateError(format!("更新拦截 URL 失败: {e}")))?;
if let Some(url) = urls.get_mut(id) {
url.copied = true;
@@ -117,9 +120,10 @@ impl UrlManager {
/// 标记 URL 为已在浏览器中打开
pub fn mark_as_opened(&self, id: &str) -> Result<()> {
let mut urls = self.intercepted_urls.write().map_err(|e| {
BrowserInterceptorError::StateError(format!("更新拦截 URL 失败: {}", e))
})?;
let mut urls = self
.intercepted_urls
.write()
.map_err(|e| BrowserInterceptorError::StateError(format!("更新拦截 URL 失败: {e}")))?;
if let Some(url) = urls.get_mut(id) {
url.opened_in_browser = true;
@@ -131,16 +135,17 @@ impl UrlManager {
/// 忽略(移除)指定的 URL
pub fn dismiss_url(&self, id: &str) -> Result<()> {
let mut urls = self.intercepted_urls.write().map_err(|e| {
BrowserInterceptorError::StateError(format!("移除拦截 URL 失败: {}", e))
})?;
let mut urls = self
.intercepted_urls
.write()
.map_err(|e| BrowserInterceptorError::StateError(format!("移除拦截 URL 失败: {e}")))?;
if let Some(mut url) = urls.remove(id) {
url.dismissed = true;
// 更新历史记录中的状态
let mut history = self.history.write().map_err(|e| {
BrowserInterceptorError::StateError(format!("更新历史记录失败: {}", e))
BrowserInterceptorError::StateError(format!("更新历史记录失败: {e}"))
})?;
if let Some(history_url) = history.iter_mut().find(|u| u.id == id) {
@@ -155,9 +160,10 @@ impl UrlManager {
/// 清除所有当前拦截的 URL
pub fn clear_intercepted_urls(&self) -> Result<()> {
let mut urls = self.intercepted_urls.write().map_err(|e| {
BrowserInterceptorError::StateError(format!("清除拦截 URL 失败: {}", e))
})?;
let mut urls = self
.intercepted_urls
.write()
.map_err(|e| BrowserInterceptorError::StateError(format!("清除拦截 URL 失败: {e}")))?;
let count = urls.len();
urls.clear();
@@ -171,7 +177,7 @@ impl UrlManager {
let history = self
.history
.read()
.map_err(|e| BrowserInterceptorError::StateError(format!("读取历史记录失败: {}", e)))?;
.map_err(|e| BrowserInterceptorError::StateError(format!("读取历史记录失败: {e}")))?;
let mut result = history.clone();
result.sort_by(|a, b| b.timestamp.cmp(&a.timestamp)); // 按时间倒序排列
@@ -188,7 +194,7 @@ impl UrlManager {
let history = self
.history
.read()
.map_err(|e| BrowserInterceptorError::StateError(format!("搜索历史记录失败: {}", e)))?;
.map_err(|e| BrowserInterceptorError::StateError(format!("搜索历史记录失败: {e}")))?;
let query_lower = query.to_lowercase();
let mut result: Vec<InterceptedUrl> = history
@@ -211,14 +217,15 @@ impl UrlManager {
/// 获取统计信息
pub fn get_statistics(&self) -> Result<UrlStatistics> {
let urls = self.intercepted_urls.read().map_err(|e| {
BrowserInterceptorError::StateError(format!("读取拦截 URL 失败: {}", e))
})?;
let urls = self
.intercepted_urls
.read()
.map_err(|e| BrowserInterceptorError::StateError(format!("读取拦截 URL 失败: {e}")))?;
let history = self
.history
.read()
.map_err(|e| BrowserInterceptorError::StateError(format!("读取历史记录失败: {}", e)))?;
.map_err(|e| BrowserInterceptorError::StateError(format!("读取历史记录失败: {e}")))?;
let current_count = urls.len();
let total_intercepted = history.len();
@@ -246,7 +253,7 @@ impl UrlManager {
pub fn save_to_storage(&self) -> Result<()> {
if let Some(storage_path) = &self.storage_path {
let history = self.history.read().map_err(|e| {
BrowserInterceptorError::StateError(format!("读取历史记录失败: {}", e))
BrowserInterceptorError::StateError(format!("读取历史记录失败: {e}"))
})?;
let storage_data = UrlStorageData {
@@ -255,19 +262,18 @@ impl UrlManager {
saved_at: Utc::now(),
};
let json_data = serde_json::to_string_pretty(&storage_data).map_err(|e| {
BrowserInterceptorError::StateError(format!("序列化数据失败: {}", e))
})?;
let json_data = serde_json::to_string_pretty(&storage_data)
.map_err(|e| BrowserInterceptorError::StateError(format!("序列化数据失败: {e}")))?;
// 确保目录存在
if let Some(parent) = Path::new(storage_path).parent() {
fs::create_dir_all(parent).map_err(|e| {
BrowserInterceptorError::StateError(format!("创建目录失败: {}", e))
BrowserInterceptorError::StateError(format!("创建目录失败: {e}"))
})?;
}
fs::write(storage_path, json_data)
.map_err(|e| BrowserInterceptorError::StateError(format!("写入文件失败: {}", e)))?;
.map_err(|e| BrowserInterceptorError::StateError(format!("写入文件失败: {e}")))?;
tracing::info!("已保存历史记录到: {}", storage_path);
}
@@ -280,17 +286,17 @@ impl UrlManager {
if let Some(storage_path) = &self.storage_path {
if Path::new(storage_path).exists() {
let json_data = fs::read_to_string(storage_path).map_err(|e| {
BrowserInterceptorError::StateError(format!("读取文件失败: {}", e))
BrowserInterceptorError::StateError(format!("读取文件失败: {e}"))
})?;
let storage_data: UrlStorageData =
serde_json::from_str(&json_data).map_err(|e| {
BrowserInterceptorError::StateError(format!("反序列化数据失败: {}", e))
BrowserInterceptorError::StateError(format!("反序列化数据失败: {e}"))
})?;
{
let mut history = self.history.write().map_err(|e| {
BrowserInterceptorError::StateError(format!("写入历史记录失败: {}", e))
BrowserInterceptorError::StateError(format!("写入历史记录失败: {e}"))
})?;
*history = storage_data.history;
}
+18 -19
View File
@@ -53,7 +53,7 @@ pub async fn agent_start_process(
agent_state.init_agent_with_db(&db).await?;
let base_url = format!("http://{}:{}", host, port);
let base_url = format!("http://{host}:{port}");
Ok(AgentProcessStatus {
running: true,
@@ -153,7 +153,7 @@ pub async fn agent_create_session(
};
{
let conn = db.lock().map_err(|e| format!("数据库锁定失败: {}", e))?;
let conn = db.lock().map_err(|e| format!("数据库锁定失败: {e}"))?;
if let Err(e) = AgentDao::create_session(&conn, &session) {
tracing::warn!("[Agent] 保存会话到数据库失败: {}", e);
}
@@ -180,10 +180,10 @@ fn build_system_prompt_with_skills(
xml.push_str(" <skill>\n");
xml.push_str(&format!(" <name>{}</name>\n", skill.name));
if let Some(desc) = &skill.description {
xml.push_str(&format!(" <description>{}</description>\n", desc));
xml.push_str(&format!(" <description>{desc}</description>\n"));
}
if let Some(path) = &skill.path {
xml.push_str(&format!(" <location>{}</location>\n", path));
xml.push_str(&format!(" <location>{path}</location>\n"));
}
xml.push_str(" </skill>\n");
}
@@ -196,7 +196,7 @@ fn build_system_prompt_with_skills(
};
match (base_prompt, skills_xml) {
(Some(base), Some(skills)) => Some(format!("{}\n\n{}", base, skills)),
(Some(base), Some(skills)) => Some(format!("{base}\n\n{skills}")),
(Some(base), None) => Some(base),
(None, Some(skills)) => Some(skills),
(None, None) => None,
@@ -242,10 +242,9 @@ pub struct SessionInfo {
/// 获取会话列表
#[tauri::command]
pub async fn agent_list_sessions(db: State<'_, DbConnection>) -> Result<Vec<SessionInfo>, String> {
let conn = db.lock().map_err(|e| format!("数据库锁定失败: {}", e))?;
let conn = db.lock().map_err(|e| format!("数据库锁定失败: {e}"))?;
let sessions =
AgentDao::list_sessions(&conn).map_err(|e| format!("获取会话列表失败: {}", e))?;
let sessions = AgentDao::list_sessions(&conn).map_err(|e| format!("获取会话列表失败: {e}"))?;
let result: Vec<SessionInfo> = sessions
.into_iter()
@@ -272,10 +271,10 @@ pub async fn agent_get_session(
db: State<'_, DbConnection>,
session_id: String,
) -> Result<SessionInfo, String> {
let conn = db.lock().map_err(|e| format!("数据库锁定失败: {}", e))?;
let conn = db.lock().map_err(|e| format!("数据库锁定失败: {e}"))?;
let session = AgentDao::get_session(&conn, &session_id)
.map_err(|e| format!("获取会话失败: {}", e))?
.map_err(|e| format!("获取会话失败: {e}"))?
.ok_or_else(|| "会话不存在".to_string())?;
let messages_count = AgentDao::get_message_count(&conn, &session_id).unwrap_or(0);
@@ -297,8 +296,8 @@ pub async fn agent_delete_session(
db: State<'_, DbConnection>,
session_id: String,
) -> Result<(), String> {
let conn = db.lock().map_err(|e| format!("数据库锁定失败: {}", e))?;
AgentDao::delete_session(&conn, &session_id).map_err(|e| format!("删除会话失败: {}", e))?;
let conn = db.lock().map_err(|e| format!("数据库锁定失败: {e}"))?;
AgentDao::delete_session(&conn, &session_id).map_err(|e| format!("删除会话失败: {e}"))?;
Ok(())
}
@@ -308,9 +307,9 @@ pub async fn agent_get_session_messages(
db: State<'_, DbConnection>,
session_id: String,
) -> Result<Vec<AgentMessage>, String> {
let conn = db.lock().map_err(|e| format!("数据库锁定失败: {}", e))?;
let conn = db.lock().map_err(|e| format!("数据库锁定失败: {e}"))?;
let messages =
AgentDao::get_messages(&conn, &session_id).map_err(|e| format!("获取消息失败: {}", e))?;
AgentDao::get_messages(&conn, &session_id).map_err(|e| format!("获取消息失败: {e}"))?;
Ok(messages)
}
@@ -321,9 +320,9 @@ pub async fn agent_rename_session(
session_id: String,
title: String,
) -> Result<(), String> {
let conn = db.lock().map_err(|e| format!("数据库锁定失败: {}", e))?;
let conn = db.lock().map_err(|e| format!("数据库锁定失败: {e}"))?;
AgentDao::update_title(&conn, &session_id, &title)
.map_err(|e| format!("更新会话标题失败: {}", e))?;
.map_err(|e| format!("更新会话标题失败: {e}"))?;
Ok(())
}
@@ -335,11 +334,11 @@ pub async fn agent_generate_title(
db: State<'_, DbConnection>,
session_id: String,
) -> Result<String, String> {
let conn = db.lock().map_err(|e| format!("数据库锁定失败: {}", e))?;
let conn = db.lock().map_err(|e| format!("数据库锁定失败: {e}"))?;
// 获取会话的前几条消息(用于生成标题)
let messages =
AgentDao::get_messages(&conn, &session_id).map_err(|e| format!("获取消息失败: {}", e))?;
AgentDao::get_messages(&conn, &session_id).map_err(|e| format!("获取消息失败: {e}"))?;
// 过滤出 user 和 assistant 消息
let chat_messages: Vec<_> = messages
@@ -366,7 +365,7 @@ pub async fn agent_generate_title(
} else {
content
};
conversation.push_str(&format!("{}:{}\n", role, truncated_content));
conversation.push_str(&format!("{role}:{truncated_content}\n"));
}
// 使用 AI 生成标题(通过 aster_agent_chat_stream 生成)
@@ -120,7 +120,7 @@ fn mask_api_key(key: &str) -> String {
} else {
let prefix: String = chars[..6].iter().collect();
let suffix: String = chars[chars.len() - 4..].iter().collect();
format!("{}****{}", prefix, suffix)
format!("{prefix}****{suffix}")
}
}
@@ -222,7 +222,7 @@ pub fn add_custom_api_key_provider(
let provider_type: ApiProviderType = request
.provider_type
.parse()
.map_err(|e: String| format!("无效的 Provider 类型: {}", e))?;
.map_err(|e: String| format!("无效的 Provider 类型: {e}"))?;
let provider = service.0.add_custom_provider(
&db,
@@ -251,7 +251,7 @@ pub fn update_api_key_provider(
.provider_type
.map(|t| t.parse())
.transpose()
.map_err(|e: String| format!("无效的 Provider 类型: {}", e))?;
.map_err(|e: String| format!("无效的 Provider 类型: {e}"))?;
let provider = service.0.update_provider(
&db,
@@ -403,7 +403,7 @@ pub fn export_api_key_providers(
include_keys: bool,
) -> Result<String, String> {
let config = service.0.export_config(&db, include_keys)?;
serde_json::to_string_pretty(&config).map_err(|e| format!("序列化失败: {}", e))
serde_json::to_string_pretty(&config).map_err(|e| format!("序列化失败: {e}"))
}
/// 导入 Provider 配置
+3 -3
View File
@@ -120,7 +120,7 @@ pub async fn delete_asr_credential(id: String) -> Result<(), String> {
.asr
.iter()
.position(|c| c.id == id)
.ok_or_else(|| format!("凭证不存在: {}", id))?;
.ok_or_else(|| format!("凭证不存在: {id}"))?;
let was_default = config.credential_pool.asr[idx].is_default;
config.credential_pool.asr.remove(idx);
@@ -143,7 +143,7 @@ pub async fn set_default_asr_credential(id: String) -> Result<(), String> {
// 检查凭证是否存在
let exists = config.credential_pool.asr.iter().any(|c| c.id == id);
if !exists {
return Err(format!("凭证不存在: {}", id));
return Err(format!("凭证不存在: {id}"));
}
// 更新默认状态
@@ -166,7 +166,7 @@ pub async fn test_asr_credential(id: String) -> Result<TestResult, String> {
.asr
.iter()
.find(|c| c.id == id)
.ok_or_else(|| format!("凭证不存在: {}", id))?;
.ok_or_else(|| format!("凭证不存在: {id}"))?;
// 根据 Provider 类型测试
match credential.provider {
+59 -58
View File
@@ -12,42 +12,10 @@ use crate::agent::{
use crate::database::dao::agent::AgentDao;
use crate::database::DbConnection;
use aster::conversation::message::Message;
use aster::session::SessionManager;
use futures::StreamExt;
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
use tauri::{AppHandle, Emitter, State};
/// 确保 session 在 Aster 数据库中存在
/// 如果不存在则创建新的 session
async fn ensure_session_exists(session_id: &str) -> Result<String, String> {
// 尝试获取现有 session
match SessionManager::get_session(session_id, false).await {
Ok(_) => {
tracing::debug!("[AsterAgent] Session 已存在: {}", session_id);
Ok(session_id.to_string())
}
Err(_) => {
// Session 不存在,创建新的
tracing::info!(
"[AsterAgent] Session 不存在,创建新 session: {}",
session_id
);
let working_dir = std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."));
let session = SessionManager::create_session(
working_dir,
"New Chat".to_string(),
aster::session::SessionType::User,
)
.await
.map_err(|e| format!("创建 session 失败: {}", e))?;
tracing::info!("[AsterAgent] 创建新 session: {}", session.id);
Ok(session.id)
}
}
}
/// Aster Agent 状态信息
#[derive(Debug, Serialize)]
pub struct AsterAgentStatus {
@@ -224,23 +192,54 @@ pub async fn aster_agent_chat_stream(
);
// 确保 Agent 已初始化(使用带数据库的版本,注入 SessionStore)
if !state.is_initialized().await {
let is_init = state.is_initialized().await;
tracing::warn!("[AsterAgent] Agent 初始化状态: {}", is_init);
if !is_init {
tracing::warn!("[AsterAgent] Agent 未初始化,开始初始化...");
state.init_agent_with_db(&db).await?;
tracing::warn!("[AsterAgent] Agent 初始化完成");
} else {
tracing::warn!("[AsterAgent] Agent 已初始化,检查 session_store...");
// 检查 session_store 是否存在
let agent_arc = state.get_agent_arc();
let guard = agent_arc.read().await;
if let Some(agent) = guard.as_ref() {
let has_store = agent.session_store().is_some();
tracing::warn!("[AsterAgent] session_store 存在: {}", has_store);
}
}
// 确保 session 在数据库中存在
// 如果 session 不存在,自动创建
let session_id = ensure_session_exists(&request.session_id).await?;
// 直接使用前端传递的 session_id
// ProxyCastSessionStore 会在 add_message 时自动创建不存在的 session
// 同时 get_session 也会自动创建不存在的 session
let session_id = &request.session_id;
// 从数据库读取 session 的 system_prompt
// 从 ProxyCast 数据库读取 session 的 system_prompt(如果存在)
let system_prompt = {
let db_conn = db
.lock()
.map_err(|e| format!("获取数据库连接失败: {}", e))?;
let session = AgentDao::get_session(&db_conn, &session_id)
.map_err(|e| format!("获取 session 失败: {}", e))?
.ok_or_else(|| format!("Session 不存在: {}", session_id))?;
session.system_prompt
let db_conn = db.lock().map_err(|e| format!("获取数据库连接失败: {e}"))?;
match AgentDao::get_session(&db_conn, session_id) {
Ok(Some(session)) => {
tracing::debug!(
"[AsterAgent] 找到 session,system_prompt: {:?}",
session.system_prompt.as_ref().map(|s| s.len())
);
session.system_prompt
}
Ok(None) => {
tracing::debug!(
"[AsterAgent] ProxyCast 数据库中未找到 session: {}",
session_id
);
None
}
Err(e) => {
tracing::warn!(
"[AsterAgent] 读取 session 失败: {}, 继续使用空 system_prompt",
e
);
None
}
}
};
// 如果提供了 Provider 配置,则配置 Provider
@@ -252,7 +251,7 @@ pub async fn aster_agent_chat_stream(
base_url: provider_config.base_url.clone(),
credential_uuid: None,
};
state.configure_provider(config, &session_id, &db).await?;
state.configure_provider(config, session_id, &db).await?;
}
// 检查 Provider 是否已配置
@@ -261,13 +260,13 @@ pub async fn aster_agent_chat_stream(
}
// 创建取消令牌
let cancel_token = state.create_cancel_token(&session_id).await;
let cancel_token = state.create_cancel_token(session_id).await;
// 创建用户消息
let user_message = Message::user().with_text(&request.message);
// 创建会话配置,包含 system_prompt
let mut session_config_builder = SessionConfigBuilder::new(&session_id);
let mut session_config_builder = SessionConfigBuilder::new(session_id);
if let Some(prompt) = system_prompt {
session_config_builder = session_config_builder.system_prompt(prompt);
}
@@ -302,7 +301,7 @@ pub async fn aster_agent_chat_stream(
Err(e) => {
// 发送错误事件
let error_event = TauriAgentEvent::Error {
message: format!("Stream error: {}", e),
message: format!("Stream error: {e}"),
};
if let Err(emit_err) = app.emit(&request.event_name, &error_event) {
tracing::error!("[AsterAgent] 发送错误事件失败: {}", emit_err);
@@ -320,19 +319,19 @@ pub async fn aster_agent_chat_stream(
Err(e) => {
// 发送错误事件
let error_event = TauriAgentEvent::Error {
message: format!("Agent error: {}", e),
message: format!("Agent error: {e}"),
};
if let Err(emit_err) = app.emit(&request.event_name, &error_event) {
tracing::error!("[AsterAgent] 发送错误事件失败: {}", emit_err);
}
return Err(format!("Agent error: {}", e));
return Err(format!("Agent error: {e}"));
}
}
// guard 会在函数结束时自动释放(stream_result 先释放)
// 清理取消令牌
state.remove_cancel_token(&session_id).await;
state.remove_cancel_token(session_id).await;
Ok(())
}
@@ -350,26 +349,28 @@ pub async fn aster_agent_stop(
/// 创建新会话
#[tauri::command]
pub async fn aster_session_create(
working_dir: Option<String>,
db: State<'_, DbConnection>,
name: Option<String>,
) -> Result<String, String> {
tracing::info!("[AsterAgent] 创建会话: name={:?}", name);
let dir = working_dir.map(PathBuf::from);
AsterAgentWrapper::create_session(dir, name).await
AsterAgentWrapper::create_session_sync(&db, name)
}
/// 列出所有会话
#[tauri::command]
pub async fn aster_session_list() -> Result<Vec<SessionInfo>, String> {
pub async fn aster_session_list(db: State<'_, DbConnection>) -> Result<Vec<SessionInfo>, String> {
tracing::info!("[AsterAgent] 列出会话");
AsterAgentWrapper::list_sessions().await
AsterAgentWrapper::list_sessions_sync(&db)
}
/// 获取会话详情
#[tauri::command]
pub async fn aster_session_get(session_id: String) -> Result<SessionDetail, String> {
pub async fn aster_session_get(
db: State<'_, DbConnection>,
session_id: String,
) -> Result<SessionDetail, String> {
tracing::info!("[AsterAgent] 获取会话: {}", session_id);
AsterAgentWrapper::get_session(&session_id).await
AsterAgentWrapper::get_session_sync(&db, &session_id)
}
/// 确认权限请求
+9 -11
View File
@@ -36,14 +36,12 @@ pub async fn auto_fix_configuration(
// 检查默认Provider配置
if let Err(e) = fix_default_provider_issue(&state, &logs, &db, &mut result).await {
result
.warnings
.push(format!("修复默认Provider时出错: {}", e));
result.warnings.push(format!("修复默认Provider时出错: {e}"));
}
// 检查凭证池状态
if let Err(e) = check_credential_pool_issues(&db, &mut result).await {
result.warnings.push(format!("检查凭证池时出错: {}", e));
result.warnings.push(format!("检查凭证池时出错: {e}"));
}
logs.write().await.add(
@@ -84,7 +82,7 @@ async fn fix_default_provider_issue(
if let Err(e) = set_default_provider_internal(state, logs, "kiro".to_string()).await {
result
.warnings
.push(format!("无法自动修复默认Provider: {}", e));
.push(format!("无法自动修复默认Provider: {e}"));
} else {
result
.fixes_applied
@@ -98,7 +96,7 @@ async fn fix_default_provider_issue(
else if !is_provider_available(&current_default, &credential_stats) {
result
.issues_found
.push(format!("默认Provider '{}' 没有可用凭证", current_default));
.push(format!("默认Provider '{current_default}' 没有可用凭证"));
// 寻找最佳替代Provider
if let Some(best_provider) = find_best_available_provider(&credential_stats) {
@@ -106,14 +104,14 @@ async fn fix_default_provider_issue(
{
result
.warnings
.push(format!("无法自动修复默认Provider: {}", e));
.push(format!("无法自动修复默认Provider: {e}"));
} else {
result
.fixes_applied
.push(format!("默认Provider已自动设置为 '{}'", best_provider));
.push(format!("默认Provider已自动设置为 '{best_provider}'"));
logs.write().await.add(
"info",
&format!("[自动修复] 默认Provider已设置为{}", best_provider),
&format!("[自动修复] 默认Provider已设置为{best_provider}"),
);
}
} else {
@@ -249,7 +247,7 @@ async fn check_credential_pool_issues(
if expired_tokens > 0 {
result
.issues_found
.push(format!("发现 {} 个过期的token缓存", expired_tokens));
.push(format!("发现 {expired_tokens} 个过期的token缓存"));
// 过期token会在使用时自动刷新,这里只是报告
}
@@ -258,7 +256,7 @@ async fn check_credential_pool_issues(
if disabled_count > 0 {
result
.issues_found
.push(format!("有 {} 个凭证被禁用", disabled_count));
.push(format!("有 {disabled_count} 个凭证被禁用"));
}
Ok(())
@@ -78,7 +78,7 @@ pub async fn temporary_disable_interceptor(duration_seconds: u64) -> Result<Stri
int.temporary_disable(duration_seconds)
.await
.map_err(|e| e.to_string())?;
Ok(format!("拦截器已临时禁用 {} 秒", duration_seconds))
Ok(format!("拦截器已临时禁用 {duration_seconds} 秒"))
} else {
Err("拦截器未运行".to_string())
}
@@ -177,7 +177,7 @@ pub async fn get_default_browser_interceptor_config() -> Result<BrowserIntercept
pub async fn validate_browser_interceptor_config(
config: BrowserInterceptorConfig,
) -> Result<String, String> {
config.validate().map_err(|e| e)?;
config.validate()?;
Ok("配置验证通过".to_string())
}
+20 -22
View File
@@ -242,7 +242,7 @@ pub fn export_config(config: Config, redact_secrets: bool) -> Result<ExportResul
// 生成带时间戳的文件名
let timestamp = chrono::Local::now().format("%Y%m%d_%H%M%S");
let suffix = if redact_secrets { "_redacted" } else { "" };
let suggested_filename = format!("proxycast_config_{}{}.yaml", timestamp, suffix);
let suggested_filename = format!("proxycast_config_{timestamp}{suffix}.yaml");
Ok(ExportResult {
content,
@@ -435,7 +435,7 @@ pub fn export_bundle(
(false, true) => "credentials",
(false, false) => "empty",
};
let suggested_filename = format!("proxycast_{}_{}{}.json", scope, timestamp, suffix);
let suggested_filename = format!("proxycast_{scope}_{timestamp}{suffix}.json");
Ok(UnifiedExportResult {
content,
@@ -460,7 +460,7 @@ pub fn export_config_yaml(config: Config, redact_secrets: bool) -> Result<Export
// 生成带时间戳的文件名
let timestamp = chrono::Local::now().format("%Y%m%d_%H%M%S");
let suffix = if redact_secrets { "_redacted" } else { "" };
let suggested_filename = format!("proxycast_config_{}{}.yaml", timestamp, suffix);
let suggested_filename = format!("proxycast_config_{timestamp}{suffix}.yaml");
Ok(ExportResult {
content,
@@ -639,7 +639,7 @@ pub async fn check_for_updates() -> Result<VersionCheckResult, String> {
latest: None,
has_update: false,
download_url: None,
error: Some(format!("解析响应失败: {}", e)),
error: Some(format!("解析响应失败: {e}")),
}),
}
} else {
@@ -657,7 +657,7 @@ pub async fn check_for_updates() -> Result<VersionCheckResult, String> {
latest: None,
has_update: false,
download_url: None,
error: Some(format!("网络请求失败: {}", e)),
error: Some(format!("网络请求失败: {e}")),
}),
}
}
@@ -837,27 +837,27 @@ pub async fn download_update(app_handle: AppHandle) -> Result<DownloadResult, St
Ok(DownloadResult {
success: true,
message: format!("下载完成: {}", filename),
message: format!("下载完成: {filename}"),
file_path: Some(file_path.to_string_lossy().to_string()),
})
}
Err(e) => Ok(DownloadResult {
success: false,
message: format!("保存文件失败: {}", e),
message: format!("保存文件失败: {e}"),
file_path: None,
}),
}
}
Err(e) => Ok(DownloadResult {
success: false,
message: format!("读取下载内容失败: {}", e),
message: format!("读取下载内容失败: {e}"),
file_path: None,
}),
}
}
Err(e) => Ok(DownloadResult {
success: false,
message: format!("网络请求失败: {}", e),
message: format!("网络请求失败: {e}"),
file_path: None,
}),
}
@@ -865,10 +865,8 @@ pub async fn download_update(app_handle: AppHandle) -> Result<DownloadResult, St
/// 从 GitHub API 获取实际的文件列表并匹配平台
async fn get_platform_download_from_github(version: &str) -> Result<(String, String), String> {
let api_url = format!(
"https://api.github.com/repos/aiclientproxy/proxycast/releases/tags/v{}",
version
);
let api_url =
format!("https://api.github.com/repos/aiclientproxy/proxycast/releases/tags/v{version}");
let client = reqwest::Client::new();
let response = client
@@ -876,7 +874,7 @@ async fn get_platform_download_from_github(version: &str) -> Result<(String, Str
.header("User-Agent", "ProxyCast")
.send()
.await
.map_err(|e| format!("请求 GitHub API 失败: {}", e))?;
.map_err(|e| format!("请求 GitHub API 失败: {e}"))?;
if !response.status().is_success() {
return Err(format!("GitHub API 请求失败: {}", response.status()));
@@ -885,7 +883,7 @@ async fn get_platform_download_from_github(version: &str) -> Result<(String, Str
let data: serde_json::Value = response
.json()
.await
.map_err(|e| format!("解析 GitHub API 响应失败: {}", e))?;
.map_err(|e| format!("解析 GitHub API 响应失败: {e}"))?;
let assets = data["assets"]
.as_array()
@@ -970,12 +968,12 @@ fn get_download_directory(app_handle: &AppHandle) -> Result<PathBuf, String> {
let app_data_dir = app_handle
.path()
.app_data_dir()
.map_err(|e| format!("无法获取应用数据目录: {}", e))?;
.map_err(|e| format!("无法获取应用数据目录: {e}"))?;
let download_dir = app_data_dir.join("downloads");
// 确保目录存在
std::fs::create_dir_all(&download_dir).map_err(|e| format!("创建下载目录失败: {}", e))?;
std::fs::create_dir_all(&download_dir).map_err(|e| format!("创建下载目录失败: {e}"))?;
Ok(download_dir)
}
@@ -1007,9 +1005,9 @@ fn run_installer(file_path: &PathBuf) -> Result<(), String> {
{
tracing::info!("macOS: 打开 DMG 文件: {:?}", file_path);
std::process::Command::new("open")
.arg(&file_path)
.arg(file_path)
.spawn()
.map_err(|e| format!("打开 macOS DMG 文件失败: {}", e))?;
.map_err(|e| format!("打开 macOS DMG 文件失败: {e}"))?;
}
#[cfg(not(target_os = "macos"))]
@@ -1060,7 +1058,7 @@ fn run_installer(file_path: &PathBuf) -> Result<(), String> {
}
}
_ => {
return Err(format!("不支持的文件类型: {}", extension));
return Err(format!("不支持的文件类型: {extension}"));
}
}
@@ -1083,9 +1081,9 @@ fn open_file_location(file_path: &PathBuf) -> Result<(), String> {
{
tracing::info!("macOS: 使用 open -R 打开文件位置: {:?}", file_path);
std::process::Command::new("open")
.args(&["-R", &file_path.to_string_lossy()])
.args(["-R", &file_path.to_string_lossy()])
.spawn()
.map_err(|e| format!("macOS open 命令失败: {}", e))?;
.map_err(|e| format!("macOS open 命令失败: {e}"))?;
}
#[cfg(target_os = "linux")]
+6 -6
View File
@@ -96,7 +96,7 @@ pub async fn init_connect_state(app_data_dir: PathBuf) -> Result<ConnectState, C
let registry = Arc::new(RelayRegistry::new(cache_path));
// 尝试从缓存加载,如果失败则从远程加载
if let Err(_) = registry.load_from_cache() {
if registry.load_from_cache().is_err() {
tracing::info!("[Connect] 缓存不存在,尝试从远程加载注册表");
if let Err(e) = registry.load_from_remote().await {
tracing::warn!("[Connect] 从远程加载注册表失败: {}", e);
@@ -209,7 +209,7 @@ pub async fn save_relay_api_key(
.get(&relay_id)
.ok_or_else(|| ConnectError {
code: "RELAY_NOT_FOUND".to_string(),
message: format!("中转商 {} 不在注册表中", relay_id),
message: format!("中转商 {relay_id} 不在注册表中"),
})?;
let protocol = relay_info.api.protocol.to_lowercase();
@@ -223,7 +223,7 @@ pub async fn save_relay_api_key(
};
// 生成 Provider ID(使用 connect- 前缀 + 中转商 ID)
let provider_id = format!("connect-{}", relay_id);
let provider_id = format!("connect-{relay_id}");
// 检查 Provider 是否已存在
let existing_provider = api_key_service
@@ -231,7 +231,7 @@ pub async fn save_relay_api_key(
.get_provider(&db, &provider_id)
.map_err(|e| ConnectError {
code: "GET_PROVIDER_FAILED".to_string(),
message: format!("查询 Provider 失败: {}", e),
message: format!("查询 Provider 失败: {e}"),
})?;
let (final_provider_id, is_new_provider) = if existing_provider.is_some() {
@@ -256,7 +256,7 @@ pub async fn save_relay_api_key(
)
.map_err(|e| ConnectError {
code: "CREATE_PROVIDER_FAILED".to_string(),
message: format!("创建 Provider 失败: {}", e),
message: format!("创建 Provider 失败: {e}"),
})?;
tracing::info!(
@@ -275,7 +275,7 @@ pub async fn save_relay_api_key(
.add_api_key(&db, &final_provider_id, &api_key, key_alias.clone())
.map_err(|e| ConnectError {
code: "ADD_API_KEY_FAILED".to_string(),
message: format!("添加 API Key 失败: {}", e),
message: format!("添加 API Key 失败: {e}"),
})?;
tracing::info!(
+6 -6
View File
@@ -168,7 +168,7 @@ pub fn connection_delete(name: String) -> ConnectionResponse {
// 检查连接是否存在
if !config.connections.contains_key(&name) {
return ConnectionResponse::err(format!("连接 '{}' 不存在", name));
return ConnectionResponse::err(format!("连接 '{name}' 不存在"));
}
// 删除并保存
@@ -234,7 +234,7 @@ pub async fn connection_test(name: String) -> ConnectionResponse {
// 获取连接
let conn = match config.get(&name) {
Some(c) => c,
None => return ConnectionResponse::err(format!("连接 '{}' 不存在", name)),
None => return ConnectionResponse::err(format!("连接 '{name}' 不存在")),
};
// 根据连接类型测试
@@ -253,14 +253,14 @@ pub async fn connection_test(name: String) -> ConnectionResponse {
let port = conn.port.unwrap_or(22);
// 简单的 TCP 连接测试
match tokio::net::TcpStream::connect(format!("{}:{}", host, port)).await {
match tokio::net::TcpStream::connect(format!("{host}:{port}")).await {
Ok(_) => {
tracing::info!("[Connection] SSH 连接测试成功: {}:{}", host, port);
ConnectionResponse::ok()
}
Err(e) => {
tracing::warn!("[Connection] SSH 连接测试失败: {} - {}", host, e);
ConnectionResponse::err(format!("连接失败: {}", e))
ConnectionResponse::err(format!("连接失败: {e}"))
}
}
}
@@ -303,7 +303,7 @@ pub fn connection_import_ssh_host(host_name: String) -> ConnectionResponse {
// 查找指定的 host
let ssh_host = match ssh_hosts.into_iter().find(|h| h.pattern == host_name) {
Some(h) => h,
None => return ConnectionResponse::err(format!("SSH Host '{}' 不存在", host_name)),
None => return ConnectionResponse::err(format!("SSH Host '{host_name}' 不存在")),
};
// 加载用户配置
@@ -314,7 +314,7 @@ pub fn connection_import_ssh_host(host_name: String) -> ConnectionResponse {
// 检查是否已存在
if config.connections.contains_key(&host_name) {
return ConnectionResponse::err(format!("连接 '{}' 已存在", host_name));
return ConnectionResponse::err(format!("连接 '{host_name}' 已存在"));
}
// 创建连接配置
+64 -66
View File
@@ -206,7 +206,7 @@ pub async fn query_flows(
request.page_size,
)
.await
.map_err(|e| format!("查询 Flow 失败: {}", e))
.map_err(|e| format!("查询 Flow 失败: {e}"))
}
/// 获取单个 Flow 详情
@@ -230,7 +230,7 @@ pub async fn get_flow_detail(
.0
.get_flow(&flow_id)
.await
.map_err(|e| format!("获取 Flow 详情失败: {}", e))
.map_err(|e| format!("获取 Flow 详情失败: {e}"))
}
/// 全文搜索 Flow
@@ -253,7 +253,7 @@ pub async fn search_flows(
.0
.search(&request.query, request.limit)
.await
.map_err(|e| format!("搜索 Flow 失败: {}", e))
.map_err(|e| format!("搜索 Flow 失败: {e}"))
}
/// 获取 Flow 统计信息
@@ -309,7 +309,7 @@ pub async fn export_flows(
.0
.query(filter, FlowSortBy::CreatedAt, true, 1, 10000)
.await
.map_err(|e| format!("查询 Flow 失败: {}", e))?;
.map_err(|e| format!("查询 Flow 失败: {e}"))?;
result.flows
};
@@ -331,11 +331,11 @@ pub async fn export_flows(
let data = match request.format {
ExportFormat::HAR => {
let har = exporter.export_har(&flows);
serde_json::to_string_pretty(&har).map_err(|e| format!("序列化 HAR 失败: {}", e))?
serde_json::to_string_pretty(&har).map_err(|e| format!("序列化 HAR 失败: {e}"))?
}
ExportFormat::JSON => {
let json = exporter.export_json(&flows);
serde_json::to_string_pretty(&json).map_err(|e| format!("序列化 JSON 失败: {}", e))?
serde_json::to_string_pretty(&json).map_err(|e| format!("序列化 JSON 失败: {e}"))?
}
ExportFormat::JSONL => exporter.export_jsonl(&flows),
ExportFormat::Markdown => exporter.export_markdown_multiple(&flows),
@@ -516,7 +516,7 @@ pub async fn cleanup_flows(
}
Err(e) => {
tracing::error!("清理所有数据失败: {}", e);
return Err(format!("清理所有数据失败: {}", e));
return Err(format!("清理所有数据失败: {e}"));
}
}
}
@@ -550,7 +550,7 @@ pub async fn cleanup_flows(
}
Err(e) => {
tracing::error!("按时间清理失败: {}", e);
return Err(format!("按时间清理失败: {}", e));
return Err(format!("按时间清理失败: {e}"));
}
}
}
@@ -576,7 +576,7 @@ pub async fn cleanup_flows(
}
Err(e) => {
tracing::error!("按数量清理失败: {}", e);
return Err(format!("按数量清理失败: {}", e));
return Err(format!("按数量清理失败: {e}"));
}
}
}
@@ -767,14 +767,14 @@ pub async fn create_test_flows(
}),
messages: vec![Message {
role: MessageRole::User,
content: crate::flow_monitor::MessageContent::Text(format!("测试消息 {}", i)),
content: crate::flow_monitor::MessageContent::Text(format!("测试消息 {i}")),
tool_calls: None,
tool_result: None,
name: None,
}],
system_prompt: None,
tools: None,
model: format!("gpt-4-test-{}", i),
model: format!("gpt-4-test-{i}"),
original_model: None,
parameters: RequestParameters {
temperature: Some(0.7),
@@ -792,13 +792,13 @@ pub async fn create_test_flows(
let metadata = FlowMetadata {
provider: ProviderType::OpenAI,
provider_id: Some("openai".to_string()),
credential_id: Some(format!("test-cred-{}", i)),
credential_name: Some(format!("测试凭证 {}", i)),
credential_id: Some(format!("test-cred-{i}")),
credential_name: Some(format!("测试凭证 {i}")),
retry_count: 0,
client_info: ClientInfo {
ip: Some("127.0.0.1".to_string()),
user_agent: Some("test-agent".to_string()),
request_id: Some(format!("test-req-{}", i)),
request_id: Some(format!("test-req-{i}")),
},
routing_info: RoutingInfo {
target_url: Some("https://api.openai.com".to_string()),
@@ -819,7 +819,7 @@ pub async fn create_test_flows(
body: serde_json::json!({
"choices": [{"message": {"role": "assistant", "content": format!("测试响应 {}", i)}}]
}),
content: format!("测试响应 {}", i),
content: format!("测试响应 {i}"),
thinking: None,
tool_calls: Vec::new(),
usage: crate::flow_monitor::TokenUsage {
@@ -1120,7 +1120,7 @@ pub async fn query_flows_with_expression(
request.page_size,
)
.await
.map_err(|e| format!("查询 Flow 失败: {}", e))
.map_err(|e| format!("查询 Flow 失败: {e}"))
}
// ============================================================================
@@ -1174,7 +1174,7 @@ pub async fn intercept_config_set(
.0
.update_config(config)
.await
.map_err(|e| format!("设置拦截器配置失败: {}", e))
.map_err(|e| format!("设置拦截器配置失败: {e}"))
}
/// 继续处理被拦截的 Flow
@@ -1200,17 +1200,15 @@ pub async fn intercept_continue(
// 确定修改数据
let modified = if let Some(req) = modified_request {
Some(ModifiedData::Request(req))
} else if let Some(resp) = modified_response {
Some(ModifiedData::Response(resp))
} else {
None
modified_response.map(ModifiedData::Response)
};
interceptor
.0
.continue_flow(&flow_id, modified)
.await
.map_err(|e| format!("继续处理 Flow 失败: {}", e))
.map_err(|e| format!("继续处理 Flow 失败: {e}"))
}
/// 取消被拦截的 Flow
@@ -1233,7 +1231,7 @@ pub async fn intercept_cancel(
.0
.cancel_flow(&flow_id)
.await
.map_err(|e| format!("取消 Flow 失败: {}", e))
.map_err(|e| format!("取消 Flow 失败: {e}"))
}
/// 获取被拦截的 Flow 详情
@@ -1354,7 +1352,7 @@ pub async fn intercept_set_editing(
.0
.set_editing(&flow_id)
.await
.map_err(|e| format!("设置编辑状态失败: {}", e))
.map_err(|e| format!("设置编辑状态失败: {e}"))
}
/// 订阅拦截事件
@@ -1446,7 +1444,7 @@ pub async fn replay_flow(
.0
.replay(&request.flow_id, request.config)
.await
.map_err(|e| format!("重放 Flow 失败: {}", e))
.map_err(|e| format!("重放 Flow 失败: {e}"))
}
/// 批量重放多个 Flow
@@ -1508,7 +1506,7 @@ pub async fn diff_flows(
.0
.get_flow(&request.left_flow_id)
.await
.map_err(|e| format!("获取左侧 Flow 失败: {}", e))?
.map_err(|e| format!("获取左侧 Flow 失败: {e}"))?
.ok_or_else(|| format!("左侧 Flow 不存在: {}", request.left_flow_id))?;
// 获取右侧 Flow
@@ -1516,7 +1514,7 @@ pub async fn diff_flows(
.0
.get_flow(&request.right_flow_id)
.await
.map_err(|e| format!("获取右侧 Flow 失败: {}", e))?
.map_err(|e| format!("获取右侧 Flow 失败: {e}"))?
.ok_or_else(|| format!("右侧 Flow 不存在: {}", request.right_flow_id))?;
// 执行差异对比
@@ -1685,7 +1683,7 @@ pub async fn create_session(
session_manager
.0
.create_session(&request.name, request.description.as_deref())
.map_err(|e| format!("创建会话失败: {}", e))
.map_err(|e| format!("创建会话失败: {e}"))
}
/// 获取会话详情
@@ -1707,7 +1705,7 @@ pub async fn get_session(
session_manager
.0
.get_session(&session_id)
.map_err(|e| format!("获取会话失败: {}", e))
.map_err(|e| format!("获取会话失败: {e}"))
}
/// 列出所有会话
@@ -1729,7 +1727,7 @@ pub async fn list_sessions(
session_manager
.0
.list_sessions(include_archived.unwrap_or(false))
.map_err(|e| format!("列出会话失败: {}", e))
.map_err(|e| format!("列出会话失败: {e}"))
}
/// 添加 Flow 到会话
@@ -1753,7 +1751,7 @@ pub async fn add_flow_to_session(
session_manager
.0
.add_flow(&session_id, &flow_id)
.map_err(|e| format!("添加 Flow 到会话失败: {}", e))
.map_err(|e| format!("添加 Flow 到会话失败: {e}"))
}
/// 从会话移除 Flow
@@ -1777,7 +1775,7 @@ pub async fn remove_flow_from_session(
session_manager
.0
.remove_flow(&session_id, &flow_id)
.map_err(|e| format!("从会话移除 Flow 失败: {}", e))
.map_err(|e| format!("从会话移除 Flow 失败: {e}"))
}
/// 更新会话信息
@@ -1803,7 +1801,7 @@ pub async fn update_session(
request.name.as_deref(),
request.description.as_ref().map(|d| d.as_deref()),
)
.map_err(|e| format!("更新会话失败: {}", e))
.map_err(|e| format!("更新会话失败: {e}"))
}
/// 归档会话
@@ -1825,7 +1823,7 @@ pub async fn archive_session(
session_manager
.0
.archive_session(&session_id)
.map_err(|e| format!("归档会话失败: {}", e))
.map_err(|e| format!("归档会话失败: {e}"))
}
/// 取消归档会话
@@ -1845,7 +1843,7 @@ pub async fn unarchive_session(
session_manager
.0
.unarchive_session(&session_id)
.map_err(|e| format!("取消归档会话失败: {}", e))
.map_err(|e| format!("取消归档会话失败: {e}"))
}
/// 删除会话
@@ -1867,7 +1865,7 @@ pub async fn delete_session(
session_manager
.0
.delete_session(&session_id)
.map_err(|e| format!("删除会话失败: {}", e))
.map_err(|e| format!("删除会话失败: {e}"))
}
/// 导出会话
@@ -1892,7 +1890,7 @@ pub async fn export_session(
let flow_ids = session_manager
.0
.get_session_flow_ids(&request.session_id)
.map_err(|e| format!("获取会话 Flow 列表失败: {}", e))?;
.map_err(|e| format!("获取会话 Flow 列表失败: {e}"))?;
// 获取所有 Flow
let mut flows = Vec::new();
@@ -1906,7 +1904,7 @@ pub async fn export_session(
session_manager
.0
.export_session(&request.session_id, &flows, request.format)
.map_err(|e| format!("导出会话失败: {}", e))
.map_err(|e| format!("导出会话失败: {e}"))
}
/// 获取会话中的 Flow 数量
@@ -1926,7 +1924,7 @@ pub async fn get_session_flow_count(
session_manager
.0
.get_session_flow_count(&session_id)
.map_err(|e| format!("获取会话 Flow 数量失败: {}", e))
.map_err(|e| format!("获取会话 Flow 数量失败: {e}"))
}
/// 检查 Flow 是否在会话中
@@ -1948,7 +1946,7 @@ pub async fn is_flow_in_session(
session_manager
.0
.is_flow_in_session(&session_id, &flow_id)
.map_err(|e| format!("检查 Flow 是否在会话中失败: {}", e))
.map_err(|e| format!("检查 Flow 是否在会话中失败: {e}"))
}
/// 获取 Flow 所属的会话列表
@@ -1968,7 +1966,7 @@ pub async fn get_sessions_for_flow(
session_manager
.0
.get_sessions_for_flow(&flow_id)
.map_err(|e| format!("获取 Flow 所属会话失败: {}", e))
.map_err(|e| format!("获取 Flow 所属会话失败: {e}"))
}
/// 获取自动会话检测配置
@@ -2107,7 +2105,7 @@ pub async fn save_quick_filter(
request.description.as_deref(),
request.group.as_deref(),
)
.map_err(|e| format!("保存快速过滤器失败: {}", e))
.map_err(|e| format!("保存快速过滤器失败: {e}"))
}
/// 获取快速过滤器
@@ -2127,7 +2125,7 @@ pub async fn get_quick_filter(
quick_filter_manager
.0
.get(&id)
.map_err(|e| format!("获取快速过滤器失败: {}", e))
.map_err(|e| format!("获取快速过滤器失败: {e}"))
}
/// 更新快速过滤器
@@ -2157,7 +2155,7 @@ pub async fn update_quick_filter(
quick_filter_manager
.0
.update(&request.id, updates)
.map_err(|e| format!("更新快速过滤器失败: {}", e))
.map_err(|e| format!("更新快速过滤器失败: {e}"))
}
/// 删除快速过滤器
@@ -2179,7 +2177,7 @@ pub async fn delete_quick_filter(
quick_filter_manager
.0
.delete(&id)
.map_err(|e| format!("删除快速过滤器失败: {}", e))
.map_err(|e| format!("删除快速过滤器失败: {e}"))
}
/// 列出所有快速过滤器
@@ -2199,7 +2197,7 @@ pub async fn list_quick_filters(
quick_filter_manager
.0
.list()
.map_err(|e| format!("列出快速过滤器失败: {}", e))
.map_err(|e| format!("列出快速过滤器失败: {e}"))
}
/// 按分组列出快速过滤器
@@ -2221,7 +2219,7 @@ pub async fn list_quick_filters_by_group(
quick_filter_manager
.0
.list_by_group(group.as_deref())
.map_err(|e| format!("按分组列出快速过滤器失败: {}", e))
.map_err(|e| format!("按分组列出快速过滤器失败: {e}"))
}
/// 列出所有分组
@@ -2241,7 +2239,7 @@ pub async fn list_quick_filter_groups(
quick_filter_manager
.0
.list_groups()
.map_err(|e| format!("列出快速过滤器分组失败: {}", e))
.map_err(|e| format!("列出快速过滤器分组失败: {e}"))
}
/// 导出快速过滤器
@@ -2263,7 +2261,7 @@ pub async fn export_quick_filters(
quick_filter_manager
.0
.export(include_presets.unwrap_or(false))
.map_err(|e| format!("导出快速过滤器失败: {}", e))
.map_err(|e| format!("导出快速过滤器失败: {e}"))
}
/// 导入快速过滤器
@@ -2285,7 +2283,7 @@ pub async fn import_quick_filters(
quick_filter_manager
.0
.import(&request.data, request.overwrite)
.map_err(|e| format!("导入快速过滤器失败: {}", e))
.map_err(|e| format!("导入快速过滤器失败: {e}"))
}
/// 按名称查找快速过滤器
@@ -2305,7 +2303,7 @@ pub async fn find_quick_filter_by_name(
quick_filter_manager
.0
.find_by_name(&name)
.map_err(|e| format!("查找快速过滤器失败: {}", e))
.map_err(|e| format!("查找快速过滤器失败: {e}"))
}
// ============================================================================
@@ -2355,7 +2353,7 @@ pub async fn export_flow_as_code(
.0
.get_flow(&request.flow_id)
.await
.map_err(|e| format!("获取 Flow 失败: {}", e))?
.map_err(|e| format!("获取 Flow 失败: {e}"))?
.ok_or_else(|| format!("Flow 不存在: {}", request.flow_id))?;
// 导出为代码
@@ -2511,7 +2509,7 @@ pub async fn add_bookmark(
request.name.as_deref(),
request.group.as_deref(),
)
.map_err(|e| format!("添加书签失败: {}", e))
.map_err(|e| format!("添加书签失败: {e}"))
}
/// 获取书签
@@ -2531,7 +2529,7 @@ pub async fn get_bookmark(
bookmark_manager
.0
.get(&bookmark_id)
.map_err(|e| format!("获取书签失败: {}", e))
.map_err(|e| format!("获取书签失败: {e}"))
}
/// 根据 Flow ID 获取书签
@@ -2551,7 +2549,7 @@ pub async fn get_bookmark_by_flow_id(
bookmark_manager
.0
.get_by_flow_id(&flow_id)
.map_err(|e| format!("获取书签失败: {}", e))
.map_err(|e| format!("获取书签失败: {e}"))
}
/// 移除书签
@@ -2573,7 +2571,7 @@ pub async fn remove_bookmark(
bookmark_manager
.0
.remove(&bookmark_id)
.map_err(|e| format!("移除书签失败: {}", e))
.map_err(|e| format!("移除书签失败: {e}"))
}
/// 根据 Flow ID 移除书签
@@ -2593,7 +2591,7 @@ pub async fn remove_bookmark_by_flow_id(
bookmark_manager
.0
.remove_by_flow_id(&flow_id)
.map_err(|e| format!("移除书签失败: {}", e))
.map_err(|e| format!("移除书签失败: {e}"))
}
/// 更新书签
@@ -2617,7 +2615,7 @@ pub async fn update_bookmark(
request.name.as_ref().map(|n| n.as_deref()),
request.group.as_ref().map(|g| g.as_deref()),
)
.map_err(|e| format!("更新书签失败: {}", e))
.map_err(|e| format!("更新书签失败: {e}"))
}
/// 列出所有书签
@@ -2639,7 +2637,7 @@ pub async fn list_bookmarks(
bookmark_manager
.0
.list(group.as_deref())
.map_err(|e| format!("列出书签失败: {}", e))
.map_err(|e| format!("列出书签失败: {e}"))
}
/// 列出所有书签分组
@@ -2659,7 +2657,7 @@ pub async fn list_bookmark_groups(
bookmark_manager
.0
.list_groups()
.map_err(|e| format!("列出书签分组失败: {}", e))
.map_err(|e| format!("列出书签分组失败: {e}"))
}
/// 检查 Flow 是否已添加书签
@@ -2679,7 +2677,7 @@ pub async fn is_flow_bookmarked(
bookmark_manager
.0
.is_bookmarked(&flow_id)
.map_err(|e| format!("检查书签状态失败: {}", e))
.map_err(|e| format!("检查书签状态失败: {e}"))
}
/// 获取书签数量
@@ -2697,7 +2695,7 @@ pub async fn get_bookmark_count(
bookmark_manager
.0
.count()
.map_err(|e| format!("获取书签数量失败: {}", e))
.map_err(|e| format!("获取书签数量失败: {e}"))
}
/// 导出书签
@@ -2717,7 +2715,7 @@ pub async fn export_bookmarks(
bookmark_manager
.0
.export()
.map_err(|e| format!("导出书签失败: {}", e))
.map_err(|e| format!("导出书签失败: {e}"))
}
/// 导入书签
@@ -2739,7 +2737,7 @@ pub async fn import_bookmarks(
bookmark_manager
.0
.import(&request.data, request.overwrite)
.map_err(|e| format!("导入书签失败: {}", e))
.map_err(|e| format!("导入书签失败: {e}"))
}
/// 切换书签状态
@@ -2767,19 +2765,19 @@ pub async fn toggle_bookmark(
let is_bookmarked = bookmark_manager
.0
.is_bookmarked(&flow_id)
.map_err(|e| format!("检查书签状态失败: {}", e))?;
.map_err(|e| format!("检查书签状态失败: {e}"))?;
if is_bookmarked {
bookmark_manager
.0
.remove_by_flow_id(&flow_id)
.map_err(|e| format!("移除书签失败: {}", e))?;
.map_err(|e| format!("移除书签失败: {e}"))?;
Ok(None)
} else {
let bookmark = bookmark_manager
.0
.add(&flow_id, name.as_deref(), group.as_deref())
.map_err(|e| format!("添加书签失败: {}", e))?;
.map_err(|e| format!("添加书签失败: {e}"))?;
Ok(Some(bookmark))
}
}
+41 -38
View File
@@ -43,8 +43,8 @@ pub async fn general_chat_create_session(
metadata,
};
let conn = db.lock().map_err(|e| format!("数据库锁定失败: {}", e))?;
GeneralChatDao::create_session(&conn, &session).map_err(|e| format!("创建会话失败: {}", e))?;
let conn = db.lock().map_err(|e| format!("数据库锁定失败: {e}"))?;
GeneralChatDao::create_session(&conn, &session).map_err(|e| format!("创建会话失败: {e}"))?;
tracing::info!(
"[GeneralChat] 创建会话: id={}, name={}",
@@ -59,9 +59,9 @@ pub async fn general_chat_create_session(
pub async fn general_chat_list_sessions(
db: State<'_, DbConnection>,
) -> Result<Vec<ChatSession>, String> {
let conn = db.lock().map_err(|e| format!("数据库锁定失败: {}", e))?;
let conn = db.lock().map_err(|e| format!("数据库锁定失败: {e}"))?;
let sessions =
GeneralChatDao::list_sessions(&conn).map_err(|e| format!("获取会话列表失败: {}", e))?;
GeneralChatDao::list_sessions(&conn).map_err(|e| format!("获取会话列表失败: {e}"))?;
Ok(sessions)
}
@@ -77,17 +77,17 @@ pub async fn general_chat_get_session(
session_id: String,
message_limit: Option<i32>,
) -> Result<SessionDetail, String> {
let conn = db.lock().map_err(|e| format!("数据库锁定失败: {}", e))?;
let conn = db.lock().map_err(|e| format!("数据库锁定失败: {e}"))?;
let session = GeneralChatDao::get_session(&conn, &session_id)
.map_err(|e| format!("获取会话失败: {}", e))?
.map_err(|e| format!("获取会话失败: {e}"))?
.ok_or_else(|| "会话不存在".to_string())?;
let messages = GeneralChatDao::get_messages(&conn, &session_id, message_limit, None)
.map_err(|e| format!("获取消息失败: {}", e))?;
.map_err(|e| format!("获取消息失败: {e}"))?;
let message_count = GeneralChatDao::get_message_count(&conn, &session_id)
.map_err(|e| format!("获取消息数量失败: {}", e))?;
.map_err(|e| format!("获取消息数量失败: {e}"))?;
Ok(SessionDetail {
session,
@@ -105,10 +105,10 @@ pub async fn general_chat_delete_session(
db: State<'_, DbConnection>,
session_id: String,
) -> Result<bool, String> {
let conn = db.lock().map_err(|e| format!("数据库锁定失败: {}", e))?;
let conn = db.lock().map_err(|e| format!("数据库锁定失败: {e}"))?;
let deleted = GeneralChatDao::delete_session(&conn, &session_id)
.map_err(|e| format!("删除会话失败: {}", e))?;
.map_err(|e| format!("删除会话失败: {e}"))?;
if deleted {
tracing::info!("[GeneralChat] 删除会话: id={}", session_id);
@@ -128,10 +128,10 @@ pub async fn general_chat_rename_session(
session_id: String,
name: String,
) -> Result<bool, String> {
let conn = db.lock().map_err(|e| format!("数据库锁定失败: {}", e))?;
let conn = db.lock().map_err(|e| format!("数据库锁定失败: {e}"))?;
let renamed = GeneralChatDao::rename_session(&conn, &session_id, &name)
.map_err(|e| format!("重命名会话失败: {}", e))?;
.map_err(|e| format!("重命名会话失败: {e}"))?;
if renamed {
tracing::info!("[GeneralChat] 重命名会话: id={}, name={}", session_id, name);
@@ -155,10 +155,10 @@ pub async fn general_chat_get_messages(
limit: Option<i32>,
before_id: Option<String>,
) -> Result<Vec<ChatMessage>, String> {
let conn = db.lock().map_err(|e| format!("数据库锁定失败: {}", e))?;
let conn = db.lock().map_err(|e| format!("数据库锁定失败: {e}"))?;
let messages = GeneralChatDao::get_messages(&conn, &session_id, limit, before_id.as_deref())
.map_err(|e| format!("获取消息失败: {}", e))?;
.map_err(|e| format!("获取消息失败: {e}"))?;
Ok(messages)
}
@@ -186,7 +186,7 @@ pub async fn general_chat_add_message(
"user" => MessageRole::User,
"assistant" => MessageRole::Assistant,
"system" => MessageRole::System,
_ => return Err(format!("无效的消息角色: {}", role)),
_ => return Err(format!("无效的消息角色: {role}")),
};
let message = ChatMessage {
@@ -200,16 +200,16 @@ pub async fn general_chat_add_message(
metadata,
};
let conn = db.lock().map_err(|e| format!("数据库锁定失败: {}", e))?;
let conn = db.lock().map_err(|e| format!("数据库锁定失败: {e}"))?;
// 检查会话是否存在
if !GeneralChatDao::session_exists(&conn, &session_id)
.map_err(|e| format!("检查会话失败: {}", e))?
.map_err(|e| format!("检查会话失败: {e}"))?
{
return Err("会话不存在".to_string());
}
GeneralChatDao::add_message(&conn, &message).map_err(|e| format!("添加消息失败: {}", e))?;
GeneralChatDao::add_message(&conn, &message).map_err(|e| format!("添加消息失败: {e}"))?;
tracing::debug!(
"[GeneralChat] 添加消息: session={}, role={:?}, len={}",
@@ -295,17 +295,17 @@ pub async fn general_chat_send_message(
};
{
let conn = db.lock().map_err(|e| format!("数据库锁定失败: {}", e))?;
let conn = db.lock().map_err(|e| format!("数据库锁定失败: {e}"))?;
// 检查会话是否存在
if !GeneralChatDao::session_exists(&conn, &request.session_id)
.map_err(|e| format!("检查会话失败: {}", e))?
.map_err(|e| format!("检查会话失败: {e}"))?
{
return Err("会话不存在".to_string());
}
GeneralChatDao::add_message(&conn, &user_message)
.map_err(|e| format!("保存用户消息失败: {}", e))?;
.map_err(|e| format!("保存用户消息失败: {e}"))?;
}
// 设置停止标志
@@ -363,9 +363,9 @@ pub async fn general_chat_send_message(
};
{
let conn = db.lock().map_err(|e| format!("数据库锁定失败: {}", e))?;
let conn = db.lock().map_err(|e| format!("数据库锁定失败: {e}"))?;
GeneralChatDao::add_message(&conn, &assistant_message)
.map_err(|e| format!("保存 AI 响应失败: {}", e))?;
.map_err(|e| format!("保存 AI 响应失败: {e}"))?;
}
// 发送完成事件
@@ -395,8 +395,8 @@ pub async fn general_chat_stop_generation(session_id: String) -> Result<bool, St
tracing::info!("[GeneralChat] 停止生成: session={}", session_id);
let mut flags = STOP_FLAGS.write().await;
if flags.contains_key(&session_id) {
flags.insert(session_id, true);
if let std::collections::hash_map::Entry::Occupied(mut e) = flags.entry(session_id) {
e.insert(true);
Ok(true)
} else {
Ok(false)
@@ -410,10 +410,10 @@ pub struct GenerateTitleRequest {
pub session_id: String,
/// 用户第一条消息内容
pub first_message: String,
/// Provider 名称(可选)
/// Provider 名称(可选,暂未使用,预留给未来支持多 provider)
#[serde(default)]
pub provider: Option<String>,
/// 模型名称(可选)
/// 模型名称(可选,用于指定生成标题的模型)
#[serde(default)]
pub model: Option<String>,
}
@@ -430,9 +430,11 @@ pub async fn general_chat_generate_title(
request: GenerateTitleRequest,
) -> Result<String, String> {
tracing::info!(
"[GeneralChat] 生成标题: session={}, message_len={}",
"[GeneralChat] 生成标题: session={}, message_len={}, provider={:?}, model={:?}",
request.session_id,
request.first_message.len()
request.first_message.len(),
request.provider,
request.model
);
// 生成标题的 prompt
@@ -441,8 +443,9 @@ pub async fn general_chat_generate_title(
request.first_message.chars().take(500).collect::<String>()
);
// 尝试调用 AI 生成标题
let title = match generate_title_with_ai(&prompt).await {
// 尝试调用 AI 生成标题,使用指定的模型或默认模型
let model = request.model.as_deref();
let title = match generate_title_with_ai(&prompt, model).await {
Ok(ai_title) => {
tracing::info!("[GeneralChat] AI 生成标题成功: {}", ai_title);
// 清理 AI 返回的标题(去除引号、换行等)
@@ -457,9 +460,9 @@ pub async fn general_chat_generate_title(
// 更新数据库中的会话标题
{
let conn = db.lock().map_err(|e| format!("数据库锁定失败: {}", e))?;
let conn = db.lock().map_err(|e| format!("数据库锁定失败: {e}"))?;
GeneralChatDao::rename_session(&conn, &request.session_id, &title)
.map_err(|e| format!("更新标题失败: {}", e))?;
.map_err(|e| format!("更新标题失败: {e}"))?;
}
tracing::info!(
@@ -472,7 +475,7 @@ pub async fn general_chat_generate_title(
}
/// 使用 AI 生成标题
async fn generate_title_with_ai(prompt: &str) -> Result<String, String> {
async fn generate_title_with_ai(prompt: &str, model: Option<&str>) -> Result<String, String> {
use crate::models::openai::{ChatCompletionRequest, ChatMessage, MessageContent};
use crate::providers::openai_custom::OpenAICustomProvider;
@@ -484,7 +487,7 @@ async fn generate_title_with_ai(prompt: &str) -> Result<String, String> {
);
let request = ChatCompletionRequest {
model: "default".to_string(), // 使用默认模型
model: model.unwrap_or("default").to_string(),
messages: vec![ChatMessage {
role: "user".to_string(),
content: Some(MessageContent::Text(prompt.to_string())),
@@ -504,17 +507,17 @@ async fn generate_title_with_ai(prompt: &str) -> Result<String, String> {
let resp = provider
.call_api(&request)
.await
.map_err(|e| format!("API 调用失败: {}", e))?;
.map_err(|e| format!("API 调用失败: {e}"))?;
let status = resp.status();
let body = resp.text().await.unwrap_or_default();
if !status.is_success() {
return Err(format!("API 返回错误: {} - {}", status, body));
return Err(format!("API 返回错误: {status} - {body}"));
}
let parsed: serde_json::Value =
serde_json::from_str(&body).map_err(|e| format!("解析响应失败: {}", e))?;
serde_json::from_str(&body).map_err(|e| format!("解析响应失败: {e}"))?;
let content = parsed["choices"]
.as_array()
+2 -2
View File
@@ -140,7 +140,7 @@ pub async fn remove_injection_rule(
.rules
.iter()
.position(|r| r.id == id)
.ok_or_else(|| format!("规则 ID '{}' 不存在", id))?;
.ok_or_else(|| format!("规则 ID '{id}' 不存在"))?;
s.config.injection.rules.remove(pos);
save_config(&s.config).map_err(|e| e.to_string())?;
@@ -162,7 +162,7 @@ pub async fn update_injection_rule(
.rules
.iter()
.position(|r| r.id == id)
.ok_or_else(|| format!("规则 ID '{}' 不存在", id))?;
.ok_or_else(|| format!("规则 ID '{id}' 不存在"))?;
s.config.injection.rules[pos] = InjectionRuleConfig {
id: rule.id,
+26 -29
View File
@@ -47,8 +47,7 @@ fn get_aws_sso_cache_dir() -> Result<PathBuf, String> {
// 确保目录存在
if !cache_dir.exists() {
fs::create_dir_all(&cache_dir)
.map_err(|e| format!("创建 AWS SSO cache 目录失败: {}", e))?;
fs::create_dir_all(&cache_dir).map_err(|e| format!("创建 AWS SSO cache 目录失败: {e}"))?;
}
Ok(cache_dir)
@@ -57,14 +56,14 @@ fn get_aws_sso_cache_dir() -> Result<PathBuf, String> {
/// 计算 clientIdHash(备用方案,使用 SHA256 的前 40 位模拟 SHA1 格式)
fn calculate_client_id_hash() -> String {
let start_url = "https://view.awsapps.com/start";
let json_str = format!("{{\"startUrl\":\"{}\"}}", start_url);
let json_str = format!("{{\"startUrl\":\"{start_url}\"}}");
let mut hasher = Sha256::new();
hasher.update(json_str.as_bytes());
let result = hasher.finalize();
// SHA1 是 40 位十六进制,取 SHA256 的前 20 字节(40 位十六进制)
format!("{:x}", result)[..40].to_string()
format!("{result:x}")[..40].to_string()
}
/// 切换 Kiro 凭证到本地
@@ -85,8 +84,8 @@ pub async fn switch_kiro_to_local(
let credential = pool_service
.0
.get_by_uuid(&db, &uuid)
.map_err(|e| format!("获取凭证失败: {}", e))?
.ok_or_else(|| format!("找不到凭证: {}", uuid))?;
.map_err(|e| format!("获取凭证失败: {e}"))?
.ok_or_else(|| format!("找不到凭证: {uuid}"))?;
// 检查是否为 Kiro 凭证
let creds_file_path = match &credential.credential {
@@ -96,32 +95,32 @@ pub async fn switch_kiro_to_local(
// 2. 读取凭证文件
let creds_content =
fs::read_to_string(&creds_file_path).map_err(|e| format!("读取凭证文件失败: {}", e))?;
fs::read_to_string(&creds_file_path).map_err(|e| format!("读取凭证文件失败: {e}"))?;
let creds: serde_json::Value =
serde_json::from_str(&creds_content).map_err(|e| format!("解析凭证文件失败: {}", e))?;
serde_json::from_str(&creds_content).map_err(|e| format!("解析凭证文件失败: {e}"))?;
// 3. 获取/生成绑定的 Machine ID
let mut fingerprint_store =
KiroFingerprintStore::load().map_err(|e| format!("加载指纹存储失败: {}", e))?;
KiroFingerprintStore::load().map_err(|e| format!("加载指纹存储失败: {e}"))?;
let profile_arn = creds.get("profileArn").and_then(|v| v.as_str());
let client_id = creds.get("clientId").and_then(|v| v.as_str());
let binding = fingerprint_store
.get_or_create_binding(&uuid, profile_arn, client_id)
.map_err(|e| format!("获取指纹绑定失败: {}", e))?;
.map_err(|e| format!("获取指纹绑定失败: {e}"))?;
let machine_id = binding.machine_id.clone();
tracing::info!("[KIRO_LOCAL] 使用 Machine ID: {}", &machine_id[..8]);
// 4. 切换系统机器码
let machine_service =
MachineIdService::new().map_err(|e| format!("初始化机器码服务失败: {}", e))?;
MachineIdService::new().map_err(|e| format!("初始化机器码服务失败: {e}"))?;
let machine_result = machine_service
.set_machine_id(&machine_id)
.await
.map_err(|e| format!("切换机器码失败: {}", e))?;
.map_err(|e| format!("切换机器码失败: {e}"))?;
if !machine_result.success {
if machine_result.requires_admin {
@@ -196,10 +195,10 @@ pub async fn switch_kiro_to_local(
};
let auth_token_json = serde_json::to_string_pretty(&auth_token)
.map_err(|e| format!("序列化 auth token 失败: {}", e))?;
.map_err(|e| format!("序列化 auth token 失败: {e}"))?;
fs::write(&auth_token_path, &auth_token_json)
.map_err(|e| format!("写入 kiro-auth-token.json 失败: {}", e))?;
.map_err(|e| format!("写入 kiro-auth-token.json 失败: {e}"))?;
tracing::info!("[KIRO_LOCAL] 已写入 kiro-auth-token.json");
@@ -220,12 +219,12 @@ pub async fn switch_kiro_to_local(
],
};
let registration_path = cache_dir.join(format!("{}.json", client_id_hash));
let registration_path = cache_dir.join(format!("{client_id_hash}.json"));
let registration_json = serde_json::to_string_pretty(&registration)
.map_err(|e| format!("序列化客户端注册信息失败: {}", e))?;
.map_err(|e| format!("序列化客户端注册信息失败: {e}"))?;
fs::write(&registration_path, &registration_json)
.map_err(|e| format!("写入客户端注册文件失败: {}", e))?;
.map_err(|e| format!("写入客户端注册文件失败: {e}"))?;
tracing::info!(
"[KIRO_LOCAL] 已写入客户端注册文件: {}.json",
@@ -237,7 +236,7 @@ pub async fn switch_kiro_to_local(
// 8. 更新最后切换时间
fingerprint_store
.update_last_switched(&uuid)
.map_err(|e| format!("更新切换时间失败: {}", e))?;
.map_err(|e| format!("更新切换时间失败: {e}"))?;
let credential_name = credential
.name
@@ -265,8 +264,8 @@ pub async fn get_kiro_fingerprint_info(
let credential = pool_service
.0
.get_by_uuid(&db, &uuid)
.map_err(|e| format!("获取凭证失败: {}", e))?
.ok_or_else(|| format!("找不到凭证: {}", uuid))?;
.map_err(|e| format!("获取凭证失败: {e}"))?
.ok_or_else(|| format!("找不到凭证: {uuid}"))?;
// 检查是否为 Kiro 凭证
let creds_file_path = match &credential.credential {
@@ -276,20 +275,20 @@ pub async fn get_kiro_fingerprint_info(
// 读取凭证文件
let creds_content =
fs::read_to_string(&creds_file_path).map_err(|e| format!("读取凭证文件失败: {}", e))?;
fs::read_to_string(&creds_file_path).map_err(|e| format!("读取凭证文件失败: {e}"))?;
let creds: serde_json::Value =
serde_json::from_str(&creds_content).map_err(|e| format!("解析凭证文件失败: {}", e))?;
serde_json::from_str(&creds_content).map_err(|e| format!("解析凭证文件失败: {e}"))?;
// 获取指纹绑定
let mut fingerprint_store =
KiroFingerprintStore::load().map_err(|e| format!("加载指纹存储失败: {}", e))?;
KiroFingerprintStore::load().map_err(|e| format!("加载指纹存储失败: {e}"))?;
let profile_arn = creds.get("profileArn").and_then(|v| v.as_str());
let client_id = creds.get("clientId").and_then(|v| v.as_str());
let binding = fingerprint_store
.get_or_create_binding(&uuid, profile_arn, client_id)
.map_err(|e| format!("获取指纹绑定失败: {}", e))?;
.map_err(|e| format!("获取指纹绑定失败: {e}"))?;
let auth_method = creds
.get("authMethod")
@@ -331,9 +330,9 @@ pub async fn get_local_kiro_credential_uuid(
}
let local_content =
fs::read_to_string(&auth_token_path).map_err(|e| format!("读取本地凭证文件失败: {}", e))?;
fs::read_to_string(&auth_token_path).map_err(|e| format!("读取本地凭证文件失败: {e}"))?;
let local_creds: serde_json::Value =
serde_json::from_str(&local_content).map_err(|e| format!("解析本地凭证文件失败: {}", e))?;
serde_json::from_str(&local_content).map_err(|e| format!("解析本地凭证文件失败: {e}"))?;
let local_access_token = local_creds.get("accessToken").and_then(|v| v.as_str());
let local_refresh_token = local_creds.get("refreshToken").and_then(|v| v.as_str());
@@ -344,9 +343,7 @@ pub async fn get_local_kiro_credential_uuid(
// 获取所有 Kiro 凭证
let overview = pool_service.0.get_overview(&db)?;
let kiro_pool = overview
.iter()
.find(|p| p.provider_type.to_string() == "kiro");
let kiro_pool = overview.iter().find(|p| p.provider_type == "kiro");
if let Some(pool) = kiro_pool {
for cred_display in &pool.credentials {
+5 -7
View File
@@ -161,7 +161,7 @@ pub async fn clear_machine_id_override() -> Result<MachineIdResult, String> {
}),
Err(e) => Ok(MachineIdResult {
success: false,
message: format!("Failed to remove override: {}", e),
message: format!("Failed to remove override: {e}"),
requires_restart: false,
requires_admin: false,
new_machine_id: None,
@@ -185,11 +185,10 @@ pub async fn clear_machine_id_override() -> Result<MachineIdResult, String> {
#[tauri::command]
pub async fn copy_machine_id_to_clipboard(machine_id: String) -> Result<bool, String> {
use arboard::Clipboard;
let mut clipboard =
Clipboard::new().map_err(|e| format!("Failed to access clipboard: {}", e))?;
let mut clipboard = Clipboard::new().map_err(|e| format!("Failed to access clipboard: {e}"))?;
clipboard
.set_text(machine_id)
.map_err(|e| format!("Failed to copy to clipboard: {}", e))?;
.map_err(|e| format!("Failed to copy to clipboard: {e}"))?;
Ok(true)
}
@@ -198,11 +197,10 @@ pub async fn copy_machine_id_to_clipboard(machine_id: String) -> Result<bool, St
#[tauri::command]
pub async fn paste_machine_id_from_clipboard() -> Result<String, String> {
use arboard::Clipboard;
let mut clipboard =
Clipboard::new().map_err(|e| format!("Failed to access clipboard: {}", e))?;
let mut clipboard = Clipboard::new().map_err(|e| format!("Failed to access clipboard: {e}"))?;
let text = clipboard
.get_text()
.map_err(|e| format!("Failed to read from clipboard: {}", e))?;
.map_err(|e| format!("Failed to read from clipboard: {e}"))?;
// 基本验证
let cleaned = text.replace("-", "").replace(" ", "").trim().to_lowercase();
+2 -2
View File
@@ -40,7 +40,7 @@ pub async fn refresh_credential_models(
let conn = db.lock().map_err(|e| e.to_string())?;
ProviderPoolDao::get_by_uuid(&conn, &credential_uuid)
.map_err(|e| e.to_string())?
.ok_or_else(|| format!("凭证不存在: {}", credential_uuid))?
.ok_or_else(|| format!("凭证不存在: {credential_uuid}"))?
};
tracing::info!(
@@ -127,7 +127,7 @@ pub async fn refresh_all_credential_models(
model_service.update_credential_models(&db, &credential.uuid, models.clone())
{
tracing::error!("[REFRESH_ALL] 更新数据库失败: {}", e);
Err(format!("更新数据库失败: {}", e))
Err(format!("更新数据库失败: {e}"))
} else {
tracing::info!("[REFRESH_ALL] 成功刷新 {} 个模型", models.len());
Ok(models)
+3 -3
View File
@@ -136,7 +136,7 @@ pub async fn get_models_by_tier(
let tier: ModelTier = tier
.parse()
.map_err(|_| format!("无效的服务等级: {}", tier))?;
.map_err(|_| format!("无效的服务等级: {tier}"))?;
Ok(service.get_models_by_tier(tier).await)
}
@@ -227,13 +227,13 @@ pub async fn fetch_provider_models_auto(
let provider = api_key_service
.0
.get_provider(&db, &provider_id)?
.ok_or_else(|| format!("Provider 不存在: {}", provider_id))?;
.ok_or_else(|| format!("Provider 不存在: {provider_id}"))?;
// 获取 API Key
let api_key = api_key_service
.0
.get_next_api_key(&db, &provider_id)?
.ok_or_else(|| format!("Provider {} 没有可用的 API Key", provider_id))?;
.ok_or_else(|| format!("Provider {provider_id} 没有可用的 API Key"))?;
// 获取 API Host
let api_host = provider.provider.api_host.clone();
+7 -7
View File
@@ -162,7 +162,7 @@ pub async fn add_model_to_provider(
if let Some(provider_config) = state.config.models.providers.get_mut(&provider) {
// 检查是否已存在
if provider_config.models.iter().any(|m| m.id == model_id) {
return Err(format!("模型 {} 已存在于 {} 中", model_id, provider));
return Err(format!("模型 {model_id} 已存在于 {provider} 中"));
}
provider_config.models.push(ModelInfo {
id: model_id,
@@ -170,7 +170,7 @@ pub async fn add_model_to_provider(
enabled: true,
});
} else {
return Err(format!("Provider {} 不存在", provider));
return Err(format!("Provider {provider} 不存在"));
}
save_config(&state.config).map_err(|e| e.to_string())?;
@@ -189,7 +189,7 @@ pub async fn remove_model_from_provider(
if let Some(provider_config) = state.config.models.providers.get_mut(&provider) {
provider_config.models.retain(|m| m.id != model_id);
} else {
return Err(format!("Provider {} 不存在", provider));
return Err(format!("Provider {provider} 不存在"));
}
save_config(&state.config).map_err(|e| e.to_string())?;
@@ -210,10 +210,10 @@ pub async fn toggle_model_enabled(
if let Some(model) = provider_config.models.iter_mut().find(|m| m.id == model_id) {
model.enabled = enabled;
} else {
return Err(format!("模型 {} 不存在于 {} 中", model_id, provider));
return Err(format!("模型 {model_id} 不存在于 {provider} 中"));
}
} else {
return Err(format!("Provider {} 不存在", provider));
return Err(format!("Provider {provider} 不存在"));
}
save_config(&state.config).map_err(|e| e.to_string())?;
@@ -230,7 +230,7 @@ pub async fn add_provider(
let mut state = app_state.write().await;
if state.config.models.providers.contains_key(&provider_id) {
return Err(format!("Provider {} 已存在", provider_id));
return Err(format!("Provider {provider_id} 已存在"));
}
state.config.models.providers.insert(
@@ -254,7 +254,7 @@ pub async fn remove_provider(
let mut state = app_state.write().await;
if state.config.models.providers.remove(&provider_id).is_none() {
return Err(format!("Provider {} 不存在", provider_id));
return Err(format!("Provider {provider_id} 不存在"));
}
save_config(&state.config).map_err(|e| e.to_string())?;
+11 -12
View File
@@ -111,17 +111,16 @@ pub async fn analyze_midi(midi_path: String) -> Result<MidiAnalysisResult, Strin
.arg(&script_path)
.arg(&midi_path)
.output()
.map_err(|e| format!("Failed to execute Python script: {}", e))?;
.map_err(|e| format!("Failed to execute Python script: {e}"))?;
if !output.status.success() {
let error = String::from_utf8_lossy(&output.stderr);
return Err(format!("MIDI analysis failed: {}", error));
return Err(format!("MIDI analysis failed: {error}"));
}
// 解析 JSON 输出
let result_json = String::from_utf8_lossy(&output.stdout);
serde_json::from_str(&result_json)
.map_err(|e| format!("Failed to parse analysis result: {}", e))
serde_json::from_str(&result_json).map_err(|e| format!("Failed to parse analysis result: {e}"))
}
/// 将 MP3 转换为 MIDI
@@ -136,11 +135,11 @@ pub async fn convert_mp3_to_midi(mp3_path: String, output_path: String) -> Resul
.arg(&mp3_path)
.arg(&output_path)
.output()
.map_err(|e| format!("Failed to execute Python script: {}", e))?;
.map_err(|e| format!("Failed to execute Python script: {e}"))?;
if !output.status.success() {
let error = String::from_utf8_lossy(&output.stderr);
return Err(format!("MP3 to MIDI conversion failed: {}", error));
return Err(format!("MP3 to MIDI conversion failed: {error}"));
}
Ok(output_path)
@@ -149,10 +148,10 @@ pub async fn convert_mp3_to_midi(mp3_path: String, output_path: String) -> Resul
/// 加载资源文件
#[tauri::command]
pub async fn load_music_resource(resource_name: String) -> Result<String, String> {
let resource_path = get_resource_path(&format!("music/{}", resource_name))?;
let resource_path = get_resource_path(&format!("music/{resource_name}"))?;
std::fs::read_to_string(&resource_path)
.map_err(|e| format!("Failed to read resource file: {}", e))
.map_err(|e| format!("Failed to read resource file: {e}"))
}
/// 获取资源文件路径
@@ -160,7 +159,7 @@ fn get_resource_path(relative_path: &str) -> Result<PathBuf, String> {
// 在开发环境中,资源文件在 src-tauri/resources/
// 在生产环境中,资源文件会被打包到应用程序包中
let mut path =
std::env::current_exe().map_err(|e| format!("Failed to get executable path: {}", e))?;
std::env::current_exe().map_err(|e| format!("Failed to get executable path: {e}"))?;
path.pop(); // 移除可执行文件名
@@ -185,7 +184,7 @@ fn get_resource_path(relative_path: &str) -> Result<PathBuf, String> {
if dev_path.exists() {
return Ok(dev_path);
}
return Err(format!("Resource not found: {}", relative_path));
return Err(format!("Resource not found: {relative_path}"));
}
Ok(path)
@@ -200,11 +199,11 @@ pub async fn install_python_dependencies() -> Result<String, String> {
.arg("install")
.args(&packages)
.output()
.map_err(|e| format!("Failed to install packages: {}", e))?;
.map_err(|e| format!("Failed to install packages: {e}"))?;
if !output.status.success() {
let error = String::from_utf8_lossy(&output.stderr);
return Err(format!("Installation failed: {}", error));
return Err(format!("Installation failed: {error}"));
}
Ok("Dependencies installed successfully".to_string())
+2 -2
View File
@@ -144,7 +144,7 @@ pub fn get_accessible_host(listen_host: &str) -> String {
/// 格式为 `http://{host}:{port}` 的 URL
pub fn get_accessible_url(listen_host: &str, port: u16) -> String {
let host = get_accessible_host(listen_host);
format!("http://{}:{}", host, port)
format!("http://{host}:{port}")
}
/// 根据监听地址生成本地访问的 URL
@@ -164,7 +164,7 @@ pub fn get_local_url(listen_host: &str, port: u16) -> String {
"0.0.0.0" | "localhost" => "127.0.0.1".to_string(),
_ => listen_host.to_string(),
};
format!("http://{}:{}", host, port)
format!("http://{host}:{port}")
}
#[cfg(test)]
+5 -7
View File
@@ -52,10 +52,8 @@ pub async fn init_orchestrator(
// 从数据库加载凭证并同步到 orchestrator
let credentials = {
let conn = db
.lock()
.map_err(|e| format!("获取数据库连接失败: {}", e))?;
ProviderPoolDao::get_all(&conn).map_err(|e| format!("获取凭证列表失败: {}", e))?
let conn = db.lock().map_err(|e| format!("获取数据库连接失败: {e}"))?;
ProviderPoolDao::get_all(&conn).map_err(|e| format!("获取凭证列表失败: {e}"))?
};
// 转换凭证格式
@@ -226,7 +224,7 @@ pub async fn get_tier_models(tier: String) -> Result<Vec<AvailableModel>, String
let orchestrator = get_global_orchestrator().ok_or("编排器未初始化")?;
let service_tier =
ServiceTier::from_str(&tier).ok_or_else(|| format!("无效的服务等级: {}", tier))?;
ServiceTier::from_str(&tier).ok_or_else(|| format!("无效的服务等级: {tier}"))?;
Ok(orchestrator.get_models(service_tier).await)
}
@@ -408,7 +406,7 @@ pub async fn select_model_for_task(tier: String, task: String) -> Result<Selecti
let orchestrator = get_global_orchestrator().ok_or("编排器未初始化")?;
let service_tier =
ServiceTier::from_str(&tier).ok_or_else(|| format!("无效的服务等级: {}", tier))?;
ServiceTier::from_str(&tier).ok_or_else(|| format!("无效的服务等级: {tier}"))?;
let task_hint = match task.to_lowercase().as_str() {
"coding" => TaskHint::Coding,
@@ -445,7 +443,7 @@ pub fn list_service_tiers() -> Vec<ServiceTierInfo> {
ServiceTier::all()
.iter()
.map(|t| ServiceTierInfo {
id: format!("{:?}", t).to_lowercase(),
id: format!("{t:?}").to_lowercase(),
display_name: t.display_name().to_string(),
description: t.description().to_string(),
level: t.level(),
+6 -6
View File
@@ -22,7 +22,7 @@ use super::plugin_install_cmd::PluginInstallerState;
/// 前端调试日志命令
#[tauri::command]
pub fn frontend_debug_log(message: String) {
println!("[Frontend] {}", message);
println!("[Frontend] {message}");
}
/// 插件管理器状态
@@ -151,7 +151,7 @@ pub async fn get_plugins_dir(
) -> Result<String, String> {
let manager = state.0.read().await;
let dir = manager.plugins_dir().to_string_lossy().to_string();
println!("[get_plugins_dir] 返回: {}", dir);
println!("[get_plugins_dir] 返回: {dir}");
Ok(dir)
}
@@ -435,7 +435,7 @@ pub async fn read_plugin_manifest_cmd(
);
// 输出序列化后的 JSON
if let Ok(json) = serde_json::to_string(&manifest) {
println!("[read_plugin_manifest_cmd] JSON: {}", json);
println!("[read_plugin_manifest_cmd] JSON: {json}");
}
return Ok(Some(manifest));
}
@@ -480,10 +480,10 @@ pub async fn launch_plugin_ui(
if let Some(m) = read_plugin_manifest(&installed.install_path) {
(m, installed.install_path.clone())
} else {
return Err(format!("插件 {} 不存在", plugin_id));
return Err(format!("插件 {plugin_id} 不存在"));
}
} else {
return Err(format!("插件 {} 不存在", plugin_id));
return Err(format!("插件 {plugin_id} 不存在"));
}
};
@@ -507,7 +507,7 @@ pub async fn launch_plugin_ui(
// 启动二进制文件
std::process::Command::new(&binary_path)
.spawn()
.map_err(|e| format!("启动插件失败: {}", e))?;
.map_err(|e| format!("启动插件失败: {e}"))?;
Ok(())
}
+1 -1
View File
@@ -69,7 +69,7 @@ pub async fn install_plugin_from_file<R: Runtime>(
return Ok(InstallResult {
success: false,
plugin: None,
error: Some(format!("文件不存在: {}", file_path)),
error: Some(format!("文件不存在: {file_path}")),
});
}
+14 -14
View File
@@ -133,14 +133,14 @@ pub async fn plugin_rpc_connect(
let plugin = plugins
.iter()
.find(|p| p.id == plugin_id)
.ok_or_else(|| format!("插件 {} 未安装", plugin_id))?;
.ok_or_else(|| format!("插件 {plugin_id} 未安装"))?;
// 读取插件 manifest
let manifest_path = plugin.install_path.join("plugin.json");
let manifest_content = std::fs::read_to_string(&manifest_path)
.map_err(|e| format!("读取 manifest 失败: {}", e))?;
let manifest: Value = serde_json::from_str(&manifest_content)
.map_err(|e| format!("解析 manifest 失败: {}", e))?;
let manifest_content =
std::fs::read_to_string(&manifest_path).map_err(|e| format!("读取 manifest 失败: {e}"))?;
let manifest: Value =
serde_json::from_str(&manifest_content).map_err(|e| format!("解析 manifest 失败: {e}"))?;
// 获取二进制文件路径
let _binary_name = manifest["binary"]["binary_name"]
@@ -159,11 +159,11 @@ pub async fn plugin_rpc_connect(
let binary_filename = manifest["binary"]["platform_binaries"][platform_key]
.as_str()
.ok_or_else(|| format!("manifest 中缺少 {} 平台的二进制文件", platform_key))?;
.ok_or_else(|| format!("manifest 中缺少 {platform_key} 平台的二进制文件"))?;
let binary_path = plugin.install_path.join(binary_filename);
if !binary_path.exists() {
return Err(format!("二进制文件不存在: {:?}", binary_path));
return Err(format!("二进制文件不存在: {binary_path:?}"));
}
// 启动进程(使用 tokio::process::Command)
@@ -172,7 +172,7 @@ pub async fn plugin_rpc_connect(
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.map_err(|e| format!("启动插件进程失败: {}", e))?;
.map_err(|e| format!("启动插件进程失败: {e}"))?;
tracing::info!("插件 {} 进程已启动, PID: {:?}", plugin_id, child.id());
@@ -322,7 +322,7 @@ pub async fn plugin_rpc_call(
let processes = rpc_state.processes.read().await;
let process_arc = processes
.get(&plugin_id)
.ok_or_else(|| format!("插件 {} 未连接", plugin_id))?
.ok_or_else(|| format!("插件 {plugin_id} 未连接"))?
.clone();
drop(processes);
@@ -338,7 +338,7 @@ pub async fn plugin_rpc_call(
};
let request_json =
serde_json::to_string(&request).map_err(|e| format!("序列化请求失败: {}", e))?;
serde_json::to_string(&request).map_err(|e| format!("序列化请求失败: {e}"))?;
// 创建响应 channel
let (response_tx, response_rx) = oneshot::channel();
@@ -355,15 +355,15 @@ pub async fn plugin_rpc_call(
stdin
.write_all(request_json.as_bytes())
.await
.map_err(|e| format!("发送请求失败: {}", e))?;
.map_err(|e| format!("发送请求失败: {e}"))?;
stdin
.write_all(b"\n")
.await
.map_err(|e| format!("发送换行失败: {}", e))?;
.map_err(|e| format!("发送换行失败: {e}"))?;
stdin
.flush()
.await
.map_err(|e| format!("刷新 stdin 失败: {}", e))?;
.map_err(|e| format!("刷新 stdin 失败: {e}"))?;
}
// 释放 process lock,让 stdout 读取任务可以处理响应
@@ -378,7 +378,7 @@ pub async fn plugin_rpc_call(
let process = process_arc.lock().await;
let mut pending = process.pending_requests.lock().await;
pending.remove(&request_id);
Err(format!("RPC 调用 {} 超时", method))
Err(format!("RPC 调用 {method} 超时"))
}
}
}
+90 -93
View File
@@ -41,7 +41,7 @@ fn get_credentials_dir() -> Result<PathBuf, String> {
// 确保目录存在
if !app_data_dir.exists() {
fs::create_dir_all(&app_data_dir).map_err(|e| format!("创建凭证存储目录失败: {}", e))?;
fs::create_dir_all(&app_data_dir).map_err(|e| format!("创建凭证存储目录失败: {e}"))?;
}
Ok(app_data_dir)
@@ -60,7 +60,7 @@ fn copy_and_rename_credential_file(
// 验证源文件存在
if !source.exists() {
return Err(format!("凭证文件不存在: {}", expanded_source));
return Err(format!("凭证文件不存在: {expanded_source}"));
}
// 生成新的文件名:{provider_type}_{uuid}_{timestamp}.json
@@ -84,9 +84,9 @@ fn copy_and_rename_credential_file(
// 对于 Kiro 凭证,需要合并 clientIdHash 文件中的 client_id/client_secret
if provider_type == "kiro" {
let content = fs::read_to_string(source).map_err(|e| format!("读取凭证文件失败: {}", e))?;
let content = fs::read_to_string(source).map_err(|e| format!("读取凭证文件失败: {e}"))?;
let mut creds: serde_json::Value =
serde_json::from_str(&content).map_err(|e| format!("解析凭证文件失败: {}", e))?;
serde_json::from_str(&content).map_err(|e| format!("解析凭证文件失败: {e}"))?;
// 检测 refreshToken 是否被截断(仅记录警告,不阻止添加)
// 正常的 refreshToken 长度应该在 500+ 字符,如果小于 100 字符则可能被截断
@@ -126,7 +126,7 @@ fn copy_and_rename_credential_file(
// 方式1:如果有 clientIdHash,读取对应文件
if let Some(hash) = creds.get("clientIdHash").and_then(|v| v.as_str()) {
let hash_file_path = aws_sso_cache_dir.join(format!("{}.json", hash));
let hash_file_path = aws_sso_cache_dir.join(format!("{hash}.json"));
if hash_file_path.exists() {
if let Ok(hash_content) = fs::read_to_string(&hash_file_path) {
@@ -213,11 +213,11 @@ fn copy_and_rename_credential_file(
// 写入合并后的凭证到副本文件
let merged_content =
serde_json::to_string_pretty(&creds).map_err(|e| format!("序列化凭证失败: {}", e))?;
fs::write(&target_path, merged_content).map_err(|e| format!("写入凭证文件失败: {}", e))?;
serde_json::to_string_pretty(&creds).map_err(|e| format!("序列化凭证失败: {e}"))?;
fs::write(&target_path, merged_content).map_err(|e| format!("写入凭证文件失败: {e}"))?;
} else {
// 其他类型直接复制
fs::copy(source, &target_path).map_err(|e| format!("复制凭证文件失败: {}", e))?;
fs::copy(source, &target_path).map_err(|e| format!("复制凭证文件失败: {e}"))?;
}
// 返回新的文件路径
@@ -235,7 +235,7 @@ fn cleanup_credential_file(file_path: &str) -> Result<(), String> {
if canonical_path.starts_with(canonical_dir) {
if let Err(e) = fs::remove_file(&canonical_path) {
// 只记录警告,不中断删除过程
println!("Warning: Failed to delete credential file: {}", e);
println!("Warning: Failed to delete credential file: {e}");
}
}
}
@@ -322,7 +322,7 @@ pub fn update_provider_pool_credential(
let conn = db.lock().map_err(|e| e.to_string())?;
let current_credential = ProviderPoolDao::get_by_uuid(&conn, &uuid)
.map_err(|e| e.to_string())?
.ok_or_else(|| format!("凭证不存在: {}", uuid))?;
.ok_or_else(|| format!("凭证不存在: {uuid}"))?;
// 根据凭证类型复制新文件
let new_stored_path = match &current_credential.credential {
@@ -413,7 +413,7 @@ pub fn update_provider_pool_credential(
let conn = db.lock().map_err(|e| e.to_string())?;
let mut current_credential = ProviderPoolDao::get_by_uuid(&conn, &uuid)
.map_err(|e| e.to_string())?
.ok_or_else(|| format!("凭证不存在: {}", uuid))?;
.ok_or_else(|| format!("凭证不存在: {uuid}"))?;
// 更新 api_key 和 base_url
match &mut current_credential.credential {
@@ -647,7 +647,7 @@ pub fn add_kiro_oauth_credential(
fn create_kiro_credential_from_json(json_content: &str) -> Result<String, String> {
// 验证 JSON 格式
let creds: serde_json::Value =
serde_json::from_str(json_content).map_err(|e| format!("JSON 格式无效: {}", e))?;
serde_json::from_str(json_content).map_err(|e| format!("JSON 格式无效: {e}"))?;
// 验证必要字段
if creds.get("refreshToken").is_none() {
@@ -699,7 +699,7 @@ fn create_kiro_credential_from_json(json_content: &str) -> Result<String, String
// 方式1:如果有 clientIdHash,读取对应文件
if let Some(hash) = merged_creds.get("clientIdHash").and_then(|v| v.as_str()) {
let hash_file_path = aws_sso_cache_dir.join(format!("{}.json", hash));
let hash_file_path = aws_sso_cache_dir.join(format!("{hash}.json"));
if hash_file_path.exists() {
if let Ok(hash_content) = fs::read_to_string(&hash_file_path) {
@@ -784,9 +784,9 @@ fn create_kiro_credential_from_json(json_content: &str) -> Result<String, String
}
// 写入凭证文件
let merged_content = serde_json::to_string_pretty(&merged_creds)
.map_err(|e| format!("序列化凭证失败: {}", e))?;
fs::write(&target_path, merged_content).map_err(|e| format!("写入凭证文件失败: {}", e))?;
let merged_content =
serde_json::to_string_pretty(&merged_creds).map_err(|e| format!("序列化凭证失败: {e}"))?;
fs::write(&target_path, merged_content).map_err(|e| format!("写入凭证文件失败: {e}"))?;
tracing::info!("[KIRO] 凭证文件已创建: {:?}", target_path);
@@ -1061,7 +1061,7 @@ pub async fn debug_kiro_credentials() -> Result<String, String> {
// P0 安全修复:不再输出敏感信息(clientIdHash、token 前缀等)
let detected_method = provider.detect_auth_method();
result.push_str(&format!("🎯 检测到的认证方式: {}\n", detected_method));
result.push_str(&format!("🎯 检测到的认证方式: {detected_method}\n"));
result.push_str("\n🚀 尝试刷新 token...\n");
match provider.refresh_token().await {
@@ -1070,12 +1070,12 @@ pub async fn debug_kiro_credentials() -> Result<String, String> {
// 不再输出 token 前缀
}
Err(e) => {
result.push_str(&format!("❌ Token 刷新失败: {}\n", e));
result.push_str(&format!("❌ Token 刷新失败: {e}\n"));
}
}
}
Err(e) => {
result.push_str(&format!("❌ 凭证加载失败: {}\n", e));
result.push_str(&format!("❌ 凭证加载失败: {e}\n"));
}
}
@@ -1139,12 +1139,12 @@ pub async fn test_user_credentials() -> Result<String, String> {
json.get("clientIdHash").and_then(|v| v.as_str()).is_some();
let region = json.get("region").and_then(|v| v.as_str());
result.push_str(&format!("🔑 有 accessToken: {}\n", has_access_token));
result.push_str(&format!("🔄 有 refreshToken: {}\n", has_refresh_token));
result.push_str(&format!("📄 authMethod: {:?}\n", auth_method));
result.push_str(&format!("🔑 有 accessToken: {has_access_token}\n"));
result.push_str(&format!("🔄 有 refreshToken: {has_refresh_token}\n"));
result.push_str(&format!("📄 authMethod: {auth_method:?}\n"));
// P0 安全修复:不输出 clientIdHash 值
result.push_str(&format!("🏷️ 有 clientIdHash: {}\n", has_client_id_hash));
result.push_str(&format!("🌍 region: {:?}\n", region));
result.push_str(&format!("🏷️ 有 clientIdHash: {has_client_id_hash}\n"));
result.push_str(&format!("🌍 region: {region:?}\n"));
// 使用 KiroProvider 测试加载
result.push_str("\n🔧 使用 KiroProvider 测试加载...\n");
@@ -1172,7 +1172,7 @@ pub async fn test_user_credentials() -> Result<String, String> {
));
let detected_method = provider.detect_auth_method();
result.push_str(&format!("🎯 检测到的认证方式: {}\n", detected_method));
result.push_str(&format!("🎯 检测到的认证方式: {detected_method}\n"));
result.push_str("\n🚀 尝试刷新 token...\n");
match provider.refresh_token().await {
@@ -1184,22 +1184,22 @@ pub async fn test_user_credentials() -> Result<String, String> {
// P0 安全修复:不输出 token 前缀
}
Err(e) => {
result.push_str(&format!("❌ Token 刷新失败: {}\n", e));
result.push_str(&format!("❌ Token 刷新失败: {e}\n"));
}
}
}
Err(e) => {
result.push_str(&format!("❌ KiroProvider 加载失败: {}\n", e));
result.push_str(&format!("❌ KiroProvider 加载失败: {e}\n"));
}
}
}
Err(e) => {
result.push_str(&format!("❌ JSON 格式无效: {}\n", e));
result.push_str(&format!("❌ JSON 格式无效: {e}\n"));
}
}
}
Err(e) => {
result.push_str(&format!("❌ 无法读取凭证文件: {}\n", e));
result.push_str(&format!("❌ 无法读取凭证文件: {e}\n"));
}
}
@@ -1267,7 +1267,7 @@ pub async fn get_antigravity_auth_url_and_wait(
let (auth_url, wait_future) =
antigravity::start_oauth_server_and_get_url(skip_project_id_fetch.unwrap_or(false))
.await
.map_err(|e| format!("启动 OAuth 服务器失败: {}", e))?;
.map_err(|e| format!("启动 OAuth 服务器失败: {e}"))?;
tracing::info!("[Antigravity OAuth] 授权 URL: {}", auth_url);
@@ -1328,7 +1328,7 @@ pub async fn start_antigravity_oauth_login(
// 启动 OAuth 登录
let result = antigravity::start_oauth_login(skip_project_id_fetch.unwrap_or(false))
.await
.map_err(|e| format!("Antigravity OAuth 登录失败: {}", e))?;
.map_err(|e| format!("Antigravity OAuth 登录失败: {e}"))?;
tracing::info!(
"[Antigravity OAuth] 登录成功,凭证保存到: {}",
@@ -1383,7 +1383,7 @@ pub async fn get_codex_auth_url_and_wait(
// 启动服务器并获取授权 URL
let (auth_url, wait_future) = codex::start_codex_oauth_server_and_get_url()
.await
.map_err(|e| format!("启动 OAuth 服务器失败: {}", e))?;
.map_err(|e| format!("启动 OAuth 服务器失败: {e}"))?;
tracing::info!("[Codex OAuth] 授权 URL: {}", auth_url);
@@ -1437,7 +1437,7 @@ pub async fn start_codex_oauth_login(
// 启动 OAuth 登录
let result = codex::start_codex_oauth_login()
.await
.map_err(|e| format!("Codex OAuth 登录失败: {}", e))?;
.map_err(|e| format!("Codex OAuth 登录失败: {e}"))?;
tracing::info!(
"[Codex OAuth] 登录成功,凭证保存到: {}",
@@ -1499,7 +1499,7 @@ pub async fn get_claude_oauth_auth_url_and_wait(
// 生成授权参数
let params = claude_oauth::generate_claude_oauth_params()
.map_err(|e| format!("生成授权参数失败: {}", e))?;
.map_err(|e| format!("生成授权参数失败: {e}"))?;
tracing::info!("[Claude OAuth] 授权 URL: {}", params.auth_url);
@@ -1546,7 +1546,7 @@ pub async fn exchange_claude_oauth_code(
&state,
)
.await
.map_err(|e| format!("Claude OAuth Token 交换失败: {}", e))?;
.map_err(|e| format!("Claude OAuth Token 交换失败: {e}"))?;
tracing::info!(
"[Claude OAuth] 登录成功,凭证保存到: {}",
@@ -1587,7 +1587,7 @@ pub async fn start_claude_oauth_login(
// 生成授权参数并打开浏览器
let params = claude_oauth::start_claude_oauth_login()
.await
.map_err(|e| format!("Claude OAuth 登录失败: {}", e))?;
.map_err(|e| format!("Claude OAuth 登录失败: {e}"))?;
Ok(ClaudeOAuthParamsResponse {
auth_url: params.auth_url,
@@ -1631,7 +1631,7 @@ pub async fn claude_oauth_with_cookie(
// 执行 Cookie 自动授权
let result = claude_oauth::oauth_with_cookie(&session_key, is_setup)
.await
.map_err(|e| format!("Claude Cookie 授权失败: {}", e))?;
.map_err(|e| format!("Claude Cookie 授权失败: {e}"))?;
tracing::info!(
"[Claude OAuth] Cookie 授权成功,凭证保存到: {}",
@@ -1688,7 +1688,7 @@ pub async fn get_kiro_credential_fingerprint(
let conn = db.lock().map_err(|e| e.to_string())?;
let credential = ProviderPoolDao::get_by_uuid(&conn, &uuid)
.map_err(|e| e.to_string())?
.ok_or_else(|| format!("凭证不存在: {}", uuid))?;
.ok_or_else(|| format!("凭证不存在: {uuid}"))?;
// 检查是否为 Kiro 凭证
match &credential.credential {
@@ -1702,7 +1702,7 @@ pub async fn get_kiro_credential_fingerprint(
provider
.load_credentials_from_path(&creds_file_path)
.await
.map_err(|e| format!("加载凭证失败: {}", e))?;
.map_err(|e| format!("加载凭证失败: {e}"))?;
// 确定指纹来源
let (source, profile_arn, client_id) = if provider.credentials.profile_arn.is_some() {
@@ -1800,7 +1800,7 @@ pub async fn get_gemini_auth_url_and_wait(
// 返回错误,让前端知道需要用户手动输入授权码
// 这不是真正的错误,只是流程需要用户交互
Err(format!("AUTH_URL:{}", auth_url))
Err(format!("AUTH_URL:{auth_url}"))
}
/// 用 Gemini 授权码交换 Token 并添加凭证
@@ -1836,7 +1836,7 @@ pub async fn exchange_gemini_code(
// 交换 token 并创建凭证
let result = gemini::exchange_gemini_code_and_create_credentials(&code, &code_verifier)
.await
.map_err(|e| format!("交换授权码失败: {}", e))?;
.map_err(|e| format!("交换授权码失败: {e}"))?;
tracing::info!(
"[Gemini OAuth] 登录成功,凭证保存到: {}",
@@ -1883,7 +1883,7 @@ pub async fn start_gemini_oauth_login(
// 启动 OAuth 登录
let result = gemini::start_gemini_oauth_login()
.await
.map_err(|e| format!("Gemini OAuth 登录失败: {}", e))?;
.map_err(|e| format!("Gemini OAuth 登录失败: {e}"))?;
tracing::info!(
"[Gemini OAuth] 登录成功,凭证保存到: {}",
@@ -1976,7 +1976,7 @@ pub async fn start_kiro_builder_id_login(
region: Option<String>,
) -> Result<KiroBuilderIdLoginResponse, String> {
let region = region.unwrap_or_else(|| "us-east-1".to_string());
let oidc_base = format!("https://oidc.{}.amazonaws.com", region);
let oidc_base = format!("https://oidc.{region}.amazonaws.com");
let start_url = "https://view.awsapps.com/start";
let scopes = vec![
"codewhisperer:completions",
@@ -2001,12 +2001,12 @@ pub async fn start_kiro_builder_id_login(
});
let reg_res = client
.post(format!("{}/client/register", oidc_base))
.post(format!("{oidc_base}/client/register"))
.header("Content-Type", "application/json")
.json(&reg_body)
.send()
.await
.map_err(|e| format!("注册客户端请求失败: {}", e))?;
.map_err(|e| format!("注册客户端请求失败: {e}"))?;
if !reg_res.status().is_success() {
let err_text = reg_res.text().await.unwrap_or_default();
@@ -2016,14 +2016,14 @@ pub async fn start_kiro_builder_id_login(
verification_uri: None,
expires_in: None,
interval: None,
error: Some(format!("注册客户端失败: {}", err_text)),
error: Some(format!("注册客户端失败: {err_text}")),
});
}
let reg_data: serde_json::Value = reg_res
.json()
.await
.map_err(|e| format!("解析注册响应失败: {}", e))?;
.map_err(|e| format!("解析注册响应失败: {e}"))?;
let client_id = reg_data["clientId"]
.as_str()
@@ -2048,12 +2048,12 @@ pub async fn start_kiro_builder_id_login(
});
let auth_res = client
.post(format!("{}/device_authorization", oidc_base))
.post(format!("{oidc_base}/device_authorization"))
.header("Content-Type", "application/json")
.json(&auth_body)
.send()
.await
.map_err(|e| format!("设备授权请求失败: {}", e))?;
.map_err(|e| format!("设备授权请求失败: {e}"))?;
if !auth_res.status().is_success() {
let err_text = auth_res.text().await.unwrap_or_default();
@@ -2063,14 +2063,14 @@ pub async fn start_kiro_builder_id_login(
verification_uri: None,
expires_in: None,
interval: None,
error: Some(format!("设备授权失败: {}", err_text)),
error: Some(format!("设备授权失败: {err_text}")),
});
}
let auth_data: serde_json::Value = auth_res
.json()
.await
.map_err(|e| format!("解析授权响应失败: {}", e))?;
.map_err(|e| format!("解析授权响应失败: {e}"))?;
let device_code = auth_data["deviceCode"]
.as_str()
@@ -2160,12 +2160,12 @@ pub async fn poll_kiro_builder_id_auth() -> Result<KiroBuilderIdPollResponse, St
});
let token_res = client
.post(format!("{}/token", oidc_base))
.post(format!("{oidc_base}/token"))
.header("Content-Type", "application/json")
.json(&token_body)
.send()
.await
.map_err(|e| format!("Token 请求失败: {}", e))?;
.map_err(|e| format!("Token 请求失败: {e}"))?;
let status = token_res.status();
@@ -2174,7 +2174,7 @@ pub async fn poll_kiro_builder_id_auth() -> Result<KiroBuilderIdPollResponse, St
let token_data: serde_json::Value = token_res
.json()
.await
.map_err(|e| format!("解析 Token 响应失败: {}", e))?;
.map_err(|e| format!("解析 Token 响应失败: {e}"))?;
tracing::info!("[Kiro Builder ID] 授权成功!");
@@ -2220,7 +2220,7 @@ pub async fn poll_kiro_builder_id_auth() -> Result<KiroBuilderIdPollResponse, St
let err_data: serde_json::Value = token_res
.json()
.await
.map_err(|e| format!("解析错误响应失败: {}", e))?;
.map_err(|e| format!("解析错误响应失败: {e}"))?;
let error = err_data["error"].as_str().unwrap_or("unknown");
@@ -2282,7 +2282,7 @@ pub async fn poll_kiro_builder_id_auth() -> Result<KiroBuilderIdPollResponse, St
success: false,
completed: false,
status: None,
error: Some(format!("授权错误: {}", error)),
error: Some(format!("授权错误: {error}")),
})
}
}
@@ -2291,7 +2291,7 @@ pub async fn poll_kiro_builder_id_auth() -> Result<KiroBuilderIdPollResponse, St
success: false,
completed: false,
status: None,
error: Some(format!("未知响应: {}", status)),
error: Some(format!("未知响应: {status}")),
})
}
}
@@ -2332,7 +2332,7 @@ pub async fn add_kiro_from_builder_id_auth(
// 将凭证 JSON 转换为字符串
let json_content =
serde_json::to_string_pretty(&creds_json).map_err(|e| format!("序列化凭证失败: {}", e))?;
serde_json::to_string_pretty(&creds_json).map_err(|e| format!("序列化凭证失败: {e}"))?;
// 使用现有的 create_kiro_credential_from_json 函数创建凭证文件
let stored_file_path = create_kiro_credential_from_json(&json_content)?;
@@ -2449,7 +2449,7 @@ pub async fn start_kiro_social_auth_login(
success: false,
login_url: None,
state: None,
error: Some(format!("不支持的登录提供商: {}", provider)),
error: Some(format!("不支持的登录提供商: {provider}")),
});
}
};
@@ -2558,12 +2558,12 @@ pub async fn exchange_kiro_social_auth_token(
});
let token_res = client
.post(format!("{}/oauth/token", KIRO_AUTH_ENDPOINT))
.post(format!("{KIRO_AUTH_ENDPOINT}/oauth/token"))
.header("Content-Type", "application/json")
.json(&token_body)
.send()
.await
.map_err(|e| format!("Token 交换请求失败: {}", e))?;
.map_err(|e| format!("Token 交换请求失败: {e}"))?;
if !token_res.status().is_success() {
let err_text = token_res.text().await.unwrap_or_default();
@@ -2574,14 +2574,14 @@ pub async fn exchange_kiro_social_auth_token(
}
return Ok(KiroSocialAuthTokenResponse {
success: false,
error: Some(format!("Token 交换失败: {}", err_text)),
error: Some(format!("Token 交换失败: {err_text}")),
});
}
let token_data: serde_json::Value = token_res
.json()
.await
.map_err(|e| format!("解析 Token 响应失败: {}", e))?;
.map_err(|e| format!("解析 Token 响应失败: {e}"))?;
tracing::info!("[Kiro Social Auth] Token 交换成功!");
@@ -2917,7 +2917,7 @@ pub async fn install_playwright(app: tauri::AppHandle) -> Result<PlaywrightStatu
"找不到 Playwright 脚本目录。已检查路径:\n{}",
possible_paths
.iter()
.map(|p| format!(" - {:?}", p))
.map(|p| format!(" - {p:?}"))
.collect::<Vec<_>>()
.join("\n")
);
@@ -2998,7 +2998,7 @@ pub async fn install_playwright(app: tauri::AppHandle) -> Result<PlaywrightStatu
return Err(error);
}
Err(e) => {
let error = format!("npm install 执行失败: {}", e);
let error = format!("npm install 执行失败: {e}");
tracing::error!("[Playwright] {}", error);
let _ = app.emit(
"playwright-install-progress",
@@ -3013,7 +3013,7 @@ pub async fn install_playwright(app: tauri::AppHandle) -> Result<PlaywrightStatu
}
}
Err(e) => {
let error = format!("无法启动 npm: {}。请确保已安装 Node.js", e);
let error = format!("无法启动 npm: {e}。请确保已安装 Node.js");
tracing::error!("[Playwright] {}", error);
let _ = app.emit(
"playwright-install-progress",
@@ -3099,7 +3099,7 @@ pub async fn install_playwright(app: tauri::AppHandle) -> Result<PlaywrightStatu
} else {
format!("退出码: {:?}", s.code())
};
let error = format!("Chromium 安装失败: {}", output);
let error = format!("Chromium 安装失败: {output}");
tracing::error!("[Playwright] {}", error);
let _ = app.emit(
"playwright-install-progress",
@@ -3112,7 +3112,7 @@ pub async fn install_playwright(app: tauri::AppHandle) -> Result<PlaywrightStatu
return Err(error);
}
Err(e) => {
let error = format!("Chromium 安装执行失败: {}", e);
let error = format!("Chromium 安装执行失败: {e}");
tracing::error!("[Playwright] {}", error);
let _ = app.emit(
"playwright-install-progress",
@@ -3127,7 +3127,7 @@ pub async fn install_playwright(app: tauri::AppHandle) -> Result<PlaywrightStatu
}
}
Err(e) => {
let error = format!("无法启动 npx: {}", e);
let error = format!("无法启动 npx: {e}");
tracing::error!("[Playwright] {}", error);
let _ = app.emit(
"playwright-install-progress",
@@ -3244,7 +3244,7 @@ pub async fn start_kiro_playwright_login(
"github" => "Github",
"builderid" => "BuilderId",
_ => {
return Err(format!("不支持的登录提供商: {}", provider));
return Err(format!("不支持的登录提供商: {provider}"));
}
};
@@ -3279,7 +3279,7 @@ pub async fn start_kiro_playwright_login(
// 获取脚本路径
let script_path = get_playwright_script_path();
if !script_path.exists() {
return Err(format!("Playwright 登录脚本不存在: {:?}", script_path));
return Err(format!("Playwright 登录脚本不存在: {script_path:?}"));
}
tracing::info!("[Playwright Login] 脚本路径: {:?}", script_path);
@@ -3292,7 +3292,7 @@ pub async fn start_kiro_playwright_login(
.stderr(std::process::Stdio::piped())
.kill_on_drop(true)
.spawn()
.map_err(|e| format!("启动 Playwright 进程失败: {}", e))?;
.map_err(|e| format!("启动 Playwright 进程失败: {e}"))?;
let stdin = child.stdin.take().ok_or("无法获取 stdin")?;
let stdout = child.stdout.take().ok_or("无法获取 stdout")?;
@@ -3311,10 +3311,10 @@ pub async fn start_kiro_playwright_login(
reader
.read_line(&mut line)
.await
.map_err(|e| format!("读取就绪信号失败: {}", e))?;
.map_err(|e| format!("读取就绪信号失败: {e}"))?;
let ready_response: serde_json::Value =
serde_json::from_str(&line.trim()).map_err(|e| format!("解析就绪信号失败: {}", e))?;
serde_json::from_str(line.trim()).map_err(|e| format!("解析就绪信号失败: {e}"))?;
if ready_response.get("action").and_then(|v| v.as_str()) != Some("ready") {
return Err("Playwright 脚本未就绪".to_string());
@@ -3331,20 +3331,20 @@ pub async fn start_kiro_playwright_login(
});
let request_str =
serde_json::to_string(&login_request).map_err(|e| format!("序列化请求失败: {}", e))?;
serde_json::to_string(&login_request).map_err(|e| format!("序列化请求失败: {e}"))?;
stdin
.write_all(request_str.as_bytes())
.await
.map_err(|e| format!("发送请求失败: {}", e))?;
.map_err(|e| format!("发送请求失败: {e}"))?;
stdin
.write_all(b"\n")
.await
.map_err(|e| format!("发送换行失败: {}", e))?;
.map_err(|e| format!("发送换行失败: {e}"))?;
stdin
.flush()
.await
.map_err(|e| format!("刷新 stdin 失败: {}", e))?;
.map_err(|e| format!("刷新 stdin 失败: {e}"))?;
tracing::info!("[Playwright Login] 已发送登录请求");
@@ -3418,7 +3418,7 @@ pub async fn start_kiro_playwright_login(
*process_guard = None;
}
return Err(format!("Playwright 登录失败: {}", error));
return Err(format!("Playwright 登录失败: {error}"));
}
break;
}
@@ -3435,7 +3435,7 @@ pub async fn start_kiro_playwright_login(
*process_guard = None;
}
return Err(format!("Playwright 错误: {}", error));
return Err(format!("Playwright 错误: {error}"));
}
_ => {}
}
@@ -3451,7 +3451,7 @@ pub async fn start_kiro_playwright_login(
let mut process_guard = PLAYWRIGHT_LOGIN_PROCESS.write().await;
*process_guard = None;
}
return Err(format!("读取响应失败: {}", e));
return Err(format!("读取响应失败: {e}"));
}
}
}
@@ -3483,22 +3483,22 @@ pub async fn start_kiro_playwright_login(
});
let token_res = client
.post(format!("{}/oauth/token", KIRO_AUTH_ENDPOINT))
.post(format!("{KIRO_AUTH_ENDPOINT}/oauth/token"))
.header("Content-Type", "application/json")
.json(&token_body)
.send()
.await
.map_err(|e| format!("Token 交换请求失败: {}", e))?;
.map_err(|e| format!("Token 交换请求失败: {e}"))?;
if !token_res.status().is_success() {
let err_text = token_res.text().await.unwrap_or_default();
return Err(format!("Token 交换失败: {}", err_text));
return Err(format!("Token 交换失败: {err_text}"));
}
let token_data: serde_json::Value = token_res
.json()
.await
.map_err(|e| format!("解析 Token 响应失败: {}", e))?;
.map_err(|e| format!("解析 Token 响应失败: {e}"))?;
tracing::info!("[Playwright Login] Token 交换成功!");
@@ -3524,7 +3524,7 @@ pub async fn start_kiro_playwright_login(
// 将凭证 JSON 转换为字符串并创建凭证文件
let json_content =
serde_json::to_string_pretty(&creds_json).map_err(|e| format!("序列化凭证失败: {}", e))?;
serde_json::to_string_pretty(&creds_json).map_err(|e| format!("序列化凭证失败: {e}"))?;
let stored_file_path = create_kiro_credential_from_json(&json_content)?;
@@ -3598,7 +3598,7 @@ pub async fn start_kiro_social_auth_callback_server(app: tauri::AppHandle) -> Re
// 尝试绑定端口
let listener = TcpListener::bind("127.0.0.1:19823")
.await
.map_err(|e| format!("无法启动回调服务器: {}", e))?;
.map_err(|e| format!("无法启动回调服务器: {e}"))?;
tracing::info!("[Kiro Social Auth] 回调服务器已启动在 127.0.0.1:19823");
@@ -3717,16 +3717,14 @@ mod playwright_tests {
// 路径应该包含 ms-playwright
assert!(
cache_dir.to_string_lossy().contains("ms-playwright"),
"缓存目录应包含 ms-playwright: {:?}",
cache_dir
"缓存目录应包含 ms-playwright: {cache_dir:?}"
);
// 路径应该是绝对路径或相对于 home 目录
#[cfg(target_os = "macos")]
assert!(
cache_dir.to_string_lossy().contains("Library/Caches"),
"macOS 缓存目录应在 Library/Caches 下: {:?}",
cache_dir
"macOS 缓存目录应在 Library/Caches 下: {cache_dir:?}"
);
#[cfg(target_os = "windows")]
@@ -3762,8 +3760,7 @@ mod playwright_tests {
path.contains("chromium")
|| path.contains("Chromium")
|| path.contains("chrome"),
"路径应包含 chromium/chrome: {}",
path
"路径应包含 chromium/chrome: {path}"
);
}
None => {
+2 -2
View File
@@ -39,12 +39,12 @@ pub async fn get_available_routes(
crate::models::route_model::RouteEndpoint {
path: "/v1/messages".to_string(),
protocol: "claude".to_string(),
url: format!("{}/v1/messages", base_url),
url: format!("{base_url}/v1/messages"),
},
crate::models::route_model::RouteEndpoint {
path: "/v1/chat/completions".to_string(),
protocol: "openai".to_string(),
url: format!("{}/v1/chat/completions", base_url),
url: format!("{base_url}/v1/chat/completions"),
},
],
tags: vec!["默认".to_string()],
+13 -14
View File
@@ -77,7 +77,7 @@ pub async fn save_experimental_config(
debug!("开始保存配置到文件...");
if let Err(e) = config_manager.save_config(&new_config).await {
error!("保存配置失败: {}", e);
return Err(format!("保存配置失败: {}", e));
return Err(format!("保存配置失败: {e}"));
}
info!("配置文件保存成功");
@@ -91,14 +91,14 @@ pub async fn save_experimental_config(
if let Err(e) = shortcut::register(&app, &experimental_config.screenshot_chat.shortcut)
{
error!("注册快捷键失败: {}", e);
return Err(format!("注册快捷键失败: {}", e));
return Err(format!("注册快捷键失败: {e}"));
}
info!("快捷键注册成功");
} else {
info!("截图对话功能已禁用,注销快捷键");
if let Err(e) = shortcut::unregister(&app) {
error!("注销快捷键失败: {}", e);
return Err(format!("注销快捷键失败: {}", e));
return Err(format!("注销快捷键失败: {e}"));
}
info!("快捷键注销成功");
}
@@ -134,7 +134,7 @@ pub async fn start_screenshot(app: AppHandle) -> Result<String, String> {
}
Err(e) => {
error!("截图失败: {}", e);
Err(format!("截图失败: {}", e))
Err(format!("截图失败: {e}"))
}
}
}
@@ -156,7 +156,7 @@ pub fn validate_shortcut(shortcut_str: String) -> Result<bool, String> {
match shortcut::validate(&shortcut_str) {
Ok(()) => Ok(true),
Err(e) => Err(format!("{}", e)),
Err(e) => Err(format!("{e}")),
}
}
@@ -183,7 +183,7 @@ pub async fn update_screenshot_shortcut(
info!("更新截图快捷键: {}", new_shortcut);
// 验证新快捷键格式
shortcut::validate(&new_shortcut).map_err(|e| format!("快捷键格式无效: {}", e))?;
shortcut::validate(&new_shortcut).map_err(|e| format!("快捷键格式无效: {e}"))?;
// 获取当前配置
let mut config = config_manager.config();
@@ -191,7 +191,7 @@ pub async fn update_screenshot_shortcut(
// 检查功能是否启用
if config.experimental.screenshot_chat.enabled {
// 更新快捷键(原子操作)
shortcut::update(&app, &new_shortcut).map_err(|e| format!("更新快捷键失败: {}", e))?;
shortcut::update(&app, &new_shortcut).map_err(|e| format!("更新快捷键失败: {e}"))?;
}
// 更新配置
@@ -201,7 +201,7 @@ pub async fn update_screenshot_shortcut(
config_manager
.save_config(&config)
.await
.map_err(|e| format!("保存配置失败: {}", e))?;
.map_err(|e| format!("保存配置失败: {e}"))?;
info!("截图快捷键更新成功");
Ok(())
@@ -220,8 +220,7 @@ pub async fn update_screenshot_shortcut(
pub fn close_screenshot_chat_window(app: AppHandle) -> Result<(), String> {
info!("关闭截图对话窗口");
crate::screenshot::window::close_floating_window(&app)
.map_err(|e| format!("关闭窗口失败: {}", e))
crate::screenshot::window::close_floating_window(&app).map_err(|e| format!("关闭窗口失败: {e}"))
}
/// 打开带预填文本的输入框
@@ -239,7 +238,7 @@ pub fn open_input_with_text(app: AppHandle, text: String) -> Result<(), String>
info!("打开带预填文本的输入框: {} 字符", text.len());
crate::screenshot::window::open_floating_window_with_text(&app, &text)
.map_err(|e| format!("打开窗口失败: {}", e))
.map_err(|e| format!("打开窗口失败: {e}"))
}
/// 读取图片文件并转换为 Base64
@@ -267,7 +266,7 @@ pub async fn read_image_as_base64(path: String) -> Result<String, String> {
// 读取文件内容
let bytes = fs::read(path)
.await
.map_err(|e| format!("读取文件失败: {}", e))?;
.map_err(|e| format!("读取文件失败: {e}"))?;
// 检查文件是否为空
if bytes.is_empty() {
@@ -335,7 +334,7 @@ pub async fn send_screenshot_chat(
if let Some(main_window) = app.get_webview_window("main") {
main_window
.emit("smart-input-message", &chat_message)
.map_err(|e| format!("发送事件失败: {}", e))?;
.map_err(|e| format!("发送事件失败: {e}"))?;
// 恢复并聚焦主窗口(主窗口在截图时被最小化)
let _ = main_window.unminimize();
@@ -344,7 +343,7 @@ pub async fn send_screenshot_chat(
} else {
// 尝试发送到所有窗口
app.emit("smart-input-message", &chat_message)
.map_err(|e| format!("发送事件失败: {}", e))?;
.map_err(|e| format!("发送事件失败: {e}"))?;
}
info!("截图对话消息已发送");
+13 -13
View File
@@ -21,7 +21,7 @@ pub fn session_files_create(
state: State<SessionFilesState>,
session_id: String,
) -> Result<SessionMeta, String> {
let storage = state.0.lock().map_err(|e| format!("锁定失败: {}", e))?;
let storage = state.0.lock().map_err(|e| format!("锁定失败: {e}"))?;
storage.create_session(&session_id)
}
@@ -31,7 +31,7 @@ pub fn session_files_exists(
state: State<SessionFilesState>,
session_id: String,
) -> Result<bool, String> {
let storage = state.0.lock().map_err(|e| format!("锁定失败: {}", e))?;
let storage = state.0.lock().map_err(|e| format!("锁定失败: {e}"))?;
Ok(storage.session_exists(&session_id))
}
@@ -41,7 +41,7 @@ pub fn session_files_get_or_create(
state: State<SessionFilesState>,
session_id: String,
) -> Result<SessionMeta, String> {
let storage = state.0.lock().map_err(|e| format!("锁定失败: {}", e))?;
let storage = state.0.lock().map_err(|e| format!("锁定失败: {e}"))?;
storage.get_or_create_session(&session_id)
}
@@ -51,14 +51,14 @@ pub fn session_files_delete(
state: State<SessionFilesState>,
session_id: String,
) -> Result<(), String> {
let storage = state.0.lock().map_err(|e| format!("锁定失败: {}", e))?;
let storage = state.0.lock().map_err(|e| format!("锁定失败: {e}"))?;
storage.delete_session(&session_id)
}
/// 列出所有会话
#[tauri::command]
pub fn session_files_list(state: State<SessionFilesState>) -> Result<Vec<SessionSummary>, String> {
let storage = state.0.lock().map_err(|e| format!("锁定失败: {}", e))?;
let storage = state.0.lock().map_err(|e| format!("锁定失败: {e}"))?;
storage.list_sessions()
}
@@ -68,7 +68,7 @@ pub fn session_files_get_detail(
state: State<SessionFilesState>,
session_id: String,
) -> Result<SessionDetail, String> {
let storage = state.0.lock().map_err(|e| format!("锁定失败: {}", e))?;
let storage = state.0.lock().map_err(|e| format!("锁定失败: {e}"))?;
storage.get_session_detail(&session_id)
}
@@ -81,7 +81,7 @@ pub fn session_files_update_meta(
theme: Option<String>,
creation_mode: Option<String>,
) -> Result<SessionMeta, String> {
let storage = state.0.lock().map_err(|e| format!("锁定失败: {}", e))?;
let storage = state.0.lock().map_err(|e| format!("锁定失败: {e}"))?;
storage.update_meta(&session_id, title, theme, creation_mode)
}
@@ -97,7 +97,7 @@ pub fn session_files_save_file(
file_name: String,
content: String,
) -> Result<SessionFile, String> {
let storage = state.0.lock().map_err(|e| format!("锁定失败: {}", e))?;
let storage = state.0.lock().map_err(|e| format!("锁定失败: {e}"))?;
storage.save_file(&session_id, &file_name, &content)
}
@@ -108,7 +108,7 @@ pub fn session_files_read_file(
session_id: String,
file_name: String,
) -> Result<String, String> {
let storage = state.0.lock().map_err(|e| format!("锁定失败: {}", e))?;
let storage = state.0.lock().map_err(|e| format!("锁定失败: {e}"))?;
storage.read_file(&session_id, &file_name)
}
@@ -119,7 +119,7 @@ pub fn session_files_delete_file(
session_id: String,
file_name: String,
) -> Result<(), String> {
let storage = state.0.lock().map_err(|e| format!("锁定失败: {}", e))?;
let storage = state.0.lock().map_err(|e| format!("锁定失败: {e}"))?;
storage.delete_file(&session_id, &file_name)
}
@@ -129,7 +129,7 @@ pub fn session_files_list_files(
state: State<SessionFilesState>,
session_id: String,
) -> Result<Vec<SessionFile>, String> {
let storage = state.0.lock().map_err(|e| format!("锁定失败: {}", e))?;
let storage = state.0.lock().map_err(|e| format!("锁定失败: {e}"))?;
storage.list_files(&session_id)
}
@@ -143,13 +143,13 @@ pub fn session_files_cleanup_expired(
state: State<SessionFilesState>,
max_age_days: Option<u32>,
) -> Result<u32, String> {
let storage = state.0.lock().map_err(|e| format!("锁定失败: {}", e))?;
let storage = state.0.lock().map_err(|e| format!("锁定失败: {e}"))?;
storage.cleanup_expired(max_age_days.unwrap_or(30))
}
/// 清理空会话
#[tauri::command]
pub fn session_files_cleanup_empty(state: State<SessionFilesState>) -> Result<u32, String> {
let storage = state.0.lock().map_err(|e| format!("锁定失败: {}", e))?;
let storage = state.0.lock().map_err(|e| format!("锁定失败: {e}"))?;
storage.cleanup_empty()
}
+1 -1
View File
@@ -149,7 +149,7 @@ pub async fn install_skill_for_app(
let skill = skills
.iter()
.find(|s| s.directory == directory)
.ok_or_else(|| format!("Skill not found: {}", directory))?;
.ok_or_else(|| format!("Skill not found: {directory}"))?;
let repo_owner = skill
.repo_owner
+5 -8
View File
@@ -74,7 +74,7 @@ pub fn check_config_sync_status(
// 解析 app_type
let app_type_enum: AppType = app_type
.parse()
.map_err(|e| format!("Invalid app type: {}", e))?;
.map_err(|e| format!("Invalid app type: {e}"))?;
// 获取当前 ProxyCast 中设置的 provider
let current_provider = SwitchService::get_current_provider(&db, &app_type)?
@@ -83,7 +83,7 @@ pub fn check_config_sync_status(
// 检查同步状态
check_config_sync(&app_type_enum, &current_provider)
.map_err(|e| format!("Failed to check config sync: {}", e))
.map_err(|e| format!("Failed to check config sync: {e}"))
}
/// 从外部配置同步到 ProxyCast
@@ -95,17 +95,14 @@ pub fn sync_from_external_config(
// 解析 app_type
let app_type_enum: AppType = app_type
.parse()
.map_err(|e| format!("Invalid app type: {}", e))?;
.map_err(|e| format!("Invalid app type: {e}"))?;
// 从外部配置获取 provider
let external_provider = sync_from_external(&app_type_enum)
.map_err(|e| format!("Failed to sync from external: {}", e))?;
.map_err(|e| format!("Failed to sync from external: {e}"))?;
// 切换到外部检测到的 provider
SwitchService::switch_provider(&db, &app_type, &external_provider)?;
Ok(format!(
"已同步到外部配置的 provider: {}",
external_provider
))
Ok(format!("已同步到外部配置的 provider: {external_provider}"))
}
+7 -7
View File
@@ -29,8 +29,8 @@ pub struct TelemetryState {
impl TelemetryState {
/// 创建独立的遥测状态(使用自己的实例)
pub fn new() -> Result<Self, String> {
let logger = RequestLogger::with_defaults()
.map_err(|e| format!("Failed to create logger: {}", e))?;
let logger =
RequestLogger::with_defaults().map_err(|e| format!("Failed to create logger: {e}"))?;
Ok(Self {
logger: Arc::new(logger),
@@ -52,7 +52,7 @@ impl TelemetryState {
Some(l) => l,
None => Arc::new(
RequestLogger::with_defaults()
.map_err(|e| format!("Failed to create logger: {}", e))?,
.map_err(|e| format!("Failed to create logger: {e}"))?,
),
};
@@ -102,7 +102,7 @@ pub async fn get_request_logs(
"timeout" => RequestStatus::Timeout,
"retrying" => RequestStatus::Retrying,
"cancelled" => RequestStatus::Cancelled,
_ => return Err(format!("Invalid status: {}", s)),
_ => return Err(format!("Invalid status: {s}")),
};
logs.retain(|l| l.status == req_status);
}
@@ -155,7 +155,7 @@ impl TimeRangeParam {
"24h" => TimeRange::last_hours(24),
"7d" => TimeRange::last_days(7),
"30d" => TimeRange::last_days(30),
_ => return Err(format!("Invalid preset: {}", preset)),
_ => return Err(format!("Invalid preset: {preset}")),
};
return Ok(Some(range));
}
@@ -163,10 +163,10 @@ impl TimeRangeParam {
match (&self.start, &self.end) {
(Some(s), Some(e)) => {
let start = DateTime::parse_from_rfc3339(s)
.map_err(|e| format!("Invalid start time: {}", e))?
.map_err(|e| format!("Invalid start time: {e}"))?
.with_timezone(&Utc);
let end = DateTime::parse_from_rfc3339(e)
.map_err(|e| format!("Invalid end time: {}", e))?
.map_err(|e| format!("Invalid end time: {e}"))?
.with_timezone(&Utc);
Ok(Some(TimeRange::new(start, end)))
}
+1 -1
View File
@@ -92,7 +92,7 @@ pub async fn update_tray_server_status(
// 更新服务器相关字段
current_state.server_running = server_running;
current_state.server_address = if server_running {
format!("{}:{}", server_host, server_port)
format!("{server_host}:{server_port}")
} else {
String::new()
};
+44 -21
View File
@@ -53,14 +53,17 @@ pub struct SendMessageRequest {
pub message: String,
/// 事件名称(用于前端监听)
pub event_name: String,
/// 图片输入(可选)
/// 图片输入(可选,用于多模态对话)
/// TODO: 实现图片处理逻辑,将图片转换为 Aster Message 的 ImageContent
pub images: Option<Vec<ImageInput>>,
}
/// 图片输入
#[derive(Debug, Deserialize)]
pub struct ImageInput {
/// Base64 编码的图片数据
pub data: String,
/// 图片 MIME 类型,如 "image/png", "image/jpeg"
pub media_type: String,
}
@@ -122,8 +125,8 @@ pub async fn chat_create_session(
// 保存到数据库
{
let conn = db.lock().map_err(|e| format!("数据库锁定失败: {}", e))?;
ChatDao::create_session(&conn, &session).map_err(|e| format!("创建会话失败: {}", e))?;
let conn = db.lock().map_err(|e| format!("数据库锁定失败: {e}"))?;
ChatDao::create_session(&conn, &session).map_err(|e| format!("创建会话失败: {e}"))?;
}
// 初始化 Aster Agent(如果是 Agent 或 Creator 模式)
@@ -155,10 +158,10 @@ pub async fn chat_list_sessions(
db: State<'_, DbConnection>,
mode: Option<ChatMode>,
) -> Result<Vec<SessionResponse>, String> {
let conn = db.lock().map_err(|e| format!("数据库锁定失败: {}", e))?;
let conn = db.lock().map_err(|e| format!("数据库锁定失败: {e}"))?;
let sessions =
ChatDao::list_sessions(&conn, mode).map_err(|e| format!("获取会话列表失败: {}", e))?;
ChatDao::list_sessions(&conn, mode).map_err(|e| format!("获取会话列表失败: {e}"))?;
let mut result: Vec<SessionResponse> = Vec::new();
for session in sessions {
@@ -177,10 +180,10 @@ pub async fn chat_get_session(
db: State<'_, DbConnection>,
session_id: String,
) -> Result<SessionResponse, String> {
let conn = db.lock().map_err(|e| format!("数据库锁定失败: {}", e))?;
let conn = db.lock().map_err(|e| format!("数据库锁定失败: {e}"))?;
let session = ChatDao::get_session(&conn, &session_id)
.map_err(|e| format!("获取会话失败: {}", e))?
.map_err(|e| format!("获取会话失败: {e}"))?
.ok_or_else(|| "会话不存在".to_string())?;
let message_count = ChatDao::get_message_count(&conn, &session_id).unwrap_or(0);
@@ -196,10 +199,10 @@ pub async fn chat_delete_session(
db: State<'_, DbConnection>,
session_id: String,
) -> Result<bool, String> {
let conn = db.lock().map_err(|e| format!("数据库锁定失败: {}", e))?;
let conn = db.lock().map_err(|e| format!("数据库锁定失败: {e}"))?;
let deleted =
ChatDao::delete_session(&conn, &session_id).map_err(|e| format!("删除会话失败: {}", e))?;
ChatDao::delete_session(&conn, &session_id).map_err(|e| format!("删除会话失败: {e}"))?;
if deleted {
tracing::info!("[UnifiedChat] 删除会话: id={}", session_id);
@@ -215,10 +218,10 @@ pub async fn chat_rename_session(
session_id: String,
title: String,
) -> Result<(), String> {
let conn = db.lock().map_err(|e| format!("数据库锁定失败: {}", e))?;
let conn = db.lock().map_err(|e| format!("数据库锁定失败: {e}"))?;
ChatDao::update_title(&conn, &session_id, &title)
.map_err(|e| format!("重命名会话失败: {}", e))?;
.map_err(|e| format!("重命名会话失败: {e}"))?;
tracing::info!(
"[UnifiedChat] 重命名会话: id={}, title={}",
@@ -240,10 +243,10 @@ pub async fn chat_get_messages(
session_id: String,
limit: Option<i32>,
) -> Result<Vec<ChatMessage>, String> {
let conn = db.lock().map_err(|e| format!("数据库锁定失败: {}", e))?;
let conn = db.lock().map_err(|e| format!("数据库锁定失败: {e}"))?;
let messages = ChatDao::get_messages(&conn, &session_id, limit)
.map_err(|e| format!("获取消息失败: {}", e))?;
.map_err(|e| format!("获取消息失败: {e}"))?;
Ok(messages)
}
@@ -258,17 +261,37 @@ pub async fn chat_send_message(
agent_state: State<'_, AsterAgentState>,
request: SendMessageRequest,
) -> Result<(), String> {
let image_count = request.images.as_ref().map(|v| v.len()).unwrap_or(0);
tracing::info!(
"[UnifiedChat] 发送消息: session={}, event={}",
"[UnifiedChat] 发送消息: session={}, event={}, images={}",
request.session_id,
request.event_name
request.event_name,
image_count
);
// TODO: 实现图片处理逻辑,将图片转换为 Aster Message 的 ImageContent
if let Some(images) = &request.images {
for (i, img) in images.iter().enumerate() {
tracing::debug!(
"[UnifiedChat] 图片 {}: media_type={}, data_len={}",
i,
img.media_type,
img.data.len()
);
}
if !images.is_empty() {
tracing::warn!(
"[UnifiedChat] 图片输入暂未实现,忽略 {} 张图片",
images.len()
);
}
}
// 获取会话信息
let session = {
let conn = db.lock().map_err(|e| format!("数据库锁定失败: {}", e))?;
let conn = db.lock().map_err(|e| format!("数据库锁定失败: {e}"))?;
ChatDao::get_session(&conn, &request.session_id)
.map_err(|e| format!("获取会话失败: {}", e))?
.map_err(|e| format!("获取会话失败: {e}"))?
.ok_or_else(|| "会话不存在".to_string())?
};
@@ -328,7 +351,7 @@ async fn send_message_with_aster(
// 构建消息(如果有 system_prompt 且是第一条消息,注入到消息前面)
let final_message = if let Some(prompt) = system_prompt {
format!("{}\n\n{}", prompt, message)
format!("{prompt}\n\n{message}")
} else {
message.to_string()
};
@@ -360,7 +383,7 @@ async fn send_message_with_aster(
}
Err(e) => {
let error_event = TauriAgentEvent::Error {
message: format!("流错误: {}", e),
message: format!("流错误: {e}"),
};
let _ = app.emit(event_name, &error_event);
}
@@ -373,10 +396,10 @@ async fn send_message_with_aster(
}
Err(e) => {
let error_event = TauriAgentEvent::Error {
message: format!("Agent 错误: {}", e),
message: format!("Agent 错误: {e}"),
};
let _ = app.emit(event_name, &error_event);
return Err(format!("Agent 错误: {}", e));
return Err(format!("Agent 错误: {e}"));
}
}
+12 -13
View File
@@ -67,7 +67,7 @@ pub async fn set_update_check_settings(
update_config.show_notification = settings.show_notification;
update_config.skipped_version = settings.skipped_version;
config::save_config(&state.config).map_err(|e| format!("保存配置失败: {}", e))
config::save_config(&state.config).map_err(|e| format!("保存配置失败: {e}"))
}
/// 跳过指定版本
@@ -80,7 +80,7 @@ pub async fn skip_update_version(
let mut state = app_state.write().await;
state.config.experimental.update_check.skipped_version = Some(version);
config::save_config(&state.config).map_err(|e| format!("保存配置失败: {}", e))?;
config::save_config(&state.config).map_err(|e| format!("保存配置失败: {e}"))?;
// 关闭更新窗口
let _ = update_window::close_update_window(&app_handle);
@@ -91,7 +91,7 @@ pub async fn skip_update_version(
/// 关闭更新提醒窗口
#[tauri::command]
pub fn close_update_window(app_handle: AppHandle) -> Result<(), String> {
update_window::close_update_window(&app_handle).map_err(|e| format!("关闭更新窗口失败: {}", e))
update_window::close_update_window(&app_handle).map_err(|e| format!("关闭更新窗口失败: {e}"))
}
/// 测试更新提醒窗口(仅开发环境使用)
@@ -102,9 +102,9 @@ pub fn test_update_window(app_handle: AppHandle) -> Result<(), String> {
current_version: current_version.to_string(),
latest_version: Some("0.99.0".to_string()),
has_update: true,
download_url: Some(format!(
"https://github.com/aiclientproxy/proxycast/releases/tag/v0.99.0"
)),
download_url: Some(
"https://github.com/aiclientproxy/proxycast/releases/tag/v0.99.0".to_string(),
),
release_notes_url: None,
checked_at: std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
@@ -114,7 +114,7 @@ pub fn test_update_window(app_handle: AppHandle) -> Result<(), String> {
};
update_window::open_update_window(&app_handle, &test_info)
.map_err(|e| format!("打开更新窗口失败: {}", e))
.map_err(|e| format!("打开更新窗口失败: {e}"))
}
/// 更新上次检查时间
@@ -128,7 +128,7 @@ pub async fn update_last_check_timestamp(app_state: State<'_, AppState>) -> Resu
let mut state = app_state.write().await;
state.config.experimental.update_check.last_check_timestamp = now;
config::save_config(&state.config).map_err(|e| format!("保存配置失败: {}", e))?;
config::save_config(&state.config).map_err(|e| format!("保存配置失败: {e}"))?;
Ok(now)
}
@@ -206,11 +206,10 @@ pub async fn start_background_update_check(
// 如果有更新且启用了通知,打开独立的更新提醒窗口
if result.has_update && show_notification {
// 检查是否跳过了此版本
let should_notify = result.latest_version.as_ref().map_or(true, |latest| {
skipped_version
.as_ref()
.map_or(true, |skipped| skipped != latest)
});
let should_notify = result
.latest_version
.as_ref()
.is_none_or(|latest| skipped_version.as_ref() != Some(latest));
if should_notify {
// 打开独立的更新提醒窗口 - 必须在主线程执行
+7 -7
View File
@@ -35,7 +35,7 @@ pub async fn get_kiro_usage(
let conn = db.lock().map_err(|e| e.to_string())?;
ProviderPoolDao::get_by_uuid(&conn, &credential_uuid)
.map_err(|e| e.to_string())?
.ok_or_else(|| format!("凭证不存在: {}", credential_uuid))?
.ok_or_else(|| format!("凭证不存在: {credential_uuid}"))?
};
// 2. 验证是否为 Kiro 凭证
@@ -60,7 +60,7 @@ pub async fn get_kiro_usage(
.map_err(|e| {
// 提供更友好的错误信息
if e.contains("401") || e.contains("Bad credentials") || e.contains("过期") || e.contains("无效") {
format!("刷新 Kiro Token 失败: OAuth 凭证已过期或无效,需要重新认证。\n💡 解决方案:\n1. 删除当前 OAuth 凭证\n2. 重新添加 OAuth 凭证\n3. 确保使用最新的凭证文件\n\n技术详情:{}", e)
format!("刷新 Kiro Token 失败: OAuth 凭证已过期或无效,需要重新认证。\n💡 解决方案:\n1. 删除当前 OAuth 凭证\n2. 重新添加 OAuth 凭证\n3. 确保使用最新的凭证文件\n\n技术详情:{e}")
} else {
e
}
@@ -92,11 +92,11 @@ fn read_kiro_credential_info(creds_file_path: &str) -> Result<(String, Option<St
// 读取文件
let content =
std::fs::read_to_string(&expanded_path).map_err(|e| format!("读取凭证文件失败: {}", e))?;
std::fs::read_to_string(&expanded_path).map_err(|e| format!("读取凭证文件失败: {e}"))?;
// 解析 JSON
let json: serde_json::Value =
serde_json::from_str(&content).map_err(|e| format!("解析凭证文件失败: {}", e))?;
serde_json::from_str(&content).map_err(|e| format!("解析凭证文件失败: {e}"))?;
// 获取 auth_method,默认为 "social"
let auth_method = json
@@ -135,7 +135,7 @@ fn get_machine_id() -> Result<String, String> {
hasher.update(raw_id.as_bytes());
let result = hasher.finalize();
Ok(format!("{:x}", result))
Ok(format!("{result:x}"))
}
/// 获取原始设备 ID
@@ -147,7 +147,7 @@ fn get_raw_machine_id() -> Result<String, String> {
let output = Command::new("ioreg")
.args(["-rd1", "-c", "IOPlatformExpertDevice"])
.output()
.map_err(|e| format!("执行 ioreg 失败: {}", e))?;
.map_err(|e| format!("执行 ioreg 失败: {e}"))?;
let stdout = String::from_utf8_lossy(&output.stdout);
for line in stdout.lines() {
@@ -225,7 +225,7 @@ mod tests {
// 这个测试在不同平台上行为不同
let result = get_machine_id();
// 应该能成功获取 machine_id
assert!(result.is_ok(), "Failed to get machine_id: {:?}", result);
assert!(result.is_ok(), "Failed to get machine_id: {result:?}");
// machine_id 应该是 64 字符的十六进制字符串(SHA256)
let id = result.unwrap();
assert_eq!(id.len(), 64, "Machine ID should be 64 hex chars");
+10 -10
View File
@@ -128,7 +128,7 @@ pub async fn create_webview_panel(
return Ok(CreateWebviewResponse {
success: false,
panel_id,
error: Some(format!("无效的 URL: {}", e)),
error: Some(format!("无效的 URL: {e}")),
});
}
};
@@ -171,7 +171,7 @@ pub async fn create_webview_panel(
Ok(CreateWebviewResponse {
success: false,
panel_id,
error: Some(format!("创建窗口失败: {}", e)),
error: Some(format!("创建窗口失败: {e}")),
})
}
}
@@ -227,13 +227,13 @@ pub async fn navigate_webview_panel(
// 解析 URL
let parsed_url = url
.parse::<url::Url>()
.map_err(|e| format!("无效的 URL: {}", e))?;
.map_err(|e| format!("无效的 URL: {e}"))?;
// 获取窗口并导航
if let Some(window) = app.get_webview_window(&panel_id) {
// 使用 eval 来导航
let js = format!("window.location.href = '{}';", parsed_url);
window.eval(&js).map_err(|e| format!("导航失败: {}", e))?;
let js = format!("window.location.href = '{parsed_url}';");
window.eval(&js).map_err(|e| format!("导航失败: {e}"))?;
// 更新状态中的 URL
let mut manager = state.0.write().await;
@@ -243,7 +243,7 @@ pub async fn navigate_webview_panel(
Ok(true)
} else {
Err(format!("窗口不存在: {}", panel_id))
Err(format!("窗口不存在: {panel_id}"))
}
}
@@ -270,7 +270,7 @@ pub async fn resize_webview_panel(
// 设置大小
window
.set_size(tauri::LogicalSize::new(width, height))
.map_err(|e| format!("设置大小失败: {}", e))?;
.map_err(|e| format!("设置大小失败: {e}"))?;
// 更新状态
let mut manager = state.0.write().await;
@@ -281,7 +281,7 @@ pub async fn resize_webview_panel(
Ok(true)
} else {
Err(format!("窗口不存在: {}", panel_id))
Err(format!("窗口不存在: {panel_id}"))
}
}
@@ -298,9 +298,9 @@ pub async fn get_webview_panels(
#[tauri::command]
pub async fn focus_webview_panel(app: AppHandle, panel_id: String) -> Result<bool, String> {
if let Some(window) = app.get_webview_window(&panel_id) {
window.set_focus().map_err(|e| format!("聚焦失败: {}", e))?;
window.set_focus().map_err(|e| format!("聚焦失败: {e}"))?;
Ok(true)
} else {
Err(format!("窗口不存在: {}", panel_id))
Err(format!("窗口不存在: {panel_id}"))
}
}
+1 -1
View File
@@ -172,7 +172,7 @@ pub async fn workspace_delete(
if let Some(workspace) = manager.get(&id)? {
let root_path = workspace.root_path;
if root_path.exists() && root_path.is_dir() {
std::fs::remove_dir_all(&root_path).map_err(|e| format!("删除目录失败: {}", e))?;
std::fs::remove_dir_all(&root_path).map_err(|e| format!("删除目录失败: {e}"))?;
tracing::info!("[Workspace] 删除目录: {:?}", root_path);
}
}
+7 -7
View File
@@ -158,11 +158,11 @@ pub enum ExportError {
impl std::fmt::Display for ExportError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ExportError::ConfigError(msg) => write!(f, "配置错误: {}", msg),
ExportError::ReadError(msg) => write!(f, "文件读取错误: {}", msg),
ExportError::SerializeError(msg) => write!(f, "序列化错误: {}", msg),
ExportError::ParseError(msg) => write!(f, "解析错误: {}", msg),
ExportError::TokenFileNotFound(path) => write!(f, "Token 文件不存在: {}", path),
ExportError::ConfigError(msg) => write!(f, "配置错误: {msg}"),
ExportError::ReadError(msg) => write!(f, "文件读取错误: {msg}"),
ExportError::SerializeError(msg) => write!(f, "序列化错误: {msg}"),
ExportError::ParseError(msg) => write!(f, "解析错误: {msg}"),
ExportError::TokenFileNotFound(path) => write!(f, "Token 文件不存在: {path}"),
}
}
}
@@ -458,7 +458,7 @@ mod base64 {
'0'..='9' => Ok((c as u32) - ('0' as u32) + 52),
'+' => Ok(62),
'/' => Ok(63),
_ => Err(format!("Invalid base64 character: {}", c)),
_ => Err(format!("Invalid base64 character: {c}")),
}
};
@@ -726,7 +726,7 @@ mod unit_tests {
let original: Vec<u8> = (0..len).map(|i| i as u8).collect();
let encoded = base64_encode(&original);
let decoded = base64_decode(&encoded).expect("解码应成功");
assert_eq!(decoded, original, "长度 {} 的数据往返失败", len);
assert_eq!(decoded, original, "长度 {len} 的数据往返失败");
}
}
+5 -5
View File
@@ -36,11 +36,11 @@ pub enum HotReloadError {
impl std::fmt::Display for HotReloadError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
HotReloadError::WatchError(msg) => write!(f, "文件监控错误: {}", msg),
HotReloadError::LoadError(msg) => write!(f, "配置加载错误: {}", msg),
HotReloadError::ValidationError(msg) => write!(f, "配置验证错误: {}", msg),
HotReloadError::RollbackError(msg) => write!(f, "回滚错误: {}", msg),
HotReloadError::ChannelError(msg) => write!(f, "通道错误: {}", msg),
HotReloadError::WatchError(msg) => write!(f, "文件监控错误: {msg}"),
HotReloadError::LoadError(msg) => write!(f, "配置加载错误: {msg}"),
HotReloadError::ValidationError(msg) => write!(f, "配置验证错误: {msg}"),
HotReloadError::RollbackError(msg) => write!(f, "回滚错误: {msg}"),
HotReloadError::ChannelError(msg) => write!(f, "通道错误: {msg}"),
}
}
}
+10 -10
View File
@@ -149,12 +149,12 @@ pub enum ImportError {
impl std::fmt::Display for ImportError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ImportError::FormatError(msg) => write!(f, "格式错误: {}", msg),
ImportError::VersionError(msg) => write!(f, "版本不兼容: {}", msg),
ImportError::ConfigError(msg) => write!(f, "配置错误: {}", msg),
ImportError::IoError(msg) => write!(f, "IO 错误: {}", msg),
ImportError::ValidationError(msg) => write!(f, "验证错误: {}", msg),
ImportError::RedactedDataError(msg) => write!(f, "脱敏数据无法导入: {}", msg),
ImportError::FormatError(msg) => write!(f, "格式错误: {msg}"),
ImportError::VersionError(msg) => write!(f, "版本不兼容: {msg}"),
ImportError::ConfigError(msg) => write!(f, "配置错误: {msg}"),
ImportError::IoError(msg) => write!(f, "IO 错误: {msg}"),
ImportError::ValidationError(msg) => write!(f, "验证错误: {msg}"),
ImportError::RedactedDataError(msg) => write!(f, "脱敏数据无法导入: {msg}"),
}
}
}
@@ -234,7 +234,7 @@ impl ImportService {
// 验证配置内容(如果存在)
if let Some(ref yaml) = bundle.config_yaml {
if let Err(e) = ConfigManager::parse_yaml(yaml) {
result.add_error(format!("配置 YAML 解析失败: {}", e));
result.add_error(format!("配置 YAML 解析失败: {e}"));
}
}
@@ -453,17 +453,17 @@ impl ImportService {
Ok(content) => {
// 检查是否是脱敏内容
if content == REDACTED_PLACEHOLDER.as_bytes() {
warnings.push(format!("Token 文件 {} 已脱敏,无法恢复", relative_path));
warnings.push(format!("Token 文件 {relative_path} 已脱敏,无法恢复"));
continue;
}
// 写入文件
if let Err(e) = std::fs::write(&token_path, &content) {
warnings.push(format!("写入 token 文件 {} 失败: {}", relative_path, e));
warnings.push(format!("写入 token 文件 {relative_path} 失败: {e}"));
}
}
Err(e) => {
warnings.push(format!("解码 token 文件 {} 失败: {}", relative_path, e));
warnings.push(format!("解码 token 文件 {relative_path} 失败: {e}"));
}
}
}
+4 -4
View File
@@ -720,7 +720,7 @@ fn arb_tilde_user_path() -> impl Strategy<Value = String> {
(username, proptest::collection::vec(path_segment, 0..4)).prop_map(|(user, segments)| {
if segments.is_empty() {
format!("~{}", user)
format!("~{user}")
} else {
format!("~{}/{}", user, segments.join("/"))
}
@@ -872,7 +872,7 @@ proptest! {
#[test]
fn prop_collapse_non_home_path_unchanged(path in arb_absolute_path()) {
// 确保路径不在主目录下(使用 /tmp 或类似路径)
let test_path = format!("/tmp{}", path);
let test_path = format!("/tmp{path}");
let collapsed = collapse_tilde(&test_path);
prop_assert_eq!(
@@ -892,7 +892,7 @@ proptest! {
/// 生成有效的 YAML 注释(以 # 开头)
fn arb_yaml_comment() -> impl Strategy<Value = String> {
// 生成注释内容:字母、数字、空格、中文字符
"[a-zA-Z0-9 ]{1,50}".prop_map(|s| format!("# {}", s))
"[a-zA-Z0-9 ]{1,50}".prop_map(|s| format!("# {s}"))
}
/// 生成带注释的 YAML 配置字符串
@@ -1039,7 +1039,7 @@ proptest! {
// 创建带头部注释的 YAML
let yaml = ConfigManager::to_yaml(&config).expect("序列化应成功");
let yaml_with_header = format!("{}\n{}", header_comment, yaml);
let yaml_with_header = format!("{header_comment}\n{yaml}");
// 写入文件
std::fs::write(&config_path, &yaml_with_header).expect("写入文件失败");
+1 -10
View File
@@ -125,7 +125,7 @@ fn default_asr_language() -> String {
}
/// Whisper 本地配置
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
pub struct WhisperLocalConfig {
/// 模型大小
#[serde(default)]
@@ -135,15 +135,6 @@ pub struct WhisperLocalConfig {
pub model_path: Option<String>,
}
impl Default for WhisperLocalConfig {
fn default() -> Self {
Self {
model: WhisperModelSize::default(),
model_path: None,
}
}
}
/// 讯飞语音配置
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct XunfeiConfig {
+5 -5
View File
@@ -27,11 +27,11 @@ pub enum ConfigError {
impl std::fmt::Display for ConfigError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ConfigError::ReadError(msg) => write!(f, "配置读取错误: {}", msg),
ConfigError::WriteError(msg) => write!(f, "配置写入错误: {}", msg),
ConfigError::ParseError(msg) => write!(f, "YAML 解析错误: {}", msg),
ConfigError::SerializeError(msg) => write!(f, "YAML 序列化错误: {}", msg),
ConfigError::ValidationError(msg) => write!(f, "配置验证错误: {}", msg),
ConfigError::ReadError(msg) => write!(f, "配置读取错误: {msg}"),
ConfigError::WriteError(msg) => write!(f, "配置写入错误: {msg}"),
ConfigError::ParseError(msg) => write!(f, "YAML 解析错误: {msg}"),
ConfigError::SerializeError(msg) => write!(f, "YAML 序列化错误: {msg}"),
ConfigError::ValidationError(msg) => write!(f, "配置验证错误: {msg}"),
}
}
}
+9 -9
View File
@@ -55,7 +55,7 @@ pub enum DeepLinkError {
impl std::fmt::Display for DeepLinkError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
DeepLinkError::InvalidUrl(msg) => write!(f, "无效的 URL: {}", msg),
DeepLinkError::InvalidUrl(msg) => write!(f, "无效的 URL: {msg}"),
DeepLinkError::MissingRelay => write!(f, "缺少必填参数: relay"),
DeepLinkError::MissingKey => write!(f, "缺少必填参数: key"),
}
@@ -273,12 +273,12 @@ mod property_tests {
ref_code in arb_ref_code(),
) {
// 构建 URL
let mut url = format!("proxycast://connect?relay={}&key={}", relay, key);
let mut url = format!("proxycast://connect?relay={relay}&key={key}");
if let Some(ref n) = name {
url.push_str(&format!("&name={}", urlencoding::encode(n)));
}
if let Some(ref r) = ref_code {
url.push_str(&format!("&ref={}", r));
url.push_str(&format!("&ref={r}"));
}
// 解析 URL
@@ -310,13 +310,13 @@ mod property_tests {
) {
let url = match error_type {
// 缺少 relay 参数
0 => format!("proxycast://connect?key={}", key),
0 => format!("proxycast://connect?key={key}"),
// 缺少 key 参数
1 => format!("proxycast://connect?relay={}", relay),
1 => format!("proxycast://connect?relay={relay}"),
// 空 relay 参数
2 => format!("proxycast://connect?relay=&key={}", key),
2 => format!("proxycast://connect?relay=&key={key}"),
// 空 key 参数
_ => format!("proxycast://connect?relay={}&key=", relay),
_ => format!("proxycast://connect?relay={relay}&key="),
};
let result = parse_deep_link(&url);
@@ -345,7 +345,7 @@ mod property_tests {
relay in arb_relay_id(),
key in arb_api_key(),
) {
let url = format!("{}://connect?relay={}&key={}", protocol, relay, key);
let url = format!("{protocol}://connect?relay={relay}&key={key}");
let result = parse_deep_link(&url);
prop_assert!(
@@ -361,7 +361,7 @@ mod property_tests {
relay in arb_relay_id(),
key in arb_api_key(),
) {
let url = format!("proxycast://{}?relay={}&key={}", path, relay, key);
let url = format!("proxycast://{path}?relay={relay}&key={key}");
let result = parse_deep_link(&url);
prop_assert!(
+11 -11
View File
@@ -407,26 +407,26 @@ mod tests {
RelayInfo {
id: id.to_string(),
name: name.to_string(),
description: format!("{} 描述", name),
description: format!("{name} 描述"),
branding: RelayBranding {
logo: format!("https://example.com/{}/logo.png", id),
logo: format!("https://example.com/{id}/logo.png"),
color: "#6366f1".to_string(),
},
links: RelayLinks {
homepage: format!("https://{}.example.com", id),
register: Some(format!("https://{}.example.com/register", id)),
homepage: format!("https://{id}.example.com"),
register: Some(format!("https://{id}.example.com/register")),
recharge: None,
docs: Some(format!("https://docs.{}.example.com", id)),
docs: Some(format!("https://docs.{id}.example.com")),
status: None,
},
api: RelayApi {
base_url: format!("https://api.{}.example.com/v1", id),
base_url: format!("https://api.{id}.example.com/v1"),
protocol: "openai".to_string(),
auth_header: "Authorization".to_string(),
auth_prefix: "Bearer".to_string(),
},
contact: RelayContact {
email: Some(format!("support@{}.example.com", id)),
email: Some(format!("support@{id}.example.com")),
discord: None,
telegram: None,
twitter: None,
@@ -610,20 +610,20 @@ mod property_tests {
(arb_relay_id(), arb_relay_name()).prop_map(|(id, name)| RelayInfo {
id: id.clone(),
name: name.clone(),
description: format!("{} 描述", name),
description: format!("{name} 描述"),
branding: RelayBranding {
logo: format!("https://example.com/{}/logo.png", id),
logo: format!("https://example.com/{id}/logo.png"),
color: "#6366f1".to_string(),
},
links: RelayLinks {
homepage: format!("https://{}.example.com", id),
homepage: format!("https://{id}.example.com"),
register: None,
recharge: None,
docs: None,
status: None,
},
api: RelayApi {
base_url: format!("https://api.{}.example.com/v1", id),
base_url: format!("https://api.{id}.example.com/v1"),
protocol: "openai".to_string(),
auth_header: "Authorization".to_string(),
auth_prefix: "Bearer".to_string(),
+24 -51
View File
@@ -61,10 +61,7 @@ impl ContentManager {
updated_at: now,
};
let conn = self
.db
.lock()
.map_err(|e| format!("数据库锁定失败: {}", e))?;
let conn = self.db.lock().map_err(|e| format!("数据库锁定失败: {e}"))?;
conn.execute(
"INSERT INTO contents (id, project_id, title, content_type, status, sort_order, body, word_count, metadata_json, session_id, created_at, updated_at)
@@ -84,7 +81,7 @@ impl ContentManager {
content.updated_at.timestamp_millis(),
],
)
.map_err(|e| format!("创建内容失败: {}", e))?;
.map_err(|e| format!("创建内容失败: {e}"))?;
tracing::info!(
"[Content] 创建: id={}, project_id={}, title={}",
@@ -131,22 +128,19 @@ impl ContentManager {
/// 获取内容
pub fn get(&self, id: &ContentId) -> Result<Option<Content>, String> {
let conn = self
.db
.lock()
.map_err(|e| format!("数据库锁定失败: {}", e))?;
let conn = self.db.lock().map_err(|e| format!("数据库锁定失败: {e}"))?;
let result = conn.query_row(
"SELECT id, project_id, title, content_type, status, sort_order, body, word_count, metadata_json, session_id, created_at, updated_at
FROM contents WHERE id = ?",
params![id],
|row| Ok(Self::row_to_content(row)?),
Self::row_to_content,
);
match result {
Ok(content) => Ok(Some(content)),
Err(rusqlite::Error::QueryReturnedNoRows) => Ok(None),
Err(e) => Err(format!("获取内容失败: {}", e)),
Err(e) => Err(format!("获取内容失败: {e}")),
}
}
@@ -156,10 +150,7 @@ impl ContentManager {
project_id: &str,
query: Option<ContentListQuery>,
) -> Result<Vec<Content>, String> {
let conn = self
.db
.lock()
.map_err(|e| format!("数据库锁定失败: {}", e))?;
let conn = self.db.lock().map_err(|e| format!("数据库锁定失败: {e}"))?;
let query = query.unwrap_or_default();
@@ -185,7 +176,7 @@ impl ContentManager {
// 搜索
if let Some(ref search) = query.search {
sql.push_str(" AND (title LIKE ? OR body LIKE ?)");
let search_pattern = format!("%{}%", search);
let search_pattern = format!("%{search}%");
params_vec.push(Box::new(search_pattern.clone()));
params_vec.push(Box::new(search_pattern));
}
@@ -193,7 +184,7 @@ impl ContentManager {
// 排序
let sort_by = query.sort_by.unwrap_or_else(|| "sort_order".to_string());
let sort_order = query.sort_order.unwrap_or_else(|| "asc".to_string());
sql.push_str(&format!(" ORDER BY {} {}", sort_by, sort_order));
sql.push_str(&format!(" ORDER BY {sort_by} {sort_order}"));
// 分页
if let Some(limit) = query.limit {
@@ -210,23 +201,20 @@ impl ContentManager {
let mut stmt = conn
.prepare(&sql)
.map_err(|e| format!("准备查询失败: {}", e))?;
.map_err(|e| format!("准备查询失败: {e}"))?;
let contents = stmt
.query_map(params_refs.as_slice(), |row| Ok(Self::row_to_content(row)?))
.map_err(|e| format!("查询失败: {}", e))?
.query_map(params_refs.as_slice(), Self::row_to_content)
.map_err(|e| format!("查询失败: {e}"))?
.collect::<Result<Vec<_>, _>>()
.map_err(|e| format!("解析结果失败: {}", e))?;
.map_err(|e| format!("解析结果失败: {e}"))?;
Ok(contents)
}
/// 更新内容
pub fn update(&self, id: &ContentId, updates: ContentUpdateRequest) -> Result<Content, String> {
let conn = self
.db
.lock()
.map_err(|e| format!("数据库锁定失败: {}", e))?;
let conn = self.db.lock().map_err(|e| format!("数据库锁定失败: {e}"))?;
let now = Utc::now().timestamp_millis();
// 构建更新语句
@@ -277,7 +265,7 @@ impl ContentManager {
params_vec.iter().map(|p| p.as_ref()).collect();
conn.execute(&sql, params_refs.as_slice())
.map_err(|e| format!("更新内容失败: {}", e))?;
.map_err(|e| format!("更新内容失败: {e}"))?;
drop(conn);
@@ -286,14 +274,11 @@ impl ContentManager {
/// 删除内容
pub fn delete(&self, id: &ContentId) -> Result<bool, String> {
let conn = self
.db
.lock()
.map_err(|e| format!("数据库锁定失败: {}", e))?;
let conn = self.db.lock().map_err(|e| format!("数据库锁定失败: {e}"))?;
let affected = conn
.execute("DELETE FROM contents WHERE id = ?", params![id])
.map_err(|e| format!("删除内容失败: {}", e))?;
.map_err(|e| format!("删除内容失败: {e}"))?;
if affected > 0 {
tracing::info!("[Content] 删除: id={}", id);
@@ -304,17 +289,14 @@ impl ContentManager {
/// 批量删除项目下的所有内容
pub fn delete_by_project(&self, project_id: &str) -> Result<i64, String> {
let conn = self
.db
.lock()
.map_err(|e| format!("数据库锁定失败: {}", e))?;
let conn = self.db.lock().map_err(|e| format!("数据库锁定失败: {e}"))?;
let affected = conn
.execute(
"DELETE FROM contents WHERE project_id = ?",
params![project_id],
)
.map_err(|e| format!("删除内容失败: {}", e))?;
.map_err(|e| format!("删除内容失败: {e}"))?;
tracing::info!(
"[Content] 批量删除: project_id={}, count={}",
@@ -327,10 +309,7 @@ impl ContentManager {
/// 获取项目的内容统计
pub fn get_project_stats(&self, project_id: &str) -> Result<(i64, i64, i64), String> {
let conn = self
.db
.lock()
.map_err(|e| format!("数据库锁定失败: {}", e))?;
let conn = self.db.lock().map_err(|e| format!("数据库锁定失败: {e}"))?;
let result = conn.query_row(
"SELECT COUNT(*), COALESCE(SUM(word_count), 0), COUNT(CASE WHEN status = 'completed' THEN 1 END)
@@ -346,16 +325,13 @@ impl ContentManager {
match result {
Ok(stats) => Ok(stats),
Err(e) => Err(format!("获取统计失败: {}", e)),
Err(e) => Err(format!("获取统计失败: {e}")),
}
}
/// 重新排序内容
pub fn reorder(&self, project_id: &str, content_ids: Vec<String>) -> Result<(), String> {
let conn = self
.db
.lock()
.map_err(|e| format!("数据库锁定失败: {}", e))?;
let conn = self.db.lock().map_err(|e| format!("数据库锁定失败: {e}"))?;
for (index, content_id) in content_ids.iter().enumerate() {
conn.execute(
@@ -367,7 +343,7 @@ impl ContentManager {
project_id
],
)
.map_err(|e| format!("重新排序失败: {}", e))?;
.map_err(|e| format!("重新排序失败: {e}"))?;
}
Ok(())
@@ -375,10 +351,7 @@ impl ContentManager {
/// 获取下一个排序顺序
fn get_next_order(&self, project_id: &str) -> Result<i32, String> {
let conn = self
.db
.lock()
.map_err(|e| format!("数据库锁定失败: {}", e))?;
let conn = self.db.lock().map_err(|e| format!("数据库锁定失败: {e}"))?;
let result: Result<i32, _> = conn.query_row(
"SELECT COALESCE(MAX(sort_order), -1) + 1 FROM contents WHERE project_id = ?",
@@ -386,7 +359,7 @@ impl ContentManager {
|row| row.get(0),
);
result.map_err(|e| format!("获取排序顺序失败: {}", e))
result.map_err(|e| format!("获取排序顺序失败: {e}"))
}
/// 从数据库行解析 Content
@@ -187,7 +187,7 @@ fn generate_random_session_id() -> String {
let n: u64 = u64::from_le_bytes([
bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7],
]) % 9_000_000_000_000_000_000;
format!("-{}", n)
format!("-{n}")
}
/// 获取默认安全设置
@@ -292,15 +292,15 @@ pub fn convert_openai_to_antigravity_with_context(
) -> serde_json::Value {
eprintln!("========== [CONVERT] OpenAI -> Antigravity 转换开始 ==========");
eprintln!("[CONVERT] 原始模型: {}", request.model);
eprintln!("[CONVERT] 项目ID: {}", project_id);
eprintln!("[CONVERT] 项目ID: {project_id}");
eprintln!("[CONVERT] 消息数量: {}", request.messages.len());
eprintln!("[CONVERT] 流式: {}", request.stream);
let actual_model = model_mapping(&request.model);
eprintln!("[CONVERT] 映射后模型: {}", actual_model);
eprintln!("[CONVERT] 映射后模型: {actual_model}");
let supports_thinking = model_supports_thinking(actual_model);
eprintln!("[CONVERT] 支持思维链: {}", supports_thinking);
eprintln!("[CONVERT] 支持思维链: {supports_thinking}");
let mut contents: Vec<GeminiContent> = Vec::new();
let mut system_instruction: Option<GeminiContent> = None;
@@ -575,15 +575,9 @@ pub fn convert_openai_to_antigravity_with_context(
"[ANTIGRAVITY] 图片生成模型 {} 已启用 IMAGE 响应模态",
actual_model
);
eprintln!(
"[ANTIGRAVITY] 图片生成模型 {} 已启用 IMAGE 响应模态",
actual_model
);
eprintln!("[ANTIGRAVITY] 图片生成模型 {actual_model} 已启用 IMAGE 响应模态");
} else {
eprintln!(
"[ANTIGRAVITY] 模型 {} 不是图片生成模型,不启用 IMAGE 响应模态",
actual_model
);
eprintln!("[ANTIGRAVITY] 模型 {actual_model} 不是图片生成模型,不启用 IMAGE 响应模态");
}
// 处理 reasoning_effort(思维链配置)
@@ -690,7 +684,7 @@ pub fn convert_openai_to_antigravity_with_context(
// 使用 SessionManager 生成稳定的会话 ID
let session_id = SessionManager::extract_session_id(request);
eprintln!("[CONVERT] 生成的稳定 SessionId: {}", session_id);
eprintln!("[CONVERT] 生成的稳定 SessionId: {session_id}");
let inner = AntigravityRequestInner {
contents,
@@ -950,11 +944,11 @@ pub fn convert_antigravity_to_openai_response(
.unwrap_or("image/png");
// 将图片作为 data URL 添加到内容中
let image_url = format!("data:{};base64,{}", mime_type, data);
let image_url = format!("data:{mime_type};base64,{data}");
if !content.is_empty() {
content.push_str("\n\n");
}
content.push_str(&format!("![image]({})", image_url));
content.push_str(&format!("![image]({image_url})"));
}
}
}
@@ -1181,7 +1175,7 @@ pub fn convert_antigravity_image_response(
}
} else {
// 构建 data URL
let data_url = format!("data:{};base64,{}", mime_type, data);
let data_url = format!("data:{mime_type};base64,{data}");
ImageData {
b64_json: None,
url: Some(data_url),
@@ -1532,7 +1526,7 @@ mod image_property_tests {
let result = convert_antigravity_image_response(&antigravity_resp, &response_format).unwrap();
prop_assert!(result.data.len() >= 1);
prop_assert!(!result.data.is_empty());
if response_format == "b64_json" {
// b64_json 格式
@@ -1546,7 +1540,7 @@ mod image_property_tests {
// 验证 data URL 格式
let url = result.data[0].url.as_ref().unwrap();
let expected_url = format!("data:{};base64,{}", mime_type, base64_data);
let expected_url = format!("data:{mime_type};base64,{base64_data}");
prop_assert_eq!(url, &expected_url);
}
}
+1 -1
View File
@@ -345,7 +345,7 @@ pub fn convert_openai_to_codewhisperer(
// P1 安全修复:使用字符边界安全的截断,防止 UTF-8 panic
description: if desc.len() > 500 {
let truncated: String = desc.chars().take(497).collect();
format!("{}...", truncated)
format!("{truncated}...")
} else {
desc
},
+7 -2
View File
@@ -16,6 +16,13 @@
//! - 只有 `content` 字段需要保留在对话历史中
//! - Tool Calls 场景下,需要正确处理 reasoning_content 的传递
//!
//! # 使用状态
//!
//! 此模块为预留功能,将在 Proxy 层集成推理模型时启用。
//! 目前代码已完成,等待在 `proxy_handler.rs` 中调用 `ReasoningHandler::preprocess_messages`。
// 预留功能模块,暂未在主流程中调用
#![allow(dead_code)]
use crate::models::openai::ChatMessage;
@@ -85,8 +92,6 @@ impl ReasoningHandler {
///
/// 清除历史消息中的 reasoning_content,只保留最后一条 assistant 消息的 reasoning_content
fn process_deepseek_messages(mut messages: Vec<ChatMessage>) -> Vec<ChatMessage> {
let len = messages.len();
// 先找出最后一条 assistant 消息的索引
let last_assistant_idx = messages
.iter()
+3 -3
View File
@@ -181,7 +181,7 @@ impl LoadBalancer {
let client = self
.proxy_factory
.create_client(credential.proxy_url())
.map_err(|e| PoolError::CredentialNotFound(format!("代理配置错误: {}", e)))?;
.map_err(|e| PoolError::CredentialNotFound(format!("代理配置错误: {e}")))?;
Ok(CredentialSelection { credential, client })
}
@@ -200,7 +200,7 @@ impl LoadBalancer {
) -> Result<Client, PoolError> {
self.proxy_factory
.create_client(credential.proxy_url())
.map_err(|e| PoolError::CredentialNotFound(format!("代理配置错误: {}", e)))
.map_err(|e| PoolError::CredentialNotFound(format!("代理配置错误: {e}")))
}
/// 选择下一个可用凭证,支持代理失败时的故障转移
@@ -468,7 +468,7 @@ mod balancer_tests {
id.to_string(),
provider,
CredentialData::ApiKey {
key: format!("key-{}", id),
key: format!("key-{id}"),
base_url: None,
},
)
+1 -1
View File
@@ -253,7 +253,7 @@ mod health_tests {
id.to_string(),
ProviderType::Kiro,
CredentialData::ApiKey {
key: format!("key-{}", id),
key: format!("key-{id}"),
base_url: None,
},
)
+3 -3
View File
@@ -52,8 +52,8 @@ pub enum PoolError {
impl std::fmt::Display for PoolError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
PoolError::CredentialExists(id) => write!(f, "凭证已存在: {}", id),
PoolError::CredentialNotFound(id) => write!(f, "凭证不存在: {}", id),
PoolError::CredentialExists(id) => write!(f, "凭证已存在: {id}"),
PoolError::CredentialNotFound(id) => write!(f, "凭证不存在: {id}"),
PoolError::EmptyPool => write!(f, "凭证池为空"),
PoolError::NoAvailableCredential => write!(f, "没有可用的凭证"),
}
@@ -291,7 +291,7 @@ mod pool_tests {
id.to_string(),
ProviderType::Kiro,
CredentialData::ApiKey {
key: format!("key-{}", id),
key: format!("key-{id}"),
base_url: None,
},
)
+4 -4
View File
@@ -262,7 +262,7 @@ impl QuotaManager {
}
// 添加 -preview 后缀
Some(format!("{}-preview", model))
Some(format!("{model}-preview"))
}
/// 检查模型是否为预览版本
@@ -391,7 +391,7 @@ pub struct QuotaAutoSwitchResult {
impl QuotaAutoSwitchResult {
/// 创建成功切换的结果
pub fn switched(new_credential_id: String) -> Self {
let message = format!("已切换到凭证: {}", new_credential_id);
let message = format!("已切换到凭证: {new_credential_id}");
Self {
switched: true,
new_credential_id: Some(new_credential_id),
@@ -403,7 +403,7 @@ impl QuotaAutoSwitchResult {
/// 创建使用预览模型的结果
pub fn preview_model(model: String) -> Self {
let message = format!("已切换到预览模型: {}", model);
let message = format!("已切换到预览模型: {model}");
Self {
switched: false,
new_credential_id: None,
@@ -427,7 +427,7 @@ impl QuotaAutoSwitchResult {
/// 创建所有凭证耗尽的结果
pub fn all_exhausted(earliest_recovery: Option<DateTime<Utc>>) -> Self {
let message = match earliest_recovery {
Some(time) => format!("所有凭证配额超限,最早恢复时间: {}", time),
Some(time) => format!("所有凭证配额超限,最早恢复时间: {time}"),
None => "所有凭证配额超限,无可用凭证".to_string(),
};
Self {
+1 -1
View File
@@ -478,7 +478,7 @@ mod tests {
for i in 0..5 {
let event = RateLimitEvent::new("cred-1".to_string())
.with_status_code(429)
.with_error_message(format!("Rate limit {}", i));
.with_error_message(format!("Rate limit {i}"));
controller.record_rate_limit(event);
}
+8 -8
View File
@@ -26,10 +26,10 @@ pub enum SyncError {
impl std::fmt::Display for SyncError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
SyncError::ConfigError(msg) => write!(f, "配置错误: {}", msg),
SyncError::IoError(msg) => write!(f, "IO 错误: {}", msg),
SyncError::CredentialNotFound(id) => write!(f, "凭证不存在: {}", id),
SyncError::InvalidCredentialType(msg) => write!(f, "无效的凭证类型: {}", msg),
SyncError::ConfigError(msg) => write!(f, "配置错误: {msg}"),
SyncError::IoError(msg) => write!(f, "IO 错误: {msg}"),
SyncError::CredentialNotFound(id) => write!(f, "凭证不存在: {id}"),
SyncError::InvalidCredentialType(msg) => write!(f, "无效的凭证类型: {msg}"),
}
}
}
@@ -67,7 +67,7 @@ impl CredentialSyncService {
let manager = self
.config_manager
.read()
.map_err(|e| SyncError::ConfigError(format!("获取配置锁失败: {}", e)))?;
.map_err(|e| SyncError::ConfigError(format!("获取配置锁失败: {e}")))?;
Ok(manager.config().clone())
}
@@ -76,7 +76,7 @@ impl CredentialSyncService {
let mut manager = self
.config_manager
.write()
.map_err(|e| SyncError::ConfigError(format!("获取配置写锁失败: {}", e)))?;
.map_err(|e| SyncError::ConfigError(format!("获取配置写锁失败: {e}")))?;
let config_path = manager.config_path().to_path_buf();
manager.set_config(config.clone());
@@ -252,7 +252,7 @@ impl CredentialSyncService {
std::fs::create_dir_all(&provider_dir)?;
// 生成 token 文件名
let token_filename = format!("{}.json", credential_id);
let token_filename = format!("{credential_id}.json");
let token_path = provider_dir.join(&token_filename);
// 展开源路径并复制文件
@@ -262,7 +262,7 @@ impl CredentialSyncService {
}
// 返回相对路径
Ok(format!("{}/{}", provider, token_filename))
Ok(format!("{provider}/{token_filename}"))
}
/// 删除凭证并同步到配置
+38 -38
View File
@@ -235,7 +235,7 @@ fn arb_unique_credentials_same_provider(
data_list
.into_iter()
.enumerate()
.map(|(i, data)| Credential::new(format!("cred-{}", i), provider, data))
.map(|(i, data)| Credential::new(format!("cred-{i}"), provider, data))
.collect()
})
}
@@ -256,10 +256,10 @@ proptest! {
// 添加 N 个凭证
for i in 0..cred_count {
let cred = Credential::new(
format!("cred-{}", i),
format!("cred-{i}"),
provider,
CredentialData::ApiKey {
key: format!("key-{}", i),
key: format!("key-{i}"),
base_url: None,
},
);
@@ -303,10 +303,10 @@ proptest! {
// 添加 N 个凭证
for i in 0..cred_count {
let cred = Credential::new(
format!("cred-{}", i),
format!("cred-{i}"),
provider,
CredentialData::ApiKey {
key: format!("key-{}", i),
key: format!("key-{i}"),
base_url: None,
},
);
@@ -550,10 +550,10 @@ proptest! {
let cred_count = 5usize;
for i in 0..cred_count {
let cred = Credential::new(
format!("cred-{}", i),
format!("cred-{i}"),
provider,
CredentialData::ApiKey {
key: format!("key-{}", i),
key: format!("key-{i}"),
base_url: None,
},
);
@@ -562,7 +562,7 @@ proptest! {
lb.register_pool(pool.clone());
let cooldown_id = format!("cred-{}", cooldown_index);
let cooldown_id = format!("cred-{cooldown_index}");
// 标记一个凭证为冷却状态(1小时后恢复)
lb.mark_cooldown(provider, &cooldown_id, Duration::hours(1)).unwrap();
@@ -631,10 +631,10 @@ proptest! {
// 添加凭证
for i in 0..cred_count {
let cred = Credential::new(
format!("cred-{}", i),
format!("cred-{i}"),
provider,
CredentialData::ApiKey {
key: format!("key-{}", i),
key: format!("key-{i}"),
base_url: None,
},
);
@@ -645,7 +645,7 @@ proptest! {
// 将所有凭证标记为冷却
for i in 0..cred_count {
lb.mark_cooldown(provider, &format!("cred-{}", i), Duration::hours(1))
lb.mark_cooldown(provider, &format!("cred-{i}"), Duration::hours(1))
.unwrap();
}
@@ -818,12 +818,12 @@ proptest! {
for i in 0..cred_count {
let cred_data = if i % 2 == 0 {
PoolCredentialData::OpenAIKey {
api_key: format!("sk-test-key-{}", i),
api_key: format!("sk-test-key-{i}"),
base_url: Some("https://api.openai.com/v1".to_string()),
}
} else {
PoolCredentialData::ClaudeKey {
api_key: format!("sk-ant-test-key-{}", i),
api_key: format!("sk-ant-test-key-{i}"),
base_url: None,
}
};
@@ -879,7 +879,7 @@ proptest! {
std::fs::create_dir_all(&source_token_dir).expect("创建源目录失败");
let source_token_path = source_token_dir.join("token.json");
let token_json = format!(r#"{{"access_token": "{}", "refresh_token": "refresh-{}", "expires_at": "2025-12-31T23:59:59Z"}}"#, token_content, token_content);
let token_json = format!(r#"{{"access_token": "{token_content}", "refresh_token": "refresh-{token_content}", "expires_at": "2025-12-31T23:59:59Z"}}"#);
std::fs::write(&source_token_path, &token_json).expect("写入源 token 文件失败");
// 根据索引选择 provider 类型
@@ -920,7 +920,7 @@ proptest! {
PoolProviderType::Gemini => "gemini",
_ => "unknown",
};
let expected_token_path = auth_dir.join(provider_name).join(format!("{}.json", original_uuid));
let expected_token_path = auth_dir.join(provider_name).join(format!("{original_uuid}.json"));
prop_assert!(
expected_token_path.exists(),
@@ -983,7 +983,7 @@ proptest! {
std::fs::create_dir_all(&source_token_dir).expect("创建源目录失败");
let source_token_path = source_token_dir.join("token.json");
let initial_json = format!(r#"{{"access_token": "{}"}}"#, initial_content);
let initial_json = format!(r#"{{"access_token": "{initial_content}"}}"#);
std::fs::write(&source_token_path, &initial_json).expect("写入初始 token 文件失败");
// 创建凭证
@@ -999,7 +999,7 @@ proptest! {
sync_service.add_credential(&credential).expect("添加凭证失败");
// 更新源 token 文件内容
let updated_json = format!(r#"{{"access_token": "{}"}}"#, updated_content);
let updated_json = format!(r#"{{"access_token": "{updated_content}"}}"#);
std::fs::write(&source_token_path, &updated_json).expect("更新源 token 文件失败");
// 更新凭证
@@ -1013,7 +1013,7 @@ proptest! {
// 验证 auth_dir 中的 token 文件已更新
let auth_dir = sync_service.get_auth_dir().expect("获取 auth_dir 失败");
let token_path = auth_dir.join("kiro").join(format!("{}.json", original_uuid));
let token_path = auth_dir.join("kiro").join(format!("{original_uuid}.json"));
let stored_content = std::fs::read_to_string(&token_path)
.expect("读取存储的 token 文件失败");
@@ -1040,8 +1040,8 @@ proptest! {
per_key_proxy in "[a-z0-9]{1,10}",
global_proxy in "[a-z0-9]{1,10}"
) {
let per_key_url = format!("http://{}:8080", per_key_proxy);
let global_url = format!("http://{}:8080", global_proxy);
let per_key_url = format!("http://{per_key_proxy}:8080");
let global_url = format!("http://{global_proxy}:8080");
let lb = LoadBalancer::new(BalanceStrategy::RoundRobin)
.with_global_proxy(Some(global_url.clone()));
@@ -1155,7 +1155,7 @@ proptest! {
// Hostname must start with a letter to be valid
proxy_host in "[a-z][a-z0-9]{0,9}"
) {
let proxy_url = format!("http://{}:8080", proxy_host);
let proxy_url = format!("http://{proxy_host}:8080");
let lb = LoadBalancer::new(BalanceStrategy::RoundRobin);
let pool = Arc::new(CredentialPool::new(provider));
@@ -1210,14 +1210,14 @@ proptest! {
Some("ftp://invalid-proxy:21".to_string())
} else {
// 其他凭证使用有效代理
Some(format!("http://valid-proxy-{}:8080", i))
Some(format!("http://valid-proxy-{i}:8080"))
};
let cred = Credential::new(
format!("cred-{}", i),
format!("cred-{i}"),
provider,
CredentialData::ApiKey {
key: format!("key-{}", i),
key: format!("key-{i}"),
base_url: None,
},
).with_proxy(proxy_url);
@@ -1255,13 +1255,13 @@ proptest! {
// 创建多个凭证,都有有效代理
for i in 0..cred_count {
let cred = Credential::new(
format!("cred-{}", i),
format!("cred-{i}"),
provider,
CredentialData::ApiKey {
key: format!("key-{}", i),
key: format!("key-{i}"),
base_url: None,
},
).with_proxy(Some(format!("http://proxy-{}:8080", i)));
).with_proxy(Some(format!("http://proxy-{i}:8080")));
pool.add(cred).unwrap();
}
@@ -1288,13 +1288,13 @@ proptest! {
// 创建多个凭证,都有无效代理
for i in 0..cred_count {
let cred = Credential::new(
format!("cred-{}", i),
format!("cred-{i}"),
provider,
CredentialData::ApiKey {
key: format!("key-{}", i),
key: format!("key-{i}"),
base_url: None,
},
).with_proxy(Some(format!("ftp://invalid-proxy-{}:21", i)));
).with_proxy(Some(format!("ftp://invalid-proxy-{i}:21")));
pool.add(cred).unwrap();
}
@@ -1545,7 +1545,7 @@ proptest! {
// 标记多个凭证为配额超限
let mut marked_ids = Vec::new();
for i in 0..cred_count {
let cred_id = format!("cred-{}", i);
let cred_id = format!("cred-{i}");
manager.mark_quota_exceeded(&cred_id, "Rate limit exceeded");
marked_ids.push(cred_id);
}
@@ -1596,7 +1596,7 @@ proptest! {
// 创建凭证 ID 列表
let available: Vec<String> = (0..cred_count)
.map(|i| format!("cred-{}", i))
.map(|i| format!("cred-{i}"))
.collect();
let failed_index = failed_index % cred_count;
@@ -1656,7 +1656,7 @@ proptest! {
// 创建凭证 ID 列表
let available: Vec<String> = (0..cred_count)
.map(|i| format!("cred-{}", i))
.map(|i| format!("cred-{i}"))
.collect();
let failed_index = failed_index % cred_count;
@@ -1701,7 +1701,7 @@ proptest! {
// 创建凭证 ID 列表
let available: Vec<String> = (0..cred_count)
.map(|i| format!("cred-{}", i))
.map(|i| format!("cred-{i}"))
.collect();
// 标记所有凭证为配额超限
@@ -1753,7 +1753,7 @@ proptest! {
// 标记多个凭证为配额超限
let cred_ids: Vec<String> = (0..cred_count)
.map(|i| format!("cred-{}", i))
.map(|i| format!("cred-{i}"))
.collect();
for cred_id in &cred_ids {
@@ -1815,7 +1815,7 @@ proptest! {
// 标记多个凭证为配额超限
let cred_ids: Vec<String> = (0..cred_count)
.map(|i| format!("cred-{}", i))
.map(|i| format!("cred-{i}"))
.collect();
for cred_id in &cred_ids {
@@ -1875,12 +1875,12 @@ proptest! {
// 标记一些凭证为立即过期
let expired_ids: Vec<String> = (0..expired_count)
.map(|i| format!("expired-{}", i))
.map(|i| format!("expired-{i}"))
.collect();
// 标记一些凭证为长时间冷却
let active_ids: Vec<String> = (0..active_count)
.map(|i| format!("active-{}", i))
.map(|i| format!("active-{i}"))
.collect();
// 先标记所有凭证
+1 -1
View File
@@ -183,7 +183,7 @@ impl AgentDao {
let tool_calls_json = message
.tool_calls
.as_ref()
.map(|tc| serde_json::to_string(tc))
.map(serde_json::to_string)
.transpose()
.map_err(|e| rusqlite::Error::ToSqlConversionFailure(Box::new(e)))?;
@@ -69,7 +69,7 @@ impl std::str::FromStr for ApiProviderType {
"ollama" => Ok(ApiProviderType::Ollama),
"new-api" => Ok(ApiProviderType::NewApi),
"gateway" => Ok(ApiProviderType::Gateway),
_ => Err(format!("Invalid provider type: {}", s)),
_ => Err(format!("Invalid provider type: {s}")),
}
}
}
@@ -113,7 +113,7 @@ impl std::str::FromStr for ProviderGroup {
"local" => Ok(ProviderGroup::Local),
"specialized" => Ok(ProviderGroup::Specialized),
"custom" => Ok(ProviderGroup::Custom),
_ => Err(format!("Invalid provider group: {}", s)),
_ => Err(format!("Invalid provider group: {s}")),
}
}
}
+4 -5
View File
@@ -53,7 +53,7 @@ impl std::str::FromStr for ChatMode {
"agent" => Ok(ChatMode::Agent),
"general" => Ok(ChatMode::General),
"creator" => Ok(ChatMode::Creator),
_ => Err(format!("未知的对话模式: {}", s)),
_ => Err(format!("未知的对话模式: {s}")),
}
}
}
@@ -279,7 +279,7 @@ impl ChatDao {
let tool_calls_json = message
.tool_calls
.as_ref()
.map(|tc| serde_json::to_string(tc))
.map(serde_json::to_string)
.transpose()
.map_err(|e| rusqlite::Error::ToSqlConversionFailure(Box::new(e)))?;
@@ -387,9 +387,8 @@ impl ChatDao {
serde_json::json!([{"type": "text", "text": content_json}])
});
let tool_calls: Option<serde_json::Value> = tool_calls_json
.map(|json| serde_json::from_str(&json).ok())
.flatten();
let tool_calls: Option<serde_json::Value> =
tool_calls_json.and_then(|json| serde_json::from_str(&json).ok());
Ok(ChatMessage {
id: row.get(0)?,
+7 -9
View File
@@ -27,7 +27,7 @@ impl GeneralChatDao {
let metadata_json = session
.metadata
.as_ref()
.map(|m| serde_json::to_string(m))
.map(serde_json::to_string)
.transpose()
.map_err(|e| rusqlite::Error::ToSqlConversionFailure(Box::new(e)))?;
@@ -167,14 +167,14 @@ impl GeneralChatDao {
let blocks_json = message
.blocks
.as_ref()
.map(|b| serde_json::to_string(b))
.map(serde_json::to_string)
.transpose()
.map_err(|e| rusqlite::Error::ToSqlConversionFailure(Box::new(e)))?;
let metadata_json = message
.metadata
.as_ref()
.map(|m| serde_json::to_string(m))
.map(serde_json::to_string)
.transpose()
.map_err(|e| rusqlite::Error::ToSqlConversionFailure(Box::new(e)))?;
@@ -225,8 +225,7 @@ impl GeneralChatDao {
FROM general_chat_messages
WHERE session_id = ?1 AND id < ?2
ORDER BY created_at DESC
LIMIT {}",
lim
LIMIT {lim}"
)
}
(Some(lim), None) => {
@@ -235,8 +234,7 @@ impl GeneralChatDao {
FROM general_chat_messages
WHERE session_id = ?1
ORDER BY created_at DESC
LIMIT {}",
lim
LIMIT {lim}"
)
}
(None, Some(_)) => {
@@ -526,10 +524,10 @@ mod tests {
for i in 1..=5 {
let msg = create_test_message(
&format!("msg-{}", i),
&format!("msg-{i}"),
"session-1",
MessageRole::User,
&format!("消息 {}", i),
&format!("消息 {i}"),
);
GeneralChatDao::add_message(&conn, &msg).unwrap();
}
@@ -65,7 +65,7 @@ impl PluginRow {
fn into_record(self) -> Result<InstalledPluginRecord, String> {
let source = deserialize_source(&self.source_type, self.source_data.as_deref())?;
let installed_at = DateTime::parse_from_rfc3339(&self.installed_at)
.map_err(|e| format!("无效的时间格式: {}", e))?
.map_err(|e| format!("无效的时间格式: {e}"))?
.with_timezone(&Utc);
Ok(InstalledPluginRecord {
@@ -112,14 +112,14 @@ fn deserialize_source(
}),
"github" => {
let data: serde_json::Value = serde_json::from_str(source_data.unwrap_or("{}"))
.map_err(|e| format!("JSON 解析错误: {}", e))?;
.map_err(|e| format!("JSON 解析错误: {e}"))?;
Ok(InstallSource::GitHub {
owner: data["owner"].as_str().unwrap_or_default().to_string(),
repo: data["repo"].as_str().unwrap_or_default().to_string(),
tag: data["tag"].as_str().unwrap_or_default().to_string(),
})
}
_ => Err(format!("未知的来源类型: {}", source_type)),
_ => Err(format!("未知的来源类型: {source_type}")),
}
}
@@ -194,7 +194,7 @@ impl InstalledPluginsDao {
},
)
.optional()
.map_err(|e| format!("数据库错误: {}", e))?;
.map_err(|e| format!("数据库错误: {e}"))?;
match result {
Some(row) => Ok(Some(row.into_record()?)),
@@ -209,7 +209,7 @@ impl InstalledPluginsDao {
"SELECT id, name, version, description, author, install_path, installed_at, source_type, source_data, enabled
FROM installed_plugins ORDER BY installed_at DESC",
)
.map_err(|e| format!("数据库错误: {}", e))?;
.map_err(|e| format!("数据库错误: {e}"))?;
let rows = stmt
.query_map([], |row| {
@@ -226,11 +226,11 @@ impl InstalledPluginsDao {
enabled: row.get(9)?,
})
})
.map_err(|e| format!("数据库错误: {}", e))?;
.map_err(|e| format!("数据库错误: {e}"))?;
let mut plugins = Vec::new();
for row in rows {
let row = row.map_err(|e| format!("数据库错误: {}", e))?;
let row = row.map_err(|e| format!("数据库错误: {e}"))?;
plugins.push(row.into_record()?);
}
@@ -291,11 +291,11 @@ mod tests {
fn create_test_plugin(id: &str) -> InstalledPluginRecord {
InstalledPluginRecord {
id: id.to_string(),
name: format!("Test Plugin {}", id),
name: format!("Test Plugin {id}"),
version: "1.0.0".to_string(),
description: Some("A test plugin".to_string()),
author: Some("Test Author".to_string()),
install_path: PathBuf::from(format!("/plugins/{}", id)),
install_path: PathBuf::from(format!("/plugins/{id}")),
installed_at: Utc::now(),
source: InstallSource::Local {
path: "/tmp/plugin.zip".to_string(),
+2 -2
View File
@@ -517,7 +517,7 @@ impl OrchestratorDao {
)
.map_err(|e| e.to_string())?;
let days_param = format!("-{} days", days);
let days_param = format!("-{days} days");
let rows = stmt
.query_map(params![model_id, days_param], |row| {
Ok(ModelUsageStats {
@@ -540,7 +540,7 @@ impl OrchestratorDao {
/// 清理旧的使用统计
pub fn cleanup_old_usage_stats(conn: &Connection, days: i32) -> Result<usize, String> {
let days_param = format!("-{} days", days);
let days_param = format!("-{days} days");
conn.execute(
"DELETE FROM model_usage_stats WHERE date < date('now', ?1)",
[days_param],
+24 -24
View File
@@ -26,7 +26,7 @@ pub fn migrate_from_json(conn: &Connection) -> Result<(), String> {
let backup_path = config_path.with_file_name("config.json.backup");
if !backup_path.exists() {
std::fs::copy(&config_path, &backup_path)
.map_err(|e| format!("备份旧配置失败: {}", e))?;
.map_err(|e| format!("备份旧配置失败: {e}"))?;
}
return Err(
@@ -80,7 +80,7 @@ pub fn migrate_api_keys_to_pool(conn: &Connection) -> Result<usize, String> {
JOIN api_key_providers p ON k.provider_id = p.id
ORDER BY k.created_at ASC",
)
.map_err(|e| format!("准备查询语句失败: {}", e))?;
.map_err(|e| format!("准备查询语句失败: {e}"))?;
let rows = stmt
.query_map([], |row| {
@@ -99,13 +99,13 @@ pub fn migrate_api_keys_to_pool(conn: &Connection) -> Result<usize, String> {
provider_name: row.get(11)?,
})
})
.map_err(|e| format!("查询 API Keys 失败: {}", e))?;
.map_err(|e| format!("查询 API Keys 失败: {e}"))?;
let mut migrated_count = 0;
let now = chrono::Utc::now().timestamp();
for row_result in rows {
let row = row_result.map_err(|e| format!("读取行数据失败: {}", e))?;
let row = row_result.map_err(|e| format!("读取行数据失败: {e}"))?;
// 检查是否已存在相同的凭证(通过 api_key_encrypted 判断)
let exists: bool = conn
@@ -228,7 +228,7 @@ pub fn migrate_api_keys_to_pool(conn: &Connection) -> Result<usize, String> {
Option::<String>::None, // proxy_url
],
)
.map_err(|e| format!("插入凭证失败: {}", e))?;
.map_err(|e| format!("插入凭证失败: {e}"))?;
tracing::info!(
"[迁移] 已迁移 API Key: {} -> {} (provider_type: {})",
@@ -245,7 +245,7 @@ pub fn migrate_api_keys_to_pool(conn: &Connection) -> Result<usize, String> {
"INSERT OR REPLACE INTO settings (key, value) VALUES ('migrated_api_keys_to_pool', 'true')",
[],
)
.map_err(|e| format!("标记迁移完成失败: {}", e))?;
.map_err(|e| format!("标记迁移完成失败: {e}"))?;
tracing::info!("[迁移] API Keys 迁移完成,共迁移 {} 条记录", migrated_count);
@@ -349,7 +349,7 @@ pub fn migrate_provider_ids(conn: &Connection) -> Result<usize, String> {
"UPDATE api_keys SET provider_id = ?1 WHERE provider_id = ?2",
params![new_id, old_id],
)
.map_err(|e| format!("迁移 API Keys 失败: {}", e))?;
.map_err(|e| format!("迁移 API Keys 失败: {e}"))?;
tracing::info!("[迁移] 已将 {} 的 API Keys 迁移到 {}", old_id, new_id);
} else {
@@ -358,13 +358,13 @@ pub fn migrate_provider_ids(conn: &Connection) -> Result<usize, String> {
"UPDATE api_key_providers SET id = ?1 WHERE id = ?2",
params![new_id, old_id],
)
.map_err(|e| format!("更新 Provider ID 失败: {}", e))?;
.map_err(|e| format!("更新 Provider ID 失败: {e}"))?;
conn.execute(
"UPDATE api_keys SET provider_id = ?1 WHERE provider_id = ?2",
params![new_id, old_id],
)
.map_err(|e| format!("更新 API Keys provider_id 失败: {}", e))?;
.map_err(|e| format!("更新 API Keys provider_id 失败: {e}"))?;
tracing::info!("[迁移] 已将 Provider {} 重命名为 {}", old_id, new_id);
migrated_count += 1;
@@ -377,7 +377,7 @@ pub fn migrate_provider_ids(conn: &Connection) -> Result<usize, String> {
"DELETE FROM api_key_providers WHERE id = ?1",
params![old_id],
)
.map_err(|e| format!("删除旧 Provider 失败: {}", e))?;
.map_err(|e| format!("删除旧 Provider 失败: {e}"))?;
tracing::info!("[迁移] 已删除旧 Provider: {}", old_id);
migrated_count += 1;
@@ -388,7 +388,7 @@ pub fn migrate_provider_ids(conn: &Connection) -> Result<usize, String> {
"INSERT OR REPLACE INTO settings (key, value) VALUES ('migrated_provider_ids_v1', 'true')",
[],
)
.map_err(|e| format!("标记迁移完成失败: {}", e))?;
.map_err(|e| format!("标记迁移完成失败: {e}"))?;
if migrated_count > 0 {
tracing::info!(
@@ -440,7 +440,7 @@ pub fn cleanup_legacy_api_key_credentials(conn: &Connection) -> Result<usize, St
"INSERT OR REPLACE INTO settings (key, value) VALUES ('cleaned_legacy_api_key_credentials', 'true')",
[],
)
.map_err(|e| format!("标记清理完成失败: {}", e))?;
.map_err(|e| format!("标记清理完成失败: {e}"))?;
return Ok(0);
}
@@ -452,7 +452,7 @@ pub fn cleanup_legacy_api_key_credentials(conn: &Connection) -> Result<usize, St
WHERE credential_data LIKE '%\"type\":\"openai_key\"%'
OR credential_data LIKE '%\"type\":\"claude_key\"%'",
)
.map_err(|e| format!("准备查询语句失败: {}", e))?;
.map_err(|e| format!("准备查询语句失败: {e}"))?;
let rows = stmt
.query_map([], |row| {
@@ -462,7 +462,7 @@ pub fn cleanup_legacy_api_key_credentials(conn: &Connection) -> Result<usize, St
row.get::<_, String>(2)?,
))
})
.map_err(|e| format!("查询旧凭证失败: {}", e))?;
.map_err(|e| format!("查询旧凭证失败: {e}"))?;
for row_result in rows {
if let Ok((uuid, name, provider_type)) = row_result {
@@ -483,14 +483,14 @@ pub fn cleanup_legacy_api_key_credentials(conn: &Connection) -> Result<usize, St
OR credential_data LIKE '%\"type\":\"claude_key\"%'",
[],
)
.map_err(|e| format!("删除旧凭证失败: {}", e))?;
.map_err(|e| format!("删除旧凭证失败: {e}"))?;
// 标记清理完成
conn.execute(
"INSERT OR REPLACE INTO settings (key, value) VALUES ('cleaned_legacy_api_key_credentials', 'true')",
[],
)
.map_err(|e| format!("标记清理完成失败: {}", e))?;
.map_err(|e| format!("标记清理完成失败: {e}"))?;
tracing::info!("[清理] 旧 API Key 凭证清理完成,共删除 {} 条记录", deleted);
@@ -594,7 +594,7 @@ pub fn migrate_general_chat_to_unified(conn: &Connection) -> Result<usize, Strin
"INSERT OR REPLACE INTO settings (key, value) VALUES ('migrated_general_chat_to_unified', 'true')",
[],
)
.map_err(|e| format!("标记迁移完成失败: {}", e))?;
.map_err(|e| format!("标记迁移完成失败: {e}"))?;
return Ok(0);
}
@@ -616,7 +616,7 @@ pub fn migrate_general_chat_to_unified(conn: &Connection) -> Result<usize, Strin
"INSERT OR REPLACE INTO settings (key, value) VALUES ('migrated_general_chat_to_unified', 'true')",
[],
)
.map_err(|e| format!("标记迁移完成失败: {}", e))?;
.map_err(|e| format!("标记迁移完成失败: {e}"))?;
tracing::info!("[迁移] General Chat 数据迁移完成!");
Ok(migrated_sessions + migrated_messages)
@@ -629,7 +629,7 @@ fn migrate_general_sessions(conn: &Connection) -> Result<usize, String> {
"SELECT id, name, created_at, updated_at, metadata
FROM general_chat_sessions",
)
.map_err(|e| format!("准备查询语句失败: {}", e))?;
.map_err(|e| format!("准备查询语句失败: {e}"))?;
let sessions: Vec<(String, String, i64, i64, Option<String>)> = stmt
.query_map([], |row| {
@@ -641,7 +641,7 @@ fn migrate_general_sessions(conn: &Connection) -> Result<usize, String> {
row.get(4)?,
))
})
.map_err(|e| format!("查询会话失败: {}", e))?
.map_err(|e| format!("查询会话失败: {e}"))?
.filter_map(|r| r.ok())
.collect();
@@ -678,7 +678,7 @@ fn migrate_general_sessions(conn: &Connection) -> Result<usize, String> {
updated_str,
],
)
.map_err(|e| format!("插入会话失败: {}", e))?;
.map_err(|e| format!("插入会话失败: {e}"))?;
count += 1;
}
@@ -693,7 +693,7 @@ fn migrate_general_messages(conn: &Connection) -> Result<usize, String> {
"SELECT id, session_id, role, content, blocks, status, created_at, metadata
FROM general_chat_messages",
)
.map_err(|e| format!("准备查询语句失败: {}", e))?;
.map_err(|e| format!("准备查询语句失败: {e}"))?;
#[allow(clippy::type_complexity)]
let messages: Vec<(
@@ -718,7 +718,7 @@ fn migrate_general_messages(conn: &Connection) -> Result<usize, String> {
row.get(7)?,
))
})
.map_err(|e| format!("查询消息失败: {}", e))?
.map_err(|e| format!("查询消息失败: {e}"))?
.filter_map(|r| r.ok())
.collect();
@@ -755,7 +755,7 @@ fn migrate_general_messages(conn: &Connection) -> Result<usize, String> {
Option::<String>::None,
],
)
.map_err(|e| format!("插入消息失败: {}", e))?;
.map_err(|e| format!("插入消息失败: {e}"))?;
count += 1;
}
+14 -3
View File
@@ -9,12 +9,23 @@ use std::sync::{Arc, Mutex};
pub type DbConnection = Arc<Mutex<Connection>>;
/// 获取数据库连接锁(自动处理 poisoned lock)
pub fn lock_db(db: &DbConnection) -> Result<std::sync::MutexGuard<'_, Connection>, String> {
match db.lock() {
Ok(guard) => Ok(guard),
Err(poisoned) => {
tracing::warn!("[数据库] 检测到数据库锁被污染,尝试恢复: {}", poisoned);
db.clear_poison();
Ok(poisoned.into_inner())
}
}
}
/// 获取数据库文件路径
pub fn get_db_path() -> Result<PathBuf, String> {
let home = dirs::home_dir().ok_or_else(|| "无法获取主目录".to_string())?;
let db_dir = home.join(".proxycast");
std::fs::create_dir_all(&db_dir)
.map_err(|e| format!("无法创建数据库目录 {:?}: {}", db_dir, e))?;
std::fs::create_dir_all(&db_dir).map_err(|e| format!("无法创建数据库目录 {db_dir:?}: {e}"))?;
Ok(db_dir.join("proxycast.db"))
}
@@ -25,7 +36,7 @@ pub fn init_database() -> Result<DbConnection, String> {
// 设置 busy_timeout 为 5 秒,避免 "database is locked" 错误
conn.busy_timeout(std::time::Duration::from_secs(5))
.map_err(|e| format!("设置 busy_timeout 失败: {}", e))?;
.map_err(|e| format!("设置 busy_timeout 失败: {e}"))?;
// 创建表结构
schema::create_tables(&conn).map_err(|e| e.to_string())?;
+2 -2
View File
@@ -94,12 +94,12 @@ impl DevBridgeServer {
let listener = match tokio::net::TcpListener::bind(&addr).await {
Ok(l) => l,
Err(e) => {
eprintln!("[DevBridge] 绑定失败: {} (地址: {})", e, addr);
eprintln!("[DevBridge] 绑定失败: {e} (地址: {addr})");
return Err(e.into());
}
};
eprintln!("[DevBridge] 正在监听: http://{}", addr);
eprintln!("[DevBridge] 正在监听: http://{addr}");
// 直接运行服务器(不使用 graceful_shutdown)
// 服务器将持续运行直到应用退出
+12 -13
View File
@@ -214,9 +214,9 @@ pub async fn handle_command(
updated_at: now,
};
let conn = db.lock().map_err(|e| format!("数据库锁定失败: {}", e))?;
let conn = db.lock().map_err(|e| format!("数据库锁定失败: {e}"))?;
AgentDao::create_session(&conn, &session)
.map_err(|e| format!("创建会话失败: {}", e))?;
.map_err(|e| format!("创建会话失败: {e}"))?;
Ok(serde_json::json!({
"session_id": session_id,
@@ -234,9 +234,9 @@ pub async fn handle_command(
if let Some(db) = &state.db {
use crate::database::dao::agent::AgentDao;
let conn = db.lock().map_err(|e| format!("数据库锁定失败: {}", e))?;
let conn = db.lock().map_err(|e| format!("数据库锁定失败: {e}"))?;
let sessions = AgentDao::list_sessions(&conn)
.map_err(|e| format!("获取会话列表失败: {}", e))?;
.map_err(|e| format!("获取会话列表失败: {e}"))?;
let result: Vec<serde_json::Value> = sessions
.into_iter()
@@ -269,10 +269,10 @@ pub async fn handle_command(
if let Some(db) = &state.db {
use crate::database::dao::agent::AgentDao;
let conn = db.lock().map_err(|e| format!("数据库锁定失败: {}", e))?;
let conn = db.lock().map_err(|e| format!("数据库锁定失败: {e}"))?;
let session = AgentDao::get_session(&conn, &session_id)
.map_err(|e| format!("获取会话失败: {}", e))?
.ok_or_else(|| "会话不存在")?;
.map_err(|e| format!("获取会话失败: {e}"))?
.ok_or("会话不存在")?;
let messages_count = AgentDao::get_message_count(&conn, &session_id).unwrap_or(0);
@@ -299,9 +299,9 @@ pub async fn handle_command(
if let Some(db) = &state.db {
use crate::database::dao::agent::AgentDao;
let conn = db.lock().map_err(|e| format!("数据库锁定失败: {}", e))?;
let conn = db.lock().map_err(|e| format!("数据库锁定失败: {e}"))?;
AgentDao::delete_session(&conn, &session_id)
.map_err(|e| format!("删除会话失败: {}", e))?;
.map_err(|e| format!("删除会话失败: {e}"))?;
Ok(serde_json::json!({ "success": true }))
} else {
@@ -319,9 +319,9 @@ pub async fn handle_command(
if let Some(db) = &state.db {
use crate::database::dao::agent::AgentDao;
let conn = db.lock().map_err(|e| format!("数据库锁定失败: {}", e))?;
let conn = db.lock().map_err(|e| format!("数据库锁定失败: {e}"))?;
let messages = AgentDao::get_messages(&conn, &session_id)
.map_err(|e| format!("获取消息失败: {}", e))?;
.map_err(|e| format!("获取消息失败: {e}"))?;
Ok(serde_json::to_value(messages)?)
} else {
@@ -330,8 +330,7 @@ pub async fn handle_command(
}
_ => Err(format!(
"[DevBridge] 未知命令: '{}'. 如需此命令,请将其添加到 dispatcher.rs 的 handle_command 函数中。",
cmd
"[DevBridge] 未知命令: '{cmd}'. 如需此命令,请将其添加到 dispatcher.rs 的 handle_command 函数中。"
)
.into()),
}
+9 -9
View File
@@ -318,13 +318,13 @@ impl BatchOperations {
Ok(Some(_)) => {}
Ok(None) => {
for flow_id in flow_ids {
result.record_failure(flow_id, format!("会话不存在: {}", session_id));
result.record_failure(flow_id, format!("会话不存在: {session_id}"));
}
return;
}
Err(e) => {
for flow_id in flow_ids {
result.record_failure(flow_id, format!("查询会话失败: {}", e));
result.record_failure(flow_id, format!("查询会话失败: {e}"));
}
return;
}
@@ -344,7 +344,7 @@ impl BatchOperations {
result.record_success();
}
Err(e) => {
result.record_failure(flow_id, format!("添加到会话失败: {}", e));
result.record_failure(flow_id, format!("添加到会话失败: {e}"));
}
}
}
@@ -404,7 +404,7 @@ mod property_tests {
// 创建测试 Flow
let mut flow_ids = Vec::new();
for i in 0..flow_count {
let id = create_test_flow(&monitor, &format!("flow-{}", i)).await;
let id = create_test_flow(&monitor, &format!("flow-{i}")).await;
flow_ids.push(id);
}
@@ -446,14 +446,14 @@ mod property_tests {
// 创建有效的 Flow
let mut valid_flow_ids = Vec::new();
for i in 0..valid_flow_count {
let id = create_test_flow(&monitor, &format!("valid-flow-{}", i)).await;
let id = create_test_flow(&monitor, &format!("valid-flow-{i}")).await;
valid_flow_ids.push(id);
}
// 创建无效的 Flow ID(不存在的)
let mut invalid_flow_ids = Vec::new();
for i in 0..invalid_flow_count {
invalid_flow_ids.push(format!("invalid-flow-{}", i));
invalid_flow_ids.push(format!("invalid-flow-{i}"));
}
// 混合有效和无效的 Flow ID
@@ -512,18 +512,18 @@ mod property_tests {
// 创建有效的 Flow
let mut valid_flow_ids = Vec::new();
for i in 0..valid_flow_count {
let id = create_test_flow(&monitor, &format!("valid-flow-{}", i)).await;
let id = create_test_flow(&monitor, &format!("valid-flow-{i}")).await;
valid_flow_ids.push(id);
}
// 创建无效的 Flow ID
let mut invalid_flow_ids = Vec::new();
for i in 0..invalid_flow_count {
invalid_flow_ids.push(format!("invalid-flow-{}", i));
invalid_flow_ids.push(format!("invalid-flow-{i}"));
}
// 创建标签列表
let tags: Vec<String> = (0..tag_count).map(|i| format!("tag-{}", i)).collect();
let tags: Vec<String> = (0..tag_count).map(|i| format!("tag-{i}")).collect();
// 混合有效和无效的 Flow ID
let mut all_flow_ids = valid_flow_ids.clone();
+2 -2
View File
@@ -901,7 +901,7 @@ mod property_tests {
let mut added_bookmarks = Vec::new();
for (i, (flow_id, name, group)) in bookmarks.iter().enumerate() {
// 确保 flow_id 唯一
let unique_flow_id = format!("{}_{}", flow_id, i);
let unique_flow_id = format!("{flow_id}_{i}");
let bookmark = manager1.add(&unique_flow_id, name.as_deref(), group.as_deref()).unwrap();
added_bookmarks.push(bookmark);
}
@@ -984,7 +984,7 @@ mod property_tests {
// 添加所有书签
let mut added_ids = Vec::new();
for (i, (flow_id, name, group)) in bookmarks.iter().enumerate() {
let unique_flow_id = format!("{}_{}", flow_id, i);
let unique_flow_id = format!("{flow_id}_{i}");
let bookmark = manager.add(&unique_flow_id, name.as_deref(), group.as_deref()).unwrap();
added_ids.push(bookmark.id);
}
+10 -14
View File
@@ -15,8 +15,10 @@ use super::models::{LLMFlow, LLMRequest};
/// 代码导出格式
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
#[derive(Default)]
pub enum CodeFormat {
/// curl 命令
#[default]
Curl,
/// Python 代码
Python,
@@ -26,12 +28,6 @@ pub enum CodeFormat {
JavaScript,
}
impl Default for CodeFormat {
fn default() -> Self {
CodeFormat::Curl
}
}
// ============================================================================
// 代码导出器
// ============================================================================
@@ -90,7 +86,7 @@ impl CodeExporter {
} else {
format!("http://localhost{}", request.path)
};
parts.push(format!("'{}'", url));
parts.push(format!("'{url}'"));
// 添加请求头
for (key, value) in &request.headers {
@@ -102,7 +98,7 @@ impl CodeExporter {
} else {
escape_shell_string(value)
};
parts.push(format!("-H '{}: {}'", key, header_value));
parts.push(format!("-H '{key}: {header_value}'"));
}
// 确保有 Content-Type 头
@@ -153,7 +149,7 @@ impl CodeExporter {
} else {
format!("http://localhost{}", request.path)
};
code.push_str(&format!("url = \"{}\"\n\n", url));
code.push_str(&format!("url = \"{url}\"\n\n"));
// 请求头
code.push_str("headers = {\n");
@@ -171,9 +167,9 @@ impl CodeExporter {
};
if key.to_lowercase() == "authorization" || key.to_lowercase() == "x-api-key" {
code.push_str(&format!(" \"{}\": {},\n", key, header_value));
code.push_str(&format!(" \"{key}\": {header_value},\n"));
} else {
code.push_str(&format!(" \"{}\": {},\n", key, header_value));
code.push_str(&format!(" \"{key}\": {header_value},\n"));
}
}
if !has_content_type {
@@ -184,7 +180,7 @@ impl CodeExporter {
// 请求体
if !request.body.is_null() {
let body_str = serde_json::to_string_pretty(&request.body).unwrap_or_default();
code.push_str(&format!("data = {}\n\n", body_str));
code.push_str(&format!("data = {body_str}\n\n"));
} else {
code.push_str("data = {}\n\n");
}
@@ -250,7 +246,7 @@ impl CodeExporter {
} else {
format!("'{}'", escape_js_string(value))
};
code.push_str(&format!(" '{}': {},\n", key, header_value));
code.push_str(&format!(" '{key}': {header_value},\n"));
}
if !has_content_type {
code.push_str(" 'Content-Type': 'application/json',\n");
@@ -330,7 +326,7 @@ impl CodeExporter {
} else {
format!("'{}'", escape_js_string(value))
};
code.push_str(&format!(" '{}': {},\n", key, header_value));
code.push_str(&format!(" '{key}': {header_value},\n"));
}
if !has_content_type {
code.push_str(" 'Content-Type': 'application/json',\n");
+33 -44
View File
@@ -20,7 +20,7 @@ use super::models::{LLMFlow, Message, MessageContent, TokenUsage};
// ============================================================================
/// 差异类型
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
pub enum DiffType {
/// 新增
Added,
@@ -29,15 +29,10 @@ pub enum DiffType {
/// 修改
Modified,
/// 未变化
#[default]
Unchanged,
}
impl Default for DiffType {
fn default() -> Self {
DiffType::Unchanged
}
}
// ============================================================================
// 差异项
// ============================================================================
@@ -344,36 +339,30 @@ impl FlowDiff {
let mut diffs = Vec::new();
// 对比模型
if !config.should_ignore("request.model") {
if left.model != right.model {
diffs.push(DiffItem::modified(
"request.model",
Value::String(left.model.clone()),
Value::String(right.model.clone()),
));
}
if !config.should_ignore("request.model") && left.model != right.model {
diffs.push(DiffItem::modified(
"request.model",
Value::String(left.model.clone()),
Value::String(right.model.clone()),
));
}
// 对比方法
if !config.should_ignore("request.method") {
if left.method != right.method {
diffs.push(DiffItem::modified(
"request.method",
Value::String(left.method.clone()),
Value::String(right.method.clone()),
));
}
if !config.should_ignore("request.method") && left.method != right.method {
diffs.push(DiffItem::modified(
"request.method",
Value::String(left.method.clone()),
Value::String(right.method.clone()),
));
}
// 对比路径
if !config.should_ignore("request.path") {
if left.path != right.path {
diffs.push(DiffItem::modified(
"request.path",
Value::String(left.path.clone()),
Value::String(right.path.clone()),
));
}
if !config.should_ignore("request.path") && left.path != right.path {
diffs.push(DiffItem::modified(
"request.path",
Value::String(left.path.clone()),
Value::String(right.path.clone()),
));
}
// 对比系统提示词
@@ -638,8 +627,8 @@ impl FlowDiff {
if !config.should_ignore("metadata.provider") && left.provider != right.provider {
diffs.push(DiffItem::modified(
"metadata.provider",
serde_json::to_value(&left.provider).unwrap_or(Value::Null),
serde_json::to_value(&right.provider).unwrap_or(Value::Null),
serde_json::to_value(left.provider).unwrap_or(Value::Null),
serde_json::to_value(right.provider).unwrap_or(Value::Null),
));
}
@@ -731,12 +720,12 @@ impl FlowDiff {
/// 对比单个消息的内容
fn diff_message_content(left: &Message, right: &Message, index: usize) -> Vec<DiffItem> {
let mut diffs = Vec::new();
let prefix = format!("messages[{}]", index);
let prefix = format!("messages[{index}]");
// 对比角色
if left.role != right.role {
diffs.push(DiffItem::modified(
format!("{}.role", prefix),
format!("{prefix}.role"),
serde_json::to_value(&left.role).unwrap_or(Value::Null),
serde_json::to_value(&right.role).unwrap_or(Value::Null),
));
@@ -747,7 +736,7 @@ impl FlowDiff {
let right_text = Self::get_message_text(&right.content);
if left_text != right_text {
diffs.push(DiffItem::modified(
format!("{}.content", prefix),
format!("{prefix}.content"),
Value::String(left_text),
Value::String(right_text),
));
@@ -757,20 +746,20 @@ impl FlowDiff {
match (&left.name, &right.name) {
(Some(l), Some(r)) if l != r => {
diffs.push(DiffItem::modified(
format!("{}.name", prefix),
format!("{prefix}.name"),
Value::String(l.clone()),
Value::String(r.clone()),
));
}
(Some(l), None) => {
diffs.push(DiffItem::removed(
format!("{}.name", prefix),
format!("{prefix}.name"),
Value::String(l.clone()),
));
}
(None, Some(r)) => {
diffs.push(DiffItem::added(
format!("{}.name", prefix),
format!("{prefix}.name"),
Value::String(r.clone()),
));
}
@@ -781,20 +770,20 @@ impl FlowDiff {
match (&left.tool_calls, &right.tool_calls) {
(Some(l), Some(r)) if l.len() != r.len() => {
diffs.push(DiffItem::modified(
format!("{}.tool_calls.count", prefix),
format!("{prefix}.tool_calls.count"),
serde_json::json!(l.len()),
serde_json::json!(r.len()),
));
}
(Some(l), None) => {
diffs.push(DiffItem::removed(
format!("{}.tool_calls", prefix),
format!("{prefix}.tool_calls"),
serde_json::to_value(l).unwrap_or(Value::Null),
));
}
(None, Some(r)) => {
diffs.push(DiffItem::added(
format!("{}.tool_calls", prefix),
format!("{prefix}.tool_calls"),
serde_json::to_value(r).unwrap_or(Value::Null),
));
}
@@ -864,7 +853,7 @@ impl FlowDiff {
let new_path = if path.is_empty() {
key.clone()
} else {
format!("{}.{}", path, key)
format!("{path}.{key}")
};
match (l.get(key), r.get(key)) {
@@ -888,7 +877,7 @@ impl FlowDiff {
(Value::Array(l), Value::Array(r)) => {
let max_len = l.len().max(r.len());
for i in 0..max_len {
let new_path = format!("{}[{}]", path, i);
let new_path = format!("{path}[{i}]");
match (l.get(i), r.get(i)) {
(Some(lv), Some(rv)) => {
diffs.extend(Self::diff_json(lv, rv, &new_path, config));
+10 -23
View File
@@ -27,7 +27,7 @@ pub struct TimeSeriesPoint {
}
/// 分布数据
#[derive(Debug, Clone, Serialize, Deserialize)]
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct Distribution {
/// 分布桶 (标签, 数量)
pub buckets: Vec<(String, u64)>,
@@ -35,15 +35,6 @@ pub struct Distribution {
pub total: u64,
}
impl Default for Distribution {
fn default() -> Self {
Self {
buckets: Vec::new(),
total: 0,
}
}
}
/// 趋势数据
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TrendData {
@@ -117,8 +108,10 @@ impl Default for StatsTimeRange {
/// 统计报告格式
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "lowercase")]
#[derive(Default)]
pub enum ReportFormat {
/// JSON 格式
#[default]
Json,
/// Markdown 格式
Markdown,
@@ -126,12 +119,6 @@ pub enum ReportFormat {
Csv,
}
impl Default for ReportFormat {
fn default() -> Self {
ReportFormat::Json
}
}
// ============================================================================
// 增强统计服务
// ============================================================================
@@ -506,7 +493,7 @@ impl EnhancedStatsService {
md.push_str("| 模型 | Token 数 |\n");
md.push_str("|------|----------|\n");
for (model, tokens) in &stats.token_by_model.buckets {
md.push_str(&format!("| {} | {} |\n", model, tokens));
md.push_str(&format!("| {model} | {tokens} |\n"));
}
md.push_str(&format!(
"| **总计** | **{}** |\n\n",
@@ -527,7 +514,7 @@ impl EnhancedStatsService {
md.push_str("| 延迟范围 | 请求数 |\n");
md.push_str("|----------|--------|\n");
for (range, count) in &stats.latency_histogram.buckets {
md.push_str(&format!("| {} | {} |\n", range, count));
md.push_str(&format!("| {range} | {count} |\n"));
}
md.push('\n');
@@ -537,7 +524,7 @@ impl EnhancedStatsService {
md.push_str("| 错误类型 | 数量 |\n");
md.push_str("|----------|------|\n");
for (error_type, count) in &stats.error_distribution.buckets {
md.push_str(&format!("| {} | {} |\n", error_type, count));
md.push_str(&format!("| {error_type} | {count} |\n"));
}
md.push('\n');
}
@@ -553,7 +540,7 @@ impl EnhancedStatsService {
csv.push_str("# Token Distribution by Model\n");
csv.push_str("Model,Tokens\n");
for (model, tokens) in &stats.token_by_model.buckets {
csv.push_str(&format!("{},{}\n", model, tokens));
csv.push_str(&format!("{model},{tokens}\n"));
}
csv.push('\n');
@@ -561,7 +548,7 @@ impl EnhancedStatsService {
csv.push_str("# Success Rate by Provider\n");
csv.push_str("Provider,SuccessRate\n");
for (provider, rate) in &stats.success_by_provider {
csv.push_str(&format!("{},{:.4}\n", provider, rate));
csv.push_str(&format!("{provider},{rate:.4}\n"));
}
csv.push('\n');
@@ -569,7 +556,7 @@ impl EnhancedStatsService {
csv.push_str("# Latency Histogram\n");
csv.push_str("Range,Count\n");
for (range, count) in &stats.latency_histogram.buckets {
csv.push_str(&format!("{},{}\n", range, count));
csv.push_str(&format!("{range},{count}\n"));
}
csv.push('\n');
@@ -577,7 +564,7 @@ impl EnhancedStatsService {
csv.push_str("# Error Distribution\n");
csv.push_str("ErrorType,Count\n");
for (error_type, count) in &stats.error_distribution.buckets {
csv.push_str(&format!("{},{}\n", error_type, count));
csv.push_str(&format!("{error_type},{count}\n"));
}
csv
+24 -28
View File
@@ -21,10 +21,12 @@ use crate::ProviderType;
/// 导出格式
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
#[derive(Default)]
pub enum ExportFormat {
/// HAR (HTTP Archive) 格式
HAR,
/// JSON 格式
#[default]
JSON,
/// JSONL (JSON Lines) 格式
JSONL,
@@ -34,12 +36,6 @@ pub enum ExportFormat {
CSV,
}
impl Default for ExportFormat {
fn default() -> Self {
ExportFormat::JSON
}
}
// ============================================================================
// 导出选项
// ============================================================================
@@ -794,7 +790,7 @@ impl FlowExporter {
}),
streaming: request.parameters.stream,
ttfb_ms: flow.timestamps.ttfb_ms,
stop_reason: response.and_then(|r| r.stop_reason.as_ref().map(|s| format!("{:?}", s))),
stop_reason: response.and_then(|r| r.stop_reason.as_ref().map(|s| format!("{s:?}"))),
has_tool_calls: response.map(|r| !r.tool_calls.is_empty()).unwrap_or(false),
has_thinking: response.map(|r| r.thinking.is_some()).unwrap_or(false),
annotations: if flow.annotations.starred
@@ -880,7 +876,7 @@ impl FlowExporter {
.map(|(i, f)| {
let md = self.flow_to_markdown(f);
if i > 0 {
format!("\n---\n\n{}", md)
format!("\n---\n\n{md}")
} else {
md
}
@@ -909,7 +905,7 @@ impl FlowExporter {
));
md.push_str(&format!("- **耗时**: {} ms\n", flow.timestamps.duration_ms));
if let Some(ttfb) = flow.timestamps.ttfb_ms {
md.push_str(&format!("- **TTFB**: {} ms\n", ttfb));
md.push_str(&format!("- **TTFB**: {ttfb} ms\n"));
}
md.push_str(&format!("- **流式**: {}\n", flow.request.parameters.stream));
md.push('\n');
@@ -930,10 +926,10 @@ impl FlowExporter {
response.usage.total_tokens
));
if let Some(cache_read) = response.usage.cache_read_tokens {
md.push_str(&format!("- **缓存读取**: {}\n", cache_read));
md.push_str(&format!("- **缓存读取**: {cache_read}\n"));
}
if let Some(thinking) = response.usage.thinking_tokens {
md.push_str(&format!("- **思维链 Token**: {}\n", thinking));
md.push_str(&format!("- **思维链 Token**: {thinking}\n"));
}
md.push('\n');
}
@@ -1023,7 +1019,7 @@ impl FlowExporter {
// 停止原因
if let Some(ref stop_reason) = response.stop_reason {
md.push_str(&format!("**停止原因**: {:?}\n\n", stop_reason));
md.push_str(&format!("**停止原因**: {stop_reason:?}\n\n"));
}
}
@@ -1033,7 +1029,7 @@ impl FlowExporter {
md.push_str(&format!("- **类型**: {:?}\n", error.error_type));
md.push_str(&format!("- **消息**: {}\n", error.message));
if let Some(code) = error.status_code {
md.push_str(&format!("- **状态码**: {}\n", code));
md.push_str(&format!("- **状态码**: {code}\n"));
}
md.push_str(&format!("- **可重试**: {}\n", error.retryable));
md.push('\n');
@@ -1049,7 +1045,7 @@ impl FlowExporter {
md.push_str("- ⭐ **已收藏**\n");
}
if let Some(ref marker) = flow.annotations.marker {
md.push_str(&format!("- **标记**: {}\n", marker));
md.push_str(&format!("- **标记**: {marker}\n"));
}
if !flow.annotations.tags.is_empty() {
md.push_str(&format!(
@@ -1058,7 +1054,7 @@ impl FlowExporter {
));
}
if let Some(ref comment) = flow.annotations.comment {
md.push_str(&format!("- **评论**: {}\n", comment));
md.push_str(&format!("- **评论**: {comment}\n"));
}
md.push('\n');
}
@@ -1721,7 +1717,7 @@ mod property_tests {
// 验证每行都能反序列化
for (i, line) in lines.iter().enumerate() {
let deserialized: LLMFlow = serde_json::from_str(line)
.expect(&format!("第 {} 行应该能够反序列化", i));
.unwrap_or_else(|_| panic!("第 {i} 行应该能够反序列化"));
prop_assert_eq!(
&flows[i].id, &deserialized.id,
@@ -1843,7 +1839,7 @@ mod redaction_property_tests {
"[a-z]{3,10}",
prop_oneof!["com", "org", "net", "io"],
)
.prop_map(|(user, domain, tld)| format!("{}@{}.{}", user, domain, tld))
.prop_map(|(user, domain, tld)| format!("{user}@{domain}.{tld}"))
}
/// 生成随机中国手机号
@@ -1852,17 +1848,17 @@ mod redaction_property_tests {
prop_oneof![Just("13"), Just("15"), Just("18"), Just("19")],
"[0-9]{9}",
)
.prop_map(|(prefix, suffix)| format!("{}{}", prefix, suffix))
.prop_map(|(prefix, suffix)| format!("{prefix}{suffix}"))
}
/// 生成随机 API 密钥
fn arb_api_key() -> impl Strategy<Value = String> {
"[a-zA-Z0-9]{20,40}".prop_map(|s| format!("sk-{}", s))
"[a-zA-Z0-9]{20,40}".prop_map(|s| format!("sk-{s}"))
}
/// 生成随机 Bearer Token
fn arb_bearer_token() -> impl Strategy<Value = String> {
"[a-zA-Z0-9_.-]{20,50}".prop_map(|s| format!("Bearer {}", s))
"[a-zA-Z0-9_.-]{20,50}".prop_map(|s| format!("Bearer {s}"))
}
/// 生成包含敏感数据的文本
@@ -1870,27 +1866,27 @@ mod redaction_property_tests {
prop_oneof![
// 包含邮箱
arb_email().prop_map(|email| {
let text = format!("Contact me at {} for more info.", email);
let text = format!("Contact me at {email} for more info.");
(text, vec![email])
}),
// 包含手机号
arb_phone_cn().prop_map(|phone| {
let text = format!("My phone number is {}.", phone);
let text = format!("My phone number is {phone}.");
(text, vec![phone])
}),
// 包含 API 密钥
arb_api_key().prop_map(|key| {
let text = format!("Use this API key: {}", key);
let text = format!("Use this API key: {key}");
(text, vec![key])
}),
// 包含 Bearer Token
arb_bearer_token().prop_map(|token| {
let text = format!("Authorization: {}", token);
let text = format!("Authorization: {token}");
(text, vec![token])
}),
// 包含多种敏感数据
(arb_email(), arb_phone_cn()).prop_map(|(email, phone)| {
let text = format!("Email: {}, Phone: {}", email, phone);
let text = format!("Email: {email}, Phone: {phone}");
(text, vec![email, phone])
}),
]
@@ -2107,7 +2103,7 @@ mod redaction_property_tests {
#[test]
fn prop_redact_email(email in arb_email()) {
let redactor = Redactor::with_defaults();
let text = format!("Contact: {}", email);
let text = format!("Contact: {email}");
let redacted = redactor.redact(&text);
@@ -2129,7 +2125,7 @@ mod redaction_property_tests {
#[test]
fn prop_redact_phone(phone in arb_phone_cn()) {
let redactor = Redactor::with_defaults();
let text = format!("Phone: {}", phone);
let text = format!("Phone: {phone}");
let redacted = redactor.redact(&text);
@@ -2151,7 +2147,7 @@ mod redaction_property_tests {
#[test]
fn prop_redact_api_key(key in arb_api_key()) {
let redactor = Redactor::with_defaults();
let text = format!("API Key: {}", key);
let text = format!("API Key: {key}");
let redacted = redactor.redact(&text);

Some files were not shown because too many files have changed in this diff Show More