Files
gemini-balance/app/templates/config_editor.html
T
snaily f1f568afca feat(config): 添加模型助手功能以选择和管理模型
本次提交主要包含以下更改:

1. **后端更新**:
   - 在 `app/service/config/config_service.py` 中新增 `fetch_ui_models` 方法,用于获取可用于 UI 的模型列表,并处理相关的错误情况。
   - 在 `app/router/config_routes.py` 中新增 `/ui/models` 路由,提供模型列表的 API 接口,并添加身份验证逻辑。

2. **前端更新**:
   - 在 `app/static/js/config_editor.js` 中实现模型助手的功能,包括模型列表的加载、搜索和选择。
   - 在 `app/templates/config_editor.html` 中添加模型助手的模态框和相关的 UI 元素,允许用户从列表中选择模型。

这些更改旨在增强用户体验,使用户能够更方便地选择和管理模型,提高配置界面的交互性和功能性。
2025-05-08 19:48:03 +08:00

1719 lines
62 KiB
HTML

{% extends "base.html" %} {% block title %}配置编辑器 - Gemini Balance{%
endblock %} {% block head_extra_styles %}
<style>
/* config_editor.html specific styles */
/* Animations (already in base.html, but keep fade-in class usage) */
.fade-in {
animation: fadeIn 0.3s ease forwards;
}
/* Modal specific styles (already in base.html) */
.array-container {
max-height: 300px;
overflow-y: auto;
padding: 1rem; /* p-4 consistency */
margin-bottom: 0.5rem; /* mb-2 consistency */
background-color: rgba(255, 255, 255, 0.05); /* theming */
border: 1px solid rgba(120, 100, 200, 0.3); /* theming */
color: #e2e8f0; /* theming */
border-radius: 0.5rem; /* rounded-lg consistency */
}
#API_KEYS_container {
/* Keep specific ID styling if needed */
max-height: 300px;
overflow-y: auto;
}
.config-section {
display: none;
/* theming: new background and border for sections */
background-color: rgba(70, 50, 150, 0.5);
backdrop-filter: blur(5px);
-webkit-backdrop-filter: blur(5px);
border: 1px solid rgba(120, 100, 200, 0.2);
border-radius: 0.75rem; /* rounded-xl */
padding: 1.5rem; /* p-6 */
margin-bottom: 1.5rem; /* mb-6 */
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1),
0 2px 4px -1px rgba(0, 0, 0, 0.06); /* shadow-lg */
}
.config-section.active {
display: block;
animation: fadeIn 0.3s ease forwards; /* Use base animation */
}
.provider-config {
display: none;
}
.provider-config.active {
display: block;
}
/* Tailwind Toggle Switch Helper CSS */
.toggle-checkbox:checked {
@apply: right-0 border-violet-600; /* theming: changed from primary-600 */
right: 0;
border-color: #7c3aed; /* theming: violet-600 */
}
.toggle-checkbox:checked + .toggle-label {
@apply: bg-violet-600; /* theming: changed from primary-600 */
background-color: #7c3aed; /* theming: violet-600 */
}
.toggle-label {
/* theming: style for unchecked state */
background-color: rgba(120, 100, 200, 0.3);
}
/* 统一通知样式为黑色半透明,确保与 keys_status 一致 */
.notification {
background: rgba(0, 0, 0, 0.8) !important;
color: #fff !important;
}
/* Theming for input fields */
.form-input-themed {
background-color: rgba(0, 0, 0, 0.3) !important;
border: 1px solid rgba(120, 100, 200, 0.5) !important;
color: #e2e8f0 !important;
}
.form-input-themed::placeholder {
color: #a0aec0 !important; /* gray-400 */
}
.form-input-themed:focus {
border-color: #a78bfa !important; /* violet-400 */
box-shadow: 0 0 0 3px rgba(167, 139, 250, 0.4) !important; /* ring-violet-400 */
}
/* Theming for select fields - 改进下拉框样式 */
.form-select-themed {
background-color: rgba(
60,
40,
130,
0.6
) !important; /* 更深的紫色背景,增加透明度 */
border: 1px solid rgba(167, 139, 250, 0.7) !important; /* 更亮的紫色边框 */
color: #ffffff !important; /* 更亮的文字颜色 */
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='%23d8b4fe' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M6 8l4 4 4-4'/%3e%3c/svg%3e") !important; /* 更亮的紫色箭头 */
appearance: none !important;
padding: 0.6rem 2.5rem 0.6rem 0.8rem !important; /* 调整内边距 */
background-repeat: no-repeat !important;
background-position: right 0.6rem center !important;
background-size: 1.5em 1.5em !important;
border-radius: 0.5rem !important; /* 圆角 */
font-weight: 500 !important; /* 半粗体 */
height: auto !important; /* 自动高度 */
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1) !important; /* 微小阴影 */
cursor: pointer !important;
}
.form-select-themed:focus {
border-color: #d8b4fe !important; /* violet-300 */
box-shadow: 0 0 0 3px rgba(216, 180, 254, 0.4) !important; /* ring-violet-300 */
outline: none !important;
}
.form-select-themed option {
background-color: rgba(76, 29, 149, 0.95) !important; /* 暗紫色背景 */
color: #ffffff !important;
padding: 8px !important;
}
/* 不同级别的日志样式 */
#LOG_LEVEL {
font-weight: 600 !important;
}
#LOG_LEVEL option[value="DEBUG"] {
background-color: rgba(79, 70, 229, 0.8) !important; /* indigo */
color: white !important;
}
#LOG_LEVEL option[value="INFO"] {
background-color: rgba(59, 130, 246, 0.8) !important; /* blue */
color: white !important;
}
#LOG_LEVEL option[value="WARNING"] {
background-color: rgba(245, 158, 11, 0.8) !important; /* amber */
color: white !important;
}
#LOG_LEVEL option[value="ERROR"] {
background-color: rgba(239, 68, 68, 0.8) !important; /* red */
color: white !important;
}
#LOG_LEVEL option[value="CRITICAL"] {
background-color: rgba(185, 28, 28, 0.8) !important; /* red-700 */
color: white !important;
}
/* 思考模型预算映射样式 */
.map-item {
background-color: rgba(60, 40, 130, 0.2) !important;
border-radius: 0.5rem !important;
padding: 0.5rem !important;
border: 1px solid rgba(167, 139, 250, 0.3) !important;
transition: all 0.2s ease-in-out !important;
}
.map-item:hover {
background-color: rgba(60, 40, 130, 0.3) !important;
border-color: rgba(167, 139, 250, 0.5) !important;
}
.map-key-input {
background-color: rgba(80, 60, 170, 0.4) !important;
border: 1px solid rgba(167, 139, 250, 0.6) !important;
color: #e2e8f0 !important;
font-weight: 500 !important;
border-radius: 0.375rem !important;
}
.map-value-input {
background-color: rgba(60, 40, 130, 0.4) !important;
border: 1px solid rgba(167, 139, 250, 0.6) !important;
color: #ffffff !important;
font-weight: 600 !important;
border-radius: 0.375rem !important;
box-shadow: none !important;
transition: all 0.2s !important;
}
.map-value-input:focus {
background-color: rgba(80, 60, 170, 0.6) !important;
border-color: #d8b4fe !important; /* violet-300 */
box-shadow: 0 0 0 3px rgba(216, 180, 254, 0.4) !important;
outline: none !important;
}
/* 警告文字样式 */
.warning-text {
color: #f87171 !important; /* red-400 */
font-weight: 600 !important;
background-color: rgba(248, 113, 113, 0.15) !important;
padding: 0.5rem 0.75rem !important;
border-radius: 0.375rem !important;
border-left: 3px solid #ef4444 !important; /* red-500 */
display: flex !important;
align-items: center !important;
margin-top: 0.5rem !important;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1) !important;
}
.warning-text i {
margin-right: 0.5rem !important;
color: #ef4444 !important; /* red-500 */
}
/* 令牌生成按钮样式 */
.generate-btn {
background-color: rgba(80, 60, 170, 0.3) !important;
color: #d8b4fe !important; /* violet-300 */
border: 1px solid rgba(167, 139, 250, 0.4) !important;
border-left: none !important;
transition: all 0.2s ease !important;
}
.generate-btn:hover {
background-color: rgba(
109,
40,
217,
0.4
) !important; /* violet-700 半透明 */
color: #ffffff !important;
box-shadow: 0 0 8px rgba(167, 139, 250, 0.5) !important;
}
.generate-btn:focus {
outline: none !important;
box-shadow: 0 0 0 2px rgba(167, 139, 250, 0.5) !important;
}
/* 导航链接悬停样式 */
.nav-link {
transition: all 0.2s ease-in-out;
}
.nav-link:hover {
background-color: rgba(120, 100, 200, 0.6) !important;
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
}
/* General label and small text theming */
label {
color: #e2e8f0 !important; /* Light gray/white for labels */
font-weight: 500; /* semibold */
}
small {
color: #cbd5e0 !important; /* Lighter gray for small text */
}
</style>
{% endblock %} {% block content %}
<div class="container max-w-4xl mx-auto px-4">
<div
class="rounded-2xl shadow-xl p-6 md:p-8"
style="
background-color: rgba(80, 60, 160, 0.3);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
border: 1px solid rgba(150, 130, 230, 0.3);
"
>
<button
class="absolute top-6 right-6 bg-white bg-opacity-20 hover:bg-opacity-30 rounded-full w-8 h-8 flex items-center justify-center text-violet-300 hover:text-violet-100 transition-all duration-300"
onclick="refreshPage(this)"
>
<i class="fas fa-sync-alt"></i>
</button>
<h1
class="text-3xl font-extrabold text-center text-transparent bg-clip-text bg-gradient-to-r from-violet-400 to-pink-400 mb-4"
>
<img
src="/static/icons/logo.png"
alt="Gemini Balance Logo"
class="h-9 inline-block align-middle mr-2"
/>
Gemini Balance - 配置编辑
</h1>
<!-- Navigation Tabs -->
<div class="flex justify-center mb-8 overflow-x-auto pb-2 gap-2">
<a
href="/config"
class="whitespace-nowrap flex items-center justify-center gap-2 px-6 py-3 font-medium rounded-lg bg-violet-600 text-white shadow-md"
>
<i class="fas fa-cog"></i> 配置编辑
</a>
<a
href="/keys"
class="nav-link whitespace-nowrap flex items-center justify-center gap-2 px-6 py-3 font-medium rounded-lg text-gray-200 hover:text-white transition-all duration-200"
style="background-color: rgba(107, 70, 193, 0.4)"
>
<i class="fas fa-tachometer-alt"></i> 监控面板
</a>
<a
href="/logs"
class="nav-link whitespace-nowrap flex items-center justify-center gap-2 px-6 py-3 font-medium rounded-lg text-gray-200 hover:text-white transition-all duration-200"
style="background-color: rgba(107, 70, 193, 0.4)"
>
<i class="fas fa-exclamation-triangle"></i> 错误日志
</a>
</div>
<!-- Config Tabs -->
<div class="flex justify-center mb-6 flex-wrap gap-2">
<button
class="tab-btn bg-violet-600 text-white px-5 py-2 rounded-full shadow-md font-medium text-sm"
data-tab="api"
>
API配置
</button>
<button
class="tab-btn bg-white bg-opacity-20 text-gray-200 px-5 py-2 rounded-full font-medium text-sm hover:bg-opacity-30 transition-all duration-200"
data-tab="model"
>
模型配置
</button>
<button
class="tab-btn bg-white bg-opacity-20 text-gray-200 px-5 py-2 rounded-full font-medium text-sm hover:bg-opacity-30 transition-all duration-200"
data-tab="image"
>
图像生成
</button>
<button
class="tab-btn bg-white bg-opacity-20 text-gray-200 px-5 py-2 rounded-full font-medium text-sm hover:bg-opacity-30 transition-all duration-200"
data-tab="stream"
>
流式输出
</button>
<button
class="tab-btn bg-white bg-opacity-20 text-gray-200 px-5 py-2 rounded-full font-medium text-sm hover:bg-opacity-30 transition-all duration-200"
data-tab="scheduler"
>
定时任务
</button>
<button
class="tab-btn bg-white bg-opacity-20 text-gray-200 px-5 py-2 rounded-full font-medium text-sm hover:bg-opacity-30 transition-all duration-200"
data-tab="logging"
>
日志配置
</button>
</div>
<!-- Save Status Banner (Removed - using notification component now) -->
<!-- Configuration Form -->
<form id="configForm" class="mt-6">
<!-- API 相关配置 -->
<div class="config-section active" id="api-section">
<h2
class="text-xl font-bold mb-6 pb-3 border-b flex items-center gap-2 text-gray-100 border-violet-300 border-opacity-30"
>
<i class="fas fa-key text-violet-400"></i> API相关配置
</h2>
<!-- API密钥列表 -->
<div class="mb-6">
<label for="API_KEYS" class="block font-semibold mb-2 text-gray-700"
>API密钥列表</label
>
<div class="mb-2">
<input
type="search"
id="apiKeySearchInput"
placeholder="搜索密钥..."
class="w-full px-4 py-2 rounded-lg border border-gray-300 focus:border-primary-500 focus:ring focus:ring-primary-200 focus:ring-opacity-50 form-input-themed"
/>
</div>
<div class="array-container" id="API_KEYS_container">
<!-- 数组项将在这里动态添加 -->
</div>
<div class="flex justify-end gap-2">
<button
type="button"
class="bg-red-600 hover:bg-red-700 text-white px-4 py-2 rounded-lg font-medium transition-all duration-200 flex items-center gap-2"
id="bulkDeleteApiKeyBtn"
>
<i class="fas fa-trash-alt"></i> 删除密钥
</button>
<button
type="button"
class="bg-violet-600 hover:bg-violet-700 text-white px-4 py-2 rounded-lg font-medium transition-all duration-200 flex items-center gap-2"
id="addApiKeyBtn"
>
<i class="fas fa-plus"></i> 添加密钥
</button>
</div>
<small class="text-gray-500 mt-1 block"
>Gemini API密钥列表,每行一个</small
>
</div>
<!-- 允许的令牌列表 -->
<div class="mb-6">
<label
for="ALLOWED_TOKENS"
class="block font-semibold mb-2 text-gray-700"
>允许的令牌列表</label
>
<div class="array-container" id="ALLOWED_TOKENS_container">
<!-- 数组项将在这里动态添加 -->
</div>
<div class="flex justify-end">
<button
type="button"
class="bg-violet-600 hover:bg-violet-700 text-white px-4 py-2 rounded-lg font-medium transition-all duration-200 flex items-center gap-2"
onclick="addArrayItem('ALLOWED_TOKENS')"
>
<i class="fas fa-plus"></i> 添加令牌
</button>
</div>
<small class="text-gray-500 mt-1 block">允许访问API的令牌列表</small>
</div>
<!-- 认证令牌 -->
<div class="mb-6">
<label for="AUTH_TOKEN" class="block font-semibold mb-2 text-gray-700"
>认证令牌</label
>
<div class="flex items-center">
<div
class="flex items-center flex-grow border rounded-md focus-within:border-violet-400 focus-within:ring focus-within:ring-violet-400 focus-within:ring-opacity-50"
style="border-color: rgba(120, 100, 200, 0.5)"
>
<input
type="text"
id="AUTH_TOKEN"
name="AUTH_TOKEN"
placeholder="默认使用ALLOWED_TOKENS中的第一个"
class="array-input flex-grow px-3 py-2 border-none rounded-l-md focus:outline-none sensitive-input form-input-themed"
/>
<button
type="button"
id="generateAuthTokenBtn"
class="generate-btn px-2 py-2 text-gray-400 hover:text-violet-400 focus:outline-none rounded-r-md hover:bg-gray-600 transition-colors"
title="生成随机令牌"
style="background-color: rgba(120, 100, 200, 0.2)"
>
<i class="fas fa-dice"></i>
</button>
</div>
</div>
<small class="text-gray-500 mt-1 block">用于API认证的令牌</small>
</div>
<!-- API基础URL -->
<div class="mb-6">
<label for="BASE_URL" class="block font-semibold mb-2 text-gray-700"
>API基础URL</label
>
<input
type="text"
id="BASE_URL"
name="BASE_URL"
placeholder="https://generativelanguage.googleapis.com/v1beta"
class="w-full px-4 py-3 rounded-lg border border-gray-300 focus:border-primary-500 focus:ring focus:ring-primary-200 focus:ring-opacity-50 form-input-themed"
/>
<small class="text-gray-500 mt-1 block">Gemini API的基础URL</small>
</div>
<!-- 最大失败次数 -->
<div class="mb-6">
<label
for="MAX_FAILURES"
class="block font-semibold mb-2 text-gray-700"
>最大失败次数</label
>
<input
type="number"
id="MAX_FAILURES"
name="MAX_FAILURES"
min="1"
max="100"
class="w-full px-4 py-3 rounded-lg border border-gray-300 focus:border-primary-500 focus:ring focus:ring-primary-200 focus:ring-opacity-50 form-input-themed"
/>
<small class="text-gray-500 mt-1 block"
>API密钥失败后标记为无效的次数</small
>
</div>
<!-- 请求超时时间 -->
<div class="mb-6">
<label for="TIME_OUT" class="block font-semibold mb-2 text-gray-700"
>请求超时时间(秒)</label
>
<input
type="number"
id="TIME_OUT"
name="TIME_OUT"
min="1"
max="600"
class="w-full px-4 py-3 rounded-lg border border-gray-300 focus:border-primary-500 focus:ring focus:ring-primary-200 focus:ring-opacity-50 form-input-themed"
/>
<small class="text-gray-500 mt-1 block">API请求的超时时间</small>
</div>
<!-- 最大重试次数 -->
<div class="mb-6">
<label
for="MAX_RETRIES"
class="block font-semibold mb-2 text-gray-700"
>最大重试次数</label
>
<input
type="number"
id="MAX_RETRIES"
name="MAX_RETRIES"
min="0"
max="10"
class="w-full px-4 py-3 rounded-lg border border-gray-300 focus:border-primary-500 focus:ring focus:ring-primary-200 focus:ring-opacity-50 form-input-themed"
/>
<small class="text-gray-500 mt-1 block"
>API请求失败后的最大重试次数</small
>
</div>
<!-- 代理服务器列表 -->
<div class="mb-6">
<label for="PROXIES" class="block font-semibold mb-2 text-gray-700"
>代理服务器列表</label
>
<div class="array-container" id="PROXIES_container">
<!-- 代理项将在这里动态添加 -->
</div>
<div class="flex justify-end gap-2">
<button
type="button"
class="bg-red-600 hover:bg-red-700 text-white px-4 py-2 rounded-lg font-medium transition-all duration-200 flex items-center gap-2"
id="bulkDeleteProxyBtn"
>
<i class="fas fa-trash-alt"></i> 删除代理
</button>
<button
type="button"
class="bg-violet-600 hover:bg-violet-700 text-white px-4 py-2 rounded-lg font-medium transition-all duration-200 flex items-center gap-2"
id="addProxyBtn"
>
<i class="fas fa-plus"></i> 添加代理
</button>
</div>
<small class="text-gray-500 mt-1 block"
>代理服务器列表,支持 http 和 socks5 格式,例如:
http://user:pass@host:port 或
socks5://host:port。点击按钮可批量添加或删除。</small
>
</div>
</div>
<!-- 模型相关配置 -->
<div class="config-section" id="model-section">
<h2
class="text-xl font-bold mb-6 pb-3 border-b flex items-center gap-2 text-gray-100 border-violet-300 border-opacity-30"
>
<i class="fas fa-robot text-violet-400"></i> 模型相关配置
</h2>
<!-- 测试模型 -->
<div class="mb-6">
<label for="TEST_MODEL" class="block font-semibold mb-2 text-gray-700"
>测试模型</label
>
<div class="flex items-center gap-2">
<input
type="text"
id="TEST_MODEL"
name="TEST_MODEL"
placeholder="gemini-1.5-flash"
class="flex-grow px-4 py-3 rounded-lg border border-gray-300 focus:border-primary-500 focus:ring focus:ring-primary-200 focus:ring-opacity-50 form-input-themed"
/>
<button
type="button"
title="选择模型"
class="model-helper-trigger-btn p-2 rounded-md text-violet-300 hover:bg-violet-700 transition-colors"
data-target-input-id="TEST_MODEL"
>
<i class="fas fa-list-ul"></i>
</button>
</div>
<small class="text-gray-500 mt-1 block">用于测试API密钥的模型</small>
</div>
<!-- 图像模型列表 -->
<div class="mb-6">
<label
for="IMAGE_MODELS"
class="block font-semibold mb-2 text-gray-700"
>图像模型列表</label
>
<div class="array-container" id="IMAGE_MODELS_container">
<!-- 数组项将在这里动态添加 -->
</div>
<div class="flex justify-end gap-2 mt-2">
<button
type="button"
title="从列表选择模型添加到下方"
class="model-helper-trigger-btn p-2 rounded-md text-violet-300 hover:bg-violet-700 transition-colors"
data-target-array-key="IMAGE_MODELS"
>
<i class="fas fa-list-ul"></i>
</button>
<button
type="button"
class="bg-violet-600 hover:bg-violet-700 text-white px-4 py-2 rounded-lg font-medium transition-all duration-200 flex items-center gap-2"
onclick="addArrayItem('IMAGE_MODELS')"
>
<i class="fas fa-plus"></i> 添加模型
</button>
</div>
<small class="text-gray-500 mt-1 block">支持图像处理的模型列表</small>
</div>
<!-- 搜索模型列表 -->
<div class="mb-6">
<label
for="SEARCH_MODELS"
class="block font-semibold mb-2 text-gray-700"
>搜索模型列表</label
>
<div class="array-container" id="SEARCH_MODELS_container">
<!-- 数组项将在这里动态添加 -->
</div>
<div class="flex justify-end gap-2 mt-2">
<button
type="button"
title="从列表选择模型添加到下方"
class="model-helper-trigger-btn p-2 rounded-md text-violet-300 hover:bg-violet-700 transition-colors"
data-target-array-key="SEARCH_MODELS"
>
<i class="fas fa-list-ul"></i>
</button>
<button
type="button"
class="bg-violet-600 hover:bg-violet-700 text-white px-4 py-2 rounded-lg font-medium transition-all duration-200 flex items-center gap-2"
onclick="addArrayItem('SEARCH_MODELS')"
>
<i class="fas fa-plus"></i> 添加模型
</button>
</div>
<small class="text-gray-500 mt-1 block">支持搜索功能的模型列表</small>
</div>
<!-- 过滤模型列表 -->
<div class="mb-6">
<label
for="FILTERED_MODELS"
class="block font-semibold mb-2 text-gray-700"
>过滤模型列表</label
>
<div class="array-container" id="FILTERED_MODELS_container">
<!-- 数组项将在这里动态添加 -->
</div>
<div class="flex justify-end gap-2 mt-2">
<button
type="button"
title="从列表选择模型添加到下方"
class="model-helper-trigger-btn p-2 rounded-md text-violet-300 hover:bg-violet-700 transition-colors"
data-target-array-key="FILTERED_MODELS"
>
<i class="fas fa-list-ul"></i>
</button>
<button
type="button"
class="bg-violet-600 hover:bg-violet-700 text-white px-4 py-2 rounded-lg font-medium transition-all duration-200 flex items-center gap-2"
onclick="addArrayItem('FILTERED_MODELS')"
>
<i class="fas fa-plus"></i> 添加模型
</button>
</div>
<small class="text-gray-500 mt-1 block">需要过滤的模型列表</small>
</div>
<!-- 启用代码执行工具 -->
<div class="mb-6 flex items-center justify-between">
<label
for="TOOLS_CODE_EXECUTION_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="TOOLS_CODE_EXECUTION_ENABLED"
id="TOOLS_CODE_EXECUTION_ENABLED"
class="toggle-checkbox absolute block w-6 h-6 rounded-full bg-white border-4 appearance-none cursor-pointer"
/>
<label
for="TOOLS_CODE_EXECUTION_ENABLED"
class="toggle-label block overflow-hidden h-6 rounded-full bg-gray-300 cursor-pointer"
></label>
</div>
</div>
<!-- 显示搜索链接 -->
<div class="mb-6 flex items-center justify-between">
<label for="SHOW_SEARCH_LINK" 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="SHOW_SEARCH_LINK"
id="SHOW_SEARCH_LINK"
class="toggle-checkbox absolute block w-6 h-6 rounded-full bg-white border-4 appearance-none cursor-pointer"
/>
<label
for="SHOW_SEARCH_LINK"
class="toggle-label block overflow-hidden h-6 rounded-full bg-gray-300 cursor-pointer"
></label>
</div>
</div>
<!-- 显示思考过程 -->
<div class="mb-6 flex items-center justify-between">
<label for="SHOW_THINKING_PROCESS" 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="SHOW_THINKING_PROCESS"
id="SHOW_THINKING_PROCESS"
class="toggle-checkbox absolute block w-6 h-6 rounded-full bg-white border-4 appearance-none cursor-pointer"
/>
<label
for="SHOW_THINKING_PROCESS"
class="toggle-label block overflow-hidden h-6 rounded-full bg-gray-300 cursor-pointer"
></label>
</div>
</div>
<!-- 思考模型列表 -->
<div class="mb-6">
<label
for="THINKING_MODELS"
class="block font-semibold mb-2 text-gray-700"
>思考模型列表</label
>
<div class="array-container" id="THINKING_MODELS_container">
<!-- 数组项将在这里动态添加 -->
</div>
<div class="flex justify-end gap-2 mt-2">
<button
type="button"
title="从列表选择模型添加到下方"
class="model-helper-trigger-btn p-2 rounded-md text-violet-300 hover:bg-violet-700 transition-colors"
data-target-array-key="THINKING_MODELS"
>
<i class="fas fa-list-ul"></i>
</button>
<button
type="button"
class="bg-violet-600 hover:bg-violet-700 text-white px-4 py-2 rounded-lg font-medium transition-all duration-200 flex items-center gap-2"
onclick="addArrayItem('THINKING_MODELS')"
>
<i class="fas fa-plus"></i> 添加模型
</button>
</div>
<small class="text-gray-500 mt-1 block"
>用于"思考过程"的模型列表</small
>
</div>
<!-- 思考模型预算映射 -->
<div class="mb-6">
<label
for="THINKING_BUDGET_MAP"
class="block font-semibold mb-2 text-gray-700"
>思考模型预算映射</label
>
<div
class="bg-white rounded-lg border border-gray-200 p-4 mb-2 space-y-3"
id="THINKING_BUDGET_MAP_container"
style="
background-color: rgba(255, 255, 255, 0.05);
border: 1px solid rgba(120, 100, 200, 0.3);
color: #e2e8f0;
"
>
<!-- 键值对将在这里动态添加 -->
<div class="text-gray-500 text-sm italic">
请先在上方添加思考模型,然后在此处配置预算。
</div>
</div>
<!-- 移除添加预算映射按钮 -->
<!-- <div class="flex justify-end">
<button type="button" class="bg-primary-600 hover:bg-primary-700 text-white px-4 py-2 rounded-lg font-medium transition-all duration-200 flex items-center gap-2" id="addBudgetMapItemBtn">
<i class="fas fa-plus"></i> 添加预算映射
</button>
</div> -->
<small class="text-gray-500 mt-1 block"
>为每个思考模型设置预算(整数,最大值
24576),此项与上方模型列表自动关联。</small
>
</div>
<!-- 安全设置 -->
<div class="mb-6">
<label
for="SAFETY_SETTINGS"
class="block font-semibold mb-2 text-gray-700"
>安全设置 (Safety Settings)</label
>
<div
class="bg-white rounded-lg border border-gray-200 p-4 mb-2 space-y-3"
id="SAFETY_SETTINGS_container"
style="
background-color: rgba(255, 255, 255, 0.05);
border: 1px solid rgba(120, 100, 200, 0.3);
color: #e2e8f0;
"
>
<!-- 安全设置项将在这里动态添加 -->
<div class="text-gray-500 text-sm italic">
定义模型的安全过滤阈值。
</div>
</div>
<div class="flex justify-end">
<button
type="button"
class="bg-violet-600 hover:bg-violet-700 text-white px-4 py-2 rounded-lg font-medium transition-all duration-200 flex items-center gap-2"
id="addSafetySettingBtn"
>
<i class="fas fa-plus"></i> 添加安全设置
</button>
</div>
<small class="text-gray-500 mt-1 block"
>配置模型的安全过滤级别,例如 HARM_CATEGORY_HARASSMENT:
BLOCK_NONE。</small
>
<div class="warning-text">
<i class="fas fa-exclamation-triangle"></i>
<span
>建议设置成OFF,其他值会影响输出速度,非必要不要随便改动。</span
>
</div>
</div>
</div>
<!-- 图像生成相关配置 -->
<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-100 border-violet-300 border-opacity-30"
>
<i class="fas fa-image text-violet-400"></i> 图像生成配置
</h2>
<!-- 付费API密钥 -->
<div class="mb-6">
<label for="PAID_KEY" class="block font-semibold mb-2 text-gray-700"
>付费API密钥</label
>
<input
type="text"
id="PAID_KEY"
name="PAID_KEY"
placeholder="AIzaSyxxxxxxxxxxxxxxxxxxx"
class="w-full px-4 py-3 rounded-lg border border-gray-300 focus:border-primary-500 focus:ring focus:ring-primary-200 focus:ring-opacity-50 sensitive-input form-input-themed"
/>
<small class="text-gray-500 mt-1 block"
>用于图像生成的付费API密钥</small
>
</div>
<!-- 图像生成模型 -->
<div class="mb-6">
<label
for="CREATE_IMAGE_MODEL"
class="block font-semibold mb-2 text-gray-700"
>图像生成模型</label
>
<div class="flex items-center gap-2">
<input
type="text"
id="CREATE_IMAGE_MODEL"
name="CREATE_IMAGE_MODEL"
placeholder="imagen-3.0-generate-002"
class="flex-grow px-4 py-3 rounded-lg border border-gray-300 focus:border-primary-500 focus:ring focus:ring-primary-200 focus:ring-opacity-50 form-input-themed"
/>
<button
type="button"
title="选择模型"
class="model-helper-trigger-btn p-2 rounded-md text-violet-300 hover:bg-violet-700 transition-colors"
data-target-input-id="CREATE_IMAGE_MODEL"
>
<i class="fas fa-list-ul"></i>
</button>
</div>
<small class="text-gray-500 mt-1 block">用于图像生成的模型</small>
</div>
<!-- 上传提供商 -->
<div class="mb-6">
<label
for="UPLOAD_PROVIDER"
class="block font-semibold mb-2 text-gray-700"
>上传提供商</label
>
<select
id="UPLOAD_PROVIDER"
name="UPLOAD_PROVIDER"
class="w-full px-4 py-3 rounded-lg border border-gray-300 focus:border-primary-500 focus:ring focus:ring-primary-200 focus:ring-opacity-50 form-select-themed"
>
<option value="smms" selected>SM.MS</option>
<option value="picgo">PicGo</option>
<option value="cloudflare_imgbed">Cloudflare</option>
</select>
<small class="text-gray-500 mt-1 block">图片上传服务提供商</small>
</div>
<!-- SM.MS密钥 -->
<div class="mb-6 provider-config active" data-provider="smms">
<label
for="SMMS_SECRET_TOKEN"
class="block font-semibold mb-2 text-gray-700"
>SM.MS密钥</label
>
<input
type="text"
id="SMMS_SECRET_TOKEN"
name="SMMS_SECRET_TOKEN"
placeholder="XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
class="w-full px-4 py-3 rounded-lg border border-gray-300 focus:border-primary-500 focus:ring focus:ring-primary-200 focus:ring-opacity-50 sensitive-input form-input-themed"
/>
<small class="text-gray-500 mt-1 block">SM.MS图床的密钥</small>
</div>
<!-- PicGo API密钥 -->
<div class="mb-6 provider-config" data-provider="picgo">
<label
for="PICGO_API_KEY"
class="block font-semibold mb-2 text-gray-700"
>PicGo API密钥</label
>
<input
type="text"
id="PICGO_API_KEY"
name="PICGO_API_KEY"
placeholder="xxxx"
class="w-full px-4 py-3 rounded-lg border border-gray-300 focus:border-primary-500 focus:ring focus:ring-primary-200 focus:ring-opacity-50 sensitive-input form-input-themed"
/>
<small class="text-gray-500 mt-1 block">PicGo的API密钥</small>
</div>
<!-- Cloudflare图床URL -->
<div class="mb-6 provider-config" data-provider="cloudflare_imgbed">
<label
for="CLOUDFLARE_IMGBED_URL"
class="block font-semibold mb-2 text-gray-700"
>Cloudflare图床URL</label
>
<input
type="text"
id="CLOUDFLARE_IMGBED_URL"
name="CLOUDFLARE_IMGBED_URL"
placeholder="https://xxxxxxx.pages.dev/upload"
class="w-full px-4 py-3 rounded-lg border border-gray-300 focus:border-primary-500 focus:ring focus:ring-primary-200 focus:ring-opacity-50 form-input-themed"
/>
<small class="text-gray-500 mt-1 block">Cloudflare图床的URL</small>
</div>
<!-- Cloudflare认证码 -->
<div class="mb-6 provider-config" data-provider="cloudflare_imgbed">
<label
for="CLOUDFLARE_IMGBED_AUTH_CODE"
class="block font-semibold mb-2 text-gray-700"
>Cloudflare认证码</label
>
<input
type="text"
id="CLOUDFLARE_IMGBED_AUTH_CODE"
name="CLOUDFLARE_IMGBED_AUTH_CODE"
placeholder="xxxxxxxxx"
class="w-full px-4 py-3 rounded-lg border border-gray-300 focus:border-primary-500 focus:ring focus:ring-primary-200 focus:ring-opacity-50 sensitive-input form-input-themed"
/>
<small class="text-gray-500 mt-1 block">Cloudflare图床的认证码</small>
</div>
</div>
<!-- 流式输出优化器配置 -->
<div class="config-section" id="stream-section">
<h2
class="text-xl font-bold mb-6 pb-3 border-b flex items-center gap-2 text-gray-100 border-violet-300 border-opacity-30"
>
<i class="fas fa-stream text-violet-400"></i> 流式输出优化器
</h2>
<!-- 启用流式输出优化 -->
<div class="mb-6 flex items-center justify-between">
<label
for="STREAM_OPTIMIZER_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="STREAM_OPTIMIZER_ENABLED"
id="STREAM_OPTIMIZER_ENABLED"
class="toggle-checkbox absolute block w-6 h-6 rounded-full bg-white border-4 appearance-none cursor-pointer"
/>
<label
for="STREAM_OPTIMIZER_ENABLED"
class="toggle-label block overflow-hidden h-6 rounded-full bg-gray-300 cursor-pointer"
></label>
</div>
</div>
<!-- 最小延迟 -->
<div class="mb-6">
<label
for="STREAM_MIN_DELAY"
class="block font-semibold mb-2 text-gray-700"
>最小延迟(秒)</label
>
<input
type="number"
id="STREAM_MIN_DELAY"
name="STREAM_MIN_DELAY"
min="0"
max="1"
step="0.001"
class="w-full px-4 py-3 rounded-lg border border-gray-300 focus:border-primary-500 focus:ring focus:ring-primary-200 focus:ring-opacity-50 form-input-themed"
/>
<small class="text-gray-500 mt-1 block">流式输出的最小延迟时间</small>
</div>
<!-- 最大延迟 -->
<div class="mb-6">
<label
for="STREAM_MAX_DELAY"
class="block font-semibold mb-2 text-gray-700"
>最大延迟(秒)</label
>
<input
type="number"
id="STREAM_MAX_DELAY"
name="STREAM_MAX_DELAY"
min="0"
max="1"
step="0.001"
class="w-full px-4 py-3 rounded-lg border border-gray-300 focus:border-primary-500 focus:ring focus:ring-primary-200 focus:ring-opacity-50 form-input-themed"
/>
<small class="text-gray-500 mt-1 block">流式输出的最大延迟时间</small>
</div>
<!-- 短文本阈值 -->
<div class="mb-6">
<label
for="STREAM_SHORT_TEXT_THRESHOLD"
class="block font-semibold mb-2 text-gray-700"
>短文本阈值</label
>
<input
type="number"
id="STREAM_SHORT_TEXT_THRESHOLD"
name="STREAM_SHORT_TEXT_THRESHOLD"
min="1"
max="100"
class="w-full px-4 py-3 rounded-lg border border-gray-300 focus:border-primary-500 focus:ring focus:ring-primary-200 focus:ring-opacity-50 form-input-themed"
/>
<small class="text-gray-500 mt-1 block">短文本的字符阈值</small>
</div>
<!-- 长文本阈值 -->
<div class="mb-6">
<label
for="STREAM_LONG_TEXT_THRESHOLD"
class="block font-semibold mb-2 text-gray-700"
>长文本阈值</label
>
<input
type="number"
id="STREAM_LONG_TEXT_THRESHOLD"
name="STREAM_LONG_TEXT_THRESHOLD"
min="1"
max="1000"
class="w-full px-4 py-3 rounded-lg border border-gray-300 focus:border-primary-500 focus:ring focus:ring-primary-200 focus:ring-opacity-50 form-input-themed"
/>
<small class="text-gray-500 mt-1 block">长文本的字符阈值</small>
</div>
<!-- 分块大小 -->
<div class="mb-6">
<label
for="STREAM_CHUNK_SIZE"
class="block font-semibold mb-2 text-gray-700"
>分块大小</label
>
<input
type="number"
id="STREAM_CHUNK_SIZE"
name="STREAM_CHUNK_SIZE"
min="1"
max="100"
class="w-full px-4 py-3 rounded-lg border border-gray-300 focus:border-primary-500 focus:ring focus:ring-primary-200 focus:ring-opacity-50 form-input-themed"
/>
<small class="text-gray-500 mt-1 block">流式输出的分块大小</small>
</div>
</div>
<!-- 定时任务配置 -->
<div class="config-section" id="scheduler-section">
<h2
class="text-xl font-bold mb-6 pb-3 border-b flex items-center gap-2 text-gray-100 border-violet-300 border-opacity-30"
>
<i class="fas fa-clock text-violet-400"></i> 定时任务配置
</h2>
<!-- 检查间隔 -->
<div class="mb-6">
<label
for="CHECK_INTERVAL_HOURS"
class="block font-semibold mb-2 text-gray-700"
>检查间隔(小时)</label
>
<input
type="number"
id="CHECK_INTERVAL_HOURS"
name="CHECK_INTERVAL_HOURS"
min="1"
class="w-full px-4 py-3 rounded-lg border border-gray-300 focus:border-primary-500 focus:ring focus:ring-primary-200 focus:ring-opacity-50 form-input-themed"
/>
<small class="text-gray-500 mt-1 block"
>定时检查密钥状态的间隔时间(单位:小时)</small
>
</div>
<!-- 时区 -->
<div class="mb-6">
<label for="TIMEZONE" class="block font-semibold mb-2 text-gray-700"
>时区</label
>
<input
type="text"
id="TIMEZONE"
name="TIMEZONE"
placeholder="例如: Asia/Shanghai"
class="w-full px-4 py-3 rounded-lg border border-gray-300 focus:border-primary-500 focus:ring focus:ring-primary-200 focus:ring-opacity-50 form-input-themed"
/>
<small class="text-gray-500 mt-1 block"
>定时任务使用的时区,格式如 "Asia/Shanghai" 或 "UTC"</small
>
</div>
</div>
<!-- 日志配置 -->
<div class="config-section" id="logging-section">
<h2
class="text-xl font-bold mb-6 pb-3 border-b flex items-center gap-2 text-gray-100 border-violet-300 border-opacity-30"
>
<i class="fas fa-file-alt text-violet-400"></i> 日志配置
</h2>
<!-- 日志级别 -->
<div class="mb-6">
<label for="LOG_LEVEL" class="block font-semibold mb-2 text-gray-700"
>日志级别</label
>
<select
id="LOG_LEVEL"
name="LOG_LEVEL"
class="w-full px-4 py-3 rounded-lg form-select-themed"
>
<option value="DEBUG">DEBUG</option>
<option value="INFO">INFO</option>
<option value="WARNING">WARNING</option>
<option value="ERROR">ERROR</option>
<option value="CRITICAL">CRITICAL</option>
</select>
<small class="text-gray-500 mt-1 block"
>设置应用程序的日志记录详细程度</small
>
</div>
<!-- 自动删除错误日志 -->
<div class="mb-6">
<div class="flex items-center justify-between">
<label
for="AUTO_DELETE_ERROR_LOGS_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="AUTO_DELETE_ERROR_LOGS_ENABLED"
id="AUTO_DELETE_ERROR_LOGS_ENABLED"
class="toggle-checkbox absolute block w-6 h-6 rounded-full bg-white border-4 appearance-none cursor-pointer"
/>
<label
for="AUTO_DELETE_ERROR_LOGS_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"
>开启后,将自动删除指定天数前的错误日志。</small
>
</div>
<!-- 自动删除日志天数 -->
<div class="mb-6">
<label
for="AUTO_DELETE_ERROR_LOGS_DAYS"
class="block font-semibold mb-2 text-gray-700"
>自动删除多少天前的错误日志</label
>
<select
id="AUTO_DELETE_ERROR_LOGS_DAYS"
name="AUTO_DELETE_ERROR_LOGS_DAYS"
class="w-full px-4 py-3 rounded-lg border border-gray-300 focus:border-primary-500 focus:ring focus:ring-primary-200 focus:ring-opacity-50 form-select-themed"
>
<option value="1">1 天</option>
<option value="7">7 天</option>
<option value="30">30 天</option>
</select>
<small class="text-gray-500 mt-1 block"
>选择自动删除错误日志的天数。</small
>
</div>
<!-- 自动删除请求日志 -->
<div class="mb-6">
<div class="flex items-center justify-between">
<label
for="AUTO_DELETE_REQUEST_LOGS_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="AUTO_DELETE_REQUEST_LOGS_ENABLED"
id="AUTO_DELETE_REQUEST_LOGS_ENABLED"
class="toggle-checkbox absolute block w-6 h-6 rounded-full bg-white border-4 appearance-none cursor-pointer"
/>
<label
for="AUTO_DELETE_REQUEST_LOGS_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"
>开启后,将自动删除指定天数前的请求日志。</small
>
</div>
<!-- 自动删除请求日志天数 -->
<div class="mb-6">
<label
for="AUTO_DELETE_REQUEST_LOGS_DAYS"
class="block font-semibold mb-2 text-gray-700"
>自动删除多少天前的请求日志</label
>
<select
id="AUTO_DELETE_REQUEST_LOGS_DAYS"
name="AUTO_DELETE_REQUEST_LOGS_DAYS"
class="w-full px-4 py-3 rounded-lg border border-gray-300 focus:border-primary-500 focus:ring focus:ring-primary-200 focus:ring-opacity-50 form-select-themed"
>
<option value="1">1 天</option>
<option value="7">7 天</option>
<option value="30">30 天</option>
</select>
<small class="text-gray-500 mt-1 block"
>选择自动删除请求日志的天数。</small
>
</div>
</div>
<!-- Action Buttons -->
<div class="flex flex-col md:flex-row justify-center gap-4 mt-8">
<button
type="button"
id="saveBtn"
class="bg-gradient-to-r from-violet-500 to-pink-500 text-white px-8 py-3 rounded-xl font-semibold transition-all duration-300 transform hover:-translate-y-1 hover:shadow-lg flex items-center justify-center gap-2"
>
<i class="fas fa-save"></i> 保存配置
</button>
<button
type="button"
id="resetBtn"
class="bg-gradient-to-r from-gray-600 to-gray-700 text-white px-8 py-3 rounded-xl font-semibold transition-all duration-300 transform hover:-translate-y-1 hover:shadow-lg flex items-center justify-center gap-2"
style="
background-image: linear-gradient(
to right,
rgba(107, 70, 193, 0.7),
rgba(120, 100, 200, 0.8)
);
"
>
<i class="fas fa-undo"></i> 重置配置
</button>
</div>
</form>
</div>
</div>
<!-- Scroll buttons are now in base.html -->
<div class="scroll-buttons">
<button class="scroll-button" onclick="scrollToTop()" title="回到顶部">
<i class="fas fa-chevron-up"></i>
</button>
<button class="scroll-button" onclick="scrollToBottom()" title="滚动到底部">
<i class="fas fa-chevron-down"></i>
</button>
</div>
<!-- Notification component is now in base.html -->
<div id="notification" class="notification"></div>
<!-- Footer is now in base.html -->
<!-- API Key Add Modal -->
<div id="apiKeyModal" class="modal">
<div
class="w-full max-w-lg mx-auto rounded-2xl shadow-2xl overflow-hidden animate-fade-in"
style="
background-color: rgba(70, 50, 150, 0.95);
color: #ffffff;
border: 1px solid rgba(120, 100, 200, 0.4);
"
>
<div class="p-6">
<div class="flex justify-between items-center mb-4">
<h2 class="text-xl font-bold text-gray-100">批量添加 API 密钥</h2>
<button
id="closeApiKeyModalBtn"
class="text-gray-300 hover:text-gray-100 text-xl"
>
&times;
</button>
</div>
<p class="text-gray-300 mb-4">
每行粘贴一个或多个密钥,将自动提取有效密钥并去重。
</p>
<textarea
id="apiKeyBulkInput"
rows="10"
placeholder="在此处粘贴 API 密钥..."
class="w-full px-4 py-3 rounded-lg border font-mono text-sm form-input-themed"
></textarea>
<div class="flex justify-end gap-3 mt-6">
<button
type="button"
id="confirmAddApiKeyBtn"
class="bg-violet-600 hover:bg-violet-700 text-white px-6 py-2 rounded-lg font-medium transition"
>
确认添加
</button>
<button
type="button"
id="cancelAddApiKeyBtn"
class="bg-gray-500 bg-opacity-50 hover:bg-opacity-70 text-gray-200 px-6 py-2 rounded-lg font-medium transition"
>
取消
</button>
</div>
</div>
</div>
</div>
<!-- Bulk Delete API Key Modal -->
<div id="bulkDeleteApiKeyModal" class="modal">
<div
class="w-full max-w-lg mx-auto rounded-2xl shadow-2xl overflow-hidden animate-fade-in"
style="
background-color: rgba(70, 50, 150, 0.95);
color: #ffffff;
border: 1px solid rgba(120, 100, 200, 0.4);
"
>
<div class="p-6">
<div class="flex justify-between items-center mb-4">
<h2 class="text-xl font-bold text-gray-100">批量删除 API 密钥</h2>
<button
id="closeBulkDeleteModalBtn"
class="text-gray-300 hover:text-gray-100 text-xl"
>
&times;
</button>
</div>
<p class="text-gray-300 mb-4">
每行粘贴一个或多个密钥,将自动提取有效密钥并从列表中删除。
</p>
<textarea
id="bulkDeleteApiKeyInput"
rows="10"
placeholder="在此处粘贴要删除的 API 密钥..."
class="w-full px-4 py-3 rounded-lg border font-mono text-sm form-input-themed focus:border-red-500 focus:ring-red-500"
></textarea>
<div class="flex justify-end gap-3 mt-6">
<button
type="button"
id="confirmBulkDeleteApiKeyBtn"
class="bg-red-600 hover:bg-red-700 text-white px-6 py-2 rounded-lg font-medium transition"
>
确认删除
</button>
<button
type="button"
id="cancelBulkDeleteApiKeyBtn"
class="bg-gray-500 bg-opacity-50 hover:bg-opacity-70 text-gray-200 px-6 py-2 rounded-lg font-medium transition"
>
取消
</button>
</div>
</div>
</div>
</div>
<!-- Proxy Add Modal -->
<div id="proxyModal" class="modal">
<div
class="w-full max-w-lg mx-auto rounded-2xl shadow-2xl overflow-hidden animate-fade-in"
style="
background-color: rgba(70, 50, 150, 0.95);
color: #ffffff;
border: 1px solid rgba(120, 100, 200, 0.4);
"
>
<div class="p-6">
<div class="flex justify-between items-center mb-4">
<h2 class="text-xl font-bold text-gray-100">批量添加代理服务器</h2>
<button
id="closeProxyModalBtn"
class="text-gray-300 hover:text-gray-100 text-xl"
>
&times;
</button>
</div>
<p class="text-gray-300 mb-4">
每行粘贴一个或多个代理地址,将自动提取有效地址并去重。
</p>
<textarea
id="proxyBulkInput"
rows="10"
placeholder="在此处粘贴代理地址 (例如 http://user:pass@host:port 或 socks5://host:port)..."
class="w-full px-4 py-3 rounded-lg border font-mono text-sm form-input-themed"
></textarea>
<div class="flex justify-end gap-3 mt-6">
<button
type="button"
id="confirmAddProxyBtn"
class="bg-violet-600 hover:bg-violet-700 text-white px-6 py-2 rounded-lg font-medium transition"
>
确认添加
</button>
<button
type="button"
id="cancelAddProxyBtn"
class="bg-gray-500 bg-opacity-50 hover:bg-opacity-70 text-gray-200 px-6 py-2 rounded-lg font-medium transition"
>
取消
</button>
</div>
</div>
</div>
</div>
<!-- Bulk Delete Proxy Modal -->
<div id="bulkDeleteProxyModal" class="modal">
<div
class="w-full max-w-lg mx-auto rounded-2xl shadow-2xl overflow-hidden animate-fade-in"
style="
background-color: rgba(70, 50, 150, 0.95);
color: #ffffff;
border: 1px solid rgba(120, 100, 200, 0.4);
"
>
<div class="p-6">
<div class="flex justify-between items-center mb-4">
<h2 class="text-xl font-bold text-gray-100">批量删除代理服务器</h2>
<button
id="closeBulkDeleteProxyModalBtn"
class="text-gray-300 hover:text-gray-100 text-xl"
>
&times;
</button>
</div>
<p class="text-gray-300 mb-4">
每行粘贴一个或多个代理地址,将自动提取有效地址并从列表中删除。
</p>
<textarea
id="bulkDeleteProxyInput"
rows="10"
placeholder="在此处粘贴要删除的代理地址..."
class="w-full px-4 py-3 rounded-lg border font-mono text-sm form-input-themed focus:border-red-500 focus:ring-red-500"
></textarea>
<div class="flex justify-end gap-3 mt-6">
<button
type="button"
id="confirmBulkDeleteProxyBtn"
class="bg-red-600 hover:bg-red-700 text-white px-6 py-2 rounded-lg font-medium transition"
>
确认删除
</button>
<button
type="button"
id="cancelBulkDeleteProxyBtn"
class="bg-gray-500 bg-opacity-50 hover:bg-opacity-70 text-gray-200 px-6 py-2 rounded-lg font-medium transition"
>
取消
</button>
</div>
</div>
</div>
</div>
<!-- Reset Confirmation Modal -->
<div id="resetConfirmModal" class="modal">
<div
class="w-full max-w-md mx-auto rounded-2xl shadow-2xl overflow-hidden animate-fade-in"
style="
background-color: rgba(70, 50, 150, 0.95);
color: #ffffff;
border: 1px solid rgba(120, 100, 200, 0.4);
"
>
<div class="p-6">
<div class="flex justify-between items-center mb-4">
<h2 class="text-xl font-bold text-gray-100">确认重置配置</h2>
<button
id="closeResetModalBtn"
class="text-gray-300 hover:text-gray-100 text-xl"
>
&times;
</button>
</div>
<p class="text-gray-300 mb-6">
确定要重置所有配置吗?<br />这将恢复到默认值,此操作不可撤销。
</p>
<div class="flex justify-end gap-3">
<button
type="button"
id="confirmResetBtn"
class="bg-red-500 hover:bg-red-600 text-white px-6 py-2 rounded-lg font-medium transition"
>
确认重置
</button>
<button
type="button"
id="cancelResetBtn"
class="bg-violet-600 hover:bg-violet-700 text-white px-6 py-2 rounded-lg font-medium transition"
>
取消
</button>
</div>
</div>
</div>
</div>
<!-- Model Helper Modal -->
<div id="modelHelperModal" class="modal">
<div
class="w-full max-w-lg mx-auto rounded-2xl shadow-2xl overflow-hidden animate-fade-in"
style="
background-color: rgba(70, 50, 150, 0.95);
color: #ffffff;
border: 1px solid rgba(120, 100, 200, 0.4);
"
>
<div class="p-6">
<div class="flex justify-between items-center mb-4">
<h2 id="modelHelperTitle" class="text-xl font-bold text-gray-100">
选择模型
</h2>
<button
id="closeModelHelperModalBtn"
class="text-gray-300 hover:text-gray-100 text-xl"
>
&times;
</button>
</div>
<input
type="text"
id="modelHelperSearchInput"
placeholder="搜索模型..."
class="w-full px-4 py-3 mb-4 rounded-lg border font-mono text-sm form-input-themed"
/>
<div
id="modelHelperListContainer"
class="array-container"
style="max-height: 300px; overflow-y: auto"
>
<!-- Model items will be populated here -->
<p class="text-gray-400 text-sm italic">正在加载模型列表...</p>
</div>
<div class="flex justify-end gap-3 mt-6">
<button
type="button"
id="cancelModelHelperBtn"
class="bg-gray-500 bg-opacity-50 hover:bg-opacity-70 text-gray-200 px-6 py-2 rounded-lg font-medium transition"
>
关闭
</button>
</div>
</div>
</div>
</div>
{% endblock %} {% block body_scripts %}
<script src="/static/js/config_editor.js"></script>
<!-- 增强下拉框样式和交互性 -->
<script>
document.addEventListener("DOMContentLoaded", function () {
// 增强所有下拉框的交互性
const selects = document.querySelectorAll(".form-select-themed");
selects.forEach((select) => {
// 添加选择事件来应用选中效果
select.addEventListener("change", function () {
this.classList.add("selected");
// 设置微小的动画效果
this.style.transition = "all 0.2s ease";
this.style.transform = "scale(1.02)";
setTimeout(() => {
this.style.transform = "scale(1)";
}, 200);
});
// 添加焦点事件
select.addEventListener("focus", function () {
this.style.boxShadow = "0 0 0 3px rgba(216, 180, 254, 0.5)";
});
// 根据是否有选中值添加选中样式
if (select.value && select.value !== "") {
select.classList.add("selected");
}
});
// 美化日志级别选择的显示
const logLevelSelect = document.getElementById("LOG_LEVEL");
if (logLevelSelect) {
// 给不同日志级别添加不同的样式
const updateLogLevelStyle = () => {
const value = logLevelSelect.value;
// 根据不同日志级别设置不同的样式
switch (value) {
case "DEBUG":
logLevelSelect.style.borderColor = "rgba(129, 140, 248, 0.8)";
logLevelSelect.style.color = "#c7d2fe"; // indigo-200
break;
case "INFO":
logLevelSelect.style.borderColor = "rgba(96, 165, 250, 0.8)";
logLevelSelect.style.color = "#bfdbfe"; // blue-200
break;
case "WARNING":
logLevelSelect.style.borderColor = "rgba(251, 191, 36, 0.8)";
logLevelSelect.style.color = "#fde68a"; // amber-200
break;
case "ERROR":
logLevelSelect.style.borderColor = "rgba(239, 68, 68, 0.8)";
logLevelSelect.style.color = "#fecaca"; // red-200
break;
case "CRITICAL":
logLevelSelect.style.borderColor = "rgba(220, 38, 38, 0.8)";
logLevelSelect.style.color = "#fca5a5"; // red-300
break;
default:
logLevelSelect.style.borderColor = "rgba(167, 139, 250, 0.7)";
logLevelSelect.style.color = "#ffffff";
}
};
// 初始化和变更时更新样式
updateLogLevelStyle();
logLevelSelect.addEventListener("change", updateLogLevelStyle);
}
// 增强预算映射项交互
const budgetInputs = document.querySelectorAll(".map-value-input");
budgetInputs.forEach((input) => {
// 添加焦点和悬停效果
input.addEventListener("focus", function () {
const parentItem = this.closest(".map-item");
if (parentItem) {
parentItem.style.backgroundColor = "rgba(60, 40, 130, 0.4)";
parentItem.style.borderColor = "rgba(167, 139, 250, 0.7)";
}
});
input.addEventListener("blur", function () {
const parentItem = this.closest(".map-item");
if (parentItem) {
parentItem.style.backgroundColor = "";
parentItem.style.borderColor = "";
}
});
// 输入限制为正整数且不超过最大值
input.addEventListener("input", function () {
let val = this.value.replace(/[^0-9]/g, "");
if (val !== "") {
val = parseInt(val, 10);
if (val > 24576) val = 24576;
}
this.value = val;
// 添加微小动画反馈
this.style.transform = "scale(1.05)";
setTimeout(() => {
this.style.transform = "scale(1)";
}, 150);
});
});
});
</script>
{% endblock %}