mirror of
https://github.com/aiclientproxy/proxycast.git
synced 2026-09-24 23:10:56 +08:00
feat: v0.14.10 - Kiro 凭证指纹展示和模型列表更新
- 新增 Kiro 凭证指纹信息展示功能(Machine ID、来源、认证方式) - 更新 Kiro 模型映射表,添加 get_supported_models() 函数 - 修复 generate_machine_id_from_credentials 函数导出 - 前端添加指纹按钮和展示 UI(仅 Kiro 凭证显示)
This commit is contained in:
+1
-1
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "proxycast",
|
||||
"private": true,
|
||||
"version": "0.14.8",
|
||||
"version": "0.14.10",
|
||||
"type": "module",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
Generated
+1
-1
@@ -3367,7 +3367,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "proxycast"
|
||||
version = "0.14.8"
|
||||
version = "0.14.10"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-stream",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "proxycast"
|
||||
version = "0.14.8"
|
||||
version = "0.14.10"
|
||||
description = "AI API Proxy Desktop App"
|
||||
authors = ["you"]
|
||||
edition = "2021"
|
||||
|
||||
@@ -1249,3 +1249,83 @@ pub async fn start_antigravity_oauth_login(
|
||||
|
||||
Ok(credential)
|
||||
}
|
||||
|
||||
/// 获取 Kiro 凭证的 Machine ID 指纹信息
|
||||
///
|
||||
/// 返回凭证的唯一设备指纹,用于在 UI 中展示
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub struct KiroFingerprintInfo {
|
||||
/// Machine ID(SHA256 哈希,64 字符)
|
||||
pub machine_id: String,
|
||||
/// Machine ID 的短格式(前 16 字符)
|
||||
pub machine_id_short: String,
|
||||
/// 指纹来源(profileArn / clientId / system)
|
||||
pub source: String,
|
||||
/// 认证方式
|
||||
pub auth_method: String,
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_kiro_credential_fingerprint(
|
||||
db: State<'_, DbConnection>,
|
||||
uuid: String,
|
||||
) -> Result<KiroFingerprintInfo, String> {
|
||||
use crate::database::dao::provider_pool::ProviderPoolDao;
|
||||
use crate::providers::kiro::{generate_machine_id_from_credentials, KiroProvider};
|
||||
|
||||
// 获取凭证文件路径(在锁释放前完成)
|
||||
let creds_file_path = {
|
||||
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))?;
|
||||
|
||||
// 检查是否为 Kiro 凭证
|
||||
match &credential.credential {
|
||||
CredentialData::KiroOAuth { creds_file_path } => creds_file_path.clone(),
|
||||
_ => return Err("只有 Kiro 凭证支持获取指纹信息".to_string()),
|
||||
}
|
||||
}; // conn 在这里释放
|
||||
|
||||
// 加载凭证文件(异步操作,锁已释放)
|
||||
let mut provider = KiroProvider::new();
|
||||
provider
|
||||
.load_credentials_from_path(&creds_file_path)
|
||||
.await
|
||||
.map_err(|e| format!("加载凭证失败: {}", e))?;
|
||||
|
||||
// 确定指纹来源
|
||||
let (source, profile_arn, client_id) = if provider.credentials.profile_arn.is_some() {
|
||||
(
|
||||
"profileArn".to_string(),
|
||||
provider.credentials.profile_arn.as_deref(),
|
||||
None,
|
||||
)
|
||||
} else if provider.credentials.client_id.is_some() {
|
||||
(
|
||||
"clientId".to_string(),
|
||||
None,
|
||||
provider.credentials.client_id.as_deref(),
|
||||
)
|
||||
} else {
|
||||
("system".to_string(), None, None)
|
||||
};
|
||||
|
||||
// 生成 Machine ID
|
||||
let machine_id = generate_machine_id_from_credentials(profile_arn, client_id);
|
||||
let machine_id_short = machine_id[..16].to_string();
|
||||
|
||||
// 获取认证方式
|
||||
let auth_method = provider
|
||||
.credentials
|
||||
.auth_method
|
||||
.clone()
|
||||
.unwrap_or_else(|| "social".to_string());
|
||||
|
||||
Ok(KiroFingerprintInfo {
|
||||
machine_id,
|
||||
machine_id_short,
|
||||
source,
|
||||
auth_method,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -5,18 +5,31 @@ use std::collections::HashMap;
|
||||
use uuid::Uuid;
|
||||
|
||||
/// 模型映射表
|
||||
///
|
||||
/// 参考 AIClient-2-API 的 provider-models.js 和 claude-kiro.js
|
||||
/// 支持的模型列表:
|
||||
/// - claude-opus-4-5, claude-opus-4-5-20251101
|
||||
/// - claude-haiku-4-5, claude-haiku-4-5-20251001
|
||||
/// - claude-sonnet-4-5, claude-sonnet-4-5-20250929
|
||||
/// - claude-sonnet-4-20250514
|
||||
/// - claude-3-7-sonnet-20250219, claude-3-5-sonnet-20241022, claude-3-5-sonnet-latest
|
||||
pub fn get_model_map() -> HashMap<&'static str, &'static str> {
|
||||
let mut map = HashMap::new();
|
||||
// Opus 4.5 系列
|
||||
map.insert("claude-opus-4-5", "claude-opus-4.5");
|
||||
map.insert("claude-opus-4-5-20251101", "claude-opus-4.5");
|
||||
// Haiku 4.5 系列
|
||||
map.insert("claude-haiku-4-5", "claude-haiku-4.5");
|
||||
map.insert("claude-haiku-4-5-20251001", "claude-haiku-4.5");
|
||||
// Sonnet 4.5 系列
|
||||
map.insert("claude-sonnet-4-5", "CLAUDE_SONNET_4_5_20250929_V1_0");
|
||||
map.insert(
|
||||
"claude-sonnet-4-5-20250929",
|
||||
"CLAUDE_SONNET_4_5_20250929_V1_0",
|
||||
);
|
||||
// Sonnet 4 系列
|
||||
map.insert("claude-sonnet-4-20250514", "CLAUDE_SONNET_4_20250514_V1_0");
|
||||
// Sonnet 3.7/3.5 系列(兼容旧版本)
|
||||
map.insert(
|
||||
"claude-3-7-sonnet-20250219",
|
||||
"CLAUDE_3_7_SONNET_20250219_V1_0",
|
||||
@@ -32,6 +45,22 @@ pub fn get_model_map() -> HashMap<&'static str, &'static str> {
|
||||
map
|
||||
}
|
||||
|
||||
/// 获取 Kiro 支持的模型列表
|
||||
///
|
||||
/// 返回所有支持的模型 ID,用于前端展示和健康检查
|
||||
pub fn get_supported_models() -> Vec<&'static str> {
|
||||
vec![
|
||||
"claude-opus-4-5",
|
||||
"claude-opus-4-5-20251101",
|
||||
"claude-haiku-4-5",
|
||||
"claude-haiku-4-5-20251001",
|
||||
"claude-sonnet-4-5",
|
||||
"claude-sonnet-4-5-20250929",
|
||||
"claude-sonnet-4-20250514",
|
||||
"claude-3-7-sonnet-20250219",
|
||||
]
|
||||
}
|
||||
|
||||
pub const DEFAULT_MODEL: &str = "CLAUDE_SONNET_4_5_20250929_V1_0";
|
||||
|
||||
/// 预处理消息:合并连续的 tool 消息到前一个 assistant 消息后的 user 消息
|
||||
|
||||
@@ -1683,6 +1683,7 @@ pub fn run() {
|
||||
commands::provider_pool_cmd::migrate_private_config_to_pool,
|
||||
commands::provider_pool_cmd::start_antigravity_oauth_login,
|
||||
commands::provider_pool_cmd::get_antigravity_auth_url_and_wait,
|
||||
commands::provider_pool_cmd::get_kiro_credential_fingerprint,
|
||||
// Route commands
|
||||
commands::route_cmd::get_available_routes,
|
||||
commands::route_cmd::get_route_curl_examples,
|
||||
|
||||
@@ -38,9 +38,12 @@ const REFRESH_SKEW: i64 = 3000;
|
||||
pub const ANTIGRAVITY_MODELS: &[&str] = &[
|
||||
"gemini-3-pro-preview",
|
||||
"gemini-3-pro-image-preview",
|
||||
"gemini-3-flash-preview",
|
||||
"gemini-2.5-flash",
|
||||
"gemini-2.5-computer-use-preview-10-2025",
|
||||
"gemini-claude-sonnet-4-5",
|
||||
"gemini-claude-sonnet-4-5-thinking",
|
||||
"gemini-claude-opus-4-5-thinking",
|
||||
];
|
||||
|
||||
/// 模型别名映射(用户友好名称 -> 内部名称)
|
||||
@@ -49,8 +52,11 @@ fn alias_to_model_name(model: &str) -> &str {
|
||||
"gemini-2.5-computer-use-preview-10-2025" => "rev19-uic3-1p",
|
||||
"gemini-3-pro-image-preview" => "gemini-3-pro-image",
|
||||
"gemini-3-pro-preview" => "gemini-3-pro-high",
|
||||
"gemini-3-flash-preview" => "gemini-3-flash",
|
||||
"gemini-2.5-flash" => "gemini-2.5-flash",
|
||||
"gemini-claude-sonnet-4-5" => "claude-sonnet-4-5",
|
||||
"gemini-claude-sonnet-4-5-thinking" => "claude-sonnet-4-5-thinking",
|
||||
"gemini-claude-opus-4-5-thinking" => "claude-opus-4-5-thinking",
|
||||
_ => model,
|
||||
}
|
||||
}
|
||||
@@ -62,8 +68,11 @@ fn model_name_to_alias(model: &str) -> &str {
|
||||
"rev19-uic3-1p" => "gemini-2.5-computer-use-preview-10-2025",
|
||||
"gemini-3-pro-image" => "gemini-3-pro-image-preview",
|
||||
"gemini-3-pro-high" => "gemini-3-pro-preview",
|
||||
"gemini-3-flash" => "gemini-3-flash-preview",
|
||||
"gemini-2.5-flash" => "gemini-2.5-flash",
|
||||
"claude-sonnet-4-5" => "gemini-claude-sonnet-4-5",
|
||||
"claude-sonnet-4-5-thinking" => "gemini-claude-sonnet-4-5-thinking",
|
||||
"claude-opus-4-5-thinking" => "gemini-claude-opus-4-5-thinking",
|
||||
_ => model,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,73 @@ use serde::{Deserialize, Serialize};
|
||||
use std::error::Error;
|
||||
use std::path::PathBuf;
|
||||
|
||||
/// 生成设备指纹 (Machine ID 的 SHA256)
|
||||
/// 根据凭证信息生成唯一的 Machine ID(参考 AIClient-2-API 实现)
|
||||
///
|
||||
/// 关键改进:每个凭证生成独立的 Machine ID,避免多账号共用同一指纹被检测
|
||||
/// 优先级:profileArn > clientId > 系统硬件 ID
|
||||
///
|
||||
/// 这样每个 OAuth 凭证都有自己独立的指纹,模拟不同设备登录
|
||||
pub fn generate_machine_id_from_credentials(
|
||||
profile_arn: Option<&str>,
|
||||
client_id: Option<&str>,
|
||||
) -> String {
|
||||
use sha2::{Digest, Sha256};
|
||||
|
||||
// 优先使用凭证相关的唯一标识,确保每个账号有独立的 Machine ID
|
||||
let unique_key = profile_arn
|
||||
.filter(|s| !s.is_empty())
|
||||
.or(client_id.filter(|s| !s.is_empty()))
|
||||
.map(|s| s.to_string())
|
||||
.unwrap_or_else(|| {
|
||||
// 回退到系统硬件 ID
|
||||
get_raw_machine_id().unwrap_or_else(|| "KIRO_DEFAULT_MACHINE".to_string())
|
||||
});
|
||||
|
||||
let mut hasher = Sha256::new();
|
||||
hasher.update(unique_key.as_bytes());
|
||||
let result = hasher.finalize();
|
||||
format!("{:x}", result)
|
||||
}
|
||||
|
||||
/// 获取系统运行时信息
|
||||
///
|
||||
/// 返回真实的操作系统名称和版本,用于构建更真实的 User-Agent
|
||||
fn get_system_runtime_info() -> (String, String) {
|
||||
let os_name = if cfg!(target_os = "macos") {
|
||||
// macOS: 获取真实版本号
|
||||
let version = std::process::Command::new("sw_vers")
|
||||
.arg("-productVersion")
|
||||
.output()
|
||||
.ok()
|
||||
.and_then(|o| String::from_utf8(o.stdout).ok())
|
||||
.map(|s| s.trim().to_string())
|
||||
.unwrap_or_else(|| "14.0".to_string());
|
||||
format!("macos#{}", version)
|
||||
} else if cfg!(target_os = "linux") {
|
||||
// Linux: 获取内核版本
|
||||
let version = std::process::Command::new("uname")
|
||||
.arg("-r")
|
||||
.output()
|
||||
.ok()
|
||||
.and_then(|o| String::from_utf8(o.stdout).ok())
|
||||
.map(|s| s.trim().to_string())
|
||||
.unwrap_or_else(|| "5.15.0".to_string());
|
||||
format!("linux#{}", version)
|
||||
} else if cfg!(target_os = "windows") {
|
||||
// Windows: 使用固定版本(实际应该获取真实版本)
|
||||
"windows#10.0".to_string()
|
||||
} else {
|
||||
"other#1.0".to_string()
|
||||
};
|
||||
|
||||
// Node.js 版本模拟(Kiro IDE 使用 Electron,内置 Node.js)
|
||||
// 使用常见的 LTS 版本
|
||||
let node_version = "20.18.0".to_string();
|
||||
|
||||
(os_name, node_version)
|
||||
}
|
||||
|
||||
/// 生成设备指纹 (Machine ID 的 SHA256) - 保留用于兼容
|
||||
///
|
||||
/// 与 Kiro IDE 保持一致的指纹生成方式(参考 Kir-Manager):
|
||||
/// - macOS: 使用 IOPlatformUUID(硬件级别唯一标识)
|
||||
@@ -657,7 +723,11 @@ impl KiroProvider {
|
||||
);
|
||||
|
||||
// 获取设备指纹和版本号(用于 Social 认证的 User-Agent)
|
||||
let device_fp = get_device_fingerprint();
|
||||
// 使用基于凭证的 Machine ID,确保每个账号有独立的指纹
|
||||
let machine_id = generate_machine_id_from_credentials(
|
||||
self.credentials.profile_arn.as_deref(),
|
||||
self.credentials.client_id.as_deref(),
|
||||
);
|
||||
let kiro_version = get_kiro_version();
|
||||
|
||||
let resp = if auth_method == "idc" {
|
||||
@@ -690,11 +760,11 @@ impl KiroProvider {
|
||||
.header("Host", "oidc.us-east-1.amazonaws.com")
|
||||
.header(
|
||||
"x-amz-user-agent",
|
||||
"aws-sdk-js/3.738.0 ua/2.1 os/other lang/js api/sso-oidc#3.738.0 m/E KiroIDE",
|
||||
format!("aws-sdk-js/3.738.0 ua/2.1 os/other lang/js api/sso-oidc#3.738.0 m/E KiroIDE-{}-{}", kiro_version, machine_id),
|
||||
)
|
||||
.header("User-Agent", "node")
|
||||
.header("Accept", "*/*")
|
||||
.header("Connection", "keep-alive")
|
||||
.header("Connection", "close")
|
||||
.json(&body)
|
||||
.send()
|
||||
.await?
|
||||
@@ -707,13 +777,14 @@ impl KiroProvider {
|
||||
.post(&refresh_url)
|
||||
.header(
|
||||
"User-Agent",
|
||||
format!("KiroIDE-{}-{}", kiro_version, device_fp),
|
||||
format!("KiroIDE-{}-{}", kiro_version, machine_id),
|
||||
)
|
||||
.header("Accept", "application/json, text/plain, */*")
|
||||
.header("Accept-Encoding", "br, gzip, deflate")
|
||||
.header("Content-Type", "application/json")
|
||||
.header("Accept-Language", "*")
|
||||
.header("Sec-Fetch-Mode", "cors")
|
||||
.header("Connection", "close")
|
||||
.json(&body)
|
||||
.send()
|
||||
.await?
|
||||
@@ -881,7 +952,7 @@ impl KiroProvider {
|
||||
None
|
||||
};
|
||||
|
||||
let cw_request = convert_openai_to_codewhisperer(request, profile_arn);
|
||||
let cw_request = convert_openai_to_codewhisperer(request, profile_arn.clone());
|
||||
let url = self.get_base_url();
|
||||
|
||||
// Debug: 记录转换后的请求
|
||||
@@ -923,9 +994,20 @@ impl KiroProvider {
|
||||
);
|
||||
}
|
||||
|
||||
// 生成设备指纹用于伪装 Kiro IDE
|
||||
let device_fp = get_device_fingerprint();
|
||||
// 生成基于凭证的唯一 Machine ID(关键改进:每个账号独立指纹)
|
||||
let machine_id = generate_machine_id_from_credentials(
|
||||
profile_arn.as_deref(),
|
||||
self.credentials.client_id.as_deref(),
|
||||
);
|
||||
let kiro_version = get_kiro_version();
|
||||
let (os_name, node_version) = get_system_runtime_info();
|
||||
|
||||
tracing::debug!(
|
||||
"[KIRO_FINGERPRINT] machine_id={} (based on profile_arn={}, client_id={})",
|
||||
&machine_id[..16],
|
||||
profile_arn.is_some(),
|
||||
self.credentials.client_id.is_some()
|
||||
);
|
||||
|
||||
let resp = self
|
||||
.client
|
||||
@@ -935,17 +1017,20 @@ impl KiroProvider {
|
||||
.header("Accept", "application/json")
|
||||
.header("amz-sdk-invocation-id", uuid::Uuid::new_v4().to_string())
|
||||
.header("amz-sdk-request", "attempt=1; max=1")
|
||||
.header("x-amzn-kiro-agent-mode", "vibe")
|
||||
// 关键指纹头:使用基于凭证的唯一 Machine ID
|
||||
.header(
|
||||
"x-amz-user-agent",
|
||||
format!("aws-sdk-js/1.0.7 KiroIDE-{kiro_version}-{device_fp}"),
|
||||
format!("aws-sdk-js/1.0.0 KiroIDE-{kiro_version}-{machine_id}"),
|
||||
)
|
||||
.header(
|
||||
"user-agent",
|
||||
format!(
|
||||
"aws-sdk-js/1.0.7 ua/2.1 os/macos#14.0 lang/js md/nodejs#20.16.0 api/codewhispererstreaming#1.0.7 m/E KiroIDE-{kiro_version}-{device_fp}"
|
||||
"aws-sdk-js/1.0.0 ua/2.1 os/{os_name} lang/js md/nodejs#{node_version} api/codewhispererruntime#1.0.0 m/E KiroIDE-{kiro_version}-{machine_id}"
|
||||
),
|
||||
)
|
||||
.header("x-amzn-kiro-agent-mode", "vibe")
|
||||
// 添加 Connection: close 避免连接复用被检测
|
||||
.header("Connection", "close")
|
||||
.json(&cw_request)
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://schema.tauri.app/config/2",
|
||||
"productName": "ProxyCast",
|
||||
"version": "0.14.8",
|
||||
"version": "0.14.10",
|
||||
"identifier": "com.proxycast.app",
|
||||
"build": {
|
||||
"beforeDevCommand": "npm run dev",
|
||||
|
||||
@@ -17,11 +17,18 @@ import {
|
||||
Globe,
|
||||
BarChart3,
|
||||
ChevronUp,
|
||||
Fingerprint,
|
||||
Copy,
|
||||
Check,
|
||||
} from "lucide-react";
|
||||
import type {
|
||||
CredentialDisplay,
|
||||
CredentialSource,
|
||||
} from "@/lib/api/providerPool";
|
||||
import {
|
||||
getKiroCredentialFingerprint,
|
||||
type KiroFingerprintInfo,
|
||||
} from "@/lib/api/providerPool";
|
||||
import { usageApi, type UsageInfo } from "@/lib/api/usage";
|
||||
import { UsageDisplay } from "./UsageDisplay";
|
||||
|
||||
@@ -59,6 +66,46 @@ export function CredentialCard({
|
||||
const [usageInfo, setUsageInfo] = useState<UsageInfo | null>(null);
|
||||
const [usageError, setUsageError] = useState<string | null>(null);
|
||||
|
||||
// 指纹信息状态(仅 Kiro 凭证)
|
||||
const [fingerprintInfo, setFingerprintInfo] =
|
||||
useState<KiroFingerprintInfo | null>(null);
|
||||
const [fingerprintLoading, setFingerprintLoading] = useState(false);
|
||||
const [fingerprintExpanded, setFingerprintExpanded] = useState(false);
|
||||
const [fingerprintCopied, setFingerprintCopied] = useState(false);
|
||||
|
||||
// 查询指纹信息
|
||||
const handleCheckFingerprint = async () => {
|
||||
if (fingerprintExpanded && fingerprintInfo) {
|
||||
// 已展开且有数据,直接折叠
|
||||
setFingerprintExpanded(false);
|
||||
return;
|
||||
}
|
||||
|
||||
setFingerprintExpanded(true);
|
||||
setFingerprintLoading(true);
|
||||
|
||||
try {
|
||||
const info = await getKiroCredentialFingerprint(credential.uuid);
|
||||
setFingerprintInfo(info);
|
||||
} catch (e) {
|
||||
console.error("获取指纹信息失败:", e);
|
||||
} finally {
|
||||
setFingerprintLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
// 复制 Machine ID
|
||||
const handleCopyMachineId = async () => {
|
||||
if (!fingerprintInfo) return;
|
||||
try {
|
||||
await navigator.clipboard.writeText(fingerprintInfo.machine_id);
|
||||
setFingerprintCopied(true);
|
||||
setTimeout(() => setFingerprintCopied(false), 2000);
|
||||
} catch (e) {
|
||||
console.error("复制失败:", e);
|
||||
}
|
||||
};
|
||||
|
||||
// 查询用量
|
||||
const handleCheckUsage = async () => {
|
||||
if (usageExpanded && usageInfo) {
|
||||
@@ -295,6 +342,24 @@ export function CredentialCard({
|
||||
</button>
|
||||
)}
|
||||
|
||||
{/* 指纹信息按钮 - 仅 Kiro 凭证显示 */}
|
||||
{isKiroCredential && (
|
||||
<button
|
||||
onClick={handleCheckFingerprint}
|
||||
disabled={fingerprintLoading}
|
||||
className={`rounded-lg p-2 transition-colors ${
|
||||
fingerprintExpanded
|
||||
? "bg-indigo-200 text-indigo-800 dark:bg-indigo-800 dark:text-indigo-200"
|
||||
: "bg-indigo-100 text-indigo-700 hover:bg-indigo-200 dark:bg-indigo-900/30 dark:text-indigo-400"
|
||||
} disabled:opacity-50`}
|
||||
title="查看设备指纹"
|
||||
>
|
||||
<Fingerprint
|
||||
className={`h-4 w-4 ${fingerprintLoading ? "animate-pulse" : ""}`}
|
||||
/>
|
||||
</button>
|
||||
)}
|
||||
|
||||
{/* 用量查询按钮 - 仅 Kiro 凭证显示 */}
|
||||
{isKiroCredential && (
|
||||
<button
|
||||
@@ -362,6 +427,85 @@ export function CredentialCard({
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 指纹信息展示区域 - 仅 Kiro 凭证 */}
|
||||
{isKiroCredential && fingerprintExpanded && (
|
||||
<div className="mt-3 pt-3 border-t border-border/30">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<span className="text-xs font-medium text-muted-foreground flex items-center gap-1">
|
||||
<Fingerprint className="h-3 w-3" />
|
||||
设备指纹
|
||||
</span>
|
||||
<button
|
||||
onClick={() => setFingerprintExpanded(false)}
|
||||
className="text-muted-foreground hover:text-foreground"
|
||||
>
|
||||
<ChevronUp className="h-4 w-4" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{fingerprintLoading ? (
|
||||
<div className="flex items-center gap-2 text-xs text-muted-foreground">
|
||||
<div className="animate-spin h-3 w-3 border border-current border-t-transparent rounded-full" />
|
||||
加载中...
|
||||
</div>
|
||||
) : fingerprintInfo ? (
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-xs text-muted-foreground">
|
||||
Machine ID:
|
||||
</span>
|
||||
<code className="text-xs font-mono bg-muted px-1.5 py-0.5 rounded">
|
||||
{fingerprintInfo.machine_id_short}...
|
||||
</code>
|
||||
<button
|
||||
onClick={handleCopyMachineId}
|
||||
className="p-1 rounded hover:bg-muted transition-colors"
|
||||
title="复制完整 Machine ID"
|
||||
>
|
||||
{fingerprintCopied ? (
|
||||
<Check className="h-3 w-3 text-green-500" />
|
||||
) : (
|
||||
<Copy className="h-3 w-3 text-muted-foreground" />
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
<div className="flex items-center gap-4 text-xs">
|
||||
<span className="flex items-center gap-1">
|
||||
<span className="text-muted-foreground">来源:</span>
|
||||
<span
|
||||
className={`px-1.5 py-0.5 rounded ${
|
||||
fingerprintInfo.source === "profileArn"
|
||||
? "bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400"
|
||||
: fingerprintInfo.source === "clientId"
|
||||
? "bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400"
|
||||
: "bg-gray-100 text-gray-700 dark:bg-gray-800 dark:text-gray-400"
|
||||
}`}
|
||||
>
|
||||
{fingerprintInfo.source}
|
||||
</span>
|
||||
</span>
|
||||
<span className="flex items-center gap-1">
|
||||
<span className="text-muted-foreground">认证:</span>
|
||||
<span
|
||||
className={`px-1.5 py-0.5 rounded ${
|
||||
fingerprintInfo.auth_method.toLowerCase() === "idc"
|
||||
? "bg-purple-100 text-purple-700 dark:bg-purple-900/30 dark:text-purple-400"
|
||||
: "bg-orange-100 text-orange-700 dark:bg-orange-900/30 dark:text-orange-400"
|
||||
}`}
|
||||
>
|
||||
{fingerprintInfo.auth_method}
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-xs text-muted-foreground">
|
||||
无法获取指纹信息
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 用量信息展示区域 - 仅 Kiro 凭证 */}
|
||||
{isKiroCredential && usageExpanded && (
|
||||
<div className="mt-3 pt-3 border-t border-border/30">
|
||||
|
||||
@@ -404,3 +404,22 @@ export interface MigrationResult {
|
||||
skipped_count: number;
|
||||
errors: string[];
|
||||
}
|
||||
|
||||
// Kiro 凭证指纹信息
|
||||
export interface KiroFingerprintInfo {
|
||||
/** Machine ID(SHA256 哈希,64 字符) */
|
||||
machine_id: string;
|
||||
/** Machine ID 的短格式(前 16 字符) */
|
||||
machine_id_short: string;
|
||||
/** 指纹来源(profileArn / clientId / system) */
|
||||
source: string;
|
||||
/** 认证方式 */
|
||||
auth_method: string;
|
||||
}
|
||||
|
||||
// 获取 Kiro 凭证的指纹信息
|
||||
export async function getKiroCredentialFingerprint(
|
||||
uuid: string,
|
||||
): Promise<KiroFingerprintInfo> {
|
||||
return invoke("get_kiro_credential_fingerprint", { uuid });
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user