| | |
| | 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); |
| | } |
| | }); |
| | }); |
| | } |
| |
|
| | |
| |
|