修改中转站
This commit is contained in:
@@ -204,15 +204,6 @@ pub fn apply_relay_station_to_config(station: &RelayStation) -> Result<(), Strin
|
|||||||
"packycode" => {
|
"packycode" => {
|
||||||
// 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" => {
|
"custom" => {
|
||||||
// 自定义适配器,使用原始配置
|
// 自定义适配器,使用原始配置
|
||||||
}
|
}
|
||||||
|
File diff suppressed because it is too large
Load Diff
@@ -19,9 +19,6 @@ pub enum RelayStationAdapter {
|
|||||||
Glm, // 智谱GLM
|
Glm, // 智谱GLM
|
||||||
Qwen, // 千问Qwen
|
Qwen, // 千问Qwen
|
||||||
Kimi, // Kimi k2
|
Kimi, // Kimi k2
|
||||||
Newapi, // NewAPI 兼容平台
|
|
||||||
Oneapi, // OneAPI 兼容平台
|
|
||||||
Yourapi, // YourAPI 特定平台
|
|
||||||
Custom, // 自定义简单配置
|
Custom, // 自定义简单配置
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -33,9 +30,6 @@ impl RelayStationAdapter {
|
|||||||
RelayStationAdapter::Glm => "glm",
|
RelayStationAdapter::Glm => "glm",
|
||||||
RelayStationAdapter::Qwen => "qwen",
|
RelayStationAdapter::Qwen => "qwen",
|
||||||
RelayStationAdapter::Kimi => "kimi",
|
RelayStationAdapter::Kimi => "kimi",
|
||||||
RelayStationAdapter::Newapi => "newapi",
|
|
||||||
RelayStationAdapter::Oneapi => "oneapi",
|
|
||||||
RelayStationAdapter::Yourapi => "yourapi",
|
|
||||||
RelayStationAdapter::Custom => "custom",
|
RelayStationAdapter::Custom => "custom",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -60,7 +54,7 @@ pub struct RelayStation {
|
|||||||
pub adapter: RelayStationAdapter, // 适配器类型
|
pub adapter: RelayStationAdapter, // 适配器类型
|
||||||
pub auth_method: AuthMethod, // 认证方式
|
pub auth_method: AuthMethod, // 认证方式
|
||||||
pub system_token: String, // 系统令牌
|
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 adapter_config: Option<HashMap<String, serde_json::Value>>, // 适配器特定配置
|
||||||
pub enabled: bool, // 启用状态
|
pub enabled: bool, // 启用状态
|
||||||
pub created_at: i64, // 创建时间
|
pub created_at: i64, // 创建时间
|
||||||
|
@@ -73,6 +73,17 @@ const RelayStationManager: React.FC<RelayStationManagerProps> = ({ onBack }) =>
|
|||||||
|
|
||||||
const { t } = useTranslation();
|
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
|
// 显示Toast
|
||||||
const showToast = (message: string, type: "success" | "error" = "success") => {
|
const showToast = (message: string, type: "success" | "error" = "success") => {
|
||||||
setToast({ message, type });
|
setToast({ message, type });
|
||||||
@@ -98,13 +109,13 @@ const RelayStationManager: React.FC<RelayStationManagerProps> = ({ onBack }) =>
|
|||||||
setLoadingConfig(true);
|
setLoadingConfig(true);
|
||||||
// 读取完整的 ~/.claude/settings.json 文件
|
// 读取完整的 ~/.claude/settings.json 文件
|
||||||
const settings = await api.getClaudeSettings();
|
const settings = await api.getClaudeSettings();
|
||||||
|
|
||||||
// 保存配置用于简单视图显示
|
// 保存配置用于简单视图显示
|
||||||
setCurrentConfig({
|
setCurrentConfig({
|
||||||
api_url: settings.env?.ANTHROPIC_BASE_URL || '',
|
api_url: settings.env?.ANTHROPIC_BASE_URL || '',
|
||||||
api_token: settings.env?.ANTHROPIC_AUTH_TOKEN || ''
|
api_token: settings.env?.ANTHROPIC_AUTH_TOKEN || ''
|
||||||
});
|
});
|
||||||
|
|
||||||
// 格式化完整的JSON字符串
|
// 格式化完整的JSON字符串
|
||||||
setConfigJson(JSON.stringify(settings, null, 2));
|
setConfigJson(JSON.stringify(settings, null, 2));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -140,10 +151,10 @@ const RelayStationManager: React.FC<RelayStationManagerProps> = ({ onBack }) =>
|
|||||||
setSavingConfig(true);
|
setSavingConfig(true);
|
||||||
// 验证JSON格式
|
// 验证JSON格式
|
||||||
const parsedConfig = JSON.parse(configJson);
|
const parsedConfig = JSON.parse(configJson);
|
||||||
|
|
||||||
// 保存配置到 ~/.claude/settings.json
|
// 保存配置到 ~/.claude/settings.json
|
||||||
await api.saveClaudeSettings(parsedConfig);
|
await api.saveClaudeSettings(parsedConfig);
|
||||||
|
|
||||||
showToast(t('relayStation.configSaved'), "success");
|
showToast(t('relayStation.configSaved'), "success");
|
||||||
setEditingConfig(false);
|
setEditingConfig(false);
|
||||||
loadCurrentConfig();
|
loadCurrentConfig();
|
||||||
@@ -218,9 +229,6 @@ const RelayStationManager: React.FC<RelayStationManagerProps> = ({ onBack }) =>
|
|||||||
case 'glm': return '智谱GLM';
|
case 'glm': return '智谱GLM';
|
||||||
case 'qwen': return '千问Qwen';
|
case 'qwen': return '千问Qwen';
|
||||||
case 'kimi': return 'Kimi k2';
|
case 'kimi': return 'Kimi k2';
|
||||||
case 'newapi': return 'NewAPI';
|
|
||||||
case 'oneapi': return 'OneAPI';
|
|
||||||
case 'yourapi': return 'YourAPI';
|
|
||||||
case 'custom': return t('relayStation.custom');
|
case 'custom': return t('relayStation.custom');
|
||||||
default: return adapter;
|
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">
|
<Card className="border-blue-200 dark:border-blue-900 bg-blue-50/50 dark:bg-blue-950/20">
|
||||||
<CardHeader className="pb-3">
|
<CardHeader className="pb-3">
|
||||||
<div className="flex justify-between items-center">
|
<div className="flex items-center gap-2">
|
||||||
<div className="flex items-center gap-2">
|
<Settings className="h-5 w-5 text-blue-600 dark:text-blue-400" />
|
||||||
<Settings className="h-5 w-5 text-blue-600 dark:text-blue-400" />
|
<CardTitle className="text-lg">{t('relayStation.currentConfig')}</CardTitle>
|
||||||
<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>
|
</div>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
@@ -422,49 +412,71 @@ const RelayStationManager: React.FC<RelayStationManagerProps> = ({ onBack }) =>
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="space-y-2">
|
<div className="flex gap-4">
|
||||||
<div className="flex justify-between items-center mb-2">
|
{/* 左侧数据展示 */}
|
||||||
<span className="text-sm font-medium">{t('relayStation.configPreview')}</span>
|
<div className="flex-1 space-y-2">
|
||||||
<div className="flex gap-2">
|
<div className="text-sm font-medium mb-2">{t('relayStation.configPreview')}</div>
|
||||||
<Button
|
<div className="space-y-1.5 text-sm">
|
||||||
variant="outline"
|
<div className="flex items-start gap-2">
|
||||||
size="sm"
|
<span className="text-muted-foreground min-w-[80px]">API URL:</span>
|
||||||
onClick={() => setJsonConfigView(true)}
|
<span className="font-mono text-xs break-all">
|
||||||
>
|
{currentConfig.api_url || t('relayStation.notConfigured')}
|
||||||
<Eye className="h-4 w-4 mr-1" />
|
</span>
|
||||||
{t('relayStation.viewJson')}
|
</div>
|
||||||
</Button>
|
<div className="flex items-start gap-2">
|
||||||
<Button
|
<span className="text-muted-foreground min-w-[80px]">API Token:</span>
|
||||||
variant="outline"
|
<span className="font-mono text-xs">
|
||||||
size="sm"
|
{currentConfig.api_token ? maskToken(currentConfig.api_token) : t('relayStation.notConfigured')}
|
||||||
onClick={handleFlushDns}
|
</span>
|
||||||
disabled={flushingDns}
|
</div>
|
||||||
>
|
<div className="text-xs text-muted-foreground mt-2">
|
||||||
{flushingDns ? (
|
{t('relayStation.configLocation')}: ~/.claude/settings.json
|
||||||
<div className="h-4 w-4 animate-spin rounded-full border-b-2 border-current mr-1" />
|
</div>
|
||||||
) : (
|
|
||||||
<RefreshCw className="h-4 w-4 mr-1" />
|
|
||||||
)}
|
|
||||||
{t('relayStation.flushDns')}
|
|
||||||
</Button>
|
|
||||||
</div>
|
</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>
|
<div className="flex flex-col gap-2 min-w-[100px]">
|
||||||
<span className="font-mono text-xs break-all">
|
<Button
|
||||||
{currentConfig.api_url || t('relayStation.notConfigured')}
|
variant="outline"
|
||||||
</span>
|
size="sm"
|
||||||
</div>
|
onClick={() => {
|
||||||
<div className="flex items-start gap-2">
|
loadCurrentConfig();
|
||||||
<span className="font-medium text-muted-foreground min-w-[100px]">API Token:</span>
|
syncConfig();
|
||||||
<span className="font-mono text-xs">
|
}}
|
||||||
{currentConfig.api_token || t('relayStation.notConfigured')}
|
disabled={loadingConfig}
|
||||||
</span>
|
className="w-full h-9 justify-start"
|
||||||
</div>
|
>
|
||||||
<div className="text-xs text-muted-foreground mt-3">
|
{loadingConfig ? (
|
||||||
{t('relayStation.configLocation')}: ~/.claude/settings.json
|
<div className="h-4 w-4 animate-spin rounded-full border-b-2 border-current mr-2" />
|
||||||
</div>
|
) : (
|
||||||
|
<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>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -542,7 +554,7 @@ const RelayStationManager: React.FC<RelayStationManagerProps> = ({ onBack }) =>
|
|||||||
</div>
|
</div>
|
||||||
{quotaData[station.id].plan_expires_at && (
|
{quotaData[station.id].plan_expires_at && (
|
||||||
<span className="text-muted-foreground">
|
<span className="text-muted-foreground">
|
||||||
到期: {new Date(quotaData[station.id].plan_expires_at).toLocaleDateString()}
|
到期: {new Date(quotaData[station.id].plan_expires_at!).toLocaleDateString()}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -561,7 +573,7 @@ const RelayStationManager: React.FC<RelayStationManagerProps> = ({ onBack }) =>
|
|||||||
<span className="text-muted-foreground">日额度:</span>
|
<span className="text-muted-foreground">日额度:</span>
|
||||||
<div className="flex items-center gap-2">
|
<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);
|
const daily_budget = Number(quotaData[station.id].daily_budget_usd);
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -579,14 +591,14 @@ const RelayStationManager: React.FC<RelayStationManagerProps> = ({ onBack }) =>
|
|||||||
<div
|
<div
|
||||||
className={`h-full transition-all ${
|
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);
|
const daily_budget = Number(quotaData[station.id].daily_budget_usd);
|
||||||
return daily_spent / daily_budget > 0.8;
|
return daily_spent / daily_budget > 0.8;
|
||||||
})() ? 'bg-orange-500' : 'bg-green-500'
|
})() ? 'bg-orange-500' : 'bg-green-500'
|
||||||
}`}
|
}`}
|
||||||
style={{ width: `${Math.min(
|
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);
|
const daily_budget = Number(quotaData[station.id].daily_budget_usd);
|
||||||
return (daily_spent / daily_budget) * 100;
|
return (daily_spent / daily_budget) * 100;
|
||||||
})(), 100)}%` }}
|
})(), 100)}%` }}
|
||||||
@@ -600,7 +612,7 @@ const RelayStationManager: React.FC<RelayStationManagerProps> = ({ onBack }) =>
|
|||||||
<span className="text-muted-foreground">月额度:</span>
|
<span className="text-muted-foreground">月额度:</span>
|
||||||
<div className="flex items-center gap-2">
|
<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);
|
const monthly_budget = Number(quotaData[station.id].monthly_budget_usd);
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -618,14 +630,14 @@ const RelayStationManager: React.FC<RelayStationManagerProps> = ({ onBack }) =>
|
|||||||
<div
|
<div
|
||||||
className={`h-full transition-all ${
|
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);
|
const monthly_budget = Number(quotaData[station.id].monthly_budget_usd);
|
||||||
return monthly_spent / monthly_budget > 0.8;
|
return monthly_spent / monthly_budget > 0.8;
|
||||||
})() ? 'bg-orange-500' : 'bg-green-500'
|
})() ? 'bg-orange-500' : 'bg-green-500'
|
||||||
}`}
|
}`}
|
||||||
style={{ width: `${Math.min(
|
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);
|
const monthly_budget = Number(quotaData[station.id].monthly_budget_usd);
|
||||||
return (monthly_spent / monthly_budget) * 100;
|
return (monthly_spent / monthly_budget) * 100;
|
||||||
})(), 100)}%` }}
|
})(), 100)}%` }}
|
||||||
@@ -786,7 +798,7 @@ const CreateStationDialog: React.FC<{
|
|||||||
const [packycodeService, setPackycodeService] = useState<string>('bus'); // 默认公交车
|
const [packycodeService, setPackycodeService] = useState<string>('bus'); // 默认公交车
|
||||||
const [packycodeNode, setPackycodeNode] = useState<string>('https://api.packycode.com'); // 默认节点(公交车用)
|
const [packycodeNode, setPackycodeNode] = useState<string>('https://api.packycode.com'); // 默认节点(公交车用)
|
||||||
const [packycodeTaxiNode, setPackycodeTaxiNode] = useState<string>('https://share-api.packycode.com'); // 滴滴车节点
|
const [packycodeTaxiNode, setPackycodeTaxiNode] = useState<string>('https://share-api.packycode.com'); // 滴滴车节点
|
||||||
|
|
||||||
// 测速弹出框状态
|
// 测速弹出框状态
|
||||||
const [showSpeedTestModal, setShowSpeedTestModal] = useState(false);
|
const [showSpeedTestModal, setShowSpeedTestModal] = useState(false);
|
||||||
const [speedTestResults, setSpeedTestResults] = useState<{ url: string; name: string; responseTime: number | null; status: 'testing' | 'success' | 'failed' }[]>([]);
|
const [speedTestResults, setSpeedTestResults] = useState<{ url: string; name: string; responseTime: number | null; status: 'testing' | 'success' | 'failed' }[]>([]);
|
||||||
@@ -828,7 +840,7 @@ const CreateStationDialog: React.FC<{
|
|||||||
const performSpeedTest = async (nodes: { url: string; name: string }[], onComplete: (bestNode: { url: string; name: string }) => void) => {
|
const performSpeedTest = async (nodes: { url: string; name: string }[], onComplete: (bestNode: { url: string; name: string }) => void) => {
|
||||||
setShowSpeedTestModal(true);
|
setShowSpeedTestModal(true);
|
||||||
setSpeedTestInProgress(true);
|
setSpeedTestInProgress(true);
|
||||||
|
|
||||||
// 初始化测速结果
|
// 初始化测速结果
|
||||||
const initialResults = nodes.map(node => ({
|
const initialResults = nodes.map(node => ({
|
||||||
url: node.url,
|
url: node.url,
|
||||||
@@ -837,42 +849,41 @@ const CreateStationDialog: React.FC<{
|
|||||||
status: 'testing' as const
|
status: 'testing' as const
|
||||||
}));
|
}));
|
||||||
setSpeedTestResults(initialResults);
|
setSpeedTestResults(initialResults);
|
||||||
|
|
||||||
let bestNode = nodes[0];
|
let bestNode = nodes[0];
|
||||||
let minTime = Infinity;
|
let minTime = Infinity;
|
||||||
|
|
||||||
// 并行测试所有节点
|
// 并行测试所有节点
|
||||||
const testPromises = nodes.map(async (node, index) => {
|
const testPromises = nodes.map(async (node, index) => {
|
||||||
try {
|
try {
|
||||||
const startTime = Date.now();
|
const startTime = Date.now();
|
||||||
await fetch(node.url, {
|
await fetch(node.url, {
|
||||||
method: 'HEAD',
|
method: 'HEAD',
|
||||||
timeout: 5000,
|
|
||||||
mode: 'no-cors'
|
mode: 'no-cors'
|
||||||
});
|
});
|
||||||
const responseTime = Date.now() - startTime;
|
const responseTime = Date.now() - startTime;
|
||||||
|
|
||||||
// 更新单个节点的测试结果
|
// 更新单个节点的测试结果
|
||||||
setSpeedTestResults(prev => prev.map((result, i) =>
|
setSpeedTestResults(prev => prev.map((result, i) =>
|
||||||
i === index ? { ...result, responseTime, status: 'success' } : result
|
i === index ? { ...result, responseTime, status: 'success' } : result
|
||||||
));
|
));
|
||||||
|
|
||||||
if (responseTime < minTime) {
|
if (responseTime < minTime) {
|
||||||
minTime = responseTime;
|
minTime = responseTime;
|
||||||
bestNode = node;
|
bestNode = node;
|
||||||
}
|
}
|
||||||
|
|
||||||
return { node, responseTime };
|
return { node, responseTime };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(`Node ${node.url} failed:`, error);
|
console.log(`Node ${node.url} failed:`, error);
|
||||||
// 标记节点为失败
|
// 标记节点为失败
|
||||||
setSpeedTestResults(prev => prev.map((result, i) =>
|
setSpeedTestResults(prev => prev.map((result, i) =>
|
||||||
i === index ? { ...result, responseTime: null, status: 'failed' } : result
|
i === index ? { ...result, responseTime: null, status: 'failed' } : result
|
||||||
));
|
));
|
||||||
return { node, responseTime: null };
|
return { node, responseTime: null };
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await Promise.all(testPromises);
|
await Promise.all(testPromises);
|
||||||
// 测试完成后等待2秒让用户看到结果
|
// 测试完成后等待2秒让用户看到结果
|
||||||
@@ -898,10 +909,7 @@ const CreateStationDialog: React.FC<{
|
|||||||
if (formData.adapter === 'packycode') {
|
if (formData.adapter === 'packycode') {
|
||||||
setFormData(prev => ({
|
setFormData(prev => ({
|
||||||
...prev,
|
...prev,
|
||||||
auth_method: 'api_key', // PackyCode 固定使用 API Key
|
auth_method: 'api_key' // PackyCode 固定使用 API Key
|
||||||
api_url: packycodeService === 'taxi'
|
|
||||||
? packycodeTaxiNode
|
|
||||||
: packycodeNode
|
|
||||||
}));
|
}));
|
||||||
} else if (formData.adapter === 'custom') {
|
} else if (formData.adapter === 'custom') {
|
||||||
setFormData(prev => ({
|
setFormData(prev => ({
|
||||||
@@ -914,7 +922,7 @@ const CreateStationDialog: React.FC<{
|
|||||||
auth_method: 'bearer_token'
|
auth_method: 'bearer_token'
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
}, [formData.adapter, packycodeService, packycodeNode, packycodeTaxiNode]);
|
}, [formData.adapter]);
|
||||||
|
|
||||||
// 自动填充中转站名称
|
// 自动填充中转站名称
|
||||||
const fillStationName = (serviceType: string) => {
|
const fillStationName = (serviceType: string) => {
|
||||||
@@ -979,8 +987,8 @@ const CreateStationDialog: React.FC<{
|
|||||||
? 'bg-blue-600 hover:bg-blue-700 text-white border-2 border-blue-700'
|
? 'bg-blue-600 hover:bg-blue-700 text-white border-2 border-blue-700'
|
||||||
: 'hover:bg-blue-50 dark:hover:bg-blue-950 border-2 border-transparent'
|
: 'hover:bg-blue-50 dark:hover:bg-blue-950 border-2 border-transparent'
|
||||||
}`}
|
}`}
|
||||||
onClick={() => setFormData(prev => ({
|
onClick={() => setFormData(prev => ({
|
||||||
...prev,
|
...prev,
|
||||||
adapter: 'packycode',
|
adapter: 'packycode',
|
||||||
name: 'PackyCode',
|
name: 'PackyCode',
|
||||||
api_url: 'https://api.packycode.com'
|
api_url: 'https://api.packycode.com'
|
||||||
@@ -1001,8 +1009,8 @@ const CreateStationDialog: React.FC<{
|
|||||||
? 'bg-indigo-600 hover:bg-indigo-700 text-white border-2 border-indigo-700'
|
? 'bg-indigo-600 hover:bg-indigo-700 text-white border-2 border-indigo-700'
|
||||||
: 'hover:bg-indigo-50 dark:hover:bg-indigo-950 border-2 border-transparent'
|
: 'hover:bg-indigo-50 dark:hover:bg-indigo-950 border-2 border-transparent'
|
||||||
}`}
|
}`}
|
||||||
onClick={() => setFormData(prev => ({
|
onClick={() => setFormData(prev => ({
|
||||||
...prev,
|
...prev,
|
||||||
adapter: 'deepseek',
|
adapter: 'deepseek',
|
||||||
name: 'DeepSeek v3.1',
|
name: 'DeepSeek v3.1',
|
||||||
api_url: 'https://api.deepseek.com/anthropic'
|
api_url: 'https://api.deepseek.com/anthropic'
|
||||||
@@ -1023,8 +1031,8 @@ const CreateStationDialog: React.FC<{
|
|||||||
? 'bg-cyan-600 hover:bg-cyan-700 text-white border-2 border-cyan-700'
|
? 'bg-cyan-600 hover:bg-cyan-700 text-white border-2 border-cyan-700'
|
||||||
: 'hover:bg-cyan-50 dark:hover:bg-cyan-950 border-2 border-transparent'
|
: 'hover:bg-cyan-50 dark:hover:bg-cyan-950 border-2 border-transparent'
|
||||||
}`}
|
}`}
|
||||||
onClick={() => setFormData(prev => ({
|
onClick={() => setFormData(prev => ({
|
||||||
...prev,
|
...prev,
|
||||||
adapter: 'glm',
|
adapter: 'glm',
|
||||||
name: '智谱GLM',
|
name: '智谱GLM',
|
||||||
api_url: 'https://open.bigmodel.cn/api/anthropic'
|
api_url: 'https://open.bigmodel.cn/api/anthropic'
|
||||||
@@ -1046,8 +1054,8 @@ const CreateStationDialog: React.FC<{
|
|||||||
? 'bg-amber-600 hover:bg-amber-700 text-white border-2 border-amber-700'
|
? 'bg-amber-600 hover:bg-amber-700 text-white border-2 border-amber-700'
|
||||||
: 'hover:bg-amber-50 dark:hover:bg-amber-950 border-2 border-transparent'
|
: 'hover:bg-amber-50 dark:hover:bg-amber-950 border-2 border-transparent'
|
||||||
}`}
|
}`}
|
||||||
onClick={() => setFormData(prev => ({
|
onClick={() => setFormData(prev => ({
|
||||||
...prev,
|
...prev,
|
||||||
adapter: 'qwen',
|
adapter: 'qwen',
|
||||||
name: '千问Qwen',
|
name: '千问Qwen',
|
||||||
api_url: 'https://dashscope.aliyuncs.com/api/v2/apps/claude-code-proxy'
|
api_url: 'https://dashscope.aliyuncs.com/api/v2/apps/claude-code-proxy'
|
||||||
@@ -1068,8 +1076,8 @@ const CreateStationDialog: React.FC<{
|
|||||||
? 'bg-violet-600 hover:bg-violet-700 text-white border-2 border-violet-700'
|
? 'bg-violet-600 hover:bg-violet-700 text-white border-2 border-violet-700'
|
||||||
: 'hover:bg-violet-50 dark:hover:bg-violet-950 border-2 border-transparent'
|
: 'hover:bg-violet-50 dark:hover:bg-violet-950 border-2 border-transparent'
|
||||||
}`}
|
}`}
|
||||||
onClick={() => setFormData(prev => ({
|
onClick={() => setFormData(prev => ({
|
||||||
...prev,
|
...prev,
|
||||||
adapter: 'kimi',
|
adapter: 'kimi',
|
||||||
name: 'Kimi k2',
|
name: 'Kimi k2',
|
||||||
api_url: 'https://api.moonshot.cn/anthropic'
|
api_url: 'https://api.moonshot.cn/anthropic'
|
||||||
@@ -1082,69 +1090,6 @@ const CreateStationDialog: React.FC<{
|
|||||||
</div>
|
</div>
|
||||||
</Button>
|
</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
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
@@ -1196,6 +1141,7 @@ const CreateStationDialog: React.FC<{
|
|||||||
onClick={() => {
|
onClick={() => {
|
||||||
setPackycodeService('taxi');
|
setPackycodeService('taxi');
|
||||||
fillStationName('taxi');
|
fillStationName('taxi');
|
||||||
|
setFormData(prev => ({ ...prev, api_url: packycodeTaxiNode }));
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className="text-xl">🚗</div>
|
<div className="text-xl">🚗</div>
|
||||||
@@ -1216,6 +1162,7 @@ const CreateStationDialog: React.FC<{
|
|||||||
onClick={() => {
|
onClick={() => {
|
||||||
setPackycodeService('bus');
|
setPackycodeService('bus');
|
||||||
fillStationName('bus');
|
fillStationName('bus');
|
||||||
|
setFormData(prev => ({ ...prev, api_url: packycodeNode }));
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className="text-xl">🚌</div>
|
<div className="text-xl">🚌</div>
|
||||||
@@ -1243,7 +1190,10 @@ const CreateStationDialog: React.FC<{
|
|||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<Select
|
<Select
|
||||||
value={packycodeNode}
|
value={packycodeNode}
|
||||||
onValueChange={(value: string) => setPackycodeNode(value)}
|
onValueChange={(value: string) => {
|
||||||
|
setPackycodeNode(value);
|
||||||
|
setFormData(prev => ({ ...prev, api_url: value }));
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<SelectTrigger>
|
<SelectTrigger>
|
||||||
<SelectValue placeholder={t('relayStation.selectNode')} />
|
<SelectValue placeholder={t('relayStation.selectNode')} />
|
||||||
@@ -1291,7 +1241,7 @@ const CreateStationDialog: React.FC<{
|
|||||||
{ url: "https://api-us-cn2.packycode.com", name: "🔄 备用1 (US-CN2)" },
|
{ url: "https://api-us-cn2.packycode.com", name: "🔄 备用1 (US-CN2)" },
|
||||||
{ url: "https://api-cf-pro.packycode.com", name: "☁️ 备用2 (CF-Pro)" }
|
{ url: "https://api-cf-pro.packycode.com", name: "☁️ 备用2 (CF-Pro)" }
|
||||||
];
|
];
|
||||||
|
|
||||||
await performSpeedTest(busNodes, (bestNode) => {
|
await performSpeedTest(busNodes, (bestNode) => {
|
||||||
setPackycodeNode(bestNode.url);
|
setPackycodeNode(bestNode.url);
|
||||||
});
|
});
|
||||||
@@ -1316,7 +1266,10 @@ const CreateStationDialog: React.FC<{
|
|||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<Select
|
<Select
|
||||||
value={packycodeTaxiNode}
|
value={packycodeTaxiNode}
|
||||||
onValueChange={(value: string) => setPackycodeTaxiNode(value)}
|
onValueChange={(value: string) => {
|
||||||
|
setPackycodeTaxiNode(value);
|
||||||
|
setFormData(prev => ({ ...prev, api_url: value }));
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<SelectTrigger>
|
<SelectTrigger>
|
||||||
<SelectValue placeholder={t('relayStation.selectNode')} />
|
<SelectValue placeholder={t('relayStation.selectNode')} />
|
||||||
@@ -1343,7 +1296,7 @@ const CreateStationDialog: React.FC<{
|
|||||||
{ url: "https://share-api-cf-pro.packycode.com", name: "☁️ 备用1 (CF-Pro)" },
|
{ url: "https://share-api-cf-pro.packycode.com", name: "☁️ 备用1 (CF-Pro)" },
|
||||||
{ url: "https://share-api-hk-cn2.packycode.com", name: "🇭🇰 备用2 (HK-CN2)" }
|
{ url: "https://share-api-hk-cn2.packycode.com", name: "🇭🇰 备用2 (HK-CN2)" }
|
||||||
];
|
];
|
||||||
|
|
||||||
await performSpeedTest(taxiNodes, (bestNode) => {
|
await performSpeedTest(taxiNodes, (bestNode) => {
|
||||||
setPackycodeTaxiNode(bestNode.url);
|
setPackycodeTaxiNode(bestNode.url);
|
||||||
});
|
});
|
||||||
@@ -1474,18 +1427,6 @@ const CreateStationDialog: React.FC<{
|
|||||||
)}
|
)}
|
||||||
</div>
|
</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">
|
<div className="flex justify-end space-x-3 pt-3">
|
||||||
@@ -1601,15 +1542,9 @@ const EditStationDialog: React.FC<{
|
|||||||
}
|
}
|
||||||
return 'https://api.packycode.com';
|
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 [showSpeedTestModal, setShowSpeedTestModal] = useState(false);
|
||||||
const [speedTestResults, setSpeedTestResults] = useState<{ url: string; name: string; responseTime: number | null; status: 'testing' | 'success' | 'failed' }[]>([]);
|
const [speedTestResults, setSpeedTestResults] = useState<{ url: string; name: string; responseTime: number | null; status: 'testing' | 'success' | 'failed' }[]>([]);
|
||||||
const [speedTestInProgress, setSpeedTestInProgress] = useState(false);
|
const [speedTestInProgress, setSpeedTestInProgress] = useState(false);
|
||||||
@@ -1650,7 +1585,7 @@ const EditStationDialog: React.FC<{
|
|||||||
const performSpeedTest = async (nodes: { url: string; name: string }[], onComplete: (bestNode: { url: string; name: string }) => void) => {
|
const performSpeedTest = async (nodes: { url: string; name: string }[], onComplete: (bestNode: { url: string; name: string }) => void) => {
|
||||||
setShowSpeedTestModal(true);
|
setShowSpeedTestModal(true);
|
||||||
setSpeedTestInProgress(true);
|
setSpeedTestInProgress(true);
|
||||||
|
|
||||||
// 初始化测速结果
|
// 初始化测速结果
|
||||||
const initialResults = nodes.map(node => ({
|
const initialResults = nodes.map(node => ({
|
||||||
url: node.url,
|
url: node.url,
|
||||||
@@ -1659,42 +1594,41 @@ const EditStationDialog: React.FC<{
|
|||||||
status: 'testing' as const
|
status: 'testing' as const
|
||||||
}));
|
}));
|
||||||
setSpeedTestResults(initialResults);
|
setSpeedTestResults(initialResults);
|
||||||
|
|
||||||
let bestNode = nodes[0];
|
let bestNode = nodes[0];
|
||||||
let minTime = Infinity;
|
let minTime = Infinity;
|
||||||
|
|
||||||
// 并行测试所有节点
|
// 并行测试所有节点
|
||||||
const testPromises = nodes.map(async (node, index) => {
|
const testPromises = nodes.map(async (node, index) => {
|
||||||
try {
|
try {
|
||||||
const startTime = Date.now();
|
const startTime = Date.now();
|
||||||
await fetch(node.url, {
|
await fetch(node.url, {
|
||||||
method: 'HEAD',
|
method: 'HEAD',
|
||||||
timeout: 5000,
|
|
||||||
mode: 'no-cors'
|
mode: 'no-cors'
|
||||||
});
|
});
|
||||||
const responseTime = Date.now() - startTime;
|
const responseTime = Date.now() - startTime;
|
||||||
|
|
||||||
// 更新单个节点的测试结果
|
// 更新单个节点的测试结果
|
||||||
setSpeedTestResults(prev => prev.map((result, i) =>
|
setSpeedTestResults(prev => prev.map((result, i) =>
|
||||||
i === index ? { ...result, responseTime, status: 'success' } : result
|
i === index ? { ...result, responseTime, status: 'success' } : result
|
||||||
));
|
));
|
||||||
|
|
||||||
if (responseTime < minTime) {
|
if (responseTime < minTime) {
|
||||||
minTime = responseTime;
|
minTime = responseTime;
|
||||||
bestNode = node;
|
bestNode = node;
|
||||||
}
|
}
|
||||||
|
|
||||||
return { node, responseTime };
|
return { node, responseTime };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(`Node ${node.url} failed:`, error);
|
console.log(`Node ${node.url} failed:`, error);
|
||||||
// 标记节点为失败
|
// 标记节点为失败
|
||||||
setSpeedTestResults(prev => prev.map((result, i) =>
|
setSpeedTestResults(prev => prev.map((result, i) =>
|
||||||
i === index ? { ...result, responseTime: null, status: 'failed' } : result
|
i === index ? { ...result, responseTime: null, status: 'failed' } : result
|
||||||
));
|
));
|
||||||
return { node, responseTime: null };
|
return { node, responseTime: null };
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await Promise.all(testPromises);
|
await Promise.all(testPromises);
|
||||||
// 测试完成后等待2秒让用户看到结果
|
// 测试完成后等待2秒让用户看到结果
|
||||||
@@ -1720,10 +1654,7 @@ const EditStationDialog: React.FC<{
|
|||||||
if (formData.adapter === 'packycode') {
|
if (formData.adapter === 'packycode') {
|
||||||
setFormData(prev => ({
|
setFormData(prev => ({
|
||||||
...prev,
|
...prev,
|
||||||
auth_method: 'api_key', // PackyCode 固定使用 API Key
|
auth_method: 'api_key' // PackyCode 固定使用 API Key
|
||||||
api_url: packycodeService === 'taxi'
|
|
||||||
? packycodeTaxiNode
|
|
||||||
: packycodeNode
|
|
||||||
}));
|
}));
|
||||||
} else if (formData.adapter === 'custom') {
|
} else if (formData.adapter === 'custom') {
|
||||||
setFormData(prev => ({
|
setFormData(prev => ({
|
||||||
@@ -1736,7 +1667,7 @@ const EditStationDialog: React.FC<{
|
|||||||
auth_method: 'bearer_token'
|
auth_method: 'bearer_token'
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
}, [formData.adapter, packycodeService, packycodeNode, packycodeTaxiNode]);
|
}, [formData.adapter]);
|
||||||
|
|
||||||
|
|
||||||
const handleSubmit = async (e: React.FormEvent) => {
|
const handleSubmit = async (e: React.FormEvent) => {
|
||||||
@@ -1788,8 +1719,8 @@ const EditStationDialog: React.FC<{
|
|||||||
? 'bg-blue-600 hover:bg-blue-700 text-white border-2 border-blue-700'
|
? 'bg-blue-600 hover:bg-blue-700 text-white border-2 border-blue-700'
|
||||||
: 'hover:bg-blue-50 dark:hover:bg-blue-950 border-2 border-transparent'
|
: 'hover:bg-blue-50 dark:hover:bg-blue-950 border-2 border-transparent'
|
||||||
}`}
|
}`}
|
||||||
onClick={() => setFormData(prev => ({
|
onClick={() => setFormData(prev => ({
|
||||||
...prev,
|
...prev,
|
||||||
adapter: 'packycode',
|
adapter: 'packycode',
|
||||||
name: 'PackyCode',
|
name: 'PackyCode',
|
||||||
api_url: 'https://api.packycode.com'
|
api_url: 'https://api.packycode.com'
|
||||||
@@ -1810,8 +1741,8 @@ const EditStationDialog: React.FC<{
|
|||||||
? 'bg-indigo-600 hover:bg-indigo-700 text-white border-2 border-indigo-700'
|
? 'bg-indigo-600 hover:bg-indigo-700 text-white border-2 border-indigo-700'
|
||||||
: 'hover:bg-indigo-50 dark:hover:bg-indigo-950 border-2 border-transparent'
|
: 'hover:bg-indigo-50 dark:hover:bg-indigo-950 border-2 border-transparent'
|
||||||
}`}
|
}`}
|
||||||
onClick={() => setFormData(prev => ({
|
onClick={() => setFormData(prev => ({
|
||||||
...prev,
|
...prev,
|
||||||
adapter: 'deepseek',
|
adapter: 'deepseek',
|
||||||
name: 'DeepSeek v3.1',
|
name: 'DeepSeek v3.1',
|
||||||
api_url: 'https://api.deepseek.com/anthropic'
|
api_url: 'https://api.deepseek.com/anthropic'
|
||||||
@@ -1832,8 +1763,8 @@ const EditStationDialog: React.FC<{
|
|||||||
? 'bg-cyan-600 hover:bg-cyan-700 text-white border-2 border-cyan-700'
|
? 'bg-cyan-600 hover:bg-cyan-700 text-white border-2 border-cyan-700'
|
||||||
: 'hover:bg-cyan-50 dark:hover:bg-cyan-950 border-2 border-transparent'
|
: 'hover:bg-cyan-50 dark:hover:bg-cyan-950 border-2 border-transparent'
|
||||||
}`}
|
}`}
|
||||||
onClick={() => setFormData(prev => ({
|
onClick={() => setFormData(prev => ({
|
||||||
...prev,
|
...prev,
|
||||||
adapter: 'glm',
|
adapter: 'glm',
|
||||||
name: '智谱GLM',
|
name: '智谱GLM',
|
||||||
api_url: 'https://open.bigmodel.cn/api/anthropic'
|
api_url: 'https://open.bigmodel.cn/api/anthropic'
|
||||||
@@ -1855,8 +1786,8 @@ const EditStationDialog: React.FC<{
|
|||||||
? 'bg-amber-600 hover:bg-amber-700 text-white border-2 border-amber-700'
|
? 'bg-amber-600 hover:bg-amber-700 text-white border-2 border-amber-700'
|
||||||
: 'hover:bg-amber-50 dark:hover:bg-amber-950 border-2 border-transparent'
|
: 'hover:bg-amber-50 dark:hover:bg-amber-950 border-2 border-transparent'
|
||||||
}`}
|
}`}
|
||||||
onClick={() => setFormData(prev => ({
|
onClick={() => setFormData(prev => ({
|
||||||
...prev,
|
...prev,
|
||||||
adapter: 'qwen',
|
adapter: 'qwen',
|
||||||
name: '千问Qwen',
|
name: '千问Qwen',
|
||||||
api_url: 'https://dashscope.aliyuncs.com/api/v2/apps/claude-code-proxy'
|
api_url: 'https://dashscope.aliyuncs.com/api/v2/apps/claude-code-proxy'
|
||||||
@@ -1877,8 +1808,8 @@ const EditStationDialog: React.FC<{
|
|||||||
? 'bg-violet-600 hover:bg-violet-700 text-white border-2 border-violet-700'
|
? 'bg-violet-600 hover:bg-violet-700 text-white border-2 border-violet-700'
|
||||||
: 'hover:bg-violet-50 dark:hover:bg-violet-950 border-2 border-transparent'
|
: 'hover:bg-violet-50 dark:hover:bg-violet-950 border-2 border-transparent'
|
||||||
}`}
|
}`}
|
||||||
onClick={() => setFormData(prev => ({
|
onClick={() => setFormData(prev => ({
|
||||||
...prev,
|
...prev,
|
||||||
adapter: 'kimi',
|
adapter: 'kimi',
|
||||||
name: 'Kimi k2',
|
name: 'Kimi k2',
|
||||||
api_url: 'https://api.moonshot.cn/anthropic'
|
api_url: 'https://api.moonshot.cn/anthropic'
|
||||||
@@ -1891,69 +1822,6 @@ const EditStationDialog: React.FC<{
|
|||||||
</div>
|
</div>
|
||||||
</Button>
|
</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
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
@@ -2006,7 +1874,7 @@ const EditStationDialog: React.FC<{
|
|||||||
setPackycodeService('taxi');
|
setPackycodeService('taxi');
|
||||||
setFormData(prev => ({
|
setFormData(prev => ({
|
||||||
...prev,
|
...prev,
|
||||||
api_url: 'https://share-api.packycode.com'
|
api_url: packycodeTaxiNode
|
||||||
}));
|
}));
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -2027,6 +1895,7 @@ const EditStationDialog: React.FC<{
|
|||||||
}`}
|
}`}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setPackycodeService('bus');
|
setPackycodeService('bus');
|
||||||
|
setFormData(prev => ({ ...prev, api_url: packycodeNode }));
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className="text-xl">🚌</div>
|
<div className="text-xl">🚌</div>
|
||||||
@@ -2096,7 +1965,7 @@ const EditStationDialog: React.FC<{
|
|||||||
{ url: "https://api-us-cn2.packycode.com", name: "🔄 备用1 (US-CN2)" },
|
{ url: "https://api-us-cn2.packycode.com", name: "🔄 备用1 (US-CN2)" },
|
||||||
{ url: "https://api-cf-pro.packycode.com", name: "☁️ 备用2 (CF-Pro)" }
|
{ url: "https://api-cf-pro.packycode.com", name: "☁️ 备用2 (CF-Pro)" }
|
||||||
];
|
];
|
||||||
|
|
||||||
await performSpeedTest(busNodes, (bestNode) => {
|
await performSpeedTest(busNodes, (bestNode) => {
|
||||||
setPackycodeNode(bestNode.url);
|
setPackycodeNode(bestNode.url);
|
||||||
});
|
});
|
||||||
@@ -2227,18 +2096,6 @@ const EditStationDialog: React.FC<{
|
|||||||
)}
|
)}
|
||||||
</div>
|
</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 justify-between p-3 bg-muted/30 rounded-lg">
|
||||||
<div className="flex items-center space-x-3">
|
<div className="flex items-center space-x-3">
|
||||||
|
@@ -457,9 +457,6 @@ export type RelayStationAdapter =
|
|||||||
| 'glm' // 智谱GLM
|
| 'glm' // 智谱GLM
|
||||||
| 'qwen' // 千问Qwen
|
| 'qwen' // 千问Qwen
|
||||||
| 'kimi' // Kimi k2
|
| 'kimi' // Kimi k2
|
||||||
| 'newapi' // NewAPI 兼容平台
|
|
||||||
| 'oneapi' // OneAPI 兼容平台
|
|
||||||
| 'yourapi' // YourAPI 特定平台
|
|
||||||
| 'custom'; // 自定义简单配置
|
| 'custom'; // 自定义简单配置
|
||||||
|
|
||||||
/** 认证方式 */
|
/** 认证方式 */
|
||||||
@@ -477,7 +474,7 @@ export interface RelayStation {
|
|||||||
adapter: RelayStationAdapter; // 适配器类型
|
adapter: RelayStationAdapter; // 适配器类型
|
||||||
auth_method: AuthMethod; // 认证方式
|
auth_method: AuthMethod; // 认证方式
|
||||||
system_token: string; // 系统令牌
|
system_token: string; // 系统令牌
|
||||||
user_id?: string; // 用户 ID(NewAPI 必需)
|
user_id?: string; // 用户 ID(可选)
|
||||||
adapter_config?: Record<string, any>; // 适配器特定配置
|
adapter_config?: Record<string, any>; // 适配器特定配置
|
||||||
enabled: boolean; // 启用状态
|
enabled: boolean; // 启用状态
|
||||||
created_at: number; // 创建时间
|
created_at: number; // 创建时间
|
||||||
@@ -589,17 +586,17 @@ export interface NodeSpeedTestResult {
|
|||||||
|
|
||||||
/** PackyCode 用户额度信息 */
|
/** PackyCode 用户额度信息 */
|
||||||
export interface PackycodeUserQuota {
|
export interface PackycodeUserQuota {
|
||||||
daily_budget_usd: string | number; // 日预算(美元)
|
daily_budget_usd: number; // 日预算(美元)
|
||||||
daily_spent_usd: string | number | null; // 日已使用(美元)
|
daily_spent_usd: number; // 日已使用(美元)
|
||||||
monthly_budget_usd: string | number; // 月预算(美元)
|
monthly_budget_usd: number; // 月预算(美元)
|
||||||
monthly_spent_usd: string | number | null; // 月已使用(美元)
|
monthly_spent_usd: number; // 月已使用(美元)
|
||||||
balance_usd: string | number; // 账户余额(美元)
|
balance_usd: number; // 账户余额(美元)
|
||||||
total_spent_usd: string | number; // 总消费(美元)
|
total_spent_usd: number; // 总消费(美元)
|
||||||
plan_type: string; // 计划类型 (pro, basic, etc.)
|
plan_type: string; // 计划类型 (pro, basic, etc.)
|
||||||
plan_expires_at: string; // 计划过期时间
|
plan_expires_at?: string; // 计划过期时间
|
||||||
username?: string; // 用户名
|
username?: string; // 用户名
|
||||||
email?: string; // 邮箱
|
email?: string; // 邮箱
|
||||||
opus_enabled?: boolean; // 是否启用Opus模型
|
opus_enabled?: boolean; // 是否启用Opus模型
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -867,7 +867,7 @@
|
|||||||
"tokenPlaceholder": "Enter your API token",
|
"tokenPlaceholder": "Enter your API token",
|
||||||
"tokenRequired": "System token is required",
|
"tokenRequired": "System token is required",
|
||||||
"userId": "User ID",
|
"userId": "User ID",
|
||||||
"userIdPlaceholder": "Required for NewAPI/OneAPI",
|
"userIdPlaceholder": "Optional",
|
||||||
"enabled": "Enabled",
|
"enabled": "Enabled",
|
||||||
"testConnection": "Test Connection",
|
"testConnection": "Test Connection",
|
||||||
"connectionSuccess": "Connection successful",
|
"connectionSuccess": "Connection successful",
|
||||||
|
@@ -794,7 +794,7 @@
|
|||||||
"tokenPlaceholder": "输入您的 API 令牌",
|
"tokenPlaceholder": "输入您的 API 令牌",
|
||||||
"tokenRequired": "系统令牌必填",
|
"tokenRequired": "系统令牌必填",
|
||||||
"userId": "用户 ID",
|
"userId": "用户 ID",
|
||||||
"userIdPlaceholder": "NewAPI/OneAPI 必需",
|
"userIdPlaceholder": "可选",
|
||||||
"enabled": "启用",
|
"enabled": "启用",
|
||||||
"testConnection": "测试连接",
|
"testConnection": "测试连接",
|
||||||
"connectionSuccess": "连接成功",
|
"connectionSuccess": "连接成功",
|
||||||
|
Reference in New Issue
Block a user