chore: 更新 Vertex API 相关注释和正则表达式为 Vertex Express API,确保一致性和准确性。修改了多个文件中的相关描述和提示信息,以反映 API 名称的变化。

This commit is contained in:
snaily
2025-07-08 15:27:16 +08:00
parent 231b69ecf8
commit d234f826f4
3 changed files with 468 additions and 246 deletions

View File

@@ -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

View File

@@ -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 =
'<div class="text-gray-500 text-sm italic">添加自定义请求头,例如 X-Api-Key: your-key</div>';
'<div class="text-gray-500 text-sm italic">添加自定义请求头,例如 X-Api-Key: your-key</div>';
}
// 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 =
'<div class="text-gray-500 text-sm italic">添加自定义请求头,例如 X-Api-Key: your-key</div>';
}
});
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 = '<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);
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();
}
});
}

View File

@@ -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 %}
<i class="fas fa-sync-alt"></i>
</button>
<h1
class="text-3xl font-extrabold text-center text-gray-800 mb-4"
>
<h1 class="text-3xl font-extrabold text-center text-gray-800 mb-4">
<img
src="/static/icons/logo.png"
alt="Gemini Balance Logo"
@@ -705,11 +810,13 @@ endblock %} {% block head_extra_styles %}
</h1>
<!-- Navigation Tabs -->
<div class="nav-buttons-container flex justify-center mb-8 overflow-x-auto gap-2">
<div
class="nav-buttons-container flex justify-center mb-8 overflow-x-auto gap-2"
>
<a
href="/config"
class="main-nav-btn whitespace-nowrap flex items-center justify-center gap-2 px-6 py-3 font-medium rounded-lg shadow-md hover:shadow-lg transition-all duration-200"
style="background-color: #3b82f6 !important; color: #ffffff !important;"
style="background-color: #3b82f6 !important; color: #ffffff !important"
>
<i class="fas fa-cog"></i> 配置编辑
</a>
@@ -734,49 +841,93 @@ endblock %} {% block head_extra_styles %}
<button
class="tab-btn active px-5 py-2 rounded-full font-medium text-sm transition-all duration-200"
data-tab="api"
style="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;"
style="
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;
"
>
API配置
</button>
<button
class="tab-btn px-5 py-2 rounded-full font-medium text-sm transition-all duration-200"
data-tab="model"
style="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;"
style="
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;
"
>
模型配置
</button>
<button
class="tab-btn px-5 py-2 rounded-full font-medium text-sm transition-all duration-200"
data-tab="tts"
style="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;"
style="
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;
"
>
TTS 配置
</button>
<button
class="tab-btn px-5 py-2 rounded-full font-medium text-sm transition-all duration-200"
data-tab="image"
style="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;"
style="
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;
"
>
图像生成
</button>
<button
class="tab-btn px-5 py-2 rounded-full font-medium text-sm transition-all duration-200"
data-tab="stream"
style="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;"
style="
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;
"
>
流式输出
</button>
<button
class="tab-btn px-5 py-2 rounded-full font-medium text-sm transition-all duration-200"
data-tab="scheduler"
style="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;"
style="
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;
"
>
定时任务
</button>
<button
class="tab-btn px-5 py-2 rounded-full font-medium text-sm transition-all duration-200"
data-tab="logging"
style="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;"
style="
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;
"
>
日志配置
</button>
@@ -901,25 +1052,45 @@ endblock %} {% block head_extra_styles %}
<!-- 自定义Headers -->
<div class="mb-6">
<label for="CUSTOM_HEADERS" class="block font-semibold mb-2 text-gray-700">自定义Headers</label>
<div class="bg-white rounded-lg border border-gray-200 p-4 mb-2 space-y-3" id="CUSTOM_HEADERS_container" style="background-color: rgba(255, 255, 255, 0.95); border: 1px solid rgba(0, 0, 0, 0.12); color: #374151;">
<!-- 键值对将在这里动态添加 -->
<div class="text-gray-500 text-sm italic">
添加自定义请求头,例如 X-Api-Key: your-key
</div>
<label
for="CUSTOM_HEADERS"
class="block font-semibold mb-2 text-gray-700"
>自定义Headers</label
>
<div
class="bg-white rounded-lg border border-gray-200 p-4 mb-2 space-y-3"
id="CUSTOM_HEADERS_container"
style="
background-color: rgba(255, 255, 255, 0.95);
border: 1px solid rgba(0, 0, 0, 0.12);
color: #374151;
"
>
<!-- 键值对将在这里动态添加 -->
<div class="text-gray-500 text-sm italic">
添加自定义请求头,例如 X-Api-Key: your-key
</div>
<div class="flex justify-end">
<button type="button" class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-lg font-medium transition-all duration-200 flex items-center gap-2" id="addCustomHeaderBtn">
<i class="fas fa-plus"></i> 添加Header
</button>
</div>
<small class="text-gray-500 mt-1 block">在这里添加的键值对将被添加到所有出站API请求的Header中。</small>
</div>
<div class="flex justify-end">
<button
type="button"
class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-lg font-medium transition-all duration-200 flex items-center gap-2"
id="addCustomHeaderBtn"
>
<i class="fas fa-plus"></i> 添加Header
</button>
</div>
<small class="text-gray-500 mt-1 block"
>在这里添加的键值对将被添加到所有出站API请求的Header中。</small
>
</div>
<!-- Vertex API密钥列表 -->
<!-- Vertex Express API密钥列表 -->
<div class="mb-6">
<label for="VERTEX_API_KEYS" class="block font-semibold mb-2 text-gray-700"
>Vertex API密钥列表</label
<label
for="VERTEX_API_KEYS"
class="block font-semibold mb-2 text-gray-700"
>Vertex Express API密钥列表</label
>
<div class="array-container" id="VERTEX_API_KEYS_container">
<!-- 数组项将在这里动态添加 -->
@@ -944,10 +1115,12 @@ endblock %} {% block head_extra_styles %}
>Vertex AI Platform API密钥列表。点击按钮可批量添加或删除。</small
>
</div>
<!-- Vertex Express API基础URL -->
<div class="mb-6">
<label for="VERTEX_EXPRESS_BASE_URL" class="block font-semibold mb-2 text-gray-700"
<label
for="VERTEX_EXPRESS_BASE_URL"
class="block font-semibold mb-2 text-gray-700"
>Vertex Express API基础URL</label
>
<input
@@ -957,30 +1130,32 @@ endblock %} {% block head_extra_styles %}
placeholder="https://aiplatform.googleapis.com/v1beta1/publishers/google/models"
class="w-full px-4 py-3 rounded-lg form-input-themed"
/>
<small class="text-gray-500 mt-1 block">Vertex Express API的基础URL</small>
<small class="text-gray-500 mt-1 block"
>Vertex Express API的基础URL</small
>
</div>
<!-- 智能路由配置 -->
<div class="mb-6">
<div class="flex items-center justify-between">
<label
for="URL_NORMALIZATION_ENABLED"
class="font-semibold text-gray-700"
<label
for="URL_NORMALIZATION_ENABLED"
class="font-semibold text-gray-700"
>启用智能路由映射</label
>
<div
class="relative inline-block w-10 mr-2 align-middle select-none transition duration-200 ease-in"
>
<input
type="checkbox"
name="URL_NORMALIZATION_ENABLED"
id="URL_NORMALIZATION_ENABLED"
class="toggle-checkbox absolute block w-6 h-6 rounded-full bg-white border-4 appearance-none cursor-pointer"
/>
<label
for="URL_NORMALIZATION_ENABLED"
class="toggle-label block overflow-hidden h-6 rounded-full bg-gray-300 cursor-pointer"
></label>
</div>
>
<div
class="relative inline-block w-10 mr-2 align-middle select-none transition duration-200 ease-in"
>
<input
type="checkbox"
name="URL_NORMALIZATION_ENABLED"
id="URL_NORMALIZATION_ENABLED"
class="toggle-checkbox absolute block w-6 h-6 rounded-full bg-white border-4 appearance-none cursor-pointer"
/>
<label
for="URL_NORMALIZATION_ENABLED"
class="toggle-label block overflow-hidden h-6 rounded-full bg-gray-300 cursor-pointer"
></label>
</div>
</div>
<small class="text-gray-500 mt-1 block">
自动客户端请求的url拼接为正确格式仅保证正常聊天出现问题请关闭
@@ -1074,28 +1249,28 @@ endblock %} {% block head_extra_styles %}
<!-- 代理使用策略 -->
<div class="mb-6">
<div class="flex items-center justify-between">
<label
for="PROXIES_USE_CONSISTENCY_HASH_BY_API_KEY"
class="font-semibold text-gray-700"
<label
for="PROXIES_USE_CONSISTENCY_HASH_BY_API_KEY"
class="font-semibold text-gray-700"
>是否开启固定代理策略</label
>
<div
class="relative inline-block w-10 mr-2 align-middle select-none transition duration-200 ease-in"
>
<input
type="checkbox"
name="PROXIES_USE_CONSISTENCY_HASH_BY_API_KEY"
id="PROXIES_USE_CONSISTENCY_HASH_BY_API_KEY"
class="toggle-checkbox absolute block w-6 h-6 rounded-full bg-white border-4 appearance-none cursor-pointer"
/>
<label
for="PROXIES_USE_CONSISTENCY_HASH_BY_API_KEY"
class="toggle-label block overflow-hidden h-6 rounded-full bg-gray-300 cursor-pointer"
></label>
</div>
>
<div
class="relative inline-block w-10 mr-2 align-middle select-none transition duration-200 ease-in"
>
<input
type="checkbox"
name="PROXIES_USE_CONSISTENCY_HASH_BY_API_KEY"
id="PROXIES_USE_CONSISTENCY_HASH_BY_API_KEY"
class="toggle-checkbox absolute block w-6 h-6 rounded-full bg-white border-4 appearance-none cursor-pointer"
/>
<label
for="PROXIES_USE_CONSISTENCY_HASH_BY_API_KEY"
class="toggle-label block overflow-hidden h-6 rounded-full bg-gray-300 cursor-pointer"
></label>
</div>
</div>
<small class="text-gray-500 mt-1 block"
>开启后对于每一个API_KEY将根据算法从代理列表中选取同一个代理IP防止一个API_KEY同时被多个IP访问也同时防止了一个IP访问了过多的API_KEY。</small
>开启后对于每一个API_KEY将根据算法从代理列表中选取同一个代理IP防止一个API_KEY同时被多个IP访问也同时防止了一个IP访问了过多的API_KEY。</small
>
</div>
</div>
@@ -1394,8 +1569,8 @@ endblock %} {% block head_extra_styles %}
</div>
</div>
</div>
<!-- TTS配置 -->
<!-- TTS配置 -->
<div class="config-section" id="tts-section">
<h2
class="text-xl font-bold mb-6 pb-3 border-b flex items-center gap-2 text-gray-800 border-violet-300 border-opacity-30"
@@ -1413,15 +1588,21 @@ endblock %} {% block head_extra_styles %}
name="TTS_MODEL"
class="w-full px-4 py-3 rounded-lg form-select-themed"
>
<option value="gemini-2.5-flash-preview-tts">gemini-2.5-flash-preview-tts</option>
<option value="gemini-2.5-pro-preview-tts">gemini-2.5-pro-preview-tts</option>
<option value="gemini-2.5-flash-preview-tts">
gemini-2.5-flash-preview-tts
</option>
<option value="gemini-2.5-pro-preview-tts">
gemini-2.5-pro-preview-tts
</option>
</select>
<small class="text-gray-500 mt-1 block">用于TTS的模型</small>
</div>
<!-- TTS 语音名称 -->
<div class="mb-6">
<label for="TTS_VOICE_NAME" class="block font-semibold mb-2 text-gray-700"
<label
for="TTS_VOICE_NAME"
class="block font-semibold mb-2 text-gray-700"
>TTS 语音名称</label
>
<select
@@ -1460,7 +1641,9 @@ endblock %} {% block head_extra_styles %}
<option value="Sadaltager">Sadaltager (博学)</option>
<option value="Sulafat">Sulafat (温暖)</option>
</select>
<small class="text-gray-500 mt-1 block">TTS 的语音名称,控制风格、语调、口音和节奏</small>
<small class="text-gray-500 mt-1 block"
>TTS 的语音名称,控制风格、语调、口音和节奏</small
>
</div>
<!-- TTS 语速 -->
@@ -1481,9 +1664,9 @@ endblock %} {% block head_extra_styles %}
</div>
</div>
<!-- 图像生成相关配置 -->
<div class="config-section" id="image-section">
<h2
<!-- 图像生成相关配置 -->
<div class="config-section" id="image-section">
<h2
class="text-xl font-bold mb-6 pb-3 border-b flex items-center gap-2 text-gray-800 border-violet-300 border-opacity-30"
>
<i class="fas fa-image text-violet-400"></i> 图像生成配置
@@ -1634,7 +1817,9 @@ endblock %} {% block head_extra_styles %}
placeholder=""
class="w-full px-4 py-3 rounded-lg form-input-themed"
/>
<small class="text-gray-500 mt-1 block">Cloudflare图床的上传文件夹路径可选</small>
<small class="text-gray-500 mt-1 block"
>Cloudflare图床的上传文件夹路径可选</small
>
</div>
</div>
@@ -1990,12 +2175,17 @@ endblock %} {% block head_extra_styles %}
</div>
<!-- Action Buttons -->
<div class="flex flex-col md:flex-row justify-center gap-4 mt-8 pt-4 pb-2">
<div
class="flex flex-col md:flex-row justify-center gap-4 mt-8 pt-4 pb-2"
>
<button
type="button"
id="saveBtn"
class="action-btn 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: #3b82f6 !important; color: #ffffff !important;"
style="
background-color: #3b82f6 !important;
color: #ffffff !important;
"
>
<i class="fas fa-save"></i> 保存配置
</button>
@@ -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;
"
>
<i class="fas fa-undo"></i> 重置配置
</button>
@@ -2264,8 +2457,8 @@ endblock %} {% block head_extra_styles %}
</div>
</div>
</div>
<!-- Vertex API Key Add Modal -->
<!-- Vertex Express API Key Add Modal -->
<div id="vertexApiKeyModal" class="modal">
<div
class="w-full max-w-lg mx-auto rounded-2xl shadow-2xl overflow-hidden animate-fade-in"
@@ -2277,7 +2470,9 @@ endblock %} {% block head_extra_styles %}
>
<div class="p-6">
<div class="flex justify-between items-center mb-4">
<h2 class="text-xl font-bold text-gray-800">批量添加 Vertex API 密钥</h2>
<h2 class="text-xl font-bold text-gray-800">
批量添加 Vertex Express API 密钥
</h2>
<button
id="closeVertexApiKeyModalBtn"
class="text-gray-300 hover:text-gray-800 text-xl"
@@ -2286,12 +2481,13 @@ endblock %} {% block head_extra_styles %}
</button>
</div>
<p class="text-gray-300 mb-4">
每行粘贴一个或多个密钥,将自动提取有效密钥 (格式: AQ.开头共53位) 并去重。
每行粘贴一个或多个密钥,将自动提取有效密钥 (格式: AQ.开头共53位)
并去重。
</p>
<textarea
id="vertexApiKeyBulkInput"
rows="10"
placeholder="在此处粘贴 Vertex API 密钥..."
placeholder="在此处粘贴 Vertex Express API 密钥..."
class="w-full px-4 py-3 rounded-lg font-mono text-sm form-input-themed"
></textarea>
<div class="flex justify-end gap-3 mt-6">
@@ -2313,8 +2509,8 @@ endblock %} {% block head_extra_styles %}
</div>
</div>
</div>
<!-- Bulk Delete Vertex API Key Modal -->
<!-- Bulk Delete Vertex Express API Key Modal -->
<div id="bulkDeleteVertexApiKeyModal" class="modal">
<div
class="w-full max-w-lg mx-auto rounded-2xl shadow-2xl overflow-hidden animate-fade-in"
@@ -2326,7 +2522,9 @@ endblock %} {% block head_extra_styles %}
>
<div class="p-6">
<div class="flex justify-between items-center mb-4">
<h2 class="text-xl font-bold text-gray-800">批量删除 Vertex API 密钥</h2>
<h2 class="text-xl font-bold text-gray-800">
批量删除 Vertex Express API 密钥
</h2>
<button
id="closeBulkDeleteVertexModalBtn"
class="text-gray-300 hover:text-gray-800 text-xl"
@@ -2340,7 +2538,7 @@ endblock %} {% block head_extra_styles %}
<textarea
id="bulkDeleteVertexApiKeyInput"
rows="10"
placeholder="在此处粘贴要删除的 Vertex API 密钥..."
placeholder="在此处粘贴要删除的 Vertex Express API 密钥..."
class="w-full px-4 py-3 rounded-lg font-mono text-sm form-input-themed"
></textarea>
<div class="flex justify-end gap-3 mt-6">
@@ -2362,7 +2560,7 @@ endblock %} {% block head_extra_styles %}
</div>
</div>
</div>
<!-- Model Helper Modal -->
<div id="modelHelperModal" class="modal">
<div
@@ -2490,8 +2688,10 @@ endblock %} {% block head_extra_styles %}
input.addEventListener("focus", function () {
const parentItem = this.closest(".map-item");
if (parentItem) {
parentItem.style.backgroundColor = "rgba(243, 244, 246, 1)"; /* gray-100 */
parentItem.style.borderColor = "rgba(59, 130, 246, 0.5)"; /* blue-500 */
parentItem.style.backgroundColor =
"rgba(243, 244, 246, 1)"; /* gray-100 */
parentItem.style.borderColor =
"rgba(59, 130, 246, 0.5)"; /* blue-500 */
}
});