443 lines
25 KiB
JavaScript
443 lines
25 KiB
JavaScript
/**
|
||
* ChatbotModelSelectorUI 模型选择器界面渲染与交互逻辑
|
||
*
|
||
* 主要功能:
|
||
* 1. 渲染自定义模型选择界面,支持多模型、Batch模型、参数调节等。
|
||
* 2. 负责模型、温度、最大token、并发等参数的选择与本地存储。
|
||
* 3. 绑定所有相关交互事件(切换、参数同步、帮助提示、返回等)。
|
||
* 4. 支持参数的本地持久化与回显。
|
||
*/
|
||
|
||
/**
|
||
* XSS 防护:安全地转义 HTML(优先使用 ChatbotUtils,降级到本地实现)
|
||
* @param {string} str - 需要转义的字符串
|
||
* @returns {string} 转义后的安全字符串
|
||
*/
|
||
function safeEscapeHtmlForModel(str) {
|
||
if (typeof str !== 'string') return '';
|
||
if (typeof window.ChatbotUtils !== 'undefined' && typeof window.ChatbotUtils.escapeHtml === 'function') {
|
||
return window.ChatbotUtils.escapeHtml(str);
|
||
}
|
||
// 降级方案
|
||
return str.replace(/[&<>"']/g, function (c) {
|
||
return {'&':'&','<':'<','>':'>','"':'"','\'':'''}[c];
|
||
});
|
||
}
|
||
|
||
window.ChatbotModelSelectorUI = {
|
||
/**
|
||
* 渲染模型选择器主界面,并绑定所有交互事件。
|
||
*
|
||
* 主要逻辑:
|
||
* 1. 隐藏 preset 区和聊天区,显示模型选择器。
|
||
* 2. 渲染模型下拉、特殊模型、温度、并发、最大token等参数输入。
|
||
* 3. 绑定所有输入、切换、帮助、返回等事件。
|
||
* 4. 参数变更自动保存到 localStorage 或调用 saveSettings。
|
||
*
|
||
* @param {HTMLElement} mainContentArea - 主内容区容器。
|
||
* @param {HTMLElement} chatBody - 聊天内容区。
|
||
* @param {Array} availableModels - 可用模型列表。
|
||
* @param {object} currentSettings - 当前设置。
|
||
* @param {function} updateChatbotUI - UI刷新回调。
|
||
*/
|
||
render: function(mainContentArea, chatBody, availableModels, currentSettings, updateChatbotUI) {
|
||
// 隐藏预设和聊天区
|
||
const presetContainer = document.getElementById('chatbot-preset-container');
|
||
if (presetContainer) presetContainer.style.display = 'none';
|
||
if (chatBody) chatBody.style.display = 'none';
|
||
|
||
// 处理模型列表和默认选中
|
||
let models = availableModels;
|
||
if (!Array.isArray(models) || models.length === 0) models = [];
|
||
let settings = currentSettings;
|
||
let defaultModelId = settings.selectedCustomModelId || localStorage.getItem('lastSelectedCustomModel') || (models[0]?.id || models[0] || '');
|
||
|
||
// 移除已存在的选择器,防止重复渲染
|
||
let modelSelectorDiv = document.getElementById('chatbot-model-selector');
|
||
if (modelSelectorDiv) modelSelectorDiv.remove();
|
||
|
||
// 创建主容器
|
||
modelSelectorDiv = document.createElement('div');
|
||
modelSelectorDiv.id = 'chatbot-model-selector';
|
||
modelSelectorDiv.style.margin = '-30px auto 0 auto';
|
||
modelSelectorDiv.style.maxWidth = '340px';
|
||
modelSelectorDiv.style.background = 'linear-gradient(135deg,#f0f9ff 80%,#e0f2fe 100%)';
|
||
modelSelectorDiv.style.border = '2px dashed #93c5fd';
|
||
modelSelectorDiv.style.borderRadius = '16px';
|
||
modelSelectorDiv.style.padding = '20px 16px 16px 16px';
|
||
modelSelectorDiv.style.maxHeight = '100%';
|
||
modelSelectorDiv.style.overflowY = 'auto';
|
||
|
||
// 读取默认参数
|
||
let defaultTemperature = 0.5;
|
||
let defaultMaxTokens = 8000;
|
||
try {
|
||
if (settings.customModelSettings) {
|
||
defaultTemperature = typeof settings.customModelSettings.temperature === 'number' ? settings.customModelSettings.temperature : defaultTemperature;
|
||
defaultMaxTokens = typeof settings.customModelSettings.max_tokens === 'number' ? settings.customModelSettings.max_tokens : defaultMaxTokens;
|
||
}
|
||
} catch (e) {
|
||
console.error("Error accessing customModelSettings:", e);
|
||
}
|
||
const defaultConcurrency = (window.chatbotActiveOptions && Number.isInteger(window.chatbotActiveOptions.segmentConcurrency))
|
||
? window.chatbotActiveOptions.segmentConcurrency : 20;
|
||
|
||
// 主体HTML结构
|
||
modelSelectorDiv.innerHTML = `
|
||
<div style="text-align:center;font-size:17px;font-weight:700;color:#2563eb;margin-bottom:8px;">选择自定义模型</div>
|
||
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:12px;gap:8px;">
|
||
<span style="font-size:14px;font-weight:500;">默认模型</span>
|
||
<div style="display:flex;align-items:center;gap:6px;">
|
||
<button id="chatbot-detect-gemini-btn" title="检测 Gemini 可用模型" style="border:1px dashed #93c5fd;background:#eff6ff;color:#2563eb;border-radius:6px;padding:4px 8px;font-size:12px;">检测 Gemini</button>
|
||
<button id="chatbot-detect-deepseek-btn" title="检测 DeepSeek 可用模型" style="border:1px dashed #93c5fd;background:#eff6ff;color:#2563eb;border-radius:6px;padding:4px 8px;font-size:12px;">检测 DeepSeek</button>
|
||
<button id="chatbot-detect-tongyi-btn" title="检测 通义可用模型" style="border:1px dashed #93c5fd;background:#eff6ff;color:#2563eb;border-radius:6px;padding:4px 8px;font-size:12px;">检测 通义</button>
|
||
<button id="chatbot-add-special-models-btn" style="width:24px;height:24px;border:none;background:transparent;font-size:18px;cursor:pointer;line-height:18px;">+</button>
|
||
</div>
|
||
</div>
|
||
<div id="chatbot-special-models-container" style="display:none;flex-direction:column;gap:12px;margin-bottom:12px;">
|
||
<div>
|
||
<div style="font-size:14px;color:#1e3a8a;font-weight:500;margin-bottom:4px;">多模态模型</div>
|
||
<select id="chatbot-multimodal-model-select" style="width:100%;padding:6px 8px;border-radius:6px;border:1px solid #93c5fd;">
|
||
<option value="">使用默认模型</option>
|
||
${models.map(m => {
|
||
// XSS 防护:转义模型 ID 和名称
|
||
const modelId = typeof m === 'string' ? safeEscapeHtmlForModel(m) : safeEscapeHtmlForModel(m.id);
|
||
const modelName = typeof m === 'string' ? safeEscapeHtmlForModel(m) : safeEscapeHtmlForModel(m.name || m.id);
|
||
return `<option value="${modelId}">${modelName}</option>`;
|
||
}).join('')}
|
||
</select>
|
||
</div>
|
||
<div>
|
||
<div style="font-size:14px;color:#1e3a8a;font-weight:500;margin-bottom:4px;">Batch模型</div>
|
||
<select id="chatbot-batch-model-select" style="width:100%;padding:6px 8px;border-radius:6px;border:1px solid #93c5fd;">
|
||
<option value="">使用默认模型</option>
|
||
${models.map(m => {
|
||
// XSS 防护:转义模型 ID 和名称
|
||
const modelId = typeof m === 'string' ? safeEscapeHtmlForModel(m) : safeEscapeHtmlForModel(m.id);
|
||
const modelName = typeof m === 'string' ? safeEscapeHtmlForModel(m) : safeEscapeHtmlForModel(m.name || m.id);
|
||
return `<option value="${modelId}">${modelName}</option>`;
|
||
}).join('')}
|
||
</select>
|
||
</div>
|
||
</div>
|
||
<select id="chatbot-model-select" style="width:100%;margin-bottom:12px;padding:12px 16px;border-radius:10px;border:2px solid #93c5fd;background:white;color:#1e3a8a;font-size:15px;font-weight:600;outline:none;">
|
||
${models.length === 0 ? '<option value="">(无可用模型)</option>' : models.map(m => {
|
||
// XSS 防护:转义模型 ID 和名称
|
||
const modelId = typeof m === 'string' ? safeEscapeHtmlForModel(m) : safeEscapeHtmlForModel(m.id);
|
||
const modelName = typeof m === 'string' ? safeEscapeHtmlForModel(m) : safeEscapeHtmlForModel(m.name || m.id);
|
||
const rawId = typeof m === 'string' ? m : m.id;
|
||
const selected = rawId === defaultModelId ? ' selected' : '';
|
||
return `<option value="${modelId}"${selected}>${modelName}</option>`;
|
||
}).join('')}
|
||
</select>
|
||
<div style="display:flex;justify-content:space-between;gap:16px;margin:4px 0;">
|
||
<div style="flex:1;">
|
||
<div style="font-size:14px;color:#1e3a8a;font-weight:500;display:flex;align-items:center;justify-content:space-between;">
|
||
<div style="display:flex;align-items:center;gap:4px;">
|
||
<span>温度 (0-1)</span><button id="chatbot-temp-help-btn" style="background:none;border:none;color:#2563eb;cursor:pointer;display:flex;align-items:center;justify-content:center;"><i class="fa-solid fa-circle-info"></i></button>
|
||
</div>
|
||
<input id="chatbot-temp-input" type="number" min="0" max="1" step="0.01" value="${defaultTemperature}" style="width:50px;padding:2px;border-radius:4px;border:1px solid #93c5fd;font-size:14px;" />
|
||
</div>
|
||
<input id="chatbot-temp-range" type="range" min="0" max="1" step="0.01" value="${defaultTemperature}" style="width:100%;margin-top:4px;height:20px;" />
|
||
</div>
|
||
<div style="flex:1;">
|
||
<div style="font-size:14px;color:#1e3a8a;font-weight:500;display:flex;align-items:center;justify-content:space-between;">
|
||
<div style="display:flex;align-items:center;gap:4px;">
|
||
<span>并发上限</span><button id="chatbot-concurrency-help-btn" style="background:none;border:none;color:#2563eb;cursor:pointer;display:flex;align-items:center;justify-content:center;"><i class="fa-solid fa-circle-info"></i></button>
|
||
</div>
|
||
<input id="chatbot-concurrency-input" type="number" min="1" max="50" step="1" value="${defaultConcurrency}" style="width:50px;padding:2px;border-radius:4px;border:1px solid #93c5fd;font-size:14px;" />
|
||
</div>
|
||
<input id="chatbot-concurrency-range" type="range" min="1" max="50" step="1" value="${defaultConcurrency}" style="width:100%;margin-top:4px;height:20px;" />
|
||
</div>
|
||
</div>
|
||
<div style="margin-bottom:12px;">
|
||
<div style="display:flex;justify-content:space-between;font-size:14px;color:#1e3a8a;font-weight:500;">
|
||
<div style="display:flex;align-items:center;gap:4px;">
|
||
<span>回复长度</span><button id="chatbot-maxtokens-help-btn" style="background:none;border:none;color:#2563eb;cursor:pointer;display:flex;align-items:center;justify-content:center;"><i class="fa-solid fa-circle-info"></i></button>
|
||
</div>
|
||
<span style="font-size:12px;color:#64748b;">(max_tokens)</span>
|
||
</div>
|
||
<div style="display:flex;align-items:center;gap:8px;margin-top:4px;">
|
||
<input id="chatbot-maxtokens-range" type="range" min="256" max="32768" step="64" value="${defaultMaxTokens}" style="flex:1;height:24px;" />
|
||
<input id="chatbot-maxtokens-input" type="number" min="256" max="32768" step="1" value="${defaultMaxTokens}" style="width:70px;height:24px;padding:0 4px;border-radius:4px;border:1px solid #93c5fd;font-size:14px;" />
|
||
</div>
|
||
</div>
|
||
<button id="chatbot-model-back-btn" style="margin-top:8px;width:100%;padding:8px 0;font-size:15px;font-weight:600;background:linear-gradient(90deg,#3b82f6,#2563eb);color:white;border:none;border-radius:8px;cursor:pointer;transition:all 0.2s;">返回</button>
|
||
`;
|
||
|
||
mainContentArea.insertBefore(modelSelectorDiv, chatBody);
|
||
|
||
// 事件绑定区域
|
||
// 展开/收起特殊模型
|
||
const addSpecialBtn = modelSelectorDiv.querySelector('#chatbot-add-special-models-btn');
|
||
const specialContainer = modelSelectorDiv.querySelector('#chatbot-special-models-container');
|
||
if (addSpecialBtn && specialContainer) {
|
||
addSpecialBtn.onclick = () => {
|
||
if (specialContainer.style.display === 'none') {
|
||
specialContainer.style.display = 'flex';
|
||
addSpecialBtn.textContent = '-';
|
||
} else {
|
||
specialContainer.style.display = 'none';
|
||
addSpecialBtn.textContent = '+';
|
||
}
|
||
};
|
||
}
|
||
|
||
// 多模态模型选择
|
||
const multiSelect = modelSelectorDiv.querySelector('#chatbot-multimodal-model-select');
|
||
if (multiSelect) {
|
||
multiSelect.onchange = e => {
|
||
window.chatbotActiveOptions.multimodalModel = e.target.value;
|
||
let settings = {};
|
||
try { settings = typeof loadSettings === 'function' ? loadSettings() : JSON.parse(localStorage.getItem('paperBurnerSettings') || '{}'); } catch {}
|
||
settings.multimodalModel = e.target.value;
|
||
if (typeof saveSettings === 'function') {
|
||
saveSettings(settings);
|
||
} else {
|
||
localStorage.setItem('paperBurnerSettings', JSON.stringify(settings));
|
||
}
|
||
};
|
||
let userSettings = {};
|
||
try { userSettings = typeof loadSettings === 'function' ? loadSettings() : JSON.parse(localStorage.getItem('paperBurnerSettings') || '{}'); } catch {}
|
||
multiSelect.value = userSettings.multimodalModel || '';
|
||
window.chatbotActiveOptions.multimodalModel = userSettings.multimodalModel || '';
|
||
}
|
||
|
||
// Batch模型选择
|
||
const batchSelect = modelSelectorDiv.querySelector('#chatbot-batch-model-select');
|
||
if (batchSelect) {
|
||
batchSelect.onchange = e => {
|
||
window.chatbotActiveOptions.batchModel = e.target.value;
|
||
let settings = {};
|
||
try { settings = typeof loadSettings === 'function' ? loadSettings() : JSON.parse(localStorage.getItem('paperBurnerSettings') || '{}'); } catch {}
|
||
settings.batchModel = e.target.value;
|
||
if (typeof saveSettings === 'function') {
|
||
saveSettings(settings);
|
||
} else {
|
||
localStorage.setItem('paperBurnerSettings', JSON.stringify(settings));
|
||
}
|
||
};
|
||
let userSettings = {};
|
||
try { userSettings = typeof loadSettings === 'function' ? loadSettings() : JSON.parse(localStorage.getItem('paperBurnerSettings') || '{}'); } catch {}
|
||
batchSelect.value = userSettings.batchModel || '';
|
||
window.chatbotActiveOptions.batchModel = userSettings.batchModel || '';
|
||
}
|
||
|
||
// 并发参数绑定
|
||
const concurrencyInput = modelSelectorDiv.querySelector('#chatbot-concurrency-input');
|
||
const concurrencyRange = modelSelectorDiv.querySelector('#chatbot-concurrency-range');
|
||
function saveConcurrency() {
|
||
let v = parseInt(concurrencyInput.value);
|
||
if (isNaN(v) || v < 1) v = 1;
|
||
if (v > 50) v = 50;
|
||
concurrencyInput.value = v;
|
||
concurrencyRange.value = v;
|
||
window.chatbotActiveOptions.segmentConcurrency = v;
|
||
let settings = {};
|
||
try { settings = typeof loadSettings === 'function' ? loadSettings() : JSON.parse(localStorage.getItem('paperBurnerSettings') || '{}'); } catch {};
|
||
settings.segmentConcurrency = v;
|
||
if (typeof saveSettings === 'function') saveSettings(settings);
|
||
else localStorage.setItem('paperBurnerSettings', JSON.stringify(settings));
|
||
}
|
||
if (concurrencyInput && concurrencyRange) {
|
||
concurrencyInput.oninput = saveConcurrency;
|
||
concurrencyRange.oninput = saveConcurrency; // Should be oninput or onchange, not saveConcurrency directly
|
||
concurrencyRange.oninput = () => { concurrencyInput.value = concurrencyRange.value; saveConcurrency(); };
|
||
concurrencyInput.oninput = () => { concurrencyRange.value = concurrencyInput.value; saveConcurrency(); };
|
||
}
|
||
|
||
// 帮助按钮
|
||
const tempHelpBtn = modelSelectorDiv.querySelector('#chatbot-temp-help-btn');
|
||
if (tempHelpBtn) tempHelpBtn.onclick = () => ChatbotUtils.showToast('温度:调节模型生成的随机性,0表示最确定,1表示最随机', 'info', 3000);
|
||
const concurrencyHelpBtn = modelSelectorDiv.querySelector('#chatbot-concurrency-help-btn');
|
||
if (concurrencyHelpBtn) concurrencyHelpBtn.onclick = () => ChatbotUtils.showToast('并发上限:控制同时处理分段的最大并发请求数', 'info', 3000);
|
||
const maxTokensHelpBtn = modelSelectorDiv.querySelector('#chatbot-maxtokens-help-btn');
|
||
if (maxTokensHelpBtn) maxTokensHelpBtn.onclick = () => ChatbotUtils.showToast('回复长度:模型最大输出的token数量', 'info', 3000);
|
||
|
||
// 主模型选择
|
||
const select = document.getElementById('chatbot-model-select');
|
||
if (select) {
|
||
select.onchange = function() {
|
||
localStorage.setItem('lastSelectedCustomModel', this.value);
|
||
let settings = {};
|
||
try {
|
||
settings = typeof loadSettings === 'function' ? loadSettings() : JSON.parse(localStorage.getItem('paperBurnerSettings') || '{}');
|
||
} catch (e) {}
|
||
settings.selectedCustomModelId = this.value;
|
||
if (typeof saveSettings === 'function') {
|
||
saveSettings(settings);
|
||
} else {
|
||
localStorage.setItem('paperBurnerSettings', JSON.stringify(settings));
|
||
}
|
||
};
|
||
}
|
||
|
||
// 工具:将新模型ID列表追加到主下拉
|
||
async function appendModelsToMainSelect(ids) {
|
||
const mainSelect = document.getElementById('chatbot-model-select');
|
||
if (!mainSelect || !Array.isArray(ids)) return 0;
|
||
const existing = new Set(Array.from(mainSelect.options).map(o => o.value));
|
||
let added = 0;
|
||
ids.forEach(id => {
|
||
const mid = String(id || '').trim();
|
||
if (!mid || existing.has(mid)) return;
|
||
const opt = document.createElement('option');
|
||
opt.value = mid; opt.textContent = mid;
|
||
mainSelect.appendChild(opt);
|
||
added++;
|
||
});
|
||
return added;
|
||
}
|
||
|
||
// 检测 Gemini 可用模型并填充列表(仅当当前选择或默认选择是 Gemini 时生效)
|
||
const detectGeminiBtn = modelSelectorDiv.querySelector('#chatbot-detect-gemini-btn');
|
||
if (detectGeminiBtn) {
|
||
detectGeminiBtn.onclick = async () => {
|
||
try {
|
||
// 仅在可用 Key 存在时进行
|
||
const keys = (typeof loadModelKeys === 'function') ? (loadModelKeys('gemini') || []) : [];
|
||
const usableKeys = keys.filter(k => k.status !== 'invalid' && k.value);
|
||
if (usableKeys.length === 0) {
|
||
return ChatbotUtils.showToast('请先在模型与Key管理中配置 Gemini 的 API Key', 'warning', 3000);
|
||
}
|
||
const apiKey = usableKeys[0].value.trim();
|
||
detectGeminiBtn.disabled = true;
|
||
detectGeminiBtn.textContent = '检测中...';
|
||
const resp = await fetch(`https://generativelanguage.googleapis.com/v1beta/models?key=${encodeURIComponent(apiKey)}`);
|
||
if (!resp.ok) throw new Error(`${resp.status} ${resp.statusText}`);
|
||
const data = await resp.json();
|
||
const items = Array.isArray(data.models || data.data) ? (data.models || data.data) : [];
|
||
if (items.length === 0) {
|
||
return ChatbotUtils.showToast('未返回模型列表', 'info', 3000);
|
||
}
|
||
const ids = items.map(m => (m.name ? String(m.name).split('/').pop() : (m.id || ''))).filter(Boolean);
|
||
const added = await appendModelsToMainSelect(ids);
|
||
if (added > 0) ChatbotUtils.showToast(`已加入 ${added} 个 Gemini 模型到列表`, 'success', 3000);
|
||
} catch (e) {
|
||
ChatbotUtils.showToast(`检测失败:${e.message}`, 'error', 3000);
|
||
} finally {
|
||
detectGeminiBtn.disabled = false;
|
||
detectGeminiBtn.textContent = '检测 Gemini';
|
||
}
|
||
};
|
||
}
|
||
|
||
// 检测 DeepSeek 可用模型
|
||
const detectDeepseekBtn = modelSelectorDiv.querySelector('#chatbot-detect-deepseek-btn');
|
||
if (detectDeepseekBtn) {
|
||
detectDeepseekBtn.onclick = async () => {
|
||
try {
|
||
const keys = (typeof loadModelKeys === 'function') ? (loadModelKeys('deepseek') || []) : [];
|
||
const usable = keys.filter(k => k.status !== 'invalid' && k.value);
|
||
if (usable.length === 0) {
|
||
return ChatbotUtils.showToast('请先配置 DeepSeek 的 API Key', 'warning', 3000);
|
||
}
|
||
const apiKey = usable[0].value.trim();
|
||
detectDeepseekBtn.disabled = true; detectDeepseekBtn.textContent = '检测中...';
|
||
const resp = await fetch('https://api.deepseek.com/v1/models', { headers: { 'Authorization': `Bearer ${apiKey}` } });
|
||
if (!resp.ok) throw new Error(`${resp.status} ${resp.statusText}`);
|
||
const data = await resp.json();
|
||
const items = Array.isArray(data.data) ? data.data : [];
|
||
const ids = items.map(m => m.id).filter(Boolean);
|
||
const added = await appendModelsToMainSelect(ids);
|
||
ChatbotUtils.showToast(added > 0 ? `已加入 ${added} 个 DeepSeek 模型到列表` : '未返回模型列表', added > 0 ? 'success' : 'info', 3000);
|
||
} catch (e) {
|
||
ChatbotUtils.showToast(`检测失败:${e.message}`, 'error', 3000);
|
||
} finally {
|
||
detectDeepseekBtn.disabled = false; detectDeepseekBtn.textContent = '检测 DeepSeek';
|
||
}
|
||
};
|
||
}
|
||
|
||
// 检测 通义(DashScope)可用模型
|
||
const detectTongyiBtn = modelSelectorDiv.querySelector('#chatbot-detect-tongyi-btn');
|
||
if (detectTongyiBtn) {
|
||
detectTongyiBtn.onclick = async () => {
|
||
try {
|
||
// 统一使用 'tongyi' 的 Key 列表
|
||
let keys = [];
|
||
if (typeof loadModelKeys === 'function') {
|
||
keys = loadModelKeys('tongyi') || [];
|
||
}
|
||
const usable = keys.filter(k => k.status !== 'invalid' && k.value);
|
||
if (usable.length === 0) {
|
||
return ChatbotUtils.showToast('请先配置 通义 的 API Key', 'warning', 3000);
|
||
}
|
||
const apiKey = usable[0].value.trim();
|
||
detectTongyiBtn.disabled = true; detectTongyiBtn.textContent = '检测中...';
|
||
// 使用 OpenAI 兼容模式的模型列表端点(用户指定)
|
||
const resp = await fetch('https://dashscope.aliyuncs.com/compatible-mode/v1/models', { headers: { 'Authorization': `Bearer ${apiKey}` } });
|
||
if (!resp.ok) throw new Error(`${resp.status} ${resp.statusText}`);
|
||
const data = await resp.json();
|
||
const items = Array.isArray(data.data) ? data.data : (Array.isArray(data.models) ? data.models : (Array.isArray(data?.data?.models) ? data.data.models : []));
|
||
const ids = items.map(m => (m.model || m.id || m.name)).filter(Boolean);
|
||
const added = await appendModelsToMainSelect(ids);
|
||
ChatbotUtils.showToast(added > 0 ? `已加入 ${added} 个 通义 模型到列表` : '未返回模型列表', added > 0 ? 'success' : 'info', 3000);
|
||
} catch (e) {
|
||
ChatbotUtils.showToast(`检测失败:${e.message}`, 'error', 3000);
|
||
} finally {
|
||
detectTongyiBtn.disabled = false; detectTongyiBtn.textContent = '检测 通义';
|
||
}
|
||
};
|
||
}
|
||
|
||
// 检测 火山(Ark)可用模型
|
||
const detectVolcanoBtn = modelSelectorDiv.querySelector('#chatbot-detect-volcano-btn');
|
||
if (detectVolcanoBtn) {
|
||
// 按用户要求:不提供在线检测,改为手动填写
|
||
detectVolcanoBtn.style.display = 'none';
|
||
}
|
||
|
||
// 温度、最大token参数绑定
|
||
const tempInput = document.getElementById('chatbot-temp-input');
|
||
const tempRange = document.getElementById('chatbot-temp-range');
|
||
const maxTokensInput = document.getElementById('chatbot-maxtokens-input');
|
||
const maxTokensRange = document.getElementById('chatbot-maxtokens-range');
|
||
|
||
function saveCustomModelParams() {
|
||
let settings = {};
|
||
try {
|
||
settings = typeof loadSettings === 'function' ? loadSettings() : JSON.parse(localStorage.getItem('paperBurnerSettings') || '{}');
|
||
} catch (e) {}
|
||
if (!settings.customModelSettings) settings.customModelSettings = {};
|
||
let t = parseFloat(tempInput.value);
|
||
if (isNaN(t) || t < 0) t = 0;
|
||
if (t > 1) t = 1;
|
||
let m = parseInt(maxTokensInput.value);
|
||
if (isNaN(m) || m < 256) m = 256;
|
||
if (m > 32768) m = 32768;
|
||
tempInput.value = t;
|
||
tempRange.value = t;
|
||
maxTokensInput.value = m;
|
||
maxTokensRange.value = m;
|
||
settings.customModelSettings.temperature = t;
|
||
settings.customModelSettings.max_tokens = m;
|
||
if (typeof saveSettings === 'function') {
|
||
saveSettings(settings);
|
||
} else {
|
||
localStorage.setItem('paperBurnerSettings', JSON.stringify(settings));
|
||
}
|
||
}
|
||
|
||
if (tempInput && tempRange) {
|
||
tempInput.oninput = function() { tempRange.value = tempInput.value; saveCustomModelParams(); };
|
||
tempRange.oninput = function() { tempInput.value = tempRange.value; saveCustomModelParams(); };
|
||
}
|
||
if (maxTokensInput && maxTokensRange) {
|
||
maxTokensInput.oninput = function() { maxTokensRange.value = maxTokensInput.value; saveCustomModelParams(); };
|
||
maxTokensRange.oninput = function() { maxTokensInput.value = maxTokensRange.value; saveCustomModelParams(); };
|
||
}
|
||
|
||
// 返回按钮
|
||
const backBtn = document.getElementById('chatbot-model-back-btn');
|
||
if (backBtn) {
|
||
backBtn.onclick = function() {
|
||
window.isModelSelectorOpen = false;
|
||
// 清除配置缓存,以便下次获取最新配置
|
||
window._cachedChatbotConfig = null;
|
||
updateChatbotUI();
|
||
};
|
||
}
|
||
}
|
||
};
|