Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src-tauri/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ tauri-plugin-shell = "2"
tauri-plugin-single-instance = "2"
tauri-plugin-dialog = "2"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
serde_json = { version = "1", features = ["preserve_order"] }
dirs = "6"
toml = "0.9"
toml_edit = "0.23"
Expand Down
115 changes: 115 additions & 0 deletions src-tauri/src/commands/amp_commands.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
//! AMP Code 用户认证相关命令
//!
//! 通过 AMP Code Access Token 调用 ampcode.com API 获取用户信息

use ::duckcoding::services::proxy_config_manager::ProxyConfigManager;

/// AMP Code 用户信息响应
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
pub struct AmpUserInfo {
pub id: String,
pub email: Option<String>,
pub name: Option<String>,
pub username: Option<String>,
}

/// 通过 AMP Code Access Token 获取用户信息
///
/// 调用 ampcode.com/api/user 验证 token 并获取用户信息
#[tauri::command]
pub async fn get_amp_user_info(access_token: String) -> Result<AmpUserInfo, String> {
let client = reqwest::Client::builder()
.timeout(std::time::Duration::from_secs(10))
.build()
.map_err(|e| format!("创建 HTTP 客户端失败: {}", e))?;

let response = client
.get("https://ampcode.com/api/user")
.header("Authorization", format!("Bearer {}", access_token))
.header("X-Api-Key", &access_token)
.header("Content-Type", "application/json")
.send()
.await
.map_err(|e| format!("请求 AMP Code API 失败: {}", e))?;

if !response.status().is_success() {
let status = response.status();
let body = response
.text()
.await
.unwrap_or_else(|_| "无法读取响应".to_string());
return Err(format!("AMP Code API 返回错误 {}: {}", status, body));
}

let user_info: AmpUserInfo = response
.json()
.await
.map_err(|e| format!("解析用户信息失败: {}", e))?;

tracing::info!(
user_id = %user_info.id,
username = ?user_info.username,
"成功获取 AMP Code 用户信息"
);

Ok(user_info)
}

/// 验证 AMP Access Token 并保存到代理配置
///
/// 1. 调用 get_amp_user_info 验证 token
/// 2. 成功后保存 real_api_key 和 real_base_url 到 proxy.json
#[tauri::command]
pub async fn validate_and_save_amp_token(access_token: String) -> Result<AmpUserInfo, String> {
// 1. 验证 token
let user_info = get_amp_user_info(access_token.clone()).await?;

// 2. 保存到 proxy.json
let proxy_mgr = ProxyConfigManager::new().map_err(|e| e.to_string())?;

let mut config = proxy_mgr
.get_config("amp-code")
.map_err(|e| e.to_string())?
.unwrap_or_else(|| {
use ::duckcoding::models::proxy_config::ToolProxyConfig;
ToolProxyConfig::new(8790)
});

config.real_api_key = Some(access_token);
config.real_base_url = Some("https://ampcode.com".to_string());

proxy_mgr
.update_config("amp-code", config)
.map_err(|e| e.to_string())?;

tracing::info!(
user_id = %user_info.id,
"AMP Code Access Token 验证成功,已保存到代理配置"
);

Ok(user_info)
}

/// 获取已保存的 AMP Code 用户信息(从 proxy.json 读取 token 并验证)
#[tauri::command]
pub async fn get_saved_amp_user_info() -> Result<Option<AmpUserInfo>, String> {
let proxy_mgr = ProxyConfigManager::new().map_err(|e| e.to_string())?;

let config = proxy_mgr
.get_config("amp-code")
.map_err(|e| e.to_string())?;

match config.and_then(|c| c.real_api_key) {
Some(token) => {
// 有保存的 token,尝试获取用户信息
match get_amp_user_info(token).await {
Ok(info) => Ok(Some(info)),
Err(e) => {
tracing::warn!("已保存的 AMP Code Token 无效: {}", e);
Ok(None)
}
}
}
None => Ok(None),
}
}
2 changes: 2 additions & 0 deletions src-tauri/src/commands/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
pub mod amp_commands; // AMP 用户认证命令
pub mod balance_commands;
pub mod config_commands;
pub mod dashboard_commands; // 仪表板状态管理命令
Expand All @@ -19,6 +20,7 @@ pub mod watcher_commands;
pub mod window_commands;

// 重新导出所有命令函数
pub use amp_commands::*; // AMP 用户认证命令
pub use balance_commands::*;
pub use config_commands::*;
pub use dashboard_commands::*; // 仪表板状态管理命令
Expand Down
54 changes: 53 additions & 1 deletion src-tauri/src/commands/profile_commands.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//! Profile 管理 Tauri 命令(v2.1 - 简化版)

use super::error::AppResult;
use ::duckcoding::services::profile_manager::ProfileDescriptor;
use ::duckcoding::services::profile_manager::{AmpProfileSelection, ProfileDescriptor, ProfileRef};
use serde::Deserialize;
use std::sync::Arc;
use tokio::sync::RwLock;
Expand Down Expand Up @@ -193,3 +193,55 @@ pub async fn pm_capture_from_native(
let manager = state.manager.write().await;
Ok(manager.capture_from_native(&tool_id, &name)?)
}

// ==================== AMP Profile Selection ====================

/// AMP Profile 选择输入(前端传递)
#[derive(Debug, Deserialize)]
pub struct AmpSelectionInput {
pub claude: Option<ProfileRefInput>,
pub codex: Option<ProfileRefInput>,
pub gemini: Option<ProfileRefInput>,
}

#[derive(Debug, Deserialize)]
pub struct ProfileRefInput {
pub tool_id: String,
pub profile_name: String,
}

/// 获取 AMP Profile 选择
#[tauri::command]
pub async fn pm_get_amp_selection(
state: tauri::State<'_, ProfileManagerState>,
) -> AppResult<AmpProfileSelection> {
let manager = state.manager.read().await;
Ok(manager.get_amp_selection()?)
}

/// 保存 AMP Profile 选择
#[tauri::command]
pub async fn pm_save_amp_selection(
state: tauri::State<'_, ProfileManagerState>,
input: AmpSelectionInput,
) -> AppResult<()> {
let manager = state.manager.write().await;

let selection = AmpProfileSelection {
claude: input.claude.map(|r| ProfileRef {
tool_id: r.tool_id,
profile_name: r.profile_name,
}),
codex: input.codex.map(|r| ProfileRef {
tool_id: r.tool_id,
profile_name: r.profile_name,
}),
gemini: input.gemini.map(|r| ProfileRef {
tool_id: r.tool_id,
profile_name: r.profile_name,
}),
updated_at: chrono::Utc::now(),
};

Ok(manager.save_amp_selection(&selection)?)
}
Loading