aiclient-2-api / static /app /models-manager.js
Jaasomn
Initial deployment
ceb3821
/**
* Models Manager - 管理可用模型列表的显示和复制功能
* Models Manager - Manages the display and copy functionality of available models
*/
import { t } from './i18n.js';
// 模型数据缓存
let modelsCache = null;
/**
* 获取所有提供商的可用模型
* @returns {Promise<Object>} 模型数据
*/
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<boolean>} 是否复制成功
*/
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 = `
<i class="fas fa-check-circle"></i>
<span>${t('models.copied') || '已复制'}: ${modelName}</span>
`;
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 = `
<div class="models-empty">
<i class="fas fa-cube"></i>
<p data-i18n="models.empty">${t('models.empty') || '暂无可用模型'}</p>
</div>
`;
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 += `
<div class="provider-models-group" data-provider="${providerType}">
<div class="provider-models-header" onclick="window.toggleProviderModels('${providerType}')">
<div class="provider-models-title">
<i class="${providerIcon}"></i>
<h3>${providerDisplayName}</h3>
<span class="provider-models-count">${modelList.length}</span>
</div>
<div class="provider-models-toggle">
<i class="fas fa-chevron-down"></i>
</div>
</div>
<div class="provider-models-content" id="models-${providerType}">
${modelList.map(model => `
<div class="model-item" onclick="window.copyModelName('${escapeHtml(model)}', this)" title="${t('models.clickToCopy') || '点击复制'}">
<div class="model-item-icon">
<i class="fas fa-cube"></i>
</div>
<span class="model-item-name">${escapeHtml(model)}</span>
<div class="model-item-copy">
<i class="fas fa-copy"></i>
</div>
</div>
`).join('')}
</div>
</div>
`;
}
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)',
'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 = `
<div class="models-empty">
<i class="fas fa-exclamation-triangle"></i>
<p>${t('models.loadError') || '加载模型列表失败'}</p>
</div>
`;
}
}
/**
* 刷新模型列表
*/
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
};