整合自定义 json
This commit is contained in:
@@ -189,7 +189,7 @@ pub fn apply_relay_station_to_config(station: &RelayStation) -> Result<(), Strin
|
|||||||
// 读取当前配置
|
// 读取当前配置
|
||||||
let mut config = read_claude_config()?;
|
let mut config = read_claude_config()?;
|
||||||
|
|
||||||
// 仅更新这三个关键字段,保留其他所有配置不变:
|
// 更新三个关键字段:
|
||||||
// 1. ANTHROPIC_BASE_URL
|
// 1. ANTHROPIC_BASE_URL
|
||||||
config.env.anthropic_base_url = Some(station.api_url.clone());
|
config.env.anthropic_base_url = Some(station.api_url.clone());
|
||||||
|
|
||||||
@@ -199,6 +199,29 @@ pub fn apply_relay_station_to_config(station: &RelayStation) -> Result<(), Strin
|
|||||||
// 3. apiKeyHelper - 设置为 echo 格式
|
// 3. apiKeyHelper - 设置为 echo 格式
|
||||||
config.api_key_helper = Some(format!("echo '{}'", station.system_token));
|
config.api_key_helper = Some(format!("echo '{}'", station.system_token));
|
||||||
|
|
||||||
|
// 处理 adapter_config 中的自定义字段
|
||||||
|
if let Some(ref adapter_config) = station.adapter_config {
|
||||||
|
log::info!("[CLAUDE_CONFIG] Applying adapter_config: {:?}", adapter_config);
|
||||||
|
|
||||||
|
// 遍历 adapter_config 中的所有字段
|
||||||
|
for (key, value) in adapter_config {
|
||||||
|
match key.as_str() {
|
||||||
|
// 已知的字段直接写入对应位置
|
||||||
|
"model" => {
|
||||||
|
if let Some(model_value) = value.as_str() {
|
||||||
|
config.model = Some(model_value.to_string());
|
||||||
|
log::info!("[CLAUDE_CONFIG] Set model: {}", model_value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 其他字段写入到 extra_fields 中
|
||||||
|
_ => {
|
||||||
|
config.extra_fields.insert(key.clone(), value.clone());
|
||||||
|
log::info!("[CLAUDE_CONFIG] Set extra field {}: {:?}", key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 如果是特定适配器,可能需要特殊处理 URL 格式
|
// 如果是特定适配器,可能需要特殊处理 URL 格式
|
||||||
match station.adapter.as_str() {
|
match station.adapter.as_str() {
|
||||||
"packycode" => {
|
"packycode" => {
|
||||||
@@ -213,7 +236,7 @@ pub fn apply_relay_station_to_config(station: &RelayStation) -> Result<(), Strin
|
|||||||
// 写入更新后的配置
|
// 写入更新后的配置
|
||||||
write_claude_config(&config)?;
|
write_claude_config(&config)?;
|
||||||
|
|
||||||
log::info!("已将中转站 {} 的 API 配置(apiKeyHelper, ANTHROPIC_BASE_URL, ANTHROPIC_AUTH_TOKEN)应用到 Claude 配置文件", station.name);
|
log::info!("已将中转站 {} 的 API 配置(apiKeyHelper, ANTHROPIC_BASE_URL, ANTHROPIC_AUTH_TOKEN)及自定义配置应用到 Claude 配置文件", station.name);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -241,13 +264,22 @@ pub fn clear_relay_station_from_config() -> Result<(), String> {
|
|||||||
// 恢复原始的 apiKeyHelper(如果有备份的话)
|
// 恢复原始的 apiKeyHelper(如果有备份的话)
|
||||||
if let Some(backup) = backup_config {
|
if let Some(backup) = backup_config {
|
||||||
config.api_key_helper = backup.api_key_helper;
|
config.api_key_helper = backup.api_key_helper;
|
||||||
|
config.model = backup.model.clone();
|
||||||
// 如果备份中有 ANTHROPIC_AUTH_TOKEN,也恢复它
|
// 如果备份中有 ANTHROPIC_AUTH_TOKEN,也恢复它
|
||||||
if backup.env.anthropic_auth_token.is_some() {
|
if backup.env.anthropic_auth_token.is_some() {
|
||||||
config.env.anthropic_auth_token = backup.env.anthropic_auth_token;
|
config.env.anthropic_auth_token = backup.env.anthropic_auth_token;
|
||||||
}
|
}
|
||||||
|
// 恢复备份中的 extra_fields
|
||||||
|
config.extra_fields = backup.extra_fields.clone();
|
||||||
|
log::info!("[CLAUDE_CONFIG] Restored model from backup: {:?}", backup.model);
|
||||||
|
log::info!("[CLAUDE_CONFIG] Restored {} extra fields from backup", config.extra_fields.len());
|
||||||
} else {
|
} else {
|
||||||
// 如果没有备份,清除 apiKeyHelper
|
// 如果没有备份,清除 apiKeyHelper 和 model
|
||||||
config.api_key_helper = None;
|
config.api_key_helper = None;
|
||||||
|
config.model = None;
|
||||||
|
// 清除所有额外的自定义字段
|
||||||
|
config.extra_fields.clear();
|
||||||
|
log::info!("[CLAUDE_CONFIG] Cleared model and all extra fields (no backup found)");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 写入更新后的配置
|
// 写入更新后的配置
|
||||||
|
|||||||
@@ -351,6 +351,9 @@ pub async fn relay_station_create(
|
|||||||
.trim_matches('"')
|
.trim_matches('"')
|
||||||
.to_string();
|
.to_string();
|
||||||
|
|
||||||
|
// 记录adapter_config日志
|
||||||
|
log::info!("[CREATE] Received adapter_config: {:?}", request.adapter_config);
|
||||||
|
|
||||||
let adapter_config_str = request
|
let adapter_config_str = request
|
||||||
.adapter_config
|
.adapter_config
|
||||||
.as_ref()
|
.as_ref()
|
||||||
@@ -358,6 +361,8 @@ pub async fn relay_station_create(
|
|||||||
.transpose()
|
.transpose()
|
||||||
.map_err(|_| i18n::t("relay_station.invalid_config"))?;
|
.map_err(|_| i18n::t("relay_station.invalid_config"))?;
|
||||||
|
|
||||||
|
log::info!("[CREATE] Serialized adapter_config_str: {:?}", adapter_config_str);
|
||||||
|
|
||||||
// 如果要启用这个新中转站,先禁用所有其他中转站
|
// 如果要启用这个新中转站,先禁用所有其他中转站
|
||||||
if request.enabled {
|
if request.enabled {
|
||||||
conn.execute("UPDATE relay_stations SET enabled = 0", [])
|
conn.execute("UPDATE relay_stations SET enabled = 0", [])
|
||||||
@@ -408,7 +413,8 @@ pub async fn relay_station_create(
|
|||||||
updated_at: now,
|
updated_at: now,
|
||||||
};
|
};
|
||||||
|
|
||||||
log::info!("Created relay station: {} ({})", station.name, id);
|
log::info!("[CREATE] Created relay station: {} ({})", station.name, id);
|
||||||
|
log::info!("[CREATE] Final station.adapter_config: {:?}", station.adapter_config);
|
||||||
Ok(station)
|
Ok(station)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -438,6 +444,9 @@ pub async fn relay_station_update(
|
|||||||
.trim_matches('"')
|
.trim_matches('"')
|
||||||
.to_string();
|
.to_string();
|
||||||
|
|
||||||
|
// 记录adapter_config日志
|
||||||
|
log::info!("[UPDATE] Received adapter_config: {:?}", request.adapter_config);
|
||||||
|
|
||||||
let adapter_config_str = request
|
let adapter_config_str = request
|
||||||
.adapter_config
|
.adapter_config
|
||||||
.as_ref()
|
.as_ref()
|
||||||
@@ -445,6 +454,8 @@ pub async fn relay_station_update(
|
|||||||
.transpose()
|
.transpose()
|
||||||
.map_err(|_| i18n::t("relay_station.invalid_config"))?;
|
.map_err(|_| i18n::t("relay_station.invalid_config"))?;
|
||||||
|
|
||||||
|
log::info!("[UPDATE] Serialized adapter_config_str: {:?}", adapter_config_str);
|
||||||
|
|
||||||
// 如果要启用这个中转站,先禁用所有其他中转站
|
// 如果要启用这个中转站,先禁用所有其他中转站
|
||||||
if request.enabled {
|
if request.enabled {
|
||||||
conn.execute(
|
conn.execute(
|
||||||
@@ -504,7 +515,8 @@ pub async fn relay_station_update(
|
|||||||
updated_at: now,
|
updated_at: now,
|
||||||
};
|
};
|
||||||
|
|
||||||
log::info!("Updated relay station: {} ({})", station.name, request.id);
|
log::info!("[UPDATE] Updated relay station: {} ({})", station.name, request.id);
|
||||||
|
log::info!("[UPDATE] Final station.adapter_config: {:?}", station.adapter_config);
|
||||||
Ok(station)
|
Ok(station)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -853,6 +853,8 @@ 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 [customJson, setCustomJson] = useState<string>(''); // 自定义JSON配置
|
||||||
|
const [originalCustomJson] = useState<string>(''); // 原始JSON配置(用于比较是否修改)
|
||||||
|
|
||||||
// 测速弹出框状态
|
// 测速弹出框状态
|
||||||
const [showSpeedTestModal, setShowSpeedTestModal] = useState(false);
|
const [showSpeedTestModal, setShowSpeedTestModal] = useState(false);
|
||||||
@@ -1013,6 +1015,38 @@ const CreateStationDialog: React.FC<{
|
|||||||
try {
|
try {
|
||||||
setSubmitting(true);
|
setSubmitting(true);
|
||||||
|
|
||||||
|
// 处理自定义JSON配置
|
||||||
|
let adapterConfig: Record<string, any> = {};
|
||||||
|
let shouldUpdateConfig = false;
|
||||||
|
|
||||||
|
console.log('[DEBUG] Custom JSON Input:', customJson);
|
||||||
|
console.log('[DEBUG] Original Custom JSON:', originalCustomJson);
|
||||||
|
|
||||||
|
if (customJson.trim()) {
|
||||||
|
// 用户输入了JSON内容
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(customJson);
|
||||||
|
adapterConfig = parsed;
|
||||||
|
shouldUpdateConfig = true;
|
||||||
|
console.log('[DEBUG] Parsed JSON config:', adapterConfig);
|
||||||
|
} catch (error) {
|
||||||
|
setFormToast({ message: t('relayStation.invalidJson'), type: "error" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else if (customJson === '' && originalCustomJson !== '') {
|
||||||
|
// 用户清空了输入框(原不为空,现为空)
|
||||||
|
shouldUpdateConfig = true;
|
||||||
|
adapterConfig = {};
|
||||||
|
console.log('[DEBUG] User cleared custom config');
|
||||||
|
} else if (customJson === '' && originalCustomJson === '') {
|
||||||
|
// 一直为空(创建新中转站或未修改)
|
||||||
|
shouldUpdateConfig = false;
|
||||||
|
console.log('[DEBUG] No custom config update needed');
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('[DEBUG] Should update config:', shouldUpdateConfig);
|
||||||
|
console.log('[DEBUG] Adapter config to send:', shouldUpdateConfig ? adapterConfig : 'undefined');
|
||||||
|
|
||||||
// PackyCode 保存时自动选择最佳节点
|
// PackyCode 保存时自动选择最佳节点
|
||||||
if (formData.adapter === 'packycode') {
|
if (formData.adapter === 'packycode') {
|
||||||
let finalApiUrl = formData.api_url;
|
let finalApiUrl = formData.api_url;
|
||||||
@@ -1047,17 +1081,29 @@ const CreateStationDialog: React.FC<{
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const finalConfig = shouldUpdateConfig ? {
|
||||||
|
service_type: packycodeService,
|
||||||
|
...adapterConfig
|
||||||
|
} : undefined;
|
||||||
|
|
||||||
|
console.log('[DEBUG] Final adapter_config for PackyCode:', finalConfig);
|
||||||
|
|
||||||
// 使用选择的最佳节点创建中转站
|
// 使用选择的最佳节点创建中转站
|
||||||
await api.relayStationCreate({
|
await api.relayStationCreate({
|
||||||
...formData,
|
...formData,
|
||||||
api_url: finalApiUrl,
|
api_url: finalApiUrl,
|
||||||
adapter_config: {
|
adapter_config: finalConfig
|
||||||
service_type: packycodeService
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
const finalConfig = shouldUpdateConfig ? adapterConfig : undefined;
|
||||||
|
|
||||||
|
console.log('[DEBUG] Final adapter_config for non-PackyCode:', finalConfig);
|
||||||
|
|
||||||
// 非 PackyCode 适配器直接创建
|
// 非 PackyCode 适配器直接创建
|
||||||
await api.relayStationCreate(formData);
|
await api.relayStationCreate({
|
||||||
|
...formData,
|
||||||
|
adapter_config: finalConfig
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
onSuccess();
|
onSuccess();
|
||||||
@@ -1467,6 +1513,25 @@ const CreateStationDialog: React.FC<{
|
|||||||
<p className="text-xs text-muted-foreground">
|
<p className="text-xs text-muted-foreground">
|
||||||
{t('relayStation.packycodeTokenNote')}
|
{t('relayStation.packycodeTokenNote')}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
{/* 自定义JSON配置 */}
|
||||||
|
<div className="space-y-2">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<Label htmlFor="custom-json">{t('relayStation.customJson')}</Label>
|
||||||
|
<span className="text-xs text-muted-foreground">{t('relayStation.customJsonOptional')}</span>
|
||||||
|
</div>
|
||||||
|
<Textarea
|
||||||
|
id="custom-json"
|
||||||
|
value={customJson}
|
||||||
|
onChange={(e) => setCustomJson(e.target.value)}
|
||||||
|
placeholder='{"key": "value"}'
|
||||||
|
rows={3}
|
||||||
|
className="w-full font-mono text-xs"
|
||||||
|
/>
|
||||||
|
<p className="text-xs text-muted-foreground">
|
||||||
|
{t('relayStation.customJsonNote')}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
// 其他适配器显示认证方式选择
|
// 其他适配器显示认证方式选择
|
||||||
@@ -1518,6 +1583,25 @@ const CreateStationDialog: React.FC<{
|
|||||||
className="w-full font-mono text-sm"
|
className="w-full font-mono text-sm"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* 自定义JSON配置 */}
|
||||||
|
<div className="space-y-2">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<Label htmlFor="custom-json">{t('relayStation.customJson')}</Label>
|
||||||
|
<span className="text-xs text-muted-foreground">{t('relayStation.customJsonOptional')}</span>
|
||||||
|
</div>
|
||||||
|
<Textarea
|
||||||
|
id="custom-json"
|
||||||
|
value={customJson}
|
||||||
|
onChange={(e) => setCustomJson(e.target.value)}
|
||||||
|
placeholder='{"key": "value"}'
|
||||||
|
rows={3}
|
||||||
|
className="w-full font-mono text-xs"
|
||||||
|
/>
|
||||||
|
<p className="text-xs text-muted-foreground">
|
||||||
|
{t('relayStation.customJsonNote')}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -1554,6 +1638,9 @@ const CreateStationDialog: React.FC<{
|
|||||||
<DialogContent className="sm:max-w-[500px]">
|
<DialogContent className="sm:max-w-[500px]">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>{t('relayStation.speedTest')}</DialogTitle>
|
<DialogTitle>{t('relayStation.speedTest')}</DialogTitle>
|
||||||
|
<DialogDescription>
|
||||||
|
{speedTestInProgress ? t('relayStation.testingNodes') : t('relayStation.testCompleted')}
|
||||||
|
</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div className="text-sm text-muted-foreground">
|
<div className="text-sm text-muted-foreground">
|
||||||
@@ -1644,6 +1731,28 @@ const EditStationDialog: React.FC<{
|
|||||||
}
|
}
|
||||||
return 'https://share-api.packycode.com';
|
return 'https://share-api.packycode.com';
|
||||||
});
|
});
|
||||||
|
const [customJson, setCustomJson] = useState<string>(() => {
|
||||||
|
// 从 adapter_config 中提取自定义JSON
|
||||||
|
if (station.adapter_config) {
|
||||||
|
// 排除 service_type 等已知字段
|
||||||
|
const { service_type, ...customFields } = station.adapter_config as any;
|
||||||
|
if (Object.keys(customFields).length > 0) {
|
||||||
|
return JSON.stringify(customFields, null, 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
});
|
||||||
|
const [originalCustomJson] = useState<string>(() => {
|
||||||
|
// 从 adapter_config 中提取自定义JSON
|
||||||
|
if (station.adapter_config) {
|
||||||
|
// 排除 service_type 等已知字段
|
||||||
|
const { service_type, ...customFields } = station.adapter_config as any;
|
||||||
|
if (Object.keys(customFields).length > 0) {
|
||||||
|
return JSON.stringify(customFields, null, 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
});
|
||||||
|
|
||||||
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' }[]>([]);
|
||||||
@@ -1791,6 +1900,38 @@ const EditStationDialog: React.FC<{
|
|||||||
try {
|
try {
|
||||||
setSubmitting(true);
|
setSubmitting(true);
|
||||||
|
|
||||||
|
// 处理自定义JSON配置
|
||||||
|
let adapterConfig: Record<string, any> = {};
|
||||||
|
let shouldUpdateConfig = false;
|
||||||
|
|
||||||
|
console.log('[DEBUG-EDIT] Custom JSON Input:', customJson);
|
||||||
|
console.log('[DEBUG-EDIT] Original Custom JSON:', originalCustomJson);
|
||||||
|
|
||||||
|
if (customJson.trim()) {
|
||||||
|
// 用户输入了JSON内容
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(customJson);
|
||||||
|
adapterConfig = parsed;
|
||||||
|
shouldUpdateConfig = true;
|
||||||
|
console.log('[DEBUG-EDIT] Parsed JSON config:', adapterConfig);
|
||||||
|
} catch (error) {
|
||||||
|
setFormToast({ message: t('relayStation.invalidJson'), type: "error" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else if (customJson === '' && originalCustomJson !== '') {
|
||||||
|
// 用户清空了输入框(原不为空,现为空)
|
||||||
|
shouldUpdateConfig = true;
|
||||||
|
adapterConfig = {};
|
||||||
|
console.log('[DEBUG-EDIT] User cleared custom config');
|
||||||
|
} else if (customJson === '' && originalCustomJson === '') {
|
||||||
|
// 一直为空(未修改)
|
||||||
|
shouldUpdateConfig = false;
|
||||||
|
console.log('[DEBUG-EDIT] No custom config update needed');
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('[DEBUG-EDIT] Should update config:', shouldUpdateConfig);
|
||||||
|
console.log('[DEBUG-EDIT] Adapter config to send:', shouldUpdateConfig ? adapterConfig : 'undefined');
|
||||||
|
|
||||||
// PackyCode 保存时自动选择最佳节点
|
// PackyCode 保存时自动选择最佳节点
|
||||||
if (formData.adapter === 'packycode') {
|
if (formData.adapter === 'packycode') {
|
||||||
let finalApiUrl = formData.api_url;
|
let finalApiUrl = formData.api_url;
|
||||||
@@ -1930,17 +2071,29 @@ const EditStationDialog: React.FC<{
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const finalConfig = shouldUpdateConfig ? {
|
||||||
|
service_type: packycodeService,
|
||||||
|
...adapterConfig
|
||||||
|
} : undefined;
|
||||||
|
|
||||||
|
console.log('[DEBUG-EDIT] Final adapter_config for PackyCode:', finalConfig);
|
||||||
|
|
||||||
// 使用选择的最佳节点更新中转站
|
// 使用选择的最佳节点更新中转站
|
||||||
await api.relayStationUpdate({
|
await api.relayStationUpdate({
|
||||||
...formData,
|
...formData,
|
||||||
api_url: finalApiUrl,
|
api_url: finalApiUrl,
|
||||||
adapter_config: {
|
adapter_config: finalConfig
|
||||||
service_type: packycodeService
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
const finalConfig = shouldUpdateConfig ? adapterConfig : undefined;
|
||||||
|
|
||||||
|
console.log('[DEBUG-EDIT] Final adapter_config for non-PackyCode:', finalConfig);
|
||||||
|
|
||||||
// 非 PackyCode 适配器直接更新
|
// 非 PackyCode 适配器直接更新
|
||||||
await api.relayStationUpdate(formData);
|
await api.relayStationUpdate({
|
||||||
|
...formData,
|
||||||
|
adapter_config: finalConfig
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
onSuccess();
|
onSuccess();
|
||||||
@@ -2407,6 +2560,25 @@ const EditStationDialog: React.FC<{
|
|||||||
<p className="text-xs text-muted-foreground">
|
<p className="text-xs text-muted-foreground">
|
||||||
{t('relayStation.packycodeTokenNote')}
|
{t('relayStation.packycodeTokenNote')}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
{/* 自定义JSON配置 */}
|
||||||
|
<div className="space-y-2">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<Label htmlFor="edit-custom-json">{t('relayStation.customJson')}</Label>
|
||||||
|
<span className="text-xs text-muted-foreground">{t('relayStation.customJsonOptional')}</span>
|
||||||
|
</div>
|
||||||
|
<Textarea
|
||||||
|
id="edit-custom-json"
|
||||||
|
value={customJson}
|
||||||
|
onChange={(e) => setCustomJson(e.target.value)}
|
||||||
|
placeholder='{"key": "value"}'
|
||||||
|
rows={3}
|
||||||
|
className="w-full font-mono text-xs"
|
||||||
|
/>
|
||||||
|
<p className="text-xs text-muted-foreground">
|
||||||
|
{t('relayStation.customJsonNote')}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
// 其他适配器显示认证方式选择
|
// 其他适配器显示认证方式选择
|
||||||
@@ -2458,6 +2630,25 @@ const EditStationDialog: React.FC<{
|
|||||||
className="w-full font-mono text-sm"
|
className="w-full font-mono text-sm"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* 自定义JSON配置 */}
|
||||||
|
<div className="space-y-2">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<Label htmlFor="edit-custom-json">{t('relayStation.customJson')}</Label>
|
||||||
|
<span className="text-xs text-muted-foreground">{t('relayStation.customJsonOptional')}</span>
|
||||||
|
</div>
|
||||||
|
<Textarea
|
||||||
|
id="edit-custom-json"
|
||||||
|
value={customJson}
|
||||||
|
onChange={(e) => setCustomJson(e.target.value)}
|
||||||
|
placeholder='{"key": "value"}'
|
||||||
|
rows={3}
|
||||||
|
className="w-full font-mono text-xs"
|
||||||
|
/>
|
||||||
|
<p className="text-xs text-muted-foreground">
|
||||||
|
{t('relayStation.customJsonNote')}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -2514,6 +2705,9 @@ const EditStationDialog: React.FC<{
|
|||||||
<DialogContent className="sm:max-w-[500px]">
|
<DialogContent className="sm:max-w-[500px]">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>{t('relayStation.speedTest')}</DialogTitle>
|
<DialogTitle>{t('relayStation.speedTest')}</DialogTitle>
|
||||||
|
<DialogDescription>
|
||||||
|
{speedTestInProgress ? t('relayStation.testingNodes') : t('relayStation.testCompleted')}
|
||||||
|
</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div className="text-sm text-muted-foreground">
|
<div className="text-sm text-muted-foreground">
|
||||||
|
|||||||
@@ -1092,7 +1092,10 @@
|
|||||||
"importSuccess": "Success",
|
"importSuccess": "Success",
|
||||||
"importSkipped": "Skipped (duplicate)",
|
"importSkipped": "Skipped (duplicate)",
|
||||||
"importFailed": "Failed",
|
"importFailed": "Failed",
|
||||||
"allDuplicate": "All configurations already exist, nothing imported"
|
"allDuplicate": "All configurations already exist, nothing imported",
|
||||||
|
"customJson": "Custom JSON Configuration",
|
||||||
|
"customJsonOptional": "Optional",
|
||||||
|
"customJsonNote": "Enter JSON to merge into adapter_config; clear input to remove all custom config"
|
||||||
},
|
},
|
||||||
"status": {
|
"status": {
|
||||||
"connected": "Connected",
|
"connected": "Connected",
|
||||||
|
|||||||
@@ -1017,7 +1017,10 @@
|
|||||||
"importSuccess": "成功",
|
"importSuccess": "成功",
|
||||||
"importSkipped": "跳过(重复)",
|
"importSkipped": "跳过(重复)",
|
||||||
"importFailed": "失败",
|
"importFailed": "失败",
|
||||||
"allDuplicate": "所有配置都已存在,未导入任何新配置"
|
"allDuplicate": "所有配置都已存在,未导入任何新配置",
|
||||||
|
"customJson": "自定义JSON配置",
|
||||||
|
"customJsonOptional": "可选",
|
||||||
|
"customJsonNote": "输入JSON将合并到adapter_config中;清空输入框将清除所有自定义配置"
|
||||||
},
|
},
|
||||||
"status": {
|
"status": {
|
||||||
"connected": "已连接",
|
"connected": "已连接",
|
||||||
|
|||||||
18
src/main.tsx
18
src/main.tsx
@@ -17,6 +17,24 @@ try {
|
|||||||
console.error("[Monaco] loader.config failed:", e);
|
console.error("[Monaco] loader.config failed:", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 全局捕获未处理的Promise拒绝,防止Monaco Editor错误
|
||||||
|
window.addEventListener('unhandledrejection', (event) => {
|
||||||
|
const error = event.reason;
|
||||||
|
if (error && error.message && error.message.includes('URL is not valid')) {
|
||||||
|
event.preventDefault();
|
||||||
|
console.warn('[Monaco] Suppressed URL validation error:', error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 全局捕获window.onerror
|
||||||
|
window.addEventListener('error', (event) => {
|
||||||
|
if (event.error && event.error.message && event.error.message.includes('URL is not valid')) {
|
||||||
|
console.warn('[Monaco] Suppressed URL validation error:', event.error);
|
||||||
|
event.preventDefault();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Initialize analytics before rendering (will no-op if no consent or no key)
|
// Initialize analytics before rendering (will no-op if no consent or no key)
|
||||||
analytics.initialize();
|
analytics.initialize();
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user