| |
| const SENSITIVE_INPUT_CLASS = "sensitive-input"; |
| const ARRAY_ITEM_CLASS = "array-item"; |
| const ARRAY_INPUT_CLASS = "array-input"; |
| const MAP_ITEM_CLASS = "map-item"; |
| const MAP_KEY_INPUT_CLASS = "map-key-input"; |
| const MAP_VALUE_INPUT_CLASS = "map-value-input"; |
| const CUSTOM_HEADER_ITEM_CLASS = "custom-header-item"; |
| const CUSTOM_HEADER_KEY_INPUT_CLASS = "custom-header-key-input"; |
| const CUSTOM_HEADER_VALUE_INPUT_CLASS = "custom-header-value-input"; |
| const SAFETY_SETTING_ITEM_CLASS = "safety-setting-item"; |
| const SHOW_CLASS = "show"; |
| const API_KEY_REGEX = /AIzaSy\S{33}/g; |
| const PROXY_REGEX = |
| /(?:https?|socks5):\/\/(?:[^:@\/]+(?::[^@\/]+)?@)?(?:[^:\/\s]+)(?::\d+)?/g; |
| const VERTEX_API_KEY_REGEX = /AQ\.[a-zA-Z0-9_\-]{50}/g; |
| const MASKED_VALUE = "••••••••"; |
|
|
| |
| const API_KEYS_PER_PAGE = 20; |
| let currentApiKeyPage = 1; |
| let totalApiKeyPages = 1; |
| let allApiKeys = []; |
| let filteredApiKeys = []; |
|
|
| |
| const safetySettingsContainer = document.getElementById( |
| "SAFETY_SETTINGS_container" |
| ); |
| const thinkingModelsContainer = document.getElementById( |
| "THINKING_MODELS_container" |
| ); |
| const apiKeyModal = document.getElementById("apiKeyModal"); |
| const apiKeyBulkInput = document.getElementById("apiKeyBulkInput"); |
| const apiKeySearchInput = document.getElementById("apiKeySearchInput"); |
| const bulkDeleteApiKeyModal = document.getElementById("bulkDeleteApiKeyModal"); |
| const bulkDeleteApiKeyInput = document.getElementById("bulkDeleteApiKeyInput"); |
| const proxyModal = document.getElementById("proxyModal"); |
| const proxyBulkInput = document.getElementById("proxyBulkInput"); |
| const bulkDeleteProxyModal = document.getElementById("bulkDeleteProxyModal"); |
| const bulkDeleteProxyInput = document.getElementById("bulkDeleteProxyInput"); |
| const resetConfirmModal = document.getElementById("resetConfirmModal"); |
| const configForm = document.getElementById("configForm"); |
|
|
| |
| const vertexApiKeyModal = document.getElementById("vertexApiKeyModal"); |
| const vertexApiKeyBulkInput = document.getElementById("vertexApiKeyBulkInput"); |
| const bulkDeleteVertexApiKeyModal = document.getElementById( |
| "bulkDeleteVertexApiKeyModal" |
| ); |
| const bulkDeleteVertexApiKeyInput = document.getElementById( |
| "bulkDeleteVertexApiKeyInput" |
| ); |
|
|
| |
| const modelHelperModal = document.getElementById("modelHelperModal"); |
| const modelHelperTitleElement = document.getElementById("modelHelperTitle"); |
| const modelHelperSearchInput = document.getElementById( |
| "modelHelperSearchInput" |
| ); |
| const modelHelperListContainer = document.getElementById( |
| "modelHelperListContainer" |
| ); |
| const closeModelHelperModalBtn = document.getElementById( |
| "closeModelHelperModalBtn" |
| ); |
| const cancelModelHelperBtn = document.getElementById("cancelModelHelperBtn"); |
|
|
| let cachedModelsList = null; |
| let currentModelHelperTarget = null; |
|
|
| |
| function openModal(modalElement) { |
| if (modalElement) { |
| modalElement.classList.add(SHOW_CLASS); |
| } |
| } |
|
|
| function closeModal(modalElement) { |
| if (modalElement) { |
| modalElement.classList.remove(SHOW_CLASS); |
| } |
| } |
|
|
| document.addEventListener("DOMContentLoaded", function () { |
| |
| initConfig(); |
|
|
| |
| const tabButtons = document.querySelectorAll(".tab-btn"); |
| tabButtons.forEach((button) => { |
| button.addEventListener("click", function (e) { |
| e.stopPropagation(); |
| const tabId = this.getAttribute("data-tab"); |
| switchTab(tabId); |
| }); |
| }); |
|
|
| |
| const uploadProviderSelect = document.getElementById("UPLOAD_PROVIDER"); |
| if (uploadProviderSelect) { |
| uploadProviderSelect.addEventListener("change", function () { |
| toggleProviderConfig(this.value); |
| }); |
| } |
|
|
| |
| const toggleSwitches = document.querySelectorAll(".toggle-switch"); |
| toggleSwitches.forEach((toggleSwitch) => { |
| toggleSwitch.addEventListener("click", function (e) { |
| e.stopPropagation(); |
| const checkbox = this.querySelector('input[type="checkbox"]'); |
| if (checkbox) { |
| checkbox.checked = !checkbox.checked; |
| } |
| }); |
| }); |
|
|
| |
| const saveBtn = document.getElementById("saveBtn"); |
| if (saveBtn) { |
| saveBtn.addEventListener("click", saveConfig); |
| } |
|
|
| |
| const resetBtn = document.getElementById("resetBtn"); |
| if (resetBtn) { |
| resetBtn.addEventListener("click", resetConfig); |
| } |
|
|
| |
| window.addEventListener("scroll", toggleScrollButtons); |
|
|
| |
| const addApiKeyBtn = document.getElementById("addApiKeyBtn"); |
| const closeApiKeyModalBtn = document.getElementById("closeApiKeyModalBtn"); |
| const cancelAddApiKeyBtn = document.getElementById("cancelAddApiKeyBtn"); |
| const confirmAddApiKeyBtn = document.getElementById("confirmAddApiKeyBtn"); |
|
|
| if (addApiKeyBtn) { |
| addApiKeyBtn.addEventListener("click", () => { |
| openModal(apiKeyModal); |
| if (apiKeyBulkInput) apiKeyBulkInput.value = ""; |
| }); |
| } |
| if (closeApiKeyModalBtn) |
| closeApiKeyModalBtn.addEventListener("click", () => |
| closeModal(apiKeyModal) |
| ); |
| if (cancelAddApiKeyBtn) |
| cancelAddApiKeyBtn.addEventListener("click", () => closeModal(apiKeyModal)); |
| if (confirmAddApiKeyBtn) |
| confirmAddApiKeyBtn.addEventListener("click", handleBulkAddApiKeys); |
| if (apiKeySearchInput) |
| apiKeySearchInput.addEventListener("input", handleApiKeySearch); |
|
|
| |
| const apiKeyPrevBtn = document.getElementById("apiKeyPrevBtn"); |
| const apiKeyNextBtn = document.getElementById("apiKeyNextBtn"); |
| |
| if (apiKeyPrevBtn) { |
| apiKeyPrevBtn.addEventListener("click", prevApiKeyPage); |
| } |
| if (apiKeyNextBtn) { |
| apiKeyNextBtn.addEventListener("click", nextApiKeyPage); |
| } |
|
|
| |
| const bulkDeleteApiKeyBtn = document.getElementById("bulkDeleteApiKeyBtn"); |
| const closeBulkDeleteModalBtn = document.getElementById( |
| "closeBulkDeleteModalBtn" |
| ); |
| const cancelBulkDeleteApiKeyBtn = document.getElementById( |
| "cancelBulkDeleteApiKeyBtn" |
| ); |
| const confirmBulkDeleteApiKeyBtn = document.getElementById( |
| "confirmBulkDeleteApiKeyBtn" |
| ); |
|
|
| if (bulkDeleteApiKeyBtn) { |
| bulkDeleteApiKeyBtn.addEventListener("click", () => { |
| openModal(bulkDeleteApiKeyModal); |
| if (bulkDeleteApiKeyInput) bulkDeleteApiKeyInput.value = ""; |
| }); |
| } |
| if (closeBulkDeleteModalBtn) |
| closeBulkDeleteModalBtn.addEventListener("click", () => |
| closeModal(bulkDeleteApiKeyModal) |
| ); |
| if (cancelBulkDeleteApiKeyBtn) |
| cancelBulkDeleteApiKeyBtn.addEventListener("click", () => |
| closeModal(bulkDeleteApiKeyModal) |
| ); |
| if (confirmBulkDeleteApiKeyBtn) |
| confirmBulkDeleteApiKeyBtn.addEventListener( |
| "click", |
| handleBulkDeleteApiKeys |
| ); |
|
|
| |
| const addProxyBtn = document.getElementById("addProxyBtn"); |
| const closeProxyModalBtn = document.getElementById("closeProxyModalBtn"); |
| const cancelAddProxyBtn = document.getElementById("cancelAddProxyBtn"); |
| const confirmAddProxyBtn = document.getElementById("confirmAddProxyBtn"); |
| |
| |
| const checkAllProxiesBtn = document.getElementById("checkAllProxiesBtn"); |
| const proxyCheckModal = document.getElementById("proxyCheckModal"); |
| const closeProxyCheckModalBtn = document.getElementById("closeProxyCheckModalBtn"); |
| const closeProxyCheckBtn = document.getElementById("closeProxyCheckBtn"); |
| const retryFailedProxiesBtn = document.getElementById("retryFailedProxiesBtn"); |
|
|
| if (addProxyBtn) { |
| addProxyBtn.addEventListener("click", () => { |
| openModal(proxyModal); |
| if (proxyBulkInput) proxyBulkInput.value = ""; |
| }); |
| } |
| |
| if (checkAllProxiesBtn) { |
| checkAllProxiesBtn.addEventListener("click", checkAllProxies); |
| } |
| |
| if (closeProxyCheckModalBtn) { |
| closeProxyCheckModalBtn.addEventListener("click", () => closeModal(proxyCheckModal)); |
| } |
| |
| if (closeProxyCheckBtn) { |
| closeProxyCheckBtn.addEventListener("click", () => closeModal(proxyCheckModal)); |
| } |
| |
| if (retryFailedProxiesBtn) { |
| retryFailedProxiesBtn.addEventListener("click", () => { |
| |
| checkAllProxies(); |
| }); |
| } |
| if (closeProxyModalBtn) |
| closeProxyModalBtn.addEventListener("click", () => closeModal(proxyModal)); |
| if (cancelAddProxyBtn) |
| cancelAddProxyBtn.addEventListener("click", () => closeModal(proxyModal)); |
| if (confirmAddProxyBtn) |
| confirmAddProxyBtn.addEventListener("click", handleBulkAddProxies); |
|
|
| |
| const bulkDeleteProxyBtn = document.getElementById("bulkDeleteProxyBtn"); |
| const closeBulkDeleteProxyModalBtn = document.getElementById( |
| "closeBulkDeleteProxyModalBtn" |
| ); |
| const cancelBulkDeleteProxyBtn = document.getElementById( |
| "cancelBulkDeleteProxyBtn" |
| ); |
| const confirmBulkDeleteProxyBtn = document.getElementById( |
| "confirmBulkDeleteProxyBtn" |
| ); |
|
|
| if (bulkDeleteProxyBtn) { |
| bulkDeleteProxyBtn.addEventListener("click", () => { |
| openModal(bulkDeleteProxyModal); |
| if (bulkDeleteProxyInput) bulkDeleteProxyInput.value = ""; |
| }); |
| } |
| if (closeBulkDeleteProxyModalBtn) |
| closeBulkDeleteProxyModalBtn.addEventListener("click", () => |
| closeModal(bulkDeleteProxyModal) |
| ); |
| if (cancelBulkDeleteProxyBtn) |
| cancelBulkDeleteProxyBtn.addEventListener("click", () => |
| closeModal(bulkDeleteProxyModal) |
| ); |
| if (confirmBulkDeleteProxyBtn) |
| confirmBulkDeleteProxyBtn.addEventListener( |
| "click", |
| handleBulkDeleteProxies |
| ); |
|
|
| |
| const closeResetModalBtn = document.getElementById("closeResetModalBtn"); |
| const cancelResetBtn = document.getElementById("cancelResetBtn"); |
| const confirmResetBtn = document.getElementById("confirmResetBtn"); |
|
|
| if (closeResetModalBtn) |
| closeResetModalBtn.addEventListener("click", () => |
| closeModal(resetConfirmModal) |
| ); |
| if (cancelResetBtn) |
| cancelResetBtn.addEventListener("click", () => |
| closeModal(resetConfirmModal) |
| ); |
| if (confirmResetBtn) { |
| confirmResetBtn.addEventListener("click", () => { |
| closeModal(resetConfirmModal); |
| executeReset(); |
| }); |
| } |
|
|
| |
| window.addEventListener("click", (event) => { |
| const modals = [ |
| apiKeyModal, |
| resetConfirmModal, |
| bulkDeleteApiKeyModal, |
| proxyModal, |
| bulkDeleteProxyModal, |
| vertexApiKeyModal, |
| bulkDeleteVertexApiKeyModal, |
| modelHelperModal, |
| ]; |
| modals.forEach((modal) => { |
| if (event.target === modal) { |
| closeModal(modal); |
| } |
| }); |
| }); |
|
|
| |
|
|
| |
| const generateAuthTokenBtn = document.getElementById("generateAuthTokenBtn"); |
| const authTokenInput = document.getElementById("AUTH_TOKEN"); |
| if (generateAuthTokenBtn && authTokenInput) { |
| generateAuthTokenBtn.addEventListener("click", function () { |
| const newToken = generateRandomToken(); |
| authTokenInput.value = newToken; |
| if (authTokenInput.classList.contains(SENSITIVE_INPUT_CLASS)) { |
| const event = new Event("focusout", { |
| bubbles: true, |
| cancelable: true, |
| }); |
| authTokenInput.dispatchEvent(event); |
| } |
| showNotification("已生成新认证令牌", "success"); |
| }); |
| } |
|
|
| |
| if (thinkingModelsContainer) { |
| thinkingModelsContainer.addEventListener("input", function (event) { |
| const target = event.target; |
| if ( |
| target && |
| target.classList.contains(ARRAY_INPUT_CLASS) && |
| target.closest(`.${ARRAY_ITEM_CLASS}[data-model-id]`) |
| ) { |
| const modelInput = target; |
| const modelItem = modelInput.closest(`.${ARRAY_ITEM_CLASS}`); |
| const modelId = modelItem.getAttribute("data-model-id"); |
| const budgetKeyInput = document.querySelector( |
| `.${MAP_KEY_INPUT_CLASS}[data-model-id="${modelId}"]` |
| ); |
| if (budgetKeyInput) { |
| budgetKeyInput.value = modelInput.value; |
| } |
| } |
| }); |
| } |
|
|
| |
| if (configForm) { |
| |
| configForm.addEventListener("click", function (event) { |
| const target = event.target; |
| const removeButton = target.closest(".remove-btn"); |
| const generateButton = target.closest(".generate-btn"); |
|
|
| if (removeButton && removeButton.closest(`.${ARRAY_ITEM_CLASS}`)) { |
| const arrayItem = removeButton.closest(`.${ARRAY_ITEM_CLASS}`); |
| const parentContainer = arrayItem.parentElement; |
| const isThinkingModelItem = |
| arrayItem.hasAttribute("data-model-id") && |
| parentContainer && |
| parentContainer.id === "THINKING_MODELS_container"; |
| const isSafetySettingItem = arrayItem.classList.contains( |
| SAFETY_SETTING_ITEM_CLASS |
| ); |
|
|
| if (isThinkingModelItem) { |
| const modelId = arrayItem.getAttribute("data-model-id"); |
| const budgetMapItem = document.querySelector( |
| `.${MAP_ITEM_CLASS}[data-model-id="${modelId}"]` |
| ); |
| if (budgetMapItem) { |
| budgetMapItem.remove(); |
| } |
| |
| const budgetContainer = document.getElementById( |
| "THINKING_BUDGET_MAP_container" |
| ); |
| if (budgetContainer && budgetContainer.children.length === 0) { |
| budgetContainer.innerHTML = |
| '<div class="text-gray-500 text-sm italic">请在上方添加思考模型,预算将自动关联。</div>'; |
| } |
| } |
| arrayItem.remove(); |
| |
| if ( |
| isSafetySettingItem && |
| parentContainer && |
| parentContainer.children.length === 0 |
| ) { |
| parentContainer.innerHTML = |
| '<div class="text-gray-500 text-sm italic">定义模型的安全过滤阈值。</div>'; |
| } |
| } else if ( |
| generateButton && |
| generateButton.closest(`.${ARRAY_ITEM_CLASS}`) |
| ) { |
| const inputField = generateButton |
| .closest(`.${ARRAY_ITEM_CLASS}`) |
| .querySelector(`.${ARRAY_INPUT_CLASS}`); |
| if (inputField) { |
| const newToken = generateRandomToken(); |
| inputField.value = newToken; |
| if (inputField.classList.contains(SENSITIVE_INPUT_CLASS)) { |
| const event = new Event("focusout", { |
| bubbles: true, |
| cancelable: true, |
| }); |
| inputField.dispatchEvent(event); |
| } |
| showNotification("已生成新令牌", "success"); |
| } |
| } |
| }); |
| } |
|
|
| |
| const addSafetySettingBtn = document.getElementById("addSafetySettingBtn"); |
| if (addSafetySettingBtn) { |
| addSafetySettingBtn.addEventListener("click", () => addSafetySettingItem()); |
| } |
|
|
| |
| const addCustomHeaderBtn = document.getElementById("addCustomHeaderBtn"); |
| if (addCustomHeaderBtn) { |
| addCustomHeaderBtn.addEventListener("click", () => addCustomHeaderItem()); |
| } |
|
|
| initializeSensitiveFields(); |
|
|
| |
| const addVertexApiKeyBtn = document.getElementById("addVertexApiKeyBtn"); |
| const closeVertexApiKeyModalBtn = document.getElementById( |
| "closeVertexApiKeyModalBtn" |
| ); |
| const cancelAddVertexApiKeyBtn = document.getElementById( |
| "cancelAddVertexApiKeyBtn" |
| ); |
| const confirmAddVertexApiKeyBtn = document.getElementById( |
| "confirmAddVertexApiKeyBtn" |
| ); |
| const bulkDeleteVertexApiKeyBtn = document.getElementById( |
| "bulkDeleteVertexApiKeyBtn" |
| ); |
| const closeBulkDeleteVertexModalBtn = document.getElementById( |
| "closeBulkDeleteVertexModalBtn" |
| ); |
| const cancelBulkDeleteVertexApiKeyBtn = document.getElementById( |
| "cancelBulkDeleteVertexApiKeyBtn" |
| ); |
| const confirmBulkDeleteVertexApiKeyBtn = document.getElementById( |
| "confirmBulkDeleteVertexApiKeyBtn" |
| ); |
|
|
| if (addVertexApiKeyBtn) { |
| addVertexApiKeyBtn.addEventListener("click", () => { |
| openModal(vertexApiKeyModal); |
| if (vertexApiKeyBulkInput) vertexApiKeyBulkInput.value = ""; |
| }); |
| } |
| if (closeVertexApiKeyModalBtn) |
| closeVertexApiKeyModalBtn.addEventListener("click", () => |
| closeModal(vertexApiKeyModal) |
| ); |
| if (cancelAddVertexApiKeyBtn) |
| cancelAddVertexApiKeyBtn.addEventListener("click", () => |
| closeModal(vertexApiKeyModal) |
| ); |
| if (confirmAddVertexApiKeyBtn) |
| confirmAddVertexApiKeyBtn.addEventListener( |
| "click", |
| handleBulkAddVertexApiKeys |
| ); |
|
|
| if (bulkDeleteVertexApiKeyBtn) { |
| bulkDeleteVertexApiKeyBtn.addEventListener("click", () => { |
| openModal(bulkDeleteVertexApiKeyModal); |
| if (bulkDeleteVertexApiKeyInput) bulkDeleteVertexApiKeyInput.value = ""; |
| }); |
| } |
| if (closeBulkDeleteVertexModalBtn) |
| closeBulkDeleteVertexModalBtn.addEventListener("click", () => |
| closeModal(bulkDeleteVertexApiKeyModal) |
| ); |
| if (cancelBulkDeleteVertexApiKeyBtn) |
| cancelBulkDeleteVertexApiKeyBtn.addEventListener("click", () => |
| closeModal(bulkDeleteVertexApiKeyModal) |
| ); |
| if (confirmBulkDeleteVertexApiKeyBtn) |
| confirmBulkDeleteVertexApiKeyBtn.addEventListener( |
| "click", |
| handleBulkDeleteVertexApiKeys |
| ); |
|
|
| |
| if (closeModelHelperModalBtn) { |
| closeModelHelperModalBtn.addEventListener("click", () => |
| closeModal(modelHelperModal) |
| ); |
| } |
| if (cancelModelHelperBtn) { |
| cancelModelHelperBtn.addEventListener("click", () => |
| closeModal(modelHelperModal) |
| ); |
| } |
| if (modelHelperSearchInput) { |
| modelHelperSearchInput.addEventListener("input", () => |
| renderModelsInModal() |
| ); |
| } |
|
|
| |
| const modelHelperTriggerBtns = document.querySelectorAll( |
| ".model-helper-trigger-btn" |
| ); |
| modelHelperTriggerBtns.forEach((btn) => { |
| btn.addEventListener("click", () => { |
| const targetInputId = btn.dataset.targetInputId; |
| const targetArrayKey = btn.dataset.targetArrayKey; |
|
|
| if (targetInputId) { |
| currentModelHelperTarget = { |
| type: "input", |
| target: document.getElementById(targetInputId), |
| }; |
| } else if (targetArrayKey) { |
| currentModelHelperTarget = { type: "array", targetKey: targetArrayKey }; |
| } |
| openModelHelperModal(); |
| }); |
| }); |
| }); |
|
|
| |
| |
| |
| function initializeSensitiveFields() { |
| if (!configForm) return; |
|
|
| |
| function maskField(field) { |
| if (field.value && field.value !== MASKED_VALUE) { |
| field.setAttribute("data-real-value", field.value); |
| field.value = MASKED_VALUE; |
| } else if (!field.value) { |
| |
| field.removeAttribute("data-real-value"); |
| |
| if (field.value === MASKED_VALUE) field.value = ""; |
| } |
| } |
|
|
| |
| function unmaskField(field) { |
| if (field.hasAttribute("data-real-value")) { |
| field.value = field.getAttribute("data-real-value"); |
| } |
| |
| else if ( |
| field.value === MASKED_VALUE && |
| !field.hasAttribute("data-real-value") |
| ) { |
| field.value = ""; |
| } |
| } |
|
|
| |
| |
| function initialMaskAllExisting() { |
| const sensitiveFields = configForm.querySelectorAll( |
| `.${SENSITIVE_INPUT_CLASS}` |
| ); |
| sensitiveFields.forEach((field) => { |
| if (field.type === "password") { |
| |
| |
| if (field.value) { |
| field.setAttribute("data-real-value", field.value); |
| } |
| |
| } else if ( |
| field.type === "text" || |
| field.tagName.toLowerCase() === "textarea" |
| ) { |
| maskField(field); |
| } |
| }); |
| } |
| initialMaskAllExisting(); |
|
|
| |
| configForm.addEventListener("focusin", function (event) { |
| const target = event.target; |
| if (target.classList.contains(SENSITIVE_INPUT_CLASS)) { |
| if (target.type === "password") { |
| |
| if (!target.hasAttribute("data-original-type")) { |
| target.setAttribute("data-original-type", "password"); |
| } |
| target.type = "text"; |
| |
| if (target.hasAttribute("data-real-value")) { |
| target.value = target.getAttribute("data-real-value"); |
| } |
| |
| } else { |
| |
| unmaskField(target); |
| } |
| } |
| }); |
|
|
| configForm.addEventListener("focusout", function (event) { |
| const target = event.target; |
| if (target.classList.contains(SENSITIVE_INPUT_CLASS)) { |
| |
| if ( |
| target.type === "text" || |
| target.tagName.toLowerCase() === "textarea" |
| ) { |
| if (target.value && target.value !== MASKED_VALUE) { |
| target.setAttribute("data-real-value", target.value); |
| } else if (!target.value) { |
| |
| target.removeAttribute("data-real-value"); |
| } |
| } |
|
|
| |
| if ( |
| target.getAttribute("data-original-type") === "password" && |
| target.type === "text" |
| ) { |
| target.type = "password"; |
| |
| |
| } else if ( |
| target.type === "text" || |
| target.tagName.toLowerCase() === "textarea" |
| ) { |
| |
| maskField(target); |
| } |
| } |
| }); |
| } |
|
|
| |
| |
| |
| |
| function generateUUID() { |
| return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function (c) { |
| var r = (Math.random() * 16) | 0, |
| v = c == "x" ? r : (r & 0x3) | 0x8; |
| return v.toString(16); |
| }); |
| } |
|
|
| |
| |
| |
| async function initConfig() { |
| try { |
| showNotification("正在加载配置...", "info"); |
| const response = await fetch("/api/config"); |
|
|
| if (!response.ok) { |
| throw new Error(`HTTP error! status: ${response.status}`); |
| } |
|
|
| const config = await response.json(); |
|
|
| |
| if ( |
| !config.API_KEYS || |
| !Array.isArray(config.API_KEYS) || |
| config.API_KEYS.length === 0 |
| ) { |
| config.API_KEYS = ["请在此处输入 API 密钥"]; |
| } |
|
|
| if ( |
| !config.ALLOWED_TOKENS || |
| !Array.isArray(config.ALLOWED_TOKENS) || |
| config.ALLOWED_TOKENS.length === 0 |
| ) { |
| config.ALLOWED_TOKENS = [""]; |
| } |
|
|
| if ( |
| !config.IMAGE_MODELS || |
| !Array.isArray(config.IMAGE_MODELS) || |
| config.IMAGE_MODELS.length === 0 |
| ) { |
| config.IMAGE_MODELS = ["gemini-1.5-pro-latest"]; |
| } |
|
|
| if ( |
| !config.SEARCH_MODELS || |
| !Array.isArray(config.SEARCH_MODELS) || |
| config.SEARCH_MODELS.length === 0 |
| ) { |
| config.SEARCH_MODELS = ["gemini-1.5-flash-latest"]; |
| } |
|
|
| if ( |
| !config.FILTERED_MODELS || |
| !Array.isArray(config.FILTERED_MODELS) || |
| config.FILTERED_MODELS.length === 0 |
| ) { |
| config.FILTERED_MODELS = ["gemini-1.0-pro-latest"]; |
| } |
| |
| if (!config.VERTEX_API_KEYS || !Array.isArray(config.VERTEX_API_KEYS)) { |
| config.VERTEX_API_KEYS = []; |
| } |
| |
| if (typeof config.VERTEX_EXPRESS_BASE_URL === "undefined") { |
| config.VERTEX_EXPRESS_BASE_URL = ""; |
| } |
| |
| if (!config.PROXIES || !Array.isArray(config.PROXIES)) { |
| config.PROXIES = []; |
| } |
| |
| if (!config.THINKING_MODELS || !Array.isArray(config.THINKING_MODELS)) { |
| config.THINKING_MODELS = []; |
| } |
| if ( |
| !config.THINKING_BUDGET_MAP || |
| typeof config.THINKING_BUDGET_MAP !== "object" || |
| config.THINKING_BUDGET_MAP === null |
| ) { |
| config.THINKING_BUDGET_MAP = {}; |
| } |
| |
| if ( |
| !config.CUSTOM_HEADERS || |
| typeof config.CUSTOM_HEADERS !== "object" || |
| config.CUSTOM_HEADERS === null |
| ) { |
| config.CUSTOM_HEADERS = {}; |
| } |
| |
| if (!config.SAFETY_SETTINGS || !Array.isArray(config.SAFETY_SETTINGS)) { |
| config.SAFETY_SETTINGS = []; |
| } |
| |
| if (typeof config.URL_CONTEXT_ENABLED === "undefined") { |
| config.URL_CONTEXT_ENABLED = true; |
| } |
| if (!config.URL_CONTEXT_MODELS || !Array.isArray(config.URL_CONTEXT_MODELS)) { |
| config.URL_CONTEXT_MODELS = []; |
| } |
| |
| |
| if (typeof config.AUTO_DELETE_ERROR_LOGS_ENABLED === "undefined") { |
| config.AUTO_DELETE_ERROR_LOGS_ENABLED = false; |
| } |
| if (typeof config.AUTO_DELETE_ERROR_LOGS_DAYS === "undefined") { |
| config.AUTO_DELETE_ERROR_LOGS_DAYS = 7; |
| } |
| |
|
|
| |
| if (typeof config.AUTO_DELETE_REQUEST_LOGS_ENABLED === "undefined") { |
| config.AUTO_DELETE_REQUEST_LOGS_ENABLED = false; |
| } |
| if (typeof config.AUTO_DELETE_REQUEST_LOGS_DAYS === "undefined") { |
| config.AUTO_DELETE_REQUEST_LOGS_DAYS = 30; |
| } |
| |
|
|
| |
| if (typeof config.FAKE_STREAM_ENABLED === "undefined") { |
| config.FAKE_STREAM_ENABLED = false; |
| } |
| if (typeof config.FAKE_STREAM_EMPTY_DATA_INTERVAL_SECONDS === "undefined") { |
| config.FAKE_STREAM_EMPTY_DATA_INTERVAL_SECONDS = 5; |
| } |
| |
|
|
| populateForm(config); |
| |
| if (configForm) { |
| |
| initializeSensitiveFields(); |
| } |
|
|
| |
| const uploadProvider = document.getElementById("UPLOAD_PROVIDER"); |
| if (uploadProvider && !uploadProvider.value) { |
| uploadProvider.value = "smms"; |
| toggleProviderConfig("smms"); |
| } |
|
|
| showNotification("配置加载成功", "success"); |
| } catch (error) { |
| console.error("加载配置失败:", error); |
| showNotification("加载配置失败: " + error.message, "error"); |
|
|
| |
| const defaultConfig = { |
| API_KEYS: [""], |
| ALLOWED_TOKENS: [""], |
| IMAGE_MODELS: ["gemini-1.5-pro-latest"], |
| SEARCH_MODELS: ["gemini-1.5-flash-latest"], |
| FILTERED_MODELS: ["gemini-1.0-pro-latest"], |
| UPLOAD_PROVIDER: "smms", |
| PROXIES: [], |
| VERTEX_API_KEYS: [], |
| VERTEX_EXPRESS_BASE_URL: "", |
| THINKING_MODELS: [], |
| THINKING_BUDGET_MAP: {}, |
| CUSTOM_HEADERS: {}, |
| AUTO_DELETE_ERROR_LOGS_ENABLED: false, |
| AUTO_DELETE_ERROR_LOGS_DAYS: 7, |
| AUTO_DELETE_REQUEST_LOGS_ENABLED: false, |
| AUTO_DELETE_REQUEST_LOGS_DAYS: 30, |
| |
| FAKE_STREAM_ENABLED: false, |
| FAKE_STREAM_EMPTY_DATA_INTERVAL_SECONDS: 5, |
| |
| }; |
|
|
| populateForm(defaultConfig); |
| if (configForm) { |
| |
| initializeSensitiveFields(); |
| } |
| toggleProviderConfig("smms"); |
| } |
| } |
|
|
| |
| |
| |
| |
| function populateForm(config) { |
| const modelIdMap = {}; |
|
|
| |
| const arrayContainers = document.querySelectorAll(".array-container"); |
| arrayContainers.forEach((container) => { |
| container.innerHTML = ""; |
| }); |
| const budgetMapContainer = document.getElementById( |
| "THINKING_BUDGET_MAP_container" |
| ); |
| if (budgetMapContainer) { |
| budgetMapContainer.innerHTML = ""; |
| } else { |
| console.error("Critical: THINKING_BUDGET_MAP_container not found!"); |
| return; |
| } |
|
|
| |
| if (Array.isArray(config.THINKING_MODELS)) { |
| const container = document.getElementById("THINKING_MODELS_container"); |
| if (container) { |
| config.THINKING_MODELS.forEach((modelName) => { |
| if (modelName && typeof modelName === "string" && modelName.trim()) { |
| const trimmedModelName = modelName.trim(); |
| const modelId = addArrayItemWithValue( |
| "THINKING_MODELS", |
| trimmedModelName |
| ); |
| if (modelId) { |
| modelIdMap[trimmedModelName] = modelId; |
| } else { |
| console.warn( |
| `Failed to get modelId for THINKING_MODEL: '${trimmedModelName}'` |
| ); |
| } |
| } else { |
| console.warn(`Invalid THINKING_MODEL entry found:`, modelName); |
| } |
| }); |
| } else { |
| console.error("Critical: THINKING_MODELS_container not found!"); |
| } |
| } |
|
|
| |
| let budgetItemsAdded = false; |
| if ( |
| config.THINKING_BUDGET_MAP && |
| typeof config.THINKING_BUDGET_MAP === "object" |
| ) { |
| for (const [modelName, budgetValue] of Object.entries( |
| config.THINKING_BUDGET_MAP |
| )) { |
| if (modelName && typeof modelName === "string") { |
| const trimmedModelName = modelName.trim(); |
| const modelId = modelIdMap[trimmedModelName]; |
| if (modelId) { |
| createAndAppendBudgetMapItem(trimmedModelName, budgetValue, modelId); |
| budgetItemsAdded = true; |
| } else { |
| console.warn( |
| `Budget map: Could not find model ID for '${trimmedModelName}'. Skipping budget item.` |
| ); |
| } |
| } else { |
| console.warn(`Invalid key found in THINKING_BUDGET_MAP:`, modelName); |
| } |
| } |
| } |
| if (!budgetItemsAdded && budgetMapContainer) { |
| budgetMapContainer.innerHTML = |
| '<div class="text-gray-500 text-sm italic">请在上方添加思考模型,预算将自动关联。</div>'; |
| } |
|
|
| |
| const customHeadersContainer = document.getElementById( |
| "CUSTOM_HEADERS_container" |
| ); |
| let customHeadersAdded = false; |
| if ( |
| customHeadersContainer && |
| config.CUSTOM_HEADERS && |
| typeof config.CUSTOM_HEADERS === "object" |
| ) { |
| for (const [key, value] of Object.entries(config.CUSTOM_HEADERS)) { |
| createAndAppendCustomHeaderItem(key, value); |
| customHeadersAdded = true; |
| } |
| } |
| if (!customHeadersAdded && customHeadersContainer) { |
| customHeadersContainer.innerHTML = |
| '<div class="text-gray-500 text-sm italic">添加自定义请求头,例如 X-Api-Key: your-key</div>'; |
| } |
|
|
| |
| for (const [key, value] of Object.entries(config)) { |
| if (Array.isArray(value) && key !== "THINKING_MODELS" && key !== "API_KEYS") { |
| const container = document.getElementById(`${key}_container`); |
| if (container) { |
| value.forEach((itemValue) => { |
| if (typeof itemValue === "string") { |
| addArrayItemWithValue(key, itemValue); |
| } else { |
| console.warn(`Invalid item found in array '${key}':`, itemValue); |
| } |
| }); |
| } |
| } |
| } |
|
|
| |
| if (Array.isArray(config.API_KEYS)) { |
| allApiKeys = config.API_KEYS.filter(key => |
| typeof key === "string" && key.trim() !== "" |
| ); |
| filteredApiKeys = [...allApiKeys]; |
| currentApiKeyPage = 1; |
| renderApiKeyPage(); |
| updateApiKeyPagination(); |
| } |
|
|
| |
| for (const [key, value] of Object.entries(config)) { |
| if ( |
| !Array.isArray(value) && |
| !( |
| typeof value === "object" && |
| value !== null && |
| key === "THINKING_BUDGET_MAP" |
| ) |
| ) { |
| const element = document.getElementById(key); |
| if (element) { |
| if (element.type === "checkbox" && typeof value === "boolean") { |
| element.checked = value; |
| } else if (element.type !== "checkbox") { |
| if (key === "LOG_LEVEL" && typeof value === "string") { |
| element.value = value.toUpperCase(); |
| } else { |
| element.value = value !== null && value !== undefined ? value : ""; |
| } |
| } |
| } |
| } |
| } |
|
|
| |
| const uploadProvider = document.getElementById("UPLOAD_PROVIDER"); |
| if (uploadProvider) { |
| toggleProviderConfig(uploadProvider.value); |
| } |
|
|
| |
| let safetyItemsAdded = false; |
| if (safetySettingsContainer && Array.isArray(config.SAFETY_SETTINGS)) { |
| config.SAFETY_SETTINGS.forEach((setting) => { |
| if ( |
| setting && |
| typeof setting === "object" && |
| setting.category && |
| setting.threshold |
| ) { |
| addSafetySettingItem(setting.category, setting.threshold); |
| safetyItemsAdded = true; |
| } else { |
| console.warn("Invalid safety setting item found:", setting); |
| } |
| }); |
| } |
| if (safetySettingsContainer && !safetyItemsAdded) { |
| safetySettingsContainer.innerHTML = |
| '<div class="text-gray-500 text-sm italic">定义模型的安全过滤阈值。</div>'; |
| } |
|
|
| |
| const autoDeleteEnabledCheckbox = document.getElementById( |
| "AUTO_DELETE_ERROR_LOGS_ENABLED" |
| ); |
| const autoDeleteDaysSelect = document.getElementById( |
| "AUTO_DELETE_ERROR_LOGS_DAYS" |
| ); |
|
|
| if (autoDeleteEnabledCheckbox && autoDeleteDaysSelect) { |
| autoDeleteEnabledCheckbox.checked = !!config.AUTO_DELETE_ERROR_LOGS_ENABLED; |
| autoDeleteDaysSelect.value = config.AUTO_DELETE_ERROR_LOGS_DAYS || 7; |
|
|
| |
| autoDeleteDaysSelect.disabled = !autoDeleteEnabledCheckbox.checked; |
|
|
| |
| autoDeleteEnabledCheckbox.addEventListener("change", function () { |
| autoDeleteDaysSelect.disabled = !this.checked; |
| }); |
| } |
| |
|
|
| |
| const autoDeleteRequestEnabledCheckbox = document.getElementById( |
| "AUTO_DELETE_REQUEST_LOGS_ENABLED" |
| ); |
| const autoDeleteRequestDaysSelect = document.getElementById( |
| "AUTO_DELETE_REQUEST_LOGS_DAYS" |
| ); |
|
|
| if (autoDeleteRequestEnabledCheckbox && autoDeleteRequestDaysSelect) { |
| autoDeleteRequestEnabledCheckbox.checked = |
| !!config.AUTO_DELETE_REQUEST_LOGS_ENABLED; |
| autoDeleteRequestDaysSelect.value = |
| config.AUTO_DELETE_REQUEST_LOGS_DAYS || 30; |
| autoDeleteRequestDaysSelect.disabled = |
| !autoDeleteRequestEnabledCheckbox.checked; |
|
|
| autoDeleteRequestEnabledCheckbox.addEventListener("change", function () { |
| autoDeleteRequestDaysSelect.disabled = !this.checked; |
| }); |
| } |
| |
|
|
| |
| const fakeStreamEnabledCheckbox = document.getElementById( |
| "FAKE_STREAM_ENABLED" |
| ); |
| const fakeStreamIntervalInput = document.getElementById( |
| "FAKE_STREAM_EMPTY_DATA_INTERVAL_SECONDS" |
| ); |
|
|
| if (fakeStreamEnabledCheckbox && fakeStreamIntervalInput) { |
| fakeStreamEnabledCheckbox.checked = !!config.FAKE_STREAM_ENABLED; |
| fakeStreamIntervalInput.value = |
| config.FAKE_STREAM_EMPTY_DATA_INTERVAL_SECONDS || 5; |
| |
| |
| |
| |
| |
| } |
| |
| } |
|
|
| |
| |
| |
| function handleBulkAddApiKeys() { |
| if (!apiKeyBulkInput || !apiKeyModal) return; |
|
|
| const bulkText = apiKeyBulkInput.value; |
| const extractedKeys = bulkText.match(API_KEY_REGEX) || []; |
|
|
| |
| const combinedKeys = new Set([...allApiKeys, ...extractedKeys]); |
| const uniqueKeys = Array.from(combinedKeys); |
|
|
| |
| allApiKeys = uniqueKeys; |
| |
| |
| const searchTerm = apiKeySearchInput ? apiKeySearchInput.value.toLowerCase() : ""; |
| if (!searchTerm) { |
| filteredApiKeys = [...allApiKeys]; |
| } else { |
| filteredApiKeys = allApiKeys.filter(key => |
| key.toLowerCase().includes(searchTerm) |
| ); |
| } |
|
|
| |
| renderApiKeyPage(); |
| updateApiKeyPagination(); |
|
|
| closeModal(apiKeyModal); |
| showNotification(`添加/更新了 ${uniqueKeys.length} 个唯一密钥`, "success"); |
| } |
|
|
| |
| |
| |
| function handleApiKeySearch() { |
| if (!apiKeySearchInput) return; |
|
|
| const searchTerm = apiKeySearchInput.value.toLowerCase(); |
| |
| |
| if (!searchTerm) { |
| filteredApiKeys = [...allApiKeys]; |
| } else { |
| filteredApiKeys = allApiKeys.filter(key => |
| key.toLowerCase().includes(searchTerm) |
| ); |
| } |
|
|
| |
| currentApiKeyPage = 1; |
| |
| |
| renderApiKeyPage(); |
| updateApiKeyPagination(); |
| } |
|
|
| |
| |
| |
| function renderApiKeyPage() { |
| const apiKeyContainer = document.getElementById("API_KEYS_container"); |
| if (!apiKeyContainer) return; |
|
|
| |
| apiKeyContainer.innerHTML = ""; |
|
|
| |
| const startIndex = (currentApiKeyPage - 1) * API_KEYS_PER_PAGE; |
| const endIndex = Math.min(startIndex + API_KEYS_PER_PAGE, filteredApiKeys.length); |
| const pageKeys = filteredApiKeys.slice(startIndex, endIndex); |
|
|
| |
| pageKeys.forEach((key) => { |
| addArrayItemWithValue("API_KEYS", key); |
| }); |
|
|
| |
| if (pageKeys.length === 0) { |
| const emptyMessage = document.createElement("div"); |
| emptyMessage.className = "text-gray-500 text-sm italic text-center py-4"; |
| emptyMessage.textContent = filteredApiKeys.length === 0 ? |
| (allApiKeys.length === 0 ? "暂无API密钥" : "未找到匹配的密钥") : |
| "当前页无数据"; |
| apiKeyContainer.appendChild(emptyMessage); |
| } |
| } |
|
|
| |
| |
| |
| function updateApiKeyPagination() { |
| totalApiKeyPages = Math.max(1, Math.ceil(filteredApiKeys.length / API_KEYS_PER_PAGE)); |
| |
| |
| if (currentApiKeyPage > totalApiKeyPages) { |
| currentApiKeyPage = totalApiKeyPages; |
| } |
|
|
| const paginationContainer = document.getElementById("apiKeyPagination"); |
| if (!paginationContainer) return; |
|
|
| |
| if (totalApiKeyPages <= 1) { |
| paginationContainer.style.display = "none"; |
| return; |
| } |
|
|
| paginationContainer.style.display = "flex"; |
|
|
| |
| const pageInfo = document.getElementById("apiKeyPageInfo"); |
| if (pageInfo) { |
| pageInfo.textContent = `第 ${currentApiKeyPage} 页,共 ${totalApiKeyPages} 页 (${filteredApiKeys.length} 个密钥)`; |
| } |
|
|
| |
| const prevBtn = document.getElementById("apiKeyPrevBtn"); |
| const nextBtn = document.getElementById("apiKeyNextBtn"); |
| |
| if (prevBtn) { |
| prevBtn.disabled = currentApiKeyPage <= 1; |
| prevBtn.className = currentApiKeyPage <= 1 ? |
| "px-3 py-1 rounded bg-gray-300 text-gray-500 cursor-not-allowed" : |
| "px-3 py-1 rounded bg-blue-500 text-white hover:bg-blue-600 cursor-pointer"; |
| } |
| |
| if (nextBtn) { |
| nextBtn.disabled = currentApiKeyPage >= totalApiKeyPages; |
| nextBtn.className = currentApiKeyPage >= totalApiKeyPages ? |
| "px-3 py-1 rounded bg-gray-300 text-gray-500 cursor-not-allowed" : |
| "px-3 py-1 rounded bg-blue-500 text-white hover:bg-blue-600 cursor-pointer"; |
| } |
| } |
|
|
| |
| |
| |
| function goToApiKeyPage(page) { |
| if (page < 1 || page > totalApiKeyPages) return; |
| |
| currentApiKeyPage = page; |
| renderApiKeyPage(); |
| updateApiKeyPagination(); |
| } |
|
|
| |
| |
| |
| function prevApiKeyPage() { |
| if (currentApiKeyPage > 1) { |
| goToApiKeyPage(currentApiKeyPage - 1); |
| } |
| } |
|
|
| |
| |
| |
| function nextApiKeyPage() { |
| if (currentApiKeyPage < totalApiKeyPages) { |
| goToApiKeyPage(currentApiKeyPage + 1); |
| } |
| } |
|
|
| |
| |
| |
| function handleBulkDeleteApiKeys() { |
| if (!bulkDeleteApiKeyInput || !bulkDeleteApiKeyModal) return; |
|
|
| const bulkText = bulkDeleteApiKeyInput.value; |
| if (!bulkText.trim()) { |
| showNotification("请粘贴需要删除的 API 密钥", "warning"); |
| return; |
| } |
|
|
| const keysToDelete = new Set(bulkText.match(API_KEY_REGEX) || []); |
|
|
| if (keysToDelete.size === 0) { |
| showNotification("未在输入内容中提取到有效的 API 密钥格式", "warning"); |
| return; |
| } |
|
|
| |
| let deleteCount = 0; |
| allApiKeys = allApiKeys.filter(key => { |
| if (keysToDelete.has(key)) { |
| deleteCount++; |
| return false; |
| } |
| return true; |
| }); |
|
|
| |
| const searchTerm = apiKeySearchInput ? apiKeySearchInput.value.toLowerCase() : ""; |
| if (!searchTerm) { |
| filteredApiKeys = [...allApiKeys]; |
| } else { |
| filteredApiKeys = allApiKeys.filter(key => |
| key.toLowerCase().includes(searchTerm) |
| ); |
| } |
|
|
| |
| renderApiKeyPage(); |
| updateApiKeyPagination(); |
|
|
| closeModal(bulkDeleteApiKeyModal); |
|
|
| if (deleteCount > 0) { |
| showNotification(`成功删除了 ${deleteCount} 个匹配的密钥`, "success"); |
| } else { |
| showNotification("列表中未找到您输入的任何密钥进行删除", "info"); |
| } |
| bulkDeleteApiKeyInput.value = ""; |
| } |
|
|
| |
| |
| |
| function handleBulkAddProxies() { |
| const proxyContainer = document.getElementById("PROXIES_container"); |
| if (!proxyBulkInput || !proxyContainer || !proxyModal) return; |
|
|
| const bulkText = proxyBulkInput.value; |
| const extractedProxies = bulkText.match(PROXY_REGEX) || []; |
|
|
| const currentProxyInputs = proxyContainer.querySelectorAll( |
| `.${ARRAY_INPUT_CLASS}` |
| ); |
| const currentProxies = Array.from(currentProxyInputs) |
| .map((input) => input.value) |
| .filter((proxy) => proxy.trim() !== ""); |
|
|
| const combinedProxies = new Set([...currentProxies, ...extractedProxies]); |
| const uniqueProxies = Array.from(combinedProxies); |
|
|
| proxyContainer.innerHTML = ""; |
|
|
| uniqueProxies.forEach((proxy) => { |
| addArrayItemWithValue("PROXIES", proxy); |
| }); |
|
|
| closeModal(proxyModal); |
| showNotification(`添加/更新了 ${uniqueProxies.length} 个唯一代理`, "success"); |
| } |
|
|
| |
| |
| |
| function handleBulkDeleteProxies() { |
| const proxyContainer = document.getElementById("PROXIES_container"); |
| if (!bulkDeleteProxyInput || !proxyContainer || !bulkDeleteProxyModal) return; |
|
|
| const bulkText = bulkDeleteProxyInput.value; |
| if (!bulkText.trim()) { |
| showNotification("请粘贴需要删除的代理地址", "warning"); |
| return; |
| } |
|
|
| const proxiesToDelete = new Set(bulkText.match(PROXY_REGEX) || []); |
|
|
| if (proxiesToDelete.size === 0) { |
| showNotification("未在输入内容中提取到有效的代理地址格式", "warning"); |
| return; |
| } |
|
|
| const proxyItems = proxyContainer.querySelectorAll(`.${ARRAY_ITEM_CLASS}`); |
| let deleteCount = 0; |
|
|
| proxyItems.forEach((item) => { |
| const input = item.querySelector(`.${ARRAY_INPUT_CLASS}`); |
| if (input && proxiesToDelete.has(input.value)) { |
| item.remove(); |
| deleteCount++; |
| } |
| }); |
|
|
| closeModal(bulkDeleteProxyModal); |
|
|
| if (deleteCount > 0) { |
| showNotification(`成功删除了 ${deleteCount} 个匹配的代理`, "success"); |
| } else { |
| showNotification("列表中未找到您输入的任何代理进行删除", "info"); |
| } |
| bulkDeleteProxyInput.value = ""; |
| } |
|
|
| |
| |
| |
| function handleBulkAddVertexApiKeys() { |
| const vertexApiKeyContainer = document.getElementById( |
| "VERTEX_API_KEYS_container" |
| ); |
| if (!vertexApiKeyBulkInput || !vertexApiKeyContainer || !vertexApiKeyModal) { |
| return; |
| } |
|
|
| const bulkText = vertexApiKeyBulkInput.value; |
| const extractedKeys = bulkText.match(VERTEX_API_KEY_REGEX) || []; |
|
|
| const currentKeyInputs = vertexApiKeyContainer.querySelectorAll( |
| `.${ARRAY_INPUT_CLASS}.${SENSITIVE_INPUT_CLASS}` |
| ); |
| let currentKeys = Array.from(currentKeyInputs) |
| .map((input) => { |
| return input.hasAttribute("data-real-value") |
| ? input.getAttribute("data-real-value") |
| : input.value; |
| }) |
| .filter((key) => key && key.trim() !== "" && key !== MASKED_VALUE); |
|
|
| const combinedKeys = new Set([...currentKeys, ...extractedKeys]); |
| const uniqueKeys = Array.from(combinedKeys); |
|
|
| vertexApiKeyContainer.innerHTML = ""; |
|
|
| uniqueKeys.forEach((key) => { |
| addArrayItemWithValue("VERTEX_API_KEYS", key); |
| }); |
|
|
| |
| const newKeyInputs = vertexApiKeyContainer.querySelectorAll( |
| `.${ARRAY_INPUT_CLASS}.${SENSITIVE_INPUT_CLASS}` |
| ); |
| newKeyInputs.forEach((input) => { |
| if (configForm && typeof initializeSensitiveFields === "function") { |
| const focusoutEvent = new Event("focusout", { |
| bubbles: true, |
| cancelable: true, |
| }); |
| input.dispatchEvent(focusoutEvent); |
| } |
| }); |
|
|
| closeModal(vertexApiKeyModal); |
| showNotification( |
| `添加/更新了 ${uniqueKeys.length} 个唯一 Vertex 密钥`, |
| "success" |
| ); |
| vertexApiKeyBulkInput.value = ""; |
| } |
|
|
| |
| |
| |
| function handleBulkDeleteVertexApiKeys() { |
| const vertexApiKeyContainer = document.getElementById( |
| "VERTEX_API_KEYS_container" |
| ); |
| if ( |
| !bulkDeleteVertexApiKeyInput || |
| !vertexApiKeyContainer || |
| !bulkDeleteVertexApiKeyModal |
| ) { |
| return; |
| } |
|
|
| const bulkText = bulkDeleteVertexApiKeyInput.value; |
| if (!bulkText.trim()) { |
| showNotification("请粘贴需要删除的 Vertex Express API 密钥", "warning"); |
| return; |
| } |
|
|
| const keysToDelete = new Set(bulkText.match(VERTEX_API_KEY_REGEX) || []); |
|
|
| if (keysToDelete.size === 0) { |
| showNotification( |
| "未在输入内容中提取到有效的 Vertex Express API 密钥格式", |
| "warning" |
| ); |
| return; |
| } |
|
|
| const keyItems = vertexApiKeyContainer.querySelectorAll( |
| `.${ARRAY_ITEM_CLASS}` |
| ); |
| let deleteCount = 0; |
|
|
| keyItems.forEach((item) => { |
| const input = item.querySelector( |
| `.${ARRAY_INPUT_CLASS}.${SENSITIVE_INPUT_CLASS}` |
| ); |
| const realValue = |
| input && |
| (input.hasAttribute("data-real-value") |
| ? input.getAttribute("data-real-value") |
| : input.value); |
| if (realValue && keysToDelete.has(realValue)) { |
| item.remove(); |
| deleteCount++; |
| } |
| }); |
|
|
| closeModal(bulkDeleteVertexApiKeyModal); |
|
|
| if (deleteCount > 0) { |
| showNotification( |
| `成功删除了 ${deleteCount} 个匹配的 Vertex 密钥`, |
| "success" |
| ); |
| } else { |
| showNotification("列表中未找到您输入的任何 Vertex 密钥进行删除", "info"); |
| } |
| bulkDeleteVertexApiKeyInput.value = ""; |
| } |
|
|
| |
| |
| |
| |
| function switchTab(tabId) { |
| console.log(`Switching to tab: ${tabId}`); |
|
|
| |
| const activeStyle = |
| "background-color: #3b82f6 !important; color: #ffffff !important; border: 2px solid #2563eb !important; box-shadow: 0 4px 12px -2px rgba(59, 130, 246, 0.4), 0 2px 6px -1px rgba(59, 130, 246, 0.2) !important; transform: translateY(-2px) !important; font-weight: 600 !important;"; |
| const inactiveStyle = |
| "background-color: #f8fafc !important; color: #64748b !important; border: 2px solid #e2e8f0 !important; box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1) !important; font-weight: 500 !important; transform: none !important;"; |
|
|
| |
| const tabButtons = document.querySelectorAll(".tab-btn"); |
| console.log(`Found ${tabButtons.length} tab buttons`); |
|
|
| tabButtons.forEach((button) => { |
| const buttonTabId = button.getAttribute("data-tab"); |
| if (buttonTabId === tabId) { |
| |
| button.classList.add("active"); |
| button.setAttribute("style", activeStyle); |
| console.log(`Applied active style to button: ${buttonTabId}`); |
| } else { |
| |
| button.classList.remove("active"); |
| button.setAttribute("style", inactiveStyle); |
| console.log(`Applied inactive style to button: ${buttonTabId}`); |
| } |
| }); |
|
|
| |
| const sections = document.querySelectorAll(".config-section"); |
| sections.forEach((section) => { |
| if (section.id === `${tabId}-section`) { |
| section.classList.add("active"); |
| } else { |
| section.classList.remove("active"); |
| } |
| }); |
| } |
|
|
| |
| |
| |
| |
| function toggleProviderConfig(provider) { |
| const providerConfigs = document.querySelectorAll(".provider-config"); |
| providerConfigs.forEach((config) => { |
| if (config.getAttribute("data-provider") === provider) { |
| config.classList.add("active"); |
| } else { |
| config.classList.remove("active"); |
| } |
| }); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| function createArrayInput(key, value, isSensitive, modelId = null) { |
| const input = document.createElement("input"); |
| input.type = "text"; |
| input.name = `${key}[]`; |
| input.value = value; |
| let inputClasses = `${ARRAY_INPUT_CLASS} flex-grow px-3 py-2 border-none rounded-l-md focus:outline-none form-input-themed`; |
| if (isSensitive) { |
| inputClasses += ` ${SENSITIVE_INPUT_CLASS}`; |
| } |
| input.className = inputClasses; |
| if (modelId) { |
| input.setAttribute("data-model-id", modelId); |
| input.placeholder = "思考模型名称"; |
| } |
| return input; |
| } |
|
|
| |
| |
| |
| |
| function createGenerateTokenButton() { |
| const generateBtn = document.createElement("button"); |
| generateBtn.type = "button"; |
| generateBtn.className = |
| "generate-btn px-2 py-2 text-gray-500 hover:text-primary-600 focus:outline-none rounded-r-md bg-gray-100 hover:bg-gray-200 transition-colors"; |
| generateBtn.innerHTML = '<i class="fas fa-dice"></i>'; |
| generateBtn.title = "生成随机令牌"; |
| |
| return generateBtn; |
| } |
|
|
| |
| |
| |
| |
| function createRemoveButton() { |
| const removeBtn = document.createElement("button"); |
| removeBtn.type = "button"; |
| removeBtn.className = |
| "remove-btn text-gray-400 hover:text-red-500 focus:outline-none transition-colors duration-150"; |
| removeBtn.innerHTML = '<i class="fas fa-trash-alt"></i>'; |
| removeBtn.title = "删除"; |
| |
| return removeBtn; |
| } |
|
|
| |
| |
| |
| |
| function createProxyStatusIcon() { |
| const statusIcon = document.createElement("span"); |
| statusIcon.className = "proxy-status-icon px-2 py-2 text-gray-400"; |
| statusIcon.innerHTML = '<i class="fas fa-question-circle" title="未检测"></i>'; |
| statusIcon.setAttribute("data-status", "unknown"); |
| return statusIcon; |
| } |
|
|
| |
| |
| |
| |
| function createProxyCheckButton() { |
| const checkBtn = document.createElement("button"); |
| checkBtn.type = "button"; |
| checkBtn.className = |
| "proxy-check-btn px-2 py-2 text-blue-500 hover:text-blue-700 focus:outline-none transition-colors duration-150 rounded-r-md"; |
| checkBtn.innerHTML = '<i class="fas fa-globe"></i>'; |
| checkBtn.title = "检测此代理"; |
| |
| |
| checkBtn.addEventListener("click", function(e) { |
| e.preventDefault(); |
| e.stopPropagation(); |
| const inputElement = this.closest('.flex').querySelector('.array-input'); |
| if (inputElement && inputElement.value.trim()) { |
| checkSingleProxy(inputElement.value.trim(), this); |
| } else { |
| showNotification("请先输入代理地址", "warning"); |
| } |
| }); |
| |
| return checkBtn; |
| } |
|
|
| |
| |
| |
| |
| |
| function addArrayItem(key) { |
| const container = document.getElementById(`${key}_container`); |
| if (!container) return; |
|
|
| const newItemValue = ""; |
| const modelId = addArrayItemWithValue(key, newItemValue); |
|
|
| if (key === "THINKING_MODELS" && modelId) { |
| createAndAppendBudgetMapItem(newItemValue, -1, modelId); |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| function addArrayItemWithValue(key, value) { |
| const container = document.getElementById(`${key}_container`); |
| if (!container) return null; |
|
|
| const isThinkingModel = key === "THINKING_MODELS"; |
| const isAllowedToken = key === "ALLOWED_TOKENS"; |
| const isVertexApiKey = key === "VERTEX_API_KEYS"; |
| const isProxy = key === "PROXIES"; |
| const isSensitive = key === "API_KEYS" || isAllowedToken || isVertexApiKey; |
| const modelId = isThinkingModel ? generateUUID() : null; |
|
|
| const arrayItem = document.createElement("div"); |
| arrayItem.className = `${ARRAY_ITEM_CLASS} flex items-center mb-2 gap-2`; |
| if (isThinkingModel) { |
| arrayItem.setAttribute("data-model-id", modelId); |
| } |
|
|
| const inputWrapper = document.createElement("div"); |
| inputWrapper.className = |
| "flex items-center flex-grow rounded-md focus-within:border-blue-500 focus-within:ring focus-within:ring-blue-500 focus-within:ring-opacity-50"; |
| |
| inputWrapper.style.border = "1px solid rgba(0, 0, 0, 0.12)"; |
| inputWrapper.style.backgroundColor = "transparent"; |
|
|
| const input = createArrayInput( |
| key, |
| value, |
| isSensitive, |
| isThinkingModel ? modelId : null |
| ); |
| inputWrapper.appendChild(input); |
|
|
| if (isAllowedToken) { |
| const generateBtn = createGenerateTokenButton(); |
| inputWrapper.appendChild(generateBtn); |
| } else if (isProxy) { |
| |
| const proxyStatusIcon = createProxyStatusIcon(); |
| inputWrapper.appendChild(proxyStatusIcon); |
| |
| const proxyCheckBtn = createProxyCheckButton(); |
| inputWrapper.appendChild(proxyCheckBtn); |
| } else { |
| |
| input.classList.add("rounded-r-md"); |
| } |
|
|
| const removeBtn = createRemoveButton(); |
|
|
| arrayItem.appendChild(inputWrapper); |
| arrayItem.appendChild(removeBtn); |
| container.appendChild(arrayItem); |
|
|
| |
| if (isSensitive && input.value) { |
| if (configForm && typeof initializeSensitiveFields === "function") { |
| const focusoutEvent = new Event("focusout", { |
| bubbles: true, |
| cancelable: true, |
| }); |
| input.dispatchEvent(focusoutEvent); |
| } |
| } |
| return isThinkingModel ? modelId : null; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| function createAndAppendBudgetMapItem(mapKey, mapValue, modelId) { |
| const container = document.getElementById("THINKING_BUDGET_MAP_container"); |
| if (!container) { |
| console.error( |
| "Cannot add budget item: THINKING_BUDGET_MAP_container not found!" |
| ); |
| return; |
| } |
|
|
| |
| const placeholder = container.querySelector(".text-gray-500.italic"); |
| |
| if ( |
| placeholder && |
| container.children.length === 1 && |
| container.firstChild === placeholder |
| ) { |
| container.innerHTML = ""; |
| } |
|
|
| const mapItem = document.createElement("div"); |
| mapItem.className = `${MAP_ITEM_CLASS} flex items-center mb-2 gap-2`; |
| mapItem.setAttribute("data-model-id", modelId); |
|
|
| const keyInput = document.createElement("input"); |
| keyInput.type = "text"; |
| keyInput.value = mapKey; |
| keyInput.placeholder = "模型名称 (自动关联)"; |
| keyInput.readOnly = true; |
| keyInput.className = `${MAP_KEY_INPUT_CLASS} flex-grow px-3 py-2 border border-gray-300 rounded-md focus:outline-none bg-gray-100 text-gray-500`; |
| keyInput.setAttribute("data-model-id", modelId); |
|
|
| const valueInput = document.createElement("input"); |
| valueInput.type = "number"; |
| const intValue = parseInt(mapValue, 10); |
| valueInput.value = isNaN(intValue) ? -1 : intValue; |
| valueInput.placeholder = "预算 (整数)"; |
| valueInput.className = `${MAP_VALUE_INPUT_CLASS} w-24 px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:border-primary-500 focus:ring focus:ring-primary-200 focus:ring-opacity-50`; |
| valueInput.min = -1; |
| valueInput.max = 32767; |
| valueInput.addEventListener("input", function () { |
| let val = this.value.replace(/[^0-9-]/g, ""); |
| if (val !== "") { |
| val = parseInt(val, 10); |
| if (val < -1) val = -1; |
| if (val > 32767) val = 32767; |
| } |
| this.value = val; |
| }); |
|
|
| |
| |
| |
| |
| |
| |
| |
|
|
| mapItem.appendChild(keyInput); |
| mapItem.appendChild(valueInput); |
| |
|
|
| container.appendChild(mapItem); |
| } |
|
|
| |
| |
| |
| function addCustomHeaderItem() { |
| createAndAppendCustomHeaderItem("", ""); |
| } |
|
|
| |
| |
| |
| |
| |
| function createAndAppendCustomHeaderItem(key, value) { |
| const container = document.getElementById("CUSTOM_HEADERS_container"); |
| if (!container) { |
| console.error( |
| "Cannot add custom header: CUSTOM_HEADERS_container not found!" |
| ); |
| return; |
| } |
|
|
| const placeholder = container.querySelector(".text-gray-500.italic"); |
| if ( |
| placeholder && |
| container.children.length === 1 && |
| container.firstChild === placeholder |
| ) { |
| container.innerHTML = ""; |
| } |
|
|
| const headerItem = document.createElement("div"); |
| headerItem.className = `${CUSTOM_HEADER_ITEM_CLASS} flex items-center mb-2 gap-2`; |
|
|
| const keyInput = document.createElement("input"); |
| keyInput.type = "text"; |
| keyInput.value = key; |
| keyInput.placeholder = "Header Name"; |
| keyInput.className = `${CUSTOM_HEADER_KEY_INPUT_CLASS} flex-grow px-3 py-2 border border-gray-300 rounded-md focus:outline-none bg-gray-100 text-gray-500`; |
|
|
| const valueInput = document.createElement("input"); |
| valueInput.type = "text"; |
| valueInput.value = value; |
| valueInput.placeholder = "Header Value"; |
| valueInput.className = `${CUSTOM_HEADER_VALUE_INPUT_CLASS} flex-grow px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:border-primary-500 focus:ring focus:ring-primary-200 focus:ring-opacity-50`; |
|
|
| const removeBtn = createRemoveButton(); |
| removeBtn.addEventListener("click", () => { |
| headerItem.remove(); |
| if (container.children.length === 0) { |
| container.innerHTML = |
| '<div class="text-gray-500 text-sm italic">添加自定义请求头,例如 X-Api-Key: your-key</div>'; |
| } |
| }); |
|
|
| headerItem.appendChild(keyInput); |
| headerItem.appendChild(valueInput); |
| headerItem.appendChild(removeBtn); |
|
|
| container.appendChild(headerItem); |
| } |
|
|
| |
| |
| |
| |
| function collectFormData() { |
| const formData = {}; |
|
|
| |
| const inputsAndSelects = document.querySelectorAll( |
| 'input[type="text"], input[type="number"], input[type="password"], select, textarea' |
| ); |
| inputsAndSelects.forEach((element) => { |
| if ( |
| element.name && |
| !element.name.includes("[]") && |
| !element.closest(".array-container") && |
| !element.closest(`.${MAP_ITEM_CLASS}`) && |
| !element.closest(`.${SAFETY_SETTING_ITEM_CLASS}`) |
| ) { |
| if (element.type === "number") { |
| formData[element.name] = parseFloat(element.value); |
| } else if ( |
| element.classList.contains(SENSITIVE_INPUT_CLASS) && |
| element.hasAttribute("data-real-value") |
| ) { |
| formData[element.name] = element.getAttribute("data-real-value"); |
| } else { |
| formData[element.name] = element.value; |
| } |
| } |
| }); |
|
|
| const checkboxes = document.querySelectorAll('input[type="checkbox"]'); |
| checkboxes.forEach((checkbox) => { |
| formData[checkbox.name] = checkbox.checked; |
| }); |
|
|
| const arrayContainers = document.querySelectorAll(".array-container"); |
| arrayContainers.forEach((container) => { |
| const key = container.id.replace("_container", ""); |
| |
| |
| if (key === "API_KEYS") { |
| formData[key] = allApiKeys.filter( |
| (value) => value && value.trim() !== "" && value !== MASKED_VALUE |
| ); |
| return; |
| } |
| |
| const arrayInputs = container.querySelectorAll(`.${ARRAY_INPUT_CLASS}`); |
| formData[key] = Array.from(arrayInputs) |
| .map((input) => { |
| if ( |
| input.classList.contains(SENSITIVE_INPUT_CLASS) && |
| input.hasAttribute("data-real-value") |
| ) { |
| return input.getAttribute("data-real-value"); |
| } |
| return input.value; |
| }) |
| .filter( |
| (value) => value && value.trim() !== "" && value !== MASKED_VALUE |
| ); |
| }); |
|
|
| const budgetMapContainer = document.getElementById( |
| "THINKING_BUDGET_MAP_container" |
| ); |
| if (budgetMapContainer) { |
| formData["THINKING_BUDGET_MAP"] = {}; |
| const mapItems = budgetMapContainer.querySelectorAll(`.${MAP_ITEM_CLASS}`); |
| mapItems.forEach((item) => { |
| const keyInput = item.querySelector(`.${MAP_KEY_INPUT_CLASS}`); |
| const valueInput = item.querySelector(`.${MAP_VALUE_INPUT_CLASS}`); |
| if (keyInput && valueInput && keyInput.value.trim() !== "") { |
| const budgetValue = parseInt(valueInput.value, 10); |
| formData["THINKING_BUDGET_MAP"][keyInput.value.trim()] = isNaN( |
| budgetValue |
| ) |
| ? -1 |
| : budgetValue; |
| } |
| }); |
| } |
|
|
| const customHeadersContainer = document.getElementById( |
| "CUSTOM_HEADERS_container" |
| ); |
| if (customHeadersContainer) { |
| formData["CUSTOM_HEADERS"] = {}; |
| const customHeaderItems = customHeadersContainer.querySelectorAll( |
| `.${CUSTOM_HEADER_ITEM_CLASS}` |
| ); |
| customHeaderItems.forEach((item) => { |
| const keyInput = item.querySelector(`.${CUSTOM_HEADER_KEY_INPUT_CLASS}`); |
| const valueInput = item.querySelector( |
| `.${CUSTOM_HEADER_VALUE_INPUT_CLASS}` |
| ); |
| if (keyInput && valueInput && keyInput.value.trim() !== "") { |
| formData["CUSTOM_HEADERS"][keyInput.value.trim()] = |
| valueInput.value.trim(); |
| } |
| }); |
| } |
|
|
| if (safetySettingsContainer) { |
| formData["SAFETY_SETTINGS"] = []; |
| const settingItems = safetySettingsContainer.querySelectorAll( |
| `.${SAFETY_SETTING_ITEM_CLASS}` |
| ); |
| settingItems.forEach((item) => { |
| const categorySelect = item.querySelector(".safety-category-select"); |
| const thresholdSelect = item.querySelector(".safety-threshold-select"); |
| if ( |
| categorySelect && |
| thresholdSelect && |
| categorySelect.value && |
| thresholdSelect.value |
| ) { |
| formData["SAFETY_SETTINGS"].push({ |
| category: categorySelect.value, |
| threshold: thresholdSelect.value, |
| }); |
| } |
| }); |
| } |
|
|
| |
| const autoDeleteEnabledCheckbox = document.getElementById( |
| "AUTO_DELETE_ERROR_LOGS_ENABLED" |
| ); |
| if (autoDeleteEnabledCheckbox) { |
| formData["AUTO_DELETE_ERROR_LOGS_ENABLED"] = |
| autoDeleteEnabledCheckbox.checked; |
| } |
|
|
| const autoDeleteDaysSelect = document.getElementById( |
| "AUTO_DELETE_ERROR_LOGS_DAYS" |
| ); |
| if (autoDeleteDaysSelect) { |
| |
| |
| |
| formData["AUTO_DELETE_ERROR_LOGS_DAYS"] = parseInt( |
| autoDeleteDaysSelect.value, |
| 10 |
| ); |
| } |
| |
|
|
| |
| const autoDeleteRequestEnabledCheckbox = document.getElementById( |
| "AUTO_DELETE_REQUEST_LOGS_ENABLED" |
| ); |
| if (autoDeleteRequestEnabledCheckbox) { |
| formData["AUTO_DELETE_REQUEST_LOGS_ENABLED"] = |
| autoDeleteRequestEnabledCheckbox.checked; |
| } |
|
|
| const autoDeleteRequestDaysSelect = document.getElementById( |
| "AUTO_DELETE_REQUEST_LOGS_DAYS" |
| ); |
| if (autoDeleteRequestDaysSelect) { |
| formData["AUTO_DELETE_REQUEST_LOGS_DAYS"] = parseInt( |
| autoDeleteRequestDaysSelect.value, |
| 10 |
| ); |
| } |
| |
|
|
| |
| const fakeStreamEnabledCheckbox = document.getElementById( |
| "FAKE_STREAM_ENABLED" |
| ); |
| if (fakeStreamEnabledCheckbox) { |
| formData["FAKE_STREAM_ENABLED"] = fakeStreamEnabledCheckbox.checked; |
| } |
| const fakeStreamIntervalInput = document.getElementById( |
| "FAKE_STREAM_EMPTY_DATA_INTERVAL_SECONDS" |
| ); |
| if (fakeStreamIntervalInput) { |
| formData["FAKE_STREAM_EMPTY_DATA_INTERVAL_SECONDS"] = parseInt( |
| fakeStreamIntervalInput.value, |
| 10 |
| ); |
| } |
| |
|
|
| return formData; |
| } |
|
|
| |
| |
| |
| async function stopScheduler() { |
| try { |
| const response = await fetch("/api/scheduler/stop", { method: "POST" }); |
| if (!response.ok) { |
| console.warn(`停止定时任务失败: ${response.status}`); |
| } else { |
| console.log("定时任务已停止"); |
| } |
| } catch (error) { |
| console.error("调用停止定时任务API时出错:", error); |
| } |
| } |
|
|
| |
| |
| |
| async function startScheduler() { |
| try { |
| const response = await fetch("/api/scheduler/start", { method: "POST" }); |
| if (!response.ok) { |
| console.warn(`启动定时任务失败: ${response.status}`); |
| } else { |
| console.log("定时任务已启动"); |
| } |
| } catch (error) { |
| console.error("调用启动定时任务API时出错:", error); |
| } |
| } |
|
|
| |
| |
| |
| async function saveConfig() { |
| try { |
| const formData = collectFormData(); |
|
|
| showNotification("正在保存配置...", "info"); |
|
|
| |
| await stopScheduler(); |
|
|
| const response = await fetch("/api/config", { |
| method: "PUT", |
| headers: { |
| "Content-Type": "application/json", |
| }, |
| body: JSON.stringify(formData), |
| }); |
|
|
| if (!response.ok) { |
| const errorData = await response.json(); |
| throw new Error( |
| errorData.detail || `HTTP error! status: ${response.status}` |
| ); |
| } |
|
|
| const result = await response.json(); |
|
|
| |
|
|
| showNotification("配置保存成功", "success"); |
|
|
| |
| await startScheduler(); |
| } catch (error) { |
| console.error("保存配置失败:", error); |
| |
| await startScheduler(); |
| |
|
|
| showNotification("保存配置失败: " + error.message, "error"); |
| } |
| } |
|
|
| |
| |
| |
| |
| function resetConfig(event) { |
| |
| if (event) { |
| event.preventDefault(); |
| event.stopPropagation(); |
| } |
|
|
| console.log( |
| "resetConfig called. Event target:", |
| event ? event.target.id : "No event" |
| ); |
|
|
| |
| if ( |
| !event || |
| event.target.id === "resetBtn" || |
| (event.currentTarget && event.currentTarget.id === "resetBtn") |
| ) { |
| if (resetConfirmModal) { |
| openModal(resetConfirmModal); |
| } else { |
| console.error( |
| "Reset confirmation modal not found! Falling back to default confirm." |
| ); |
| if (confirm("确定要重置所有配置吗?这将恢复到默认值。")) { |
| executeReset(); |
| } |
| } |
| } |
| } |
|
|
| |
| |
| |
| async function executeReset() { |
| try { |
| showNotification("正在重置配置...", "info"); |
|
|
| |
| await stopScheduler(); |
| const response = await fetch("/api/config/reset", { method: "POST" }); |
| if (!response.ok) { |
| throw new Error(`HTTP error! status: ${response.status}`); |
| } |
| const config = await response.json(); |
| populateForm(config); |
| |
| if (configForm && typeof initializeSensitiveFields === "function") { |
| const sensitiveFields = configForm.querySelectorAll( |
| `.${SENSITIVE_INPUT_CLASS}` |
| ); |
| sensitiveFields.forEach((field) => { |
| if (field.type === "password") { |
| if (field.value) field.setAttribute("data-real-value", field.value); |
| } else if ( |
| field.type === "text" || |
| field.tagName.toLowerCase() === "textarea" |
| ) { |
| const focusoutEvent = new Event("focusout", { |
| bubbles: true, |
| cancelable: true, |
| }); |
| field.dispatchEvent(focusoutEvent); |
| } |
| }); |
| } |
| showNotification("配置已重置为默认值", "success"); |
|
|
| |
| await startScheduler(); |
| } catch (error) { |
| console.error("重置配置失败:", error); |
| showNotification("重置配置失败: " + error.message, "error"); |
| |
| await startScheduler(); |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| function showNotification(message, type = "info") { |
| const notification = document.getElementById("notification"); |
| notification.textContent = message; |
|
|
| |
| notification.classList.remove("bg-danger-500"); |
| notification.classList.add("bg-black"); |
| notification.style.backgroundColor = "rgba(0,0,0,0.8)"; |
| notification.style.color = "#fff"; |
|
|
| |
| notification.style.opacity = "1"; |
| notification.style.transform = "translate(-50%, 0)"; |
|
|
| |
| setTimeout(() => { |
| notification.style.opacity = "0"; |
| notification.style.transform = "translate(-50%, 10px)"; |
| }, 3000); |
| } |
|
|
| |
| |
| |
| |
| function refreshPage(button) { |
| if (button) button.classList.add("loading"); |
| location.reload(); |
| } |
|
|
| |
| |
| |
| function scrollToTop() { |
| window.scrollTo({ top: 0, behavior: "smooth" }); |
| } |
|
|
| |
| |
| |
| function scrollToBottom() { |
| window.scrollTo({ top: document.body.scrollHeight, behavior: "smooth" }); |
| } |
|
|
| |
| |
| |
| function toggleScrollButtons() { |
| const scrollButtons = document.querySelector(".scroll-buttons"); |
| if (scrollButtons) { |
| scrollButtons.style.display = window.scrollY > 200 ? "flex" : "none"; |
| } |
| } |
|
|
| |
| |
| |
| |
| function generateRandomToken() { |
| const characters = |
| "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_"; |
| const length = 48; |
| let result = "sk-"; |
| for (let i = 0; i < length; i++) { |
| result += characters.charAt(Math.floor(Math.random() * characters.length)); |
| } |
| return result; |
| } |
|
|
| |
| |
| |
| |
| |
| function addSafetySettingItem(category = "", threshold = "") { |
| const container = document.getElementById("SAFETY_SETTINGS_container"); |
| if (!container) { |
| console.error( |
| "Cannot add safety setting: SAFETY_SETTINGS_container not found!" |
| ); |
| return; |
| } |
|
|
| |
| const placeholder = container.querySelector(".text-gray-500.italic"); |
| if ( |
| placeholder && |
| container.children.length === 1 && |
| container.firstChild === placeholder |
| ) { |
| container.innerHTML = ""; |
| } |
|
|
| const harmCategories = [ |
| "HARM_CATEGORY_HARASSMENT", |
| "HARM_CATEGORY_HATE_SPEECH", |
| "HARM_CATEGORY_SEXUALLY_EXPLICIT", |
| "HARM_CATEGORY_DANGEROUS_CONTENT", |
| "HARM_CATEGORY_CIVIC_INTEGRITY", |
| ]; |
| const harmThresholds = [ |
| "BLOCK_NONE", |
| "BLOCK_LOW_AND_ABOVE", |
| "BLOCK_MEDIUM_AND_ABOVE", |
| "BLOCK_ONLY_HIGH", |
| "OFF", |
| ]; |
|
|
| const settingItem = document.createElement("div"); |
| settingItem.className = `${SAFETY_SETTING_ITEM_CLASS} flex items-center mb-2 gap-2`; |
|
|
| const categorySelect = document.createElement("select"); |
| categorySelect.className = |
| "safety-category-select flex-grow px-3 py-2 rounded-md focus:outline-none focus:border-primary-500 focus:ring focus:ring-primary-200 focus:ring-opacity-50 form-select-themed"; |
| harmCategories.forEach((cat) => { |
| const option = document.createElement("option"); |
| option.value = cat; |
| option.textContent = cat.replace("HARM_CATEGORY_", ""); |
| if (cat === category) option.selected = true; |
| categorySelect.appendChild(option); |
| }); |
|
|
| const thresholdSelect = document.createElement("select"); |
| thresholdSelect.className = |
| "safety-threshold-select w-48 px-3 py-2 rounded-md focus:outline-none focus:border-primary-500 focus:ring focus:ring-primary-200 focus:ring-opacity-50 form-select-themed"; |
| harmThresholds.forEach((thr) => { |
| const option = document.createElement("option"); |
| option.value = thr; |
| option.textContent = thr.replace("BLOCK_", "").replace("_AND_ABOVE", "+"); |
| if (thr === threshold) option.selected = true; |
| thresholdSelect.appendChild(option); |
| }); |
|
|
| const removeBtn = document.createElement("button"); |
| removeBtn.type = "button"; |
| removeBtn.className = |
| "remove-btn text-gray-400 hover:text-red-500 focus:outline-none transition-colors duration-150"; |
| removeBtn.innerHTML = '<i class="fas fa-trash-alt"></i>'; |
| removeBtn.title = "删除此设置"; |
| |
|
|
| settingItem.appendChild(categorySelect); |
| settingItem.appendChild(thresholdSelect); |
| settingItem.appendChild(removeBtn); |
|
|
| container.appendChild(settingItem); |
| } |
|
|
| |
| async function fetchModels() { |
| if (cachedModelsList) { |
| return cachedModelsList; |
| } |
| try { |
| showNotification("正在从 /api/config/ui/models 加载模型列表...", "info"); |
| const response = await fetch("/api/config/ui/models"); |
| if (!response.ok) { |
| const errorData = await response.text(); |
| throw new Error(`HTTP error ${response.status}: ${errorData}`); |
| } |
| const responseData = await response.json(); |
| |
| if ( |
| responseData && |
| responseData.success && |
| Array.isArray(responseData.data) |
| ) { |
| cachedModelsList = responseData.data; |
| showNotification("模型列表加载成功", "success"); |
| return cachedModelsList; |
| } else { |
| console.error("Invalid model list format received:", responseData); |
| throw new Error("模型列表格式无效或请求未成功"); |
| } |
| } catch (error) { |
| console.error("加载模型列表失败:", error); |
| showNotification(`加载模型列表失败: ${error.message}`, "error"); |
| cachedModelsList = []; |
| return []; |
| } |
| } |
|
|
| function renderModelsInModal() { |
| if (!modelHelperListContainer) return; |
| if (!cachedModelsList) { |
| modelHelperListContainer.innerHTML = |
| '<p class="text-gray-400 text-sm italic">模型列表尚未加载。</p>'; |
| return; |
| } |
|
|
| const searchTerm = modelHelperSearchInput.value.toLowerCase(); |
| const filteredModels = cachedModelsList.filter((model) => |
| model.id.toLowerCase().includes(searchTerm) |
| ); |
|
|
| modelHelperListContainer.innerHTML = ""; |
|
|
| if (filteredModels.length === 0) { |
| modelHelperListContainer.innerHTML = |
| '<p class="text-gray-400 text-sm italic">未找到匹配的模型。</p>'; |
| return; |
| } |
|
|
| filteredModels.forEach((model) => { |
| const modelItemElement = document.createElement("button"); |
| modelItemElement.type = "button"; |
| modelItemElement.textContent = model.id; |
| modelItemElement.className = |
| "block w-full text-left px-4 py-2 rounded-md hover:bg-blue-100 focus:bg-blue-100 focus:outline-none transition-colors text-gray-700 hover:text-gray-800"; |
| |
|
|
| modelItemElement.addEventListener("click", () => |
| handleModelSelection(model.id) |
| ); |
| modelHelperListContainer.appendChild(modelItemElement); |
| }); |
| } |
|
|
| async function openModelHelperModal() { |
| if (!currentModelHelperTarget) { |
| console.error("Model helper target not set."); |
| showNotification("无法打开模型助手:目标未设置", "error"); |
| return; |
| } |
|
|
| await fetchModels(); |
| renderModelsInModal(); |
|
|
| if (modelHelperTitleElement) { |
| if ( |
| currentModelHelperTarget.type === "input" && |
| currentModelHelperTarget.target |
| ) { |
| const label = document.querySelector( |
| `label[for="${currentModelHelperTarget.target.id}"]` |
| ); |
| modelHelperTitleElement.textContent = label |
| ? `为 "${label.textContent.trim()}" 选择模型` |
| : "选择模型"; |
| } else if (currentModelHelperTarget.type === "array") { |
| modelHelperTitleElement.textContent = `为 ${currentModelHelperTarget.targetKey} 添加模型`; |
| } else { |
| modelHelperTitleElement.textContent = "选择模型"; |
| } |
| } |
| if (modelHelperSearchInput) modelHelperSearchInput.value = ""; |
| if (modelHelperModal) openModal(modelHelperModal); |
| } |
|
|
| function handleModelSelection(selectedModelId) { |
| if (!currentModelHelperTarget) return; |
|
|
| if ( |
| currentModelHelperTarget.type === "input" && |
| currentModelHelperTarget.target |
| ) { |
| const inputElement = currentModelHelperTarget.target; |
| inputElement.value = selectedModelId; |
| |
| if (inputElement.classList.contains(SENSITIVE_INPUT_CLASS)) { |
| const event = new Event("focusout", { bubbles: true, cancelable: true }); |
| inputElement.dispatchEvent(event); |
| } |
| |
| inputElement.dispatchEvent(new Event("input", { bubbles: true })); |
| } else if ( |
| currentModelHelperTarget.type === "array" && |
| currentModelHelperTarget.targetKey |
| ) { |
| const modelId = addArrayItemWithValue( |
| currentModelHelperTarget.targetKey, |
| selectedModelId |
| ); |
| if (currentModelHelperTarget.targetKey === "THINKING_MODELS" && modelId) { |
| |
| createAndAppendBudgetMapItem(selectedModelId, -1, modelId); |
| } |
| } |
|
|
| if (modelHelperModal) closeModal(modelHelperModal); |
| currentModelHelperTarget = null; |
| } |
|
|
| |
|
|
| |
|
|
| |
| |
| |
| |
| |
| async function checkSingleProxy(proxy, buttonElement) { |
| const statusIcon = buttonElement.parentElement.querySelector('.proxy-status-icon'); |
| const originalButtonContent = buttonElement.innerHTML; |
| |
| try { |
| |
| buttonElement.innerHTML = '<i class="fas fa-spinner fa-spin"></i>'; |
| buttonElement.disabled = true; |
| if (statusIcon) { |
| statusIcon.className = "proxy-status-icon px-2 py-2 text-blue-500"; |
| statusIcon.innerHTML = '<i class="fas fa-spinner fa-spin" title="检测中..."></i>'; |
| statusIcon.setAttribute("data-status", "checking"); |
| } |
| |
| const response = await fetch('/api/config/proxy/check', { |
| method: 'POST', |
| headers: { |
| 'Content-Type': 'application/json', |
| }, |
| body: JSON.stringify({ |
| proxy: proxy, |
| use_cache: true |
| }) |
| }); |
| |
| if (!response.ok) { |
| throw new Error(`检测请求失败: ${response.status}`); |
| } |
| |
| const result = await response.json(); |
| updateProxyStatus(statusIcon, result); |
| |
| |
| if (result.is_available) { |
| showNotification(`代理可用 (${result.response_time}s)`, "success"); |
| } else { |
| showNotification(`代理不可用: ${result.error_message}`, "error"); |
| } |
| |
| } catch (error) { |
| console.error('代理检测失败:', error); |
| if (statusIcon) { |
| statusIcon.className = "proxy-status-icon px-2 py-2 text-red-500"; |
| statusIcon.innerHTML = '<i class="fas fa-times-circle" title="检测失败"></i>'; |
| statusIcon.setAttribute("data-status", "error"); |
| } |
| showNotification(`检测失败: ${error.message}`, "error"); |
| } finally { |
| |
| buttonElement.innerHTML = originalButtonContent; |
| buttonElement.disabled = false; |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| function updateProxyStatus(statusIcon, result) { |
| if (!statusIcon) return; |
| |
| if (result.is_available) { |
| statusIcon.className = "proxy-status-icon px-2 py-2 text-green-500"; |
| statusIcon.innerHTML = `<i class="fas fa-check-circle" title="可用 (${result.response_time}s)"></i>`; |
| statusIcon.setAttribute("data-status", "available"); |
| } else { |
| statusIcon.className = "proxy-status-icon px-2 py-2 text-red-500"; |
| statusIcon.innerHTML = `<i class="fas fa-times-circle" title="不可用: ${result.error_message}"></i>`; |
| statusIcon.setAttribute("data-status", "unavailable"); |
| } |
| } |
|
|
| |
| |
| |
| async function checkAllProxies() { |
| const proxyContainer = document.getElementById("PROXIES_container"); |
| if (!proxyContainer) return; |
| |
| const proxyInputs = proxyContainer.querySelectorAll('.array-input'); |
| const proxies = Array.from(proxyInputs) |
| .map(input => input.value.trim()) |
| .filter(proxy => proxy.length > 0); |
| |
| if (proxies.length === 0) { |
| showNotification("没有代理需要检测", "warning"); |
| return; |
| } |
| |
| |
| const proxyCheckModal = document.getElementById("proxyCheckModal"); |
| if (proxyCheckModal) { |
| openModal(proxyCheckModal); |
| |
| |
| const progressContainer = document.getElementById("proxyCheckProgress"); |
| const summaryContainer = document.getElementById("proxyCheckSummary"); |
| const resultsContainer = document.getElementById("proxyCheckResults"); |
| |
| if (progressContainer) progressContainer.classList.remove("hidden"); |
| if (summaryContainer) summaryContainer.classList.add("hidden"); |
| if (resultsContainer) resultsContainer.innerHTML = ""; |
| |
| |
| const totalCountElement = document.getElementById("totalCount"); |
| if (totalCountElement) totalCountElement.textContent = proxies.length; |
| |
| try { |
| const response = await fetch('/api/config/proxy/check-all', { |
| method: 'POST', |
| headers: { |
| 'Content-Type': 'application/json', |
| }, |
| body: JSON.stringify({ |
| proxies: proxies, |
| use_cache: true, |
| max_concurrent: 5 |
| }) |
| }); |
| |
| if (!response.ok) { |
| throw new Error(`批量检测请求失败: ${response.status}`); |
| } |
| |
| const results = await response.json(); |
| displayProxyCheckResults(results); |
| updateProxyStatusInList(results); |
| |
| } catch (error) { |
| console.error('批量代理检测失败:', error); |
| showNotification(`批量检测失败: ${error.message}`, "error"); |
| if (resultsContainer) { |
| resultsContainer.innerHTML = `<div class="text-red-500 text-center py-4">检测失败: ${error.message}</div>`; |
| } |
| } finally { |
| |
| if (progressContainer) progressContainer.classList.add("hidden"); |
| } |
| } |
| } |
|
|
| |
| |
| |
| |
| function displayProxyCheckResults(results) { |
| const summaryContainer = document.getElementById("proxyCheckSummary"); |
| const resultsContainer = document.getElementById("proxyCheckResults"); |
| const availableCountElement = document.getElementById("availableCount"); |
| const unavailableCountElement = document.getElementById("unavailableCount"); |
| const retryButton = document.getElementById("retryFailedProxiesBtn"); |
| |
| if (!resultsContainer) return; |
| |
| |
| const availableCount = results.filter(r => r.is_available).length; |
| const unavailableCount = results.length - availableCount; |
| |
| |
| if (availableCountElement) availableCountElement.textContent = availableCount; |
| if (unavailableCountElement) unavailableCountElement.textContent = unavailableCount; |
| if (summaryContainer) summaryContainer.classList.remove("hidden"); |
| |
| |
| if (retryButton) { |
| if (unavailableCount > 0) { |
| retryButton.classList.remove("hidden"); |
| } else { |
| retryButton.classList.add("hidden"); |
| } |
| } |
| |
| |
| resultsContainer.innerHTML = ""; |
| |
| results.forEach(result => { |
| const resultItem = document.createElement("div"); |
| resultItem.className = `flex items-center justify-between p-3 border rounded-lg ${ |
| result.is_available ? 'border-green-200 bg-green-50' : 'border-red-200 bg-red-50' |
| }`; |
| |
| const statusIcon = result.is_available ? |
| '<i class="fas fa-check-circle text-green-500"></i>' : |
| '<i class="fas fa-times-circle text-red-500"></i>'; |
| |
| const responseTimeText = result.response_time ? |
| ` (${result.response_time}s)` : ''; |
| |
| const errorText = result.error_message ? |
| `<span class="text-red-600 text-sm ml-2">${result.error_message}</span>` : ''; |
| |
| resultItem.innerHTML = ` |
| <div class="flex items-center gap-3"> |
| ${statusIcon} |
| <span class="font-mono text-sm">${result.proxy}</span> |
| ${responseTimeText} |
| </div> |
| <div class="flex items-center"> |
| <span class="text-sm ${result.is_available ? 'text-green-700' : 'text-red-700'}"> |
| ${result.is_available ? '可用' : '不可用'} |
| </span> |
| ${errorText} |
| </div> |
| `; |
| |
| resultsContainer.appendChild(resultItem); |
| }); |
| } |
|
|
| |
| |
| |
| |
| function updateProxyStatusInList(results) { |
| const proxyContainer = document.getElementById("PROXIES_container"); |
| if (!proxyContainer) return; |
| |
| results.forEach(result => { |
| const proxyInputs = proxyContainer.querySelectorAll('.array-input'); |
| proxyInputs.forEach(input => { |
| if (input.value.trim() === result.proxy) { |
| const statusIcon = input.parentElement.querySelector('.proxy-status-icon'); |
| updateProxyStatus(statusIcon, result); |
| } |
| }); |
| }); |
| } |
|
|
| |
|
|