ccpoad / web /channels.html
anyalerob's picture
Upload folder using huggingface_hub
2986042 verified
Raw
History Blame Contribute Delete
83.5 kB
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" type="image/x-icon" href="/web/favicon.ico">
<link rel="apple-touch-icon" href="/web/apple-touch-icon.png">
<link rel="manifest" href="/web/manifest.json">
<meta name="theme-color" content="#3b82f6">
<title data-i18n="channels.title">渠道管理 - Claude Code & Codex Proxy</title>
<link rel="stylesheet" href="/web/assets/css/styles.css?v=__VERSION__">
<link rel="stylesheet" href="/web/assets/css/channels.css?v=__VERSION__">
<script defer src="/web/assets/locales/zh-CN.js?v=__VERSION__"></script>
<script defer src="/web/assets/locales/en.js?v=__VERSION__"></script>
<script defer src="/web/assets/js/i18n.js?v=__VERSION__"></script>
<script defer src="/web/assets/js/ui.js?v=__VERSION__"></script>
<script defer src="/web/assets/js/template-engine.js?v=__VERSION__"></script>
<script defer src="/web/assets/js/date-range-selector.js?v=__VERSION__"></script>
<script defer src="/web/assets/js/channels-state.js?v=__VERSION__"></script>
<script defer src="/web/assets/js/channels-protocols.js?v=__VERSION__"></script>
<script defer src="/web/assets/js/channels-render.js?v=__VERSION__"></script>
<script defer src="/web/assets/js/channels-filters.js?v=__VERSION__"></script>
<script defer src="/web/assets/js/channels-data.js?v=__VERSION__"></script>
<script defer src="/web/assets/js/channels-import-export.js?v=__VERSION__"></script>
<script defer src="/web/assets/js/channels-keys.js?v=__VERSION__"></script>
<script defer src="/web/assets/js/channels-urls.js?v=__VERSION__"></script>
<script defer src="/web/assets/js/channels-modals.js?v=__VERSION__"></script>
<script defer src="/web/assets/js/channels-custom-rules.js?v=__VERSION__"></script>
<script defer src="/web/assets/js/channels-test.js?v=__VERSION__"></script>
<script defer src="/web/assets/js/channels-sort.js?v=__VERSION__"></script>
<script defer src="/web/assets/js/channels-init.js?v=__VERSION__"></script>
</head>
<body>
<div class="app-container">
<main class="main-content">
<div class="content-area">
<header class="mt-2 mb-2">
<div class="glass-card" style="padding: var(--space-6);">
<div class="channel-page-hero">
<p class="page-subtitle" style="margin: 0;" data-i18n="channels.description">
配置API渠道、优先级和模型支持(<b>优先请求高优先级渠道,相同优先级按Key数量加权随机</b></p>
<div class="channel-page-actions">
<button id="exportCsvBtn" type="button" class="btn btn-secondary channel-page-action-btn"
data-i18n-title="channels.exportCsvTitle"
title="导出渠道列表为CSV">
<span data-i18n="channels.exportCsv">导出 CSV</span>
</button>
<button id="importCsvBtn" type="button" class="btn btn-secondary channel-page-action-btn"
data-i18n-title="channels.importCsvTitle"
title="从CSV导入渠道">
<span data-i18n="channels.importCsv">导入 CSV</span>
</button>
<button type="button" data-action="show-add-modal" class="btn btn-primary channel-page-action-btn"
data-i18n-title="channels.addChannelTitle" title="添加新渠道">
<span data-i18n="channels.addChannel">+ 添加渠道</span>
</button>
<input type="file" id="importCsvInput" accept=".csv" style="display: none;" />
</div>
</div>
</div>
</header>
<!-- Filter Bar -->
<div class="filter-bar">
<div class="filter-controls channels-filter-controls">
<!-- 渠道类型筛选 -->
<div class="filter-group">
<label class="filter-label" data-i18n="channels.filterChannelType">渠道类型</label>
<select id="channelTypeFilter" class="filter-select filter-control--compact">
<!-- 动态加载渠道类型选项 -->
</select>
</div>
<div class="filter-group">
<label class="filter-label" data-i18n="channels.filterStatus">状态</label>
<select id="statusFilter" class="filter-select filter-control--compact">
<option value="all" data-i18n="channels.statusAll">所有状态</option>
<option value="enabled" data-i18n="channels.statusEnabled">已启用</option>
<option value="disabled" data-i18n="channels.statusDisabled">已禁用</option>
<option value="cooldown" data-i18n="channels.statusCooldown">冷却中</option>
</select>
</div>
<div class="filter-group channel-model-filter-group">
<label class="filter-label" data-i18n="channels.filterModel">模型</label>
<div class="filter-combobox-wrapper filter-control--compact">
<input id="modelFilter" class="filter-select filter-combobox" type="text" autocomplete="off"
spellcheck="false" />
<div id="modelFilterDropdown" class="filter-dropdown" role="listbox" aria-label="模型"></div>
</div>
</div>
<div class="filter-group channel-search-filter-group">
<label class="filter-label" data-i18n="channels.filterSearch">渠道名称</label>
<div class="channel-search-control">
<div class="filter-combobox-wrapper filter-control--compact">
<input id="searchInput" class="filter-select filter-combobox" type="text" autocomplete="off"
spellcheck="false" />
<div id="searchInputDropdown" class="filter-dropdown" role="listbox" aria-label="渠道名称"></div>
</div>
</div>
</div>
<div class="channel-filter-summary">
<div class="filter-info" id="filterInfo"><span id="filteredCount">0</span> / <span id="totalCount">0</span>
<span data-i18n="channels.channelCount">个渠道</span>
</div>
<div class="channel-toolbar-actions">
<button id="clearSearchBtn" type="button" class="btn btn-secondary btn-sm"
data-i18n="common.clear" data-i18n-title="common.clearSearch" title="清空搜索">
清空
</button>
<button id="btn_sort" type="button" class="btn btn-secondary channel-filter-action-btn"
data-i18n="channels.sortBtn">排序</button>
<button id="btn_filter" type="button" class="btn btn-primary channel-filter-action-btn"
data-i18n="channels.filterBtn">筛选</button>
</div>
</div>
</div>
</div>
<section>
<div id="channels-container" class="grid grid-cols-1 gap-6"></div>
</section>
<div class="glass-card logs-pagination-card">
<div class="pagination-container">
<div class="pagination-controls logs-pagination-controls">
<button id="channels_first_page" class="btn btn-secondary btn-sm" type="button" data-action="first-channels-page">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M18 17l-5-5 5-5"/>
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18V6"/>
</svg>
<span data-i18n="common.firstPage">首页</span>
</button>
<button id="channels_prev_page" class="btn btn-secondary btn-sm" type="button" data-action="prev-channels-page">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 18l-6-6 6-6"/>
</svg>
<span data-i18n="common.prevPage">上一页</span>
</button>
<span class="pagination-info logs-pagination-info">
<span data-i18n="logs.pagePrefix"></span> <span id="channels_current_page">1</span> <span data-i18n="logs.pageMid">页,共</span> <span id="channels_total_pages">1</span> <span data-i18n="logs.pageSuffix"></span>
<span class="logs-pagination-separator">|</span>
<span data-i18n="logs.jumpTo">跳转到</span>
<input
id="channels_jump_page"
class="logs-jump-input"
type="number"
min="1"
data-i18n-placeholder="logs.pagePlaceholder"
placeholder="页码">
<span data-i18n="logs.pageUnit"></span>
</span>
<button id="channels_next_page" class="btn btn-secondary btn-sm" type="button" data-action="next-channels-page">
<span data-i18n="common.nextPage">下一页</span>
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 6l6 6-6 6"/>
</svg>
</button>
<button id="channels_last_page" class="btn btn-secondary btn-sm" type="button" data-action="last-channels-page">
<span data-i18n="common.lastPage">尾页</span>
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 7l5 5-5 5"/>
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M18 6v12"/>
</svg>
</button>
<span class="logs-pagination-separator">|</span>
<span class="pagination-page-size-control">
<span data-i18n="channels.pageSize">每页</span>
<select id="channels_page_size" class="logs-jump-input">
<option value="10">10</option>
<option value="20" selected>20</option>
<option value="50">50</option>
<option value="100">100</option>
</select>
<span data-i18n="channels.pageSizeUnit"></span>
</span>
</div>
</div>
</div>
<div id="batchFloatingMenu" class="channel-batch-float" aria-hidden="true">
<div class="channel-batch-float__content">
<div class="channel-batch-float__header">
<div class="channel-batch-selection">
<span id="selectedChannelsCountBadge" class="channel-batch-count-badge">0</span>
<div class="channel-batch-selection-meta">
<span id="selectedChannelsSummary" class="channel-batch-summary">渠道已选择</span>
</div>
</div>
<span class="channel-batch-divider" aria-hidden="true"></span>
</div>
<div class="channel-batch-actions">
<button id="batchEnableChannelsBtn" type="button"
class="btn btn-sm channel-batch-action channel-batch-action--enable"
data-action="batch-enable-channels" disabled>
<span class="channel-batch-action__icon" aria-hidden="true">
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M4.5 10.3L8.15 13.95L15.35 6.75" stroke="currentColor" stroke-width="2.2"
stroke-linecap="round" stroke-linejoin="round" />
</svg>
</span>
<span data-i18n="channels.batchEnableChannels">批量启用</span>
</button>
<button id="batchDisableChannelsBtn" type="button"
class="btn btn-sm channel-batch-action channel-batch-action--disable"
data-action="batch-disable-channels" disabled>
<span class="channel-batch-action__icon" aria-hidden="true">
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="10" cy="10" r="7" stroke="currentColor" stroke-width="1.8" />
<path d="M5.5 5.5L14.5 14.5" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" />
</svg>
</span>
<span data-i18n="channels.batchDisableChannels">批量禁用</span>
</button>
<button id="batchDeleteChannelsBtn" type="button"
class="btn btn-sm channel-batch-action channel-batch-action--delete"
data-action="batch-delete-channels" disabled>
<span class="channel-batch-action__icon" aria-hidden="true">
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6.2 6.2H13.8" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" />
<path d="M7.3 6.2V4.9C7.3 4.4 7.7 4 8.2 4H11.8C12.3 4 12.7 4.4 12.7 4.9V6.2" stroke="currentColor"
stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" />
<path d="M7.1 8.1V13.2C7.1 13.9 7.6 14.4 8.3 14.4H11.7C12.4 14.4 12.9 13.9 12.9 13.2V8.1"
stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" />
<path d="M8.9 8.9V12.6M11.1 8.9V12.6" stroke="currentColor" stroke-width="1.8"
stroke-linecap="round" />
</svg>
</span>
<span data-i18n="channels.batchDeleteChannels">批量删除</span>
</button>
<button id="batchRefreshMergeBtn" type="button"
class="btn btn-sm channel-batch-action channel-batch-action--refresh"
data-action="batch-refresh-channels-merge" disabled>
<span class="channel-batch-action__icon" aria-hidden="true">
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M15.9 7.5A6.6 6.6 0 0 0 4.2 7.1" stroke="currentColor" stroke-width="1.8"
stroke-linecap="round" />
<path d="M15.8 4.3V7.7H12.4" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"
stroke-linejoin="round" />
<path d="M4.1 12.5A6.6 6.6 0 0 0 15.8 12.9" stroke="currentColor" stroke-width="1.8"
stroke-linecap="round" />
<path d="M4.2 15.7V12.3H7.6" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"
stroke-linejoin="round" />
</svg>
</span>
<span data-i18n="channels.batchRefreshMerge">模型增量刷新</span>
</button>
<button id="batchRefreshReplaceBtn" type="button"
class="btn btn-sm channel-batch-action channel-batch-action--refresh"
data-action="batch-refresh-channels-replace" disabled>
<span class="channel-batch-action__icon" aria-hidden="true">
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M15.9 7.5A6.6 6.6 0 0 0 4.2 7.1" stroke="currentColor" stroke-width="1.8"
stroke-linecap="round" />
<path d="M15.8 4.3V7.7H12.4" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"
stroke-linejoin="round" />
<path d="M4.1 12.5A6.6 6.6 0 0 0 15.8 12.9" stroke="currentColor" stroke-width="1.8"
stroke-linecap="round" />
<path d="M4.2 15.7V12.3H7.6" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"
stroke-linejoin="round" />
</svg>
</span>
<span data-i18n="channels.batchRefreshReplace">模型覆盖刷新</span>
</button>
</div>
<button id="batchFloatingMenuCloseBtn" type="button" class="channel-batch-close"
data-action="clear-selected-channels" data-i18n-title="common.close" title="关闭" disabled>
<svg width="26" height="26" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg"
aria-hidden="true" focusable="false">
<path d="M5 5L15 15M15 5L5 15" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" />
</svg>
</button>
</div>
</div>
</div>
</main>
</div>
<!-- 添加/编辑渠道模态框 -->
<div id="channelModal" class="modal">
<div class="modal-content channel-editor-modal">
<div class="modal-header">
<h2 class="modal-title" id="modalTitle" data-i18n="channels.modalAddTitle">添加渠道</h2>
<button type="button" class="close-btn" data-action="close-channel-modal">&times;</button>
</div>
<form id="channelForm" class="channel-editor-form">
<div class="channel-editor-body">
<div class="form-group channel-editor-group channel-editor-group--primary">
<div class="channel-editor-primary-row">
<div class="channel-editor-primary-field channel-editor-primary-field--name">
<label class="form-label channel-editor-inline-label" data-i18n="channels.channelName">渠道名称
*</label>
<input type="text" id="channelName" class="form-input channel-editor-input" required>
</div>
<div class="channel-editor-primary-field channel-editor-primary-field--type">
<label class="form-label channel-editor-inline-label channel-editor-inline-label--muted"
data-i18n="channels.modal.upstreamProtocol">上游协议</label>
<div class="channel-editor-radio-group" id="channelTypeRadios">
<!-- 动态加载渠道类型 -->
</div>
</div>
</div>
<div class="channel-editor-primary-row">
<div class="channel-editor-primary-field channel-editor-primary-field--transforms">
<div class="channel-editor-inline-group">
<label class="form-label channel-editor-inline-label"
data-i18n="channels.modal.protocolTransforms">协议转换</label>
<div class="channel-editor-radio-group" id="protocolTransformsContainer">
<!-- 动态渲染协议转换选项 -->
</div>
</div>
</div>
<div class="channel-editor-primary-field channel-editor-primary-field--mode">
<label class="form-label channel-editor-inline-label"
data-i18n="channels.modal.protocolTransformMode">转换方式</label>
<div class="channel-editor-radio-group" id="protocolTransformModeContainer">
<!-- 动态渲染转换方式选项 -->
</div>
</div>
</div>
</div>
<div class="form-group channel-editor-group">
<!-- API URL和Key策略在第二行 -->
<div>
<div class="channel-editor-section-header channel-editor-section-header--inline">
<label class="form-label channel-editor-section-title">
<span data-i18n="channels.apiUrl">API URL *</span> <span class="models-hint channel-editor-section-meta"><span data-i18n="channels.keyCountPrefix"></span> <span
id="inlineUrlCount">0</span> <span data-i18n="channels.urlCountSuffix">个URL</span></span>
<span class="models-hint channel-editor-section-meta channel-editor-exact-url-hint"
data-i18n="channels.exactUrlMarkerHint">URL 末尾 # 表示完整上游请求地址,不自动追加协议路径</span>
</label>
<div class="channel-editor-section-actions">
<button type="button" class="btn btn-secondary btn-sm channel-editor-action-btn" data-action="add-inline-url"
data-i18n-title="channels.addUrlTitle" title="添加URL">
<svg width="12" height="12" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8 3.5V12.5M3.5 8H12.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"
stroke-linejoin="round" />
</svg>
<span data-i18n="channels.addUrl">添加URL</span>
</button>
<button type="button" id="batchDeleteUrlsBtn" data-action="batch-delete-urls" disabled
data-i18n-title="channels.batchDeleteUrlsTitle" title="批量删除选中的URL" class="btn btn-secondary btn-sm"
style="opacity: 0.5;">
<svg width="12" height="12" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M5.5 2.5V1.5C5.5 1.22386 5.72386 1 6 1H8C8.27614 1 8.5 1.22386 8.5 1.5V2.5M2 3.5H12M3 3.5V11.5C3 12.0523 3.44772 12.5 4 12.5H10C10.5523 12.5 11 12.0523 11 11.5V3.5M5.5 6.5V9.5M8.5 6.5V9.5"
stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round" />
</svg>
<span data-i18n="channels.deleteSelected">删除选中</span>
</button>
</div>
</div>
<!-- 隐藏字段:与后端保持兼容,提交时按换行拼接 -->
<input type="hidden" id="channelUrl" required>
<div class="inline-table-container mobile-inline-table-container">
<table class="inline-table mobile-inline-table inline-url-table">
<thead>
<tr>
<th class="inline-url-col-select">
<div style="display: flex; align-items: center; gap: 6px;">
<input type="checkbox" id="selectAllURLs" data-change-action="toggle-select-all-urls"
style="width: 16px; height: 16px;" data-i18n-title="common.selectAll" title="全选/取消全选">
<span>#</span>
</div>
</th>
<th class="inline-url-col-url">
<div class="inline-url-header">
<span data-i18n="channels.tableApiUrl">API URL</span>
<span class="models-hint inline-url-header-hint" data-i18n="channels.multiUrlStrategyHint">多URL时自动启用智能调度:基于延迟加权随机分发请求,失败URL独立冷却(2min→30min指数退避),优先探索未测试的URL</span>
</div>
</th>
<th class="inline-url-col-actions"></th>
</tr>
</thead>
<tbody id="inlineUrlTableBody">
<!-- URL行动态渲染 -->
</tbody>
</table>
</div>
<div id="channelDuplicateHint" class="channel-duplicate-hint" hidden role="status" aria-live="polite"></div>
</div>
</div>
<div class="form-group channel-editor-group">
<!-- API Key标签行:包含标签、Key计数和操作按钮 -->
<div class="channel-editor-section-header">
<div class="form-label channel-editor-section-title channel-editor-section-title--key">
<span data-i18n="channels.apiKey">API Key *</span> <span class="models-hint channel-editor-section-meta"><span data-i18n="channels.keyCountPrefix"></span> <span
id="inlineKeyCount">0</span> <span data-i18n="channels.keyCountSuffix">个Key</span> <span
id="virtualScrollHint" style="display: none; color: var(--primary-600); font-weight: 600;"
data-i18n="channels.virtualScrollEnabled">· 虚拟滚动已启用</span></span>
<div class="channel-editor-inline-strategy">
<label class="channel-editor-inline-label channel-editor-inline-label--muted"
data-i18n="channels.keyStrategy">Key 策略</label>
<div class="channel-editor-radio-group channel-editor-radio-group--strategy" id="keyStrategyRadios">
<label class="channel-editor-radio-option">
<input type="radio" name="keyStrategy" value="sequential" checked>
<span data-i18n="channels.keyStrategySequential">顺序</span>
</label>
<label class="channel-editor-radio-option">
<input type="radio" name="keyStrategy" value="round_robin">
<span data-i18n="channels.keyStrategyRoundRobin">轮询</span>
</label>
</div>
<span class="models-hint" data-i18n="channels.keyDragSortHint">拖动API Key可排序</span>
</div>
</div>
<div class="channel-editor-section-actions channel-editor-section-actions--keys">
<button type="button" class="btn btn-secondary btn-sm channel-editor-action-btn" data-action="open-key-import-modal">
<svg width="12" height="12" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8 3.5V12M8 12L5 9M8 12L11 9" stroke="currentColor" stroke-width="1.5"
stroke-linecap="round" stroke-linejoin="round" />
<path d="M2 13.5H14" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" />
</svg>
<span data-i18n="channels.importKeys">导入</span>
</button>
<button type="button" id="exportKeysBtn" class="btn btn-secondary btn-sm" data-action="open-key-export-modal"
disabled
style="opacity: 0.5;">
<svg width="12" height="12" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8 12.5V4M8 4L5 7M8 4L11 7" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"
stroke-linejoin="round" />
<path d="M2 2.5H14" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" />
</svg>
<span data-i18n="channels.exportKeys">导出</span>
</button>
<button type="button" id="toggleInlineKeyBtn" class="channel-hover-key-toggle-btn" data-action="toggle-inline-key-visibility"
style="width: 26px; height: 26px; border-radius: 6px; border: 1px solid var(--neutral-300); background: white; color: var(--neutral-500); cursor: pointer; padding: 0; display: inline-flex; align-items: center; justify-content: center; transition: all 0.2s;"
data-i18n-title="channels.toggleKeyVisibility" title="显示/隐藏API Key">
<svg id="inlineEyeIcon" width="13" height="13" viewBox="0 0 16 16" fill="none"
xmlns="http://www.w3.org/2000/svg">
<path
d="M1.5 8C1.5 8 3.5 3.5 8 3.5C12.5 3.5 14.5 8 14.5 8C14.5 8 12.5 12.5 8 12.5C3.5 12.5 1.5 8 1.5 8Z"
stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round" />
<circle cx="8" cy="8" r="2" stroke="currentColor" stroke-width="1.2" stroke-linecap="round"
stroke-linejoin="round" />
</svg>
<svg id="inlineEyeOffIcon" width="13" height="13" viewBox="0 0 16 16" fill="none"
xmlns="http://www.w3.org/2000/svg" style="display: none;">
<path
d="M2 2L14 14M6.5 6.5C6.96785 6.03214 7.60786 5.75 8.3 5.75C9.73071 5.75 10.9 6.91929 10.9 8.35C10.9 9.04214 10.6179 9.68215 10.15 10.15M4.5 4.5C2.73 5.67 1.5 8 1.5 8C1.5 8 3.5 12.5 8 12.5C9.35 12.5 10.58 11.97 11.55 11.5M12.85 11.85C13.97 10.73 14.5 8 14.5 8C14.5 8 12.5 3.5 8 3.5C7.23 3.5 6.5 3.73 5.85 4.05"
stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round" />
</svg>
</button>
<button type="button" id="batchDeleteKeysBtn" data-action="batch-delete-keys" disabled
data-i18n-title="channels.batchDeleteKeysTitle" title="批量删除选中的Keys" class="btn btn-secondary btn-sm"
style="opacity: 0.5;">
<svg width="12" height="12" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M5.5 2.5V1.5C5.5 1.22386 5.72386 1 6 1H8C8.27614 1 8.5 1.22386 8.5 1.5V2.5M2 3.5H12M3 3.5V11.5C3 12.0523 3.44772 12.5 4 12.5H10C10.5523 12.5 11 12.0523 11 11.5V3.5M5.5 6.5V9.5M8.5 6.5V9.5"
stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round" />
</svg>
<span data-i18n="channels.deleteSelected">删除选中</span>
</button>
</div>
</div>
<!-- 隐藏的input用于表单验证和提交 -->
<input type="hidden" id="channelApiKey" required>
<!-- Key表格容器(最多显示5行+滚动) -->
<div class="inline-table-container mobile-inline-table-container">
<table class="inline-table mobile-inline-table inline-key-table">
<thead>
<tr>
<th style="width: 70px;">
<div style="display: flex; align-items: center; gap: 6px;">
<input type="checkbox" id="selectAllKeys" data-change-action="toggle-select-all-keys"
style="width: 16px; height: 16px;" data-i18n-title="common.selectAll" title="全选/取消全选">
<span>#</span>
</div>
</th>
<th data-i18n="channels.tableApiKey">API Key</th>
<th style="width: 200px;">
<select id="keyStatusFilter" class="modal-inline-select" data-change-action="filter-keys-by-status"
style="width: 100%; padding: 4px 8px; border: 1px solid var(--neutral-300); border-radius: 6px; font-size: 13px; font-weight: 600; cursor: pointer; background: white;">
<option value="all" data-i18n="channels.keyStatusAll">全部</option>
<option value="normal" data-i18n="channels.keyStatusNormal">正常</option>
<option value="cooldown" data-i18n="channels.keyStatusCooldown">冷却中</option>
<option value="disabled" data-i18n="channels.keyStatusDisabled">已禁用</option>
</select>
</th>
<th style="width: 80px;"></th>
</tr>
</thead>
<tbody id="inlineKeyTableBody">
<!-- Key行动态渲染 -->
</tbody>
</table>
</div>
</div>
<div class="form-group channel-editor-group">
<!-- 模型配置标签行:包含标签、添加按钮组和删除按钮 -->
<div class="channel-editor-section-header">
<label class="form-label channel-editor-section-title">
<span data-i18n="channels.modelConfig">模型配置 *</span> <span class="models-hint channel-editor-section-meta"><span data-i18n="channels.keyCountPrefix"></span> <span
id="redirectCount">0</span> <span data-i18n="channels.modelCountSuffix">个模型</span></span>
</label>
<div class="channel-editor-section-actions channel-editor-section-actions--models">
<div class="channel-editor-action-row">
<button type="button" class="btn btn-secondary btn-sm" data-action="add-common-models"
data-i18n-title="channels.addCommonModelsTitle" title="添加当前渠道类型的常用模型">
<svg width="12" height="12" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8 1L10 5.5L15 6L11.5 9.5L12.5 14.5L8 12L3.5 14.5L4.5 9.5L1 6L6 5.5L8 1Z"
stroke="currentColor" stroke-width="1.2" stroke-linejoin="round" />
</svg>
<span data-i18n="channels.commonModels">常用模型</span>
</button>
<button type="button" class="btn btn-secondary btn-sm" data-action="fetch-models-from-api"
data-i18n-title="channels.fetchModelsTitle" title="从渠道API获取可用模型列表">
<svg width="12" height="12" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M13.65 2.35C12.2 0.9 10.21 0 8 0 3.58 0 0 3.58 0 8s3.58 8 8 8c3.73 0 6.84-2.55 7.73-6h-2.08c-.82 2.33-3.04 4-5.65 4-3.31 0-6-2.69-6-6s2.69-6 6-6c1.66 0 3.14.69 4.22 1.78L9 7h7V0l-2.35 2.35z"
fill="currentColor" />
</svg>
<span data-i18n="channels.fetchModels">获取模型</span>
</button>
<button type="button" class="btn btn-secondary btn-sm" data-action="add-redirect-row"
data-i18n-title="channels.addModelManualTitle" title="手动添加模型">
<svg width="12" height="12" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8 3.5V12.5M3.5 8H12.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"
stroke-linejoin="round" />
</svg>
<span data-i18n="channels.addModel">添加模型</span>
</button>
</div>
<button type="button" id="batchLowercaseModelsBtn" data-action="batch-lowercase-models" disabled
data-i18n-title="channels.batchLowercaseModelsTitle" title="批量转换选中模型为小写"
class="btn btn-secondary btn-sm"
style="opacity: 0.5;">
<svg width="12" height="12" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M1.5 10.5L3.5 4H5L7 10.5M2.5 8.5H6M8.5 10.5V7.5C8.5 6.5 9.5 6 10.5 6C11.5 6 12.5 6.5 12.5 7.5V10.5M8.5 8.5H12.5"
stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round" />
</svg>
<span data-i18n="channels.lowercaseSelected">转小写</span>
</button>
<button type="button" id="batchDeleteModelsBtn" data-action="batch-delete-models" disabled
data-i18n-title="channels.batchDeleteModelsTitle" title="批量删除选中的模型" class="btn btn-secondary btn-sm"
style="opacity: 0.5;">
<svg width="12" height="12" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M5.5 2.5V1.5C5.5 1.22386 5.72386 1 6 1H8C8.27614 1 8.5 1.22386 8.5 1.5V2.5M2 3.5H12M3 3.5V11.5C3 12.0523 3.44772 12.5 4 12.5H10C10.5523 12.5 11 12.0523 11 11.5V3.5M5.5 6.5V9.5M8.5 6.5V9.5"
stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round" />
</svg>
<span data-i18n="channels.deleteSelected">删除选中</span>
</button>
</div>
</div>
<!-- 模型表格容器 -->
<div class="inline-table-container mobile-inline-table-container tall">
<table class="inline-table mobile-inline-table redirect-model-table">
<thead>
<tr>
<th style="width: 70px;">
<div style="display: flex; align-items: center; gap: 6px;">
<input type="checkbox" id="selectAllModels" data-change-action="toggle-select-all-models"
style="width: 16px; height: 16px;" data-i18n-title="common.selectAll" title="全选/取消全选">
<span>#</span>
</div>
</th>
<th style="width: 42%;">
<input type="text" id="modelFilterInput" class="table-filter-input"
data-i18n-placeholder="channels.searchModelPlaceholder" placeholder="搜索模型名称..."
data-input-action="filter-models-by-keyword">
</th>
<th style="width: 42%;" data-i18n="channels.redirectTarget">重定向目标(可选)</th>
<th style="width: 60px;"></th>
</tr>
</thead>
<tbody id="redirectTableBody">
<!-- 重定向行动态渲染 -->
</tbody>
</table>
</div>
</div>
</div>
<div class="form-group channel-editor-group channel-editor-group--footer">
<div class="channel-editor-footer">
<label class="form-label channel-editor-checkbox-label">
<input type="checkbox" id="channelEnabled" checked> <span data-i18n="channels.enableChannel">启用</span>
</label>
<label id="channelScheduledCheckEnabledWrapper" class="form-label channel-editor-checkbox-label" hidden>
<input type="checkbox" id="channelScheduledCheckEnabled"> <span data-i18n="channels.enableScheduledCheck">定时检测</span>
</label>
<div class="channel-editor-footer-fields">
<div id="channelScheduledCheckModelWrapper" class="channel-editor-inline-field channel-editor-inline-field--scheduled-model" hidden>
<label class="form-label channel-editor-inline-label" for="channelScheduledCheckModelInput"
data-i18n="channels.scheduledCheckModel">检测模型</label>
<div class="filter-combobox-wrapper channel-editor-scheduled-model-control">
<input id="channelScheduledCheckModelInput" class="filter-select filter-combobox" type="text"
autocomplete="off" spellcheck="false">
<div id="channelScheduledCheckModelDropdown" class="filter-dropdown" role="listbox"></div>
<input type="hidden" id="channelScheduledCheckModel" value="">
</div>
</div>
<div class="channel-editor-inline-field">
<label class="form-label channel-editor-inline-label" for="channelPriority"
data-i18n="channels.priority">优先级</label>
<input type="number" id="channelPriority" class="form-input" value="0" min="-99999" max="99999" step="1"
style="width: 80px; min-width: 80px;">
</div>
<div class="channel-editor-inline-field">
<label class="form-label channel-editor-inline-label" for="channelRPMLimit"
data-i18n="channels.rpmLimit">RPM限制</label>
<input type="number" id="channelRPMLimit" class="form-input" value="0" min="0" step="1"
style="width: 74px; min-width: 74px;" data-i18n-placeholder="channels.rpmLimitPlaceholder"
placeholder="0=无限制">
</div>
<div class="channel-editor-inline-field channel-editor-inline-field--currency">
<label class="form-label channel-editor-inline-label" for="channelDailyCostLimit"
data-i18n="channels.dailyCostLimit">每日限额</label>
<div class="channel-editor-inline-field-input">
<input type="number" id="channelDailyCostLimit" class="form-input" value="0" min="0" step="1"
style="width: 74px; min-width: 74px;" data-i18n-placeholder="channels.dailyCostLimitPlaceholder"
placeholder="0=无限制">
</div>
</div>
<div class="channel-editor-inline-field">
<label class="form-label channel-editor-inline-label" for="channelCostMultiplier"
data-i18n="channels.costMultiplier">成本倍率</label>
<input type="number" id="channelCostMultiplier" class="form-input" value="1" min="0" step="0.01"
style="width: 84px; min-width: 84px;" data-i18n-placeholder="channels.costMultiplierPlaceholder"
placeholder="默认1">
</div>
</div>
<div class="channel-editor-footer-actions">
<button type="button" class="btn btn-secondary channel-editor-footer-btn" data-action="open-custom-rules-modal">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false"><path d="M12 15.5C13.93 15.5 15.5 13.93 15.5 12C15.5 10.07 13.93 8.5 12 8.5C10.07 8.5 8.5 10.07 8.5 12C8.5 13.93 10.07 15.5 12 15.5Z" stroke="currentColor" stroke-width="1.8"/><path d="M19.4 15C19.3 15.2 19.3 15.4 19.4 15.6L20.7 17C21 17.3 21 17.7 20.7 18L19.2 19.5C18.9 19.8 18.5 19.8 18.2 19.5L16.8 18.2C16.6 18.1 16.4 18.1 16.2 18.2C15.8 18.4 15.5 18.5 15.1 18.6C14.9 18.6 14.7 18.8 14.7 19.1L14.4 21C14.3 21.4 14 21.7 13.6 21.7H11.4C11 21.7 10.7 21.4 10.6 21L10.3 19.1C10.3 18.8 10.1 18.7 9.9 18.6C9.5 18.5 9.2 18.4 8.8 18.2C8.6 18.1 8.4 18.1 8.2 18.2L6.8 19.5C6.5 19.8 6.1 19.8 5.8 19.5L4.3 18C4 17.7 4 17.3 4.3 17L5.6 15.6C5.7 15.4 5.7 15.2 5.6 15C5.4 14.6 5.3 14.3 5.2 13.9C5.1 13.7 4.9 13.5 4.6 13.5L2.7 13.2C2.3 13.1 2 12.8 2 12.4V10.2C2 9.8 2.3 9.5 2.7 9.4L4.6 9.1C4.9 9.1 5 8.9 5.1 8.7C5.2 8.3 5.3 8 5.5 7.6C5.6 7.4 5.6 7.2 5.5 7L4.2 5.6C3.9 5.3 3.9 4.9 4.2 4.6L5.7 3.1C6 2.8 6.4 2.8 6.7 3.1L8.1 4.4C8.3 4.5 8.5 4.5 8.7 4.4C9.1 4.2 9.4 4.1 9.8 4C10 4 10.2 3.8 10.2 3.5L10.5 1.6C10.6 1.2 10.9 0.9 11.3 0.9H13.5C13.9 0.9 14.2 1.2 14.3 1.6L14.6 3.5C14.6 3.8 14.8 3.9 15 4C15.4 4.1 15.7 4.2 16.1 4.4C16.3 4.5 16.5 4.5 16.7 4.4L18.1 3.1C18.4 2.8 18.8 2.8 19.1 3.1L20.6 4.6C20.9 4.9 20.9 5.3 20.6 5.6L19.3 7C19.2 7.2 19.2 7.4 19.3 7.6C19.5 8 19.6 8.3 19.7 8.7C19.8 8.9 19.9 9 20.2 9.1L22.1 9.4C22.5 9.5 22.8 9.8 22.8 10.2V12.4C22.8 12.8 22.5 13.1 22.1 13.2L20.2 13.5C19.9 13.5 19.8 13.7 19.7 13.9C19.6 14.3 19.5 14.6 19.4 15Z" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/></svg>
<span data-i18n="channels.customRules.advanced">高级</span>
</button>
<button type="button" class="btn btn-secondary channel-editor-footer-btn" data-action="close-channel-modal">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false"><path d="M6 6L18 18M18 6L6 18" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/></svg>
<span data-i18n="common.cancel">取消</span>
</button>
<button type="submit" id="channelSaveBtn" class="btn btn-primary channel-editor-footer-btn">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false"><path d="M5 12L10 17L19 7" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/></svg>
<span data-i18n="common.save">保存</span>
</button>
</div>
</div>
</div>
</form>
</div>
</div>
<!-- 删除确认模态框 -->
<div id="deleteModal" class="modal">
<div class="modal-content confirm-modal">
<h2 class="modal-title" data-i18n="channels.confirmDeleteTitle">确认删除</h2>
<p id="deleteModalMessage" data-i18n="channels.confirmDeleteMsg">确定要删除渠道吗?</p>
<p style="color: var(--error-600); font-size: 0.875rem;" data-i18n="channels.deleteWarning">此操作不可恢复!</p>
<div class="confirm-actions">
<button type="button" class="btn btn-secondary" data-action="close-delete-modal" data-i18n="common.cancel">取消</button>
<button type="button" class="btn btn-danger" data-action="confirm-delete-channel" data-i18n="common.delete">删除</button>
</div>
</div>
</div>
<!-- 自定义请求规则模态框 -->
<div id="customRulesModal" class="modal">
<div class="modal-content custom-rules-modal">
<div class="modal-header">
<h2 class="modal-title" data-i18n="channels.customRules.modalTitle">自定义请求规则</h2>
<button type="button" class="close-btn" data-action="close-custom-rules-modal">&times;</button>
</div>
<div class="custom-rules-tabs" role="tablist">
<button type="button" class="custom-rules-tab-button active" data-custom-rules-tab="headers"
role="tab" aria-selected="true">
<span data-i18n="channels.customRules.tabHeaders">请求头</span>
<span class="custom-rules-tab-count" id="customRulesHeadersCount">(0)</span>
<span class="custom-rules-help-icon" data-custom-rules-help="headers"
title="" data-i18n-title="channels.customRules.helpIconTitle" aria-label="help">?</span>
</button>
<button type="button" class="custom-rules-tab-button" data-custom-rules-tab="body"
role="tab" aria-selected="false">
<span data-i18n="channels.customRules.tabBody">请求参数</span>
<span class="custom-rules-tab-count" id="customRulesBodyCount">(0)</span>
<span class="custom-rules-help-icon" data-custom-rules-help="body"
title="" data-i18n-title="channels.customRules.helpIconTitle" aria-label="help">?</span>
</button>
</div>
<div class="custom-rules-help-popup" id="customRulesHelpPopup" hidden>
<pre id="customRulesHelpContent"></pre>
<button type="button" class="btn btn-secondary btn-sm" data-action="close-custom-rules-help"
data-i18n="common.close">关闭</button>
</div>
<div class="custom-rules-anyrouter-hint" id="customRulesAnyrouterHint" hidden>
<div class="custom-rules-anyrouter-hint-title" data-i18n="channels.customRules.anyrouterHintTitle">系统自动注入规则(anyrouter 渠道)</div>
<ul class="custom-rules-anyrouter-hint-list">
<li data-i18n="channels.customRules.anyrouterHintBeta">请求头追加 anthropic-beta: context-1m-2025-08-07(可被下方自定义规则覆盖或移除)</li>
<li data-i18n="channels.customRules.anyrouterHintThinking">请求参数注入 thinking.type = adaptive(仅 /v1/messages 且未声明 thinking 时生效)</li>
</ul>
</div>
<div class="custom-rules-panel" id="customRulesPanelHeaders">
<div class="custom-rules-list" id="customRulesListHeaders"></div>
<button type="button" class="btn btn-secondary custom-rules-add-btn"
data-action="add-custom-rule" data-custom-rules-target="headers"
data-i18n="channels.customRules.addRule">+ 添加规则</button>
</div>
<div class="custom-rules-panel hidden" id="customRulesPanelBody">
<div class="custom-rules-list" id="customRulesListBody"></div>
<button type="button" class="btn btn-secondary custom-rules-add-btn"
data-action="add-custom-rule" data-custom-rules-target="body"
data-i18n="channels.customRules.addRule">+ 添加规则</button>
</div>
<div id="customRulesError" class="custom-rules-error" hidden></div>
<div class="modal-footer custom-rules-footer">
<button type="button" class="btn btn-secondary" data-action="close-custom-rules-modal"
data-i18n="common.cancel">取消</button>
<button type="button" class="btn btn-primary" data-action="apply-custom-rules"
data-i18n="common.confirm">确定</button>
</div>
</div>
</div>
<!-- 测试渠道模态框 -->
<div id="testModal" class="modal">
<div class="modal-content test-modal-content">
<div class="modal-header">
<h2 class="modal-title"><span data-i18n="channels.testChannelTitle">测试渠道</span> - <span
id="testChannelName"></span></h2>
<button type="button" class="close-btn" data-action="close-test-modal">&times;</button>
</div>
<div class="form-group">
<label class="form-label" for="testChannelType" data-i18n="channels.channelType">渠道类型</label>
<select id="testChannelType" class="form-input" data-change-action="update-test-url">
<!-- 动态加载渠道类型 -->
</select>
</div>
<div class="form-group">
<label class="form-label" for="testModelSelect" data-i18n="channels.selectTestModel">选择测试模型</label>
<select id="testModelSelect" class="model-select">
<!-- 动态填充模型选项 -->
</select>
</div>
<div class="form-group hidden" id="testKeySelectGroup">
<label class="form-label" for="testKeySelect">
<span data-i18n="channels.selectApiKey">选择 API Key</span>
<span class="models-hint channel-test-inline-hint" data-i18n="channels.testKeyHint">单次测试时使用的
Key(最多显示前10个)</span>
</label>
<select id="testKeySelect" class="form-input">
<!-- 动态填充 Key 选项(限制前10个) -->
</select>
</div>
<div class="form-group">
<label class="form-label" for="testContentInput" data-i18n="channels.testContent">测试内容</label>
<textarea id="testContentInput" class="form-input channel-test-textarea" rows="3"
data-i18n-placeholder="channels.testContentPlaceholder" placeholder="输入测试内容..."></textarea>
<div class="models-hint" data-i18n="channels.testContentHint">默认内容从系统设置加载,可在"系统设置"页面修改</div>
</div>
<div class="form-group">
<label class="form-label channel-test-checkbox-label">
<input type="checkbox" id="testStreamEnabled" class="control-checkbox">
<span data-i18n="channels.enableStream">启用流式请求(Stream)</span>
</label>
<div id="streamHint" class="models-hint" data-i18n="channels.streamHint">默认使用非流模式测试,可根据需要开启流式</div>
</div>
<div class="form-group">
<label class="form-label" for="testConcurrency">
<span data-i18n="channels.batchConcurrency">批量测试并发数</span>
<span class="models-hint channel-test-inline-hint"
data-i18n="channels.batchConcurrencyHint">同时测试的Key数量(建议5-20)。冷却策略由服务器端自动应用(指数退避:2min→4min→8min→30min)</span>
</label>
<input type="number" id="testConcurrency" class="form-input channel-test-concurrency-input" value="10" min="1"
max="50">
</div>
<!-- 测试进度 -->
<div id="testProgress" class="test-progress">
<div class="test-spinner"></div>
<span id="testProgressText" data-i18n="channels.testingApi">正在测试API连接...</span>
</div>
<!-- 批量测试进度 -->
<div id="batchTestProgress" class="channel-batch-progress hidden">
<div class="channel-batch-progress-header">
<span class="channel-batch-progress-title" data-i18n="channels.batchTestProgress">批量测试进度</span>
<span id="batchTestCounter" class="channel-batch-progress-counter">0 / 0</span>
</div>
<div class="channel-batch-progress-track">
<div id="batchTestProgressBar" class="channel-batch-progress-bar"></div>
</div>
<div id="batchTestStatus" class="channel-batch-progress-status"></div>
</div>
<!-- 测试结果 -->
<div id="testResult" class="test-result">
<div id="testResultContent"></div>
<div id="testResultDetails" class="test-details"></div>
</div>
<div class="form-actions">
<button type="button" class="btn btn-secondary" data-action="close-test-modal" data-i18n="common.close">关闭</button>
<button type="button" id="runTestBtn" class="btn btn-primary" data-action="run-channel-test"
data-i18n="channels.singleTest">单个测试</button>
<button type="button" id="batchTestBtn" class="btn btn-primary hidden" data-action="run-batch-test"
data-i18n="channels.batchTestAllKeys">批量测试所有Key</button>
</div>
</div>
</div>
<!-- Key导入模态框 -->
<div id="keyImportModal" class="modal">
<div class="modal-content modal-content--md">
<div class="modal-header">
<h2 class="modal-title" data-i18n="channels.importKeysTitle">批量导入 API Keys</h2>
<button type="button" class="close-btn channel-modal-close-btn channel-hover-modal-close-btn"
data-action="close-key-import-modal">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10.5 3.5L3.5 10.5M3.5 3.5L10.5 10.5" stroke="currentColor" stroke-width="1.5"
stroke-linecap="round" stroke-linejoin="round" />
</svg>
</button>
</div>
<div class="channel-import-modal-body">
<div class="form-group">
<label class="form-label"><span data-i18n="channels.pasteApiKeys">粘贴API
Keys</span> <span class="channel-import-hint"
data-i18n="channels.keysSeparatorHint">(支持逗号或换行分隔)</span></label>
<textarea id="keyImportTextarea" class="form-input channel-import-textarea" rows="10"
placeholder="sk-ant-key1,sk-ant-key2&#10;sk-ant-key3&#10;sk-ant-key4"></textarea>
</div>
<div class="channel-import-info channel-import-info--compact">
<div class="channel-import-info-row">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"
class="channel-import-info-icon">
<circle cx="8" cy="8" r="6.5" stroke="currentColor" stroke-width="1.5" />
<path d="M8 5V8.5M8 11H8.005" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" />
</svg>
<div>
<strong data-i18n="channels.usageInstructions">使用说明:</strong>
<ul class="channel-import-info-list">
<li><span data-i18n="channels.commaSeparated">支持逗号分隔:</span><code
class="channel-import-code">key1,key2,key3</code>
</li>
<li data-i18n="channels.newlineSeparated">支持换行分隔:每行一个Key</li>
<li data-i18n="channels.autoDedup">自动去除空格、空行和重复Key</li>
</ul>
</div>
</div>
</div>
<div id="keyImportPreview" class="channel-import-preview">
<div id="keyImportPreviewContent" class="channel-import-preview-content hidden">
<div class="channel-import-preview-row">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3 8L6.5 11.5L13 5" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"
stroke-linejoin="round" />
</svg>
<span><span data-i18n="channels.parseSuccess">解析成功:将导入</span> <span id="keyImportCount">0</span> <span
data-i18n="channels.keyCountSuffix">个Key</span></span>
</div>
</div>
</div>
</div>
<div class="form-actions">
<button type="button" class="btn btn-secondary" data-action="close-key-import-modal"
data-i18n="common.cancel">取消</button>
<button type="button" class="btn btn-primary" data-action="confirm-inline-key-import"
data-i18n="channels.confirmImport">确认导入</button>
</div>
</div>
</div>
<!-- Key导出模态框 -->
<div id="keyExportModal" class="modal">
<div class="modal-content modal-content--sm">
<div class="modal-header">
<h2 class="modal-title" data-i18n="channels.exportKeysTitle">导出 API Keys</h2>
<button type="button" class="close-btn channel-modal-close-btn channel-hover-modal-close-btn"
data-action="close-key-export-modal">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10.5 3.5L3.5 10.5M3.5 3.5L10.5 10.5" stroke="currentColor" stroke-width="1.5"
stroke-linecap="round" stroke-linejoin="round" />
</svg>
</button>
</div>
<div class="channel-export-modal-body">
<!-- 分隔符选项 -->
<div class="form-group">
<label class="form-label" data-i18n="channels.exportSeparator">分隔方式</label>
<div class="channel-export-options">
<label class="channel-export-option">
<input type="radio" name="exportSeparator" value="newline" checked data-change-action="update-export-preview">
<span data-i18n="channels.separatorNewline">换行分隔</span>
</label>
<label class="channel-export-option">
<input type="radio" name="exportSeparator" value="comma" data-change-action="update-export-preview">
<span data-i18n="channels.separatorComma">逗号分隔</span>
</label>
</div>
</div>
<!-- 预览区域 -->
<div class="form-group">
<label class="form-label" data-i18n="channels.exportPreview">预览</label>
<textarea id="keyExportPreview" class="form-input channel-export-preview" rows="8" readonly></textarea>
</div>
</div>
<div class="form-actions channel-export-actions">
<button type="button" class="btn btn-secondary" data-action="close-key-export-modal"
data-i18n="common.cancel">取消</button>
<button type="button" class="btn btn-secondary" data-action="copy-export-keys" data-i18n="common.copy">复制</button>
<button type="button" class="btn btn-primary" data-action="download-export-keys"
data-i18n="channels.exportToFile">导出文件</button>
</div>
</div>
</div>
<!-- 模型导入模态框 -->
<div id="modelImportModal" class="modal">
<div class="modal-content modal-content--md">
<div class="modal-header">
<h2 class="modal-title" data-i18n="channels.importModelsTitle">批量添加模型</h2>
<button type="button" class="close-btn channel-modal-close-btn channel-hover-modal-close-btn"
data-action="close-model-import-modal">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10.5 3.5L3.5 10.5M3.5 3.5L10.5 10.5" stroke="currentColor" stroke-width="1.5"
stroke-linecap="round" stroke-linejoin="round" />
</svg>
</button>
</div>
<div class="channel-import-modal-body">
<div class="form-group">
<label class="form-label"><span data-i18n="channels.inputModelNames">输入模型名称</span>
<span class="channel-import-hint"
data-i18n="channels.keysSeparatorHint">(支持逗号或换行分隔)</span></label>
<textarea id="modelImportTextarea" class="form-input channel-import-textarea" rows="10"
placeholder="gpt-4o,gpt-4o-mini&#10;claude-3-5-sonnet-20241022&#10;claude-3-5-haiku-latest"></textarea>
</div>
<div class="channel-import-info">
<div class="channel-import-info-row">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"
class="channel-import-info-icon">
<circle cx="8" cy="8" r="6.5" stroke="currentColor" stroke-width="1.5" />
<path d="M8 5V8.5M8 11H8.005" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" />
</svg>
<div>
<strong data-i18n="channels.usageInstructions">使用说明:</strong>
<ul class="channel-import-info-list">
<li><span data-i18n="channels.commaSeparatedModel">支持逗号分隔:</span><code
class="channel-import-code">model1,model2,model3</code>
</li>
<li data-i18n="channels.newlineSeparatedModel">支持换行分隔:每行一个模型</li>
<li data-i18n="channels.autoDedupModel">自动去除空格、空行和重复模型</li>
</ul>
</div>
</div>
</div>
<div id="modelImportPreview" class="channel-import-preview">
<div id="modelImportPreviewContent" class="channel-import-preview-content hidden">
<div class="channel-import-preview-row">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3 8L6.5 11.5L13 5" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"
stroke-linejoin="round" />
</svg>
<span><span data-i18n="channels.parseSuccessModel">解析成功:将添加</span> <span id="modelImportCount">0</span>
<span data-i18n="channels.modelCountSuffix">个模型</span></span>
</div>
</div>
</div>
</div>
<div class="form-actions">
<button type="button" class="btn btn-secondary" data-action="close-model-import-modal"
data-i18n="common.cancel">取消</button>
<button type="button" class="btn btn-primary" data-action="confirm-model-import"
data-i18n="channels.confirmAdd">确认添加</button>
</div>
</div>
</div>
<!-- 渠道排序模态框 -->
<div id="sortModal" class="modal">
<div class="modal-content modal-content--xl modal-content--tall channel-sort-modal">
<div class="modal-header">
<h2 class="modal-title" data-i18n="channels.sortModalTitle">渠道排序</h2>
<button type="button" class="close-btn" data-action="close-sort-modal">&times;</button>
</div>
<div class="channel-sort-modal-body">
<!-- 排序列表容器 -->
<div id="sortListContainer" class="channel-sort-list">
<!-- 动态渲染渠道列表 -->
</div>
</div>
<div class="form-actions channel-sort-actions">
<div class="channel-sort-hint">
<svg width="14" height="14" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"
class="channel-sort-hint-icon">
<circle cx="8" cy="8" r="6.5" stroke="currentColor" stroke-width="1.5" />
<path d="M8 5V8.5M8 11H8.005" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" />
</svg>
<span data-i18n="channels.sortHint">拖动卡片调整顺序 · 保存后相邻优先级相差10</span>
</div>
<div class="channel-sort-action-buttons">
<button type="button" class="btn btn-secondary" data-action="close-sort-modal"
data-i18n="common.cancel">取消</button>
<button type="button" class="btn btn-primary" data-action="save-sort-order"
data-i18n="channels.saveSortOrder">保存排序</button>
</div>
</div>
</div>
</div>
<!-- HTML模板定义 (分离HTML与JS) -->
<template id="tpl-key-row">
<tr draggable="true" class="mobile-inline-row inline-key-row draggable-key-row" data-index="{{index}}"
style="border-bottom: 1px solid var(--neutral-200); height: 36px;">
<td class="inline-key-col-select mobile-inline-no-label" style="padding: 4px 8px;">
<div style="display: flex; align-items: center; gap: 6px;">
<input type="checkbox" class="key-checkbox" data-index="{{index}}" style="width: 16px; height: 16px;">
<span style="color: var(--neutral-600); font-weight: 500; font-size: 13px;">{{displayIndex}}</span>
</div>
</td>
<td class="inline-key-col-key" style="padding: 4px 8px;" data-mobile-label="{{mobileLabelKey}}">
<input type="{{inputType}}" value="{{key}}" class="inline-key-input modal-inline-input" data-index="{{index}}"
style="width: 100%; padding: 4px 7px; border: 1px solid var(--neutral-300); border-radius: 6px; font-family: 'Monaco', 'Menlo', 'Courier New', monospace; font-size: 13px; transition: all 0.2s;">
</td>
<td class="inline-key-col-status" style="padding: 4px 8px;" data-mobile-label="{{mobileLabelStatus}}">{{{cooldownHtml}}}</td>
<td class="inline-key-col-actions" style="padding: 4px 8px; text-align: center;" data-mobile-label="{{mobileLabelActions}}">{{{actionsHtml}}}</td>
</tr>
</template>
<template id="tpl-key-empty">
<tr>
<td colspan="4" style="padding: 30px; text-align: center; color: var(--neutral-500); font-size: 14px;">
{{message}}
</td>
</tr>
</template>
<template id="tpl-cooldown-badge">
<span
style="color: #dc2626; font-size: 12px; font-weight: 500; background: linear-gradient(135deg, #fee2e2 0%, #fecaca 100%); padding: 2px 8px; border-radius: 4px; border: 1px solid #fca5a5; white-space: nowrap;"
data-i18n-template="channels.cooldownBadge" data-i18n-time="{{text}}">⚠️ 冷却中·{{text}}</span>
</template>
<template id="tpl-key-normal-status">
<span style="color: var(--success-600); font-size: 12px;" data-i18n="channels.statusNormal">✓ 正常</span>
</template>
<template id="tpl-key-actions">
<div class="inline-key-actions" style="display: flex; gap: 6px; justify-content: center;">
<button type="button" class="key-action-btn" data-action="copy" data-index="{{index}}"
data-i18n-title="channels.copyThisKey" title="复制此Key"
style="width: 26px; height: 26px; border-radius: 6px; border: 1px solid var(--neutral-200); background: white; color: var(--neutral-500); cursor: pointer; transition: all 0.2s; display: inline-flex; align-items: center; justify-content: center; padding: 0;">
<svg width="12" height="12" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M4.5 2H10.5C11.0523 2 11.5 2.44772 11.5 3V4H12.5C13.0523 4 13.5 4.44772 13.5 5V13C13.5 13.5523 13.0523 14 12.5 14H6.5C5.94772 14 5.5 13.5523 5.5 13V12H4.5C3.94772 12 3.5 11.5523 3.5 11V3C3.5 2.44772 3.94772 2 4.5 2Z"
stroke="currentColor" stroke-width="1.2" fill="none" />
<path d="M5.5 4H10.5C11.0523 4 11.5 4.44772 11.5 5V11" stroke="currentColor" stroke-width="1.2" fill="none" />
</svg>
</button>
<button type="button" class="key-action-btn" data-action="test" data-index="{{index}}"
data-i18n-title="channels.testThisKey" title="测试此Key"
style="width: 26px; height: 26px; border-radius: 6px; border: 1px solid var(--neutral-200); background: white; color: var(--neutral-500); cursor: pointer; transition: all 0.2s; display: inline-flex; align-items: center; justify-content: center; padding: 0;">
<svg width="12" height="12" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M4 2L12 8L4 14V2Z" fill="currentColor" />
</svg>
</button>
<button type="button" class="key-action-btn" data-action="delete" data-index="{{index}}"
data-i18n-title="channels.deleteThisKey" title="删除此Key"
style="width: 26px; height: 26px; border-radius: 6px; border: 1px solid var(--neutral-200); background: white; color: var(--neutral-500); cursor: pointer; transition: all 0.2s; display: inline-flex; align-items: center; justify-content: center; padding: 0;">
<svg width="12" height="12" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M5.5 2.5V1.5C5.5 1.22386 5.72386 1 6 1H8C8.27614 1 8.5 1.22386 8.5 1.5V2.5M2 3.5H12M3 3.5V11.5C3 12.0523 3.44772 12.5 4 12.5H10C10.5523 12.5 11 12.0523 11 11.5V3.5M5.5 6.5V9.5M8.5 6.5V9.5"
stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round" />
</svg>
</button>
</div>
</template>
<template id="tpl-url-row">
<tr class="mobile-inline-row inline-url-row" style="border-bottom: 1px solid var(--neutral-200);">
<td class="inline-url-col-select mobile-inline-no-label">
<div style="display: flex; align-items: center; gap: 6px;">
<input type="checkbox" class="url-checkbox" data-index="{{index}}"
style="width: 16px; height: 16px;">
<span style="color: var(--neutral-600); font-weight: 500; font-size: 13px;">{{displayIndex}}</span>
</div>
</td>
<td class="inline-url-col-url" data-mobile-label="{{mobileLabelUrl}}">
<input type="text" value="{{url}}" class="inline-url-input modal-inline-input" data-index="{{index}}"
placeholder="https://api.example.com"
style="width: 100%; padding: 4px 7px; border: 1px solid var(--neutral-300); border-radius: 6px; font-family: 'Monaco', 'Menlo', 'Courier New', monospace; font-size: 13px;">
</td>
<td class="inline-url-col-actions inline-url-cell-center" data-mobile-label="{{mobileLabelActions}}">
<div class="inline-url-actions">
<button type="button" class="inline-url-test-btn" data-index="{{index}}" data-i18n-title="channels.testThisUrl"
title="测试此URL"
style="width: 26px; height: 26px; border-radius: 6px; border: 1px solid var(--neutral-200); background: white; color: var(--neutral-500); cursor: pointer; transition: all 0.2s; display: inline-flex; align-items: center; justify-content: center; padding: 0;">
<svg width="12" height="12" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M4 2L12 8L4 14V2Z" fill="currentColor" />
</svg>
</button>
<button type="button" class="inline-url-delete-btn" data-index="{{index}}" data-i18n-title="channels.deleteThisUrl"
title="删除此URL"
style="width: 26px; height: 26px; border-radius: 6px; border: 1px solid var(--neutral-200); background: white; color: var(--neutral-500); cursor: pointer; transition: all 0.2s; display: inline-flex; align-items: center; justify-content: center; padding: 0;">
<svg width="12" height="12" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M5.5 2.5V1.5C5.5 1.22386 5.72386 1 6 1H8C8.27614 1 8.5 1.22386 8.5 1.5V2.5M2 3.5H12M3 3.5V11.5C3 12.0523 3.44772 12.5 4 12.5H10C10.5523 12.5 11 12.0523 11 11.5V3.5M5.5 6.5V9.5M8.5 6.5V9.5"
stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round" />
</svg>
</button>
</div>
</td>
</tr>
</template>
<template id="tpl-url-empty">
<tr>
<td colspan="3" style="padding: 20px; text-align: center; color: var(--neutral-500);">
{{message}}
</td>
</tr>
</template>
<!-- 渠道表格行模板 -->
<template id="tpl-channel-card">
<tr class="{{rowClasses}}" id="channel-{{id}}" data-channel-id="{{id}}">
<td class="ch-col-checkbox">
<input type="checkbox" class="channel-select-checkbox" data-channel-id="{{id}}"
data-i18n-title="channels.selectChannel" title="选择渠道">
</td>
<td class="ch-col-name">
<div class="ch-name-cell">
<div class="ch-name-line">
<div class="ch-name-main">{{{typeBadge}}}<strong>{{name}}</strong>{{{protocolTransformBadges}}}</div>
</div>
<div class="ch-url-line" title="{{url}}">{{url}}</div>
<div class="ch-refresh-result-slot">{{{batchRefreshStatusHtml}}}</div>
{{{healthHtml}}}
<div class="ch-last-request-slot ch-last-request-slot--desktop">{{{lastRequestFailureHtml}}}</div>
{{{nameMultiplierBadge}}}
</div>
</td>
<td class="ch-col-models" data-mobile-label="{{mobileLabelModels}}">
<span class="ch-models-text" title="{{modelsText}}">{{modelsText}}</span>
</td>
<td class="ch-col-priority" data-mobile-label="{{mobileLabelPriority}}">
{{{effectivePriorityHtml}}}
</td>
<td class="ch-col-duration {{durationCellClass}}" data-mobile-label="{{mobileLabelDuration}}">{{{durationHtml}}}</td>
<td class="ch-col-usage {{usageCellClass}}" data-mobile-label="{{mobileLabelUsage}}">{{{usageHtml}}}</td>
<td class="ch-col-cost {{costCellClass}}" data-mobile-label="{{mobileLabelCost}}">{{{costHtml}}}</td>
<td class="ch-col-last-success {{lastSuccessCellClass}}" data-mobile-label="{{mobileLabelLastSuccess}}">{{{lastSuccessHtml}}}<div class="ch-last-request-slot ch-last-request-slot--mobile">{{{lastRequestFailureHtml}}}</div></td>
<td class="ch-col-enabled" data-mobile-label="{{mobileLabelEnabled}}">
<button class="channel-enable-switch channel-action-btn {{toggleSwitchClass}}" data-action="toggle"
data-channel-id="{{id}}" data-enabled="{{enabled}}" role="switch" aria-checked="{{enabled}}"
title="{{toggleTitle}}" aria-label="{{toggleTitle}}">
<span class="channel-enable-switch__knob" aria-hidden="true"></span>
</button>
</td>
<td class="ch-col-actions" data-mobile-label="{{mobileLabelActions}}">
<div class="ch-actions-stack">
<div class="ch-action-statuses">{{{cooldownBadge}}}</div>
<div class="ch-action-group">
<button class="btn-icon channel-action-btn" data-action="edit" data-channel-id="{{id}}"
data-i18n-title="common.edit" title="编辑" aria-label="编辑">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false"><path d="M3 17.25V21H6.75L17.81 9.94L14.06 6.19L3 17.25Z" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/><path d="M20.71 7.04C21.1 6.65 21.1 6.02 20.71 5.63L18.37 3.29C17.98 2.9 17.35 2.9 16.96 3.29L15.13 5.12L18.88 8.87L20.71 7.04Z" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/></svg>
</button>
<button class="btn-icon channel-action-btn" data-action="test" data-channel-id="{{id}}"
data-channel-name="{{name}}" data-i18n-title="channels.testApiKey" title="测试API Key" aria-label="测试API Key">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false"><path d="M13 2L4 14H11L9 22L20 10H13L13 2Z" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/></svg>
</button>
<button class="btn-icon channel-action-btn" data-action="copy" data-channel-id="{{id}}"
data-channel-name="{{name}}" data-i18n-title="channels.copyChannelTitle" title="复制渠道" aria-label="复制渠道">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false"><rect x="9" y="9" width="11" height="11" rx="2" stroke="currentColor" stroke-width="1.8"/><path d="M5 15H4C2.9 15 2 14.1 2 13V4C2 2.9 2.9 2 4 2H13C14.1 2 15 2.9 15 4V5" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/></svg>
</button>
<button class="btn-icon btn-danger channel-action-btn" data-action="delete" data-channel-id="{{id}}"
data-channel-name="{{name}}" data-i18n-title="common.delete" title="删除" aria-label="删除">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false"><path d="M3 6H21" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/><path d="M8 6V4H16V6" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/><path d="M19 6L18 20H6L5 6" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/><path d="M10 11V17" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/><path d="M14 11V17" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/></svg>
</button>
</div>
</div>
</td>
</tr>
</template>
<!-- 重定向规则行模板 -->
<template id="tpl-redirect-row">
<tr class="mobile-inline-row redirect-row">
<td class="redirect-col-select mobile-inline-no-label">
<div class="redirect-row-select">
<input type="checkbox" class="model-checkbox redirect-row-checkbox" data-index="{{index}}">
<span class="redirect-row-index">{{displayIndex}}</span>
</div>
</td>
<td class="redirect-col-model" data-mobile-label="{{mobileLabelModel}}">
<div class="redirect-model-field">
<input type="text" class="redirect-from-input modal-inline-input" data-index="{{index}}" value="{{from}}"
placeholder="claude-3-opus-20240229">
<button type="button" class="lowercase-btn redirect-lowercase-btn" data-index="{{index}}"
data-i18n-title="channels.toLowercase" title="转为小写">
<svg width="12" height="12" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M1.5 10.5L3.5 4H5L7 10.5M2.5 8.5H6M8.5 10.5V7.5C8.5 6.5 9.5 6 10.5 6C11.5 6 12.5 6.5 12.5 7.5V10.5M8.5 8.5H12.5"
stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round" />
</svg>
</button>
</div>
</td>
<td class="redirect-col-target" data-mobile-label="{{mobileLabelTarget}}">
<input type="text" class="redirect-to-input modal-inline-input" data-index="{{index}}" value="{{to}}"
placeholder="{{toPlaceholder}}">
</td>
<td class="redirect-col-actions" data-mobile-label="{{mobileLabelActions}}">
<button type="button" class="redirect-delete-btn" data-index="{{index}}"
data-i18n-title="channels.deleteThisModel" title="删除此模型">
<svg width="12" height="12" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M5.5 2.5V1.5C5.5 1.22386 5.72386 1 6 1H8C8.27614 1 8.5 1.22386 8.5 1.5V2.5M2 3.5H12M3 3.5V11.5C3 12.0523 3.44772 12.5 4 12.5H10C10.5523 12.5 11 12.0523 11 11.5V3.5M5.5 6.5V9.5M8.5 6.5V9.5"
stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round" />
</svg>
</button>
</td>
</tr>
</template>
<!-- 重定向规则空状态模板 -->
<template id="tpl-redirect-empty">
<tr>
<td colspan="4" class="redirect-empty-cell">
{{message}}
</td>
</tr>
</template>
<!-- 排序卡片模板 -->
<template id="tpl-sort-item">
<div class="sort-item" data-channel-id="{{id}}" draggable="true">
<div class="sort-item-body">
<div class="sort-item-main">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"
class="sort-item-handle" aria-hidden="true" focusable="false">
<path d="M2 4H14M2 8H14M2 12H14" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" />
</svg>
<span class="sort-item-name">{{name}}</span>
</div>
<div class="sort-item-meta">
<span class="sort-item-priority-label" data-i18n="channels.currentPriority">当前优先级:</span>
<strong>{{priority}}</strong>
{{{statusBadge}}}
</div>
</div>
</div>
</template>
<!-- 测试结果头部模板 -->
<template id="tpl-test-result-header">
<div class="test-result-header">
<span class="test-result-header-icon">{{icon}}</span>
<strong>{{message}}</strong>
</div>
</template>
<!-- 响应区块模板 -->
<template id="tpl-response-section">
<div class="response-section">
<div class="response-section-header">
<h4 class="response-section-title">{{title}}</h4>
{{{toggleBtn}}}
</div>
<div id="{{contentId}}" class="response-content" style="display: {{display}};">{{{content}}}</div>
</div>
</template>
<!-- 批量测试失败详情模板 -->
<template id="tpl-batch-fail-item">
<li class="batch-fail-item"><strong>Key #{{keyNum}}</strong> ({{keyMask}}): {{error}}</li>
</template>
<!-- 上游详情 Modal -->
<div id="upstreamDetailModal" class="modal">
<div class="modal-content upstream-detail-modal-content">
<div class="modal-header">
<h2 class="modal-title" data-i18n="channels.test.upstreamDetail">上游请求/响应详情</h2>
<button type="button" class="close-btn" data-action="close-upstream-detail">&times;</button>
</div>
<div class="upstream-detail-tabs">
<button type="button" class="upstream-tab active" data-tab="request" data-i18n="channels.test.tabRequest">Request</button>
<button type="button" class="upstream-tab" data-tab="response" data-i18n="channels.test.tabResponse">Response</button>
</div>
<div id="upstreamTabRequest" class="upstream-tab-panel active">
<div class="upstream-field">
<label data-i18n="channels.test.requestUrl">URL</label>
<pre id="upstreamReqUrl" class="upstream-pre"></pre>
</div>
<div class="upstream-field">
<label data-i18n="channels.test.requestHeaders">Headers</label>
<pre id="upstreamReqHeaders" class="upstream-pre"></pre>
</div>
<div class="upstream-field">
<label data-i18n="channels.test.requestBody">Body</label>
<pre id="upstreamReqBody" class="upstream-pre upstream-pre--tall"></pre>
</div>
</div>
<div id="upstreamTabResponse" class="upstream-tab-panel">
<div class="upstream-field">
<label data-i18n="channels.test.responseStatus">Status</label>
<pre id="upstreamRespStatus" class="upstream-pre"></pre>
</div>
<div class="upstream-field">
<label data-i18n="channels.test.responseHeaders">Headers</label>
<pre id="upstreamRespHeaders" class="upstream-pre"></pre>
</div>
<div class="upstream-field">
<label data-i18n="channels.test.responseBody">Body</label>
<pre id="upstreamRespBody" class="upstream-pre upstream-pre--tall"></pre>
</div>
</div>
</div>
</div>
</body>
</html>