diff --git a/i18n.js b/i18n.js index 0a328ea..f71f7fb 100644 --- a/i18n.js +++ b/i18n.js @@ -149,6 +149,10 @@ const i18n = { 'ai_providers.gemini_edit_modal_title': '编辑Gemini API密钥', 'ai_providers.gemini_edit_modal_key_label': 'API密钥:', 'ai_providers.gemini_delete_confirm': '确定要删除这个Gemini密钥吗?', + 'ai_providers.excluded_models_label': '排除的模型 (可选):', + 'ai_providers.excluded_models_placeholder': '用逗号或换行分隔,例如: gemini-1.5-pro, gemini-1.5-flash', + 'ai_providers.excluded_models_hint': '留空表示不过滤;保存时会自动去重并忽略空白。', + 'ai_providers.excluded_models_count': '排除 {count} 个模型', 'ai_providers.codex_title': 'Codex API 配置', 'ai_providers.codex_add_button': '添加配置', @@ -755,6 +759,10 @@ const i18n = { 'ai_providers.gemini_edit_modal_title': 'Edit Gemini API Key', 'ai_providers.gemini_edit_modal_key_label': 'API Key:', 'ai_providers.gemini_delete_confirm': 'Are you sure you want to delete this Gemini key?', + 'ai_providers.excluded_models_label': 'Excluded models (optional):', + 'ai_providers.excluded_models_placeholder': 'Comma or newline separated, e.g. gemini-1.5-pro, gemini-1.5-flash', + 'ai_providers.excluded_models_hint': 'Leave empty to allow all models; values are trimmed and deduplicated automatically.', + 'ai_providers.excluded_models_count': 'Excluding {count} models', 'ai_providers.codex_title': 'Codex API Configuration', 'ai_providers.codex_add_button': 'Add Configuration', diff --git a/src/modules/ai-providers.js b/src/modules/ai-providers.js index 82d4d5c..5eabe4f 100644 --- a/src/modules/ai-providers.js +++ b/src/modules/ai-providers.js @@ -59,6 +59,59 @@ const normalizeModelList = (payload) => { return []; }; +const normalizeExcludedModels = (input) => { + const rawList = Array.isArray(input) + ? input + : (typeof input === 'string' ? input.split(/[\n,]/) : []); + const seen = new Set(); + const normalized = []; + + rawList.forEach(item => { + if (item === undefined || item === null) { + return; + } + const trimmed = String(item).trim(); + if (!trimmed) return; + const key = trimmed.toLowerCase(); + if (seen.has(key)) return; + seen.add(key); + normalized.push(trimmed); + }); + + return normalized; +}; + +export function collectExcludedModels(textareaId) { + const textarea = document.getElementById(textareaId); + if (!textarea) return []; + return normalizeExcludedModels(textarea.value); +} + +export function setExcludedModelsValue(textareaId, models = []) { + const textarea = document.getElementById(textareaId); + if (!textarea) return; + textarea.value = normalizeExcludedModels(models).join('\n'); +} + +export function renderExcludedModelBadges(models) { + const normalized = normalizeExcludedModels(models); + if (!normalized.length) { + return ''; + } + const badges = normalized.map(model => ` + + ${this.escapeHtml(model)} + + `).join(''); + + return ` +
${i18n.t('ai_providers.excluded_models_count', { count: normalized.length })}
+
+ ${badges} +
+ `; +} + export async function loadGeminiKeys() { try { const config = await this.getConfig(); @@ -144,6 +197,7 @@ export async function renderGeminiKeys(keys, keyStats = null) { const configJson = JSON.stringify(config).replace(/"/g, '"'); const apiKeyJson = JSON.stringify(rawKey || '').replace(/"/g, '"'); const baseUrl = config['base-url'] || config['base_url'] || ''; + const excludedModelsHtml = this.renderExcludedModelBadges(config['excluded-models']); return `
@@ -151,6 +205,7 @@ export async function renderGeminiKeys(keys, keyStats = null) {
${i18n.t('common.api_key')}: ${maskedDisplay}
${baseUrl ? `
${i18n.t('common.base_url')}: ${this.escapeHtml(baseUrl)}
` : ''} ${this.renderHeaderBadges(config.headers)} + ${excludedModelsHtml}
${i18n.t('stats.success')}: ${usageStats.success} @@ -191,6 +246,11 @@ export function showAddGeminiKeyModal() {
+
+ +

${i18n.t('ai_providers.excluded_models_hint')}

+ +
+
+ +

${i18n.t('ai_providers.excluded_models_hint')}

+ +