增加opus开启标识
This commit is contained in:
@@ -1002,6 +1002,7 @@ pub struct PackycodeUserQuota {
|
||||
pub plan_expires_at: String, // 计划过期时间
|
||||
pub username: Option<String>, // 用户名
|
||||
pub email: Option<String>, // 邮箱
|
||||
pub opus_enabled: Option<bool>, // 是否启用Opus模型
|
||||
}
|
||||
|
||||
/// 获取 PackyCode 用户信息(额度等)
|
||||
@@ -1044,23 +1045,37 @@ pub async fn packycode_get_user_quota(station_id: String, db: State<'_, AgentDb>
|
||||
"https://www.packycode.com/api/backend/users/info"
|
||||
};
|
||||
|
||||
// 创建 HTTP 客户端
|
||||
// 创建 HTTP 客户端 - 禁用系统代理
|
||||
let client = Client::builder()
|
||||
.timeout(Duration::from_secs(10))
|
||||
.timeout(Duration::from_secs(30))
|
||||
.no_proxy() // 禁用所有代理
|
||||
.build()
|
||||
.map_err(|e| format!("创建 HTTP 客户端失败: {}", e))?;
|
||||
|
||||
// 发送请求
|
||||
log::info!("正在请求 PackyCode 用户信息: {}", url);
|
||||
log::debug!("使用 Token: {}...", &station.system_token[..10.min(station.system_token.len())]);
|
||||
|
||||
// 发送请求 - 只使用必要的请求头
|
||||
let response = client
|
||||
.get(url)
|
||||
.header("Authorization", format!("Bearer {}", station.system_token))
|
||||
.header("User-Agent", "Apifox/1.0.0 (https://apifox.com)")
|
||||
.header("User-Agent", "Claudia")
|
||||
.header("Accept", "*/*")
|
||||
.header("Host", if url.contains("share.packycode.com") { "share.packycode.com" } else { "www.packycode.com" })
|
||||
.header("Connection", "keep-alive")
|
||||
.send()
|
||||
.await
|
||||
.map_err(|e| format!("请求失败: {}", e))?;
|
||||
.map_err(|e| {
|
||||
log::error!("请求 PackyCode API 失败: {}", e);
|
||||
// 提供更详细的错误信息
|
||||
if e.is_connect() {
|
||||
format!("网络连接失败: {}", e)
|
||||
} else if e.is_timeout() {
|
||||
format!("请求超时: {}", e)
|
||||
} else if e.is_request() {
|
||||
format!("请求错误: {}", e)
|
||||
} else {
|
||||
format!("请求失败: {}", e)
|
||||
}
|
||||
})?;
|
||||
|
||||
// 检查响应状态
|
||||
if !response.status().is_success() {
|
||||
@@ -1082,28 +1097,86 @@ pub async fn packycode_get_user_quota(station_id: String, db: State<'_, AgentDb>
|
||||
// 提取额度信息
|
||||
let quota = PackycodeUserQuota {
|
||||
daily_budget_usd: response_data.get("daily_budget_usd")
|
||||
.and_then(|v| v.as_str())
|
||||
.and_then(|s| s.parse::<f64>().ok())
|
||||
.and_then(|v| {
|
||||
if v.is_string() {
|
||||
v.as_str().and_then(|s| s.parse::<f64>().ok())
|
||||
} else if v.is_f64() {
|
||||
v.as_f64()
|
||||
} else if v.is_i64() {
|
||||
v.as_i64().map(|i| i as f64)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.unwrap_or(0.0),
|
||||
daily_spent_usd: response_data.get("daily_spent_usd")
|
||||
.and_then(|v| v.as_str())
|
||||
.and_then(|s| s.parse::<f64>().ok())
|
||||
.and_then(|v| {
|
||||
if v.is_null() {
|
||||
Some(0.0)
|
||||
} else if v.is_string() {
|
||||
v.as_str().and_then(|s| s.parse::<f64>().ok())
|
||||
} else if v.is_f64() {
|
||||
v.as_f64()
|
||||
} else if v.is_i64() {
|
||||
v.as_i64().map(|i| i as f64)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.unwrap_or(0.0),
|
||||
monthly_budget_usd: response_data.get("monthly_budget_usd")
|
||||
.and_then(|v| v.as_str())
|
||||
.and_then(|s| s.parse::<f64>().ok())
|
||||
.and_then(|v| {
|
||||
if v.is_string() {
|
||||
v.as_str().and_then(|s| s.parse::<f64>().ok())
|
||||
} else if v.is_f64() {
|
||||
v.as_f64()
|
||||
} else if v.is_i64() {
|
||||
v.as_i64().map(|i| i as f64)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.unwrap_or(0.0),
|
||||
monthly_spent_usd: response_data.get("monthly_spent_usd")
|
||||
.and_then(|v| v.as_str())
|
||||
.and_then(|s| s.parse::<f64>().ok())
|
||||
.and_then(|v| {
|
||||
if v.is_null() {
|
||||
Some(0.0)
|
||||
} else if v.is_string() {
|
||||
v.as_str().and_then(|s| s.parse::<f64>().ok())
|
||||
} else if v.is_f64() {
|
||||
v.as_f64()
|
||||
} else if v.is_i64() {
|
||||
v.as_i64().map(|i| i as f64)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.unwrap_or(0.0),
|
||||
balance_usd: response_data.get("balance_usd")
|
||||
.and_then(|v| v.as_str())
|
||||
.and_then(|s| s.parse::<f64>().ok())
|
||||
.and_then(|v| {
|
||||
if v.is_string() {
|
||||
v.as_str().and_then(|s| s.parse::<f64>().ok())
|
||||
} else if v.is_f64() {
|
||||
v.as_f64()
|
||||
} else if v.is_i64() {
|
||||
v.as_i64().map(|i| i as f64)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.unwrap_or(0.0),
|
||||
total_spent_usd: response_data.get("total_spent_usd")
|
||||
.and_then(|v| v.as_str())
|
||||
.and_then(|s| s.parse::<f64>().ok())
|
||||
.and_then(|v| {
|
||||
if v.is_string() {
|
||||
v.as_str().and_then(|s| s.parse::<f64>().ok())
|
||||
} else if v.is_f64() {
|
||||
v.as_f64()
|
||||
} else if v.is_i64() {
|
||||
v.as_i64().map(|i| i as f64)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.unwrap_or(0.0),
|
||||
plan_type: response_data.get("plan_type")
|
||||
.and_then(|v| v.as_str())
|
||||
@@ -1119,6 +1192,8 @@ pub async fn packycode_get_user_quota(station_id: String, db: State<'_, AgentDb>
|
||||
email: response_data.get("email")
|
||||
.and_then(|v| v.as_str())
|
||||
.map(|s| s.to_string()),
|
||||
opus_enabled: response_data.get("opus_enabled")
|
||||
.and_then(|v| v.as_bool()),
|
||||
};
|
||||
|
||||
Ok(quota)
|
||||
|
@@ -357,6 +357,11 @@ const RelayStationManager: React.FC<RelayStationManagerProps> = ({ onBack }) =>
|
||||
<Badge variant="secondary" className="text-xs">
|
||||
{quotaData[station.id].plan_type.toUpperCase()}
|
||||
</Badge>
|
||||
{quotaData[station.id].opus_enabled && (
|
||||
<Badge variant="default" className="text-xs bg-purple-600">
|
||||
Opus
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
{quotaData[station.id].plan_expires_at && (
|
||||
<span className="text-muted-foreground">
|
||||
@@ -369,7 +374,7 @@ const RelayStationManager: React.FC<RelayStationManagerProps> = ({ onBack }) =>
|
||||
<div className="flex items-center justify-between text-sm">
|
||||
<span className="text-muted-foreground">账户余额:</span>
|
||||
<span className="font-semibold text-blue-600">
|
||||
${quotaData[station.id].balance_usd.toFixed(2)}
|
||||
${Number(quotaData[station.id].balance_usd).toFixed(2)}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -378,21 +383,36 @@ const RelayStationManager: React.FC<RelayStationManagerProps> = ({ onBack }) =>
|
||||
<div className="flex items-center justify-between text-sm">
|
||||
<span className="text-muted-foreground">日额度:</span>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className={quotaData[station.id].daily_spent_usd > quotaData[station.id].daily_budget_usd * 0.8 ? 'text-orange-600' : 'text-green-600'}>
|
||||
${quotaData[station.id].daily_spent_usd.toFixed(2)}
|
||||
{(() => {
|
||||
const daily_spent = Number(quotaData[station.id].daily_spent_usd || 0);
|
||||
const daily_budget = Number(quotaData[station.id].daily_budget_usd);
|
||||
return (
|
||||
<>
|
||||
<span className={daily_spent > daily_budget * 0.8 ? 'text-orange-600' : 'text-green-600'}>
|
||||
${daily_spent.toFixed(2)}
|
||||
</span>
|
||||
<span className="text-muted-foreground">/</span>
|
||||
<span className="text-muted-foreground">${quotaData[station.id].daily_budget_usd.toFixed(2)}</span>
|
||||
<span className="text-muted-foreground">${daily_budget.toFixed(2)}</span>
|
||||
</>
|
||||
);
|
||||
})()}
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-full h-1.5 bg-gray-200 dark:bg-gray-700 rounded-full overflow-hidden">
|
||||
<div
|
||||
className={`h-full transition-all ${
|
||||
quotaData[station.id].daily_spent_usd / quotaData[station.id].daily_budget_usd > 0.8
|
||||
? 'bg-orange-500'
|
||||
: 'bg-green-500'
|
||||
(() => {
|
||||
const daily_spent = Number(quotaData[station.id].daily_spent_usd || 0);
|
||||
const daily_budget = Number(quotaData[station.id].daily_budget_usd);
|
||||
return daily_spent / daily_budget > 0.8;
|
||||
})() ? 'bg-orange-500' : 'bg-green-500'
|
||||
}`}
|
||||
style={{ width: `${Math.min((quotaData[station.id].daily_spent_usd / quotaData[station.id].daily_budget_usd) * 100, 100)}%` }}
|
||||
style={{ width: `${Math.min(
|
||||
(() => {
|
||||
const daily_spent = Number(quotaData[station.id].daily_spent_usd || 0);
|
||||
const daily_budget = Number(quotaData[station.id].daily_budget_usd);
|
||||
return (daily_spent / daily_budget) * 100;
|
||||
})(), 100)}%` }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -402,28 +422,43 @@ const RelayStationManager: React.FC<RelayStationManagerProps> = ({ onBack }) =>
|
||||
<div className="flex items-center justify-between text-sm">
|
||||
<span className="text-muted-foreground">月额度:</span>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className={quotaData[station.id].monthly_spent_usd > quotaData[station.id].monthly_budget_usd * 0.8 ? 'text-orange-600' : 'text-green-600'}>
|
||||
${quotaData[station.id].monthly_spent_usd.toFixed(2)}
|
||||
{(() => {
|
||||
const monthly_spent = Number(quotaData[station.id].monthly_spent_usd || 0);
|
||||
const monthly_budget = Number(quotaData[station.id].monthly_budget_usd);
|
||||
return (
|
||||
<>
|
||||
<span className={monthly_spent > monthly_budget * 0.8 ? 'text-orange-600' : 'text-green-600'}>
|
||||
${monthly_spent.toFixed(2)}
|
||||
</span>
|
||||
<span className="text-muted-foreground">/</span>
|
||||
<span className="text-muted-foreground">${quotaData[station.id].monthly_budget_usd.toFixed(2)}</span>
|
||||
<span className="text-muted-foreground">${monthly_budget.toFixed(2)}</span>
|
||||
</>
|
||||
);
|
||||
})()}
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-full h-1.5 bg-gray-200 dark:bg-gray-700 rounded-full overflow-hidden">
|
||||
<div
|
||||
className={`h-full transition-all ${
|
||||
quotaData[station.id].monthly_spent_usd / quotaData[station.id].monthly_budget_usd > 0.8
|
||||
? 'bg-orange-500'
|
||||
: 'bg-green-500'
|
||||
(() => {
|
||||
const monthly_spent = Number(quotaData[station.id].monthly_spent_usd || 0);
|
||||
const monthly_budget = Number(quotaData[station.id].monthly_budget_usd);
|
||||
return monthly_spent / monthly_budget > 0.8;
|
||||
})() ? 'bg-orange-500' : 'bg-green-500'
|
||||
}`}
|
||||
style={{ width: `${Math.min((quotaData[station.id].monthly_spent_usd / quotaData[station.id].monthly_budget_usd) * 100, 100)}%` }}
|
||||
style={{ width: `${Math.min(
|
||||
(() => {
|
||||
const monthly_spent = Number(quotaData[station.id].monthly_spent_usd || 0);
|
||||
const monthly_budget = Number(quotaData[station.id].monthly_budget_usd);
|
||||
return (monthly_spent / monthly_budget) * 100;
|
||||
})(), 100)}%` }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 总消费 */}
|
||||
<div className="flex items-center justify-between text-xs text-muted-foreground pt-2 border-t">
|
||||
<span>总消费: ${quotaData[station.id].total_spent_usd.toFixed(2)}</span>
|
||||
<span>总消费: ${Number(quotaData[station.id].total_spent_usd).toFixed(2)}</span>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
|
@@ -227,6 +227,7 @@ export const Terminal: React.FC<TerminalProps> = ({
|
||||
if (terminalRef.current) {
|
||||
xterm.open(terminalRef.current);
|
||||
} else {
|
||||
|
||||
console.error('[Terminal] Terminal container ref is null');
|
||||
return;
|
||||
}
|
||||
|
@@ -585,16 +585,17 @@ export interface NodeSpeedTestResult {
|
||||
|
||||
/** PackyCode 用户额度信息 */
|
||||
export interface PackycodeUserQuota {
|
||||
daily_budget_usd: number; // 日预算(美元)
|
||||
daily_spent_usd: number; // 日已使用(美元)
|
||||
monthly_budget_usd: number; // 月预算(美元)
|
||||
monthly_spent_usd: number; // 月已使用(美元)
|
||||
balance_usd: number; // 账户余额(美元)
|
||||
total_spent_usd: number; // 总消费(美元)
|
||||
daily_budget_usd: string | number; // 日预算(美元)
|
||||
daily_spent_usd: string | number | null; // 日已使用(美元)
|
||||
monthly_budget_usd: string | number; // 月预算(美元)
|
||||
monthly_spent_usd: string | number | null; // 月已使用(美元)
|
||||
balance_usd: string | number; // 账户余额(美元)
|
||||
total_spent_usd: string | number; // 总消费(美元)
|
||||
plan_type: string; // 计划类型 (pro, basic, etc.)
|
||||
plan_expires_at: string; // 计划过期时间
|
||||
username?: string; // 用户名
|
||||
email?: string; // 邮箱
|
||||
opus_enabled?: boolean; // 是否启用Opus模型
|
||||
}
|
||||
|
||||
/**
|
||||
|
Reference in New Issue
Block a user