From 6837100decec119a267f24586a4dca40cd967fa6 Mon Sep 17 00:00:00 2001 From: Supra4E8C Date: Sun, 9 Nov 2025 18:27:29 +0800 Subject: [PATCH] feat(app.js, i18n, styles): implement custom headers functionality in API key management - Added the ability to input custom HTTP headers for API keys in the CLIProxyManager. - Introduced new methods for adding, populating, collecting, and applying custom headers. - Updated the UI to include input fields for custom headers in the modals for adding and editing API keys. - Enhanced internationalization strings to support custom headers in both English and Chinese. - Added corresponding styles for the new header input fields and badges in the UI. --- app.js | 190 +++++++++++++++++++++++++++++++++++++++++++++++++++-- i18n.js | 10 +++ styles.css | 62 +++++++++++++++++ 3 files changed, 258 insertions(+), 4 deletions(-) diff --git a/app.js b/app.js index c0d4cd4..6e614ac 100644 --- a/app.js +++ b/app.js @@ -2322,14 +2322,122 @@ class CLIProxyManager { return []; } + // 添加一行自定义请求头输入 + addHeaderField(wrapperId, header = {}) { + const wrapper = document.getElementById(wrapperId); + if (!wrapper) return; + + const row = document.createElement('div'); + row.className = 'header-input-row'; + const keyValue = typeof header.key === 'string' ? header.key : ''; + const valueValue = typeof header.value === 'string' ? header.value : ''; + row.innerHTML = ` +
+ + : + + +
+ `; + + const removeBtn = row.querySelector('.header-remove-btn'); + if (removeBtn) { + removeBtn.addEventListener('click', () => { + wrapper.removeChild(row); + if (wrapper.childElementCount === 0) { + this.addHeaderField(wrapperId); + } + }); + } + + wrapper.appendChild(row); + } + + // 填充自定义请求头输入 + populateHeaderFields(wrapperId, headers = null) { + const wrapper = document.getElementById(wrapperId); + if (!wrapper) return; + wrapper.innerHTML = ''; + + const entries = (headers && typeof headers === 'object') + ? Object.entries(headers).filter(([key, value]) => key && value !== undefined && value !== null) + : []; + + if (!entries.length) { + this.addHeaderField(wrapperId); + return; + } + + entries.forEach(([key, value]) => this.addHeaderField(wrapperId, { key, value: String(value ?? '') })); + } + + // 收集自定义请求头输入 + collectHeaderInputs(wrapperId) { + const wrapper = document.getElementById(wrapperId); + if (!wrapper) return null; + + const rows = Array.from(wrapper.querySelectorAll('.header-input-row')); + const headers = {}; + + rows.forEach(row => { + const keyInput = row.querySelector('.header-key-input'); + const valueInput = row.querySelector('.header-value-input'); + const key = keyInput ? keyInput.value.trim() : ''; + const value = valueInput ? valueInput.value.trim() : ''; + if (key && value) { + headers[key] = value; + } + }); + + return Object.keys(headers).length ? headers : null; + } + + // 规范化并写入请求头 + applyHeadersToConfig(target, headers) { + if (!target) { + return; + } + if (headers && typeof headers === 'object' && Object.keys(headers).length) { + target.headers = { ...headers }; + } else { + delete target.headers; + } + } + + // 渲染请求头徽章 + renderHeaderBadges(headers) { + if (!headers || typeof headers !== 'object') { + return ''; + } + const entries = Object.entries(headers).filter(([key, value]) => key && value !== undefined && value !== null && value !== ''); + if (!entries.length) { + return ''; + } + + const badges = entries.map(([key, value]) => ` + ${this.escapeHtml(key)}: ${this.escapeHtml(String(value))} + `).join(''); + + return ` +
+ ${i18n.t('common.custom_headers_label')}: +
+ ${badges} +
+
+ `; + } + // 构造Codex配置,保持未展示的字段 - buildCodexConfig(apiKey, baseUrl, proxyUrl, original = {}) { - return { + buildCodexConfig(apiKey, baseUrl, proxyUrl, original = {}, headers = null) { + const result = { ...original, 'api-key': apiKey, 'base-url': baseUrl || '', 'proxy-url': proxyUrl || '' }; + this.applyHeadersToConfig(result, headers); + return result; } // 显示添加API密钥模态框 @@ -2496,6 +2604,7 @@ class CLIProxyManager {
${i18n.t('ai_providers.gemini_item_title')} #${index + 1}
${this.maskApiKey(rawKey || '')}
${config['base-url'] ? `
${i18n.t('common.base_url')}: ${this.escapeHtml(config['base-url'])}
` : ''} + ${this.renderHeaderBadges(config.headers)}
${i18n.t('stats.success')}: ${keyStats.success} @@ -2533,6 +2642,12 @@ class CLIProxyManager {
+
+ +

${i18n.t('common.custom_headers_hint')}

+
+ +
+
+ +

${i18n.t('common.custom_headers_hint')}

+
+ +