mirror of
https://github.com/aiclientproxy/proxycast.git
synced 2026-09-24 23:10:56 +08:00
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:
+2
-1
@@ -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",
|
||||
|
||||
Generated
+16
-16
@@ -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]]
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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, "操作已取消")
|
||||
|
||||
@@ -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"
|
||||
@@ -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::*;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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}"))
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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}"));
|
||||
// 不中断流程
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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,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}"
|
||||
))),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 配置
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
/// 确认权限请求
|
||||
|
||||
@@ -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(¤t_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())
|
||||
}
|
||||
|
||||
|
||||
@@ -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")]
|
||||
|
||||
@@ -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!(
|
||||
|
||||
@@ -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}' 已存在"));
|
||||
}
|
||||
|
||||
// 创建连接配置
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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(®istration)
|
||||
.map_err(|e| format!("序列化客户端注册信息失败: {}", e))?;
|
||||
.map_err(|e| format!("序列化客户端注册信息失败: {e}"))?;
|
||||
|
||||
fs::write(®istration_path, ®istration_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 {
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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())?;
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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)]
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
|
||||
@@ -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}")),
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -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} 超时"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 ¤t_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(®_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 => {
|
||||
|
||||
@@ -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()],
|
||||
|
||||
@@ -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!("截图对话消息已发送");
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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, ¤t_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}"))
|
||||
}
|
||||
|
||||
@@ -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)))
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
};
|
||||
|
||||
@@ -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}"));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
// 打开独立的更新提醒窗口 - 必须在主线程执行
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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}"))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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} 的数据往返失败");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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}"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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("写入文件失败");
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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!(
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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_url));
|
||||
content.push_str(&format!(""));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
},
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
)
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
)
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
)
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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}"))
|
||||
}
|
||||
|
||||
/// 删除凭证并同步到配置
|
||||
|
||||
@@ -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();
|
||||
|
||||
// 先标记所有凭证
|
||||
|
||||
@@ -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}")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)?,
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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],
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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())?;
|
||||
|
||||
@@ -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)
|
||||
// 服务器将持续运行直到应用退出
|
||||
|
||||
@@ -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()),
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user