export const apiKeysModule = {
// 加载API密钥
async loadApiKeys() {
try {
const data = await this.makeRequest('/api-keys');
const apiKeysValue = data?.['api-keys'] || [];
const keys = Array.isArray(apiKeysValue) ? apiKeysValue : [];
this.renderApiKeys(keys);
} catch (error) {
console.error('加载API密钥失败:', error);
}
},
// 渲染API密钥列表
renderApiKeys(keys) {
const container = document.getElementById('api-keys-list');
if (keys.length === 0) {
container.innerHTML = `
${i18n.t('api_keys.empty_title')}
${i18n.t('api_keys.empty_desc')}
`;
return;
}
const rows = keys.map((key, index) => {
const normalizedKey = typeof key === 'string' ? key : String(key ?? '');
const maskedDisplay = this.escapeHtml(this.maskApiKey(normalizedKey));
const keyArgument = encodeURIComponent(normalizedKey);
return `
#${index + 1}
${i18n.t('api_keys.item_title')}
${maskedDisplay}
`;
}).join('');
container.innerHTML = `
${rows}
`;
this.bindApiKeyListEvents(container);
},
// 注意: escapeHtml, maskApiKey, normalizeArrayResponse
// 现在由 app.js 通过工具模块提供,通过 this 访问
// 添加一行自定义请求头输入
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;
},
addApiKeyEntryField(wrapperId, entry = {}) {
const wrapper = document.getElementById(wrapperId);
if (!wrapper) return;
const row = document.createElement('div');
row.className = 'api-key-input-row';
const keyValue = typeof entry?.['api-key'] === 'string' ? entry['api-key'] : '';
const proxyValue = typeof entry?.['proxy-url'] === 'string' ? entry['proxy-url'] : '';
row.innerHTML = `
`;
const removeBtn = row.querySelector('.api-key-remove-btn');
if (removeBtn) {
removeBtn.addEventListener('click', () => {
wrapper.removeChild(row);
if (wrapper.childElementCount === 0) {
this.addApiKeyEntryField(wrapperId);
}
});
}
wrapper.appendChild(row);
},
populateApiKeyEntryFields(wrapperId, entries = []) {
const wrapper = document.getElementById(wrapperId);
if (!wrapper) return;
wrapper.innerHTML = '';
if (!Array.isArray(entries) || entries.length === 0) {
this.addApiKeyEntryField(wrapperId);
return;
}
entries.forEach(entry => this.addApiKeyEntryField(wrapperId, entry));
},
collectApiKeyEntryInputs(wrapperId) {
const wrapper = document.getElementById(wrapperId);
if (!wrapper) return [];
const rows = Array.from(wrapper.querySelectorAll('.api-key-input-row'));
const entries = [];
rows.forEach(row => {
const keyInput = row.querySelector('.api-key-value-input');
const proxyInput = row.querySelector('.api-key-proxy-input');
const key = keyInput ? keyInput.value.trim() : '';
const proxy = proxyInput ? proxyInput.value.trim() : '';
if (key) {
entries.push({ 'api-key': key, 'proxy-url': proxy });
}
});
return entries;
},
// 规范化并写入请求头
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]) => `
`).join('');
return `
`;
},
// 构造Codex配置,保持未展示的字段
buildCodexConfig(apiKey, baseUrl, proxyUrl, original = {}, headers = null, excludedModels = null) {
const result = {
...original,
'api-key': apiKey,
'base-url': baseUrl || '',
'proxy-url': proxyUrl || ''
};
this.applyHeadersToConfig(result, headers);
if (Array.isArray(excludedModels)) {
result['excluded-models'] = excludedModels;
}
return result;
},
// 显示添加API密钥模态框
showAddApiKeyModal() {
const modal = document.getElementById('modal');
const modalBody = document.getElementById('modal-body');
modalBody.innerHTML = `
${i18n.t('api_keys.add_modal_title')}
`;
modal.style.display = 'block';
},
// 添加API密钥
async addApiKey() {
const newKey = document.getElementById('new-api-key').value.trim();
if (!newKey) {
this.showNotification(`${i18n.t('notification.please_enter')} ${i18n.t('notification.api_key')}`, 'error');
return;
}
try {
const data = await this.makeRequest('/api-keys');
const currentKeys = data['api-keys'] || [];
currentKeys.push(newKey);
await this.makeRequest('/api-keys', {
method: 'PUT',
body: JSON.stringify(currentKeys)
});
this.clearCache('api-keys'); // 仅清除 api-keys 段缓存
this.closeModal();
this.loadApiKeys();
this.showNotification(i18n.t('notification.api_key_added'), 'success');
} catch (error) {
this.showNotification(`${i18n.t('notification.add_failed')}: ${error.message}`, 'error');
}
},
// 编辑API密钥
editApiKey(index, currentKey) {
const modal = document.getElementById('modal');
const modalBody = document.getElementById('modal-body');
modalBody.innerHTML = `
${i18n.t('api_keys.edit_modal_title')}
`;
modal.style.display = 'block';
},
// 更新API密钥
async updateApiKey(index) {
const newKey = document.getElementById('edit-api-key').value.trim();
if (!newKey) {
this.showNotification(`${i18n.t('notification.please_enter')} ${i18n.t('notification.api_key')}`, 'error');
return;
}
try {
await this.makeRequest('/api-keys', {
method: 'PATCH',
body: JSON.stringify({ index, value: newKey })
});
this.clearCache('api-keys'); // 仅清除 api-keys 段缓存
this.closeModal();
this.loadApiKeys();
this.showNotification(i18n.t('notification.api_key_updated'), 'success');
} catch (error) {
this.showNotification(`${i18n.t('notification.update_failed')}: ${error.message}`, 'error');
}
},
// 删除API密钥
async deleteApiKey(index) {
if (!confirm(i18n.t('api_keys.delete_confirm'))) return;
try {
await this.makeRequest(`/api-keys?index=${index}`, { method: 'DELETE' });
this.clearCache('api-keys'); // 仅清除 api-keys 段缓存
this.loadApiKeys();
this.showNotification(i18n.t('notification.api_key_deleted'), 'success');
} catch (error) {
this.showNotification(`${i18n.t('notification.delete_failed')}: ${error.message}`, 'error');
}
},
bindApiKeyListEvents(container = null) {
if (this.apiKeyListEventsBound) {
return;
}
const listContainer = container || document.getElementById('api-keys-list');
if (!listContainer) return;
listContainer.addEventListener('click', (event) => {
const button = event.target.closest('[data-action][data-index]');
if (!button || !listContainer.contains(button)) return;
const action = button.dataset.action;
const index = Number(button.dataset.index);
if (!Number.isFinite(index)) return;
switch (action) {
case 'edit-api-key': {
const rawKey = button.dataset.key || '';
let decodedKey = '';
try {
decodedKey = decodeURIComponent(rawKey);
} catch (e) {
decodedKey = rawKey;
}
this.editApiKey(index, decodedKey);
break;
}
case 'delete-api-key':
this.deleteApiKey(index);
break;
default:
break;
}
});
this.apiKeyListEventsBound = true;
}
};