/** * UI 嵌入与重排配置渲染模块 * 提取嵌入模型(Embedding)和重排(Rerank)的配置界面渲染代码 */ (function(window) { 'use strict'; // 确保 EmbeddingClient 已加载(必要时动态注入脚本) async function ensureEmbeddingClientLoaded() { if (window.EmbeddingClient && typeof window.EmbeddingClient.saveConfig === 'function') return true; // 从已加载脚本推断候选路径 const candidates = []; try { const scripts = Array.from(document.getElementsByTagName('script')); const sem = scripts.find(s => (s.src || '').includes('semantic-vector-search.js')); if (sem && sem.src) candidates.push(sem.src.replace('semantic-vector-search.js', 'embedding-client.js')); const rer = scripts.find(s => (s.src || '').includes('rerank-client.js')); if (rer && rer.src) candidates.push(rer.src.replace('rerank-client.js', 'embedding-client.js')); } catch(_) {} // 兜底:相对当前页面常见路径 candidates.push('js/chatbot/agents/embedding-client.js'); // 动态加载(无论是否已存在旧标签,均追加一个带缓存破坏参数的标签) for (const base of Array.from(new Set(candidates))) { const url = base + (base.includes('?') ? '&' : '?') + 'v=' + Date.now(); try { await new Promise((resolve, reject) => { const s = document.createElement('script'); s.src = url; s.async = true; s.onload = () => resolve(); s.onerror = () => reject(new Error('load failed: ' + url)); document.head.appendChild(s); }); if (window.EmbeddingClient && typeof window.EmbeddingClient.saveConfig === 'function') return true; } catch(_) { // try next } } return !!(window.EmbeddingClient && typeof window.EmbeddingClient.saveConfig === 'function'); } /** * 显示嵌入模型选择器对话框 * @param {Array} models - 模型列表 * @param {HTMLInputElement} targetInput - 目标输入框 */ function showEmbeddingModelSelector(models, targetInput) { // 创建一个简单的选择对话框 const container = document.createElement('div'); container.style.cssText = ` position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 400px; max-width: 90vw; max-height: 60vh; background: #fff; border-radius: 12px; box-shadow: 0 20px 60px rgba(0,0,0,0.4); z-index: 100002; padding: 0; overflow: hidden; `; const header = document.createElement('div'); header.style.cssText = 'padding: 16px 20px; border-bottom: 1px solid #e5e7eb; display: flex; justify-content: space-between; align-items: center;'; header.innerHTML = `

选择嵌入模型

`; const list = document.createElement('div'); list.style.cssText = 'max-height: 400px; overflow-y: auto; padding: 8px;'; models.forEach(model => { const item = document.createElement('div'); item.style.cssText = ` padding: 12px 16px; margin: 4px 0; border-radius: 8px; cursor: pointer; transition: all 0.2s; border: 1px solid #e5e7eb; `; item.innerHTML = `
${model.id}
${model.owned_by ? `
by ${model.owned_by}
` : ''} `; item.onmouseover = () => { item.style.background = '#f3f4f6'; item.style.borderColor = '#3b82f6'; }; item.onmouseout = () => { item.style.background = '#fff'; item.style.borderColor = '#e5e7eb'; }; item.onclick = () => { targetInput.value = model.id; document.body.removeChild(overlay); document.body.removeChild(container); }; list.appendChild(item); }); container.appendChild(header); container.appendChild(list); const overlay = document.createElement('div'); overlay.style.cssText = ` position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0, 0, 0, 0.5); z-index: 100001; `; const closeHandler = () => { document.body.removeChild(overlay); document.body.removeChild(container); }; overlay.onclick = closeHandler; header.querySelector('.model-selector-close').onclick = closeHandler; document.body.appendChild(overlay); document.body.appendChild(container); } /** * 显示重排模型选择器对话框 * @param {Array} models - 模型列表 * @param {HTMLInputElement} targetInput - 目标输入框 */ function showRerankModelSelector(models, targetInput) { const container = document.createElement('div'); container.style.cssText = ` position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 420px; max-width: 92vw; max-height: 60vh; background: #fff; border-radius: 12px; box-shadow: 0 20px 60px rgba(0,0,0,0.4); z-index: 100002; padding: 0; overflow: hidden; `; const header = document.createElement('div'); header.style.cssText = 'padding: 16px 20px; border-bottom: 1px solid #e5e7eb; display: flex; justify-content: space-between; align-items: center;'; header.innerHTML = `

选择重排模型

`; const list = document.createElement('div'); list.style.cssText = 'max-height: 400px; overflow-y: auto; padding: 8px;'; (models || []).forEach(model => { const item = document.createElement('div'); item.style.cssText = ` padding: 12px 16px; margin: 4px 0; border-radius: 8px; cursor: pointer; transition: all 0.2s; border: 1px solid #e5e7eb; `; const id = model.id || model.name || ''; item.innerHTML = `
${id}
${model.owned_by ? `
by ${model.owned_by}
` : ''} `; item.onmouseover = () => { item.style.background = '#f3f4f6'; item.style.borderColor = '#737373'; }; item.onmouseout = () => { item.style.background = '#fff'; item.style.borderColor = '#e5e7eb'; }; item.onclick = () => { if (id) targetInput.value = id; document.body.removeChild(overlay); document.body.removeChild(container); }; list.appendChild(item); }); container.appendChild(header); container.appendChild(list); const overlay = document.createElement('div'); overlay.style.cssText = ` position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0, 0, 0, 0.5); z-index: 100001; `; const closeHandler = () => { document.body.removeChild(overlay); document.body.removeChild(container); }; overlay.onclick = closeHandler; header.querySelector('.model-selector-close').onclick = closeHandler; document.body.appendChild(overlay); document.body.appendChild(container); } /** * 渲染嵌入模型配置界面(包含向量搜索和重排两个tab) * @param {HTMLElement} container - 配置容器元素(modelConfigColumn) */ function renderEmbeddingConfig(container) { // 从localStorage加载配置 const config = window.EmbeddingClient?.config || {}; const rerankConfig = window.RerankClient?.config || {}; const PRESETS = { openai: { name: 'OpenAI格式', endpoint: 'https://api.openai.com/v1/embeddings' }, jina: { name: 'Jina AI', endpoint: 'https://api.jina.ai/v1/embeddings' }, zhipu: { name: '智谱AI', endpoint: 'https://open.bigmodel.cn/api/paas/v4/embeddings' }, alibaba: { name: '阿里云百炼', endpoint: 'https://dashscope.aliyuncs.com/compatible-mode/v1/embeddings' } }; // 阿里云百炼支持的模型和维度 const ALIBABA_MODELS = { 'text-embedding-v1': { name: 'text-embedding-v1 (中文)', dims: 1536 }, 'text-embedding-v2': { name: 'text-embedding-v2 (多语言)', dims: 1536 }, 'text-embedding-v3': { name: 'text-embedding-v3 (高性能)', dims: 1024 }, 'text-embedding-v4': { name: 'text-embedding-v4 (多语言,支持2048维)', dims: 2048 } }; const mainContainer = document.createElement('div'); // Tabs(样式更内敛) const tabsDiv = document.createElement('div'); tabsDiv.className = 'flex border-b border-gray-200 mb-4'; tabsDiv.innerHTML = ` `; mainContainer.appendChild(tabsDiv); // 向量搜索Tab内容 const vectorContainer = document.createElement('div'); vectorContainer.id = 'emb-km-vector-content'; vectorContainer.className = 'emb-km-tab-content space-y-4'; // 启用开关 const enabledDiv = document.createElement('div'); enabledDiv.className = 'flex items-center gap-2'; enabledDiv.innerHTML = ` `; vectorContainer.appendChild(enabledDiv); // 服务商选择 const providerDiv = document.createElement('div'); providerDiv.innerHTML = ` `; vectorContainer.appendChild(providerDiv); // API Key(带显示/隐藏按钮) const keyDiv = document.createElement('div'); keyDiv.innerHTML = `
`; vectorContainer.appendChild(keyDiv); // Base URL const urlDiv = document.createElement('div'); // 显示时去掉 /embeddings 后缀 const displayUrl = (config.endpoint || '').replace(/\/embeddings\/?$/, ''); urlDiv.innerHTML = ` `; vectorContainer.appendChild(urlDiv); // 模型选择 const modelDiv = document.createElement('div'); modelDiv.innerHTML = `

请输入服务商支持的嵌入模型ID

`; vectorContainer.appendChild(modelDiv); // 向量维度 (OpenAI可选) const dimsDiv = document.createElement('div'); dimsDiv.id = 'emb-dims-wrap-km'; dimsDiv.innerHTML = `

降低维度可减少存储和计算,但可能影响精度

`; vectorContainer.appendChild(dimsDiv); // 并发数配置 const concurrencyDiv = document.createElement('div'); concurrencyDiv.innerHTML = `

提高并发数可加快索引构建速度,但注意API速率限制

`; vectorContainer.appendChild(concurrencyDiv); // 测试和保存按钮 const buttonsDiv = document.createElement('div'); buttonsDiv.className = 'flex gap-3 pt-2'; buttonsDiv.innerHTML = ` `; vectorContainer.appendChild(buttonsDiv); // 测试结果 const resultDiv = document.createElement('div'); resultDiv.id = 'emb-test-result-km'; resultDiv.className = 'text-sm mt-2'; resultDiv.style.display = 'none'; vectorContainer.appendChild(resultDiv); mainContainer.appendChild(vectorContainer); // 重排Tab内容 const rerankContainer = document.createElement('div'); rerankContainer.id = 'emb-km-rerank-content'; rerankContainer.className = 'emb-km-tab-content space-y-4 hidden'; // 重排启用开关 const rerankEnabledDiv = document.createElement('div'); rerankEnabledDiv.className = 'flex items-center gap-2'; rerankEnabledDiv.innerHTML = ` `; rerankContainer.appendChild(rerankEnabledDiv); // 应用范围 const rerankScopeDiv = document.createElement('div'); const scope = rerankConfig.scope || 'vector-only'; rerankScopeDiv.innerHTML = `

选择重排功能的应用范围,失败时自动降级为原始排序

`; rerankContainer.appendChild(rerankScopeDiv); // 服务商选择 const rerankProviderDiv = document.createElement('div'); rerankProviderDiv.innerHTML = ` `; rerankContainer.appendChild(rerankProviderDiv); // API Key(带显示/隐藏按钮) const rerankKeyDiv = document.createElement('div'); rerankKeyDiv.innerHTML = `
`; rerankContainer.appendChild(rerankKeyDiv); // Base URL(显示时去掉 /rerank 后缀) const rerankUrlDiv = document.createElement('div'); const displayRerankBaseUrl = (rerankConfig.endpoint || '').replace(/\/rerank\/?$/, ''); rerankUrlDiv.innerHTML = ` `; rerankContainer.appendChild(rerankUrlDiv); // 模型ID(支持 OpenAI 格式获取列表与模型检测) const rerankModelDiv = document.createElement('div'); rerankModelDiv.innerHTML = `

请输入服务商支持的重排模型ID;OpenAI格式可点击“获取列表”

`; rerankContainer.appendChild(rerankModelDiv); // Top N const rerankTopNDiv = document.createElement('div'); rerankTopNDiv.innerHTML = `

建议 5-20,根据实际需求调整

`; rerankContainer.appendChild(rerankTopNDiv); // 重排测试和保存按钮 const rerankButtonsDiv = document.createElement('div'); rerankButtonsDiv.className = 'flex gap-3 pt-2'; rerankButtonsDiv.innerHTML = ` `; rerankContainer.appendChild(rerankButtonsDiv); // 重排测试结果 const rerankResultDiv = document.createElement('div'); rerankResultDiv.id = 'rerank-test-result-km'; rerankResultDiv.className = 'text-sm mt-2'; rerankResultDiv.style.display = 'none'; rerankContainer.appendChild(rerankResultDiv); // 说明 const rerankNoticeDiv = document.createElement('div'); rerankNoticeDiv.className = 'mt-4 p-3 bg-blue-50 border border-blue-200 rounded-md'; rerankNoticeDiv.innerHTML = `

💡 重排工作原理:对搜索结果进行二次排序,使用更精确的模型计算相关性分数,提升最终结果的准确度。

`; rerankContainer.appendChild(rerankNoticeDiv); mainContainer.appendChild(rerankContainer); container.appendChild(mainContainer); // 事件绑定 const $= (id) => document.getElementById(id); // API Key 显示/隐藏切换(Embedding) (function() { const toggleBtn = $('emb-api-key-toggle-km'); const input = $('emb-api-key-km'); if (toggleBtn && input) { toggleBtn.addEventListener('click', () => { const isPassword = input.type === 'password'; input.type = isPassword ? 'text' : 'password'; toggleBtn.innerHTML = isPassword ? '隐藏' : '显示'; }); } })(); // API Key 显示/隐藏切换(Rerank) (function() { const toggleBtn = $('rerank-api-key-toggle-km'); const input = $('rerank-api-key-km'); if (toggleBtn && input) { toggleBtn.addEventListener('click', () => { const isPassword = input.type === 'password'; input.type = isPassword ? 'text' : 'password'; toggleBtn.innerHTML = isPassword ? '隐藏' : '显示'; }); } })(); // Tabs切换事件(中性灰) const kmTabs = document.querySelectorAll('.emb-km-tab'); const kmTabContents = document.querySelectorAll('.emb-km-tab-content'); kmTabs.forEach(tab => { tab.addEventListener('click', () => { // 更新tab样式 kmTabs.forEach(t => { t.classList.remove('text-gray-800', 'border-gray-300'); t.classList.add('text-gray-500', 'border-transparent'); }); tab.classList.remove('text-gray-500', 'border-transparent'); tab.classList.add('text-gray-800', 'border-gray-300'); // 切换内容 const targetId = tab.id.replace('-tab-', '-') + '-content'; kmTabContents.forEach(content => { content.classList.add('hidden'); }); const targetContent = document.getElementById(targetId); if (targetContent) { targetContent.classList.remove('hidden'); } }); }); // 服务商切换 $('emb-provider-km').onchange = function() { const provider = this.value; const fetchBtn = $('emb-fetch-models-km'); const modelHint = $('emb-model-hint-km'); // 显示/隐藏获取模型列表按钮(仅 OpenAI格式支持) if (provider === 'openai') { fetchBtn.style.display = 'block'; modelHint.textContent = '可手动输入模型ID,或点击"获取列表"从服务器获取'; } else { fetchBtn.style.display = 'none'; modelHint.textContent = '请输入服务商支持的嵌入模型ID'; } // 当选择阿里云百炼时,更新维度提示 if (provider === 'alibaba') { const dimsInput = $('emb-dimensions-km'); const dimsHint = dimsInput.nextElementSibling; const modelInput = $('emb-model-km'); // 根据当前模型更新默认维度 const updateDimensionsForModel = () => { const modelId = modelInput.value.trim(); const modelInfo = ALIBABA_MODELS[modelId]; if (modelInfo) { dimsInput.placeholder = `默认: ${modelInfo.dims}`; dimsHint.textContent = `默认维度: ${modelInfo.dims}。可输入1-${modelInfo.dims}之间的整数,留空使用默认。`; } }; // 初始化时更新一次 updateDimensionsForModel(); // 模型改变时更新 modelInput.addEventListener('change', updateDimensionsForModel); } }; // 初始化时更新 UI (function() { const provider = config.provider || 'openai'; const fetchBtn = $('emb-fetch-models-km'); const modelHint = $('emb-model-hint-km'); if (provider === 'openai') { fetchBtn.style.display = 'block'; modelHint.textContent = '可手动输入模型ID,或点击"获取列表"从服务器获取'; } })(); // 获取模型列表(仅 OpenAI格式) $('emb-fetch-models-km').onclick = async () => { const btn = $('emb-fetch-models-km'); const modelInput = $('emb-model-km'); const modelHint = $('emb-model-hint-km'); const provider = $('emb-provider-km').value; const apiKey = $('emb-api-key-km').value; let endpoint = $('emb-endpoint-km').value; if (!apiKey) { modelHint.style.color = '#dc2626'; modelHint.textContent = '❌ 请先输入 API Key'; setTimeout(() => { modelHint.style.color = '#6b7280'; modelHint.textContent = '可手动输入模型ID,或点击"获取列表"从服务器获取'; }, 3000); return; } if (!endpoint) { endpoint = PRESETS[provider]?.endpoint || ''; } // 自动补全路径 if (endpoint && !endpoint.endsWith('/embeddings')) { endpoint = endpoint.replace(/\/+$/, '') + '/embeddings'; } // 构建 models 端点 let modelsEndpoint = endpoint.replace('/embeddings', '/models'); btn.textContent = '获取中...'; btn.disabled = true; modelHint.style.color = '#6b7280'; modelHint.textContent = '正在获取模型列表...'; try { const response = await fetch(modelsEndpoint, { headers: { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json' } }); if (!response.ok) { throw new Error(`HTTP ${response.status}: ${response.statusText}`); } const data = await response.json(); const models = data.data || []; // 过滤出嵌入模型(支持多种命名模式) const embeddingModels = models.filter(m => { const id = (m.id || '').toLowerCase(); return id.includes('embedding') || id.includes('embed') || id.includes('bge') || id.includes('text-similarity') || id.includes('sentence') || id.includes('vector'); }); if (embeddingModels.length === 0) { modelHint.style.color = '#f59e0b'; modelHint.textContent = `⚠️ 未找到嵌入模型(共 ${models.length} 个模型)`; setTimeout(() => { modelHint.style.color = '#6b7280'; modelHint.textContent = '可手动输入模型ID,或点击"获取列表"从服务器获取'; }, 3000); return; } // 显示模型选择器 showEmbeddingModelSelector(embeddingModels, modelInput); modelHint.style.color = '#059669'; modelHint.textContent = `✅ 找到 ${embeddingModels.length} 个嵌入模型`; setTimeout(() => { modelHint.style.color = '#6b7280'; modelHint.textContent = '可手动输入模型ID,或点击"获取列表"从服务器获取'; }, 3000); } catch (error) { modelHint.style.color = '#dc2626'; modelHint.textContent = `❌ 获取失败: ${error.message}`; setTimeout(() => { modelHint.style.color = '#6b7280'; modelHint.textContent = '可手动输入模型ID,或点击"获取列表"从服务器获取'; }, 3000); } finally { btn.textContent = '获取列表'; btn.disabled = false; } }; // 测试连接 $('emb-test-km').onclick = async () => { const btn = $('emb-test-km'); const result = $('emb-test-result-km'); let baseUrl = $('emb-endpoint-km').value.trim(); // 自动补全 /embeddings 路径 if (baseUrl && !baseUrl.endsWith('/embeddings')) { baseUrl = baseUrl.replace(/\/+$/, '') + '/embeddings'; } const testConfig = { provider: $('emb-provider-km').value, apiKey: $('emb-api-key-km').value, endpoint: baseUrl, model: $('emb-model-km').value, dimensions: parseInt($('emb-dimensions-km').value) || null }; if (!testConfig.apiKey || !testConfig.endpoint || !testConfig.model) { result.style.display = 'block'; result.style.color = '#dc2626'; result.textContent = '❌ 请填写完整配置'; return; } btn.disabled = true; btn.textContent = '测试中...'; result.style.display = 'none'; try { if (!window.EmbeddingClient || typeof window.EmbeddingClient.saveConfig !== 'function') { const ok = await ensureEmbeddingClientLoaded(); if (!ok) throw new Error('EmbeddingClient 未加载'); } window.EmbeddingClient.saveConfig({ ...testConfig, enabled: true }); const vector = await window.EmbeddingClient.embed('测试文本'); result.style.display = 'block'; result.style.color = '#059669'; result.textContent = `✅ 连接成功!向量维度: ${vector.length}`; } catch (error) { result.style.display = 'block'; result.style.color = '#dc2626'; result.textContent = `❌ 连接失败: ${error.message}`; } finally { btn.disabled = false; btn.textContent = '测试连接'; } }; // 保存配置 $('emb-save-km').onclick = async () => { let baseUrl = $('emb-endpoint-km').value.trim(); // 自动补全 /embeddings 路径 if (baseUrl && !baseUrl.endsWith('/embeddings')) { baseUrl = baseUrl.replace(/\/+$/, '') + '/embeddings'; } const newConfig = { enabled: $('emb-enabled-km').checked, provider: $('emb-provider-km').value, apiKey: $('emb-api-key-km').value, endpoint: baseUrl, model: $('emb-model-km').value, dimensions: parseInt($('emb-dimensions-km').value) || null, concurrency: Math.max(1, Math.min(parseInt($('emb-concurrency-km').value) || 5, 50)) }; if (!window.EmbeddingClient || typeof window.EmbeddingClient.saveConfig !== 'function') { const ok = await ensureEmbeddingClientLoaded(); if (!ok) { alert('❌ 保存失败:EmbeddingClient 未加载'); return; } } window.EmbeddingClient.saveConfig(newConfig); if (typeof showNotification === 'function') { showNotification('向量搜索配置已保存', 'success'); } else { alert('配置已保存'); } }; // Rerank: 服务商切换(仅 OpenAI 格式显示“获取列表”) $('rerank-provider-km').onchange = function() { const provider = this.value; const fetchBtn = $('rerank-fetch-models-km'); const hint = $('rerank-model-hint-km'); const endpointInput = $('rerank-endpoint-km'); if (provider === 'openai') { fetchBtn.style.display = 'block'; hint.textContent = '可手动输入模型ID,或点击"获取列表"从服务器获取'; if (!endpointInput.value.trim()) endpointInput.placeholder = 'https://api.openai.com/v1'; } else { fetchBtn.style.display = 'none'; hint.textContent = '请输入服务商支持的重排模型ID'; if (!endpointInput.value.trim()) { endpointInput.placeholder = provider === 'jina' ? 'https://api.jina.ai/v1' : 'https://api.cohere.ai/v1'; } } }; // 初始化 Rerank 提示与按钮显示 (function() { const provider = ($('rerank-provider-km')?.value) || 'jina'; const fetchBtn = $('rerank-fetch-models-km'); const hint = $('rerank-model-hint-km'); const endpointInput = $('rerank-endpoint-km'); if (provider === 'openai') { fetchBtn.style.display = 'block'; hint.textContent = '可手动输入模型ID,或点击"获取列表"从服务器获取'; if (!endpointInput.value.trim()) endpointInput.placeholder = 'https://api.openai.com/v1'; } else { fetchBtn.style.display = 'none'; hint.textContent = '请输入服务商支持的重排模型ID'; } })(); // Rerank: 获取模型列表(OpenAI 格式) $('rerank-fetch-models-km').onclick = async () => { const btn = $('rerank-fetch-models-km'); const modelInput = $('rerank-model-km'); const hint = $('rerank-model-hint-km'); const apiKey = $('rerank-api-key-km').value; let baseUrl = $('rerank-endpoint-km').value.trim(); if (!apiKey) { hint.style.color = '#dc2626'; hint.textContent = '❌ 请先输入 API Key'; setTimeout(() => { hint.style.color = '#6b7280'; hint.textContent = '可手动输入模型ID,或点击"获取列表"从服务器获取'; }, 3000); return; } if (!baseUrl) { baseUrl = 'https://api.openai.com/v1'; } const modelsEndpoint = baseUrl.replace(/\/+$/, '') + '/models'; btn.textContent = '获取中...'; btn.disabled = true; hint.style.color = '#6b7280'; hint.textContent = '正在获取模型列表...'; try { const response = await fetch(modelsEndpoint, { headers: { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json' } }); if (!response.ok) throw new Error(`HTTP ${response.status}: ${response.statusText}`); const data = await response.json(); const models = data.data || []; const rerankModels = models.filter(m => { const id = (m.id || '').toLowerCase(); return id.includes('rerank') || id.includes('rank') || id.includes('relevance') || id.includes('search'); }); const list = rerankModels.length > 0 ? rerankModels : models; if (list.length === 0) { hint.style.color = '#f59e0b'; hint.textContent = '⚠️ 未从服务端获取到模型列表'; setTimeout(() => { hint.style.color = '#6b7280'; hint.textContent = '可手动输入模型ID,或点击"获取列表"从服务器获取'; }, 3000); return; } showRerankModelSelector(list, modelInput); hint.style.color = '#059669'; hint.textContent = `✅ 找到 ${list.length} 个模型`; setTimeout(() => { hint.style.color = '#6b7280'; hint.textContent = '可手动输入模型ID,或点击"获取列表"从服务器获取'; }, 3000); } catch (error) { hint.style.color = '#dc2626'; hint.textContent = `❌ 获取失败: ${error.message}`; setTimeout(() => { hint.style.color = '#6b7280'; hint.textContent = '可手动输入模型ID,或点击"获取列表"从服务器获取'; }, 3000); } finally { btn.textContent = '获取列表'; btn.disabled = false; } }; // Rerank: 模型检测 $('rerank-check-model-km').onclick = async () => { const btn = $('rerank-check-model-km'); const modelId = $('rerank-model-km').value.trim(); const provider = $('rerank-provider-km').value; const apiKey = $('rerank-api-key-km').value; let baseUrl = $('rerank-endpoint-km').value.trim(); const hint = $('rerank-model-hint-km'); if (!modelId) { hint.style.color = '#dc2626'; hint.textContent = '❌ 请输入模型ID'; setTimeout(() => { hint.style.color = '#6b7280'; hint.textContent = '请输入服务商支持的重排模型ID;OpenAI格式可点击“获取列表”'; }, 2500); return; } if (!apiKey) { hint.style.color = '#dc2626'; hint.textContent = '❌ 请输入 API Key'; setTimeout(() => { hint.style.color = '#6b7280'; hint.textContent = '请输入服务商支持的重排模型ID;OpenAI格式可点击“获取列表”'; }, 2500); return; } btn.disabled = true; btn.textContent = '检测中...'; hint.style.color = '#6b7280'; hint.textContent = '正在检测模型...'; try { if (provider === 'openai') { if (!baseUrl) baseUrl = 'https://api.openai.com/v1'; const modelsEndpoint = baseUrl.replace(/\/+$/, '') + '/models'; const resp = await fetch(modelsEndpoint, { headers: { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json' } }); if (!resp.ok) throw new Error(`HTTP ${resp.status}: ${resp.statusText}`); const data = await resp.json(); const models = (data.data || []).map(m => m.id); if (models.includes(modelId)) { hint.style.color = '#059669'; hint.textContent = '✅ 模型可用'; } else { hint.style.color = '#f59e0b'; hint.textContent = '⚠️ 未在列表中找到该模型(可能仍可用)'; } } else { const endpoint = (baseUrl || (provider === 'jina' ? 'https://api.jina.ai/v1' : 'https://api.cohere.ai/v1')).replace(/\/+$/, '') + '/rerank'; const resp = await fetch(endpoint, { method: 'POST', headers: { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ model: modelId, query: 'ping', documents: ['pong'], top_n: 1 }) }); if (resp.ok) { hint.style.color = '#059669'; hint.textContent = '✅ 模型可用'; } else { const text = await resp.text(); throw new Error(`${resp.status} ${text}`); } } } catch (error) { hint.style.color = '#dc2626'; hint.textContent = `❌ 检测失败: ${error.message}`; } finally { btn.disabled = false; btn.textContent = '检测模型'; } }; // 重排测试连接 $('rerank-test-km').onclick = async () => { const btn = $('rerank-test-km'); const result = $('rerank-test-result-km'); // 自动补全 /rerank 路径 let rerankBase = $('rerank-endpoint-km').value.trim(); if (rerankBase && !/\/rerank\/?$/.test(rerankBase)) { rerankBase = rerankBase.replace(/\/+$/, '') + '/rerank'; } const testConfig = { provider: $('rerank-provider-km').value, apiKey: $('rerank-api-key-km').value, endpoint: rerankBase, model: $('rerank-model-km').value, topN: parseInt($('rerank-top-n-km').value) || 10 }; if (!testConfig.apiKey || !testConfig.model) { result.style.display = 'block'; result.style.color = '#dc2626'; result.textContent = '❌ 请填写完整配置'; return; } btn.disabled = true; btn.textContent = '测试中...'; result.style.display = 'none'; try { if (!window.RerankClient) { throw new Error('RerankClient 未加载'); } window.RerankClient.saveConfig({ ...testConfig, enabled: true }); const testQuery = '测试查询'; const testDocs = ['文档1内容', '文档2内容', '文档3内容']; const results = await window.RerankClient.rerank(testQuery, testDocs); result.style.display = 'block'; result.style.color = '#059669'; result.textContent = `✅ 连接成功!返回 ${results.length} 个结果`; } catch (error) { result.style.display = 'block'; result.style.color = '#dc2626'; result.textContent = `❌ 连接失败: ${error.message}`; } finally { btn.disabled = false; btn.textContent = '测试连接'; } }; // 重排保存配置(补全 /rerank) $('rerank-save-km').onclick = () => { // 获取选中的scope const scopeRadios = document.getElementsByName('rerank-scope-km'); let scope = 'vector-only'; for (const radio of scopeRadios) { if (radio.checked) { scope = radio.value; break; } } // 自动补全 /rerank 路径 let rerankBase = $('rerank-endpoint-km').value.trim(); if (rerankBase && !/\/rerank\/?$/.test(rerankBase)) { rerankBase = rerankBase.replace(/\/+$/, '') + '/rerank'; } const newConfig = { enabled: $('rerank-enabled-km').checked, scope: scope, provider: $('rerank-provider-km').value, apiKey: $('rerank-api-key-km').value, endpoint: rerankBase, model: $('rerank-model-km').value, topN: parseInt($('rerank-top-n-km').value) || 10 }; if (!window.RerankClient) { alert('RerankClient 未加载'); return; } window.RerankClient.saveConfig(newConfig); if (typeof showNotification === 'function') { showNotification('重排配置已保存', 'success'); } else { alert('配置已保存'); } }; } // 导出到全局作用域 window.UIEmbeddingConfigRenderer = { renderEmbeddingConfig }; })(window);