From d234f826f415099c68ee5d3cbfd9a7ad12267b95 Mon Sep 17 00:00:00 2001 From: snaily Date: Tue, 8 Jul 2025 15:27:16 +0800 Subject: [PATCH] =?UTF-8?q?chore:=20=E6=9B=B4=E6=96=B0=20Vertex=20API=20?= =?UTF-8?q?=E7=9B=B8=E5=85=B3=E6=B3=A8=E9=87=8A=E5=92=8C=E6=AD=A3=E5=88=99?= =?UTF-8?q?=E8=A1=A8=E8=BE=BE=E5=BC=8F=E4=B8=BA=20Vertex=20Express=20API?= =?UTF-8?q?=EF=BC=8C=E7=A1=AE=E4=BF=9D=E4=B8=80=E8=87=B4=E6=80=A7=E5=92=8C?= =?UTF-8?q?=E5=87=86=E7=A1=AE=E6=80=A7=E3=80=82=E4=BF=AE=E6=94=B9=E4=BA=86?= =?UTF-8?q?=E5=A4=9A=E4=B8=AA=E6=96=87=E4=BB=B6=E4=B8=AD=E7=9A=84=E7=9B=B8?= =?UTF-8?q?=E5=85=B3=E6=8F=8F=E8=BF=B0=E5=92=8C=E6=8F=90=E7=A4=BA=E4=BF=A1?= =?UTF-8?q?=E6=81=AF=EF=BC=8C=E4=BB=A5=E5=8F=8D=E6=98=A0=20API=20=E5=90=8D?= =?UTF-8?q?=E7=A7=B0=E7=9A=84=E5=8F=98=E5=8C=96=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/service/key/key_manager.py | 46 +-- app/static/js/config_editor.js | 146 +++++---- app/templates/config_editor.html | 522 +++++++++++++++++++++---------- 3 files changed, 468 insertions(+), 246 deletions(-) diff --git a/app/service/key/key_manager.py b/app/service/key/key_manager.py index 94b9ae6..73d897b 100644 --- a/app/service/key/key_manager.py +++ b/app/service/key/key_manager.py @@ -34,7 +34,7 @@ class KeyManager: return next(self.key_cycle) async def get_next_vertex_key(self) -> str: - """获取下一个 Vertex API key""" + """获取下一个 Vertex Express API key""" async with self.vertex_key_cycle_lock: return next(self.vertex_key_cycle) @@ -98,7 +98,7 @@ class KeyManager: return current_key async def get_next_working_vertex_key(self) -> str: - """获取下一可用的 Vertex API key""" + """获取下一可用的 Vertex Express API key""" initial_key = await self.get_next_vertex_key() current_key = initial_key @@ -124,12 +124,12 @@ class KeyManager: return "" async def handle_vertex_api_failure(self, api_key: str, retries: int) -> str: - """处理 Vertex API 调用失败""" + """处理 Vertex Express API 调用失败""" async with self.vertex_failure_count_lock: self.vertex_key_failure_counts[api_key] += 1 if self.vertex_key_failure_counts[api_key] >= self.MAX_FAILURES: logger.warning( - f"Vertex API key {api_key} has failed {self.MAX_FAILURES} times" + f"Vertex Express API key {api_key} has failed {self.MAX_FAILURES} times" ) def get_fail_count(self, key: str) -> int: @@ -156,7 +156,7 @@ class KeyManager: return {"valid_keys": valid_keys, "invalid_keys": invalid_keys} async def get_vertex_keys_by_status(self) -> dict: - """获取分类后的 Vertex API key 列表,包括失败次数""" + """获取分类后的 Vertex Express API key 列表,包括失败次数""" valid_keys = {} invalid_keys = {} @@ -178,8 +178,7 @@ class KeyManager: if self.api_keys: return self.api_keys[0] if not self.api_keys: - logger.warning( - "API key list is empty, cannot get first valid key.") + logger.warning("API key list is empty, cannot get first valid key.") return "" return self.api_keys[0] @@ -214,7 +213,7 @@ async def get_key_manager_instance( ) if vertex_api_keys is None: raise ValueError( - "Vertex API keys are required to initialize or re-initialize the KeyManager instance." + "Vertex Express API keys are required to initialize or re-initialize the KeyManager instance." ) if not api_keys: @@ -223,12 +222,12 @@ async def get_key_manager_instance( ) if not vertex_api_keys: logger.warning( - "Initializing KeyManager with an empty list of Vertex API keys." + "Initializing KeyManager with an empty list of Vertex Express API keys." ) _singleton_instance = KeyManager(api_keys, vertex_api_keys) logger.info( - f"KeyManager instance created/re-created with {len(api_keys)} API keys and {len(vertex_api_keys)} Vertex API keys." + f"KeyManager instance created/re-created with {len(api_keys)} API keys and {len(vertex_api_keys)} Vertex Express API keys." ) # 1. 恢复失败计数 @@ -253,8 +252,7 @@ async def get_key_manager_instance( _singleton_instance.vertex_key_failure_counts = ( current_vertex_failure_counts ) - logger.info( - "Inherited failure counts for applicable Vertex keys.") + logger.info("Inherited failure counts for applicable Vertex keys.") _preserved_vertex_failure_counts = None # 2. 调整 key_cycle 的起始点 @@ -351,7 +349,7 @@ async def get_key_manager_instance( break except ValueError: logger.warning( - f"Preserved next key '{_preserved_vertex_next_key_in_cycle}' not found in preserved old Vertex API keys. " + f"Preserved next key '{_preserved_vertex_next_key_in_cycle}' not found in preserved old Vertex Express API keys. " "New cycle will start from the beginning of the new list." ) except Exception as e: @@ -372,12 +370,12 @@ async def get_key_manager_instance( ) except ValueError: logger.warning( - f"Determined start key '{start_key_for_new_vertex_cycle}' not found in new Vertex API keys during cycle advancement. " + f"Determined start key '{start_key_for_new_vertex_cycle}' not found in new Vertex Express API keys during cycle advancement. " "New cycle will start from the beginning." ) except StopIteration: logger.error( - "StopIteration while advancing Vertex key cycle, implies empty new Vertex API key list previously missed." + "StopIteration while advancing Vertex key cycle, implies empty new Vertex Express API key list previously missed." ) except Exception as e: logger.error( @@ -386,11 +384,11 @@ async def get_key_manager_instance( else: if _singleton_instance.vertex_api_keys: logger.info( - "New Vertex key cycle will start from the beginning of the new Vertex API key list (no specific start key determined or needed)." + "New Vertex key cycle will start from the beginning of the new Vertex Express API key list (no specific start key determined or needed)." ) else: logger.info( - "New Vertex key cycle not applicable as the new Vertex API key list is empty." + "New Vertex key cycle not applicable as the new Vertex Express API key list is empty." ) # 清理所有保存的状态 @@ -411,11 +409,15 @@ async def reset_key_manager_instance(): if _singleton_instance: # 1. 保存失败计数 _preserved_failure_counts = _singleton_instance.key_failure_counts.copy() - _preserved_vertex_failure_counts = _singleton_instance.vertex_key_failure_counts.copy() + _preserved_vertex_failure_counts = ( + _singleton_instance.vertex_key_failure_counts.copy() + ) # 2. 保存旧的 API keys 列表 _preserved_old_api_keys_for_reset = _singleton_instance.api_keys.copy() - _preserved_vertex_old_api_keys_for_reset = _singleton_instance.vertex_api_keys.copy() + _preserved_vertex_old_api_keys_for_reset = ( + _singleton_instance.vertex_api_keys.copy() + ) # 3. 保存 key_cycle 的下一个 key 提示 try: @@ -431,8 +433,7 @@ async def reset_key_manager_instance(): ) _preserved_next_key_in_cycle = None except Exception as e: - logger.error( - f"Error preserving next key hint during reset: {e}") + logger.error(f"Error preserving next key hint during reset: {e}") _preserved_next_key_in_cycle = None # 4. 保存 vertex_key_cycle 的下一个 key 提示 @@ -449,8 +450,7 @@ async def reset_key_manager_instance(): ) _preserved_vertex_next_key_in_cycle = None except Exception as e: - logger.error( - f"Error preserving next key hint during reset: {e}") + logger.error(f"Error preserving next key hint during reset: {e}") _preserved_vertex_next_key_in_cycle = None _singleton_instance = None diff --git a/app/static/js/config_editor.js b/app/static/js/config_editor.js index 874cf1b..8182510 100644 --- a/app/static/js/config_editor.js +++ b/app/static/js/config_editor.js @@ -13,7 +13,7 @@ const SHOW_CLASS = "show"; // For modals 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; // 新增 Vertex API Key 正则 +const VERTEX_API_KEY_REGEX = /AQ\.[a-zA-Z0-9_]{50}/g; // 新增 Vertex Express API Key 正则 const MASKED_VALUE = "••••••••"; // DOM Elements - Global Scope for frequently accessed elements @@ -35,7 +35,7 @@ const bulkDeleteProxyInput = document.getElementById("bulkDeleteProxyInput"); const resetConfirmModal = document.getElementById("resetConfirmModal"); const configForm = document.getElementById("configForm"); // Added for frequent use -// Vertex API Key Modal Elements +// Vertex Express API Key Modal Elements const vertexApiKeyModal = document.getElementById("vertexApiKeyModal"); const vertexApiKeyBulkInput = document.getElementById("vertexApiKeyBulkInput"); const bulkDeleteVertexApiKeyModal = document.getElementById( @@ -394,7 +394,7 @@ document.addEventListener("DOMContentLoaded", function () { initializeSensitiveFields(); // Initialize sensitive field handling - // Vertex API Key Modal Elements and Events + // Vertex Express API Key Modal Elements and Events const addVertexApiKeyBtn = document.getElementById("addVertexApiKeyBtn"); const closeVertexApiKeyModalBtn = document.getElementById( "closeVertexApiKeyModalBtn" @@ -873,9 +873,15 @@ function populateForm(config) { } // Populate CUSTOM_HEADERS - const customHeadersContainer = document.getElementById("CUSTOM_HEADERS_container"); + const customHeadersContainer = document.getElementById( + "CUSTOM_HEADERS_container" + ); let customHeadersAdded = false; - if (customHeadersContainer && config.CUSTOM_HEADERS && typeof config.CUSTOM_HEADERS === "object") { + 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; @@ -883,7 +889,7 @@ function populateForm(config) { } if (!customHeadersAdded && customHeadersContainer) { customHeadersContainer.innerHTML = - '
添加自定义请求头,例如 X-Api-Key: your-key
'; + '
添加自定义请求头,例如 X-Api-Key: your-key
'; } // 4. Populate other array fields (excluding THINKING_MODELS) @@ -1211,17 +1217,13 @@ function handleBulkDeleteProxies() { } /** - * Handles the bulk addition of Vertex API keys from the modal input. + * Handles the bulk addition of Vertex Express API keys from the modal input. */ function handleBulkAddVertexApiKeys() { const vertexApiKeyContainer = document.getElementById( "VERTEX_API_KEYS_container" ); - if ( - !vertexApiKeyBulkInput || - !vertexApiKeyContainer || - !vertexApiKeyModal - ) { + if (!vertexApiKeyBulkInput || !vertexApiKeyContainer || !vertexApiKeyModal) { return; } @@ -1271,7 +1273,7 @@ function handleBulkAddVertexApiKeys() { } /** - * Handles the bulk deletion of Vertex API keys based on input from the modal. + * Handles the bulk deletion of Vertex Express API keys based on input from the modal. */ function handleBulkDeleteVertexApiKeys() { const vertexApiKeyContainer = document.getElementById( @@ -1287,7 +1289,7 @@ function handleBulkDeleteVertexApiKeys() { const bulkText = bulkDeleteVertexApiKeyInput.value; if (!bulkText.trim()) { - showNotification("请粘贴需要删除的 Vertex API 密钥", "warning"); + showNotification("请粘贴需要删除的 Vertex Express API 密钥", "warning"); return; } @@ -1295,13 +1297,15 @@ function handleBulkDeleteVertexApiKeys() { if (keysToDelete.size === 0) { showNotification( - "未在输入内容中提取到有效的 Vertex API 密钥格式", + "未在输入内容中提取到有效的 Vertex Express API 密钥格式", "warning" ); return; } - const keyItems = vertexApiKeyContainer.querySelectorAll(`.${ARRAY_ITEM_CLASS}`); + const keyItems = vertexApiKeyContainer.querySelectorAll( + `.${ARRAY_ITEM_CLASS}` + ); let deleteCount = 0; keyItems.forEach((item) => { @@ -1322,7 +1326,10 @@ function handleBulkDeleteVertexApiKeys() { closeModal(bulkDeleteVertexApiKeyModal); if (deleteCount > 0) { - showNotification(`成功删除了 ${deleteCount} 个匹配的 Vertex 密钥`, "success"); + showNotification( + `成功删除了 ${deleteCount} 个匹配的 Vertex 密钥`, + "success" + ); } else { showNotification("列表中未找到您输入的任何 Vertex 密钥进行删除", "info"); } @@ -1337,8 +1344,10 @@ 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 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"); @@ -1471,8 +1480,7 @@ function addArrayItemWithValue(key, value) { const isThinkingModel = key === "THINKING_MODELS"; const isAllowedToken = key === "ALLOWED_TOKENS"; const isVertexApiKey = key === "VERTEX_API_KEYS"; // 新增判断 - const isSensitive = - key === "API_KEYS" || isAllowedToken || isVertexApiKey; // 更新敏感判断 + const isSensitive = key === "API_KEYS" || isAllowedToken || isVertexApiKey; // 更新敏感判断 const modelId = isThinkingModel ? generateUUID() : null; const arrayItem = document.createElement("div"); @@ -1598,7 +1606,7 @@ function createAndAppendBudgetMapItem(mapKey, mapValue, modelId) { * Adds a new custom header item to the DOM. */ function addCustomHeaderItem() { - createAndAppendCustomHeaderItem("", ""); + createAndAppendCustomHeaderItem("", ""); } /** @@ -1607,45 +1615,52 @@ function addCustomHeaderItem() { * @param {string} value - The header value. */ 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 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 = + '
添加自定义请求头,例如 X-Api-Key: your-key
'; } + }); - const placeholder = container.querySelector(".text-gray-500.italic"); - if (placeholder && container.children.length === 1 && container.firstChild === placeholder) { - container.innerHTML = ""; - } + headerItem.appendChild(keyInput); + headerItem.appendChild(valueInput); + headerItem.appendChild(removeBtn); - 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 = '
添加自定义请求头,例如 X-Api-Key: your-key
'; - } - }); - - headerItem.appendChild(keyInput); - headerItem.appendChild(valueInput); - headerItem.appendChild(removeBtn); - - container.appendChild(headerItem); + container.appendChild(headerItem); } /** @@ -1724,15 +1739,22 @@ function collectFormData() { }); } - const customHeadersContainer = document.getElementById("CUSTOM_HEADERS_container"); + const customHeadersContainer = document.getElementById( + "CUSTOM_HEADERS_container" + ); if (customHeadersContainer) { formData["CUSTOM_HEADERS"] = {}; - const customHeaderItems = customHeadersContainer.querySelectorAll(`.${CUSTOM_HEADER_ITEM_CLASS}`); + 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}`); + const valueInput = item.querySelector( + `.${CUSTOM_HEADER_VALUE_INPUT_CLASS}` + ); if (keyInput && valueInput && keyInput.value.trim() !== "") { - formData["CUSTOM_HEADERS"][keyInput.value.trim()] = valueInput.value.trim(); + formData["CUSTOM_HEADERS"][keyInput.value.trim()] = + valueInput.value.trim(); } }); } diff --git a/app/templates/config_editor.html b/app/templates/config_editor.html index 5e83547..e2854e3 100644 --- a/app/templates/config_editor.html +++ b/app/templates/config_editor.html @@ -96,12 +96,7 @@ endblock %} {% block head_extra_styles %} /* Theming for select fields - 改进下拉框样式 */ .form-select-themed { - background-color: rgba( - 255, - 255, - 255, - 0.95 - ) !important; /* 白色背景 */ + background-color: rgba(255, 255, 255, 0.95) !important; /* 白色背景 */ border: 1px solid rgba(0, 0, 0, 0.12) !important; /* 灰色边框 */ color: #374151 !important; /* gray-700 文字颜色 */ background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M6 8l4 4 4-4'/%3e%3c/svg%3e") !important; /* 灰色箭头 */ @@ -124,7 +119,12 @@ endblock %} {% block head_extra_styles %} } .form-select-themed option { - background-color: rgba(255, 255, 255, 0.98) !important; /* white background */ + background-color: rgba( + 255, + 255, + 255, + 0.98 + ) !important; /* white background */ color: #374151 !important; /* gray-700 */ padding: 8px !important; } @@ -141,22 +141,42 @@ endblock %} {% block head_extra_styles %} } #LOG_LEVEL option[value="INFO"] { - background-color: rgba(249, 250, 251, 0.98) !important; /* gray-50 浅灰背景 */ + background-color: rgba( + 249, + 250, + 251, + 0.98 + ) !important; /* gray-50 浅灰背景 */ color: #374151 !important; /* gray-700 深灰色文字 */ } #LOG_LEVEL option[value="WARNING"] { - background-color: rgba(243, 244, 246, 0.98) !important; /* gray-100 稍深灰背景 */ + background-color: rgba( + 243, + 244, + 246, + 0.98 + ) !important; /* gray-100 稍深灰背景 */ color: #374151 !important; /* gray-700 深灰色文字 */ } #LOG_LEVEL option[value="ERROR"] { - background-color: rgba(229, 231, 235, 0.98) !important; /* gray-200 中灰背景 */ + background-color: rgba( + 229, + 231, + 235, + 0.98 + ) !important; /* gray-200 中灰背景 */ color: #374151 !important; /* gray-700 深灰色文字 */ } #LOG_LEVEL option[value="CRITICAL"] { - background-color: rgba(209, 213, 219, 0.98) !important; /* gray-300 深灰背景 */ + background-color: rgba( + 209, + 213, + 219, + 0.98 + ) !important; /* gray-300 深灰背景 */ color: #374151 !important; /* gray-700 深灰色文字 */ } @@ -228,7 +248,12 @@ endblock %} {% block head_extra_styles %} } .generate-btn:hover { - background-color: rgba(59, 130, 246, 0.2) !important; /* blue-500 more opaque */ + background-color: rgba( + 59, + 130, + 246, + 0.2 + ) !important; /* blue-500 more opaque */ color: #1d4ed8 !important; /* blue-700 */ box-shadow: 0 0 8px rgba(59, 130, 246, 0.3) !important; } @@ -302,36 +327,47 @@ endblock %} {% block head_extra_styles %} } /* Override all violet/purple buttons to light blue */ - .bg-violet-600, button.bg-violet-600 { + .bg-violet-600, + button.bg-violet-600 { background-color: #3b82f6 !important; /* blue-500 - light blue */ } - .bg-violet-600:hover, button.bg-violet-600:hover, + .bg-violet-600:hover, + button.bg-violet-600:hover, .hover\\:bg-violet-700:hover { background-color: #2563eb !important; /* blue-600 - darker light blue */ } /* Override blue buttons to light blue */ - .bg-blue-600, button.bg-blue-600 { + .bg-blue-600, + button.bg-blue-600 { background-color: #3b82f6 !important; /* blue-500 - light blue */ } - .bg-blue-600:hover, button.bg-blue-600:hover, + .bg-blue-600:hover, + button.bg-blue-600:hover, .hover\\:bg-blue-700:hover { background-color: #2563eb !important; /* blue-600 - darker light blue */ } /* Override red buttons to bright light red */ - .bg-red-600, button.bg-red-600, - .bg-red-700, button.bg-red-700, - .bg-red-800, button.bg-red-800 { + .bg-red-600, + button.bg-red-600, + .bg-red-700, + button.bg-red-700, + .bg-red-800, + button.bg-red-800 { background-color: #f87171 !important; /* red-400 - bright light red */ } - .bg-red-600:hover, button.bg-red-600:hover, - .bg-red-700:hover, button.bg-red-700:hover, - .bg-red-800:hover, button.bg-red-800:hover, - .hover\\:bg-red-700:hover, .hover\\:bg-red-800:hover { + .bg-red-600:hover, + button.bg-red-600:hover, + .bg-red-700:hover, + button.bg-red-700:hover, + .bg-red-800:hover, + button.bg-red-800:hover, + .hover\\:bg-red-700:hover, + .hover\\:bg-red-800:hover { background-color: #ef4444 !important; /* red-500 - darker bright light red */ } @@ -349,7 +385,8 @@ endblock %} {% block head_extra_styles %} button.tab-btn.active { background-color: #3b82f6 !important; /* blue-500 */ color: #ffffff !important; /* 确保白色文字 */ - box-shadow: 0 4px 12px -2px rgba(59, 130, 246, 0.4), 0 2px 6px -1px rgba(59, 130, 246, 0.2) !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; /* 更明显的上移效果 */ border: 2px solid #2563eb !important; /* blue-600 边框 */ font-weight: 600 !important; /* 加粗字体 */ @@ -388,7 +425,8 @@ endblock %} {% block head_extra_styles %} 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; + 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; } @@ -409,7 +447,8 @@ endblock %} {% block head_extra_styles %} background-color: rgba(255, 255, 255, 0.98) !important; color: #374151 !important; /* gray-700 */ border-color: rgba(0, 0, 0, 0.08) !important; - box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04) !important; + box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), + 0 10px 10px -5px rgba(0, 0, 0, 0.04) !important; } /* Fix modal titles */ @@ -428,62 +467,78 @@ endblock %} {% block head_extra_styles %} } /* Fix modal body text */ - .modal p, .modal label, .modal span { + .modal p, + .modal label, + .modal span { color: #374151 !important; /* gray-700 */ } /* Fix modal textarea and input styling */ - .modal textarea, .modal input { + .modal textarea, + .modal input { background-color: rgba(255, 255, 255, 0.95) !important; color: #374151 !important; /* gray-700 */ border: 1px solid rgba(0, 0, 0, 0.12) !important; } - .modal textarea:focus, .modal input:focus { + .modal textarea:focus, + .modal input:focus { border-color: #3b82f6 !important; /* blue-500 */ box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1) !important; } /* Fix modal button styling */ - .modal .bg-violet-600, .modal button.bg-violet-600 { + .modal .bg-violet-600, + .modal button.bg-violet-600 { background-color: #3b82f6 !important; /* blue-500 - light blue */ color: #ffffff !important; border: 1px solid #2563eb !important; /* blue-600 */ } - .modal .bg-violet-600:hover, .modal button.bg-violet-600:hover { + .modal .bg-violet-600:hover, + .modal button.bg-violet-600:hover { background-color: #2563eb !important; /* blue-600 - darker light blue */ transform: translateY(-1px) !important; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1) !important; } /* Fix modal blue button styling */ - .modal .bg-blue-500, .modal button.bg-blue-500, - .modal .bg-blue-600, .modal button.bg-blue-600, - .modal .bg-blue-700, .modal button.bg-blue-700 { + .modal .bg-blue-500, + .modal button.bg-blue-500, + .modal .bg-blue-600, + .modal button.bg-blue-600, + .modal .bg-blue-700, + .modal button.bg-blue-700 { background-color: #3b82f6 !important; /* blue-500 - light blue */ color: #ffffff !important; border: 1px solid #2563eb !important; /* blue-600 */ } - .modal .bg-blue-500:hover, .modal button.bg-blue-500:hover, - .modal .bg-blue-600:hover, .modal button.bg-blue-600:hover, - .modal .bg-blue-700:hover, .modal button.bg-blue-700:hover { + .modal .bg-blue-500:hover, + .modal button.bg-blue-500:hover, + .modal .bg-blue-600:hover, + .modal button.bg-blue-600:hover, + .modal .bg-blue-700:hover, + .modal button.bg-blue-700:hover { background-color: #2563eb !important; /* blue-600 - darker light blue */ transform: translateY(-1px) !important; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1) !important; } /* Fix modal cancel/secondary buttons */ - .modal .bg-gray-500, .modal button.bg-gray-500, - .modal .bg-gray-600, .modal button.bg-gray-600 { + .modal .bg-gray-500, + .modal button.bg-gray-500, + .modal .bg-gray-600, + .modal button.bg-gray-600 { background-color: #e5e7eb !important; /* gray-200 - light gray */ color: #374151 !important; /* gray-700 - dark text for contrast */ border: 1px solid #d1d5db !important; /* gray-300 */ } - .modal .bg-gray-500:hover, .modal button.bg-gray-500:hover, - .modal .bg-gray-600:hover, .modal button.bg-gray-600:hover { + .modal .bg-gray-500:hover, + .modal button.bg-gray-500:hover, + .modal .bg-gray-600:hover, + .modal button.bg-gray-600:hover { background-color: #d1d5db !important; /* gray-300 - darker light gray */ color: #374151 !important; /* gray-700 - dark text for contrast */ transform: translateY(-1px) !important; @@ -491,17 +546,23 @@ endblock %} {% block head_extra_styles %} } /* Fix modal red/danger buttons */ - .modal .bg-red-500, .modal button.bg-red-500, - .modal .bg-red-600, .modal button.bg-red-600, - .modal .bg-red-700, .modal button.bg-red-700 { + .modal .bg-red-500, + .modal button.bg-red-500, + .modal .bg-red-600, + .modal button.bg-red-600, + .modal .bg-red-700, + .modal button.bg-red-700 { background-color: #f87171 !important; /* red-400 - bright light red */ color: #ffffff !important; border: 1px solid #ef4444 !important; /* red-500 */ } - .modal .bg-red-500:hover, .modal button.bg-red-500:hover, - .modal .bg-red-600:hover, .modal button.bg-red-600:hover, - .modal .bg-red-700:hover, .modal button.bg-red-700:hover { + .modal .bg-red-500:hover, + .modal button.bg-red-500:hover, + .modal .bg-red-600:hover, + .modal button.bg-red-600:hover, + .modal .bg-red-700:hover, + .modal button.bg-red-700:hover { background-color: #ef4444 !important; /* red-500 - darker bright light red */ transform: translateY(-1px) !important; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1) !important; @@ -542,27 +603,54 @@ endblock %} {% block head_extra_styles %} } /* Comprehensive button text color fixes */ - .bg-blue-500, .bg-blue-600, .bg-blue-700, - .bg-red-500, .bg-red-600, .bg-red-700, .bg-red-800, - .bg-green-500, .bg-green-600, .bg-green-700, - .bg-sky-500, .bg-sky-600, .bg-sky-700, - .bg-purple-500, .bg-purple-600, .bg-purple-700, - .bg-violet-500, .bg-violet-600, .bg-violet-700 { + .bg-blue-500, + .bg-blue-600, + .bg-blue-700, + .bg-red-500, + .bg-red-600, + .bg-red-700, + .bg-red-800, + .bg-green-500, + .bg-green-600, + .bg-green-700, + .bg-sky-500, + .bg-sky-600, + .bg-sky-700, + .bg-purple-500, + .bg-purple-600, + .bg-purple-700, + .bg-violet-500, + .bg-violet-600, + .bg-violet-700 { color: #ffffff !important; } /* Ensure button children inherit white text */ - .bg-blue-500 *, .bg-blue-600 *, .bg-blue-700 *, - .bg-red-500 *, .bg-red-600 *, .bg-red-700 *, .bg-red-800 *, - .bg-green-500 *, .bg-green-600 *, .bg-green-700 *, - .bg-sky-500 *, .bg-sky-600 *, .bg-sky-700 *, - .bg-purple-500 *, .bg-purple-600 *, .bg-purple-700 *, - .bg-violet-500 *, .bg-violet-600 *, .bg-violet-700 * { + .bg-blue-500 *, + .bg-blue-600 *, + .bg-blue-700 *, + .bg-red-500 *, + .bg-red-600 *, + .bg-red-700 *, + .bg-red-800 *, + .bg-green-500 *, + .bg-green-600 *, + .bg-green-700 *, + .bg-sky-500 *, + .bg-sky-600 *, + .bg-sky-700 *, + .bg-purple-500 *, + .bg-purple-600 *, + .bg-purple-700 *, + .bg-violet-500 *, + .bg-violet-600 *, + .bg-violet-700 * { color: inherit !important; } /* Fix page title gradient - comprehensive override */ - h1.text-transparent, .text-transparent.bg-clip-text, + h1.text-transparent, + .text-transparent.bg-clip-text, .bg-gradient-to-r.from-violet-400.to-pink-400 { background: none !important; color: #1f2937 !important; /* gray-800 - consistent with other pages */ @@ -579,15 +667,24 @@ endblock %} {% block head_extra_styles %} } /* Ensure all violet/purple colors are converted to blue theme */ - .text-violet-300, .text-violet-400, .text-violet-100 { + .text-violet-300, + .text-violet-400, + .text-violet-100 { color: #3b82f6 !important; /* blue-500 */ } - .border-violet-300, .border-violet-400 { - border-color: rgba(59, 130, 246, 0.3) !important; /* blue-500 with opacity */ + .border-violet-300, + .border-violet-400 { + border-color: rgba( + 59, + 130, + 246, + 0.3 + ) !important; /* blue-500 with opacity */ } - .hover\\:text-violet-400:hover, .hover\\:text-violet-100:hover { + .hover\\:text-violet-400:hover, + .hover\\:text-violet-100:hover { color: #2563eb !important; /* blue-600 */ } @@ -613,7 +710,9 @@ endblock %} {% block head_extra_styles %} } /* Even more specific selector targeting the exact auth token wrapper */ - .mb-6 .flex.items-center div.flex.items-center.flex-grow.border.rounded-md:focus-within { + .mb-6 + .flex.items-center + div.flex.items-center.flex-grow.border.rounded-md:focus-within { border-color: #3b82f6 !important; /* blue-500 */ } @@ -625,7 +724,12 @@ endblock %} {% block head_extra_styles %} .focus\\:ring-primary-200:focus, .focus\\:ring-primary-300:focus { - --tw-ring-color: rgba(59, 130, 246, 0.2) !important; /* blue-500 with opacity */ + --tw-ring-color: rgba( + 59, + 130, + 246, + 0.2 + ) !important; /* blue-500 with opacity */ } /* Fix select element styling */ @@ -645,15 +749,18 @@ endblock %} {% block head_extra_styles %} } /* Override any remaining primary colors */ - .text-primary-600, .text-primary-500 { + .text-primary-600, + .text-primary-500 { color: #3b82f6 !important; /* blue-500 */ } - .bg-primary-600, .bg-primary-500 { + .bg-primary-600, + .bg-primary-500 { background-color: #3b82f6 !important; /* blue-500 */ } - .bg-primary-700:hover, .hover\\:bg-primary-700:hover { + .bg-primary-700:hover, + .hover\\:bg-primary-700:hover { background-color: #2563eb !important; /* blue-600 */ } @@ -693,9 +800,7 @@ endblock %} {% block head_extra_styles %} -

+

Gemini Balance Logo - - -
-

+
+

图像生成配置 @@ -1634,7 +1817,9 @@ endblock %} {% block head_extra_styles %} placeholder="" class="w-full px-4 py-3 rounded-lg form-input-themed" /> - Cloudflare图床的上传文件夹路径(可选) + Cloudflare图床的上传文件夹路径(可选)

@@ -1990,12 +2175,17 @@ endblock %} {% block head_extra_styles %} -
+
@@ -2003,7 +2193,10 @@ endblock %} {% block head_extra_styles %} type="button" id="resetBtn" class="action-btn bg-gradient-to-r from-gray-600 to-gray-700 text-white px-8 py-3 rounded-xl font-semibold transition-all duration-300 hover:shadow-lg flex items-center justify-center gap-2" - style="background-color: #6b7280 !important; color: #ffffff !important;" + style=" + background-color: #6b7280 !important; + color: #ffffff !important; + " > 重置配置 @@ -2264,8 +2457,8 @@ endblock %} {% block head_extra_styles %}
- - + +