mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-04-29 06:04:29 +08:00
Compare commits
2 Commits
fix/proxy-
...
fix/misc-u
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d49481a259 | ||
|
|
92a07bdb2b |
@@ -69,8 +69,6 @@ pub struct ToolVersion {
|
|||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn get_tool_versions() -> Result<Vec<ToolVersion>, String> {
|
pub async fn get_tool_versions() -> Result<Vec<ToolVersion>, String> {
|
||||||
use std::process::Command;
|
|
||||||
|
|
||||||
let tools = vec!["claude", "codex", "gemini"];
|
let tools = vec!["claude", "codex", "gemini"];
|
||||||
let mut results = Vec::new();
|
let mut results = Vec::new();
|
||||||
|
|
||||||
@@ -81,39 +79,16 @@ pub async fn get_tool_versions() -> Result<Vec<ToolVersion>, String> {
|
|||||||
.map_err(|e| e.to_string())?;
|
.map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
for tool in tools {
|
for tool in tools {
|
||||||
// 1. 获取本地版本 (保持不变)
|
// 1. 获取本地版本 - 先尝试直接执行,失败则扫描常见路径
|
||||||
let (local_version, local_error) = {
|
let (local_version, local_error) = {
|
||||||
let output = if cfg!(target_os = "windows") {
|
// 先尝试直接执行
|
||||||
Command::new("cmd")
|
let direct_result = try_get_version(tool);
|
||||||
.args(["/C", &format!("{tool} --version")])
|
|
||||||
.output()
|
|
||||||
} else {
|
|
||||||
Command::new("sh")
|
|
||||||
.arg("-c")
|
|
||||||
.arg(format!("{tool} --version"))
|
|
||||||
.output()
|
|
||||||
};
|
|
||||||
|
|
||||||
match output {
|
if direct_result.0.is_some() {
|
||||||
Ok(out) => {
|
direct_result
|
||||||
if out.status.success() {
|
} else {
|
||||||
(
|
// 扫描常见的 npm 全局安装路径
|
||||||
Some(String::from_utf8_lossy(&out.stdout).trim().to_string()),
|
scan_cli_version(tool)
|
||||||
None,
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
let err = String::from_utf8_lossy(&out.stderr).trim().to_string();
|
|
||||||
(
|
|
||||||
None,
|
|
||||||
Some(if err.is_empty() {
|
|
||||||
"未安装或无法执行".to_string()
|
|
||||||
} else {
|
|
||||||
err
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(e) => (None, Some(e.to_string())),
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -153,3 +128,124 @@ async fn fetch_npm_latest_version(client: &reqwest::Client, package: &str) -> Op
|
|||||||
Err(_) => None,
|
Err(_) => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 从版本输出中提取纯版本号
|
||||||
|
fn extract_version(raw: &str) -> String {
|
||||||
|
// 匹配 semver 格式: x.y.z 或 x.y.z-xxx
|
||||||
|
let re = regex::Regex::new(r"\d+\.\d+\.\d+(-[\w.]+)?").unwrap();
|
||||||
|
re.find(raw)
|
||||||
|
.map(|m| m.as_str().to_string())
|
||||||
|
.unwrap_or_else(|| raw.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 尝试直接执行命令获取版本
|
||||||
|
fn try_get_version(tool: &str) -> (Option<String>, Option<String>) {
|
||||||
|
use std::process::Command;
|
||||||
|
|
||||||
|
let output = if cfg!(target_os = "windows") {
|
||||||
|
Command::new("cmd")
|
||||||
|
.args(["/C", &format!("{tool} --version")])
|
||||||
|
.output()
|
||||||
|
} else {
|
||||||
|
Command::new("sh")
|
||||||
|
.arg("-c")
|
||||||
|
.arg(format!("{tool} --version"))
|
||||||
|
.output()
|
||||||
|
};
|
||||||
|
|
||||||
|
match output {
|
||||||
|
Ok(out) => {
|
||||||
|
if out.status.success() {
|
||||||
|
let raw = String::from_utf8_lossy(&out.stdout).trim().to_string();
|
||||||
|
(Some(extract_version(&raw)), None)
|
||||||
|
} else {
|
||||||
|
let err = String::from_utf8_lossy(&out.stderr).trim().to_string();
|
||||||
|
(
|
||||||
|
None,
|
||||||
|
Some(if err.is_empty() {
|
||||||
|
"未安装或无法执行".to_string()
|
||||||
|
} else {
|
||||||
|
err
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => (None, Some(e.to_string())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 扫描常见路径查找 CLI
|
||||||
|
fn scan_cli_version(tool: &str) -> (Option<String>, Option<String>) {
|
||||||
|
use std::process::Command;
|
||||||
|
|
||||||
|
let home = dirs::home_dir().unwrap_or_default();
|
||||||
|
|
||||||
|
// 常见的 npm 全局安装路径
|
||||||
|
let mut search_paths: Vec<std::path::PathBuf> = vec![
|
||||||
|
home.join(".npm-global/bin"),
|
||||||
|
home.join(".local/bin"),
|
||||||
|
home.join("n/bin"), // n version manager
|
||||||
|
];
|
||||||
|
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
{
|
||||||
|
search_paths.push(std::path::PathBuf::from("/opt/homebrew/bin"));
|
||||||
|
search_paths.push(std::path::PathBuf::from("/usr/local/bin"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
{
|
||||||
|
search_paths.push(std::path::PathBuf::from("/usr/local/bin"));
|
||||||
|
search_paths.push(std::path::PathBuf::from("/usr/bin"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
{
|
||||||
|
if let Some(appdata) = dirs::data_dir() {
|
||||||
|
search_paths.push(appdata.join("npm"));
|
||||||
|
}
|
||||||
|
search_paths.push(std::path::PathBuf::from("C:\\Program Files\\nodejs"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 扫描 nvm 目录下的所有 node 版本
|
||||||
|
let nvm_base = home.join(".nvm/versions/node");
|
||||||
|
if nvm_base.exists() {
|
||||||
|
if let Ok(entries) = std::fs::read_dir(&nvm_base) {
|
||||||
|
for entry in entries.flatten() {
|
||||||
|
let bin_path = entry.path().join("bin");
|
||||||
|
if bin_path.exists() {
|
||||||
|
search_paths.push(bin_path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 在每个路径中查找工具
|
||||||
|
for path in &search_paths {
|
||||||
|
let tool_path = if cfg!(target_os = "windows") {
|
||||||
|
path.join(format!("{tool}.cmd"))
|
||||||
|
} else {
|
||||||
|
path.join(tool)
|
||||||
|
};
|
||||||
|
|
||||||
|
if tool_path.exists() {
|
||||||
|
// 构建 PATH 环境变量,确保 node 可被找到
|
||||||
|
let current_path = std::env::var("PATH").unwrap_or_default();
|
||||||
|
let new_path = format!("{}:{}", path.display(), current_path);
|
||||||
|
|
||||||
|
let output = Command::new(&tool_path)
|
||||||
|
.arg("--version")
|
||||||
|
.env("PATH", &new_path)
|
||||||
|
.output();
|
||||||
|
|
||||||
|
if let Ok(out) = output {
|
||||||
|
if out.status.success() {
|
||||||
|
let raw = String::from_utf8_lossy(&out.stdout).trim().to_string();
|
||||||
|
return (Some(extract_version(&raw)), None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
(None, Some("未安装或无法执行".to_string()))
|
||||||
|
}
|
||||||
|
|||||||
@@ -559,7 +559,7 @@ const EndpointSpeedTest: React.FC<EndpointSpeedTestProps> = ({
|
|||||||
<div
|
<div
|
||||||
key={entry.id}
|
key={entry.id}
|
||||||
onClick={() => handleSelect(entry.url)}
|
onClick={() => handleSelect(entry.url)}
|
||||||
className={`group flex cursor-pointer items-center justify-between px-3 py-2.5 rounded-lg border transition ${
|
className={`group flex cursor-pointer items-center justify-between px-3 py-2.5 rounded-lg border transition text-foreground ${
|
||||||
isSelected
|
isSelected
|
||||||
? "border-primary/70 bg-primary/5 shadow-sm"
|
? "border-primary/70 bg-primary/5 shadow-sm"
|
||||||
: "border-border-default bg-background hover:bg-muted"
|
: "border-border-default bg-background hover:bg-muted"
|
||||||
@@ -577,7 +577,7 @@ const EndpointSpeedTest: React.FC<EndpointSpeedTestProps> = ({
|
|||||||
|
|
||||||
{/* 内容 */}
|
{/* 内容 */}
|
||||||
<div className="min-w-0 flex-1">
|
<div className="min-w-0 flex-1">
|
||||||
<div className="truncate text-sm text-gray-900 dark:text-gray-100">
|
<div className="truncate text-sm text-foreground">
|
||||||
{entry.url}
|
{entry.url}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
|||||||
<input
|
<input
|
||||||
type={type}
|
type={type}
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex h-9 w-full rounded-md border border-border-default bg-background px-3 py-1 text-sm shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-blue-500/20 dark:focus:ring-blue-400/20 disabled:cursor-not-allowed disabled:opacity-50",
|
"flex h-9 w-full rounded-md border border-border-default bg-background text-foreground px-3 py-1 text-sm shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-blue-500/20 dark:focus:ring-blue-400/20 disabled:cursor-not-allowed disabled:opacity-50",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
|
|||||||
Reference in New Issue
Block a user