/**
* 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);