Files
gemini-balance/app/templates/config_editor.html
snaily a7d548a849 feat: 实现伪流式传输功能
本次提交引入了伪流式传输(Fake Streaming)功能,旨在为不支持原生流式响应的语言模型或特定场景提供类似流式的用户体验。

主要变更包括:

- **配置更新**:
    - 在 `.env.example` 和 `app/config/config.py` 中添加了新的配置项 `FAKE_STREAM_ENABLED` 和 `FAKE_STREAM_EMPTY_DATA_INTERVAL_SECONDS`,用于控制伪流式功能的启用和心跳包发送间隔。
    - 更新了 `README.md` 以包含新的伪流式配置说明。

- **核心服务逻辑**:
    - 在 `app/service/chat/openai_chat_service.py` 中:
        - 新增 `_fake_stream_logic_impl` 方法,用于处理伪流式调用的核心逻辑。当启用伪流式时,该方法会调用非流式接口,并在等待期间定期发送空数据块以维持连接。
        - 修改 `_handle_stream_completion` 方法,使其能够根据 `FAKE_STREAM_ENABLED` 配置在真实流式和伪流式逻辑之间切换。
        - 改进了流式处理中的重试逻辑、API密钥切换机制以及错误日志记录,使其更加健壮。特别是在伪流式场景下,确保了即使在非流式调用中也能正确处理和记录错误。

- **前端配置界面**:
    - 在 `app/static/js/config_editor.js` 中添加了处理和填充伪流式配置项的逻辑。
    - 在 `app/templates/config_editor.html` 中为伪流式配置添加了相应的表单控件,允许用户在配置编辑器中启用/禁用伪流式并设置空数据发送间隔。

该功能通过在后端模拟流式输出,即使底层模型不支持流式传输,也能向客户端提供持续的数据流,从而改善了用户体验,特别是在处理可能耗时较长的请求时。
2025-05-08 23:37:35 +08:00

1773 lines
64 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{% 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>
<!-- Fake Streaming Configuration -->
<h3
class="text-lg font-semibold mb-4 pt-4 border-t border-violet-300 border-opacity-20 text-gray-200"
>
<i class="fas fa-ghost text-violet-400"></i> 假流式配置 (Fake
Streaming)
</h3>
<!-- 启用假流式输出 -->
<div class="mb-6 flex items-center justify-between">
<label for="FAKE_STREAM_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="FAKE_STREAM_ENABLED"
id="FAKE_STREAM_ENABLED"
class="toggle-checkbox absolute block w-6 h-6 rounded-full bg-white border-4 appearance-none cursor-pointer"
/>
<label
for="FAKE_STREAM_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 mb-4"
>当启用时,将调用非流式接口,并在等待响应期间发送空数据以维持连接。</small
>
<!-- 假流式发送空数据的间隔时间 -->
<div class="mb-6">
<label
for="FAKE_STREAM_EMPTY_DATA_INTERVAL_SECONDS"
class="block font-semibold mb-2 text-gray-700"
>假流式空数据发送间隔(秒)</label
>
<input
type="number"
id="FAKE_STREAM_EMPTY_DATA_INTERVAL_SECONDS"
name="FAKE_STREAM_EMPTY_DATA_INTERVAL_SECONDS"
min="1"
max="60"
step="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"
>在启用假流式输出时,向客户端发送空数据以维持连接状态的时间间隔(建议
3-10 秒)。</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 %}