mirror of
https://github.com/Ed1s0nZ/CyberStrikeAI.git
synced 2026-08-29 06:00:52 +02:00
fix: auto-detect deepseek profile in settings
This commit is contained in:
@@ -51,3 +51,12 @@ test('Eino 模型 retry/failover 设置项有中英文文案', () => {
|
|||||||
assert.ok(en.settingsBasic[key].length > 0, `en ${key} is empty`);
|
assert.ok(en.settingsBasic[key].length > 0, `en ${key} is empty`);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('AI 通道保存前会自动识别 DeepSeek 官方线路', () => {
|
||||||
|
assert.match(settings, /function\s+isOfficialDeepSeekBaseURL/);
|
||||||
|
assert.match(settings, /api\.deepseek\.com/);
|
||||||
|
assert.match(settings, /profile:\s*'deepseek'/);
|
||||||
|
assert.match(settings, /normalizeAIChannelProviderProfile\(\{/);
|
||||||
|
assert.match(settings, /normalizeAIConfigProviderProfiles\(currentConfig\.ai\)/);
|
||||||
|
assert.match(template, /<option value="deepseek">deepseek<\/option>/);
|
||||||
|
});
|
||||||
|
|||||||
@@ -1985,6 +1985,7 @@ async function applySettings() {
|
|||||||
const activeChannelId = normalizeAIChannelId(selectedAIChannelId || currentConfig.ai.default_channel || 'default');
|
const activeChannelId = normalizeAIChannelId(selectedAIChannelId || currentConfig.ai.default_channel || 'default');
|
||||||
currentConfig.ai.channels[activeChannelId] = readAIChannelFromMainForm(activeChannelId);
|
currentConfig.ai.channels[activeChannelId] = readAIChannelFromMainForm(activeChannelId);
|
||||||
currentConfig.ai.default_channel = activeChannelId;
|
currentConfig.ai.default_channel = activeChannelId;
|
||||||
|
currentConfig.ai = normalizeAIConfigProviderProfiles(currentConfig.ai);
|
||||||
renderAIChannelSelect();
|
renderAIChannelSelect();
|
||||||
const activeChannel = currentConfig.ai.channels[activeChannelId] || {};
|
const activeChannel = currentConfig.ai.channels[activeChannelId] || {};
|
||||||
const prevOpenai = activeChannel;
|
const prevOpenai = activeChannel;
|
||||||
@@ -1999,7 +2000,7 @@ async function applySettings() {
|
|||||||
return String(s || '').split(/[\n,,]/).map(v => v.trim()).filter(Boolean);
|
return String(s || '').split(/[\n,,]/).map(v => v.trim()).filter(Boolean);
|
||||||
};
|
};
|
||||||
const config = {
|
const config = {
|
||||||
ai: currentConfig.ai,
|
ai: normalizeAIConfigProviderProfiles(currentConfig.ai),
|
||||||
vision: visionPayload,
|
vision: visionPayload,
|
||||||
fofa: {
|
fofa: {
|
||||||
api_key: document.getElementById('fofa-api-key')?.value.trim() || '',
|
api_key: document.getElementById('fofa-api-key')?.value.trim() || '',
|
||||||
@@ -2533,6 +2534,43 @@ function normalizeAIChannelId(name) {
|
|||||||
return id || 'default';
|
return id || 'default';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function aiChannelBaseURLHost(baseUrl) {
|
||||||
|
const raw = String(baseUrl || '').trim();
|
||||||
|
if (!raw) return '';
|
||||||
|
try {
|
||||||
|
return new URL(raw).hostname.toLowerCase().replace(/^www\./, '');
|
||||||
|
} catch (e) {
|
||||||
|
try {
|
||||||
|
return new URL(`https://${raw.replace(/^\/+/, '')}`).hostname.toLowerCase().replace(/^www\./, '');
|
||||||
|
} catch (_) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function isOfficialDeepSeekBaseURL(baseUrl) {
|
||||||
|
return aiChannelBaseURLHost(baseUrl) === 'api.deepseek.com';
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeAIChannelProviderProfile(channel) {
|
||||||
|
if (!channel || typeof channel !== 'object') return channel;
|
||||||
|
if (isOfficialDeepSeekBaseURL(channel.base_url)) {
|
||||||
|
channel.reasoning = {
|
||||||
|
...(channel.reasoning || {}),
|
||||||
|
profile: 'deepseek'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return channel;
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeAIConfigProviderProfiles(ai) {
|
||||||
|
if (!ai || typeof ai !== 'object' || !ai.channels || typeof ai.channels !== 'object') return ai;
|
||||||
|
Object.keys(ai.channels).forEach((id) => {
|
||||||
|
ai.channels[id] = normalizeAIChannelProviderProfile(ai.channels[id] || {});
|
||||||
|
});
|
||||||
|
return ai;
|
||||||
|
}
|
||||||
|
|
||||||
function escapeAIChannelHtml(value) {
|
function escapeAIChannelHtml(value) {
|
||||||
return String(value == null ? '' : value)
|
return String(value == null ? '' : value)
|
||||||
.replace(/&/g, '&')
|
.replace(/&/g, '&')
|
||||||
@@ -2559,13 +2597,13 @@ function ensureAIConfigShape(cfg) {
|
|||||||
reasoning: oa.reasoning || {}
|
reasoning: oa.reasoning || {}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return { default_channel: def, channels };
|
return normalizeAIConfigProviderProfiles({ default_channel: def, channels });
|
||||||
}
|
}
|
||||||
|
|
||||||
function readAIChannelFromMainForm(id) {
|
function readAIChannelFromMainForm(id) {
|
||||||
const prev = currentConfig?.ai?.channels?.[id] || {};
|
const prev = currentConfig?.ai?.channels?.[id] || {};
|
||||||
const maxCompletionTokens = parseInt(document.getElementById('openai-max-completion-tokens')?.value, 10) || 32768;
|
const maxCompletionTokens = parseInt(document.getElementById('openai-max-completion-tokens')?.value, 10) || 32768;
|
||||||
return {
|
return normalizeAIChannelProviderProfile({
|
||||||
...prev,
|
...prev,
|
||||||
name: (document.getElementById('ai-channel-name')?.value || '').trim() || prev.name || id,
|
name: (document.getElementById('ai-channel-name')?.value || '').trim() || prev.name || id,
|
||||||
provider: document.getElementById('openai-provider')?.value || 'openai',
|
provider: document.getElementById('openai-provider')?.value || 'openai',
|
||||||
@@ -2581,7 +2619,7 @@ function readAIChannelFromMainForm(id) {
|
|||||||
profile: document.getElementById('openai-reasoning-profile')?.value || 'auto',
|
profile: document.getElementById('openai-reasoning-profile')?.value || 'auto',
|
||||||
allow_client_reasoning: document.getElementById('openai-reasoning-allow-client')?.checked !== false
|
allow_client_reasoning: document.getElementById('openai-reasoning-allow-client')?.checked !== false
|
||||||
}
|
}
|
||||||
};
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function writeAIChannelToMainForm(id) {
|
function writeAIChannelToMainForm(id) {
|
||||||
@@ -2611,7 +2649,7 @@ function writeAIChannelToMainForm(id) {
|
|||||||
const effEl = document.getElementById('openai-reasoning-effort');
|
const effEl = document.getElementById('openai-reasoning-effort');
|
||||||
if (effEl) effEl.value = ['', 'low', 'medium', 'high', 'max', 'xhigh'].includes(String(r.effort || '').toLowerCase()) ? String(r.effort || '').toLowerCase() : '';
|
if (effEl) effEl.value = ['', 'low', 'medium', 'high', 'max', 'xhigh'].includes(String(r.effort || '').toLowerCase()) ? String(r.effort || '').toLowerCase() : '';
|
||||||
const profileEl = document.getElementById('openai-reasoning-profile');
|
const profileEl = document.getElementById('openai-reasoning-profile');
|
||||||
if (profileEl) profileEl.value = ['auto', 'deepseek_compat', 'openai_compat', 'output_config_effort'].includes(String(r.profile || '').toLowerCase()) ? String(r.profile || '').toLowerCase() : 'auto';
|
if (profileEl) profileEl.value = ['auto', 'deepseek', 'deepseek_compat', 'openai_compat', 'output_config_effort'].includes(String(r.profile || '').toLowerCase()) ? String(r.profile || '').toLowerCase() : 'auto';
|
||||||
const allowEl = document.getElementById('openai-reasoning-allow-client');
|
const allowEl = document.getElementById('openai-reasoning-allow-client');
|
||||||
if (allowEl) allowEl.checked = r.allow_client_reasoning !== false;
|
if (allowEl) allowEl.checked = r.allow_client_reasoning !== false;
|
||||||
syncModelListFetchButtons();
|
syncModelListFetchButtons();
|
||||||
@@ -2943,6 +2981,7 @@ async function persistAIChannelsToServer(successMessage, options = {}) {
|
|||||||
currentConfig.ai.default_channel = latestAI.default_channel || id;
|
currentConfig.ai.default_channel = latestAI.default_channel || id;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
currentConfig.ai = normalizeAIConfigProviderProfiles(currentConfig.ai);
|
||||||
const updateResponse = await apiFetch('/api/config', {
|
const updateResponse = await apiFetch('/api/config', {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
@@ -2971,6 +3010,7 @@ async function persistAIConfigOnlyToServer(successMessage) {
|
|||||||
if (typeof requirePermission === 'function' && !requirePermission('config:write')) return false;
|
if (typeof requirePermission === 'function' && !requirePermission('config:write')) return false;
|
||||||
if (!currentConfig) return false;
|
if (!currentConfig) return false;
|
||||||
currentConfig.ai = ensureAIConfigShape(currentConfig);
|
currentConfig.ai = ensureAIConfigShape(currentConfig);
|
||||||
|
currentConfig.ai = normalizeAIConfigProviderProfiles(currentConfig.ai);
|
||||||
showAIChannelSaveHint(settingsT('settingsBasic.aiChannelSaving', '正在保存通道...'), true);
|
showAIChannelSaveHint(settingsT('settingsBasic.aiChannelSaving', '正在保存通道...'), true);
|
||||||
try {
|
try {
|
||||||
const updateResponse = await apiFetch('/api/config', {
|
const updateResponse = await apiFetch('/api/config', {
|
||||||
|
|||||||
@@ -3695,6 +3695,7 @@
|
|||||||
<label for="openai-reasoning-profile" data-i18n="settingsBasic.openaiReasoningProfile">线路</label>
|
<label for="openai-reasoning-profile" data-i18n="settingsBasic.openaiReasoningProfile">线路</label>
|
||||||
<select id="openai-reasoning-profile">
|
<select id="openai-reasoning-profile">
|
||||||
<option value="auto">auto</option>
|
<option value="auto">auto</option>
|
||||||
|
<option value="deepseek">deepseek</option>
|
||||||
<option value="deepseek_compat">deepseek_compat</option>
|
<option value="deepseek_compat">deepseek_compat</option>
|
||||||
<option value="openai_compat">openai_compat</option>
|
<option value="openai_compat">openai_compat</option>
|
||||||
<option value="output_config_effort">output_config_effort</option>
|
<option value="output_config_effort">output_config_effort</option>
|
||||||
|
|||||||
Reference in New Issue
Block a user