Files
gemini-banlancer/web/templates/keys.html
2025-11-20 12:24:05 +08:00

943 lines
61 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 %}API 分组管理 - GEMINI BALANCER{% endblock %}
{% block content %}
<div class="w-full h-full flex flex-col pl-0 pr-3 lg:px-0">
<!-- [核心] 页面顶栏:标题与全局控制器 -->
<div class="flex items-center justify-between mb-6 shrink-0">
<h2 class="text-3xl font-bold tracking-tight">API 管理</h2>
<button class="lg:hidden text-zinc-500 dark:text-zinc-400">
<i class="fas fa-search text-lg"></i>
</button>
</div>
<div class="flex flex-col lg:flex-row flex-grow gap-x-4 overflow-hidden min-h-0">
<!-- 左侧分栏: Group 列表 -->
<aside class="w-full lg:w-1/4 flex flex-col p-0 relative mb-3 lg:mb-0 lg:min-h-0 shrink-0">
<!-- 桌面端搜索框 -->
<div class="relative mb-3 mr-4 shrink-0 hidden lg:block">
<input type="text" class="pl-8 h-9 bg-transparent border border-gray-300 dark:border-gray-700 dark:text-white w-full rounded-md text-sm transition-colors duration-200 ease-in-out focus:outline-none focus:border-blue-500 dark:focus:border-blue-500 focus:ring-1 focus:ring-blue-500 focus:ring-inset" placeholder="Search Groups...">
<i class="fas fa-search w-4 absolute text-gray-400 top-1/2 transform -translate-y-1/2 left-3"></i>
</div>
<!-- 移动端首屏控件 -->
<div class="lg:hidden flex items-center gap-x-2">
<div class="mobile-group-selector">
<div>
<h3 class="font-semibold text-sm">Loading...</h3>
<p class="card-sub-text">当前选择</p>
</div>
<button id="group-menu-toggle" class="text-zinc-500 dark:text-zinc-400">
<i class="fas fa-bars text-lg"></i>
</button>
</div>
<div id="add-group-btn-container-mobile" class="shrink-0">
<button class="add-group-btn add-group-btn-mobile flex items-center justify-center rounded-lg border-2 border-dashed transition-all duration-200">
<i class="fas fa-plus text-lg"></i>
</button>
</div>
</div>
<div id="group-list-collapsible" class="hidden lg:flex flex-col flex-grow overflow-y-auto pr-1">
<div id="desktop-group-cards-list" class="hidden lg:flex flex-col">
<div class="card-list-content space-y-2"><!-- JS通过 desktopGroupContainer 选择器填充这里 --></div>
<div id="add-group-btn-container" class="sticky pr-1 bottom-0 mt-2 mr-2 shrink-0 pt-2 bg-white dark:bg-zinc-800">
<button class="add-group-btn add-group-btn-desktop flex items-center justify-center rounded-lg border-2 border-dashed transition-all duration-200">
<i class="fas fa-plus text-lg"></i>
</button>
</div>
</div>
<!-- [移动端] 列表 -->
<div id="mobile-group-cards-list" class="block lg:hidden space-y-2"></div>
</div>
</aside>
{# 右侧主内容区: Group 详情与 API 管理 #}
<main class="w-full lg:w-3/4 flex flex-grow flex-col gap-y-4 overflow-y-auto lg:min-h-0">
<!-- Group Dashboard -->
<div id="group-dashboard" class="bg-zinc-100 dark:bg-zinc-900/50 border border-zinc-200 dark:border-zinc-700/60 rounded-lg p-4 shrink-0">
<!-- Header -->
<div class="flex justify-between items-center mb-4">
<h2 class="text-xl font-bold">默认分组 (Default)</h2>
<div class="flex items-center gap-x-3 text-zinc-400">
<button data-action="clone-group" class="hover:text-blue-500 transition-colors duration-200" title="克隆分组"><i class="fas fa-clone"></i></button>
<button data-action="edit-group" class="hover:text-blue-500 transition-colors duration-200" title="编辑分组"><i class="fas fa-cog"></i></button>
<button data-action="open-settings" class="hover:text-blue-500 transition-colors duration-200" title="高级请求设置"><i class="fas fa-sliders-h"></i></button>
<button data-action="delete-group" class="hover:text-red-500 transition-colors duration-200" title="删除分组"><i class="fas fa-trash"></i></button>
</div>
</div>
<!-- Stats -->
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4 text-center">
<!-- 密钥统计 -->
<div>
<p class="text-2xl font-semibold tracking-tight text-zinc-900 dark:text-zinc-100">7 / 10</p>
<p class="text-xs text-zinc-500 dark:text-zinc-400 mt-1">有效密钥 / 总密钥数</p>
<div class="mt-2 h-4"></div> <!-- 健康度图标占位 -->
</div>
<!-- 请求总览 -->
<div>
<p class="text-2xl font-semibold tracking-tight text-zinc-900 dark:text-zinc-100">0</p>
<p class="text-xs text-zinc-500 dark:text-zinc-400 mt-1">24小时请求数</p>
<div class="mt-2 h-4"></div> <!-- 健康度图标占位 -->
</div>
<!-- Token 消耗 -->
<div>
<p class="text-2xl font-semibold tracking-tight text-zinc-900 dark:text-zinc-100">0</p>
<p class="text-xs text-zinc-500 dark:text-zinc-400 mt-1">24小时消耗</p>
<div class="mt-2 h-4"></div> <!-- 健康度图标占位 -->
</div>
<!-- 请求成功率 -->
<div>
<p class="text-2xl font-semibold tracking-tight text-zinc-900 dark:text-zinc-100">0%</p>
<p class="text-xs text-zinc-500 dark:text-zinc-400 mt-1">24小时成功率</p>
<div class="mt-2 h-4"></div> <!-- 健康度图标占位 -->
</div>
</div>
</div>
<!-- API Management Area -->
<div class="bg-zinc-100 border border-zinc-200 dark:border-zinc-700/60 dark:bg-zinc-900/50 rounded-lg p-4 flex-grow flex flex-col lg:min-h-0">
<!-- Controls Header (已进行响应式重构 - V3 Final) -->
<div class="flex flex-col gap-y-3 mb-4">
<!--
============================================================
移动端第一行 / 桌面端完整操作行
============================================================
- 使用 justify-between 将左右两组推开。
-->
<div class="flex items-center justify-between">
<!-- 左侧主要操作 (始终显示) -->
<div class="flex items-center gap-x-2">
<button id="add-api-btn" class="px-3 py-1.5 text-sm bg-blue-600 text-white rounded-md hover:bg-blue-700 transition-colors">
<i class="fas fa-plus mr-1"></i> KEY
</button>
<button id="delete-api-btn" class="px-3 py-1.5 text-sm bg-red-600/80 text-white rounded-md hover:bg-red-700 transition-colors">
<i class="fas fa-minus mr-1"></i> KEY
</button>
</div>
<!-- 右侧操作组 -->
<div class="flex items-center gap-x-2">
<!-- 移动端: 快速处置 -->
<div class="lg:hidden items-center gap-x-2 relative inline-block custom-select" id="mobile-quick-actions-dropdown">
<button class="px-3 py-1.5 text-sm bg-zinc-200 dark:bg-zinc-700 rounded-md hover:bg-zinc-300 dark:hover:bg-zinc-600 transition-colors custom-select-trigger">
<i class="fas fa-bolt"></i>
</button>
<div class="dropdown-panel z-30 w-48 custom-select-panel hidden" id="mobile-quick-actions-panel">
<!-- Dropdown content will be injected by JavaScript -->
</div>
</div>
<!-- 桌面端操作组 (lg及以上屏幕显示) -->
<div class="hidden lg:flex items-center gap-x-2">
<div class="relative inline-block batch-action-dropdown custom-select">
<button class="px-3 py-1.5 text-sm bg-zinc-200 dark:bg-zinc-700 rounded-md hover:bg-zinc-300 dark:hover:bg-zinc-600 transition-colors flex items-center gap-x-2 batch-action-btn custom-select-trigger">
<span>批量操作</span>
<i class="fas fa-chevron-down text-xs text-zinc-400"></i>
</button>
<div class="dropdown-panel batch-action-panel custom-select-panel hidden">
<div class="py-1">
<button data-batch-action="copy-to-clipboard" class="menu-item">
<i class="fas fa-copy menu-item-icon menu-item-icon-neutral"></i>
<span>批量复制</span>
</button>
<button data-batch-action="set-status-active" class="menu-item">
<i class="fas fa-check-circle menu-item-icon text-green-500"></i>
<span>批量启用</span>
</button>
<button data-batch-action="set-status-disabled" class="menu-item">
<i class="fas fa-ban menu-item-icon text-yellow-500"></i>
<span>批量禁用</span>
</button>
<button data-batch-action="revalidate" class="menu-item">
<i class="fas fa-rocket menu-item-icon text-blue-500"></i>
<span>批量验证</span>
</button>
<!-- Use the new .menu-divider class -->
<div class="menu-divider"></div>
<!-- Use the base class and the danger modifier -->
<button data-batch-action="delete" class="menu-item menu-item-danger">
<i class="fas fa-trash-alt menu-item-icon"></i> <!-- Color is inherited from .menu-item-danger -->
<span>批量移除</span>
</button>
</div>
</div>
</div>
<!-- 桌面端: 快速处置 -->
<div class="relative inline-block custom-select" id="desktop-quick-actions-dropdown">
<button class="px-3 py-1.5 text-sm bg-zinc-200 dark:bg-zinc-700 rounded-md hover:bg-zinc-300 dark:hover:bg-zinc-600 transition-colors flex items-center gap-x-2 custom-select-trigger">
<i class="fas fa-bolt"></i>
<span>快速处置</span>
</button>
<div class="dropdown-panel z-30 w-48 custom-select-panel hidden" id="desktop-quick-actions-panel">
<!-- Dropdown content will be injected by JavaScript -->
</div>
</div>
<div class="relative inline-block text-left">
<!-- 1. The Trigger Button -->
<button data-toggle="custom-select"
data-target="#desktop-multifunction-panel"
class="px-3 py-1.5 text-sm bg-zinc-200 dark:bg-zinc-700 rounded-md hover:bg-zinc-300 dark:hover:bg-zinc-600 transition-colors">
<i class="fas fa-ellipsis-h"></i>
</button>
<!-- 2. The Dropdown Panel (initially hidden) -->
<div id="desktop-multifunction-panel"
class="custom-select-panel absolute right-0 z-30 mt-2 min-w-max origin-top-right rounded-md bg-white dark:bg-zinc-800 shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none hidden">
<!-- JS will inject menu items here -->
</div>
</div>
</div>
</div>
</div>
<!--
============================================================
移动端第二行 / 桌面端完整过滤行
============================================================
- display: lg:hidden 表示此行在桌面端将被隐藏,仅用于移动端布局。
-->
<div class="flex items-center justify-between lg:hidden">
<!-- 左侧:状态选择器 -->
<div class="custom-select relative status-filter-select">
<select class="hidden">
<option value="all">所有状态</option>
<option value="active">有效</option>
<option value="cooldown">冷却</option>
<option value="pending">待验证</option>
<option value="disabled">禁用</option>
<option value="banned">无效</option>
</select>
<div class="custom-select-trigger flex items-center justify-between w-32 cursor-pointer rounded-md border border-zinc-300 dark:border-zinc-600 bg-transparent px-3 py-1.5">
<span>所有状态</span>
<i class="fas fa-chevron-down text-xs text-zinc-400"></i>
</div>
<div class="custom-select-panel hidden absolute z-10 mt-1 w-full rounded-md border border-zinc-200 bg-white shadow-lg dark:border-zinc-600 dark:bg-zinc-700"></div>
</div>
<!-- 右侧:批量操作 和 多功能按钮 -->
<div class="flex items-center gap-x-2">
<div class="relative inline-block batch-action-dropdown custom-select">
<button class="px-3 py-1.5 text-sm bg-zinc-200 dark:bg-zinc-700 rounded-md hover:bg-zinc-300 dark:hover:bg-zinc-600 transition-colors flex items-center gap-x-2 batch-action-btn custom-select-trigger">
<span>批量操作</span>
<i class="fas fa-chevron-down text-xs text-zinc-400"></i>
</button>
<div class="dropdown-panel batch-action-panel custom-select-panel hidden">
<div class="py-1">
<button data-batch-action="copy-to-clipboard" class="menu-item">
<i class="fas fa-copy menu-item-icon menu-item-icon-neutral"></i>
<span>批量复制</span>
</button>
<button data-batch-action="set-status-active" class="menu-item">
<i class="fas fa-check-circle menu-item-icon text-green-500"></i>
<span>批量启用</span>
</button>
<button data-batch-action="set-status-disabled" class="menu-item">
<i class="fas fa-ban menu-item-icon text-yellow-500"></i>
<span>批量禁用</span>
</button>
<button data-batch-action="revalidate" class="menu-item">
<i class="fas fa-rocket menu-item-icon text-blue-500"></i>
<span>批量验证</span>
</button>
<!-- Use the new .menu-divider class -->
<div class="menu-divider"></div>
<!-- Use the base class and the danger modifier -->
<button data-batch-action="delete" class="menu-item menu-item-danger">
<i class="fas fa-trash-alt menu-item-icon"></i> <!-- Color is inherited from .menu-item-danger -->
<span>批量移除</span>
</button>
</div>
</div>
</div>
<div class="relative inline-block text-left">
<!-- 1. The Trigger Button -->
<button data-toggle="custom-select"
data-target="#desktop-multifunction-panel"
class="px-3 py-1.5 text-sm bg-zinc-200 dark:bg-zinc-700 rounded-md hover:bg-zinc-300 dark:hover:bg-zinc-600 transition-colors">
<i class="fas fa-ellipsis-h"></i>
</button>
<!-- 2. The Dropdown Panel (initially hidden) -->
<div id="mobile-multifunction-panel"
class="custom-select-panel absolute right-0 z-30 mt-2 min-w-max origin-top-right rounded-md bg-white dark:bg-zinc-800 shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none hidden">
<!-- JS will inject menu items here -->
</div>
</div>
</div>
</div>
<!--
============================================================
移动端第三行 / 合并入桌面端的第二行
============================================================
- 这个容器在两个断点都存在,但其内部元素的可见性不同。
-->
<div class="flex items-center justify-between text-sm">
<!-- 桌面端左侧: 状态选择器 -->
<div class="custom-select relative hidden lg:block status-filter-select">
<select class="hidden">
<option value="all">所有状态</option>
<option value="active">有效</option>
<option value="cooldown">冷却</option>
<option value="pending">待验证</option>
<option value="disabled">禁用</option>
<option value="banned">无效</option>
</select>
<div class="custom-select-trigger flex items-center justify-between w-32 cursor-pointer rounded-md border border-zinc-300 dark:border-zinc-600 bg-transparent px-3 py-1.5">
<span>所有状态</span>
<i class="fas fa-chevron-down text-xs text-zinc-400"></i>
</div>
<div class="custom-select-panel hidden absolute z-10 mt-1 w-full rounded-md border border-zinc-200 bg-white shadow-lg dark:border-zinc-600 dark:bg-zinc-700"></div>
</div>
<!-- 移动端左侧: 搜索图标 -->
<button id="mobile-search-btn" class="px-2 py-1.5 text-sm bg-transparent rounded-md hover:bg-zinc-200 dark:hover:bg-zinc-700 transition-colors lg:hidden">
<i class="fas fa-search text-zinc-500"></i>
</button>
<!-- 右侧组 (通用) -->
<div class="flex items-center gap-x-3">
<!-- 桌面端搜索框 -->
<div id="desktop-search-container" class="relative hidden lg:block w-full sm:w-48">
<i class="fas fa-search absolute left-3 top-1/2 -translate-y-1/2 text-zinc-500 dark:text-zinc-300"></i>
<input type="text" id="desktop-search-input" placeholder="模糊查找..." class="w-full pl-9 pr-3 py-1.5 text-sm text-zinc-500 dark:text-zinc-400 border border-zinc-300 dark:border-zinc-600 rounded-md bg-transparent focus:outline-none focus:ring-2 focus:ring-blue-500">
</div>
<!-- 通用分页与选择 -->
<div class="custom-select relative items-per-page-select"> <!-- [ADD CLASS] -->
<select class="hidden">
<option value="20" selected>20</option> <!-- [ADD VALUE & SELECTED] -->
<option value="50">50</option>
<option value="100">100</option>
</select>
<div class="custom-select-trigger flex items-center justify-between w-28 cursor-pointer rounded-md border border-zinc-300 dark:border-zinc-600 bg-transparent px-3 py-1.5">
<span>20 / 页</span>
<i class="fas fa-chevron-down text-xs text-zinc-400"></i>
</div>
<div class="custom-select-panel hidden absolute z-30 mt-1 w-full rounded-md border border-zinc-200 bg-white shadow-lg dark:border-zinc-600 dark:bg-zinc-700"></div>
</div>
<div class="flex items-center gap-x-2">
<input type="checkbox" id="select-all" class="h-4 w-4 rounded border-zinc-300 text-blue-600 focus:ring-blue-500">
<label for="select-all" class="text-zinc-600 dark:text-zinc-300">全选</label>
</div>
</div>
</div>
</div>
<!-- API List Container -->
<div id="api-list-container" class="flex-grow overflow-y-auto pr-2 -mr-2 main-content-scroll">
<!-- Content will be rendered here by JS -->
</div>
<!-- Pagination Controls -->
<div class="pagination-controls flex justify-center items-center mt-4 pt-4 border-t border-zinc-200 dark:border-zinc-700 space-x-2 lg:shrink-0"> <!-- [ADD CLASS] -->
<!-- Content will be rendered here by JS -->
</div>
</div>
</main>
</div>
</div>
{% endblock %}
{% block modals %}
<!-- Add/Edit Group Modal -->
<div id="keygroup-modal" class="modal-overlay hidden">
<div class="modal-panel max-w-3xl max-h-[90vh]">
<!-- Header -->
<div class="modal-header shrink-0">
<h2 id="modal-title" class="modal-title">创建新的 Key Group</h2>
<button data-modal-close="keygroup-modal" id="modal-close-btn" class="modal-close-btn">
<i class="fas fa-times text-lg"></i>
</button>
</div>
<!-- Form Body -->
<div class="modal-body flex-grow overflow-y-auto pr-4 -mr-4">
<div class="grid grid-cols-2 gap-x-6 gap-y-4">
<!-- Name -->
<div>
<label for="group-name" class="flex items-center modal-label">
<span>分组名称<span class="text-red-500">*</span></span>
<i class="fas fa-question-circle tooltip-icon" data-tooltip-text="分组的唯一程序内标识符,例如 'default-group'。创建后不可修改。"></i>
</label>
<input type="text" id="group-name" class="modal-input">
</div>
<!-- Display Name -->
<div>
<label for="group-display-name" class="flex items-center modal-label">
<span>显示名称<span class="text-red-500">*</span></span>
<i class="fas fa-question-circle tooltip-icon" data-tooltip-text="显示在UI上的名称方便识别例如'默认分组'。"></i>
</label>
<input type="text" id="group-display-name" class="modal-input">
</div>
<!-- Description -->
<div class="col-span-2">
<label for="group-description" class="flex items-center modal-label">
<span>描述</span>
<i class="fas fa-question-circle tooltip-icon" data-tooltip-text="关于此分组用途的简短说明。"></i>
</label>
<textarea id="group-description" rows="2" class="modal-input"></textarea>
</div>
<!-- Allowed Models -->
<div class="col-span-2">
<label class="flex items-center modal-label">
<span>允许模型</span>
<i class="fas fa-question-circle tooltip-icon" data-tooltip-text="允许该分组使用的模型ID列表。留空表示允许所有模型。"></i>
</label>
<div id="allowed-models-container" class="tag-input-container">
<span class="tag-item">gemini-pro<button class="tag-delete">&times;</button></span>
<span class="tag-item">gemini-1.5-pro-latest<button class="tag-delete">&times;</button></span>
<input type="text" placeholder="添加模型..." class="tag-input-new">
</div>
</div>
<!-- Allowed Tokens -->
<div class="col-span-2">
<label class="flex items-center modal-label">
<span>专属密钥</span>
<i class="fas fa-question-circle tooltip-icon" data-tooltip-text="只有列表中的认证令牌 (Auth Tokens) 才能使用此分组。留空则所有密钥都可用。"></i>
</label>
<div id="allowed-tokens-container" class="tag-input-container">
<input type="text" placeholder="添加专属密钥..." class="tag-input-new">
</div>
</div>
<!-- Polling Strategy -->
<div>
<label for="group-strategy" class="flex items-center modal-label">
<span>轮询模式</span>
<i class="fas fa-question-circle tooltip-icon" data-tooltip-text="从此分组中选择可用API Key的策略。"></i>
</label>
<select id="group-strategy" class="modal-input">
<option value="random">随机</option>
<option value="sequential">顺序</option>
<option value="weighted">加权</option>
</select>
</div>
<!-- Channel Type -->
<div>
<label for="group-channel-type" class="flex items-center modal-label">
<span>渠道类型</span>
<i class="fas fa-question-circle tooltip-icon" data-tooltip-text="此分组关联的上游渠道类型,由系统确定。"></i>
</label>
<input type="text" id="group-channel-type" class="modal-input bg-zinc-100 dark:bg-zinc-700/50" value="Gemini" disabled>
</div>
<!-- Max Retries (新增) -->
<div>
<label for="group-max-retries" class="flex items-center modal-label">
<span>重试次数</span>
<i class="fas fa-question-circle tooltip-icon" data-tooltip-text="单个请求使用不同Key的最大重试次数。"></i>
</label>
<input type="number" id="group-max-retries" class="modal-input" placeholder="默认: 3">
</div>
<!-- Failure Threshold-->
<div>
<label for="group-key-blacklist-threshold" class="flex items-center modal-label">
<span>失败阈值</span>
<i class="fas fa-question-circle tooltip-icon" data-tooltip-text="连续失败多少次后将密钥列入冷却状态。"></i>
</label>
<input type="number" id="group-key-blacklist-threshold" class="modal-input" placeholder="默认: 3">
</div>
<!-- Enable Proxy -->
<div class="flex items-center">
<label for="group-enable-proxy" class="modal-label flex-grow flex items-center">
<span>启用代理</span>
<i class="fas fa-question-circle tooltip-icon" data-tooltip-text="是否为此分组的请求启用系统配置的代理。"></i>
</label>
<div class="relative inline-block w-10 mr-2 align-middle select-none transition duration-200 ease-in">
<input type="checkbox" name="group-enable-proxy" id="group-enable-proxy" class="toggle-checkbox absolute block w-6 h-6 rounded-full bg-white border-4 appearance-none cursor-pointer">
<label for="group-enable-proxy" class="toggle-label block overflow-hidden h-6 rounded-full bg-gray-300 cursor-pointer"></label>
</div>
</div>
<!-- Enable Smart Gateway -->
<div class="flex items-center">
<label for="group-enable-smart-gateway" class="modal-label flex-grow flex items-center">
<span>启用智能网关</span>
<i class="fas fa-question-circle tooltip-icon" data-tooltip-text="是否启用智能网关功能,自动处理模型映射和重试。"></i>
</label>
<div class="relative inline-block w-10 mr-2 align-middle select-none transition duration-200 ease-in">
<input type="checkbox" name="group-enable-smart-gateway" id="group-enable-smart-gateway" class="toggle-checkbox absolute block w-6 h-6 rounded-full bg-white border-4 appearance-none cursor-pointer">
<label for="group-enable-smart-gateway" class="toggle-label block overflow-hidden h-6 rounded-full bg-gray-300 cursor-pointer"></label>
</div>
</div>
<!-- Allowed Upstreams -->
<div class="col-span-2">
<label class="flex items-center modal-label">
<span>上游地址</span>
<i class="fas fa-question-circle tooltip-icon" data-tooltip-text="指定可用的上游API地址。如果留空将使用系统默认地址。"></i>
</label>
<div id="allowed-upstreams-container" class="tag-input-container">
<input type="text" placeholder="添加上游地址..." class="tag-input-new">
</div>
</div>
<!-- Max Retries (Moved to Advanced Request Settings) -->
<!-- API Key Auto Validation -->
<div class="col-span-2 mt-2">
<div class="flex items-center justify-between">
<label for="group-enable-key-check" class="modal-label flex-grow flex items-center font-semibold">
<span>开启自动验证</span>
<i class="fas fa-question-circle tooltip-icon" data-tooltip-text="启用后,系统将定期自动验证该分组下所有密钥的有效性。"></i>
</label>
<div class="relative inline-block w-10 mr-2 align-middle select-none transition duration-200 ease-in">
<input type="checkbox" name="group-enable-key-check" id="group-enable-key-check" class="toggle-checkbox absolute block w-6 h-6 rounded-full bg-white border-4 appearance-none cursor-pointer" checked>
<label for="group-enable-key-check" class="toggle-label block overflow-hidden h-6 rounded-full bg-gray-300 cursor-pointer"></label>
</div>
</div>
<!-- Collapsible settings for key check -->
<div id="key-check-settings" class="grid grid-cols-2 gap-x-6 gap-y-4 mt-4 ">
<!-- Detection Model (从全宽收缩为半宽,并移到最前) -->
<div>
<label for="group-key-check-model" class="flex items-center modal-label">
<span>检测模型</span>
<i class="fas fa-question-circle tooltip-icon" data-tooltip-text="用于验证此分组下API Key有效性的模型ID。"></i>
</label>
<input type="text" id="group-key-check-model" class="modal-input" placeholder="默认: gemini-1.5-flash">
</div>
<!-- Detection Interval -->
<div>
<label for="group-key-check-interval-minutes" class="flex items-center modal-label">
<span>检测周期 (分钟)</span>
<i class="fas fa-question-circle tooltip-icon" data-tooltip-text="每隔多少分钟对分组内的密钥进行一次健康检查。"></i>
</label>
<input type="number" id="group-key-check-interval-minutes" class="modal-input" placeholder="默认: 60">
</div>
<!-- Detection Concurrency -->
<div>
<label for="group-key-check-concurrency" class="flex items-center modal-label">
<span>检测并发数</span>
<i class="fas fa-question-circle tooltip-icon" data-tooltip-text="同时执行密钥验证的最大并发数。"></i>
</label>
<input type="number" id="group-key-check-concurrency" class="modal-input" placeholder="默认: 5">
</div>
<!-- Cooldown Duration -->
<div>
<label for="group-key-cooldown-minutes" class="flex items-center modal-label">
<span>冷却时长 (分钟)</span>
<i class="fas fa-question-circle tooltip-icon" data-tooltip-text="密钥进入冷却状态后,需要等待多长时间才能被再次检查。"></i>
</label>
<input type="number" id="group-key-cooldown-minutes" class="modal-input" placeholder="默认: 10">
</div>
<!-- Detection Endpoint (保持全宽) -->
<div class="col-span-2">
<label for="group-key-check-endpoint" class="flex items-center modal-label">
<span>检测端点</span>
<i class="fas fa-question-circle tooltip-icon" data-tooltip-text="用于密钥验证的上游API地址。如果留空将使用系统默认地址。"></i>
</label>
<input type="text" id="group-key-check-endpoint" class="modal-input" placeholder="留空以使用全局默认">
</div>
</div>
</div>
</div>
</div>
<!-- Footer -->
<div class="modal-footer shrink-0">
<button data-modal-close="keygroup-modal" id="modal-cancel-btn" class="modal-btn modal-btn-secondary">取消</button>
<button id="modal-save-btn" class="modal-btn modal-btn-primary">保存</button>
</div>
</div>
</div>
<!-- Add API Modal -->
<div id="add-api-modal" class="modal-overlay hidden">
<div class="modal-panel">
<!-- Header -->
<div class="modal-header">
<h2 id="add-api-modal-title" class="modal-title">批量添加 API Keys</h2>
<button data-modal-close="add-api-modal" class="modal-close-btn">
<i class="fas fa-times text-lg"></i>
</button>
</div>
<!-- Body 现在包含两个可切换的视图 -->
<div class="modal-body">
<!-- 视图 1: 输入视图 (默认显示) -->
<div id="add-api-input-view">
<label for="api-add-textarea" class="modal-label">API Keys (每行一个)</label>
<textarea id="api-add-textarea" rows="15" class="modal-input mt-1 font-mono text-xs"
placeholder="sk-abc...&#10;AIza...&#10;gsk_..."></textarea>
</div>
<!-- 视图 2: 结果/进度视图 (默认隐藏) -->
<div id="add-api-result-view" class="hidden">
<!-- 这里的内容将由JS动态填充 -->
</div>
</div>
<!-- Footer -->
<div id="add-api-modal-footer" class="modal-footer justify-between">
<!-- 左側:新增的驗證開關 -->
<div class="flex items-center space-x-4">
<input type="checkbox" id="validate-on-import-checkbox" class="form-checkbox h-5 w-5 ml-2 text-blue-600 focus:ring-blue-500 border-gray-300" checked>
<label for="validate-on-import-checkbox" class="text-sm font-medium text-zinc-700 dark:text-zinc-300 select-none"> 同步验证</label>
<i class="fas fa-question-circle text-gray-400 tooltip-icon"
data-tooltip-text="推荐开启。密钥导入后同步调用测试模型对API进行有效性验证无效Key将被隔离。"></i>
</div>
<!-- 右側:現有的按鈕組 -->
<div class="flex items-center space-x-2">
<button data-modal-close="add-api-modal" class="modal-btn modal-btn-secondary">取消</button>
<button id="add-api-import-btn" class="modal-btn modal-btn-primary">導入</button>
</div>
</div>
</div>
</div>
<!-- Delete API Modal -->
<div id="delete-api-modal" class="modal-overlay hidden">
<div class="modal-panel">
<!-- Header -->
<div class="modal-header">
<h2 class="modal-title">批量删除 API Keys</h2>
<button data-modal-close="delete-api-modal" class="modal-close-btn">
<i class="fas fa-times text-lg"></i>
</button>
</div>
<!-- Body -->
<div class="modal-body">
<label for="api-delete-textarea" class="text-sm font-medium text-zinc-700 dark:text-zinc-300">API Keys (每行一个)</label>
<textarea id="api-delete-textarea" rows="15" class="modal-input mt-1 font-mono text-xs" placeholder="sk-abc...&#10;AIza...&#10;gsk_..."></textarea>
</div>
<!-- Footer -->
<div class="modal-footer">
<button data-modal-close="delete-api-modal" class="modal-btn modal-btn-secondary">取消</button>
<button class="modal-btn modal-btn-danger">删除</button>
</div>
</div>
</div>
<!-- Delete Group Confirmation Modal -->
<div id="delete-group-modal" class="modal-overlay hidden">
<div class="modal-panel">
<!-- Header -->
<div class="modal-header">
<h2 id="delete-group-modal-title" class="modal-title">确认删除分组</h2>
<button data-modal-close="delete-group-modal" class="modal-close-btn">
<i class="fas fa-times text-lg"></i>
</button>
</div>
<!-- Body -->
<div class="modal-body space-y-4">
<p class="text-sm text-zinc-600 dark:text-zinc-400">
这是一个危险且不可逆的操作。请仔细阅读以下警告:
</p>
<ul class="list-disc list-inside space-y-2 text-sm bg-yellow-100 dark:bg-yellow-900/30 p-3 rounded-lg border border-yellow-300 dark:border-yellow-700">
<li>此操作将解除所有 API Key 与该分组的关联。</li>
<li class="font-semibold">未与其他分组关联的 Key 将被 <strong class="text-red-500">彻底从数据库中删除</strong></li>
<li>该操作无法撤销。</li>
</ul>
<div>
<label for="delete-group-confirm-input" class="text-sm font-medium text-zinc-700 dark:text-zinc-300">
请输入 <code class="text-red-500 bg-zinc-200 dark:bg-zinc-700 px-1 py-0.5 rounded">删除</code> 来确认操作:
</label>
<input type="text" id="delete-group-confirm-input" class="modal-input mt-1 font-mono" autocomplete="off">
</div>
</div>
<!-- Footer -->
<div class="modal-footer">
<button data-modal-close="delete-group-modal" class="modal-btn modal-btn-secondary">取消</button>
<button id="delete-group-confirm-btn" class="modal-btn modal-btn-danger" disabled>确认删除</button>
</div>
</div>
</div>
<!-- Clone Group Confirmation Modal -->
<div id="clone-group-modal" class="modal-overlay hidden">
<div class="modal-panel">
<!-- Header -->
<div class="modal-header">
<h2 id="clone-group-modal-title" class="modal-title">确认克隆分组</h2>
<button data-modal-close="clone-group-modal" class="modal-close-btn">
<i class="fas fa-times text-lg"></i>
</button>
</div>
<!-- Body -->
<div class="modal-body space-y-4">
<p class="text-sm text-zinc-600 dark:text-zinc-400">
此操作将创建一个当前分组的完整副本,包括:
</p>
<ul class="list-disc list-inside space-y-2 text-sm bg-blue-100 dark:bg-blue-900/30 p-3 rounded-lg border border-blue-300 dark:border-blue-700">
<li>所有相同的 API Key 关联关系。</li>
<li>所有允许的模型和上游设置。</li>
<li>所有请求配置和运营设置。</li>
</ul>
<p class="text-sm text-zinc-600 dark:text-zinc-400">
新分组的名称将被自动命名为 <code class="bg-zinc-200 dark:bg-zinc-700 px-1 py-0.5 rounded">[原名称]-clone-[时间戳]</code>,您可以在之后对其进行编辑。
</p>
</div>
<!-- Footer -->
<div class="modal-footer">
<button data-modal-close="clone-group-modal" class="modal-btn modal-btn-secondary">取消</button>
<button id="clone-group-confirm-btn" class="modal-btn modal-btn-primary">确认克隆</button>
</div>
</div>
</div>
<!-- ========================================================================== -->
<!-- NEW: Advanced Request Settings Modal -->
<!-- ========================================================================== -->
<div id="request-settings-modal" class="modal-overlay hidden">
<div class="modal-panel max-w-3xl max-h-[90vh]">
<!-- Header -->
<div class="modal-header shrink-0">
<h2 class="modal-title">高级请求设置</h2>
<button data-modal-close="request-settings-modal" class="modal-close-btn">
<i class="fas fa-times text-lg"></i>
</button>
</div>
<!-- Form Body -->
<div class="modal-body flex-grow overflow-y-auto pr-4 -mr-4 space-y-8"> <!-- 增加了 space-y-8 来拉开分区距离 -->
<!-- 1. Custom Headers -->
<div>
<label class="flex items-center modal-label text-lg font-semibold">
<i class="fas fa-file-alt w-6 text-center text-zinc-400 mr-2"></i> <!-- ICON -->
<span>自定义请求头 (Custom Headers)</span>
<i class="fas fa-question-circle tooltip-icon" data-tooltip-text="在这里添加的键值对将被添加到所有出站API请求的Header中。"></i>
</label>
<div id="CUSTOM_HEADERS_container" class="mt-2 space-y-2">
<!-- JS will populate this -->
</div>
<button id="addCustomHeaderBtn" type="button" class="mt-2 text-sm text-blue-600 hover:text-blue-800 dark:text-blue-400 dark:hover:text-blue-300 font-medium">
<i class="fas fa-plus mr-1"></i> 添加 Header
</button>
</div>
<!-- 2. Streaming Optimization (折叠优化) -->
<div class="border-t dark:border-zinc-700 pt-6"> <!-- 分隔线 -->
<div class="flex items-center justify-between">
<label class="flex items-center modal-label text-lg font-semibold">
<i class="fas fa-stream w-6 text-center text-zinc-400 mr-2"></i> <!-- ICON -->
<span>流式输出优化</span>
<i class="fas fa-question-circle tooltip-icon" data-tooltip-text="启用后,可对流式输出进行延迟、分块等优化,改善用户体验。"></i>
</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>
<!-- [折叠容器] 这个容器将由JS控制显示/隐藏 -->
<div id="streaming-settings-panel" class="hidden mt-4">
<div class="grid grid-cols-2 gap-x-6 gap-y-4">
<div>
<label for="STREAM_MIN_DELAY" class="flex items-center modal-label"><span>最小延迟 (ms)</span></label>
<input type="number" id="STREAM_MIN_DELAY" class="modal-input" placeholder="例如: 16">
</div>
<div>
<label for="STREAM_MAX_DELAY" class="flex items-center modal-label"><span>最大延迟 (ms)</span></label>
<input type="number" id="STREAM_MAX_DELAY" class="modal-input" placeholder="例如: 24">
</div>
<div>
<label for="STREAM_SHORT_TEXT_THRESHOLD" class="flex items-center modal-label"><span>短文本阈值</span></label>
<input type="number" id="STREAM_SHORT_TEXT_THRESHOLD" class="modal-input" placeholder="例如: 10">
</div>
<div>
<label for="STREAM_LONG_TEXT_THRESHOLD" class="flex items-center modal-label"><span>长文本阈值</span></label>
<input type="number" id="STREAM_LONG_TEXT_THRESHOLD" class="modal-input" placeholder="例如: 50">
</div>
<div class="col-span-2">
<label for="STREAM_CHUNK_SIZE" class="flex items-center modal-label"><span>分块大小</span></label>
<input type="number" id="STREAM_CHUNK_SIZE" class="modal-input" placeholder="例如: 5">
</div>
</div>
<!-- [假流式输出同行] -->
<div class="border-t dark:border-zinc-700 pt-4 mt-4 flex items-center justify-between gap-x-6">
<div class="flex items-center flex-grow translate-y-3">
<label for="FAKE_STREAM_ENABLED" class="modal-label flex items-center">
<span>启用假流式输出</span>
<i class="fas fa-question-circle tooltip-icon" data-tooltip-text="当启用时,将调用非流式接口,并在等待响应期间发送空数据以维持连接。"></i>
</label>
<div class="relative inline-block w-10 ml-auto mr-2 align-middle select-none">
<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>
<div class="flex-grow">
<label for="FAKE_STREAM_EMPTY_DATA_INTERVAL_SECONDS" class="flex items-center modal-label"><span>发送间隔 (s)</span></label>
<input type="number" id="FAKE_STREAM_EMPTY_DATA_INTERVAL_SECONDS" class="modal-input" placeholder="例如: 5">
</div>
</div>
</div>
</div>
<!-- 3. Safety Settings -->
<div class="border-t dark:border-zinc-700 pt-6"> <!-- 分隔线 -->
<label class="flex items-center modal-label text-lg font-semibold">
<i class="fas fa-shield-alt w-6 text-center text-zinc-400 mr-2"></i> <!-- ICON -->
<span>安全设置 (Safety Settings)</span>
<i class="fas fa-question-circle tooltip-icon" data-tooltip-text="配置模型的安全过滤级别,例如 HARM_CATEGORY_HARASSMENT: BLOCK_NONE。"></i>
</label>
<div id="SAFETY_SETTINGS_container" class="mt-2 space-y-2">
<!-- JS will populate this -->
</div>
<button id="addSafetySettingBtn" type="button" class="mt-2 text-sm text-blue-600 hover:text-blue-800 dark:text-blue-400 dark:hover:text-blue-300 font-medium">
<i class="fas fa-plus mr-1"></i> 添加安全设置
</button>
</div>
<!-- 4. Advanced Model Settings (保持折叠) -->
<div class="border-t dark:border-zinc-700 pt-6"> <!-- 分隔线 -->
<details>
<summary class="cursor-pointer text-lg font-semibold text-zinc-700 dark:text-zinc-300 flex items-center">
<i class="fas fa-cogs w-6 text-center text-zinc-400 mr-2"></i> <!-- ICON -->
<span>高级模型设置</span>
<i class="fas fa-question-circle tooltip-icon" data-tooltip-text="配置模型的高级设置,自定义图像模型/搜索模型/思考设置等,非熟知不建议修改。"></i>
<i class="fas fa-chevron-down text-sm ml-auto transition-transform"></i>
</summary>
<div class="mt-4 space-y-6">
<!-- Image Models -->
<div>
<label class="flex items-center modal-label">
<span>图像模型 (Image Models)</span>
<i class="fas fa-question-circle tooltip-icon" data-tooltip-text="支持图像处理的模型列表。"></i>
</label>
<div id="IMAGE_MODELS_container" class="tag-input-container">
<input type="text" placeholder="添加模型..." class="tag-input-new">
</div>
</div>
<!-- Search Models -->
<div>
<label class="flex items-center modal-label">
<span>搜索模型 (Search Models)</span>
<i class="fas fa-question-circle tooltip-icon" data-tooltip-text="支持搜索功能的模型列表。"></i>
</label>
<div id="SEARCH_MODELS_container" class="tag-input-container">
<input type="text" placeholder="添加模型..." class="tag-input-new">
</div>
</div>
<!-- Filtered Models -->
<div>
<label class="flex items-center modal-label">
<span>过滤模型 (Filtered Models)</span>
<i class="fas fa-question-circle tooltip-icon" data-tooltip-text="需要从模型列表中排除的模型。"></i>
</label>
<div id="FILTERED_MODELS_container" class="tag-input-container">
<input type="text" placeholder="添加模型..." class="tag-input-new">
</div>
</div>
<!-- Enable Code Execution -->
<div class="flex items-center">
<label for="TOOLS_CODE_EXECUTION_ENABLED" class="modal-label flex-grow flex items-center">
<span>启用代码执行工具</span>
<i class="fas fa-question-circle tooltip-icon" data-tooltip-text="是否为模型启用代码执行工具。"></i>
</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>
<!-- Enable URL Context -->
<div class="flex items-center">
<label for="URL_CONTEXT_ENABLED" class="modal-label flex-grow flex items-center">
<span>启用网址上下文</span>
<i class="fas fa-question-circle tooltip-icon" data-tooltip-text="是否启用网址上下文功能。"></i>
</label>
<div class="relative inline-block w-10 mr-2 align-middle select-none transition duration-200 ease-in">
<input type="checkbox" name="URL_CONTEXT_ENABLED" id="URL_CONTEXT_ENABLED" class="toggle-checkbox absolute block w-6 h-6 rounded-full bg-white border-4 appearance-none cursor-pointer">
<label for="URL_CONTEXT_ENABLED" class="toggle-label block overflow-hidden h-6 rounded-full bg-gray-300 cursor-pointer"></label>
</div>
</div>
<!-- URL Context Models -->
<div>
<label class="flex items-center modal-label">
<span>网址上下文模型</span>
<i class="fas fa-question-circle tooltip-icon" data-tooltip-text="支持网址上下文功能的模型列表。"></i>
</label>
<div id="URL_CONTEXT_MODELS_container" class="tag-input-container">
<input type="text" placeholder="添加模型..." class="tag-input-new">
</div>
</div>
<!-- Show Search Link -->
<div class="flex items-center">
<label for="SHOW_SEARCH_LINK" class="modal-label flex-grow flex items-center">
<span>显示搜索链接</span>
</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>
<!-- Show Thinking Process -->
<div class="flex items-center">
<label for="SHOW_THINKING_PROCESS" class="modal-label flex-grow flex items-center">
<span>显示思考过程</span>
</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>
<!-- Thinking Models -->
<div>
<label class="flex items-center modal-label">
<span>思考模型 (Thinking Models)</span>
<i class="fas fa-question-circle tooltip-icon" data-tooltip-text="用于“思考过程”的模型列表。"></i>
</label>
<div id="THINKING_MODELS_container" class="tag-input-container">
<input type="text" placeholder="添加模型..." class="tag-input-new">
</div>
</div>
<!-- Thinking Budget Map -->
<div>
<label class="flex items-center modal-label">
<span>思考模型预算映射</span>
<i class="fas fa-question-circle tooltip-icon" data-tooltip-text="为每个思考模型设置预算(-1为auto此项与上方模型列表自动关联。"></i>
</label>
<div id="THINKING_BUDGET_MAP_container" class="mt-2 space-y-2">
<!-- JS will populate this -->
</div>
</div>
</div>
</details>
</div>
<!-- 5. Config Overrides (折叠优化) -->
<div class="border-t dark:border-zinc-700 pt-6"> <!-- 分隔线 -->
<details>
<summary class="cursor-pointer text-lg font-semibold text-zinc-700 dark:text-zinc-300 flex items-center">
<i class="fas fa-code w-6 text-center text-zinc-400 mr-2"></i> <!-- ICON -->
<span>覆盖参数 (JSON)</span>
<i class="fas fa-question-circle tooltip-icon" data-tooltip-text="使用JSON格式覆盖默认的请求参数。"></i>
<i class="fas fa-chevron-down text-sm ml-auto transition-transform"></i>
</summary>
<!-- [修复] 将 placeholder 内容放在一行,避免被截断 -->
<textarea id="group-config-overrides" rows="6" class="mt-2 modal-input font-mono text-xs" placeholder='{
"temperature": 0.8
}'></textarea>
</details>
</div>
</div>
<!-- Footer -->
<div class="modal-footer shrink-0">
<button data-modal-close="request-settings-modal" class="modal-btn modal-btn-secondary">取消</button>
<button id="request-settings-save-btn" class="modal-btn modal-btn-primary">保存</button>
</div>
</div>
</div>
{% endblock modals %}
{% block page_scripts %}
{% endblock page_scripts %}