/** * Models Manager - 管理可用模型列表的显示和复制功能 * Models Manager - Manages the display and copy functionality of available models */ import { t } from './i18n.js'; // 模型数据缓存 let modelsCache = null; /** * 获取所有提供商的可用模型 * @returns {Promise} 模型数据 */ async function fetchProviderModels() { if (modelsCache) { return modelsCache; } try { const response = await fetch('/api/provider-models', { headers: { 'Authorization': `Bearer ${localStorage.getItem('authToken') || ''}` } }); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } modelsCache = await response.json(); return modelsCache; } catch (error) { console.error('[Models Manager] Failed to fetch provider models:', error); throw error; } } /** * 复制文本到剪贴板 * @param {string} text - 要复制的文本 * @returns {Promise} 是否复制成功 */ async function copyToClipboard(text) { try { if (navigator.clipboard && navigator.clipboard.writeText) { await navigator.clipboard.writeText(text); return true; } // Fallback for older browsers const textArea = document.createElement('textarea'); textArea.value = text; textArea.style.position = 'fixed'; textArea.style.left = '-9999px'; textArea.style.top = '-9999px'; document.body.appendChild(textArea); textArea.focus(); textArea.select(); const successful = document.execCommand('copy'); document.body.removeChild(textArea); return successful; } catch (error) { console.error('[Models Manager] Failed to copy to clipboard:', error); return false; } } /** * 显示复制成功的 Toast 提示 * @param {string} modelName - 模型名称 */ function showCopyToast(modelName) { const toastContainer = document.getElementById('toastContainer'); if (!toastContainer) return; const toast = document.createElement('div'); toast.className = 'toast toast-success'; toast.innerHTML = ` ${t('models.copied') || '已复制'}: ${modelName} `; toastContainer.appendChild(toast); // 自动移除 setTimeout(() => { toast.classList.add('toast-fade-out'); setTimeout(() => { toast.remove(); }, 300); }, 2000); } /** * 渲染模型列表 * @param {Object} models - 模型数据 */ function renderModelsList(models) { const container = document.getElementById('modelsList'); if (!container) return; // 检查是否有模型数据 const providerTypes = Object.keys(models); if (providerTypes.length === 0) { container.innerHTML = `

${t('models.empty') || '暂无可用模型'}

`; return; } // 渲染每个提供商的模型组 let html = ''; for (const providerType of providerTypes) { const modelList = models[providerType]; if (!modelList || modelList.length === 0) continue; const providerDisplayName = getProviderDisplayName(providerType); const providerIcon = getProviderIcon(providerType); html += `

${providerDisplayName}

${modelList.length}
${modelList.map(model => `
${escapeHtml(model)}
`).join('')}
`; } container.innerHTML = html; } /** * 获取提供商显示名称 * @param {string} providerType - 提供商类型 * @returns {string} 显示名称 */ function getProviderDisplayName(providerType) { const displayNames = { 'gemini-cli-oauth': 'Gemini CLI (OAuth)', 'gemini-antigravity': 'Gemini Antigravity', 'claude-custom': 'Claude Custom', 'claude-kiro-oauth': 'Claude Kiro (OAuth)', 'claude-orchids-oauth': 'Claude Orchids (OAuth)', 'openai-custom': 'OpenAI Custom', 'openaiResponses-custom': 'OpenAI Responses Custom', 'openai-qwen-oauth': 'Qwen (OAuth)', 'openai-iflow': 'iFlow', 'openai-codex-oauth': 'OpenAI Codex (OAuth)' }; return displayNames[providerType] || providerType; } /** * 获取提供商图标 * @param {string} providerType - 提供商类型 * @returns {string} 图标类名 */ function getProviderIcon(providerType) { if (providerType.includes('gemini')) { return 'fas fa-gem'; } else if (providerType.includes('claude')) { return 'fas fa-robot'; } else if (providerType.includes('openai') || providerType.includes('qwen') || providerType.includes('iflow')) { return 'fas fa-brain'; } return 'fas fa-server'; } /** * HTML 转义 * @param {string} text - 原始文本 * @returns {string} 转义后的文本 */ function escapeHtml(text) { const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } /** * 切换提供商模型列表的展开/折叠状态 * @param {string} providerType - 提供商类型 */ function toggleProviderModels(providerType) { const group = document.querySelector(`.provider-models-group[data-provider="${providerType}"]`); if (!group) return; const header = group.querySelector('.provider-models-header'); const content = group.querySelector('.provider-models-content'); if (content.classList.contains('collapsed')) { content.classList.remove('collapsed'); header.classList.remove('collapsed'); } else { content.classList.add('collapsed'); header.classList.add('collapsed'); } } /** * 复制模型名称 * @param {string} modelName - 模型名称 * @param {HTMLElement} element - 点击的元素 */ async function copyModelName(modelName, element) { const success = await copyToClipboard(modelName); if (success) { // 添加复制成功的视觉反馈 element.classList.add('copied'); setTimeout(() => { element.classList.remove('copied'); }, 1000); // 显示 Toast 提示 showCopyToast(modelName); } } /** * 初始化模型管理器 */ async function initModelsManager() { const container = document.getElementById('modelsList'); if (!container) return; try { const models = await fetchProviderModels(); renderModelsList(models); } catch (error) { container.innerHTML = `

${t('models.loadError') || '加载模型列表失败'}

`; } } /** * 刷新模型列表 */ async function refreshModels() { modelsCache = null; await initModelsManager(); } // 导出到全局作用域供 HTML 调用 window.toggleProviderModels = toggleProviderModels; window.copyModelName = copyModelName; window.refreshModels = refreshModels; // 监听组件加载完成事件 window.addEventListener('componentsLoaded', () => { initModelsManager(); }); // 导出函数 export { initModelsManager, refreshModels, fetchProviderModels };