修改中转站
This commit is contained in:
@@ -204,15 +204,6 @@ pub fn apply_relay_station_to_config(station: &RelayStation) -> Result<(), Strin
|
||||
"packycode" => {
|
||||
// PackyCode 使用原始配置,不做特殊处理
|
||||
}
|
||||
"newapi" | "oneapi" => {
|
||||
// NewAPI 和 OneAPI 兼容 OpenAI 格式,不需要特殊处理
|
||||
}
|
||||
"yourapi" => {
|
||||
// YourAPI 可能需要特殊的路径格式
|
||||
if !station.api_url.ends_with("/v1") {
|
||||
config.env.anthropic_base_url = Some(format!("{}/v1", station.api_url));
|
||||
}
|
||||
}
|
||||
"custom" => {
|
||||
// 自定义适配器,使用原始配置
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -19,9 +19,6 @@ pub enum RelayStationAdapter {
|
||||
Glm, // 智谱GLM
|
||||
Qwen, // 千问Qwen
|
||||
Kimi, // Kimi k2
|
||||
Newapi, // NewAPI 兼容平台
|
||||
Oneapi, // OneAPI 兼容平台
|
||||
Yourapi, // YourAPI 特定平台
|
||||
Custom, // 自定义简单配置
|
||||
}
|
||||
|
||||
@@ -33,9 +30,6 @@ impl RelayStationAdapter {
|
||||
RelayStationAdapter::Glm => "glm",
|
||||
RelayStationAdapter::Qwen => "qwen",
|
||||
RelayStationAdapter::Kimi => "kimi",
|
||||
RelayStationAdapter::Newapi => "newapi",
|
||||
RelayStationAdapter::Oneapi => "oneapi",
|
||||
RelayStationAdapter::Yourapi => "yourapi",
|
||||
RelayStationAdapter::Custom => "custom",
|
||||
}
|
||||
}
|
||||
@@ -60,7 +54,7 @@ pub struct RelayStation {
|
||||
pub adapter: RelayStationAdapter, // 适配器类型
|
||||
pub auth_method: AuthMethod, // 认证方式
|
||||
pub system_token: String, // 系统令牌
|
||||
pub user_id: Option<String>, // 用户 ID(NewAPI 必需)
|
||||
pub user_id: Option<String>, // 用户 ID(可选)
|
||||
pub adapter_config: Option<HashMap<String, serde_json::Value>>, // 适配器特定配置
|
||||
pub enabled: bool, // 启用状态
|
||||
pub created_at: i64, // 创建时间
|
||||
|
@@ -73,6 +73,17 @@ const RelayStationManager: React.FC<RelayStationManagerProps> = ({ onBack }) =>
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
// Token 脱敏函数
|
||||
const maskToken = (token: string): string => {
|
||||
if (!token || token.length <= 8) {
|
||||
return '*'.repeat(token?.length || 0);
|
||||
}
|
||||
const start = token.substring(0, 4);
|
||||
const end = token.substring(token.length - 4);
|
||||
const middleLength = Math.max(token.length - 8, 8);
|
||||
return `${start}${'*'.repeat(middleLength)}${end}`;
|
||||
};
|
||||
|
||||
// 显示Toast
|
||||
const showToast = (message: string, type: "success" | "error" = "success") => {
|
||||
setToast({ message, type });
|
||||
@@ -218,9 +229,6 @@ const RelayStationManager: React.FC<RelayStationManagerProps> = ({ onBack }) =>
|
||||
case 'glm': return '智谱GLM';
|
||||
case 'qwen': return '千问Qwen';
|
||||
case 'kimi': return 'Kimi k2';
|
||||
case 'newapi': return 'NewAPI';
|
||||
case 'oneapi': return 'OneAPI';
|
||||
case 'yourapi': return 'YourAPI';
|
||||
case 'custom': return t('relayStation.custom');
|
||||
default: return adapter;
|
||||
}
|
||||
@@ -322,27 +330,9 @@ const RelayStationManager: React.FC<RelayStationManagerProps> = ({ onBack }) =>
|
||||
{/* 当前配置状态 */}
|
||||
<Card className="border-blue-200 dark:border-blue-900 bg-blue-50/50 dark:bg-blue-950/20">
|
||||
<CardHeader className="pb-3">
|
||||
<div className="flex justify-between items-center">
|
||||
<div className="flex items-center gap-2">
|
||||
<Settings className="h-5 w-5 text-blue-600 dark:text-blue-400" />
|
||||
<CardTitle className="text-lg">{t('relayStation.currentConfig')}</CardTitle>
|
||||
</div>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
loadCurrentConfig();
|
||||
syncConfig();
|
||||
}}
|
||||
disabled={loadingConfig}
|
||||
>
|
||||
{loadingConfig ? (
|
||||
<div className="h-4 w-4 animate-spin rounded-full border-b-2 border-current" />
|
||||
) : (
|
||||
<RefreshCw className="h-4 w-4" />
|
||||
)}
|
||||
<span className="ml-2">{t('relayStation.syncConfig')}</span>
|
||||
</Button>
|
||||
<div className="flex items-center gap-2">
|
||||
<Settings className="h-5 w-5 text-blue-600 dark:text-blue-400" />
|
||||
<CardTitle className="text-lg">{t('relayStation.currentConfig')}</CardTitle>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
@@ -422,49 +412,71 @@ const RelayStationManager: React.FC<RelayStationManagerProps> = ({ onBack }) =>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-2">
|
||||
<div className="flex justify-between items-center mb-2">
|
||||
<span className="text-sm font-medium">{t('relayStation.configPreview')}</span>
|
||||
<div className="flex gap-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => setJsonConfigView(true)}
|
||||
>
|
||||
<Eye className="h-4 w-4 mr-1" />
|
||||
{t('relayStation.viewJson')}
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={handleFlushDns}
|
||||
disabled={flushingDns}
|
||||
>
|
||||
{flushingDns ? (
|
||||
<div className="h-4 w-4 animate-spin rounded-full border-b-2 border-current mr-1" />
|
||||
) : (
|
||||
<RefreshCw className="h-4 w-4 mr-1" />
|
||||
)}
|
||||
{t('relayStation.flushDns')}
|
||||
</Button>
|
||||
<div className="flex gap-4">
|
||||
{/* 左侧数据展示 */}
|
||||
<div className="flex-1 space-y-2">
|
||||
<div className="text-sm font-medium mb-2">{t('relayStation.configPreview')}</div>
|
||||
<div className="space-y-1.5 text-sm">
|
||||
<div className="flex items-start gap-2">
|
||||
<span className="text-muted-foreground min-w-[80px]">API URL:</span>
|
||||
<span className="font-mono text-xs break-all">
|
||||
{currentConfig.api_url || t('relayStation.notConfigured')}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-start gap-2">
|
||||
<span className="text-muted-foreground min-w-[80px]">API Token:</span>
|
||||
<span className="font-mono text-xs">
|
||||
{currentConfig.api_token ? maskToken(currentConfig.api_token) : t('relayStation.notConfigured')}
|
||||
</span>
|
||||
</div>
|
||||
<div className="text-xs text-muted-foreground mt-2">
|
||||
{t('relayStation.configLocation')}: ~/.claude/settings.json
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-2 text-sm">
|
||||
<div className="flex items-start gap-2">
|
||||
<span className="font-medium text-muted-foreground min-w-[100px]">API URL:</span>
|
||||
<span className="font-mono text-xs break-all">
|
||||
{currentConfig.api_url || t('relayStation.notConfigured')}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-start gap-2">
|
||||
<span className="font-medium text-muted-foreground min-w-[100px]">API Token:</span>
|
||||
<span className="font-mono text-xs">
|
||||
{currentConfig.api_token || t('relayStation.notConfigured')}
|
||||
</span>
|
||||
</div>
|
||||
<div className="text-xs text-muted-foreground mt-3">
|
||||
{t('relayStation.configLocation')}: ~/.claude/settings.json
|
||||
</div>
|
||||
|
||||
{/* 右侧按钮区域 */}
|
||||
<div className="flex flex-col gap-2 min-w-[100px]">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
loadCurrentConfig();
|
||||
syncConfig();
|
||||
}}
|
||||
disabled={loadingConfig}
|
||||
className="w-full h-9 justify-start"
|
||||
>
|
||||
{loadingConfig ? (
|
||||
<div className="h-4 w-4 animate-spin rounded-full border-b-2 border-current mr-2" />
|
||||
) : (
|
||||
<RefreshCw className="h-4 w-4 mr-2" />
|
||||
)}
|
||||
<span className="text-xs">{t('relayStation.syncConfig')}</span>
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={handleFlushDns}
|
||||
disabled={flushingDns}
|
||||
className="w-full h-9 justify-start"
|
||||
>
|
||||
{flushingDns ? (
|
||||
<div className="h-4 w-4 animate-spin rounded-full border-b-2 border-current mr-2" />
|
||||
) : (
|
||||
<RefreshCw className="h-4 w-4 mr-2" />
|
||||
)}
|
||||
<span className="text-xs">{t('relayStation.flushDns')}</span>
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => setJsonConfigView(true)}
|
||||
className="w-full h-9 justify-start"
|
||||
>
|
||||
<Eye className="h-4 w-4 mr-2" />
|
||||
<span className="text-xs">{t('relayStation.viewJson')}</span>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
@@ -542,7 +554,7 @@ const RelayStationManager: React.FC<RelayStationManagerProps> = ({ onBack }) =>
|
||||
</div>
|
||||
{quotaData[station.id].plan_expires_at && (
|
||||
<span className="text-muted-foreground">
|
||||
到期: {new Date(quotaData[station.id].plan_expires_at).toLocaleDateString()}
|
||||
到期: {new Date(quotaData[station.id].plan_expires_at!).toLocaleDateString()}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
@@ -561,7 +573,7 @@ const RelayStationManager: React.FC<RelayStationManagerProps> = ({ onBack }) =>
|
||||
<span className="text-muted-foreground">日额度:</span>
|
||||
<div className="flex items-center gap-2">
|
||||
{(() => {
|
||||
const daily_spent = Number(quotaData[station.id].daily_spent_usd || 0);
|
||||
const daily_spent = Number(quotaData[station.id].daily_spent_usd);
|
||||
const daily_budget = Number(quotaData[station.id].daily_budget_usd);
|
||||
return (
|
||||
<>
|
||||
@@ -579,14 +591,14 @@ const RelayStationManager: React.FC<RelayStationManagerProps> = ({ onBack }) =>
|
||||
<div
|
||||
className={`h-full transition-all ${
|
||||
(() => {
|
||||
const daily_spent = Number(quotaData[station.id].daily_spent_usd || 0);
|
||||
const daily_spent = Number(quotaData[station.id].daily_spent_usd);
|
||||
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(
|
||||
(() => {
|
||||
const daily_spent = Number(quotaData[station.id].daily_spent_usd || 0);
|
||||
const daily_spent = Number(quotaData[station.id].daily_spent_usd);
|
||||
const daily_budget = Number(quotaData[station.id].daily_budget_usd);
|
||||
return (daily_spent / daily_budget) * 100;
|
||||
})(), 100)}%` }}
|
||||
@@ -600,7 +612,7 @@ const RelayStationManager: React.FC<RelayStationManagerProps> = ({ onBack }) =>
|
||||
<span className="text-muted-foreground">月额度:</span>
|
||||
<div className="flex items-center gap-2">
|
||||
{(() => {
|
||||
const monthly_spent = Number(quotaData[station.id].monthly_spent_usd || 0);
|
||||
const monthly_spent = Number(quotaData[station.id].monthly_spent_usd);
|
||||
const monthly_budget = Number(quotaData[station.id].monthly_budget_usd);
|
||||
return (
|
||||
<>
|
||||
@@ -618,14 +630,14 @@ const RelayStationManager: React.FC<RelayStationManagerProps> = ({ onBack }) =>
|
||||
<div
|
||||
className={`h-full transition-all ${
|
||||
(() => {
|
||||
const monthly_spent = Number(quotaData[station.id].monthly_spent_usd || 0);
|
||||
const monthly_spent = Number(quotaData[station.id].monthly_spent_usd);
|
||||
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(
|
||||
(() => {
|
||||
const monthly_spent = Number(quotaData[station.id].monthly_spent_usd || 0);
|
||||
const monthly_spent = Number(quotaData[station.id].monthly_spent_usd);
|
||||
const monthly_budget = Number(quotaData[station.id].monthly_budget_usd);
|
||||
return (monthly_spent / monthly_budget) * 100;
|
||||
})(), 100)}%` }}
|
||||
@@ -847,7 +859,6 @@ const CreateStationDialog: React.FC<{
|
||||
const startTime = Date.now();
|
||||
await fetch(node.url, {
|
||||
method: 'HEAD',
|
||||
timeout: 5000,
|
||||
mode: 'no-cors'
|
||||
});
|
||||
const responseTime = Date.now() - startTime;
|
||||
@@ -898,10 +909,7 @@ const CreateStationDialog: React.FC<{
|
||||
if (formData.adapter === 'packycode') {
|
||||
setFormData(prev => ({
|
||||
...prev,
|
||||
auth_method: 'api_key', // PackyCode 固定使用 API Key
|
||||
api_url: packycodeService === 'taxi'
|
||||
? packycodeTaxiNode
|
||||
: packycodeNode
|
||||
auth_method: 'api_key' // PackyCode 固定使用 API Key
|
||||
}));
|
||||
} else if (formData.adapter === 'custom') {
|
||||
setFormData(prev => ({
|
||||
@@ -914,7 +922,7 @@ const CreateStationDialog: React.FC<{
|
||||
auth_method: 'bearer_token'
|
||||
}));
|
||||
}
|
||||
}, [formData.adapter, packycodeService, packycodeNode, packycodeTaxiNode]);
|
||||
}, [formData.adapter]);
|
||||
|
||||
// 自动填充中转站名称
|
||||
const fillStationName = (serviceType: string) => {
|
||||
@@ -1082,69 +1090,6 @@ const CreateStationDialog: React.FC<{
|
||||
</div>
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
type="button"
|
||||
variant={formData.adapter === 'newapi' ? 'default' : 'outline'}
|
||||
className={`p-3 h-auto flex flex-col items-center space-y-1 transition-all ${
|
||||
formData.adapter === 'newapi'
|
||||
? 'bg-green-600 hover:bg-green-700 text-white border-2 border-green-700'
|
||||
: 'hover:bg-green-50 dark:hover:bg-green-950 border-2 border-transparent'
|
||||
}`}
|
||||
onClick={() => setFormData(prev => ({
|
||||
...prev,
|
||||
adapter: 'newapi',
|
||||
name: 'NewAPI'
|
||||
}))}
|
||||
>
|
||||
<div className="text-xl">🆕</div>
|
||||
<div className="text-center">
|
||||
<div className="font-semibold text-sm">NewAPI</div>
|
||||
<div className="text-xs opacity-80 mt-1">通用接口</div>
|
||||
</div>
|
||||
</Button>
|
||||
|
||||
{/* 第三行:其他适配器 */}
|
||||
<Button
|
||||
type="button"
|
||||
variant={formData.adapter === 'oneapi' ? 'default' : 'outline'}
|
||||
className={`p-3 h-auto flex flex-col items-center space-y-1 transition-all ${
|
||||
formData.adapter === 'oneapi'
|
||||
? 'bg-purple-600 hover:bg-purple-700 text-white border-2 border-purple-700'
|
||||
: 'hover:bg-purple-50 dark:hover:bg-purple-950 border-2 border-transparent'
|
||||
}`}
|
||||
onClick={() => setFormData(prev => ({
|
||||
...prev,
|
||||
adapter: 'oneapi',
|
||||
name: 'OneAPI'
|
||||
}))}
|
||||
>
|
||||
<div className="text-xl">1️⃣</div>
|
||||
<div className="text-center">
|
||||
<div className="font-semibold text-sm">OneAPI</div>
|
||||
<div className="text-xs opacity-80 mt-1">统一接口</div>
|
||||
</div>
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
type="button"
|
||||
variant={formData.adapter === 'yourapi' ? 'default' : 'outline'}
|
||||
className={`p-3 h-auto flex flex-col items-center space-y-1 transition-all ${
|
||||
formData.adapter === 'yourapi'
|
||||
? 'bg-orange-600 hover:bg-orange-700 text-white border-2 border-orange-700'
|
||||
: 'hover:bg-orange-50 dark:hover:bg-orange-950 border-2 border-transparent'
|
||||
}`}
|
||||
onClick={() => setFormData(prev => ({
|
||||
...prev,
|
||||
adapter: 'yourapi',
|
||||
name: 'YourAPI'
|
||||
}))}
|
||||
>
|
||||
<div className="text-xl">👤</div>
|
||||
<div className="text-center">
|
||||
<div className="font-semibold text-sm">YourAPI</div>
|
||||
<div className="text-xs opacity-80 mt-1">个人接口</div>
|
||||
</div>
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
type="button"
|
||||
@@ -1196,6 +1141,7 @@ const CreateStationDialog: React.FC<{
|
||||
onClick={() => {
|
||||
setPackycodeService('taxi');
|
||||
fillStationName('taxi');
|
||||
setFormData(prev => ({ ...prev, api_url: packycodeTaxiNode }));
|
||||
}}
|
||||
>
|
||||
<div className="text-xl">🚗</div>
|
||||
@@ -1216,6 +1162,7 @@ const CreateStationDialog: React.FC<{
|
||||
onClick={() => {
|
||||
setPackycodeService('bus');
|
||||
fillStationName('bus');
|
||||
setFormData(prev => ({ ...prev, api_url: packycodeNode }));
|
||||
}}
|
||||
>
|
||||
<div className="text-xl">🚌</div>
|
||||
@@ -1243,7 +1190,10 @@ const CreateStationDialog: React.FC<{
|
||||
<div className="flex-1">
|
||||
<Select
|
||||
value={packycodeNode}
|
||||
onValueChange={(value: string) => setPackycodeNode(value)}
|
||||
onValueChange={(value: string) => {
|
||||
setPackycodeNode(value);
|
||||
setFormData(prev => ({ ...prev, api_url: value }));
|
||||
}}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder={t('relayStation.selectNode')} />
|
||||
@@ -1316,7 +1266,10 @@ const CreateStationDialog: React.FC<{
|
||||
<div className="flex-1">
|
||||
<Select
|
||||
value={packycodeTaxiNode}
|
||||
onValueChange={(value: string) => setPackycodeTaxiNode(value)}
|
||||
onValueChange={(value: string) => {
|
||||
setPackycodeTaxiNode(value);
|
||||
setFormData(prev => ({ ...prev, api_url: value }));
|
||||
}}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder={t('relayStation.selectNode')} />
|
||||
@@ -1474,18 +1427,6 @@ const CreateStationDialog: React.FC<{
|
||||
)}
|
||||
</div>
|
||||
|
||||
{(formData.adapter === 'newapi' || formData.adapter === 'oneapi') && (
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="user_id">{t('relayStation.userId')}</Label>
|
||||
<Input
|
||||
id="user_id"
|
||||
value={formData.user_id}
|
||||
onChange={(e) => setFormData(prev => ({ ...prev, user_id: e.target.value }))}
|
||||
placeholder={t('relayStation.userIdPlaceholder')}
|
||||
className="w-full"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
<div className="flex justify-end space-x-3 pt-3">
|
||||
@@ -1601,15 +1542,9 @@ const EditStationDialog: React.FC<{
|
||||
}
|
||||
return 'https://api.packycode.com';
|
||||
});
|
||||
const [packycodeTaxiNode, setPackycodeTaxiNode] = useState<string>(() => {
|
||||
// 如果是PackyCode滴滴车服务,使用当前的API URL
|
||||
if (station.adapter === 'packycode' && station.api_url.includes('share-api')) {
|
||||
return station.api_url;
|
||||
}
|
||||
return 'https://share-api.packycode.com';
|
||||
});
|
||||
|
||||
// 测速弹出框状态
|
||||
// 滴滴车服务的节点
|
||||
const [packycodeTaxiNode, setPackycodeTaxiNode] = useState<string>('https://share-api.packycode.com');
|
||||
const [showSpeedTestModal, setShowSpeedTestModal] = useState(false);
|
||||
const [speedTestResults, setSpeedTestResults] = useState<{ url: string; name: string; responseTime: number | null; status: 'testing' | 'success' | 'failed' }[]>([]);
|
||||
const [speedTestInProgress, setSpeedTestInProgress] = useState(false);
|
||||
@@ -1669,7 +1604,6 @@ const EditStationDialog: React.FC<{
|
||||
const startTime = Date.now();
|
||||
await fetch(node.url, {
|
||||
method: 'HEAD',
|
||||
timeout: 5000,
|
||||
mode: 'no-cors'
|
||||
});
|
||||
const responseTime = Date.now() - startTime;
|
||||
@@ -1720,10 +1654,7 @@ const EditStationDialog: React.FC<{
|
||||
if (formData.adapter === 'packycode') {
|
||||
setFormData(prev => ({
|
||||
...prev,
|
||||
auth_method: 'api_key', // PackyCode 固定使用 API Key
|
||||
api_url: packycodeService === 'taxi'
|
||||
? packycodeTaxiNode
|
||||
: packycodeNode
|
||||
auth_method: 'api_key' // PackyCode 固定使用 API Key
|
||||
}));
|
||||
} else if (formData.adapter === 'custom') {
|
||||
setFormData(prev => ({
|
||||
@@ -1736,7 +1667,7 @@ const EditStationDialog: React.FC<{
|
||||
auth_method: 'bearer_token'
|
||||
}));
|
||||
}
|
||||
}, [formData.adapter, packycodeService, packycodeNode, packycodeTaxiNode]);
|
||||
}, [formData.adapter]);
|
||||
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
@@ -1891,69 +1822,6 @@ const EditStationDialog: React.FC<{
|
||||
</div>
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
type="button"
|
||||
variant={formData.adapter === 'newapi' ? 'default' : 'outline'}
|
||||
className={`p-3 h-auto flex flex-col items-center space-y-1 transition-all ${
|
||||
formData.adapter === 'newapi'
|
||||
? 'bg-green-600 hover:bg-green-700 text-white border-2 border-green-700'
|
||||
: 'hover:bg-green-50 dark:hover:bg-green-950 border-2 border-transparent'
|
||||
}`}
|
||||
onClick={() => setFormData(prev => ({
|
||||
...prev,
|
||||
adapter: 'newapi',
|
||||
name: 'NewAPI'
|
||||
}))}
|
||||
>
|
||||
<div className="text-xl">🆕</div>
|
||||
<div className="text-center">
|
||||
<div className="font-semibold text-sm">NewAPI</div>
|
||||
<div className="text-xs opacity-80 mt-1">通用接口</div>
|
||||
</div>
|
||||
</Button>
|
||||
|
||||
{/* 第三行:其他适配器 */}
|
||||
<Button
|
||||
type="button"
|
||||
variant={formData.adapter === 'oneapi' ? 'default' : 'outline'}
|
||||
className={`p-3 h-auto flex flex-col items-center space-y-1 transition-all ${
|
||||
formData.adapter === 'oneapi'
|
||||
? 'bg-purple-600 hover:bg-purple-700 text-white border-2 border-purple-700'
|
||||
: 'hover:bg-purple-50 dark:hover:bg-purple-950 border-2 border-transparent'
|
||||
}`}
|
||||
onClick={() => setFormData(prev => ({
|
||||
...prev,
|
||||
adapter: 'oneapi',
|
||||
name: 'OneAPI'
|
||||
}))}
|
||||
>
|
||||
<div className="text-xl">1️⃣</div>
|
||||
<div className="text-center">
|
||||
<div className="font-semibold text-sm">OneAPI</div>
|
||||
<div className="text-xs opacity-80 mt-1">统一接口</div>
|
||||
</div>
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
type="button"
|
||||
variant={formData.adapter === 'yourapi' ? 'default' : 'outline'}
|
||||
className={`p-3 h-auto flex flex-col items-center space-y-1 transition-all ${
|
||||
formData.adapter === 'yourapi'
|
||||
? 'bg-orange-600 hover:bg-orange-700 text-white border-2 border-orange-700'
|
||||
: 'hover:bg-orange-50 dark:hover:bg-orange-950 border-2 border-transparent'
|
||||
}`}
|
||||
onClick={() => setFormData(prev => ({
|
||||
...prev,
|
||||
adapter: 'yourapi',
|
||||
name: 'YourAPI'
|
||||
}))}
|
||||
>
|
||||
<div className="text-xl">👤</div>
|
||||
<div className="text-center">
|
||||
<div className="font-semibold text-sm">YourAPI</div>
|
||||
<div className="text-xs opacity-80 mt-1">个人接口</div>
|
||||
</div>
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
type="button"
|
||||
@@ -2006,7 +1874,7 @@ const EditStationDialog: React.FC<{
|
||||
setPackycodeService('taxi');
|
||||
setFormData(prev => ({
|
||||
...prev,
|
||||
api_url: 'https://share-api.packycode.com'
|
||||
api_url: packycodeTaxiNode
|
||||
}));
|
||||
}}
|
||||
>
|
||||
@@ -2027,6 +1895,7 @@ const EditStationDialog: React.FC<{
|
||||
}`}
|
||||
onClick={() => {
|
||||
setPackycodeService('bus');
|
||||
setFormData(prev => ({ ...prev, api_url: packycodeNode }));
|
||||
}}
|
||||
>
|
||||
<div className="text-xl">🚌</div>
|
||||
@@ -2227,18 +2096,6 @@ const EditStationDialog: React.FC<{
|
||||
)}
|
||||
</div>
|
||||
|
||||
{(formData.adapter === 'newapi' || formData.adapter === 'oneapi') && (
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="edit-user_id">{t('relayStation.userId')}</Label>
|
||||
<Input
|
||||
id="edit-user_id"
|
||||
value={formData.user_id}
|
||||
onChange={(e) => setFormData(prev => ({ ...prev, user_id: e.target.value }))}
|
||||
placeholder={t('relayStation.userIdPlaceholder')}
|
||||
className="w-full"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex items-center justify-between p-3 bg-muted/30 rounded-lg">
|
||||
<div className="flex items-center space-x-3">
|
||||
|
@@ -457,9 +457,6 @@ export type RelayStationAdapter =
|
||||
| 'glm' // 智谱GLM
|
||||
| 'qwen' // 千问Qwen
|
||||
| 'kimi' // Kimi k2
|
||||
| 'newapi' // NewAPI 兼容平台
|
||||
| 'oneapi' // OneAPI 兼容平台
|
||||
| 'yourapi' // YourAPI 特定平台
|
||||
| 'custom'; // 自定义简单配置
|
||||
|
||||
/** 认证方式 */
|
||||
@@ -477,7 +474,7 @@ export interface RelayStation {
|
||||
adapter: RelayStationAdapter; // 适配器类型
|
||||
auth_method: AuthMethod; // 认证方式
|
||||
system_token: string; // 系统令牌
|
||||
user_id?: string; // 用户 ID(NewAPI 必需)
|
||||
user_id?: string; // 用户 ID(可选)
|
||||
adapter_config?: Record<string, any>; // 适配器特定配置
|
||||
enabled: boolean; // 启用状态
|
||||
created_at: number; // 创建时间
|
||||
@@ -589,17 +586,17 @@ export interface NodeSpeedTestResult {
|
||||
|
||||
/** PackyCode 用户额度信息 */
|
||||
export interface PackycodeUserQuota {
|
||||
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模型
|
||||
daily_budget_usd: number; // 日预算(美元)
|
||||
daily_spent_usd: number; // 日已使用(美元)
|
||||
monthly_budget_usd: number; // 月预算(美元)
|
||||
monthly_spent_usd: number; // 月已使用(美元)
|
||||
balance_usd: number; // 账户余额(美元)
|
||||
total_spent_usd: number; // 总消费(美元)
|
||||
plan_type: string; // 计划类型 (pro, basic, etc.)
|
||||
plan_expires_at?: string; // 计划过期时间
|
||||
username?: string; // 用户名
|
||||
email?: string; // 邮箱
|
||||
opus_enabled?: boolean; // 是否启用Opus模型
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -867,7 +867,7 @@
|
||||
"tokenPlaceholder": "Enter your API token",
|
||||
"tokenRequired": "System token is required",
|
||||
"userId": "User ID",
|
||||
"userIdPlaceholder": "Required for NewAPI/OneAPI",
|
||||
"userIdPlaceholder": "Optional",
|
||||
"enabled": "Enabled",
|
||||
"testConnection": "Test Connection",
|
||||
"connectionSuccess": "Connection successful",
|
||||
|
@@ -794,7 +794,7 @@
|
||||
"tokenPlaceholder": "输入您的 API 令牌",
|
||||
"tokenRequired": "系统令牌必填",
|
||||
"userId": "用户 ID",
|
||||
"userIdPlaceholder": "NewAPI/OneAPI 必需",
|
||||
"userIdPlaceholder": "可选",
|
||||
"enabled": "启用",
|
||||
"testConnection": "测试连接",
|
||||
"connectionSuccess": "连接成功",
|
||||
|
Reference in New Issue
Block a user