cli-proxy-api / static /management.html
taitai9616's picture
部署
94d41d2
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title data-i18n="title.login">CLI Proxy API Management Center</title>
<style>
/* 全局样式 */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
/* CSS变量主题系统 */
:root {
/* 布局尺寸 */
--navbar-height: 69px;
/* 亮色主题(默认) */
--bg-primary: #f5f7fa;
--bg-secondary: #ffffff;
--bg-tertiary: #f8f9fb;
--bg-quaternary: #f7fafc;
--bg-modal: rgba(0, 0, 0, 0.5);
/* 侧边栏颜色 */
--sidebar-bg: #ffffff;
--sidebar-hover: #f0f2f5;
--sidebar-active: #e8f4ff;
--sidebar-border: #e5e7eb;
--text-primary: #1f2937;
--text-secondary: #4b5563;
--text-tertiary: #6b7280;
--text-quaternary: #9ca3af;
--text-inverse: white;
--border-primary: #e5e7eb;
--border-secondary: #d1d5db;
--border-focus: #3b82f6;
--accent-primary: #3b82f6;
--accent-secondary: #e5e7eb;
--accent-tertiary: #f3f4f6;
--primary-color: #3b82f6;
--primary-hover: #2563eb;
--card-bg: #ffffff;
--border-color: #e5e7eb;
--success-bg: #d1fae5;
--success-text: #065f46;
--success-border: #6ee7b7;
--error-bg: #fee2e2;
--error-text: #991b1b;
--error-border: #fca5a5;
--warning-bg: #fef3c7;
--warning-text: #92400e;
--warning-border: #fbbf24;
--shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
--shadow-primary: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06);
--shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
--shadow-secondary: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
--shadow-modal: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
--glass-bg: rgba(255, 255, 255, 0.95);
--glass-border: rgba(229, 231, 235, 0.8);
}
/* 暗色主题 */
[data-theme="dark"] {
--bg-primary: #111827;
--bg-secondary: #1f2937;
--bg-tertiary: #1a202c;
--bg-quaternary: #2d3748;
--bg-modal: rgba(0, 0, 0, 0.7);
/* 侧边栏颜色 */
--sidebar-bg: #1f2937;
--sidebar-hover: #374151;
--sidebar-active: #1e3a5f;
--sidebar-border: #374151;
--text-primary: #f9fafb;
--text-secondary: #e5e7eb;
--text-tertiary: #d1d5db;
--text-quaternary: #9ca3af;
--text-inverse: #ffffff;
--border-primary: #374151;
--border-secondary: #4b5563;
--border-focus: #60a5fa;
--accent-primary: #3b82f6;
--accent-secondary: #374151;
--accent-tertiary: #1f2937;
--primary-color: #60a5fa;
--primary-hover: #3b82f6;
--card-bg: #1f2937;
--border-color: #374151;
--success-bg: #064e3b;
--success-text: #6ee7b7;
--success-border: #059669;
--error-bg: #7f1d1d;
--error-text: #fca5a5;
--error-border: #dc2626;
--warning-bg: #78350f;
--warning-text: #fbbf24;
--warning-border: #f59e0b;
--shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.3);
--shadow-primary: 0 1px 3px 0 rgba(0, 0, 0, 0.3), 0 1px 2px 0 rgba(0, 0, 0, 0.2);
--shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.3), 0 2px 4px -1px rgba(0, 0, 0, 0.2);
--shadow-secondary: 0 10px 15px -3px rgba(0, 0, 0, 0.3), 0 4px 6px -2px rgba(0, 0, 0, 0.2);
--shadow-modal: 0 20px 25px -5px rgba(0, 0, 0, 0.4), 0 10px 10px -5px rgba(0, 0, 0, 0.3);
--glass-bg: rgba(31, 41, 59, 0.95);
--glass-border: rgba(75, 85, 99, 0.8);
}
/* 登录页面样式 */
.login-container {
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
background: var(--bg-primary);
padding: 20px;
}
/* 自动登录加载页面样式 */
.auto-login-content {
text-align: center;
padding: 20px;
}
.loading-spinner {
margin: 0 auto 20px;
width: 60px;
height: 60px;
}
.spinner {
width: 100%;
height: 100%;
border: 4px solid #e5e7eb;
border-top: 4px solid #3b82f6;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
.auto-login-content h2 {
color: var(--text-secondary);
margin-bottom: 10px;
font-size: 24px;
font-weight: 600;
}
.auto-login-content p {
color: var(--text-tertiary);
font-size: 16px;
line-height: 1.5;
}
/* 登录页面头部布局 */
.login-header-top {
display: flex;
flex-direction: column;
align-items: center;
margin-bottom: 30px;
position: relative;
}
.login-title {
width: 100%;
text-align: center;
margin-bottom: 30px;
}
/* 头部控制按钮组 */
.header-controls {
display: flex;
gap: 8px;
align-items: center;
height: 100%;
}
/* 登录页面的控制按钮组样式 */
.login-header-top .header-controls {
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
border-radius: 12px;
padding: 8px;
border: 1px solid rgba(255, 255, 255, 0.2);
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);
transition: all 0.3s ease;
margin-top: 8px;
/* 与标题拉开距离,避免遮挡 */
margin-bottom: 20px;
}
.login-header-top .header-controls:hover {
background: rgba(255, 255, 255, 0.15);
border-color: rgba(255, 255, 255, 0.3);
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.15);
transform: translateY(-1px);
}
[data-theme="dark"] .login-header-top .header-controls {
background: rgba(30, 41, 59, 0.8);
border: 1px solid rgba(100, 116, 139, 0.3);
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.3);
}
[data-theme="dark"] .login-header-top .header-controls:hover {
background: rgba(30, 41, 59, 0.9);
border-color: rgba(100, 116, 139, 0.5);
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.4);
}
.language-switcher,
.theme-switcher {
position: relative;
flex-shrink: 0;
display: flex;
align-items: center;
height: 100%;
}
.language-btn,
.theme-btn {
padding: 8px 14px;
font-size: 14px;
font-weight: 500;
border-radius: 6px;
background: var(--bg-tertiary);
border: 1px solid var(--border-primary);
color: var(--text-tertiary);
transition: all 0.3s ease;
white-space: nowrap;
backdrop-filter: blur(5px);
min-height: 36px;
display: inline-flex;
align-items: center;
justify-content: center;
cursor: pointer;
}
.top-navbar .language-btn,
.top-navbar .theme-btn {
padding: 6px 12px;
min-height: 36px;
}
.top-navbar-actions .btn,
.top-navbar .language-btn,
.top-navbar .theme-btn {
display: inline-flex;
align-items: center;
height: 36px;
}
/* 登录页面的按钮样式优化 */
.login-header-top .language-btn,
.login-header-top .theme-btn {
padding: 8px 12px;
font-size: 13px;
height: 36px;
min-width: 36px;
border-radius: 8px;
background: rgba(255, 255, 255, 0.9);
border: 1px solid rgba(255, 255, 255, 0.3);
color: #64748b;
backdrop-filter: blur(10px);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.login-header-top .language-btn:hover,
.login-header-top .theme-btn:hover {
background: rgba(255, 255, 255, 1);
border-color: rgba(100, 116, 139, 0.3);
color: #475569;
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
[data-theme="dark"] .login-header-top .language-btn,
[data-theme="dark"] .login-header-top .theme-btn {
background: rgba(30, 41, 59, 0.9);
border: 1px solid rgba(100, 116, 139, 0.3);
color: #94a3b8;
}
[data-theme="dark"] .login-header-top .language-btn:hover,
[data-theme="dark"] .login-header-top .theme-btn:hover {
background: rgba(51, 65, 85, 0.95);
border-color: rgba(100, 116, 139, 0.5);
color: #cbd5e1;
}
.language-btn:hover,
.theme-btn:hover {
background: var(--accent-secondary);
border-color: var(--border-secondary);
color: var(--text-secondary);
}
.language-btn i,
.theme-btn i {
margin-right: 6px;
}
.theme-btn.active {
background: var(--accent-primary);
color: var(--text-inverse);
}
.login-card {
background: var(--glass-bg);
backdrop-filter: blur(10px);
border-radius: 20px;
padding: 40px;
width: 100%;
max-width: 500px;
box-shadow: var(--shadow-secondary);
border: 1px solid var(--glass-border);
}
.login-header {
text-align: center;
margin-bottom: 25px;
}
.login-title {
display: flex;
align-items: center;
justify-content: center;
gap: 15px;
color: var(--text-secondary);
font-size: 1.8rem;
font-weight: 600;
margin-bottom: 0;
line-height: 1.2;
}
#login-logo {
height: 60px;
width: auto;
object-fit: contain;
border-radius: 6px;
padding: 2px;
background: #fff;
border: 1px solid rgba(15, 23, 42, 0.06);
box-shadow: 0 1px 2px rgba(15, 23, 42, 0.06);
}
.login-subtitle {
color: #64748b;
font-size: 1rem;
margin: 0;
text-align: center;
}
.login-body {
display: flex;
flex-direction: column;
gap: 24px;
}
.login-connection-info {
background: var(--bg-tertiary);
border: 1px solid var(--border-primary);
border-radius: 16px;
padding: 20px;
display: flex;
flex-direction: column;
gap: 12px;
}
[data-theme="dark"] .login-connection-info {
background: rgba(30, 41, 59, 0.7);
border-color: rgba(100, 116, 139, 0.3);
}
.connection-summary {
display: flex;
align-items: center;
gap: 16px;
color: var(--text-secondary);
}
.connection-summary i {
font-size: 24px;
color: var(--primary-color);
}
.connection-url {
font-size: 16px;
color: var(--text-secondary);
}
.connection-url-separator {
margin: 0 8px;
color: var(--text-tertiary);
}
#login-connection-url {
font-family: "Fira Code", "Consolas", "Courier New", monospace;
color: var(--text-primary);
word-break: break-all;
}
[data-theme="dark"] #login-connection-url {
color: var(--text-secondary);
}
.login-form {
display: flex;
flex-direction: column;
gap: 20px;
}
.login-form .form-group {
margin-bottom: 25px;
}
.login-form .form-group label {
display: block;
margin-bottom: 8px;
color: var(--text-secondary);
font-weight: 600;
font-size: 0.95rem;
}
.login-form .form-hint {
margin-top: 6px;
color: #64748b;
font-size: 12px;
}
.login-form .input-group {
display: flex;
gap: 10px;
align-items: center;
}
.login-form input[type="text"],
.login-form input[type="password"] {
flex: 1;
padding: 14px 16px;
border: 2px solid var(--border-primary);
border-radius: 10px;
font-size: 15px;
transition: all 0.3s ease;
background: var(--bg-secondary);
color: var(--text-primary);
}
.login-form input:focus {
outline: none;
border-color: var(--border-focus);
box-shadow: 0 0 0 3px var(--border-primary);
}
.login-form .btn-secondary {
padding: 14px 16px;
border: 2px solid #e2e8f0;
background: #f8fafc;
color: #64748b;
border-radius: 10px;
transition: all 0.3s ease;
}
.login-form .btn-secondary:hover {
background: #e2e8f0;
border-color: #cbd5e0;
}
.form-actions {
margin-top: 30px;
text-align: center;
}
.login-btn {
width: 100%;
padding: 16px 24px;
font-size: 16px;
font-weight: 600;
border-radius: 12px;
background: linear-gradient(135deg, #475569, #334155);
color: white;
border: none;
cursor: pointer;
transition: all 0.3s ease;
display: flex;
align-items: center;
justify-content: center;
gap: 10px;
}
.login-btn:hover {
transform: translateY(-2px);
box-shadow: 0 8px 25px rgba(51, 65, 85, 0.4);
}
.login-btn:disabled {
opacity: 0.7;
cursor: not-allowed;
transform: none;
}
.login-error {
background: var(--error-bg);
border: 1px solid var(--error-border);
color: var(--error-text);
padding: 12px 16px;
border-radius: 8px;
margin-top: 20px;
display: flex;
align-items: center;
gap: 8px;
font-size: 14px;
font-weight: 500;
}
.login-error i {
color: var(--error-text);
}
/* 响应式设计 - 登录页面 */
@media (max-width: 640px) {
.login-card {
padding: 30px 20px;
margin: 10px;
max-width: 100%;
}
.login-header-top {
flex-direction: column;
gap: 20px;
align-items: center;
margin-bottom: 30px;
}
.header-controls {
flex-direction: row;
justify-content: center;
gap: 8px;
margin-bottom: 10px;
/* 在小屏幕上给控制按钮组添加下边距 */
}
/* 登录页面小屏幕优化 */
.login-header-top .header-controls {
margin: 8px auto 0 auto;
/* 顶部留白,避免与标题拥挤 */
background: rgba(255, 255, 255, 0.08);
padding: 6px;
border-radius: 10px;
}
.login-header-top .language-btn,
.login-header-top .theme-btn {
padding: 6px 10px;
font-size: 12px;
height: 32px;
min-width: 32px;
}
.language-btn,
.theme-btn {
padding: 8px 16px;
font-size: 13px;
height: 36px;
/* 在小屏幕上稍微减小高度 */
}
.login-title {
font-size: 1.5rem;
flex-direction: column;
gap: 10px;
text-align: center;
justify-content: center;
margin-bottom: 25px;
}
#login-logo {
height: 50px;
}
.login-form .input-group {
flex-direction: column;
align-items: stretch;
}
.login-form .btn-secondary {
width: 100%;
margin-top: 10px;
}
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', sans-serif;
background: var(--bg-primary);
min-height: 100vh;
color: var(--text-primary);
transition: background-color 0.3s ease, color 0.3s ease;
overflow-x: hidden;
}
/* 侧边栏样式 */
.layout {
display: grid;
grid-template-columns: 240px 1fr;
min-height: calc(100vh - var(--navbar-height, 69px));
width: 100%;
transition: grid-template-columns 0.3s ease;
}
.layout.sidebar-collapsed {
grid-template-columns: 64px 1fr;
}
.sidebar {
width: 240px;
background: var(--sidebar-bg);
border-right: 1px solid var(--sidebar-border);
height: calc(100vh - var(--navbar-height, 69px));
position: sticky;
top: var(--navbar-height, 69px);
overflow-y: auto;
overflow-x: hidden;
display: flex;
flex-direction: column;
z-index: 5;
box-shadow: var(--shadow-sm);
transition: all 0.3s ease;
}
.sidebar.collapsed {
width: 64px;
}
.sidebar.collapsed .nav-item span {
opacity: 0;
width: 0;
overflow: hidden;
white-space: nowrap;
}
.sidebar.collapsed .nav-item {
justify-content: center;
padding: 10px 0;
}
.sidebar.collapsed .nav-item i {
margin-right: 0;
}
/* 侧边栏切换按钮(桌面端-在顶栏) */
.sidebar-toggle-btn-desktop {
display: none;
width: 40px;
height: 40px;
border: none;
background: transparent;
color: var(--text-primary);
border-radius: 8px;
cursor: pointer;
align-items: center;
justify-content: center;
font-size: 18px;
transition: all 0.2s ease;
padding: 0;
}
.sidebar-toggle-btn-desktop:hover {
background: var(--bg-tertiary);
color: var(--primary-color);
}
.sidebar-toggle-btn-desktop i {
transition: transform 0.3s ease;
}
.layout.sidebar-collapsed .sidebar-toggle-btn-desktop i {
transform: rotate(90deg);
}
/* 在大屏幕上显示桌面端切换按钮 */
@media (min-width: 1025px) {
.sidebar-toggle-btn-desktop {
display: flex;
}
}
/* 侧边栏品牌区域 */
.sidebar-brand {
padding: 24px 20px;
border-bottom: 1px solid var(--sidebar-border);
display: flex;
align-items: center;
gap: 12px;
background: var(--sidebar-bg);
}
.sidebar-brand-logo {
width: 36px;
height: 36px;
object-fit: contain;
border-radius: 8px;
}
.sidebar-brand-text {
font-size: 18px;
font-weight: 600;
color: var(--text-primary);
line-height: 1.2;
}
/* 侧边栏导航菜单 */
.sidebar .nav-menu {
flex: 1;
padding: 16px 12px;
list-style: none;
}
.sidebar .nav-menu li {
margin-bottom: 4px;
position: relative;
}
.sidebar .nav-item {
display: flex;
align-items: center;
padding: 10px 14px;
color: var(--text-secondary);
text-decoration: none;
border-radius: 8px;
transition: all 0.2s ease;
font-size: 14px;
font-weight: 500;
cursor: pointer;
position: relative;
}
.sidebar .nav-item:hover {
background: var(--sidebar-hover);
color: var(--text-primary);
}
.sidebar .nav-item.active {
background: var(--sidebar-active);
color: var(--primary-color);
}
.sidebar .nav-item i {
margin-right: 12px;
width: 18px;
font-size: 16px;
text-align: center;
transition: margin 0.3s ease;
}
/* 收起状态时的工具提示 */
.sidebar.collapsed .nav-menu li:hover::after {
content: attr(data-tooltip);
position: absolute;
left: 68px;
top: 50%;
transform: translateY(-50%);
background: var(--bg-secondary);
color: var(--text-primary);
padding: 8px 12px;
border-radius: 6px;
font-size: 13px;
font-weight: 500;
white-space: nowrap;
box-shadow: var(--shadow-md);
border: 1px solid var(--border-primary);
z-index: 1000;
pointer-events: none;
opacity: 0;
animation: tooltipFadeIn 0.2s ease forwards;
}
@keyframes tooltipFadeIn {
to {
opacity: 1;
}
}
/* 主内容区域包装器 */
.main-wrapper {
display: flex;
flex-direction: column;
background: var(--bg-primary);
min-height: 100%;
}
/* 顶部导航栏 */
.top-navbar {
background: var(--bg-secondary);
border-bottom: 1px solid var(--border-primary);
padding: 16px 24px;
display: flex;
justify-content: space-between;
align-items: center;
position: sticky;
top: 0;
z-index: 200;
box-shadow: var(--shadow-sm);
height: var(--navbar-height, 69px);
min-height: var(--navbar-height, 69px);
box-sizing: border-box;
}
.top-navbar-left {
display: flex;
align-items: center;
gap: 12px;
flex: 1;
}
.top-navbar-brand {
display: flex;
align-items: center;
gap: 12px;
max-width: max-content;
}
.top-navbar-brand-logo {
width: 32px;
height: 32px;
object-fit: contain;
border-radius: 8px;
}
.top-navbar-brand-text {
font-size: 18px;
font-weight: 600;
color: var(--text-primary);
overflow: hidden;
text-overflow: ellipsis;
}
.top-navbar-actions {
display: flex;
align-items: center;
gap: 10px;
margin-left: auto;
}
.top-navbar-actions>* {
display: inline-flex;
align-items: center;
height: 36px;
}
.top-navbar .header-controls {
display: inline-flex;
align-items: center;
gap: 8px;
height: 100%;
}
.top-navbar .language-btn,
.top-navbar .theme-btn {
width: 36px;
height: 36px;
min-height: 36px;
padding: 0;
justify-content: center;
}
.top-navbar-actions .btn {
height: 36px;
min-height: 36px;
}
@media (max-width: 768px) {
.top-navbar-actions {
flex-wrap: wrap;
justify-content: flex-end;
gap: 8px;
}
.top-navbar .header-controls {
width: 100%;
justify-content: flex-end;
order: 99;
gap: 8px;
}
.top-navbar-actions>* {
height: 34px;
min-height: 34px;
}
.top-navbar .language-btn,
.top-navbar .theme-btn {
width: 34px;
height: 34px;
}
.top-navbar-actions .btn {
height: 34px;
min-height: 34px;
padding: 0 10px;
}
.top-navbar-actions .btn span {
display: none;
}
}
.top-navbar .language-btn i,
.top-navbar .theme-btn i {
margin: 0;
}
.header-actions {
display: flex;
gap: 10px;
align-items: center;
}
.header-actions .btn {
white-space: nowrap;
}
@media (max-width: 768px) {
.header-actions .btn span {
display: none;
}
.header-actions .btn {
padding: 6px 10px;
}
}
/* 连接信息样式 */
.connection-info {
display: grid;
gap: 16px;
}
.info-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 14px 0;
border-bottom: 1px solid var(--border-primary);
}
.info-item:last-child {
border-bottom: none;
padding-bottom: 0;
}
.info-label {
display: flex;
align-items: center;
gap: 10px;
color: var(--text-primary);
font-weight: 500;
font-size: 14px;
}
.info-label i {
color: var(--text-tertiary);
width: 18px;
font-size: 16px;
}
.info-value {
color: var(--text-secondary);
font-family: 'Monaco', 'Menlo', 'Consolas', 'Ubuntu Mono', monospace;
font-size: 13px;
background: var(--bg-tertiary);
padding: 6px 12px;
border-radius: 6px;
border: 1px solid var(--border-primary);
max-width: 350px;
word-break: break-all;
}
.status-indicator {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 6px 14px;
border-radius: 16px;
font-size: 13px;
font-weight: 500;
}
.status-indicator.connected {
background: var(--success-bg);
color: var(--success-text);
}
.status-indicator.disconnected {
background: var(--error-bg);
color: var(--error-text);
}
.status-indicator.connecting {
background: var(--warning-bg);
color: var(--warning-text);
}
/* 响应式设计 - 连接信息 */
@media (max-width: 768px) {
.info-item {
flex-direction: column;
align-items: flex-start;
gap: 8px;
}
.info-value {
max-width: 100%;
width: 100%;
}
}
/* 主内容区域 */
.main-content {
flex: 1;
padding: 24px 32px;
max-width: 1400px;
width: 100%;
margin: 0 auto;
}
/* 内容区域 */
.content-area {
flex: 1;
width: 100%;
}
.content-section {
display: none;
animation: fadeIn 0.3s ease;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.content-section.active {
display: block;
}
.content-section h2 {
color: var(--text-primary);
margin-bottom: 24px;
font-size: 24px;
font-weight: 600;
}
/* 卡片样式 */
.card {
background: var(--bg-secondary);
border: 1px solid var(--border-primary);
border-radius: 12px;
margin-bottom: 20px;
box-shadow: var(--shadow-sm);
overflow: hidden;
transition: box-shadow 0.2s ease;
}
.card:hover {
box-shadow: var(--shadow-md);
}
.card-header {
background: var(--bg-secondary);
padding: 18px 24px;
border-bottom: 1px solid var(--border-primary);
display: flex;
justify-content: space-between;
align-items: center;
}
.card-header h3 {
color: var(--text-primary);
font-size: 16px;
font-weight: 600;
display: flex;
align-items: center;
}
.card-header h3 i {
color: var(--text-tertiary);
margin-right: 10px;
font-size: 18px;
}
.card-content {
padding: 24px;
}
/* 按钮样式 */
.btn {
padding: 8px 16px;
border: 1px solid transparent;
border-radius: 6px;
cursor: pointer;
font-size: 14px;
font-weight: 500;
transition: all 0.2s ease;
display: inline-flex;
align-items: center;
justify-content: center;
gap: 6px;
text-decoration: none;
min-height: 36px;
white-space: nowrap;
outline: none;
}
.btn-primary {
background: var(--primary-color);
color: white;
border-color: var(--primary-color);
}
.btn-primary:hover {
background: var(--primary-hover);
border-color: var(--primary-hover);
box-shadow: 0 2px 6px rgba(59, 130, 246, 0.3);
}
.btn-secondary {
background: var(--bg-secondary);
color: var(--text-secondary);
border-color: var(--border-primary);
}
.btn-secondary:hover {
background: var(--bg-tertiary);
border-color: var(--border-secondary);
color: var(--text-primary);
}
.btn-danger {
background: #ef4444;
color: white;
border-color: #ef4444;
}
.btn-danger:hover {
background: #dc2626;
border-color: #dc2626;
box-shadow: 0 2px 6px rgba(239, 68, 68, 0.3);
}
.btn-success {
background: #10b981;
color: white;
border-color: #10b981;
}
.btn-success:hover {
background: #059669;
border-color: #059669;
box-shadow: 0 2px 6px rgba(16, 185, 129, 0.3);
}
.btn:disabled {
opacity: 0.5;
cursor: not-allowed;
transform: none !important;
}
.btn i {
font-size: 14px;
}
/* 表单元素 */
.form-group {
margin-bottom: 18px;
}
.form-group label {
display: block;
margin-bottom: 8px;
color: var(--text-primary);
font-weight: 500;
font-size: 14px;
}
.form-hint {
font-size: 13px;
color: var(--text-tertiary);
margin: 6px 0 0 0;
line-height: 1.5;
}
.input-group {
display: flex;
gap: 10px;
align-items: center;
}
input[type="text"],
input[type="password"],
input[type="number"],
input[type="url"],
textarea,
select {
flex: 1;
padding: 10px 14px;
border: 1px solid var(--border-primary);
border-radius: 6px;
font-size: 14px;
transition: all 0.2s ease;
background: var(--bg-secondary);
color: var(--text-primary);
font-family: inherit;
}
input:hover,
textarea:hover,
select:hover {
border-color: var(--border-secondary);
}
input:focus,
textarea:focus,
select:focus {
outline: none;
border-color: var(--primary-color);
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
}
input::placeholder,
textarea::placeholder {
color: var(--text-quaternary);
}
/* 切换开关 */
.toggle-group {
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 16px;
}
.toggle-switch {
position: relative;
display: inline-block;
width: 44px;
height: 24px;
}
.toggle-switch input {
opacity: 0;
width: 0;
height: 0;
}
.slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #d1d5db;
transition: .3s;
border-radius: 24px;
}
.slider:before {
position: absolute;
content: "";
height: 18px;
width: 18px;
left: 3px;
bottom: 3px;
background-color: white;
transition: .3s;
border-radius: 50%;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
}
input:checked+.slider {
background: var(--primary-color);
}
input:checked+.slider:before {
transform: translateX(20px);
}
.toggle-label {
color: var(--text-primary);
font-weight: 500;
font-size: 14px;
}
/* 列表样式 */
.key-list,
.provider-list,
.file-list {
max-height: 400px;
overflow-y: auto;
}
.key-item,
.provider-item,
.file-item {
background: var(--bg-quaternary);
border: 1px solid var(--border-primary);
border-radius: 8px;
padding: 15px;
margin-bottom: 10px;
display: flex;
justify-content: space-between;
align-items: center;
transition: all 0.3s ease;
}
.key-item:hover,
.provider-item:hover,
.file-item:hover {
background: var(--bg-tertiary);
border-color: var(--border-secondary);
transform: translateY(-1px);
}
.item-content {
flex: 1;
}
.item-actions {
display: flex;
gap: 8px;
}
.item-title {
font-weight: 600;
color: var(--text-secondary);
margin-bottom: 4px;
}
.item-subtitle {
color: var(--text-tertiary);
font-size: 0.9rem;
}
.provider-item .provider-models {
margin-top: 8px;
display: flex;
flex-wrap: wrap;
}
.provider-model-tag {
display: inline-flex;
align-items: center;
gap: 4px;
background: var(--bg-quinary);
color: var(--text-secondary);
border: 1px solid var(--border-secondary);
border-radius: 14px;
padding: 4px 10px;
margin-right: 6px;
margin-top: 6px;
font-size: 0.85rem;
}
.provider-model-tag .model-name {
font-weight: 600;
}
.provider-model-tag .model-alias {
color: var(--text-tertiary);
font-style: italic;
}
.item-value {
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
background: var(--bg-tertiary);
padding: 4px 8px;
border-radius: 4px;
font-size: 0.85rem;
color: var(--text-secondary);
word-break: break-all;
}
/* 状态信息 */
.status-info {
background: var(--bg-quaternary);
border-radius: 8px;
padding: 20px;
}
.status-item {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 15px;
padding-bottom: 15px;
border-bottom: 1px solid var(--border-primary);
}
.status-item:last-child {
margin-bottom: 0;
padding-bottom: 0;
border-bottom: none;
}
.status-label {
font-weight: 600;
color: var(--text-secondary);
}
.status-value {
color: var(--text-tertiary);
}
/* 模态框 */
.modal {
display: none;
position: fixed;
z-index: 1000;
left: 0;
top: 0;
width: 100%;
height: 100%;
background-color: var(--bg-modal);
backdrop-filter: blur(5px);
}
.modal-content {
background-color: var(--bg-secondary);
margin: 4% auto;
padding: 0;
border-radius: 15px;
width: 90%;
max-width: 550px;
box-shadow: var(--shadow-modal);
animation: modalSlideIn 0.3s ease;
position: relative;
}
@keyframes modalSlideIn {
from {
opacity: 0;
transform: translateY(-30px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.close {
color: var(--text-tertiary);
position: absolute;
top: 15px;
right: 20px;
font-size: 28px;
font-weight: bold;
cursor: pointer;
z-index: 1001;
width: 32px;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
transition: all 0.3s ease;
}
.close:hover,
.close:focus {
color: var(--text-secondary);
background-color: var(--bg-tertiary);
}
#modal-body {
padding: 35px 30px 30px 30px;
}
/* 模态框标题样式 */
#modal-body h3 {
color: var(--text-secondary);
font-size: 1.25rem;
font-weight: 600;
margin: 0 0 20px 0;
text-align: center;
border-bottom: 2px solid var(--border-primary);
padding-bottom: 12px;
}
/* 模态框表单组 */
#modal-body .form-group {
margin-bottom: 16px;
}
#modal-body .form-group label {
display: block;
margin-bottom: 6px;
color: var(--text-secondary);
font-weight: 600;
font-size: 14px;
}
#modal-body .form-group input,
#modal-body .form-group textarea {
width: 100%;
padding: 12px 16px;
border: 2px solid var(--border-primary);
border-radius: 8px;
font-size: 14px;
transition: all 0.3s ease;
background: var(--bg-tertiary);
color: var(--text-primary);
font-family: inherit;
}
#modal-body .form-group input:focus,
#modal-body .form-group textarea:focus {
outline: none;
border-color: var(--border-focus);
box-shadow: 0 0 0 3px var(--border-primary);
}
#modal-body .form-group textarea {
resize: vertical;
min-height: 80px;
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
font-size: 13px;
}
/* 模态框按钮组 */
#modal-body .modal-actions {
display: flex;
justify-content: flex-end;
gap: 12px;
margin-top: 24px;
padding-top: 18px;
border-top: 1px solid var(--border-primary);
}
#modal-body .modal-actions .btn {
min-width: 80px;
padding: 10px 20px;
}
/* 通知 */
.notification {
position: fixed;
top: 20px;
right: 20px;
padding: 15px 20px;
border-radius: 8px;
color: white;
font-weight: 500;
z-index: 1001;
transform: translateX(400px);
transition: all 0.3s ease;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2);
}
.notification.show {
transform: translateX(0);
}
.notification.success {
background: linear-gradient(135deg, #68d391, #38a169);
}
.notification.error {
background: linear-gradient(135deg, #fc8181, #e53e3e);
}
.notification.info {
background: linear-gradient(135deg, #63b3ed, #3182ce);
}
/* 响应式设计 */
@media (max-width: 1024px) {
.layout {
position: relative;
grid-template-columns: 1fr;
min-height: calc(100vh - var(--navbar-height, 69px));
}
.sidebar {
position: fixed;
left: 0;
top: var(--navbar-height, 69px);
height: calc(100vh - var(--navbar-height, 69px));
transform: translateX(-100%);
z-index: 150;
box-shadow: var(--shadow-sm);
background: var(--sidebar-bg);
width: 240px !important;
}
.sidebar.mobile-open {
transform: translateX(0);
box-shadow: var(--shadow-secondary);
}
/* 移动端强制恢复侧栏展开状态 */
.sidebar.collapsed {
width: 240px !important;
}
.sidebar.collapsed .nav-item span {
opacity: 1 !important;
width: auto !important;
overflow: visible !important;
}
.sidebar.collapsed .nav-item {
justify-content: flex-start !important;
padding: 10px 14px !important;
}
.sidebar.collapsed .nav-item i {
margin-right: 12px !important;
}
.main-wrapper {
margin-left: 0;
width: 100%;
min-height: calc(100vh - var(--navbar-height, 69px));
overflow: hidden;
}
.main-content {
padding: 20px 16px;
}
}
@media (max-width: 768px) {
.top-navbar {
padding: 12px 16px;
}
.top-navbar-title {
font-size: 18px;
}
.top-navbar-actions {
gap: 6px;
flex-wrap: wrap;
justify-content: flex-end;
}
.top-navbar-actions>* {
height: 34px;
min-height: 34px;
}
.top-navbar .header-controls {
height: 34px;
gap: 6px;
}
.top-navbar-actions .btn,
.top-navbar .language-btn,
.top-navbar .theme-btn {
height: 34px;
min-height: 34px;
padding: 0 10px;
}
.top-navbar-actions .btn span {
display: none;
}
.btn {
padding: 6px 12px;
font-size: 13px;
}
.card {
border-radius: 8px;
}
.card-header {
padding: 16px 20px;
}
.card-content {
padding: 20px;
}
.input-group {
flex-direction: column;
align-items: stretch;
}
.card-header {
flex-direction: column;
gap: 12px;
align-items: flex-start;
}
.card-header .header-actions {
width: 100%;
justify-content: flex-start;
}
/* 模态框响应式 */
.modal-content {
margin: 5% auto;
width: 95%;
max-width: none;
}
#modal-body {
padding: 40px 20px 20px 20px;
}
#modal-body h3 {
font-size: 18px;
margin-bottom: 16px;
}
#modal-body .modal-actions {
flex-direction: column-reverse;
gap: 10px;
}
#modal-body .modal-actions .btn {
width: 100%;
margin: 0;
}
.content-section h2 {
font-size: 20px;
margin-bottom: 20px;
}
}
/* 移动端侧边栏遮罩 */
.sidebar-overlay {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
z-index: 120;
}
.sidebar-overlay.active {
display: block;
}
/* 移动端菜单按钮 */
.mobile-menu-btn {
display: none;
width: 40px;
height: 40px;
background: transparent;
border: none;
padding: 0;
cursor: pointer;
color: var(--text-primary);
font-size: 20px;
border-radius: 8px;
align-items: center;
justify-content: center;
transition: all 0.2s ease;
}
.mobile-menu-btn:hover {
background: var(--bg-tertiary);
color: var(--primary-color);
}
@media (max-width: 1024px) {
.mobile-menu-btn {
display: flex;
}
}
/* 加载动画 */
.loading {
display: inline-block;
width: 20px;
height: 20px;
border: 3px solid rgba(255, 255, 255, 0.3);
border-radius: 50%;
border-top-color: #fff;
animation: spin 1s ease-in-out infinite;
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
/* 空状态 */
.empty-state {
text-align: center;
padding: 40px 20px;
color: var(--text-tertiary);
}
.empty-state i {
font-size: 48px;
margin-bottom: 16px;
color: var(--border-secondary);
}
.empty-state h3 {
margin-bottom: 8px;
color: var(--text-secondary);
}
/* 滚动条样式 */
::-webkit-scrollbar {
width: 8px;
height: 8px;
}
::-webkit-scrollbar-track {
background: var(--bg-tertiary);
border-radius: 4px;
}
::-webkit-scrollbar-thumb {
background: var(--border-secondary);
border-radius: 4px;
transition: background 0.2s ease;
}
::-webkit-scrollbar-thumb:hover {
background: var(--text-quaternary);
}
/* 侧边栏滚动条 */
.sidebar::-webkit-scrollbar {
width: 6px;
}
.sidebar::-webkit-scrollbar-track {
background: transparent;
}
.sidebar::-webkit-scrollbar-thumb {
background: rgba(0, 0, 0, 0.1);
border-radius: 3px;
}
.sidebar::-webkit-scrollbar-thumb:hover {
background: rgba(0, 0, 0, 0.2);
}
[data-theme="dark"] .sidebar::-webkit-scrollbar-thumb {
background: rgba(255, 255, 255, 0.1);
}
[data-theme="dark"] .sidebar::-webkit-scrollbar-thumb:hover {
background: rgba(255, 255, 255, 0.2);
}
/* 连接状态指示器 */
.connection-indicator {
display: inline-block;
width: 10px;
height: 10px;
border-radius: 50%;
margin-right: 8px;
}
.connection-indicator.connected {
background-color: #68d391;
box-shadow: 0 0 8px rgba(104, 211, 145, 0.6);
}
.connection-indicator.disconnected {
background-color: #fc8181;
box-shadow: 0 0 8px rgba(252, 129, 129, 0.6);
}
.connection-indicator.connecting {
background-color: #fbb040;
animation: pulse 1.5s infinite;
}
@keyframes pulse {
0% {
opacity: 1;
}
50% {
opacity: 0.5;
}
100% {
opacity: 1;
}
}
/* Gemini Web Token 模态框样式 */
.gemini-web-form .form-group {
margin-bottom: 20px;
}
.gemini-web-form .form-group label {
display: block;
margin-bottom: 8px;
color: var(--text-secondary);
font-weight: 600;
font-size: 14px;
}
.gemini-web-form .form-group input {
width: 100%;
padding: 12px 16px;
border: 2px solid var(--border-primary);
border-radius: 8px;
font-size: 14px;
transition: all 0.3s ease;
background: var(--bg-tertiary);
color: var(--text-primary);
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
}
.gemini-web-form .form-group input:focus {
outline: none;
border-color: var(--border-focus);
box-shadow: 0 0 0 3px var(--border-primary);
}
.gemini-web-form .form-hint {
margin-top: 6px;
color: var(--text-tertiary);
font-size: 12px;
line-height: 1.4;
}
/* 使用统计样式 */
.stats-overview {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 20px;
margin-bottom: 30px;
}
.stat-card {
background: var(--card-bg);
border: 1px solid var(--border-color);
border-radius: 12px;
padding: 24px;
display: flex;
align-items: center;
gap: 16px;
transition: all 0.2s ease;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.stat-card:hover {
border-color: var(--border-primary);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
transform: translateY(-2px);
}
.stat-icon {
width: 50px;
height: 50px;
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
background: var(--primary-color);
color: white;
font-size: 20px;
flex-shrink: 0;
}
.stat-icon.success {
background: #10b981;
}
.stat-icon.error {
background: #ef4444;
}
.stat-content {
flex: 1;
}
.stat-number {
font-size: 28px;
font-weight: 700;
color: var(--text-primary);
line-height: 1;
margin-bottom: 4px;
}
.stat-label {
font-size: 14px;
color: var(--text-secondary);
font-weight: 500;
}
.charts-container {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px;
margin-bottom: 30px;
}
@media (max-width: 1200px) {
.charts-container {
grid-template-columns: 1fr;
}
}
.chart-card {
min-height: 400px;
}
.chart-container {
position: relative;
height: 300px;
width: 100%;
}
.chart-controls {
display: flex;
gap: 8px;
}
.btn.btn-small {
padding: 6px 12px;
font-size: 12px;
border-radius: 6px;
border: 1px solid var(--border-color);
background: var(--card-bg);
color: var(--text-secondary);
cursor: pointer;
transition: all 0.2s ease;
}
.btn.btn-small:hover {
border-color: var(--border-primary);
color: var(--text-primary);
}
.btn.btn-small.active {
background: var(--primary-color);
border-color: var(--primary-color);
color: white;
}
.api-stats-table {
overflow-x: auto;
}
.stats-table {
width: 100%;
border-collapse: collapse;
margin-top: 16px;
}
.stats-table th,
.stats-table td {
padding: 12px 16px;
text-align: left;
border-bottom: 1px solid var(--border-color);
}
.stats-table th {
background: var(--bg-secondary);
font-weight: 600;
color: var(--text-primary);
font-size: 14px;
}
.stats-table td {
color: var(--text-secondary);
font-size: 14px;
}
.stats-table tr:hover {
background: var(--bg-secondary);
}
.model-details {
margin-top: 8px;
padding: 8px 12px;
background: var(--bg-tertiary);
border-radius: 6px;
font-size: 12px;
}
.model-item {
display: flex;
justify-content: space-between;
margin-bottom: 4px;
color: var(--text-tertiary);
}
.model-name {
font-weight: 500;
color: var(--text-secondary);
}
.loading-placeholder {
display: flex;
align-items: center;
justify-content: center;
height: 100px;
color: var(--text-tertiary);
font-size: 14px;
}
.no-data-message {
text-align: center;
color: var(--text-tertiary);
font-style: italic;
padding: 40px;
}
/* 暗色主题适配 */
[data-theme="dark"] .stat-card {
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
}
[data-theme="dark"] .stat-card:hover {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4);
}
[data-theme="dark"] .btn.btn-small {
background: var(--bg-tertiary);
}
/* 版本信息样式 */
.version-footer {
margin-top: 40px;
padding: 24px 0;
border-top: 1px solid var(--border-primary);
}
.version-info {
text-align: center;
font-size: 13px;
color: var(--text-tertiary);
}
.version-info .separator {
margin: 0 12px;
color: var(--text-quaternary);
}
.connection-reset-btn {
display: inline-flex;
align-items: center;
gap: 6px;
white-space: nowrap;
}
.connection-reset-btn i {
margin: 0;
}
.connection-reset-btn span {
font-size: 13px;
}
[data-theme="dark"] .connection-reset-btn {
background: rgba(30, 41, 59, 0.9);
border-color: rgba(100, 116, 139, 0.4);
color: #cbd5e1;
}
[data-theme="dark"] .connection-reset-btn:hover {
background: rgba(51, 65, 85, 0.95);
border-color: rgba(100, 116, 139, 0.6);
}
.model-input-list {
display: flex;
flex-direction: column;
gap: 8px;
}
.model-input-row .input-group {
display: flex;
gap: 8px;
}
.model-input-row .model-name-input,
.model-input-row .model-alias-input {
flex: 1;
}
.model-input-row .model-alias-input {
max-width: 220px;
}
.model-input-row .model-remove-btn {
align-self: center;
}
/* Codex OAuth 样式 */
#codex-oauth-content {
transition: all 0.3s ease;
}
#codex-oauth-url {
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
font-size: 13px;
background: var(--bg-tertiary);
border: 1px solid var(--border-primary);
color: var(--text-secondary);
}
#codex-oauth-url:focus {
border-color: var(--border-focus);
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
}
#codex-oauth-status {
font-weight: 500;
padding: 8px 12px;
border-radius: 6px;
background: var(--bg-tertiary);
border: 1px solid var(--border-primary);
transition: all 0.3s ease;
}
#codex-oauth-status.success {
background: var(--success-bg);
border-color: var(--success-border);
color: var(--success-text);
}
#codex-oauth-status.error {
background: var(--error-bg);
border-color: var(--error-border);
color: var(--error-text);
}
#codex-oauth-status.warning {
background: var(--warning-bg);
border-color: var(--warning-border);
color: var(--warning-text);
}
/* Codex OAuth 按钮样式 */
#codex-open-link,
#codex-copy-link {
min-width: 100px;
white-space: nowrap;
}
#codex-open-link {
background: var(--primary-color);
border-color: var(--primary-color);
}
#codex-open-link:hover {
background: var(--primary-hover);
border-color: var(--primary-hover);
}
#codex-copy-link {
background: var(--bg-secondary);
border-color: var(--border-primary);
color: var(--text-secondary);
}
#codex-copy-link:hover {
background: var(--bg-tertiary);
border-color: var(--border-secondary);
color: var(--text-primary);
}
/* 响应式设计 - Codex OAuth */
@media (max-width: 768px) {
#codex-oauth-content .input-group {
flex-direction: column;
align-items: stretch;
}
#codex-open-link,
#codex-copy-link {
width: 100%;
margin-top: 8px;
}
#codex-oauth-url {
font-size: 12px;
}
}
/* Anthropic OAuth 样式 */
#anthropic-oauth-content {
transition: all 0.3s ease;
}
#anthropic-oauth-url {
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
font-size: 13px;
background: var(--bg-tertiary);
border: 1px solid var(--border-primary);
color: var(--text-secondary);
}
#anthropic-oauth-url:focus {
border-color: var(--border-focus);
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
}
#anthropic-oauth-status {
font-weight: 500;
padding: 8px 12px;
border-radius: 6px;
background: var(--bg-tertiary);
border: 1px solid var(--border-primary);
transition: all 0.3s ease;
}
#anthropic-oauth-status.success {
background: var(--success-bg);
border-color: var(--success-border);
color: var(--success-text);
}
#anthropic-oauth-status.error {
background: var(--error-bg);
border-color: var(--error-border);
color: var(--error-text);
}
#anthropic-oauth-status.warning {
background: var(--warning-bg);
border-color: var(--warning-border);
color: var(--warning-text);
}
/* Anthropic OAuth 按钮样式 */
#anthropic-open-link,
#anthropic-copy-link {
min-width: 100px;
white-space: nowrap;
}
#anthropic-open-link {
background: var(--primary-color);
border-color: var(--primary-color);
}
#anthropic-open-link:hover {
background: var(--primary-hover);
border-color: var(--primary-hover);
}
#anthropic-copy-link {
background: var(--bg-secondary);
border-color: var(--border-primary);
color: var(--text-secondary);
}
#anthropic-copy-link:hover {
background: var(--bg-tertiary);
border-color: var(--border-secondary);
color: var(--text-primary);
}
/* 响应式设计 - Anthropic OAuth */
@media (max-width: 768px) {
#anthropic-oauth-content .input-group {
flex-direction: column;
align-items: stretch;
}
#anthropic-open-link,
#anthropic-copy-link {
width: 100%;
margin-top: 8px;
}
#anthropic-oauth-url {
font-size: 12px;
}
}
/* Gemini CLI OAuth 样式 */
#gemini-cli-oauth-content {
transition: all 0.3s ease;
}
#gemini-cli-oauth-url {
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
font-size: 13px;
background: var(--bg-tertiary);
border: 1px solid var(--border-primary);
color: var(--text-secondary);
}
#gemini-cli-oauth-url:focus {
border-color: var(--border-focus);
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
}
#gemini-cli-oauth-status {
font-weight: 500;
padding: 8px 12px;
border-radius: 6px;
background: var(--bg-tertiary);
border: 1px solid var(--border-primary);
transition: all 0.3s ease;
}
#gemini-cli-oauth-status.success {
background: var(--success-bg);
border-color: var(--success-border);
color: var(--success-text);
}
#gemini-cli-oauth-status.error {
background: var(--error-bg);
border-color: var(--error-border);
color: var(--error-text);
}
#gemini-cli-oauth-status.warning {
background: var(--warning-bg);
border-color: var(--warning-border);
color: var(--warning-text);
}
/* Gemini CLI OAuth 按钮样式 */
#gemini-cli-open-link,
#gemini-cli-copy-link {
min-width: 100px;
white-space: nowrap;
}
#gemini-cli-open-link {
background: var(--primary-color);
border-color: var(--primary-color);
}
#gemini-cli-open-link:hover {
background: var(--primary-hover);
border-color: var(--primary-hover);
}
#gemini-cli-copy-link {
background: var(--bg-secondary);
border-color: var(--border-primary);
color: var(--text-secondary);
}
#gemini-cli-copy-link:hover {
background: var(--bg-tertiary);
border-color: var(--border-secondary);
color: var(--text-primary);
}
/* Gemini CLI 项目 ID 输入框样式 */
#gemini-cli-project-id {
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
font-size: 13px;
background: var(--bg-tertiary);
border: 1px solid var(--border-primary);
color: var(--text-secondary);
}
#gemini-cli-project-id:focus {
border-color: var(--border-focus);
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
}
/* 响应式设计 - Gemini CLI OAuth */
@media (max-width: 768px) {
#gemini-cli-oauth-content .input-group {
flex-direction: column;
align-items: stretch;
}
#gemini-cli-open-link,
#gemini-cli-copy-link {
width: 100%;
margin-top: 8px;
}
#gemini-cli-oauth-url {
font-size: 12px;
}
#gemini-cli-project-id {
font-size: 12px;
}
}
/* Qwen OAuth 样式 */
#qwen-oauth-content {
transition: all 0.3s ease;
}
#qwen-oauth-url {
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
font-size: 13px;
background: var(--bg-tertiary);
border: 1px solid var(--border-primary);
color: var(--text-secondary);
}
#qwen-oauth-url:focus {
border-color: var(--border-focus);
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
}
#qwen-oauth-status {
font-weight: 500;
padding: 8px 12px;
border-radius: 6px;
background: var(--bg-tertiary);
border: 1px solid var(--border-primary);
transition: all 0.3s ease;
}
#qwen-oauth-status.success {
background: var(--success-bg);
border-color: var(--success-border);
color: var(--success-text);
}
#qwen-oauth-status.error {
background: var(--error-bg);
border-color: var(--error-border);
color: var(--error-text);
}
#qwen-oauth-status.warning {
background: var(--warning-bg);
border-color: var(--warning-border);
color: var(--warning-text);
}
/* Qwen OAuth 按钮样式 */
#qwen-open-link,
#qwen-copy-link {
min-width: 100px;
white-space: nowrap;
}
#qwen-open-link {
background: var(--primary-color);
border-color: var(--primary-color);
}
#qwen-open-link:hover {
background: var(--primary-hover);
border-color: var(--primary-hover);
}
#qwen-copy-link {
background: var(--bg-secondary);
border-color: var(--border-primary);
color: var(--text-secondary);
}
#qwen-copy-link:hover {
background: var(--bg-tertiary);
border-color: var(--border-secondary);
color: var(--text-primary);
}
/* 响应式设计 - Qwen OAuth */
@media (max-width: 768px) {
#qwen-oauth-content .input-group {
flex-direction: column;
align-items: stretch;
}
#qwen-open-link,
#qwen-copy-link {
width: 100%;
margin-top: 8px;
}
#qwen-oauth-url {
font-size: 12px;
}
}
/* iFlow OAuth 样式 */
#iflow-oauth-content {
transition: all 0.3s ease;
}
#iflow-oauth-url {
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
font-size: 13px;
background: var(--bg-tertiary);
border: 1px solid var(--border-primary);
color: var(--text-secondary);
}
#iflow-oauth-url:focus {
border-color: var(--border-focus);
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
}
#iflow-oauth-status {
font-weight: 500;
padding: 8px 12px;
border-radius: 6px;
background: var(--bg-tertiary);
border: 1px solid var(--border-primary);
transition: all 0.3s ease;
}
#iflow-oauth-status.success {
background: var(--success-bg);
border-color: var(--success-border);
color: var(--success-text);
}
#iflow-oauth-status.error {
background: var(--error-bg);
border-color: var(--error-border);
color: var(--error-text);
}
#iflow-oauth-status.warning {
background: var(--warning-bg);
border-color: var(--warning-border);
color: var(--warning-text);
}
/* iFlow OAuth 按钮样式 */
#iflow-open-link,
#iflow-copy-link {
min-width: 100px;
white-space: nowrap;
}
#iflow-open-link {
background: var(--primary-color);
border-color: var(--primary-color);
}
#iflow-open-link:hover {
background: var(--primary-hover);
border-color: var(--primary-hover);
}
#iflow-copy-link {
background: var(--bg-secondary);
border-color: var(--border-primary);
color: var(--text-secondary);
}
#iflow-copy-link:hover {
background: var(--bg-tertiary);
border-color: var(--border-secondary);
color: var(--text-primary);
}
/* 响应式设计 - iFlow OAuth */
@media (max-width: 768px) {
#iflow-oauth-content .input-group {
flex-direction: column;
align-items: stretch;
}
#iflow-open-link,
#iflow-copy-link {
width: 100%;
margin-top: 8px;
}
#iflow-oauth-url {
font-size: 12px;
}
}
</style>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script>
// 国际化语言包
const i18n = {
// 语言配置
currentLanguage: 'zh-CN',
fallbackLanguage: 'zh-CN',
// 语言包
translations: {
'zh-CN': {
// 通用
'common.login': '登录',
'common.logout': '登出',
'common.cancel': '取消',
'common.confirm': '确认',
'common.save': '保存',
'common.delete': '删除',
'common.edit': '编辑',
'common.add': '添加',
'common.update': '更新',
'common.refresh': '刷新',
'common.close': '关闭',
'common.success': '成功',
'common.error': '错误',
'common.info': '信息',
'common.warning': '警告',
'common.loading': '加载中...',
'common.connecting': '连接中...',
'common.connected': '已连接',
'common.disconnected': '未连接',
'common.connecting_status': '连接中',
'common.connected_status': '已连接',
'common.disconnected_status': '未连接',
'common.yes': '是',
'common.no': '否',
'common.optional': '可选',
'common.required': '必填',
'common.api_key': '密钥',
'common.base_url': '地址',
'common.proxy_url': '代理',
'common.alias': '别名',
// 页面标题
'title.main': 'CLI Proxy API Management Center',
'title.login': 'CLI Proxy API Management Center',
// 自动登录
'auto_login.title': '正在自动登录...',
'auto_login.message': '正在使用本地保存的连接信息尝试连接服务器',
// 登录页面
'login.subtitle': '请输入连接信息以访问管理界面',
'login.connection_title': '连接地址',
'login.connection_current': '当前地址',
'login.connection_auto_hint': '系统将自动使用当前访问地址进行连接',
'login.custom_connection_label': '自定义连接地址:',
'login.custom_connection_placeholder': '例如: https://example.com:8317',
'login.custom_connection_hint': '默认使用当前访问地址,若需要可手动输入其他地址。',
'login.use_current_address': '使用当前地址',
'login.management_key_label': '管理密钥:',
'login.management_key_placeholder': '请输入管理密钥',
'login.connect_button': '连接',
'login.submit_button': '登录',
'login.submitting': '连接中...',
'login.error_title': '登录失败',
'login.error_required': '请填写完整的连接信息',
'login.error_invalid': '连接失败,请检查地址和密钥',
// 头部导航
'header.check_connection': '检查连接',
'header.refresh_all': '刷新全部',
'header.logout': '登出',
// 连接信息
'connection.title': '连接信息',
'connection.server_address': '服务器地址:',
'connection.management_key': '管理密钥:',
'connection.status': '连接状态:',
// 侧边栏导航
'nav.basic_settings': '基础设置',
'nav.api_keys': 'API 密钥',
'nav.ai_providers': 'AI 提供商',
'nav.auth_files': '认证文件',
'nav.usage_stats': '使用统计',
'nav.system_info': '系统信息',
// 基础设置
'basic_settings.title': '基础设置',
'basic_settings.debug_title': '调试模式',
'basic_settings.debug_enable': '启用调试模式',
'basic_settings.proxy_title': '代理设置',
'basic_settings.proxy_url_label': '代理 URL:',
'basic_settings.proxy_url_placeholder': '例如: socks5://user:pass@127.0.0.1:1080/',
'basic_settings.proxy_update': '更新',
'basic_settings.proxy_clear': '清空',
'basic_settings.retry_title': '请求重试',
'basic_settings.retry_count_label': '重试次数:',
'basic_settings.retry_update': '更新',
'basic_settings.quota_title': '配额超出行为',
'basic_settings.quota_switch_project': '自动切换项目',
'basic_settings.quota_switch_preview': '切换到预览模型',
// API 密钥管理
'api_keys.title': 'API 密钥管理',
'api_keys.proxy_auth_title': '代理服务认证密钥',
'api_keys.add_button': '添加密钥',
'api_keys.empty_title': '暂无API密钥',
'api_keys.empty_desc': '点击上方按钮添加第一个密钥',
'api_keys.item_title': 'API密钥',
'api_keys.add_modal_title': '添加API密钥',
'api_keys.add_modal_key_label': 'API密钥:',
'api_keys.add_modal_key_placeholder': '请输入API密钥',
'api_keys.edit_modal_title': '编辑API密钥',
'api_keys.edit_modal_key_label': 'API密钥:',
'api_keys.delete_confirm': '确定要删除这个API密钥吗?',
// AI 提供商
'ai_providers.title': 'AI 提供商配置',
'ai_providers.gemini_title': 'Gemini API 密钥',
'ai_providers.gemini_add_button': '添加密钥',
'ai_providers.gemini_empty_title': '暂无Gemini密钥',
'ai_providers.gemini_empty_desc': '点击上方按钮添加第一个密钥',
'ai_providers.gemini_item_title': 'Gemini密钥',
'ai_providers.gemini_add_modal_title': '添加Gemini API密钥',
'ai_providers.gemini_add_modal_key_label': 'API密钥:',
'ai_providers.gemini_add_modal_key_placeholder': '请输入Gemini API密钥',
'ai_providers.gemini_edit_modal_title': '编辑Gemini API密钥',
'ai_providers.gemini_edit_modal_key_label': 'API密钥:',
'ai_providers.gemini_delete_confirm': '确定要删除这个Gemini密钥吗?',
'ai_providers.codex_title': 'Codex API 配置',
'ai_providers.codex_add_button': '添加配置',
'ai_providers.codex_empty_title': '暂无Codex配置',
'ai_providers.codex_empty_desc': '点击上方按钮添加第一个配置',
'ai_providers.codex_item_title': 'Codex配置',
'ai_providers.codex_add_modal_title': '添加Codex API配置',
'ai_providers.codex_add_modal_key_label': 'API密钥:',
'ai_providers.codex_add_modal_key_placeholder': '请输入Codex API密钥',
'ai_providers.codex_add_modal_url_label': 'Base URL (可选):',
'ai_providers.codex_add_modal_url_placeholder': '例如: https://api.example.com',
'ai_providers.codex_add_modal_proxy_label': '代理 URL (可选):',
'ai_providers.codex_add_modal_proxy_placeholder': '例如: socks5://proxy.example.com:1080',
'ai_providers.codex_edit_modal_title': '编辑Codex API配置',
'ai_providers.codex_edit_modal_key_label': 'API密钥:',
'ai_providers.codex_edit_modal_url_label': 'Base URL (可选):',
'ai_providers.codex_edit_modal_proxy_label': '代理 URL (可选):',
'ai_providers.codex_delete_confirm': '确定要删除这个Codex配置吗?',
'ai_providers.claude_title': 'Claude API 配置',
'ai_providers.claude_add_button': '添加配置',
'ai_providers.claude_empty_title': '暂无Claude配置',
'ai_providers.claude_empty_desc': '点击上方按钮添加第一个配置',
'ai_providers.claude_item_title': 'Claude配置',
'ai_providers.claude_add_modal_title': '添加Claude API配置',
'ai_providers.claude_add_modal_key_label': 'API密钥:',
'ai_providers.claude_add_modal_key_placeholder': '请输入Claude API密钥',
'ai_providers.claude_add_modal_url_label': 'Base URL (可选):',
'ai_providers.claude_add_modal_url_placeholder': '例如: https://api.anthropic.com',
'ai_providers.claude_add_modal_proxy_label': '代理 URL (可选):',
'ai_providers.claude_add_modal_proxy_placeholder': '例如: socks5://proxy.example.com:1080',
'ai_providers.claude_edit_modal_title': '编辑Claude API配置',
'ai_providers.claude_edit_modal_key_label': 'API密钥:',
'ai_providers.claude_edit_modal_url_label': 'Base URL (可选):',
'ai_providers.claude_edit_modal_proxy_label': '代理 URL (可选):',
'ai_providers.claude_delete_confirm': '确定要删除这个Claude配置吗?',
'ai_providers.openai_title': 'OpenAI 兼容提供商',
'ai_providers.openai_add_button': '添加提供商',
'ai_providers.openai_empty_title': '暂无OpenAI兼容提供商',
'ai_providers.openai_empty_desc': '点击上方按钮添加第一个提供商',
'ai_providers.openai_add_modal_title': '添加OpenAI兼容提供商',
'ai_providers.openai_add_modal_name_label': '提供商名称:',
'ai_providers.openai_add_modal_name_placeholder': '例如: openrouter',
'ai_providers.openai_add_modal_url_label': 'Base URL:',
'ai_providers.openai_add_modal_url_placeholder': '例如: https://openrouter.ai/api/v1',
'ai_providers.openai_add_modal_keys_label': 'API密钥 (每行一个):',
'ai_providers.openai_add_modal_keys_placeholder': 'sk-key1\nsk-key2',
'ai_providers.openai_add_modal_keys_proxy_label': '代理 URL (按行对应,可选):',
'ai_providers.openai_add_modal_keys_proxy_placeholder': 'socks5://proxy.example.com:1080\n',
'ai_providers.openai_add_modal_models_label': '模型列表 (name[, alias] 每行一个):',
'ai_providers.openai_models_hint': '示例:gpt-4o-mini 或 moonshotai/kimi-k2:free, kimi-k2',
'ai_providers.openai_model_name_placeholder': '模型名称,如 moonshotai/kimi-k2:free',
'ai_providers.openai_model_alias_placeholder': '模型别名 (可选)',
'ai_providers.openai_models_add_btn': '添加模型',
'ai_providers.openai_edit_modal_title': '编辑OpenAI兼容提供商',
'ai_providers.openai_edit_modal_name_label': '提供商名称:',
'ai_providers.openai_edit_modal_url_label': 'Base URL:',
'ai_providers.openai_edit_modal_keys_label': 'API密钥 (每行一个):',
'ai_providers.openai_edit_modal_keys_proxy_label': '代理 URL (按行对应,可选):',
'ai_providers.openai_edit_modal_models_label': '模型列表 (name[, alias] 每行一个):',
'ai_providers.openai_delete_confirm': '确定要删除这个OpenAI提供商吗?',
'ai_providers.openai_keys_count': '密钥数量',
'ai_providers.openai_models_count': '模型数量',
// 认证文件管理
'auth_files.title': '认证文件管理',
'auth_files.title_section': '认证文件',
'auth_files.description': '这里管理 Qwen 和 Gemini 的认证配置文件。上传 JSON 格式的认证文件以启用相应的 AI 服务。',
'auth_files.upload_button': '上传文件',
'auth_files.delete_all_button': '删除全部',
'auth_files.empty_title': '暂无认证文件',
'auth_files.empty_desc': '点击上方按钮上传第一个文件',
'auth_files.file_size': '大小',
'auth_files.file_modified': '修改时间',
'auth_files.download_button': '下载',
'auth_files.delete_button': '删除',
'auth_files.delete_confirm': '确定要删除文件',
'auth_files.delete_all_confirm': '确定要删除所有认证文件吗?此操作不可恢复!',
'auth_files.upload_error_json': '只能上传JSON文件',
'auth_files.upload_success': '文件上传成功',
'auth_files.download_success': '文件下载成功',
'auth_files.delete_success': '文件删除成功',
'auth_files.delete_all_success': '成功删除',
'auth_files.files_count': '个文件',
// Gemini Web Token
'auth_login.gemini_web_title': 'Gemini Web Token',
'auth_login.gemini_web_button': '保存 Gemini Web Token',
'auth_login.gemini_web_hint': '从浏览器开发者工具中获取 Gemini 网页版的 Cookie 值,用于直接认证访问 Gemini。',
'auth_login.secure_1psid_label': '__Secure-1PSID Cookie:',
'auth_login.secure_1psid_placeholder': '输入 __Secure-1PSID cookie 值',
'auth_login.secure_1psidts_label': '__Secure-1PSIDTS Cookie:',
'auth_login.secure_1psidts_placeholder': '输入 __Secure-1PSIDTS cookie 值',
'auth_login.gemini_web_label_label': '标签 (可选):',
'auth_login.gemini_web_label_placeholder': '输入标签名称 (可选)',
'auth_login.gemini_web_saved': 'Gemini Web Token 保存成功',
// Codex OAuth
'auth_login.codex_oauth_title': 'Codex OAuth',
'auth_login.codex_oauth_button': '开始 Codex 登录',
'auth_login.codex_oauth_hint': '通过 OAuth 流程登录 Codex 服务,自动获取并保存认证文件。',
'auth_login.codex_oauth_url_label': '授权链接:',
'auth_login.codex_open_link': '打开链接',
'auth_login.codex_copy_link': '复制链接',
'auth_login.codex_oauth_status_waiting': '等待认证中...',
'auth_login.codex_oauth_status_success': '认证成功!',
'auth_login.codex_oauth_status_error': '认证失败:',
'auth_login.codex_oauth_start_error': '启动 Codex OAuth 失败:',
'auth_login.codex_oauth_polling_error': '检查认证状态失败:',
// Anthropic OAuth
'auth_login.anthropic_oauth_title': 'Anthropic OAuth',
'auth_login.anthropic_oauth_button': '开始 Anthropic 登录',
'auth_login.anthropic_oauth_hint': '通过 OAuth 流程登录 Anthropic (Claude) 服务,自动获取并保存认证文件。',
'auth_login.anthropic_oauth_url_label': '授权链接:',
'auth_login.anthropic_open_link': '打开链接',
'auth_login.anthropic_copy_link': '复制链接',
'auth_login.anthropic_oauth_status_waiting': '等待认证中...',
'auth_login.anthropic_oauth_status_success': '认证成功!',
'auth_login.anthropic_oauth_status_error': '认证失败:',
'auth_login.anthropic_oauth_start_error': '启动 Anthropic OAuth 失败:',
'auth_login.anthropic_oauth_polling_error': '检查认证状态失败:',
// Gemini CLI OAuth
'auth_login.gemini_cli_oauth_title': 'Gemini CLI OAuth',
'auth_login.gemini_cli_oauth_button': '开始 Gemini CLI 登录',
'auth_login.gemini_cli_oauth_hint': '通过 OAuth 流程登录 Google Gemini CLI 服务,自动获取并保存认证文件。',
'auth_login.gemini_cli_project_id_label': 'Google Cloud 项目 ID (可选):',
'auth_login.gemini_cli_project_id_placeholder': '输入 Google Cloud 项目 ID (可选)',
'auth_login.gemini_cli_project_id_hint': '如果指定了项目 ID,将使用该项目的认证信息。',
'auth_login.gemini_cli_oauth_url_label': '授权链接:',
'auth_login.gemini_cli_open_link': '打开链接',
'auth_login.gemini_cli_copy_link': '复制链接',
'auth_login.gemini_cli_oauth_status_waiting': '等待认证中...',
'auth_login.gemini_cli_oauth_status_success': '认证成功!',
'auth_login.gemini_cli_oauth_status_error': '认证失败:',
'auth_login.gemini_cli_oauth_start_error': '启动 Gemini CLI OAuth 失败:',
'auth_login.gemini_cli_oauth_polling_error': '检查认证状态失败:',
// Qwen OAuth
'auth_login.qwen_oauth_title': 'Qwen OAuth',
'auth_login.qwen_oauth_button': '开始 Qwen 登录',
'auth_login.qwen_oauth_hint': '通过设备授权流程登录 Qwen 服务,自动获取并保存认证文件。',
'auth_login.qwen_oauth_url_label': '授权链接:',
'auth_login.qwen_open_link': '打开链接',
'auth_login.qwen_copy_link': '复制链接',
'auth_login.qwen_oauth_status_waiting': '等待认证中...',
'auth_login.qwen_oauth_status_success': '认证成功!',
'auth_login.qwen_oauth_status_error': '认证失败:',
'auth_login.qwen_oauth_start_error': '启动 Qwen OAuth 失败:',
'auth_login.qwen_oauth_polling_error': '检查认证状态失败:',
// iFlow OAuth
'auth_login.iflow_oauth_title': 'iFlow OAuth',
'auth_login.iflow_oauth_button': '开始 iFlow 登录',
'auth_login.iflow_oauth_hint': '通过 OAuth 流程登录 iFlow 服务,自动获取并保存认证文件。',
'auth_login.iflow_oauth_url_label': '授权链接:',
'auth_login.iflow_open_link': '打开链接',
'auth_login.iflow_copy_link': '复制链接',
'auth_login.iflow_oauth_status_waiting': '等待认证中...',
'auth_login.iflow_oauth_status_success': '认证成功!',
'auth_login.iflow_oauth_status_error': '认证失败:',
'auth_login.iflow_oauth_start_error': '启动 iFlow OAuth 失败:',
'auth_login.iflow_oauth_polling_error': '检查认证状态失败:',
// 使用统计
'usage_stats.title': '使用统计',
'usage_stats.total_requests': '总请求数',
'usage_stats.success_requests': '成功请求',
'usage_stats.failed_requests': '失败请求',
'usage_stats.total_tokens': '总Token数',
'usage_stats.requests_trend': '请求趋势',
'usage_stats.tokens_trend': 'Token 使用趋势',
'usage_stats.api_details': 'API 详细统计',
'usage_stats.by_hour': '按小时',
'usage_stats.by_day': '按天',
'usage_stats.refresh': '刷新',
'usage_stats.no_data': '暂无数据',
'usage_stats.loading_error': '加载失败',
'usage_stats.api_endpoint': 'API端点',
'usage_stats.requests_count': '请求次数',
'usage_stats.tokens_count': 'Token数量',
'usage_stats.models': '模型统计',
'usage_stats.success_rate': '成功率',
// 系统信息
'system_info.title': '系统信息',
'system_info.connection_status_title': '连接状态',
'system_info.api_status_label': 'API 状态:',
'system_info.config_status_label': '配置状态:',
'system_info.last_update_label': '最后更新:',
'system_info.cache_data': '缓存数据',
'system_info.real_time_data': '实时数据',
'system_info.not_loaded': '未加载',
'system_info.seconds_ago': '秒前',
// 通知消息
'notification.debug_updated': '调试设置已更新',
'notification.proxy_updated': '代理设置已更新',
'notification.proxy_cleared': '代理设置已清空',
'notification.retry_updated': '重试设置已更新',
'notification.quota_switch_project_updated': '项目切换设置已更新',
'notification.quota_switch_preview_updated': '预览模型切换设置已更新',
'notification.api_key_added': 'API密钥添加成功',
'notification.api_key_updated': 'API密钥更新成功',
'notification.api_key_deleted': 'API密钥删除成功',
'notification.gemini_key_added': 'Gemini密钥添加成功',
'notification.gemini_key_updated': 'Gemini密钥更新成功',
'notification.gemini_key_deleted': 'Gemini密钥删除成功',
'notification.codex_config_added': 'Codex配置添加成功',
'notification.codex_config_updated': 'Codex配置更新成功',
'notification.codex_config_deleted': 'Codex配置删除成功',
'notification.claude_config_added': 'Claude配置添加成功',
'notification.claude_config_updated': 'Claude配置更新成功',
'notification.claude_config_deleted': 'Claude配置删除成功',
'notification.field_required': '必填字段不能为空',
'notification.openai_provider_required': '请填写提供商名称和Base URL',
'notification.openai_provider_added': 'OpenAI提供商添加成功',
'notification.openai_provider_updated': 'OpenAI提供商更新成功',
'notification.openai_provider_deleted': 'OpenAI提供商删除成功',
'notification.openai_model_name_required': '请填写模型名称',
'notification.data_refreshed': '数据刷新成功',
'notification.connection_required': '请先建立连接',
'notification.refresh_failed': '刷新失败',
'notification.update_failed': '更新失败',
'notification.add_failed': '添加失败',
'notification.delete_failed': '删除失败',
'notification.upload_failed': '上传失败',
'notification.download_failed': '下载失败',
'notification.login_failed': '登录失败',
'notification.please_enter': '请输入',
'notification.please_fill': '请填写',
'notification.provider_name_url': '提供商名称和Base URL',
'notification.api_key': 'API密钥',
'notification.gemini_api_key': 'Gemini API密钥',
'notification.codex_api_key': 'Codex API密钥',
'notification.claude_api_key': 'Claude API密钥',
// 语言切换
'language.switch': '语言',
'language.chinese': '中文',
'language.english': 'English',
// 主题切换
'theme.switch': '主题',
'theme.light': '亮色',
'theme.dark': '暗色',
'theme.switch_to_light': '切换到亮色模式',
'theme.switch_to_dark': '切换到暗色模式',
'theme.auto': '跟随系统',
// 页脚
'footer.version': '版本',
'footer.author': '作者'
},
'en-US': {
// Common
'common.login': 'Login',
'common.logout': 'Logout',
'common.cancel': 'Cancel',
'common.confirm': 'Confirm',
'common.save': 'Save',
'common.delete': 'Delete',
'common.edit': 'Edit',
'common.add': 'Add',
'common.update': 'Update',
'common.refresh': 'Refresh',
'common.close': 'Close',
'common.success': 'Success',
'common.error': 'Error',
'common.info': 'Info',
'common.warning': 'Warning',
'common.loading': 'Loading...',
'common.connecting': 'Connecting...',
'common.connected': 'Connected',
'common.disconnected': 'Disconnected',
'common.connecting_status': 'Connecting',
'common.connected_status': 'Connected',
'common.disconnected_status': 'Disconnected',
'common.yes': 'Yes',
'common.no': 'No',
'common.optional': 'Optional',
'common.required': 'Required',
'common.api_key': 'Key',
'common.base_url': 'Address',
// Page titles
'title.main': 'CLI Proxy API Management Center',
'title.login': 'CLI Proxy API Management Center',
// Auto login
'auto_login.title': 'Auto Login in Progress...',
'auto_login.message': 'Attempting to connect to server using locally saved connection information',
// Login page
'login.subtitle': 'Please enter connection information to access the management interface',
'login.connection_title': 'Connection Address',
'login.connection_current': 'Current URL',
'login.connection_auto_hint': 'The system will automatically use the current URL for connection',
'login.custom_connection_label': 'Custom Connection URL:',
'login.custom_connection_placeholder': 'Eg: https://example.com:8317',
'login.custom_connection_hint': 'By default the current URL is used. Override it here if needed.',
'login.use_current_address': 'Use Current URL',
'login.management_key_label': 'Management Key:',
'login.management_key_placeholder': 'Enter the management key',
'login.connect_button': 'Connect',
'login.submit_button': 'Login',
'login.submitting': 'Connecting...',
'login.error_title': 'Login Failed',
'login.error_required': 'Please fill in complete connection information',
'login.error_invalid': 'Connection failed, please check address and key',
// Header navigation
'header.check_connection': 'Check Connection',
'header.refresh_all': 'Refresh All',
'header.logout': 'Logout',
// Connection info
'connection.title': 'Connection Information',
'connection.server_address': 'Server Address:',
'connection.management_key': 'Management Key:',
'connection.status': 'Connection Status:',
// Sidebar navigation
'nav.basic_settings': 'Basic Settings',
'nav.api_keys': 'API Keys',
'nav.ai_providers': 'AI Providers',
'nav.auth_files': 'Auth Files',
'nav.usage_stats': 'Usage Statistics',
'nav.system_info': 'System Info',
// Basic settings
'basic_settings.title': 'Basic Settings',
'basic_settings.debug_title': 'Debug Mode',
'basic_settings.debug_enable': 'Enable Debug Mode',
'basic_settings.proxy_title': 'Proxy Settings',
'basic_settings.proxy_url_label': 'Proxy URL:',
'basic_settings.proxy_url_placeholder': 'e.g.: socks5://user:pass@127.0.0.1:1080/',
'basic_settings.proxy_update': 'Update',
'basic_settings.proxy_clear': 'Clear',
'basic_settings.retry_title': 'Request Retry',
'basic_settings.retry_count_label': 'Retry Count:',
'basic_settings.retry_update': 'Update',
'basic_settings.quota_title': 'Quota Exceeded Behavior',
'basic_settings.quota_switch_project': 'Auto Switch Project',
'basic_settings.quota_switch_preview': 'Switch to Preview Model',
// API Keys management
'api_keys.title': 'API Keys Management',
'api_keys.proxy_auth_title': 'Proxy Service Authentication Keys',
'api_keys.add_button': 'Add Key',
'api_keys.empty_title': 'No API Keys',
'api_keys.empty_desc': 'Click the button above to add the first key',
'api_keys.item_title': 'API Key',
'api_keys.add_modal_title': 'Add API Key',
'api_keys.add_modal_key_label': 'API Key:',
'api_keys.add_modal_key_placeholder': 'Please enter API key',
'api_keys.edit_modal_title': 'Edit API Key',
'api_keys.edit_modal_key_label': 'API Key:',
'api_keys.delete_confirm': 'Are you sure you want to delete this API key?',
// AI Providers
'ai_providers.title': 'AI Providers Configuration',
'ai_providers.gemini_title': 'Gemini API Keys',
'ai_providers.gemini_add_button': 'Add Key',
'ai_providers.gemini_empty_title': 'No Gemini Keys',
'ai_providers.gemini_empty_desc': 'Click the button above to add the first key',
'ai_providers.gemini_item_title': 'Gemini Key',
'ai_providers.gemini_add_modal_title': 'Add Gemini API Key',
'ai_providers.gemini_add_modal_key_label': 'API Key:',
'ai_providers.gemini_add_modal_key_placeholder': 'Please enter Gemini API key',
'ai_providers.gemini_edit_modal_title': 'Edit Gemini API Key',
'ai_providers.gemini_edit_modal_key_label': 'API Key:',
'ai_providers.gemini_delete_confirm': 'Are you sure you want to delete this Gemini key?',
'ai_providers.codex_title': 'Codex API Configuration',
'ai_providers.codex_add_button': 'Add Configuration',
'ai_providers.codex_empty_title': 'No Codex Configuration',
'ai_providers.codex_empty_desc': 'Click the button above to add the first configuration',
'ai_providers.codex_item_title': 'Codex Configuration',
'ai_providers.codex_add_modal_title': 'Add Codex API Configuration',
'ai_providers.codex_add_modal_key_label': 'API Key:',
'ai_providers.codex_add_modal_key_placeholder': 'Please enter Codex API key',
'ai_providers.codex_add_modal_url_label': 'Base URL (Optional):',
'ai_providers.codex_add_modal_url_placeholder': 'e.g.: https://api.example.com',
'ai_providers.codex_edit_modal_title': 'Edit Codex API Configuration',
'ai_providers.codex_edit_modal_key_label': 'API Key:',
'ai_providers.codex_edit_modal_url_label': 'Base URL (Optional):',
'ai_providers.codex_delete_confirm': 'Are you sure you want to delete this Codex configuration?',
'ai_providers.claude_title': 'Claude API Configuration',
'ai_providers.claude_add_button': 'Add Configuration',
'ai_providers.claude_empty_title': 'No Claude Configuration',
'ai_providers.claude_empty_desc': 'Click the button above to add the first configuration',
'ai_providers.claude_item_title': 'Claude Configuration',
'ai_providers.claude_add_modal_title': 'Add Claude API Configuration',
'ai_providers.claude_add_modal_key_label': 'API Key:',
'ai_providers.claude_add_modal_key_placeholder': 'Please enter Claude API key',
'ai_providers.claude_add_modal_url_label': 'Base URL (Optional):',
'ai_providers.claude_add_modal_url_placeholder': 'e.g.: https://api.anthropic.com',
'ai_providers.claude_edit_modal_title': 'Edit Claude API Configuration',
'ai_providers.claude_edit_modal_key_label': 'API Key:',
'ai_providers.claude_edit_modal_url_label': 'Base URL (Optional):',
'ai_providers.claude_delete_confirm': 'Are you sure you want to delete this Claude configuration?',
'ai_providers.openai_title': 'OpenAI Compatible Providers',
'ai_providers.openai_add_button': 'Add Provider',
'ai_providers.openai_empty_title': 'No OpenAI Compatible Providers',
'ai_providers.openai_empty_desc': 'Click the button above to add the first provider',
'ai_providers.openai_add_modal_title': 'Add OpenAI Compatible Provider',
'ai_providers.openai_add_modal_name_label': 'Provider Name:',
'ai_providers.openai_add_modal_name_placeholder': 'e.g.: openrouter',
'ai_providers.openai_add_modal_url_label': 'Base URL:',
'ai_providers.openai_add_modal_url_placeholder': 'e.g.: https://openrouter.ai/api/v1',
'ai_providers.openai_add_modal_keys_label': 'API Keys (one per line):',
'ai_providers.openai_add_modal_keys_placeholder': 'sk-key1\nsk-key2',
'ai_providers.openai_edit_modal_title': 'Edit OpenAI Compatible Provider',
'ai_providers.openai_edit_modal_name_label': 'Provider Name:',
'ai_providers.openai_edit_modal_url_label': 'Base URL:',
'ai_providers.openai_edit_modal_keys_label': 'API Keys (one per line):',
'ai_providers.openai_delete_confirm': 'Are you sure you want to delete this OpenAI provider?',
'ai_providers.openai_keys_count': 'Keys Count',
'ai_providers.openai_models_count': 'Models Count',
// Auth files management
'auth_files.title': 'Auth Files Management',
'auth_files.title_section': 'Auth Files',
'auth_files.description': 'Here you can manage authentication configuration files for Qwen and Gemini. Upload JSON format authentication files to enable the corresponding AI services.',
'auth_files.upload_button': 'Upload File',
'auth_files.delete_all_button': 'Delete All',
'auth_files.empty_title': 'No Auth Files',
'auth_files.empty_desc': 'Click the button above to upload the first file',
'auth_files.file_size': 'Size',
'auth_files.file_modified': 'Modified',
'auth_files.download_button': 'Download',
'auth_files.delete_button': 'Delete',
'auth_files.delete_confirm': 'Are you sure you want to delete file',
'auth_files.delete_all_confirm': 'Are you sure you want to delete all auth files? This operation cannot be undone!',
'auth_files.upload_error_json': 'Only JSON files are allowed',
'auth_files.upload_success': 'File uploaded successfully',
'auth_files.download_success': 'File downloaded successfully',
'auth_files.delete_success': 'File deleted successfully',
'auth_files.delete_all_success': 'Successfully deleted',
'auth_files.files_count': 'files',
// Gemini Web Token
'auth_login.gemini_web_title': 'Gemini Web Token',
'auth_login.gemini_web_button': 'Save Gemini Web Token',
'auth_login.gemini_web_hint': 'Obtain the Cookie value of the Gemini web version from the browser\'s developer tools, used for direct authentication to access Gemini.',
'auth_login.secure_1psid_label': '__Secure-1PSID Cookie:',
'auth_login.secure_1psid_placeholder': 'Enter __Secure-1PSID cookie value',
'auth_login.secure_1psidts_label': '__Secure-1PSIDTS Cookie:',
'auth_login.secure_1psidts_placeholder': 'Enter __Secure-1PSIDTS cookie value',
'auth_login.gemini_web_label_label': 'Label (Optional):',
'auth_login.gemini_web_label_placeholder': 'Enter label name (optional)',
'auth_login.gemini_web_saved': 'Gemini Web Token saved successfully',
// Codex OAuth
'auth_login.codex_oauth_title': 'Codex OAuth',
'auth_login.codex_oauth_button': 'Start Codex Login',
'auth_login.codex_oauth_hint': 'Login to Codex service through OAuth flow, automatically obtain and save authentication files.',
'auth_login.codex_oauth_url_label': 'Authorization URL:',
'auth_login.codex_open_link': 'Open Link',
'auth_login.codex_copy_link': 'Copy Link',
'auth_login.codex_oauth_status_waiting': 'Waiting for authentication...',
'auth_login.codex_oauth_status_success': 'Authentication successful!',
'auth_login.codex_oauth_status_error': 'Authentication failed:',
'auth_login.codex_oauth_start_error': 'Failed to start Codex OAuth:',
'auth_login.codex_oauth_polling_error': 'Failed to check authentication status:',
// Anthropic OAuth
'auth_login.anthropic_oauth_title': 'Anthropic OAuth',
'auth_login.anthropic_oauth_button': 'Start Anthropic Login',
'auth_login.anthropic_oauth_hint': 'Login to Anthropic (Claude) service through OAuth flow, automatically obtain and save authentication files.',
'auth_login.anthropic_oauth_url_label': 'Authorization URL:',
'auth_login.anthropic_open_link': 'Open Link',
'auth_login.anthropic_copy_link': 'Copy Link',
'auth_login.anthropic_oauth_status_waiting': 'Waiting for authentication...',
'auth_login.anthropic_oauth_status_success': 'Authentication successful!',
'auth_login.anthropic_oauth_status_error': 'Authentication failed:',
'auth_login.anthropic_oauth_start_error': 'Failed to start Anthropic OAuth:',
'auth_login.anthropic_oauth_polling_error': 'Failed to check authentication status:',
// Gemini CLI OAuth
'auth_login.gemini_cli_oauth_title': 'Gemini CLI OAuth',
'auth_login.gemini_cli_oauth_button': 'Start Gemini CLI Login',
'auth_login.gemini_cli_oauth_hint': 'Login to Google Gemini CLI service through OAuth flow, automatically obtain and save authentication files.',
'auth_login.gemini_cli_project_id_label': 'Google Cloud Project ID (Optional):',
'auth_login.gemini_cli_project_id_placeholder': 'Enter Google Cloud Project ID (optional)',
'auth_login.gemini_cli_project_id_hint': 'If a project ID is specified, authentication information for that project will be used.',
'auth_login.gemini_cli_oauth_url_label': 'Authorization URL:',
'auth_login.gemini_cli_open_link': 'Open Link',
'auth_login.gemini_cli_copy_link': 'Copy Link',
'auth_login.gemini_cli_oauth_status_waiting': 'Waiting for authentication...',
'auth_login.gemini_cli_oauth_status_success': 'Authentication successful!',
'auth_login.gemini_cli_oauth_status_error': 'Authentication failed:',
'auth_login.gemini_cli_oauth_start_error': 'Failed to start Gemini CLI OAuth:',
'auth_login.gemini_cli_oauth_polling_error': 'Failed to check authentication status:',
// Qwen OAuth
'auth_login.qwen_oauth_title': 'Qwen OAuth',
'auth_login.qwen_oauth_button': 'Start Qwen Login',
'auth_login.qwen_oauth_hint': 'Login to Qwen service through device authorization flow, automatically obtain and save authentication files.',
'auth_login.qwen_oauth_url_label': 'Authorization URL:',
'auth_login.qwen_open_link': 'Open Link',
'auth_login.qwen_copy_link': 'Copy Link',
'auth_login.qwen_oauth_status_waiting': 'Waiting for authentication...',
'auth_login.qwen_oauth_status_success': 'Authentication successful!',
'auth_login.qwen_oauth_status_error': 'Authentication failed:',
'auth_login.qwen_oauth_start_error': 'Failed to start Qwen OAuth:',
'auth_login.qwen_oauth_polling_error': 'Failed to check authentication status:',
// iFlow OAuth
'auth_login.iflow_oauth_title': 'iFlow OAuth',
'auth_login.iflow_oauth_button': 'Start iFlow Login',
'auth_login.iflow_oauth_hint': 'Login to iFlow service through OAuth flow, automatically obtain and save authentication files.',
'auth_login.iflow_oauth_url_label': 'Authorization URL:',
'auth_login.iflow_open_link': 'Open Link',
'auth_login.iflow_copy_link': 'Copy Link',
'auth_login.iflow_oauth_status_waiting': 'Waiting for authentication...',
'auth_login.iflow_oauth_status_success': 'Authentication successful!',
'auth_login.iflow_oauth_status_error': 'Authentication failed:',
'auth_login.iflow_oauth_start_error': 'Failed to start iFlow OAuth:',
'auth_login.iflow_oauth_polling_error': 'Failed to check authentication status:',
// Usage Statistics
'usage_stats.title': 'Usage Statistics',
'usage_stats.total_requests': 'Total Requests',
'usage_stats.success_requests': 'Success Requests',
'usage_stats.failed_requests': 'Failed Requests',
'usage_stats.total_tokens': 'Total Tokens',
'usage_stats.requests_trend': 'Request Trends',
'usage_stats.tokens_trend': 'Token Usage Trends',
'usage_stats.api_details': 'API Details',
'usage_stats.by_hour': 'By Hour',
'usage_stats.by_day': 'By Day',
'usage_stats.refresh': 'Refresh',
'usage_stats.no_data': 'No Data Available',
'usage_stats.loading_error': 'Loading Failed',
'usage_stats.api_endpoint': 'API Endpoint',
'usage_stats.requests_count': 'Request Count',
'usage_stats.tokens_count': 'Token Count',
'usage_stats.models': 'Model Statistics',
'usage_stats.success_rate': 'Success Rate',
// System info
'system_info.title': 'System Information',
'system_info.connection_status_title': 'Connection Status',
'system_info.api_status_label': 'API Status:',
'system_info.config_status_label': 'Config Status:',
'system_info.last_update_label': 'Last Update:',
'system_info.cache_data': 'Cache Data',
'system_info.real_time_data': 'Real-time Data',
'system_info.not_loaded': 'Not Loaded',
'system_info.seconds_ago': 'seconds ago',
// Notification messages
'notification.debug_updated': 'Debug settings updated',
'notification.proxy_updated': 'Proxy settings updated',
'notification.proxy_cleared': 'Proxy settings cleared',
'notification.retry_updated': 'Retry settings updated',
'notification.quota_switch_project_updated': 'Project switch settings updated',
'notification.quota_switch_preview_updated': 'Preview model switch settings updated',
'notification.api_key_added': 'API key added successfully',
'notification.api_key_updated': 'API key updated successfully',
'notification.api_key_deleted': 'API key deleted successfully',
'notification.gemini_key_added': 'Gemini key added successfully',
'notification.gemini_key_updated': 'Gemini key updated successfully',
'notification.gemini_key_deleted': 'Gemini key deleted successfully',
'notification.codex_config_added': 'Codex configuration added successfully',
'notification.codex_config_updated': 'Codex configuration updated successfully',
'notification.codex_config_deleted': 'Codex configuration deleted successfully',
'notification.claude_config_added': 'Claude configuration added successfully',
'notification.claude_config_updated': 'Claude configuration updated successfully',
'notification.claude_config_deleted': 'Claude configuration deleted successfully',
'notification.openai_provider_added': 'OpenAI provider added successfully',
'notification.openai_provider_updated': 'OpenAI provider updated successfully',
'notification.openai_provider_deleted': 'OpenAI provider deleted successfully',
'notification.openai_model_name_required': 'Model name is required',
'notification.data_refreshed': 'Data refreshed successfully',
'notification.connection_required': 'Please establish connection first',
'notification.refresh_failed': 'Refresh failed',
'notification.update_failed': 'Update failed',
'notification.add_failed': 'Add failed',
'notification.delete_failed': 'Delete failed',
'notification.upload_failed': 'Upload failed',
'notification.download_failed': 'Download failed',
'notification.login_failed': 'Login failed',
'notification.please_enter': 'Please enter',
'notification.please_fill': 'Please fill',
'notification.provider_name_url': 'provider name and Base URL',
'notification.api_key': 'API key',
'notification.gemini_api_key': 'Gemini API key',
'notification.codex_api_key': 'Codex API key',
'notification.claude_api_key': 'Claude API key',
// Language switch
'language.switch': 'Language',
'language.chinese': '中文',
'language.english': 'English',
// Theme switch
'theme.switch': 'Theme',
'theme.light': 'Light',
'theme.dark': 'Dark',
'theme.switch_to_light': 'Switch to light mode',
'theme.switch_to_dark': 'Switch to dark mode',
'theme.auto': 'Follow system',
// Footer
'footer.version': 'Version',
'footer.author': 'Author'
}
},
// 获取翻译文本
t(key, params = {}) {
const translation = this.translations[this.currentLanguage]?.[key] ||
this.translations[this.fallbackLanguage]?.[key] ||
key;
// 简单的参数替换
return translation.replace(/\{(\w+)\}/g, (match, param) => {
return params[param] || match;
});
},
// 设置语言
setLanguage(lang) {
if (this.translations[lang]) {
this.currentLanguage = lang;
localStorage.setItem('preferredLanguage', lang);
this.updatePageLanguage();
this.updateAllTexts();
}
},
// 更新页面语言属性
updatePageLanguage() {
document.documentElement.lang = this.currentLanguage;
},
// 更新所有文本
updateAllTexts() {
// 更新所有带有 data-i18n 属性的元素
document.querySelectorAll('[data-i18n]').forEach(element => {
const key = element.getAttribute('data-i18n');
const text = this.t(key);
if (element.tagName === 'INPUT' && (element.type === 'text' || element.type === 'password')) {
element.placeholder = text;
} else if (element.tagName === 'TITLE') {
element.textContent = text;
} else {
element.textContent = text;
}
});
// 更新所有带有 data-i18n-html 属性的元素(支持HTML)
document.querySelectorAll('[data-i18n-html]').forEach(element => {
const key = element.getAttribute('data-i18n-html');
const html = this.t(key);
element.innerHTML = html;
});
},
// 初始化
init() {
// 从本地存储获取用户偏好语言
const savedLanguage = localStorage.getItem('preferredLanguage');
if (savedLanguage && this.translations[savedLanguage]) {
this.currentLanguage = savedLanguage;
} else {
// 根据浏览器语言自动选择
const browserLang = navigator.language || navigator.userLanguage;
if (browserLang.startsWith('zh')) {
this.currentLanguage = 'zh-CN';
} else {
this.currentLanguage = 'en-US';
}
}
this.updatePageLanguage();
this.updateAllTexts();
}
};
// 全局函数,供HTML调用
window.t = (key, params) => i18n.t(key, params);
window.setLanguage = (lang) => i18n.setLanguage(lang);
</script>
</head>
<body>
<!-- 自动登录加载页面 -->
<div id="auto-login-loading" class="login-container" style="display: none;">
<div class="login-card">
<div class="auto-login-content">
<div class="loading-spinner">
<div class="spinner"></div>
</div>
<h2 data-i18n="auto_login.title">正在自动登录...</h2>
<p data-i18n="auto_login.message">正在使用本地保存的连接信息尝试连接服务器</p>
</div>
</div>
</div>
<!-- 登录页面 -->
<div id="login-page" class="login-container">
<div class="login-card">
<div class="login-header">
<div class="login-header-top">
<h1 class="login-title">
<img id="login-logo" alt="Logo" style="display:none" />
<span data-i18n="title.login">CLI Proxy API Management Center</span>
</h1>
<div class="header-controls">
<div class="language-switcher">
<button id="language-toggle" class="btn btn-secondary language-btn">
<i class="fas fa-globe"></i>
<span data-i18n="language.switch">语言</span>
</button>
</div>
<div class="theme-switcher">
<button id="theme-toggle" class="btn btn-secondary theme-btn">
<i class="fas fa-moon"></i>
<span data-i18n="theme.switch">主题</span>
</button>
</div>
</div>
</div>
</div>
<div class="login-body">
<div class="login-connection-info">
<div class="connection-summary">
<i class="fas fa-link"></i>
<div>
<h3 data-i18n="login.connection_title">连接地址</h3>
<p class="connection-url">
<span data-i18n="login.connection_current">当前地址</span>
<span class="connection-url-separator">:</span>
<span id="login-connection-url">-</span>
</p>
</div>
</div>
<p class="form-hint" data-i18n="login.connection_auto_hint">系统将自动使用当前访问地址进行连接</p>
</div>
<form class="login-form">
<div class="form-group">
<label for="login-api-base" data-i18n="login.custom_connection_label">自定义连接地址:</label>
<div class="input-group">
<input type="text" id="login-api-base" data-i18n="login.custom_connection_placeholder"
placeholder="例如: https://example.com:8317">
<button type="button" id="login-reset-api-base"
class="btn btn-secondary connection-reset-btn">
<i class="fas fa-location-arrow"></i>
<span data-i18n="login.use_current_address">使用当前地址</span>
</button>
</div>
<p class="form-hint" data-i18n="login.custom_connection_hint">默认使用当前访问地址,若需要可手动输入其他地址。</p>
</div>
<div class="form-group">
<label for="login-management-key" data-i18n="login.management_key_label">管理密钥:</label>
<div class="input-group">
<input type="password" id="login-management-key"
data-i18n="login.management_key_placeholder" placeholder="请输入管理密钥" required>
<button type="button" class="btn btn-secondary toggle-key-visibility">
<i class="fas fa-eye"></i>
</button>
</div>
</div>
</form>
</div>
<!-- 连接按钮 -->
<div class="form-actions">
<button type="button" id="login-submit" class="btn btn-primary login-btn">
<i class="fas fa-plug"></i> <span data-i18n="login.connect_button">Connect</span>
</button>
</div>
<div id="login-error" class="login-error" style="display: none;">
<i class="fas fa-exclamation-triangle"></i>
<span id="login-error-message"></span>
</div>
</div>
</div>
<!-- 主页面 -->
<div id="main-page" style="display: none;">
<!-- 顶部导航栏 -->
<div class="top-navbar">
<div class="top-navbar-left">
<button class="mobile-menu-btn" id="mobile-menu-btn">
<i class="fas fa-bars"></i>
</button>
<button class="sidebar-toggle-btn-desktop" id="sidebar-toggle-btn-desktop" title="收起/展开侧边栏">
<i class="fas fa-bars"></i>
</button>
<div class="top-navbar-brand">
<img id="site-logo" class="top-navbar-brand-logo" alt="Logo" style="display:none" />
<span class="top-navbar-brand-text" data-i18n="title.main">CLI Proxy API Management Center</span>
</div>
</div>
<div class="top-navbar-actions">
<div class="header-controls">
<div class="language-switcher">
<button id="language-toggle-main" class="btn btn-secondary language-btn">
<i class="fas fa-globe"></i>
</button>
</div>
<div class="theme-switcher">
<button id="theme-toggle-main" class="btn btn-secondary theme-btn">
<i class="fas fa-moon"></i>
</button>
</div>
</div>
<button id="connection-status" class="btn btn-secondary">
<i class="fas fa-circle"></i> <span data-i18n="header.check_connection">检查连接</span>
</button>
<button id="refresh-all" class="btn btn-primary">
<i class="fas fa-sync-alt"></i>
</button>
<button id="logout-btn" class="btn btn-danger">
<i class="fas fa-sign-out-alt"></i>
</button>
</div>
</div>
<div class="layout" id="layout-container">
<!-- 侧边栏 -->
<nav class="sidebar" id="sidebar">
<!-- 导航菜单 -->
<ul class="nav-menu">
<li data-tooltip="基础设置"><a href="#basic-settings" class="nav-item active"
data-section="basic-settings">
<i class="fas fa-sliders-h"></i> <span data-i18n="nav.basic_settings">基础设置</span>
</a></li>
<li data-tooltip="API 密钥"><a href="#api-keys" class="nav-item" data-section="api-keys">
<i class="fas fa-key"></i> <span data-i18n="nav.api_keys">API 密钥</span>
</a></li>
<li data-tooltip="AI 提供商"><a href="#ai-providers" class="nav-item" data-section="ai-providers">
<i class="fas fa-robot"></i> <span data-i18n="nav.ai_providers">AI 提供商</span>
</a></li>
<li data-tooltip="认证文件"><a href="#auth-files" class="nav-item" data-section="auth-files">
<i class="fas fa-file-alt"></i> <span data-i18n="nav.auth_files">认证文件</span>
</a></li>
<li data-tooltip="使用统计"><a href="#usage-stats" class="nav-item" data-section="usage-stats">
<i class="fas fa-chart-line"></i> <span data-i18n="nav.usage_stats">使用统计</span>
</a></li>
<li data-tooltip="系统信息"><a href="#system-info" class="nav-item" data-section="system-info">
<i class="fas fa-info-circle"></i> <span data-i18n="nav.system_info">系统信息</span>
</a></li>
</ul>
</nav>
<!-- 侧边栏遮罩(移动端) -->
<div class="sidebar-overlay" id="sidebar-overlay"></div>
<!-- 主内容包装器 -->
<div class="main-wrapper" id="main-wrapper">
<!-- 主内容区域 -->
<div class="main-content">
<!-- 内容区域 -->
<div class="content-area">
<!-- 基础设置 -->
<section id="basic-settings" class="content-section active">
<h2 data-i18n="basic_settings.title">基础设置</h2>
<!-- Debug 设置 -->
<div class="card">
<div class="card-header">
<h3><i class="fas fa-bug"></i> <span
data-i18n="basic_settings.debug_title">调试模式</span></h3>
</div>
<div class="card-content">
<div class="toggle-group">
<label class="toggle-switch">
<input type="checkbox" id="debug-toggle">
<span class="slider"></span>
</label>
<span class="toggle-label" data-i18n="basic_settings.debug_enable">启用调试模式</span>
</div>
</div>
</div>
<!-- 代理设置 -->
<div class="card">
<div class="card-header">
<h3><i class="fas fa-network-wired"></i> <span
data-i18n="basic_settings.proxy_title">代理设置</span></h3>
</div>
<div class="card-content">
<div class="form-group">
<label for="proxy-url" data-i18n="basic_settings.proxy_url_label">代理
URL:</label>
<div class="input-group">
<input type="text" id="proxy-url"
data-i18n="basic_settings.proxy_url_placeholder"
placeholder="例如: socks5://user:pass@127.0.0.1:1080/">
<button id="update-proxy" class="btn btn-primary"
data-i18n="basic_settings.proxy_update">更新</button>
<button id="clear-proxy" class="btn btn-danger"
data-i18n="basic_settings.proxy_clear">清空</button>
</div>
</div>
</div>
</div>
<!-- 请求重试设置 -->
<div class="card">
<div class="card-header">
<h3><i class="fas fa-redo"></i> <span
data-i18n="basic_settings.retry_title">请求重试</span></h3>
</div>
<div class="card-content">
<div class="form-group">
<label for="request-retry"
data-i18n="basic_settings.retry_count_label">重试次数:</label>
<div class="input-group">
<input type="number" id="request-retry" min="0" max="10" value="3">
<button id="update-retry" class="btn btn-primary"
data-i18n="basic_settings.retry_update">更新</button>
</div>
</div>
</div>
</div>
<!-- 配额超出行为 -->
<div class="card">
<div class="card-header">
<h3><i class="fas fa-exclamation-triangle"></i> <span
data-i18n="basic_settings.quota_title">配额超出行为</span></h3>
</div>
<div class="card-content">
<div class="toggle-group">
<label class="toggle-switch">
<input type="checkbox" id="switch-project-toggle">
<span class="slider"></span>
</label>
<span class="toggle-label"
data-i18n="basic_settings.quota_switch_project">自动切换项目</span>
</div>
<div class="toggle-group">
<label class="toggle-switch">
<input type="checkbox" id="switch-preview-model-toggle">
<span class="slider"></span>
</label>
<span class="toggle-label"
data-i18n="basic_settings.quota_switch_preview">切换到预览模型</span>
</div>
</div>
</div>
</section>
<!-- API 密钥管理 -->
<section id="api-keys" class="content-section">
<h2 data-i18n="api_keys.title">API 密钥管理</h2>
<div class="card">
<div class="card-header">
<h3><i class="fas fa-key"></i> <span
data-i18n="api_keys.proxy_auth_title">代理服务认证密钥</span></h3>
<button id="add-api-key" class="btn btn-primary">
<i class="fas fa-plus"></i> <span data-i18n="api_keys.add_button">添加密钥</span>
</button>
</div>
<div class="card-content">
<div id="api-keys-list" class="key-list"></div>
</div>
</div>
</section>
<!-- AI 提供商 -->
<section id="ai-providers" class="content-section">
<h2 data-i18n="ai_providers.title">AI 提供商配置</h2>
<!-- Gemini API Keys -->
<div class="card">
<div class="card-header">
<h3><i class="fab fa-google"></i> <span data-i18n="ai_providers.gemini_title">Gemini
API 密钥</span></h3>
<button id="add-gemini-key" class="btn btn-primary">
<i class="fas fa-plus"></i> <span
data-i18n="ai_providers.gemini_add_button">添加密钥</span>
</button>
</div>
<div class="card-content">
<div id="gemini-keys-list" class="key-list"></div>
</div>
</div>
<!-- Codex API Keys -->
<div class="card">
<div class="card-header">
<h3><i class="fas fa-code"></i> <span data-i18n="ai_providers.codex_title">Codex API
配置</span></h3>
<button id="add-codex-key" class="btn btn-primary">
<i class="fas fa-plus"></i> <span
data-i18n="ai_providers.codex_add_button">添加配置</span>
</button>
</div>
<div class="card-content">
<div id="codex-keys-list" class="provider-list"></div>
</div>
</div>
<!-- Claude API Keys -->
<div class="card">
<div class="card-header">
<h3><i class="fas fa-brain"></i> <span data-i18n="ai_providers.claude_title">Claude
API 配置</span></h3>
<button id="add-claude-key" class="btn btn-primary">
<i class="fas fa-plus"></i> <span
data-i18n="ai_providers.claude_add_button">添加配置</span>
</button>
</div>
<div class="card-content">
<div id="claude-keys-list" class="provider-list"></div>
</div>
</div>
<!-- OpenAI 兼容提供商 -->
<div class="card">
<div class="card-header">
<h3><i class="fas fa-plug"></i> <span data-i18n="ai_providers.openai_title">OpenAI
兼容提供商</span></h3>
<button id="add-openai-provider" class="btn btn-primary">
<i class="fas fa-plus"></i> <span
data-i18n="ai_providers.openai_add_button">添加提供商</span>
</button>
</div>
<div class="card-content">
<div id="openai-providers-list" class="provider-list"></div>
</div>
</div>
</section>
<!-- 认证文件管理 -->
<section id="auth-files" class="content-section">
<h2 data-i18n="auth_files.title">认证文件管理</h2>
<div class="card" style="margin-bottom: 20px;">
<div class="card-content">
<p class="form-hint" data-i18n="auth_files.description">
这里管理 Qwen 和 Gemini 的认证配置文件。上传 JSON 格式的认证文件以启用相应的 AI 服务。
</p>
</div>
</div>
<!-- Gemini Web Token -->
<div class="card">
<div class="card-header">
<h3><i class="fab fa-google"></i> <span
data-i18n="auth_login.gemini_web_title">Gemini Web Token</span></h3>
<button id="gemini-web-token-btn" class="btn btn-primary">
<i class="fas fa-save"></i> <span data-i18n="auth_login.gemini_web_button">保存
Gemini Web Token</span>
</button>
</div>
<div class="card-content">
<p class="form-hint" style="margin-bottom: 20px;"
data-i18n="auth_login.gemini_web_hint">
从浏览器开发者工具中获取 Gemini 网页版的 Cookie 值,用于直接认证访问 Gemini。
</p>
<div class="form-group">
<label for="secure-1psid-input"
data-i18n="auth_login.secure_1psid_label">__Secure-1PSID Cookie:</label>
<input type="text" id="secure-1psid-input"
data-i18n="auth_login.secure_1psid_placeholder"
placeholder="输入 __Secure-1PSID cookie 值">
</div>
<div class="form-group">
<label for="secure-1psidts-input"
data-i18n="auth_login.secure_1psidts_label">__Secure-1PSIDTS Cookie:</label>
<input type="text" id="secure-1psidts-input"
data-i18n="auth_login.secure_1psidts_placeholder"
placeholder="输入 __Secure-1PSIDTS cookie 值">
</div>
<div class="form-group">
<label for="gemini-web-label-input"
data-i18n="auth_login.gemini_web_label_label">Label (Optional):</label>
<input type="text" id="gemini-web-label-input"
data-i18n="auth_login.gemini_web_label_placeholder"
placeholder="输入标签名称 (可选)">
</div>
</div>
</div>
<!-- 认证文件 -->
<div class="card">
<div class="card-header">
<h3><i class="fas fa-file-alt"></i> <span
data-i18n="auth_files.title_section">认证文件</span></h3>
<div class="header-actions">
<button id="upload-auth-file" class="btn btn-primary">
<i class="fas fa-upload"></i> <span
data-i18n="auth_files.upload_button">上传文件</span>
</button>
<button id="delete-all-auth-files" class="btn btn-danger">
<i class="fas fa-trash"></i> <span
data-i18n="auth_files.delete_all_button">删除全部</span>
</button>
</div>
</div>
<div class="card-content">
<div id="auth-files-list" class="file-list"></div>
<input type="file" id="auth-file-input" accept=".json" style="display: none;">
</div>
</div>
<!-- Codex OAuth -->
<div class="card" id="codex-oauth-card">
<div class="card-header">
<h3><i class="fas fa-code"></i> <span data-i18n="auth_login.codex_oauth_title">Codex
OAuth</span></h3>
<button id="codex-oauth-btn" class="btn btn-primary">
<i class="fas fa-sign-in-alt"></i> <span
data-i18n="auth_login.codex_oauth_button">开始 Codex 登录</span>
</button>
</div>
<div class="card-content">
<p class="form-hint" style="margin-bottom: 20px;"
data-i18n="auth_login.codex_oauth_hint">
通过 OAuth 流程登录 Codex 服务,自动获取并保存认证文件。
</p>
<div id="codex-oauth-content" style="display: none;">
<div class="form-group">
<label data-i18n="auth_login.codex_oauth_url_label">授权链接:</label>
<div class="input-group">
<input type="text" id="codex-oauth-url" readonly>
<button id="codex-open-link" class="btn btn-primary">
<i class="fas fa-external-link-alt"></i> <span
data-i18n="auth_login.codex_open_link">打开链接</span>
</button>
<button id="codex-copy-link" class="btn btn-secondary">
<i class="fas fa-copy"></i> <span
data-i18n="auth_login.codex_copy_link">复制链接</span>
</button>
</div>
</div>
<div id="codex-oauth-status" class="form-hint" style="margin-top: 10px;"></div>
</div>
</div>
</div>
<!-- Anthropic OAuth -->
<div class="card" id="anthropic-oauth-card">
<div class="card-header">
<h3><i class="fas fa-brain"></i> <span
data-i18n="auth_login.anthropic_oauth_title">Anthropic OAuth</span></h3>
<button id="anthropic-oauth-btn" class="btn btn-primary">
<i class="fas fa-sign-in-alt"></i> <span
data-i18n="auth_login.anthropic_oauth_button">开始 Anthropic 登录</span>
</button>
</div>
<div class="card-content">
<p class="form-hint" style="margin-bottom: 20px;"
data-i18n="auth_login.anthropic_oauth_hint">
通过 OAuth 流程登录 Anthropic (Claude) 服务,自动获取并保存认证文件。
</p>
<div id="anthropic-oauth-content" style="display: none;">
<div class="form-group">
<label data-i18n="auth_login.anthropic_oauth_url_label">授权链接:</label>
<div class="input-group">
<input type="text" id="anthropic-oauth-url" readonly>
<button id="anthropic-open-link" class="btn btn-primary">
<i class="fas fa-external-link-alt"></i> <span
data-i18n="auth_login.anthropic_open_link">打开链接</span>
</button>
<button id="anthropic-copy-link" class="btn btn-secondary">
<i class="fas fa-copy"></i> <span
data-i18n="auth_login.anthropic_copy_link">复制链接</span>
</button>
</div>
</div>
<div id="anthropic-oauth-status" class="form-hint" style="margin-top: 10px;">
</div>
</div>
</div>
</div>
<!-- Gemini CLI OAuth -->
<div class="card" id="gemini-cli-oauth-card">
<div class="card-header">
<h3><i class="fab fa-google"></i> <span
data-i18n="auth_login.gemini_cli_oauth_title">Gemini CLI OAuth</span></h3>
<button id="gemini-cli-oauth-btn" class="btn btn-primary">
<i class="fas fa-sign-in-alt"></i> <span
data-i18n="auth_login.gemini_cli_oauth_button">开始 Gemini CLI 登录</span>
</button>
</div>
<div class="card-content">
<p class="form-hint" style="margin-bottom: 20px;"
data-i18n="auth_login.gemini_cli_oauth_hint">
通过 OAuth 流程登录 Google Gemini CLI 服务,自动获取并保存认证文件。
</p>
<div class="form-group" style="margin-bottom: 20px;">
<label for="gemini-cli-project-id"
data-i18n="auth_login.gemini_cli_project_id_label">Google Cloud 项目 ID
(可选):</label>
<input type="text" id="gemini-cli-project-id"
data-i18n="auth_login.gemini_cli_project_id_placeholder"
placeholder="输入 Google Cloud 项目 ID (可选)">
<div class="form-hint" data-i18n="auth_login.gemini_cli_project_id_hint">
如果指定了项目 ID,将使用该项目的认证信息。
</div>
</div>
<div id="gemini-cli-oauth-content" style="display: none;">
<div class="form-group">
<label data-i18n="auth_login.gemini_cli_oauth_url_label">授权链接:</label>
<div class="input-group">
<input type="text" id="gemini-cli-oauth-url" readonly>
<button id="gemini-cli-open-link" class="btn btn-primary">
<i class="fas fa-external-link-alt"></i> <span
data-i18n="auth_login.gemini_cli_open_link">打开链接</span>
</button>
<button id="gemini-cli-copy-link" class="btn btn-secondary">
<i class="fas fa-copy"></i> <span
data-i18n="auth_login.gemini_cli_copy_link">复制链接</span>
</button>
</div>
</div>
<div id="gemini-cli-oauth-status" class="form-hint" style="margin-top: 10px;">
</div>
</div>
</div>
</div>
<!-- Qwen OAuth -->
<div class="card" id="qwen-oauth-card">
<div class="card-header">
<h3><i class="fas fa-robot"></i> <span data-i18n="auth_login.qwen_oauth_title">Qwen
OAuth</span></h3>
<button id="qwen-oauth-btn" class="btn btn-primary">
<i class="fas fa-sign-in-alt"></i> <span
data-i18n="auth_login.qwen_oauth_button">开始 Qwen 登录</span>
</button>
</div>
<div class="card-content">
<p class="form-hint" style="margin-bottom: 20px;"
data-i18n="auth_login.qwen_oauth_hint">
通过设备授权流程登录 Qwen 服务,自动获取并保存认证文件。
</p>
<div id="qwen-oauth-content" style="display: none;">
<div class="form-group">
<label data-i18n="auth_login.qwen_oauth_url_label">授权链接:</label>
<div class="input-group">
<input type="text" id="qwen-oauth-url" readonly>
<button id="qwen-open-link" class="btn btn-primary">
<i class="fas fa-external-link-alt"></i> <span
data-i18n="auth_login.qwen_open_link">打开链接</span>
</button>
<button id="qwen-copy-link" class="btn btn-secondary">
<i class="fas fa-copy"></i> <span
data-i18n="auth_login.qwen_copy_link">复制链接</span>
</button>
</div>
</div>
<div id="qwen-oauth-status" class="form-hint" style="margin-top: 10px;"></div>
</div>
</div>
</div>
<!-- iFlow OAuth -->
<div class="card" id="iflow-oauth-card">
<div class="card-header">
<h3><i class="fas fa-stream"></i> <span
data-i18n="auth_login.iflow_oauth_title">iFlow OAuth</span></h3>
<button id="iflow-oauth-btn" class="btn btn-primary">
<i class="fas fa-sign-in-alt"></i> <span
data-i18n="auth_login.iflow_oauth_button">开始 iFlow 登录</span>
</button>
</div>
<div class="card-content">
<p class="form-hint" style="margin-bottom: 20px;"
data-i18n="auth_login.iflow_oauth_hint">
通过 OAuth 流程登录 iFlow 服务,自动获取并保存认证文件。
</p>
<div id="iflow-oauth-content" style="display: none;">
<div class="form-group">
<label data-i18n="auth_login.iflow_oauth_url_label">授权链接:</label>
<div class="input-group">
<input type="text" id="iflow-oauth-url" readonly>
<button id="iflow-open-link" class="btn btn-primary">
<i class="fas fa-external-link-alt"></i> <span
data-i18n="auth_login.iflow_open_link">打开链接</span>
</button>
<button id="iflow-copy-link" class="btn btn-secondary">
<i class="fas fa-copy"></i> <span
data-i18n="auth_login.iflow_copy_link">复制链接</span>
</button>
</div>
</div>
<div id="iflow-oauth-status" class="form-hint" style="margin-top: 10px;"></div>
</div>
</div>
</div>
</section>
<!-- 使用统计 -->
<section id="usage-stats" class="content-section">
<h2 data-i18n="usage_stats.title">使用统计</h2>
<!-- 概览统计卡片 -->
<div class="stats-overview">
<div class="stat-card">
<div class="stat-icon">
<i class="fas fa-paper-plane"></i>
</div>
<div class="stat-content">
<div class="stat-number" id="total-requests">0</div>
<div class="stat-label" data-i18n="usage_stats.total_requests">总请求数</div>
</div>
</div>
<div class="stat-card">
<div class="stat-icon success">
<i class="fas fa-check-circle"></i>
</div>
<div class="stat-content">
<div class="stat-number" id="success-requests">0</div>
<div class="stat-label" data-i18n="usage_stats.success_requests">成功请求</div>
</div>
</div>
<div class="stat-card">
<div class="stat-icon error">
<i class="fas fa-exclamation-circle"></i>
</div>
<div class="stat-content">
<div class="stat-number" id="failed-requests">0</div>
<div class="stat-label" data-i18n="usage_stats.failed_requests">失败请求</div>
</div>
</div>
<div class="stat-card">
<div class="stat-icon">
<i class="fas fa-coins"></i>
</div>
<div class="stat-content">
<div class="stat-number" id="total-tokens">0</div>
<div class="stat-label" data-i18n="usage_stats.total_tokens">总Token数</div>
</div>
</div>
</div>
<!-- 图表区域 -->
<div class="charts-container">
<!-- 请求趋势图 -->
<div class="card chart-card">
<div class="card-header">
<h3><i class="fas fa-chart-line"></i> <span
data-i18n="usage_stats.requests_trend">请求趋势</span></h3>
<div class="chart-controls">
<button class="btn btn-small" data-period="hour" id="requests-hour-btn">
<span data-i18n="usage_stats.by_hour">按小时</span>
</button>
<button class="btn btn-small active" data-period="day"
id="requests-day-btn">
<span data-i18n="usage_stats.by_day">按天</span>
</button>
</div>
</div>
<div class="card-content">
<div class="chart-container">
<canvas id="requests-chart"></canvas>
</div>
</div>
</div>
<!-- Token使用趋势图 -->
<div class="card chart-card">
<div class="card-header">
<h3><i class="fas fa-chart-area"></i> <span
data-i18n="usage_stats.tokens_trend">Token 使用趋势</span></h3>
<div class="chart-controls">
<button class="btn btn-small" data-period="hour" id="tokens-hour-btn">
<span data-i18n="usage_stats.by_hour">按小时</span>
</button>
<button class="btn btn-small active" data-period="day" id="tokens-day-btn">
<span data-i18n="usage_stats.by_day">按天</span>
</button>
</div>
</div>
<div class="card-content">
<div class="chart-container">
<canvas id="tokens-chart"></canvas>
</div>
</div>
</div>
</div>
<!-- API详细统计 -->
<div class="card">
<div class="card-header">
<h3><i class="fas fa-list"></i> <span data-i18n="usage_stats.api_details">API
详细统计</span></h3>
<button id="refresh-usage-stats" class="btn btn-primary">
<i class="fas fa-sync-alt"></i> <span data-i18n="usage_stats.refresh">刷新</span>
</button>
</div>
<div class="card-content">
<div id="api-stats-table" class="api-stats-table">
<div class="loading-placeholder" data-i18n="common.loading">正在加载...</div>
</div>
</div>
</div>
</section>
<!-- 系统信息 -->
<section id="system-info" class="content-section">
<h2 data-i18n="system_info.title">系统信息</h2>
<!-- 连接信息卡片 -->
<div class="card">
<div class="card-header">
<h3><i class="fas fa-server"></i> <span data-i18n="connection.title">连接信息</span>
</h3>
</div>
<div class="card-content">
<div class="connection-info">
<div class="info-item">
<div class="info-label">
<i class="fas fa-globe"></i>
<span data-i18n="connection.server_address">服务器地址:</span>
</div>
<div class="info-value" id="display-api-url">-</div>
</div>
<div class="info-item">
<div class="info-label">
<i class="fas fa-key"></i>
<span data-i18n="connection.management_key">管理密钥:</span>
</div>
<div class="info-value" id="display-management-key">-</div>
</div>
<div class="info-item">
<div class="info-label">
<i class="fas fa-circle"></i>
<span data-i18n="connection.status">连接状态:</span>
</div>
<div class="info-value" id="display-connection-status">
<span class="status-indicator disconnected"
data-i18n="common.disconnected">未连接</span>
</div>
</div>
</div>
</div>
</div>
<div class="card">
<div class="card-header">
<h3><i class="fas fa-info-circle"></i> <span
data-i18n="system_info.connection_status_title">连接状态</span></h3>
</div>
<div class="card-content">
<div id="system-status" class="status-info">
<div class="status-item">
<span class="status-label" data-i18n="system_info.api_status_label">API
状态:</span>
<span id="api-status" class="status-value"
data-i18n="common.disconnected">未连接</span>
</div>
<div class="status-item">
<span class="status-label"
data-i18n="system_info.config_status_label">配置状态:</span>
<span id="config-status" class="status-value"
data-i18n="system_info.not_loaded">未加载</span>
</div>
<div class="status-item">
<span class="status-label"
data-i18n="system_info.last_update_label">最后更新:</span>
<span id="last-update" class="status-value">-</span>
</div>
</div>
</div>
</div>
</section>
</div>
<!-- /内容区域 -->
<!-- 版本信息 -->
<footer class="version-footer">
<div class="version-info">
<span data-i18n="footer.version">版本</span>: v0.1.0
<span class="separator"></span>
<span data-i18n="footer.author">作者</span>: Supra4E8C
</div>
</footer>
</div>
<!-- /主内容区域 -->
</div>
<!-- /主内容包装器 -->
</div>
<!-- /主页面 -->
<!-- 模态框 -->
<div id="modal" class="modal">
<div class="modal-content">
<span class="close">&times;</span>
<div id="modal-body"></div>
</div>
</div>
<!-- 通知 -->
<div id="notification" class="notification"></div>
</div>
<script>
// CLI Proxy API 管理界面 JavaScript
class CLIProxyManager {
constructor() {
// 仅保存基础地址(不含 /v0/management),请求时自动补齐
const detectedBase = this.detectApiBaseFromLocation();
this.apiBase = detectedBase;
this.apiUrl = this.computeApiUrl(this.apiBase);
this.managementKey = '';
this.isConnected = false;
this.isLoggedIn = false;
// 配置缓存
this.configCache = null;
this.cacheTimestamp = null;
this.cacheExpiry = 30000; // 30秒缓存过期时间
// 状态更新定时器
this.statusUpdateTimer = null;
// 主题管理
this.currentTheme = 'light';
this.init();
}
// 简易防抖,减少频繁写 localStorage
debounce(fn, delay = 400) {
let timer;
return (...args) => {
clearTimeout(timer);
timer = setTimeout(() => fn.apply(this, args), delay);
};
}
// 初始化主题
initializeTheme() {
// 从本地存储获取用户偏好主题
const savedTheme = localStorage.getItem('preferredTheme');
if (savedTheme && ['light', 'dark'].includes(savedTheme)) {
this.currentTheme = savedTheme;
} else {
if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
this.currentTheme = 'dark';
} else {
this.currentTheme = 'light';
}
}
this.applyTheme(this.currentTheme);
this.updateThemeButtons();
// 监听系统主题变化
if (window.matchMedia) {
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (e) => {
if (!localStorage.getItem('preferredTheme')) {
this.currentTheme = e.matches ? 'dark' : 'light';
this.applyTheme(this.currentTheme);
this.updateThemeButtons();
}
});
}
}
// 应用主题
applyTheme(theme) {
if (theme === 'dark') {
document.documentElement.setAttribute('data-theme', 'dark');
} else {
document.documentElement.removeAttribute('data-theme');
}
this.currentTheme = theme;
}
// 切换主题
toggleTheme() {
const newTheme = this.currentTheme === 'light' ? 'dark' : 'light';
this.applyTheme(newTheme);
this.updateThemeButtons();
localStorage.setItem('preferredTheme', newTheme);
}
// 更新主题按钮状态
updateThemeButtons() {
const loginThemeBtn = document.getElementById('theme-toggle');
const mainThemeBtn = document.getElementById('theme-toggle-main');
const updateButton = (btn) => {
if (!btn) return;
const icon = btn.querySelector('i');
if (this.currentTheme === 'dark') {
icon.className = 'fas fa-sun';
btn.title = i18n.t('theme.switch_to_light');
} else {
icon.className = 'fas fa-moon';
btn.title = i18n.t('theme.switch_to_dark');
}
};
updateButton(loginThemeBtn);
updateButton(mainThemeBtn);
}
init() {
this.initializeTheme();
this.checkLoginStatus();
this.bindEvents();
this.setupNavigation();
this.setupLanguageSwitcher();
this.setupThemeSwitcher();
// loadSettings 将在登录成功后调用
this.updateLoginConnectionInfo();
// 检查主机名,如果不是 localhost 或 127.0.0.1,则隐藏 OAuth 登录框
this.checkHostAndHideOAuth();
}
// 检查主机名并隐藏 OAuth 登录框
checkHostAndHideOAuth() {
const hostname = window.location.hostname;
const isLocalhost = hostname === 'localhost' || hostname === '127.0.0.1' || hostname === '::1';
if (!isLocalhost) {
// 隐藏所有 OAuth 登录卡片
const oauthCards = [
'codex-oauth-card',
'anthropic-oauth-card',
'gemini-cli-oauth-card',
'qwen-oauth-card',
'iflow-oauth-card'
];
oauthCards.forEach(cardId => {
const card = document.getElementById(cardId);
if (card) {
card.style.display = 'none';
}
});
// 如果找不到具体的卡片 ID,尝试通过类名查找
const oauthCardElements = document.querySelectorAll('.card');
oauthCardElements.forEach(card => {
const cardText = card.textContent || '';
if (cardText.includes('Codex OAuth') ||
cardText.includes('Anthropic OAuth') ||
cardText.includes('Gemini CLI OAuth') ||
cardText.includes('Qwen OAuth') ||
cardText.includes('iFlow OAuth')) {
card.style.display = 'none';
}
});
console.log(`当前主机名: ${hostname},已隐藏 OAuth 登录框`);
}
}
// 检查登录状态
async checkLoginStatus() {
// 检查是否有保存的连接信息
const savedBase = localStorage.getItem('apiBase');
const savedKey = localStorage.getItem('managementKey');
const wasLoggedIn = localStorage.getItem('isLoggedIn') === 'true';
// 如果有完整的连接信息且之前已登录,尝试自动登录
if (savedBase && savedKey && wasLoggedIn) {
try {
console.log('检测到本地连接数据,尝试自动登录...');
this.showAutoLoginLoading();
await this.attemptAutoLogin(savedBase, savedKey);
return; // 自动登录成功,不显示登录页面
} catch (error) {
console.log('自动登录失败:', error.message);
// 清除无效的登录状态
localStorage.removeItem('isLoggedIn');
this.hideAutoLoginLoading();
}
}
// 如果没有连接信息或自动登录失败,显示登录页面
this.showLoginPage();
this.loadLoginSettings();
}
// 显示自动登录加载页面
showAutoLoginLoading() {
document.getElementById('auto-login-loading').style.display = 'flex';
document.getElementById('login-page').style.display = 'none';
document.getElementById('main-page').style.display = 'none';
}
// 隐藏自动登录加载页面
hideAutoLoginLoading() {
document.getElementById('auto-login-loading').style.display = 'none';
}
// 尝试自动登录
async attemptAutoLogin(apiBase, managementKey) {
try {
// 设置API基础地址和密钥
this.setApiBase(apiBase);
this.managementKey = managementKey;
// 恢复代理设置(如果有)
const savedProxy = localStorage.getItem('proxyUrl');
if (savedProxy) {
// 代理设置会在后续的API请求中自动使用
}
// 测试连接
await this.testConnection();
// 自动登录成功
this.isLoggedIn = true;
this.hideAutoLoginLoading();
this.showMainPage();
console.log('自动登录成功');
return true;
} catch (error) {
console.error('自动登录失败:', error);
// 重置状态
this.isLoggedIn = false;
this.isConnected = false;
throw error;
}
}
// 显示登录页面
showLoginPage() {
document.getElementById('login-page').style.display = 'flex';
document.getElementById('main-page').style.display = 'none';
this.isLoggedIn = false;
this.updateLoginConnectionInfo();
}
// 显示主页面
showMainPage() {
document.getElementById('login-page').style.display = 'none';
document.getElementById('main-page').style.display = 'block';
this.isLoggedIn = true;
this.updateConnectionInfo();
}
// 登录验证
async login(apiBase, managementKey) {
try {
// 设置API基础地址和密钥
this.setApiBase(apiBase);
this.managementKey = managementKey;
localStorage.setItem('managementKey', this.managementKey);
// 测试连接并加载所有数据
await this.testConnection();
// 登录成功
this.isLoggedIn = true;
localStorage.setItem('isLoggedIn', 'true');
this.showMainPage();
// 不需要再调用loadSettings,因为内部状态已经在上面设置了
return true;
} catch (error) {
console.error('登录失败:', error);
throw error;
}
}
// 登出
logout() {
this.isLoggedIn = false;
this.isConnected = false;
this.clearCache();
this.stopStatusUpdateTimer();
// 清除本地存储
localStorage.removeItem('isLoggedIn');
localStorage.removeItem('managementKey');
this.showLoginPage();
}
// 处理登录表单提交
async handleLogin() {
const apiBaseInput = document.getElementById('login-api-base');
const managementKeyInput = document.getElementById('login-management-key');
const managementKey = managementKeyInput ? managementKeyInput.value.trim() : '';
if (!managementKey) {
this.showLoginError(i18n.t('login.error_required'));
return;
}
if (apiBaseInput && apiBaseInput.value.trim()) {
this.setApiBase(apiBaseInput.value.trim());
}
const submitBtn = document.getElementById('login-submit');
const originalText = submitBtn ? submitBtn.innerHTML : '';
try {
if (submitBtn) {
submitBtn.innerHTML = `<div class="loading"></div> ${i18n.t('login.submitting')}`;
submitBtn.disabled = true;
}
this.hideLoginError();
this.managementKey = managementKey;
localStorage.setItem('managementKey', this.managementKey);
await this.login(this.apiBase, this.managementKey);
} catch (error) {
this.showLoginError(`${i18n.t('login.error_title')}: ${error.message}`);
} finally {
if (submitBtn) {
submitBtn.innerHTML = originalText;
submitBtn.disabled = false;
}
}
}
// 切换登录页面密钥可见性
toggleLoginKeyVisibility(button) {
const inputGroup = button.closest('.input-group');
const keyInput = inputGroup.querySelector('input[type="password"], input[type="text"]');
if (keyInput.type === 'password') {
keyInput.type = 'text';
button.innerHTML = '<i class="fas fa-eye-slash"></i>';
} else {
keyInput.type = 'password';
button.innerHTML = '<i class="fas fa-eye"></i>';
}
}
// 显示登录错误
showLoginError(message) {
const errorDiv = document.getElementById('login-error');
const errorMessage = document.getElementById('login-error-message');
errorMessage.textContent = message;
errorDiv.style.display = 'flex';
}
// 隐藏登录错误
hideLoginError() {
const errorDiv = document.getElementById('login-error');
errorDiv.style.display = 'none';
}
// 更新连接信息显示
updateConnectionInfo() {
const apiUrlElement = document.getElementById('display-api-url');
const keyElement = document.getElementById('display-management-key');
const statusElement = document.getElementById('display-connection-status');
// 显示API地址
if (apiUrlElement) {
apiUrlElement.textContent = this.apiBase || '-';
}
// 显示密钥(遮蔽显示)
if (keyElement) {
if (this.managementKey) {
const maskedKey = this.maskApiKey(this.managementKey);
keyElement.textContent = maskedKey;
} else {
keyElement.textContent = '-';
}
}
// 显示连接状态
if (statusElement) {
let statusHtml = '';
if (this.isConnected) {
statusHtml = `<span class="status-indicator connected"><i class="fas fa-circle"></i> ${i18n.t('common.connected')}</span>`;
} else {
statusHtml = `<span class="status-indicator disconnected"><i class="fas fa-circle"></i> ${i18n.t('common.disconnected')}</span>`;
}
statusElement.innerHTML = statusHtml;
}
}
// 加载登录页面设置
loadLoginSettings() {
const savedBase = localStorage.getItem('apiBase');
const savedKey = localStorage.getItem('managementKey');
const loginKeyInput = document.getElementById('login-management-key');
const apiBaseInput = document.getElementById('login-api-base');
if (savedBase) {
this.setApiBase(savedBase);
} else {
this.setApiBase(this.detectApiBaseFromLocation());
}
if (apiBaseInput) {
apiBaseInput.value = this.apiBase || '';
}
if (loginKeyInput && savedKey) {
loginKeyInput.value = savedKey;
}
this.setupLoginAutoSave();
}
setupLoginAutoSave() {
const loginKeyInput = document.getElementById('login-management-key');
const apiBaseInput = document.getElementById('login-api-base');
const resetButton = document.getElementById('login-reset-api-base');
const saveKey = (val) => {
if (val.trim()) {
this.managementKey = val;
localStorage.setItem('managementKey', this.managementKey);
}
};
const saveKeyDebounced = this.debounce(saveKey, 500);
if (loginKeyInput) {
loginKeyInput.addEventListener('change', (e) => saveKey(e.target.value));
loginKeyInput.addEventListener('input', (e) => saveKeyDebounced(e.target.value));
}
if (apiBaseInput) {
const persistBase = (val) => {
const normalized = this.normalizeBase(val);
if (normalized) {
this.setApiBase(normalized);
}
};
const persistBaseDebounced = this.debounce(persistBase, 500);
apiBaseInput.addEventListener('change', (e) => persistBase(e.target.value));
apiBaseInput.addEventListener('input', (e) => persistBaseDebounced(e.target.value));
}
if (resetButton) {
resetButton.addEventListener('click', () => {
const detected = this.detectApiBaseFromLocation();
this.setApiBase(detected);
if (apiBaseInput) {
apiBaseInput.value = detected;
}
});
}
}
// 事件绑定
bindEvents() {
// 登录相关(安全绑定)
const loginSubmit = document.getElementById('login-submit');
const logoutBtn = document.getElementById('logout-btn');
if (loginSubmit) {
loginSubmit.addEventListener('click', () => this.handleLogin());
}
if (logoutBtn) {
logoutBtn.addEventListener('click', () => this.logout());
}
// 密钥可见性切换事件
this.setupKeyVisibilityToggle();
// 主页面元素(延迟绑定,在显示主页面时绑定)
this.bindMainPageEvents();
}
// 设置密钥可见性切换
setupKeyVisibilityToggle() {
const toggleButtons = document.querySelectorAll('.toggle-key-visibility');
toggleButtons.forEach(button => {
button.addEventListener('click', () => this.toggleLoginKeyVisibility(button));
});
}
// 绑定主页面事件
bindMainPageEvents() {
// 连接状态检查
const connectionStatus = document.getElementById('connection-status');
const refreshAll = document.getElementById('refresh-all');
if (connectionStatus) {
connectionStatus.addEventListener('click', () => this.checkConnectionStatus());
}
if (refreshAll) {
refreshAll.addEventListener('click', () => this.refreshAllData());
}
// 基础设置
const debugToggle = document.getElementById('debug-toggle');
const updateProxy = document.getElementById('update-proxy');
const clearProxy = document.getElementById('clear-proxy');
const updateRetry = document.getElementById('update-retry');
const switchProjectToggle = document.getElementById('switch-project-toggle');
const switchPreviewToggle = document.getElementById('switch-preview-model-toggle');
if (debugToggle) {
debugToggle.addEventListener('change', (e) => this.updateDebug(e.target.checked));
}
if (updateProxy) {
updateProxy.addEventListener('click', () => this.updateProxyUrl());
}
if (clearProxy) {
clearProxy.addEventListener('click', () => this.clearProxyUrl());
}
if (updateRetry) {
updateRetry.addEventListener('click', () => this.updateRequestRetry());
}
if (switchProjectToggle) {
switchProjectToggle.addEventListener('change', (e) => this.updateSwitchProject(e.target.checked));
}
if (switchPreviewToggle) {
switchPreviewToggle.addEventListener('change', (e) => this.updateSwitchPreviewModel(e.target.checked));
}
// API 密钥管理
const addApiKey = document.getElementById('add-api-key');
const addGeminiKey = document.getElementById('add-gemini-key');
const addCodexKey = document.getElementById('add-codex-key');
const addClaudeKey = document.getElementById('add-claude-key');
const addOpenaiProvider = document.getElementById('add-openai-provider');
if (addApiKey) {
addApiKey.addEventListener('click', () => this.showAddApiKeyModal());
}
if (addGeminiKey) {
addGeminiKey.addEventListener('click', () => this.showAddGeminiKeyModal());
}
if (addCodexKey) {
addCodexKey.addEventListener('click', () => this.showAddCodexKeyModal());
}
if (addClaudeKey) {
addClaudeKey.addEventListener('click', () => this.showAddClaudeKeyModal());
}
if (addOpenaiProvider) {
addOpenaiProvider.addEventListener('click', () => this.showAddOpenAIProviderModal());
}
// Gemini Web Token
const geminiWebTokenBtn = document.getElementById('gemini-web-token-btn');
if (geminiWebTokenBtn) {
geminiWebTokenBtn.addEventListener('click', () => this.showGeminiWebTokenModal());
}
// 认证文件管理
const uploadAuthFile = document.getElementById('upload-auth-file');
const deleteAllAuthFiles = document.getElementById('delete-all-auth-files');
const authFileInput = document.getElementById('auth-file-input');
if (uploadAuthFile) {
uploadAuthFile.addEventListener('click', () => this.uploadAuthFile());
}
if (deleteAllAuthFiles) {
deleteAllAuthFiles.addEventListener('click', () => this.deleteAllAuthFiles());
}
if (authFileInput) {
authFileInput.addEventListener('change', (e) => this.handleFileUpload(e));
}
// Codex OAuth
const codexOauthBtn = document.getElementById('codex-oauth-btn');
const codexOpenLink = document.getElementById('codex-open-link');
const codexCopyLink = document.getElementById('codex-copy-link');
if (codexOauthBtn) {
codexOauthBtn.addEventListener('click', () => this.startCodexOAuth());
}
if (codexOpenLink) {
codexOpenLink.addEventListener('click', () => this.openCodexLink());
}
if (codexCopyLink) {
codexCopyLink.addEventListener('click', () => this.copyCodexLink());
}
// Anthropic OAuth
const anthropicOauthBtn = document.getElementById('anthropic-oauth-btn');
const anthropicOpenLink = document.getElementById('anthropic-open-link');
const anthropicCopyLink = document.getElementById('anthropic-copy-link');
if (anthropicOauthBtn) {
anthropicOauthBtn.addEventListener('click', () => this.startAnthropicOAuth());
}
if (anthropicOpenLink) {
anthropicOpenLink.addEventListener('click', () => this.openAnthropicLink());
}
if (anthropicCopyLink) {
anthropicCopyLink.addEventListener('click', () => this.copyAnthropicLink());
}
// Gemini CLI OAuth
const geminiCliOauthBtn = document.getElementById('gemini-cli-oauth-btn');
const geminiCliOpenLink = document.getElementById('gemini-cli-open-link');
const geminiCliCopyLink = document.getElementById('gemini-cli-copy-link');
if (geminiCliOauthBtn) {
geminiCliOauthBtn.addEventListener('click', () => this.startGeminiCliOAuth());
}
if (geminiCliOpenLink) {
geminiCliOpenLink.addEventListener('click', () => this.openGeminiCliLink());
}
if (geminiCliCopyLink) {
geminiCliCopyLink.addEventListener('click', () => this.copyGeminiCliLink());
}
// Qwen OAuth
const qwenOauthBtn = document.getElementById('qwen-oauth-btn');
const qwenOpenLink = document.getElementById('qwen-open-link');
const qwenCopyLink = document.getElementById('qwen-copy-link');
if (qwenOauthBtn) {
qwenOauthBtn.addEventListener('click', () => this.startQwenOAuth());
}
if (qwenOpenLink) {
qwenOpenLink.addEventListener('click', () => this.openQwenLink());
}
if (qwenCopyLink) {
qwenCopyLink.addEventListener('click', () => this.copyQwenLink());
}
// iFlow OAuth
const iflowOauthBtn = document.getElementById('iflow-oauth-btn');
const iflowOpenLink = document.getElementById('iflow-open-link');
const iflowCopyLink = document.getElementById('iflow-copy-link');
if (iflowOauthBtn) {
iflowOauthBtn.addEventListener('click', () => this.startIflowOAuth());
}
if (iflowOpenLink) {
iflowOpenLink.addEventListener('click', () => this.openIflowLink());
}
if (iflowCopyLink) {
iflowCopyLink.addEventListener('click', () => this.copyIflowLink());
}
// 使用统计
const refreshUsageStats = document.getElementById('refresh-usage-stats');
const requestsHourBtn = document.getElementById('requests-hour-btn');
const requestsDayBtn = document.getElementById('requests-day-btn');
const tokensHourBtn = document.getElementById('tokens-hour-btn');
const tokensDayBtn = document.getElementById('tokens-day-btn');
if (refreshUsageStats) {
refreshUsageStats.addEventListener('click', () => this.loadUsageStats());
}
if (requestsHourBtn) {
requestsHourBtn.addEventListener('click', () => this.switchRequestsPeriod('hour'));
}
if (requestsDayBtn) {
requestsDayBtn.addEventListener('click', () => this.switchRequestsPeriod('day'));
}
if (tokensHourBtn) {
tokensHourBtn.addEventListener('click', () => this.switchTokensPeriod('hour'));
}
if (tokensDayBtn) {
tokensDayBtn.addEventListener('click', () => this.switchTokensPeriod('day'));
}
// 模态框
const closeBtn = document.querySelector('.close');
if (closeBtn) {
closeBtn.addEventListener('click', () => this.closeModal());
}
window.addEventListener('click', (e) => {
const modal = document.getElementById('modal');
if (modal && e.target === modal) {
this.closeModal();
}
});
// 移动端菜单按钮
const mobileMenuBtn = document.getElementById('mobile-menu-btn');
const sidebarOverlay = document.getElementById('sidebar-overlay');
const sidebar = document.getElementById('sidebar');
if (mobileMenuBtn) {
mobileMenuBtn.addEventListener('click', () => this.toggleMobileSidebar());
}
if (sidebarOverlay) {
sidebarOverlay.addEventListener('click', () => this.closeMobileSidebar());
}
// 侧边栏收起/展开按钮(桌面端)
const sidebarToggleBtnDesktop = document.getElementById('sidebar-toggle-btn-desktop');
if (sidebarToggleBtnDesktop) {
sidebarToggleBtnDesktop.addEventListener('click', () => this.toggleSidebar());
}
// 从本地存储恢复侧边栏状态
this.restoreSidebarState();
// 监听窗口大小变化
window.addEventListener('resize', () => {
const sidebar = document.getElementById('sidebar');
const layout = document.getElementById('layout-container');
if (window.innerWidth <= 1024) {
// 移动端:移除收起状态
if (sidebar && layout) {
sidebar.classList.remove('collapsed');
layout.classList.remove('sidebar-collapsed');
}
} else {
// 桌面端:恢复保存的状态
this.restoreSidebarState();
}
});
// 点击侧边栏导航项时在移动端关闭侧边栏
const navItems = document.querySelectorAll('.nav-item');
navItems.forEach(item => {
item.addEventListener('click', () => {
if (window.innerWidth <= 1024) {
this.closeMobileSidebar();
}
});
});
}
// 切换移动端侧边栏
toggleMobileSidebar() {
const sidebar = document.getElementById('sidebar');
const overlay = document.getElementById('sidebar-overlay');
const layout = document.getElementById('layout-container');
const mainWrapper = document.getElementById('main-wrapper');
if (sidebar && overlay) {
const isOpen = sidebar.classList.toggle('mobile-open');
overlay.classList.toggle('active');
if (layout) {
layout.classList.toggle('sidebar-open', isOpen);
}
if (mainWrapper) {
mainWrapper.classList.toggle('sidebar-open', isOpen);
}
}
}
// 关闭移动端侧边栏
closeMobileSidebar() {
const sidebar = document.getElementById('sidebar');
const overlay = document.getElementById('sidebar-overlay');
const layout = document.getElementById('layout-container');
const mainWrapper = document.getElementById('main-wrapper');
if (sidebar && overlay) {
sidebar.classList.remove('mobile-open');
overlay.classList.remove('active');
if (layout) {
layout.classList.remove('sidebar-open');
}
if (mainWrapper) {
mainWrapper.classList.remove('sidebar-open');
}
}
}
// 切换侧边栏收起/展开状态
toggleSidebar() {
const sidebar = document.getElementById('sidebar');
const layout = document.getElementById('layout-container');
if (sidebar && layout) {
const isCollapsed = sidebar.classList.toggle('collapsed');
layout.classList.toggle('sidebar-collapsed', isCollapsed);
// 保存状态到本地存储
localStorage.setItem('sidebarCollapsed', isCollapsed ? 'true' : 'false');
// 更新按钮提示文本
const toggleBtn = document.getElementById('sidebar-toggle-btn-desktop');
if (toggleBtn) {
toggleBtn.setAttribute('title', isCollapsed ? '展开侧边栏' : '收起侧边栏');
}
}
}
// 恢复侧边栏状态
restoreSidebarState() {
// 只在桌面端恢复侧栏状态
if (window.innerWidth > 1024) {
const savedState = localStorage.getItem('sidebarCollapsed');
if (savedState === 'true') {
const sidebar = document.getElementById('sidebar');
const layout = document.getElementById('layout-container');
if (sidebar && layout) {
sidebar.classList.add('collapsed');
layout.classList.add('sidebar-collapsed');
// 更新按钮提示文本
const toggleBtn = document.getElementById('sidebar-toggle-btn-desktop');
if (toggleBtn) {
toggleBtn.setAttribute('title', '展开侧边栏');
}
}
}
}
}
// 设置导航
setupNavigation() {
const navItems = document.querySelectorAll('.nav-item');
navItems.forEach(item => {
item.addEventListener('click', (e) => {
e.preventDefault();
// 移除所有活动状态
navItems.forEach(nav => nav.classList.remove('active'));
document.querySelectorAll('.content-section').forEach(section => section.classList.remove('active'));
// 添加活动状态
item.classList.add('active');
const sectionId = item.getAttribute('data-section');
document.getElementById(sectionId).classList.add('active');
});
});
}
// 设置语言切换
setupLanguageSwitcher() {
const loginToggle = document.getElementById('language-toggle');
const mainToggle = document.getElementById('language-toggle-main');
if (loginToggle) {
loginToggle.addEventListener('click', () => this.toggleLanguage());
}
if (mainToggle) {
mainToggle.addEventListener('click', () => this.toggleLanguage());
}
}
// 设置主题切换
setupThemeSwitcher() {
const loginToggle = document.getElementById('theme-toggle');
const mainToggle = document.getElementById('theme-toggle-main');
if (loginToggle) {
loginToggle.addEventListener('click', () => this.toggleTheme());
}
if (mainToggle) {
mainToggle.addEventListener('click', () => this.toggleTheme());
}
}
// 切换语言
toggleLanguage() {
const currentLang = i18n.currentLanguage;
const newLang = currentLang === 'zh-CN' ? 'en-US' : 'zh-CN';
i18n.setLanguage(newLang);
// 更新主题按钮文本
this.updateThemeButtons();
// 更新连接状态显示
this.updateConnectionStatus();
// 重新加载所有数据以更新动态内容
if (this.isLoggedIn && this.isConnected) {
this.loadAllData(true);
}
}
// 规范化基础地址,移除尾部斜杠与 /v0/management
normalizeBase(input) {
let base = (input || '').trim();
if (!base) return '';
// 若用户粘贴了完整地址,剥离后缀
base = base.replace(/\/?v0\/management\/?$/i, '');
base = base.replace(/\/+$/i, '');
// 自动补 http://
if (!/^https?:\/\//i.test(base)) {
base = 'http://' + base;
}
return base;
}
// 由基础地址生成完整管理 API 地址
computeApiUrl(base) {
const b = this.normalizeBase(base);
if (!b) return '';
return b.replace(/\/$/, '') + '/v0/management';
}
setApiBase(newBase) {
this.apiBase = this.normalizeBase(newBase);
this.apiUrl = this.computeApiUrl(this.apiBase);
localStorage.setItem('apiBase', this.apiBase);
localStorage.setItem('apiUrl', this.apiUrl); // 兼容旧字段
this.updateLoginConnectionInfo();
}
// 加载设置(简化版,仅加载内部状态)
loadSettings() {
const savedBase = localStorage.getItem('apiBase');
const savedUrl = localStorage.getItem('apiUrl');
const savedKey = localStorage.getItem('managementKey');
if (savedBase) {
this.setApiBase(savedBase);
} else if (savedUrl) {
const base = (savedUrl || '').replace(/\/?v0\/management\/?$/i, '');
this.setApiBase(base);
} else {
this.setApiBase(this.detectApiBaseFromLocation());
}
if (savedKey) {
this.managementKey = savedKey;
}
this.updateLoginConnectionInfo();
}
// API 请求方法
async makeRequest(endpoint, options = {}) {
const url = `${this.apiUrl}${endpoint}`;
const headers = {
'Authorization': `Bearer ${this.managementKey}`,
'Content-Type': 'application/json',
...options.headers
};
try {
const response = await fetch(url, {
...options,
headers
});
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
throw new Error(errorData.error || `HTTP ${response.status}`);
}
return await response.json();
} catch (error) {
console.error('API请求失败:', error);
throw error;
}
}
// 显示通知
showNotification(message, type = 'info') {
const notification = document.getElementById('notification');
notification.textContent = message;
notification.className = `notification ${type}`;
notification.classList.add('show');
setTimeout(() => {
notification.classList.remove('show');
}, 3000);
}
// 密钥可见性切换
toggleKeyVisibility() {
const keyInput = document.getElementById('management-key');
const toggleButton = document.getElementById('toggle-key-visibility');
if (keyInput.type === 'password') {
keyInput.type = 'text';
toggleButton.innerHTML = '<i class="fas fa-eye-slash"></i>';
} else {
keyInput.type = 'password';
toggleButton.innerHTML = '<i class="fas fa-eye"></i>';
}
}
// 测试连接(简化版,用于内部调用)
async testConnection() {
try {
await this.makeRequest('/debug');
this.isConnected = true;
this.updateConnectionStatus();
this.startStatusUpdateTimer();
await this.loadAllData();
return true;
} catch (error) {
this.isConnected = false;
this.updateConnectionStatus();
this.stopStatusUpdateTimer();
throw error;
}
}
// 更新连接状态
updateConnectionStatus() {
const statusButton = document.getElementById('connection-status');
const apiStatus = document.getElementById('api-status');
const configStatus = document.getElementById('config-status');
const lastUpdate = document.getElementById('last-update');
if (this.isConnected) {
statusButton.innerHTML = `<i class="fas fa-circle connection-indicator connected"></i> ${i18n.t('common.connected')}`;
statusButton.className = 'btn btn-success';
apiStatus.textContent = i18n.t('common.connected');
// 更新配置状态
if (this.isCacheValid()) {
const cacheAge = Math.floor((Date.now() - this.cacheTimestamp) / 1000);
configStatus.textContent = `${i18n.t('system_info.cache_data')} (${cacheAge}${i18n.t('system_info.seconds_ago')})`;
configStatus.style.color = '#f59e0b'; // 橙色表示缓存
} else if (this.configCache) {
configStatus.textContent = i18n.t('system_info.real_time_data');
configStatus.style.color = '#10b981'; // 绿色表示实时
} else {
configStatus.textContent = i18n.t('system_info.not_loaded');
configStatus.style.color = '#6b7280'; // 灰色表示未加载
}
} else {
statusButton.innerHTML = `<i class="fas fa-circle connection-indicator disconnected"></i> ${i18n.t('common.disconnected')}`;
statusButton.className = 'btn btn-danger';
apiStatus.textContent = i18n.t('common.disconnected');
configStatus.textContent = i18n.t('system_info.not_loaded');
configStatus.style.color = '#6b7280';
}
lastUpdate.textContent = new Date().toLocaleString('zh-CN');
// 更新连接信息显示
this.updateConnectionInfo();
}
// 检查连接状态
async checkConnectionStatus() {
await this.testConnection();
}
// 刷新所有数据
async refreshAllData() {
if (!this.isConnected) {
this.showNotification(i18n.t('notification.connection_required'), 'error');
return;
}
const button = document.getElementById('refresh-all');
const originalText = button.innerHTML;
button.innerHTML = `<div class="loading"></div> ${i18n.t('common.loading')}`;
button.disabled = true;
try {
// 强制刷新,清除缓存
await this.loadAllData(true);
this.showNotification(i18n.t('notification.data_refreshed'), 'success');
} catch (error) {
this.showNotification(`${i18n.t('notification.refresh_failed')}: ${error.message}`, 'error');
} finally {
button.innerHTML = originalText;
button.disabled = false;
}
}
// 检查缓存是否有效
isCacheValid() {
if (!this.configCache || !this.cacheTimestamp) {
return false;
}
return (Date.now() - this.cacheTimestamp) < this.cacheExpiry;
}
// 获取配置(优先使用缓存)
async getConfig(forceRefresh = false) {
if (!forceRefresh && this.isCacheValid()) {
this.updateConnectionStatus(); // 更新状态显示
return this.configCache;
}
try {
const config = await this.makeRequest('/config');
this.configCache = config;
this.cacheTimestamp = Date.now();
this.updateConnectionStatus(); // 更新状态显示
return config;
} catch (error) {
console.error('获取配置失败:', error);
throw error;
}
}
// 清除缓存
clearCache() {
this.configCache = null;
this.cacheTimestamp = null;
}
// 启动状态更新定时器
startStatusUpdateTimer() {
if (this.statusUpdateTimer) {
clearInterval(this.statusUpdateTimer);
}
this.statusUpdateTimer = setInterval(() => {
if (this.isConnected) {
this.updateConnectionStatus();
}
}, 1000); // 每秒更新一次
}
// 停止状态更新定时器
stopStatusUpdateTimer() {
if (this.statusUpdateTimer) {
clearInterval(this.statusUpdateTimer);
this.statusUpdateTimer = null;
}
}
// 加载所有数据 - 使用新的 /config 端点一次性获取所有配置
async loadAllData(forceRefresh = false) {
try {
console.log('使用新的 /config 端点加载所有配置...');
// 使用新的 /config 端点一次性获取所有配置
const config = await this.getConfig(forceRefresh);
// 从配置中提取并设置各个设置项
this.updateSettingsFromConfig(config);
// 认证文件需要单独加载,因为不在配置中
await this.loadAuthFiles();
// 使用统计需要单独加载
await this.loadUsageStats();
console.log('配置加载完成,使用缓存:', !forceRefresh && this.isCacheValid());
} catch (error) {
console.error('加载配置失败:', error);
console.log('回退到逐个加载方式...');
// 如果新方法失败,回退到原来的逐个加载方式
await this.loadAllDataLegacy();
}
}
// 从配置对象更新所有设置
updateSettingsFromConfig(config) {
// 调试设置
if (config.debug !== undefined) {
document.getElementById('debug-toggle').checked = config.debug;
}
// 代理设置
if (config['proxy-url'] !== undefined) {
document.getElementById('proxy-url').value = config['proxy-url'] || '';
}
// 请求重试设置
if (config['request-retry'] !== undefined) {
document.getElementById('request-retry').value = config['request-retry'];
}
// 配额超出行为
if (config['quota-exceeded']) {
if (config['quota-exceeded']['switch-project'] !== undefined) {
document.getElementById('switch-project-toggle').checked = config['quota-exceeded']['switch-project'];
}
if (config['quota-exceeded']['switch-preview-model'] !== undefined) {
document.getElementById('switch-preview-model-toggle').checked = config['quota-exceeded']['switch-preview-model'];
}
}
// API 密钥
if (config['api-keys']) {
this.renderApiKeys(config['api-keys']);
}
// Gemini 密钥
if (config['generative-language-api-key']) {
this.renderGeminiKeys(config['generative-language-api-key']);
}
// Codex 密钥
if (config['codex-api-key']) {
this.renderCodexKeys(config['codex-api-key']);
}
// Claude 密钥
if (config['claude-api-key']) {
this.renderClaudeKeys(config['claude-api-key']);
}
// OpenAI 兼容提供商
if (config['openai-compatibility']) {
this.renderOpenAIProviders(config['openai-compatibility']);
}
}
// 回退方法:原来的逐个加载方式
async loadAllDataLegacy() {
await Promise.all([
this.loadDebugSettings(),
this.loadProxySettings(),
this.loadRetrySettings(),
this.loadQuotaSettings(),
this.loadApiKeys(),
this.loadGeminiKeys(),
this.loadCodexKeys(),
this.loadClaudeKeys(),
this.loadOpenAIProviders(),
this.loadAuthFiles()
]);
}
// 加载调试设置
async loadDebugSettings() {
try {
const config = await this.getConfig();
if (config.debug !== undefined) {
document.getElementById('debug-toggle').checked = config.debug;
}
} catch (error) {
console.error('加载调试设置失败:', error);
}
}
// 更新调试设置
async updateDebug(enabled) {
try {
await this.makeRequest('/debug', {
method: 'PUT',
body: JSON.stringify({ value: enabled })
});
this.clearCache(); // 清除缓存
this.showNotification(i18n.t('notification.debug_updated'), 'success');
} catch (error) {
this.showNotification(`${i18n.t('notification.update_failed')}: ${error.message}`, 'error');
// 恢复原状态
document.getElementById('debug-toggle').checked = !enabled;
}
}
// 加载代理设置
async loadProxySettings() {
try {
const config = await this.getConfig();
if (config['proxy-url'] !== undefined) {
document.getElementById('proxy-url').value = config['proxy-url'] || '';
}
} catch (error) {
console.error('加载代理设置失败:', error);
}
}
// 更新代理URL
async updateProxyUrl() {
const proxyUrl = document.getElementById('proxy-url').value.trim();
try {
await this.makeRequest('/proxy-url', {
method: 'PUT',
body: JSON.stringify({ value: proxyUrl })
});
this.clearCache(); // 清除缓存
this.showNotification(i18n.t('notification.proxy_updated'), 'success');
} catch (error) {
this.showNotification(`${i18n.t('notification.update_failed')}: ${error.message}`, 'error');
}
}
// 清空代理URL
async clearProxyUrl() {
try {
await this.makeRequest('/proxy-url', { method: 'DELETE' });
document.getElementById('proxy-url').value = '';
this.clearCache(); // 清除缓存
this.showNotification(i18n.t('notification.proxy_cleared'), 'success');
} catch (error) {
this.showNotification(`${i18n.t('notification.update_failed')}: ${error.message}`, 'error');
}
}
// 加载重试设置
async loadRetrySettings() {
try {
const config = await this.getConfig();
if (config['request-retry'] !== undefined) {
document.getElementById('request-retry').value = config['request-retry'];
}
} catch (error) {
console.error('加载重试设置失败:', error);
}
}
// 更新请求重试
async updateRequestRetry() {
const retryCount = parseInt(document.getElementById('request-retry').value);
try {
await this.makeRequest('/request-retry', {
method: 'PUT',
body: JSON.stringify({ value: retryCount })
});
this.clearCache(); // 清除缓存
this.showNotification(i18n.t('notification.retry_updated'), 'success');
} catch (error) {
this.showNotification(`${i18n.t('notification.update_failed')}: ${error.message}`, 'error');
}
}
// 加载配额设置
async loadQuotaSettings() {
try {
const config = await this.getConfig();
if (config['quota-exceeded']) {
if (config['quota-exceeded']['switch-project'] !== undefined) {
document.getElementById('switch-project-toggle').checked = config['quota-exceeded']['switch-project'];
}
if (config['quota-exceeded']['switch-preview-model'] !== undefined) {
document.getElementById('switch-preview-model-toggle').checked = config['quota-exceeded']['switch-preview-model'];
}
}
} catch (error) {
console.error('加载配额设置失败:', error);
}
}
// 更新项目切换设置
async updateSwitchProject(enabled) {
try {
await this.makeRequest('/quota-exceeded/switch-project', {
method: 'PUT',
body: JSON.stringify({ value: enabled })
});
this.clearCache(); // 清除缓存
this.showNotification(i18n.t('notification.quota_switch_project_updated'), 'success');
} catch (error) {
this.showNotification(`${i18n.t('notification.update_failed')}: ${error.message}`, 'error');
document.getElementById('switch-project-toggle').checked = !enabled;
}
}
// 更新预览模型切换设置
async updateSwitchPreviewModel(enabled) {
try {
await this.makeRequest('/quota-exceeded/switch-preview-model', {
method: 'PUT',
body: JSON.stringify({ value: enabled })
});
this.clearCache(); // 清除缓存
this.showNotification(i18n.t('notification.quota_switch_preview_updated'), 'success');
} catch (error) {
this.showNotification(`${i18n.t('notification.update_failed')}: ${error.message}`, 'error');
document.getElementById('switch-preview-model-toggle').checked = !enabled;
}
}
// 加载API密钥
async loadApiKeys() {
try {
const config = await this.getConfig();
if (config['api-keys']) {
this.renderApiKeys(config['api-keys']);
}
} catch (error) {
console.error('加载API密钥失败:', error);
}
}
// 渲染API密钥列表
renderApiKeys(keys) {
const container = document.getElementById('api-keys-list');
if (keys.length === 0) {
container.innerHTML = `
<div class="empty-state">
<i class="fas fa-key"></i>
<h3>${i18n.t('api_keys.empty_title')}</h3>
<p>${i18n.t('api_keys.empty_desc')}</p>
</div>
`;
return;
}
container.innerHTML = keys.map((key, index) => `
<div class="key-item">
<div class="item-content">
<div class="item-title">${i18n.t('api_keys.item_title')} #${index + 1}</div>
<div class="item-value">${this.maskApiKey(key)}</div>
</div>
<div class="item-actions">
<button class="btn btn-secondary" onclick="manager.editApiKey(${index}, '${key}')">
<i class="fas fa-edit"></i>
</button>
<button class="btn btn-danger" onclick="manager.deleteApiKey(${index})">
<i class="fas fa-trash"></i>
</button>
</div>
</div>
`).join('');
}
// 遮蔽API密钥显示
maskApiKey(key) {
if (key.length <= 8) return key;
return key.substring(0, 4) + '...' + key.substring(key.length - 4);
}
// HTML 转义,防止 XSS
escapeHtml(value) {
if (value === null || value === undefined) return '';
return String(value)
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;');
}
// 显示添加API密钥模态框
showAddApiKeyModal() {
const modal = document.getElementById('modal');
const modalBody = document.getElementById('modal-body');
modalBody.innerHTML = `
<h3>${i18n.t('api_keys.add_modal_title')}</h3>
<div class="form-group">
<label for="new-api-key">${i18n.t('api_keys.add_modal_key_label')}</label>
<input type="text" id="new-api-key" placeholder="${i18n.t('api_keys.add_modal_key_placeholder')}">
</div>
<div class="modal-actions">
<button class="btn btn-secondary" onclick="manager.closeModal()">${i18n.t('common.cancel')}</button>
<button class="btn btn-primary" onclick="manager.addApiKey()">${i18n.t('common.add')}</button>
</div>
`;
modal.style.display = 'block';
}
// 添加API密钥
async addApiKey() {
const newKey = document.getElementById('new-api-key').value.trim();
if (!newKey) {
this.showNotification(`${i18n.t('notification.please_enter')} ${i18n.t('notification.api_key')}`, 'error');
return;
}
try {
const data = await this.makeRequest('/api-keys');
const currentKeys = data['api-keys'] || [];
currentKeys.push(newKey);
await this.makeRequest('/api-keys', {
method: 'PUT',
body: JSON.stringify(currentKeys)
});
this.clearCache(); // 清除缓存
this.closeModal();
this.loadApiKeys();
this.showNotification(i18n.t('notification.api_key_added'), 'success');
} catch (error) {
this.showNotification(`${i18n.t('notification.add_failed')}: ${error.message}`, 'error');
}
}
// 编辑API密钥
editApiKey(index, currentKey) {
const modal = document.getElementById('modal');
const modalBody = document.getElementById('modal-body');
modalBody.innerHTML = `
<h3>${i18n.t('api_keys.edit_modal_title')}</h3>
<div class="form-group">
<label for="edit-api-key">${i18n.t('api_keys.edit_modal_key_label')}</label>
<input type="text" id="edit-api-key" value="${currentKey}">
</div>
<div class="modal-actions">
<button class="btn btn-secondary" onclick="manager.closeModal()">${i18n.t('common.cancel')}</button>
<button class="btn btn-primary" onclick="manager.updateApiKey(${index})">${i18n.t('common.update')}</button>
</div>
`;
modal.style.display = 'block';
}
// 更新API密钥
async updateApiKey(index) {
const newKey = document.getElementById('edit-api-key').value.trim();
if (!newKey) {
this.showNotification(`${i18n.t('notification.please_enter')} ${i18n.t('notification.api_key')}`, 'error');
return;
}
try {
await this.makeRequest('/api-keys', {
method: 'PATCH',
body: JSON.stringify({ index, value: newKey })
});
this.clearCache(); // 清除缓存
this.closeModal();
this.loadApiKeys();
this.showNotification(i18n.t('notification.api_key_updated'), 'success');
} catch (error) {
this.showNotification(`${i18n.t('notification.update_failed')}: ${error.message}`, 'error');
}
}
// 删除API密钥
async deleteApiKey(index) {
if (!confirm(i18n.t('api_keys.delete_confirm'))) return;
try {
await this.makeRequest(`/api-keys?index=${index}`, { method: 'DELETE' });
this.clearCache(); // 清除缓存
this.loadApiKeys();
this.showNotification(i18n.t('notification.api_key_deleted'), 'success');
} catch (error) {
this.showNotification(`${i18n.t('notification.delete_failed')}: ${error.message}`, 'error');
}
}
// 加载Gemini密钥
async loadGeminiKeys() {
try {
const config = await this.getConfig();
if (config['generative-language-api-key']) {
this.renderGeminiKeys(config['generative-language-api-key']);
}
} catch (error) {
console.error('加载Gemini密钥失败:', error);
}
}
// 渲染Gemini密钥列表
renderGeminiKeys(keys) {
const container = document.getElementById('gemini-keys-list');
if (keys.length === 0) {
container.innerHTML = `
<div class="empty-state">
<i class="fab fa-google"></i>
<h3>${i18n.t('ai_providers.gemini_empty_title')}</h3>
<p>${i18n.t('ai_providers.gemini_empty_desc')}</p>
</div>
`;
return;
}
container.innerHTML = keys.map((key, index) => `
<div class="key-item">
<div class="item-content">
<div class="item-title">${i18n.t('ai_providers.gemini_item_title')} #${index + 1}</div>
<div class="item-value">${this.maskApiKey(key)}</div>
</div>
<div class="item-actions">
<button class="btn btn-secondary" onclick="manager.editGeminiKey(${index}, '${key}')">
<i class="fas fa-edit"></i>
</button>
<button class="btn btn-danger" onclick="manager.deleteGeminiKey('${key}')">
<i class="fas fa-trash"></i>
</button>
</div>
</div>
`).join('');
}
// 显示添加Gemini密钥模态框
showAddGeminiKeyModal() {
const modal = document.getElementById('modal');
const modalBody = document.getElementById('modal-body');
modalBody.innerHTML = `
<h3>添加Gemini API密钥</h3>
<div class="form-group">
<label for="new-gemini-key">API密钥:</label>
<input type="text" id="new-gemini-key" placeholder="请输入Gemini API密钥">
</div>
<div class="modal-actions">
<button class="btn btn-secondary" onclick="manager.closeModal()">取消</button>
<button class="btn btn-primary" onclick="manager.addGeminiKey()">添加</button>
</div>
`;
modal.style.display = 'block';
}
// 添加Gemini密钥
async addGeminiKey() {
const newKey = document.getElementById('new-gemini-key').value.trim();
if (!newKey) {
this.showNotification('请输入Gemini API密钥', 'error');
return;
}
try {
const data = await this.makeRequest('/generative-language-api-key');
const currentKeys = data['generative-language-api-key'] || [];
currentKeys.push(newKey);
await this.makeRequest('/generative-language-api-key', {
method: 'PUT',
body: JSON.stringify(currentKeys)
});
this.clearCache(); // 清除缓存
this.closeModal();
this.loadGeminiKeys();
this.showNotification('Gemini密钥添加成功', 'success');
} catch (error) {
this.showNotification(`添加Gemini密钥失败: ${error.message}`, 'error');
}
}
// 编辑Gemini密钥
editGeminiKey(index, currentKey) {
const modal = document.getElementById('modal');
const modalBody = document.getElementById('modal-body');
modalBody.innerHTML = `
<h3>编辑Gemini API密钥</h3>
<div class="form-group">
<label for="edit-gemini-key">API密钥:</label>
<input type="text" id="edit-gemini-key" value="${currentKey}">
</div>
<div class="modal-actions">
<button class="btn btn-secondary" onclick="manager.closeModal()">取消</button>
<button class="btn btn-primary" onclick="manager.updateGeminiKey('${currentKey}')">更新</button>
</div>
`;
modal.style.display = 'block';
}
// 更新Gemini密钥
async updateGeminiKey(oldKey) {
const newKey = document.getElementById('edit-gemini-key').value.trim();
if (!newKey) {
this.showNotification('请输入Gemini API密钥', 'error');
return;
}
try {
await this.makeRequest('/generative-language-api-key', {
method: 'PATCH',
body: JSON.stringify({ old: oldKey, new: newKey })
});
this.clearCache(); // 清除缓存
this.closeModal();
this.loadGeminiKeys();
this.showNotification('Gemini密钥更新成功', 'success');
} catch (error) {
this.showNotification(`更新Gemini密钥失败: ${error.message}`, 'error');
}
}
// 删除Gemini密钥
async deleteGeminiKey(key) {
if (!confirm(i18n.t('ai_providers.gemini_delete_confirm'))) return;
try {
await this.makeRequest(`/generative-language-api-key?value=${encodeURIComponent(key)}`, { method: 'DELETE' });
this.clearCache(); // 清除缓存
this.loadGeminiKeys();
this.showNotification('Gemini密钥删除成功', 'success');
} catch (error) {
this.showNotification(`删除Gemini密钥失败: ${error.message}`, 'error');
}
}
// 加载Codex密钥
async loadCodexKeys() {
try {
const config = await this.getConfig();
if (config['codex-api-key']) {
this.renderCodexKeys(config['codex-api-key']);
}
} catch (error) {
console.error('加载Codex密钥失败:', error);
}
}
// 渲染Codex密钥列表
renderCodexKeys(keys) {
const container = document.getElementById('codex-keys-list');
if (keys.length === 0) {
container.innerHTML = `
<div class="empty-state">
<i class="fas fa-code"></i>
<h3>${i18n.t('ai_providers.codex_empty_title')}</h3>
<p>${i18n.t('ai_providers.codex_empty_desc')}</p>
</div>
`;
return;
}
container.innerHTML = keys.map((config, index) => `
<div class="provider-item">
<div class="item-content">
<div class="item-title">${i18n.t('ai_providers.codex_item_title')} #${index + 1}</div>
<div class="item-subtitle">${i18n.t('common.api_key')}: ${this.maskApiKey(config['api-key'])}</div>
${config['base-url'] ? `<div class="item-subtitle">${i18n.t('common.base_url')}: ${this.escapeHtml(config['base-url'])}</div>` : ''}
${config['proxy-url'] ? `<div class="item-subtitle">${i18n.t('common.proxy_url')}: ${this.escapeHtml(config['proxy-url'])}</div>` : ''}
</div>
<div class="item-actions">
<button class="btn btn-secondary" onclick="manager.editCodexKey(${index}, ${JSON.stringify(config).replace(/"/g, '&quot;')})">
<i class="fas fa-edit"></i>
</button>
<button class="btn btn-danger" onclick="manager.deleteCodexKey('${config['api-key']}')">
<i class="fas fa-trash"></i>
</button>
</div>
</div>
`).join('');
}
// 显示添加Codex密钥模态框
showAddCodexKeyModal() {
const modal = document.getElementById('modal');
const modalBody = document.getElementById('modal-body');
modalBody.innerHTML = `
<h3>${i18n.t('ai_providers.codex_add_modal_title')}</h3>
<div class="form-group">
<label for="new-codex-key">${i18n.t('ai_providers.codex_add_modal_key_label')}</label>
<input type="text" id="new-codex-key" placeholder="${i18n.t('ai_providers.codex_add_modal_key_placeholder')}">
</div>
<div class="form-group">
<label for="new-codex-url">${i18n.t('ai_providers.codex_add_modal_url_label')}</label>
<input type="text" id="new-codex-url" placeholder="${i18n.t('ai_providers.codex_add_modal_url_placeholder')}">
</div>
<div class="form-group">
<label for="new-codex-proxy">${i18n.t('ai_providers.codex_add_modal_proxy_label')}</label>
<input type="text" id="new-codex-proxy" placeholder="${i18n.t('ai_providers.codex_add_modal_proxy_placeholder')}">
</div>
<div class="modal-actions">
<button class="btn btn-secondary" onclick="manager.closeModal()">${i18n.t('common.cancel')}</button>
<button class="btn btn-primary" onclick="manager.addCodexKey()">${i18n.t('common.add')}</button>
</div>
`;
modal.style.display = 'block';
}
// 添加Codex密钥
async addCodexKey() {
const apiKey = document.getElementById('new-codex-key').value.trim();
const baseUrl = document.getElementById('new-codex-url').value.trim();
const proxyUrl = document.getElementById('new-codex-proxy').value.trim();
if (!apiKey) {
this.showNotification(i18n.t('notification.field_required'), 'error');
return;
}
try {
const data = await this.makeRequest('/codex-api-key');
const currentKeys = data['codex-api-key'] || [];
const newConfig = { 'api-key': apiKey };
if (baseUrl) {
newConfig['base-url'] = baseUrl;
}
if (proxyUrl) {
newConfig['proxy-url'] = proxyUrl;
}
currentKeys.push(newConfig);
await this.makeRequest('/codex-api-key', {
method: 'PUT',
body: JSON.stringify(currentKeys)
});
this.clearCache(); // 清除缓存
this.closeModal();
this.loadCodexKeys();
this.showNotification('Codex配置添加成功', 'success');
} catch (error) {
this.showNotification(`添加Codex配置失败: ${error.message}`, 'error');
}
}
// 编辑Codex密钥
editCodexKey(index, config) {
const modal = document.getElementById('modal');
const modalBody = document.getElementById('modal-body');
modalBody.innerHTML = `
<h3>${i18n.t('ai_providers.codex_edit_modal_title')}</h3>
<div class="form-group">
<label for="edit-codex-key">${i18n.t('ai_providers.codex_edit_modal_key_label')}</label>
<input type="text" id="edit-codex-key" value="${config['api-key']}">
</div>
<div class="form-group">
<label for="edit-codex-url">${i18n.t('ai_providers.codex_edit_modal_url_label')}</label>
<input type="text" id="edit-codex-url" value="${config['base-url'] || ''}">
</div>
<div class="form-group">
<label for="edit-codex-proxy">${i18n.t('ai_providers.codex_edit_modal_proxy_label')}</label>
<input type="text" id="edit-codex-proxy" value="${config['proxy-url'] || ''}">
</div>
<div class="modal-actions">
<button class="btn btn-secondary" onclick="manager.closeModal()">${i18n.t('common.cancel')}</button>
<button class="btn btn-primary" onclick="manager.updateCodexKey(${index})">${i18n.t('common.update')}</button>
</div>
`;
modal.style.display = 'block';
}
// 更新Codex密钥
async updateCodexKey(index) {
const apiKey = document.getElementById('edit-codex-key').value.trim();
const baseUrl = document.getElementById('edit-codex-url').value.trim();
const proxyUrl = document.getElementById('edit-codex-proxy').value.trim();
if (!apiKey) {
this.showNotification(i18n.t('notification.field_required'), 'error');
return;
}
try {
const newConfig = { 'api-key': apiKey };
if (baseUrl) {
newConfig['base-url'] = baseUrl;
}
if (proxyUrl) {
newConfig['proxy-url'] = proxyUrl;
}
await this.makeRequest('/codex-api-key', {
method: 'PATCH',
body: JSON.stringify({ index, value: newConfig })
});
this.clearCache(); // 清除缓存
this.closeModal();
this.loadCodexKeys();
this.showNotification('Codex配置更新成功', 'success');
} catch (error) {
this.showNotification(`更新Codex配置失败: ${error.message}`, 'error');
}
}
// 删除Codex密钥
async deleteCodexKey(apiKey) {
if (!confirm(i18n.t('ai_providers.codex_delete_confirm'))) return;
try {
await this.makeRequest(`/codex-api-key?api-key=${encodeURIComponent(apiKey)}`, { method: 'DELETE' });
this.clearCache(); // 清除缓存
this.loadCodexKeys();
this.showNotification('Codex配置删除成功', 'success');
} catch (error) {
this.showNotification(`删除Codex配置失败: ${error.message}`, 'error');
}
}
// 加载Claude密钥
async loadClaudeKeys() {
try {
const config = await this.getConfig();
if (config['claude-api-key']) {
this.renderClaudeKeys(config['claude-api-key']);
}
} catch (error) {
console.error('加载Claude密钥失败:', error);
}
}
// 渲染Claude密钥列表
renderClaudeKeys(keys) {
const container = document.getElementById('claude-keys-list');
if (keys.length === 0) {
container.innerHTML = `
<div class="empty-state">
<i class="fas fa-brain"></i>
<h3>${i18n.t('ai_providers.claude_empty_title')}</h3>
<p>${i18n.t('ai_providers.claude_empty_desc')}</p>
</div>
`;
return;
}
container.innerHTML = keys.map((config, index) => `
<div class="provider-item">
<div class="item-content">
<div class="item-title">${i18n.t('ai_providers.claude_item_title')} #${index + 1}</div>
<div class="item-subtitle">${i18n.t('common.api_key')}: ${this.maskApiKey(config['api-key'])}</div>
${config['base-url'] ? `<div class="item-subtitle">${i18n.t('common.base_url')}: ${this.escapeHtml(config['base-url'])}</div>` : ''}
${config['proxy-url'] ? `<div class="item-subtitle">${i18n.t('common.proxy_url')}: ${this.escapeHtml(config['proxy-url'])}</div>` : ''}
</div>
<div class="item-actions">
<button class="btn btn-secondary" onclick="manager.editClaudeKey(${index}, ${JSON.stringify(config).replace(/"/g, '&quot;')})">
<i class="fas fa-edit"></i>
</button>
<button class="btn btn-danger" onclick="manager.deleteClaudeKey('${config['api-key']}')">
<i class="fas fa-trash"></i>
</button>
</div>
</div>
`).join('');
}
// 显示添加Claude密钥模态框
showAddClaudeKeyModal() {
const modal = document.getElementById('modal');
const modalBody = document.getElementById('modal-body');
modalBody.innerHTML = `
<h3>${i18n.t('ai_providers.claude_add_modal_title')}</h3>
<div class="form-group">
<label for="new-claude-key">${i18n.t('ai_providers.claude_add_modal_key_label')}</label>
<input type="text" id="new-claude-key" placeholder="${i18n.t('ai_providers.claude_add_modal_key_placeholder')}">
</div>
<div class="form-group">
<label for="new-claude-url">${i18n.t('ai_providers.claude_add_modal_url_label')}</label>
<input type="text" id="new-claude-url" placeholder="${i18n.t('ai_providers.claude_add_modal_url_placeholder')}">
</div>
<div class="form-group">
<label for="new-claude-proxy">${i18n.t('ai_providers.claude_add_modal_proxy_label')}</label>
<input type="text" id="new-claude-proxy" placeholder="${i18n.t('ai_providers.claude_add_modal_proxy_placeholder')}">
</div>
<div class="modal-actions">
<button class="btn btn-secondary" onclick="manager.closeModal()">${i18n.t('common.cancel')}</button>
<button class="btn btn-primary" onclick="manager.addClaudeKey()">${i18n.t('common.add')}</button>
</div>
`;
modal.style.display = 'block';
}
// 添加Claude密钥
async addClaudeKey() {
const apiKey = document.getElementById('new-claude-key').value.trim();
const baseUrl = document.getElementById('new-claude-url').value.trim();
const proxyUrl = document.getElementById('new-claude-proxy').value.trim();
if (!apiKey) {
this.showNotification(i18n.t('notification.field_required'), 'error');
return;
}
try {
const data = await this.makeRequest('/claude-api-key');
const currentKeys = data['claude-api-key'] || [];
const newConfig = { 'api-key': apiKey };
if (baseUrl) {
newConfig['base-url'] = baseUrl;
}
if (proxyUrl) {
newConfig['proxy-url'] = proxyUrl;
}
currentKeys.push(newConfig);
await this.makeRequest('/claude-api-key', {
method: 'PUT',
body: JSON.stringify(currentKeys)
});
this.clearCache(); // 清除缓存
this.closeModal();
this.loadClaudeKeys();
this.showNotification('Claude配置添加成功', 'success');
} catch (error) {
this.showNotification(`添加Claude配置失败: ${error.message}`, 'error');
}
}
// 编辑Claude密钥
editClaudeKey(index, config) {
const modal = document.getElementById('modal');
const modalBody = document.getElementById('modal-body');
modalBody.innerHTML = `
<h3>${i18n.t('ai_providers.claude_edit_modal_title')}</h3>
<div class="form-group">
<label for="edit-claude-key">${i18n.t('ai_providers.claude_edit_modal_key_label')}</label>
<input type="text" id="edit-claude-key" value="${config['api-key']}">
</div>
<div class="form-group">
<label for="edit-claude-url">${i18n.t('ai_providers.claude_edit_modal_url_label')}</label>
<input type="text" id="edit-claude-url" value="${config['base-url'] || ''}">
</div>
<div class="form-group">
<label for="edit-claude-proxy">${i18n.t('ai_providers.claude_edit_modal_proxy_label')}</label>
<input type="text" id="edit-claude-proxy" value="${config['proxy-url'] || ''}">
</div>
<div class="modal-actions">
<button class="btn btn-secondary" onclick="manager.closeModal()">${i18n.t('common.cancel')}</button>
<button class="btn btn-primary" onclick="manager.updateClaudeKey(${index})">${i18n.t('common.update')}</button>
</div>
`;
modal.style.display = 'block';
}
// 更新Claude密钥
async updateClaudeKey(index) {
const apiKey = document.getElementById('edit-claude-key').value.trim();
const baseUrl = document.getElementById('edit-claude-url').value.trim();
const proxyUrl = document.getElementById('edit-claude-proxy').value.trim();
if (!apiKey) {
this.showNotification(i18n.t('notification.field_required'), 'error');
return;
}
try {
const newConfig = { 'api-key': apiKey };
if (baseUrl) {
newConfig['base-url'] = baseUrl;
}
if (proxyUrl) {
newConfig['proxy-url'] = proxyUrl;
}
await this.makeRequest('/claude-api-key', {
method: 'PATCH',
body: JSON.stringify({ index, value: newConfig })
});
this.clearCache(); // 清除缓存
this.closeModal();
this.loadClaudeKeys();
this.showNotification('Claude配置更新成功', 'success');
} catch (error) {
this.showNotification(`更新Claude配置失败: ${error.message}`, 'error');
}
}
// 删除Claude密钥
async deleteClaudeKey(apiKey) {
if (!confirm(i18n.t('ai_providers.claude_delete_confirm'))) return;
try {
await this.makeRequest(`/claude-api-key?api-key=${encodeURIComponent(apiKey)}`, { method: 'DELETE' });
this.clearCache(); // 清除缓存
this.loadClaudeKeys();
this.showNotification('Claude配置删除成功', 'success');
} catch (error) {
this.showNotification(`删除Claude配置失败: ${error.message}`, 'error');
}
}
// 加载OpenAI提供商
async loadOpenAIProviders() {
try {
const config = await this.getConfig();
if (config['openai-compatibility']) {
this.renderOpenAIProviders(config['openai-compatibility']);
}
} catch (error) {
console.error('加载OpenAI提供商失败:', error);
}
}
// 渲染OpenAI提供商列表
renderOpenAIProviders(providers) {
const container = document.getElementById('openai-providers-list');
if (providers.length === 0) {
container.innerHTML = `
<div class="empty-state">
<i class="fas fa-plug"></i>
<h3>${i18n.t('ai_providers.openai_empty_title')}</h3>
<p>${i18n.t('ai_providers.openai_empty_desc')}</p>
</div>
`;
return;
}
container.innerHTML = providers.map((provider, index) => `
<div class="provider-item">
<div class="item-content">
<div class="item-title">${this.escapeHtml(provider.name)}</div>
<div class="item-subtitle">${i18n.t('common.base_url')}: ${this.escapeHtml(provider['base-url'])}</div>
<div class="item-subtitle">${i18n.t('ai_providers.openai_keys_count')}: ${(provider['api-key-entries'] || []).length}</div>
<div class="item-subtitle">${i18n.t('ai_providers.openai_models_count')}: ${(provider.models || []).length}</div>
${this.renderOpenAIModelBadges(provider.models || [])}
</div>
<div class="item-actions">
<button class="btn btn-secondary" onclick="manager.editOpenAIProvider(${index}, ${JSON.stringify(provider).replace(/"/g, '&quot;')})">
<i class="fas fa-edit"></i>
</button>
<button class="btn btn-danger" onclick="manager.deleteOpenAIProvider('${provider.name}')">
<i class="fas fa-trash"></i>
</button>
</div>
</div>
`).join('');
}
// 显示添加OpenAI提供商模态框
showAddOpenAIProviderModal() {
const modal = document.getElementById('modal');
const modalBody = document.getElementById('modal-body');
modalBody.innerHTML = `
<h3>${i18n.t('ai_providers.openai_add_modal_title')}</h3>
<div class="form-group">
<label for="new-provider-name">${i18n.t('ai_providers.openai_add_modal_name_label')}</label>
<input type="text" id="new-provider-name" placeholder="${i18n.t('ai_providers.openai_add_modal_name_placeholder')}">
</div>
<div class="form-group">
<label for="new-provider-url">${i18n.t('ai_providers.openai_add_modal_url_label')}</label>
<input type="text" id="new-provider-url" placeholder="${i18n.t('ai_providers.openai_add_modal_url_placeholder')}">
</div>
<div class="form-group">
<label for="new-provider-keys">${i18n.t('ai_providers.openai_add_modal_keys_label')}</label>
<textarea id="new-provider-keys" rows="3" placeholder="${i18n.t('ai_providers.openai_add_modal_keys_placeholder')}"></textarea>
</div>
<div class="form-group">
<label for="new-provider-proxies">${i18n.t('ai_providers.openai_add_modal_keys_proxy_label')}</label>
<textarea id="new-provider-proxies" rows="3" placeholder="${i18n.t('ai_providers.openai_add_modal_keys_proxy_placeholder')}"></textarea>
</div>
<div class="form-group">
<label>${i18n.t('ai_providers.openai_add_modal_models_label')}</label>
<p class="form-hint">${i18n.t('ai_providers.openai_models_hint')}</p>
<div id="new-provider-models-wrapper" class="model-input-list"></div>
<button type="button" class="btn btn-secondary" onclick="manager.addModelField('new-provider-models-wrapper')">${i18n.t('ai_providers.openai_models_add_btn')}</button>
</div>
<div class="modal-actions">
<button class="btn btn-secondary" onclick="manager.closeModal()">${i18n.t('common.cancel')}</button>
<button class="btn btn-primary" onclick="manager.addOpenAIProvider()">${i18n.t('common.add')}</button>
</div>
`;
modal.style.display = 'block';
this.populateModelFields('new-provider-models-wrapper', []);
}
// 添加OpenAI提供商
async addOpenAIProvider() {
const name = document.getElementById('new-provider-name').value.trim();
const baseUrl = document.getElementById('new-provider-url').value.trim();
const keysText = document.getElementById('new-provider-keys').value.trim();
const proxiesText = document.getElementById('new-provider-proxies').value.trim();
const models = this.collectModelInputs('new-provider-models-wrapper');
if (!this.validateOpenAIProviderInput(name, baseUrl, models)) {
return;
}
try {
const data = await this.makeRequest('/openai-compatibility');
const currentProviders = data['openai-compatibility'] || [];
const apiKeys = keysText ? keysText.split('\n').map(k => k.trim()).filter(k => k) : [];
const proxies = proxiesText ? proxiesText.split('\n').map(p => p.trim()).filter(p => p) : [];
const apiKeyEntries = apiKeys.map((key, idx) => ({
'api-key': key,
'proxy-url': proxies[idx] || ''
}));
const newProvider = {
name,
'base-url': baseUrl,
'api-key-entries': apiKeyEntries,
models
};
currentProviders.push(newProvider);
await this.makeRequest('/openai-compatibility', {
method: 'PUT',
body: JSON.stringify(currentProviders)
});
this.clearCache(); // 清除缓存
this.closeModal();
this.loadOpenAIProviders();
this.showNotification('OpenAI提供商添加成功', 'success');
} catch (error) {
this.showNotification(`添加OpenAI提供商失败: ${error.message}`, 'error');
}
}
// 编辑OpenAI提供商
editOpenAIProvider(index, provider) {
const modal = document.getElementById('modal');
const modalBody = document.getElementById('modal-body');
const apiKeyEntries = provider['api-key-entries'] || [];
const apiKeysText = apiKeyEntries.map(entry => entry['api-key'] || '').join('\n');
const proxiesText = apiKeyEntries.map(entry => entry['proxy-url'] || '').join('\n');
modalBody.innerHTML = `
<h3>${i18n.t('ai_providers.openai_edit_modal_title')}</h3>
<div class="form-group">
<label for="edit-provider-name">${i18n.t('ai_providers.openai_edit_modal_name_label')}</label>
<input type="text" id="edit-provider-name" value="${provider.name}">
</div>
<div class="form-group">
<label for="edit-provider-url">${i18n.t('ai_providers.openai_edit_modal_url_label')}</label>
<input type="text" id="edit-provider-url" value="${provider['base-url']}">
</div>
<div class="form-group">
<label for="edit-provider-keys">${i18n.t('ai_providers.openai_edit_modal_keys_label')}</label>
<textarea id="edit-provider-keys" rows="3">${apiKeysText}</textarea>
</div>
<div class="form-group">
<label for="edit-provider-proxies">${i18n.t('ai_providers.openai_edit_modal_keys_proxy_label')}</label>
<textarea id="edit-provider-proxies" rows="3">${proxiesText}</textarea>
</div>
<div class="form-group">
<label>${i18n.t('ai_providers.openai_edit_modal_models_label')}</label>
<p class="form-hint">${i18n.t('ai_providers.openai_models_hint')}</p>
<div id="edit-provider-models-wrapper" class="model-input-list"></div>
<button type="button" class="btn btn-secondary" onclick="manager.addModelField('edit-provider-models-wrapper')">${i18n.t('ai_providers.openai_models_add_btn')}</button>
</div>
<div class="modal-actions">
<button class="btn btn-secondary" onclick="manager.closeModal()">${i18n.t('common.cancel')}</button>
<button class="btn btn-primary" onclick="manager.updateOpenAIProvider(${index})">${i18n.t('common.update')}</button>
</div>
`;
modal.style.display = 'block';
this.populateModelFields('edit-provider-models-wrapper', provider.models || []);
}
// 更新OpenAI提供商
async updateOpenAIProvider(index) {
const name = document.getElementById('edit-provider-name').value.trim();
const baseUrl = document.getElementById('edit-provider-url').value.trim();
const keysText = document.getElementById('edit-provider-keys').value.trim();
const proxiesText = document.getElementById('edit-provider-proxies').value.trim();
const models = this.collectModelInputs('edit-provider-models-wrapper');
if (!this.validateOpenAIProviderInput(name, baseUrl, models)) {
return;
}
try {
const apiKeys = keysText ? keysText.split('\n').map(k => k.trim()).filter(k => k) : [];
const proxies = proxiesText ? proxiesText.split('\n').map(p => p.trim()).filter(p => p) : [];
const apiKeyEntries = apiKeys.map((key, idx) => ({
'api-key': key,
'proxy-url': proxies[idx] || ''
}));
const updatedProvider = {
name,
'base-url': baseUrl,
'api-key-entries': apiKeyEntries,
models
};
await this.makeRequest('/openai-compatibility', {
method: 'PATCH',
body: JSON.stringify({ index, value: updatedProvider })
});
this.clearCache(); // 清除缓存
this.closeModal();
this.loadOpenAIProviders();
this.showNotification('OpenAI提供商更新成功', 'success');
} catch (error) {
this.showNotification(`更新OpenAI提供商失败: ${error.message}`, 'error');
}
}
// 删除OpenAI提供商
async deleteOpenAIProvider(name) {
if (!confirm(i18n.t('ai_providers.openai_delete_confirm'))) return;
try {
await this.makeRequest(`/openai-compatibility?name=${encodeURIComponent(name)}`, { method: 'DELETE' });
this.clearCache(); // 清除缓存
this.loadOpenAIProviders();
this.showNotification('OpenAI提供商删除成功', 'success');
} catch (error) {
this.showNotification(`删除OpenAI提供商失败: ${error.message}`, 'error');
}
}
// 加载认证文件
async loadAuthFiles() {
try {
const data = await this.makeRequest('/auth-files');
this.renderAuthFiles(data.files || []);
} catch (error) {
console.error('加载认证文件失败:', error);
}
}
// 渲染认证文件列表
renderAuthFiles(files) {
const container = document.getElementById('auth-files-list');
if (files.length === 0) {
container.innerHTML = `
<div class="empty-state">
<i class="fas fa-file-alt"></i>
<h3>${i18n.t('auth_files.empty_title')}</h3>
<p>${i18n.t('auth_files.empty_desc')}</p>
</div>
`;
return;
}
container.innerHTML = files.map(file => `
<div class="file-item">
<div class="item-content">
<div class="item-title">${file.name}</div>
<div class="item-subtitle">${i18n.t('auth_files.file_size')}: ${this.formatFileSize(file.size)}</div>
<div class="item-subtitle">${i18n.t('auth_files.file_modified')}: ${new Date(file.modtime).toLocaleString(i18n.currentLanguage === 'zh-CN' ? 'zh-CN' : 'en-US')}</div>
</div>
<div class="item-actions">
<button class="btn btn-primary" onclick="manager.downloadAuthFile('${file.name}')">
<i class="fas fa-download"></i>
</button>
<button class="btn btn-danger" onclick="manager.deleteAuthFile('${file.name}')">
<i class="fas fa-trash"></i>
</button>
</div>
</div>
`).join('');
}
// 格式化文件大小
formatFileSize(bytes) {
if (bytes === 0) return '0 Bytes';
const k = 1024;
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
}
// 上传认证文件
uploadAuthFile() {
document.getElementById('auth-file-input').click();
}
// 处理文件上传
async handleFileUpload(event) {
const file = event.target.files[0];
if (!file) return;
if (!file.name.endsWith('.json')) {
this.showNotification(i18n.t('auth_files.upload_error_json'), 'error');
return;
}
try {
const formData = new FormData();
formData.append('file', file);
const response = await fetch(`${this.apiUrl}/auth-files`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${this.managementKey}`
},
body: formData
});
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
throw new Error(errorData.error || `HTTP ${response.status}`);
}
this.clearCache(); // 清除缓存
this.loadAuthFiles();
this.showNotification(i18n.t('auth_files.upload_success'), 'success');
} catch (error) {
this.showNotification(`文件上传失败: ${error.message}`, 'error');
}
// 清空文件输入
event.target.value = '';
}
// 下载认证文件
async downloadAuthFile(filename) {
try {
const response = await fetch(`${this.apiUrl}/auth-files/download?name=${encodeURIComponent(filename)}`, {
headers: {
'Authorization': `Bearer ${this.managementKey}`
}
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
const blob = await response.blob();
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename;
a.click();
window.URL.revokeObjectURL(url);
this.showNotification(i18n.t('auth_files.download_success'), 'success');
} catch (error) {
this.showNotification(`文件下载失败: ${error.message}`, 'error');
}
}
// 删除认证文件
async deleteAuthFile(filename) {
if (!confirm(`${i18n.t('auth_files.delete_confirm')} "${filename}" 吗?`)) return;
try {
await this.makeRequest(`/auth-files?name=${encodeURIComponent(filename)}`, { method: 'DELETE' });
this.clearCache(); // 清除缓存
this.loadAuthFiles();
this.showNotification(i18n.t('auth_files.delete_success'), 'success');
} catch (error) {
this.showNotification(`文件删除失败: ${error.message}`, 'error');
}
}
// 删除所有认证文件
async deleteAllAuthFiles() {
if (!confirm(i18n.t('auth_files.delete_all_confirm'))) return;
try {
const response = await this.makeRequest('/auth-files?all=true', { method: 'DELETE' });
this.clearCache(); // 清除缓存
this.loadAuthFiles();
this.showNotification(`${i18n.t('auth_files.delete_all_success')} ${response.deleted} ${i18n.t('auth_files.files_count')}`, 'success');
} catch (error) {
this.showNotification(`删除文件失败: ${error.message}`, 'error');
}
}
// 显示 Gemini Web Token 模态框
showGeminiWebTokenModal() {
const inlineSecure1psid = document.getElementById('secure-1psid-input');
const inlineSecure1psidts = document.getElementById('secure-1psidts-input');
const inlineLabel = document.getElementById('gemini-web-label-input');
const modalBody = document.getElementById('modal-body');
modalBody.innerHTML = `
<h3>${i18n.t('auth_login.gemini_web_button')}</h3>
<div class="gemini-web-form">
<div class="form-group">
<label for="modal-secure-1psid">${i18n.t('auth_login.secure_1psid_label')}</label>
<input type="text" id="modal-secure-1psid" placeholder="${i18n.t('auth_login.secure_1psid_placeholder')}" required>
<div class="form-hint">从浏览器开发者工具 → Application → Cookies 中获取</div>
</div>
<div class="form-group">
<label for="modal-secure-1psidts">${i18n.t('auth_login.secure_1psidts_label')}</label>
<input type="text" id="modal-secure-1psidts" placeholder="${i18n.t('auth_login.secure_1psidts_placeholder')}" required>
<div class="form-hint">从浏览器开发者工具 → Application → Cookies 中获取</div>
</div>
<div class="form-group">
<label for="modal-gemini-web-label">${i18n.t('auth_login.gemini_web_label_label')}</label>
<input type="text" id="modal-gemini-web-label" placeholder="${i18n.t('auth_login.gemini_web_label_placeholder')}">
<div class="form-hint">为此认证文件设置一个标签名称(可选)</div>
</div>
<div class="modal-actions">
<button class="btn btn-secondary" onclick="manager.closeModal()">${i18n.t('common.cancel')}</button>
<button class="btn btn-primary" onclick="manager.saveGeminiWebToken()">${i18n.t('common.save')}</button>
</div>
</div>
`;
this.showModal();
const modalSecure1psid = document.getElementById('modal-secure-1psid');
const modalSecure1psidts = document.getElementById('modal-secure-1psidts');
const modalLabel = document.getElementById('modal-gemini-web-label');
if (modalSecure1psid && inlineSecure1psid) {
modalSecure1psid.value = inlineSecure1psid.value.trim();
}
if (modalSecure1psidts && inlineSecure1psidts) {
modalSecure1psidts.value = inlineSecure1psidts.value.trim();
}
if (modalLabel && inlineLabel) {
modalLabel.value = inlineLabel.value.trim();
}
if (modalSecure1psid) {
modalSecure1psid.focus();
}
}
// 保存 Gemini Web Token
async saveGeminiWebToken() {
const secure1psid = document.getElementById('modal-secure-1psid').value.trim();
const secure1psidts = document.getElementById('modal-secure-1psidts').value.trim();
const label = document.getElementById('modal-gemini-web-label').value.trim();
if (!secure1psid || !secure1psidts) {
this.showNotification('请填写完整的 Cookie 信息', 'error');
return;
}
try {
const requestBody = {
secure_1psid: secure1psid,
secure_1psidts: secure1psidts
};
// 如果提供了 label,则添加到请求体中
if (label) {
requestBody.label = label;
}
const response = await this.makeRequest('/gemini-web-token', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(requestBody)
});
this.closeModal();
this.loadAuthFiles(); // 刷新认证文件列表
const inlineSecure1psid = document.getElementById('secure-1psid-input');
const inlineSecure1psidts = document.getElementById('secure-1psidts-input');
const inlineLabel = document.getElementById('gemini-web-label-input');
if (inlineSecure1psid) {
inlineSecure1psid.value = secure1psid;
}
if (inlineSecure1psidts) {
inlineSecure1psidts.value = secure1psidts;
}
if (inlineLabel) {
inlineLabel.value = label;
}
this.showNotification(`${i18n.t('auth_login.gemini_web_saved')}: ${response.file}`, 'success');
} catch (error) {
this.showNotification(`保存失败: ${error.message}`, 'error');
}
}
// ===== Codex OAuth 相关方法 =====
// 开始 Codex OAuth 流程
async startCodexOAuth() {
try {
const response = await this.makeRequest('/codex-auth-url?is_webui=1');
const authUrl = response.url;
const state = this.extractStateFromUrl(authUrl);
// 显示授权链接
const urlInput = document.getElementById('codex-oauth-url');
const content = document.getElementById('codex-oauth-content');
const status = document.getElementById('codex-oauth-status');
if (urlInput) {
urlInput.value = authUrl;
}
if (content) {
content.style.display = 'block';
}
if (status) {
status.textContent = i18n.t('auth_login.codex_oauth_status_waiting');
status.style.color = 'var(--warning-text)';
}
// 开始轮询认证状态
this.startCodexOAuthPolling(state);
} catch (error) {
this.showNotification(`${i18n.t('auth_login.codex_oauth_start_error')} ${error.message}`, 'error');
}
}
// 从 URL 中提取 state 参数
extractStateFromUrl(url) {
try {
const urlObj = new URL(url);
return urlObj.searchParams.get('state');
} catch (error) {
console.error('Failed to extract state from URL:', error);
return null;
}
}
// 打开 Codex 授权链接
openCodexLink() {
const urlInput = document.getElementById('codex-oauth-url');
if (urlInput && urlInput.value) {
window.open(urlInput.value, '_blank');
}
}
// 复制 Codex 授权链接
async copyCodexLink() {
const urlInput = document.getElementById('codex-oauth-url');
if (urlInput && urlInput.value) {
try {
await navigator.clipboard.writeText(urlInput.value);
this.showNotification('链接已复制到剪贴板', 'success');
} catch (error) {
// 降级方案:使用传统的复制方法
urlInput.select();
document.execCommand('copy');
this.showNotification('链接已复制到剪贴板', 'success');
}
}
}
// 开始轮询 OAuth 状态
startCodexOAuthPolling(state) {
if (!state) {
this.showNotification('无法获取认证状态参数', 'error');
return;
}
const pollInterval = setInterval(async () => {
try {
const response = await this.makeRequest(`/get-auth-status?state=${encodeURIComponent(state)}`);
const status = response.status;
const statusElement = document.getElementById('codex-oauth-status');
if (status === 'ok') {
// 认证成功
clearInterval(pollInterval);
// 隐藏授权链接相关内容,恢复到初始状态
this.resetCodexOAuthUI();
// 显示成功通知
this.showNotification(i18n.t('auth_login.codex_oauth_status_success'), 'success');
// 刷新认证文件列表
this.loadAuthFiles();
} else if (status === 'error') {
// 认证失败
clearInterval(pollInterval);
const errorMessage = response.error || 'Unknown error';
// 显示错误信息
if (statusElement) {
statusElement.textContent = `${i18n.t('auth_login.codex_oauth_status_error')} ${errorMessage}`;
statusElement.style.color = 'var(--error-text)';
}
this.showNotification(`${i18n.t('auth_login.codex_oauth_status_error')} ${errorMessage}`, 'error');
// 3秒后重置UI,让用户能够重新开始
setTimeout(() => {
this.resetCodexOAuthUI();
}, 3000);
} else if (status === 'wait') {
// 继续等待
if (statusElement) {
statusElement.textContent = i18n.t('auth_login.codex_oauth_status_waiting');
statusElement.style.color = 'var(--warning-text)';
}
}
} catch (error) {
clearInterval(pollInterval);
const statusElement = document.getElementById('codex-oauth-status');
if (statusElement) {
statusElement.textContent = `${i18n.t('auth_login.codex_oauth_polling_error')} ${error.message}`;
statusElement.style.color = 'var(--error-text)';
}
this.showNotification(`${i18n.t('auth_login.codex_oauth_polling_error')} ${error.message}`, 'error');
// 3秒后重置UI,让用户能够重新开始
setTimeout(() => {
this.resetCodexOAuthUI();
}, 3000);
}
}, 2000); // 每2秒轮询一次
// 设置超时,5分钟后停止轮询
setTimeout(() => {
clearInterval(pollInterval);
}, 5 * 60 * 1000);
}
// 重置 Codex OAuth UI 到初始状态
resetCodexOAuthUI() {
const urlInput = document.getElementById('codex-oauth-url');
const content = document.getElementById('codex-oauth-content');
const status = document.getElementById('codex-oauth-status');
// 清空并隐藏授权链接输入框
if (urlInput) {
urlInput.value = '';
}
// 隐藏整个授权链接内容区域
if (content) {
content.style.display = 'none';
}
// 清空状态显示
if (status) {
status.textContent = '';
status.style.color = '';
status.className = '';
}
}
// ===== Anthropic OAuth 相关方法 =====
// 开始 Anthropic OAuth 流程
async startAnthropicOAuth() {
try {
const response = await this.makeRequest('/anthropic-auth-url?is_webui=1');
const authUrl = response.url;
const state = this.extractStateFromUrl(authUrl);
// 显示授权链接
const urlInput = document.getElementById('anthropic-oauth-url');
const content = document.getElementById('anthropic-oauth-content');
const status = document.getElementById('anthropic-oauth-status');
if (urlInput) {
urlInput.value = authUrl;
}
if (content) {
content.style.display = 'block';
}
if (status) {
status.textContent = i18n.t('auth_login.anthropic_oauth_status_waiting');
status.style.color = 'var(--warning-text)';
}
// 开始轮询认证状态
this.startAnthropicOAuthPolling(state);
} catch (error) {
this.showNotification(`${i18n.t('auth_login.anthropic_oauth_start_error')} ${error.message}`, 'error');
}
}
// 打开 Anthropic 授权链接
openAnthropicLink() {
const urlInput = document.getElementById('anthropic-oauth-url');
if (urlInput && urlInput.value) {
window.open(urlInput.value, '_blank');
}
}
// 复制 Anthropic 授权链接
async copyAnthropicLink() {
const urlInput = document.getElementById('anthropic-oauth-url');
if (urlInput && urlInput.value) {
try {
await navigator.clipboard.writeText(urlInput.value);
this.showNotification('链接已复制到剪贴板', 'success');
} catch (error) {
// 降级方案:使用传统的复制方法
urlInput.select();
document.execCommand('copy');
this.showNotification('链接已复制到剪贴板', 'success');
}
}
}
// 开始轮询 Anthropic OAuth 状态
startAnthropicOAuthPolling(state) {
if (!state) {
this.showNotification('无法获取认证状态参数', 'error');
return;
}
const pollInterval = setInterval(async () => {
try {
const response = await this.makeRequest(`/get-auth-status?state=${encodeURIComponent(state)}`);
const status = response.status;
const statusElement = document.getElementById('anthropic-oauth-status');
if (status === 'ok') {
// 认证成功
clearInterval(pollInterval);
// 隐藏授权链接相关内容,恢复到初始状态
this.resetAnthropicOAuthUI();
// 显示成功通知
this.showNotification(i18n.t('auth_login.anthropic_oauth_status_success'), 'success');
// 刷新认证文件列表
this.loadAuthFiles();
} else if (status === 'error') {
// 认证失败
clearInterval(pollInterval);
const errorMessage = response.error || 'Unknown error';
// 显示错误信息
if (statusElement) {
statusElement.textContent = `${i18n.t('auth_login.anthropic_oauth_status_error')} ${errorMessage}`;
statusElement.style.color = 'var(--error-text)';
}
this.showNotification(`${i18n.t('auth_login.anthropic_oauth_status_error')} ${errorMessage}`, 'error');
// 3秒后重置UI,让用户能够重新开始
setTimeout(() => {
this.resetAnthropicOAuthUI();
}, 3000);
} else if (status === 'wait') {
// 继续等待
if (statusElement) {
statusElement.textContent = i18n.t('auth_login.anthropic_oauth_status_waiting');
statusElement.style.color = 'var(--warning-text)';
}
}
} catch (error) {
clearInterval(pollInterval);
const statusElement = document.getElementById('anthropic-oauth-status');
if (statusElement) {
statusElement.textContent = `${i18n.t('auth_login.anthropic_oauth_polling_error')} ${error.message}`;
statusElement.style.color = 'var(--error-text)';
}
this.showNotification(`${i18n.t('auth_login.anthropic_oauth_polling_error')} ${error.message}`, 'error');
// 3秒后重置UI,让用户能够重新开始
setTimeout(() => {
this.resetAnthropicOAuthUI();
}, 3000);
}
}, 2000); // 每2秒轮询一次
// 设置超时,5分钟后停止轮询
setTimeout(() => {
clearInterval(pollInterval);
}, 5 * 60 * 1000);
}
// 重置 Anthropic OAuth UI 到初始状态
resetAnthropicOAuthUI() {
const urlInput = document.getElementById('anthropic-oauth-url');
const content = document.getElementById('anthropic-oauth-content');
const status = document.getElementById('anthropic-oauth-status');
// 清空并隐藏授权链接输入框
if (urlInput) {
urlInput.value = '';
}
// 隐藏整个授权链接内容区域
if (content) {
content.style.display = 'none';
}
// 清空状态显示
if (status) {
status.textContent = '';
status.style.color = '';
status.className = '';
}
}
// ===== Gemini CLI OAuth 相关方法 =====
// 开始 Gemini CLI OAuth 流程
async startGeminiCliOAuth() {
try {
// 获取项目 ID(可选)
const projectId = document.getElementById('gemini-cli-project-id').value.trim();
// 构建请求 URL
let requestUrl = '/gemini-cli-auth-url?is_webui=1';
if (projectId) {
requestUrl += `&project_id=${encodeURIComponent(projectId)}`;
}
const response = await this.makeRequest(requestUrl);
const authUrl = response.url;
const state = this.extractStateFromUrl(authUrl);
// 显示授权链接
const urlInput = document.getElementById('gemini-cli-oauth-url');
const content = document.getElementById('gemini-cli-oauth-content');
const status = document.getElementById('gemini-cli-oauth-status');
if (urlInput) {
urlInput.value = authUrl;
}
if (content) {
content.style.display = 'block';
}
if (status) {
status.textContent = i18n.t('auth_login.gemini_cli_oauth_status_waiting');
status.style.color = 'var(--warning-text)';
}
// 开始轮询认证状态
this.startGeminiCliOAuthPolling(state);
} catch (error) {
this.showNotification(`${i18n.t('auth_login.gemini_cli_oauth_start_error')} ${error.message}`, 'error');
}
}
// 打开 Gemini CLI 授权链接
openGeminiCliLink() {
const urlInput = document.getElementById('gemini-cli-oauth-url');
if (urlInput && urlInput.value) {
window.open(urlInput.value, '_blank');
}
}
// 复制 Gemini CLI 授权链接
async copyGeminiCliLink() {
const urlInput = document.getElementById('gemini-cli-oauth-url');
if (urlInput && urlInput.value) {
try {
await navigator.clipboard.writeText(urlInput.value);
this.showNotification('链接已复制到剪贴板', 'success');
} catch (error) {
// 降级方案:使用传统的复制方法
urlInput.select();
document.execCommand('copy');
this.showNotification('链接已复制到剪贴板', 'success');
}
}
}
// 开始轮询 Gemini CLI OAuth 状态
startGeminiCliOAuthPolling(state) {
if (!state) {
this.showNotification('无法获取认证状态参数', 'error');
return;
}
const pollInterval = setInterval(async () => {
try {
const response = await this.makeRequest(`/get-auth-status?state=${encodeURIComponent(state)}`);
const status = response.status;
const statusElement = document.getElementById('gemini-cli-oauth-status');
if (status === 'ok') {
// 认证成功
clearInterval(pollInterval);
// 隐藏授权链接相关内容,恢复到初始状态
this.resetGeminiCliOAuthUI();
// 显示成功通知
this.showNotification(i18n.t('auth_login.gemini_cli_oauth_status_success'), 'success');
// 刷新认证文件列表
this.loadAuthFiles();
} else if (status === 'error') {
// 认证失败
clearInterval(pollInterval);
const errorMessage = response.error || 'Unknown error';
// 显示错误信息
if (statusElement) {
statusElement.textContent = `${i18n.t('auth_login.gemini_cli_oauth_status_error')} ${errorMessage}`;
statusElement.style.color = 'var(--error-text)';
}
this.showNotification(`${i18n.t('auth_login.gemini_cli_oauth_status_error')} ${errorMessage}`, 'error');
// 3秒后重置UI,让用户能够重新开始
setTimeout(() => {
this.resetGeminiCliOAuthUI();
}, 3000);
} else if (status === 'wait') {
// 继续等待
if (statusElement) {
statusElement.textContent = i18n.t('auth_login.gemini_cli_oauth_status_waiting');
statusElement.style.color = 'var(--warning-text)';
}
}
} catch (error) {
clearInterval(pollInterval);
const statusElement = document.getElementById('gemini-cli-oauth-status');
if (statusElement) {
statusElement.textContent = `${i18n.t('auth_login.gemini_cli_oauth_polling_error')} ${error.message}`;
statusElement.style.color = 'var(--error-text)';
}
this.showNotification(`${i18n.t('auth_login.gemini_cli_oauth_polling_error')} ${error.message}`, 'error');
// 3秒后重置UI,让用户能够重新开始
setTimeout(() => {
this.resetGeminiCliOAuthUI();
}, 3000);
}
}, 2000); // 每2秒轮询一次
// 设置超时,5分钟后停止轮询
setTimeout(() => {
clearInterval(pollInterval);
}, 5 * 60 * 1000);
}
// 重置 Gemini CLI OAuth UI 到初始状态
resetGeminiCliOAuthUI() {
const urlInput = document.getElementById('gemini-cli-oauth-url');
const content = document.getElementById('gemini-cli-oauth-content');
const status = document.getElementById('gemini-cli-oauth-status');
// 清空并隐藏授权链接输入框
if (urlInput) {
urlInput.value = '';
}
// 隐藏整个授权链接内容区域
if (content) {
content.style.display = 'none';
}
// 清空状态显示
if (status) {
status.textContent = '';
status.style.color = '';
status.className = '';
}
}
// ===== Qwen OAuth 相关方法 =====
// 开始 Qwen OAuth 流程
async startQwenOAuth() {
try {
const response = await this.makeRequest('/qwen-auth-url?is_webui=1');
const authUrl = response.url;
const state = this.extractStateFromUrl(authUrl);
// 显示授权链接
const urlInput = document.getElementById('qwen-oauth-url');
const content = document.getElementById('qwen-oauth-content');
const status = document.getElementById('qwen-oauth-status');
if (urlInput) {
urlInput.value = authUrl;
}
if (content) {
content.style.display = 'block';
}
if (status) {
status.textContent = i18n.t('auth_login.qwen_oauth_status_waiting');
status.style.color = 'var(--warning-text)';
}
// 开始轮询认证状态
this.startQwenOAuthPolling(response.state);
} catch (error) {
this.showNotification(`${i18n.t('auth_login.qwen_oauth_start_error')} ${error.message}`, 'error');
}
}
// 打开 Qwen 授权链接
openQwenLink() {
const urlInput = document.getElementById('qwen-oauth-url');
if (urlInput && urlInput.value) {
window.open(urlInput.value, '_blank');
}
}
// 复制 Qwen 授权链接
async copyQwenLink() {
const urlInput = document.getElementById('qwen-oauth-url');
if (urlInput && urlInput.value) {
try {
await navigator.clipboard.writeText(urlInput.value);
this.showNotification('链接已复制到剪贴板', 'success');
} catch (error) {
// 降级方案:使用传统的复制方法
urlInput.select();
document.execCommand('copy');
this.showNotification('链接已复制到剪贴板', 'success');
}
}
}
// 开始轮询 Qwen OAuth 状态
startQwenOAuthPolling(state) {
if (!state) {
this.showNotification('无法获取认证状态参数', 'error');
return;
}
const pollInterval = setInterval(async () => {
try {
const response = await this.makeRequest(`/get-auth-status?state=${encodeURIComponent(state)}`);
const status = response.status;
const statusElement = document.getElementById('qwen-oauth-status');
if (status === 'ok') {
// 认证成功
clearInterval(pollInterval);
// 隐藏授权链接相关内容,恢复到初始状态
this.resetQwenOAuthUI();
// 显示成功通知
this.showNotification(i18n.t('auth_login.qwen_oauth_status_success'), 'success');
// 刷新认证文件列表
this.loadAuthFiles();
} else if (status === 'error') {
// 认证失败
clearInterval(pollInterval);
const errorMessage = response.error || 'Unknown error';
// 显示错误信息
if (statusElement) {
statusElement.textContent = `${i18n.t('auth_login.qwen_oauth_status_error')} ${errorMessage}`;
statusElement.style.color = 'var(--error-text)';
}
this.showNotification(`${i18n.t('auth_login.qwen_oauth_status_error')} ${errorMessage}`, 'error');
// 3秒后重置UI,让用户能够重新开始
setTimeout(() => {
this.resetQwenOAuthUI();
}, 3000);
} else if (status === 'wait') {
// 继续等待
if (statusElement) {
statusElement.textContent = i18n.t('auth_login.qwen_oauth_status_waiting');
statusElement.style.color = 'var(--warning-text)';
}
}
} catch (error) {
clearInterval(pollInterval);
const statusElement = document.getElementById('qwen-oauth-status');
if (statusElement) {
statusElement.textContent = `${i18n.t('auth_login.qwen_oauth_polling_error')} ${error.message}`;
statusElement.style.color = 'var(--error-text)';
}
this.showNotification(`${i18n.t('auth_login.qwen_oauth_polling_error')} ${error.message}`, 'error');
// 3秒后重置UI,让用户能够重新开始
setTimeout(() => {
this.resetQwenOAuthUI();
}, 3000);
}
}, 2000); // 每2秒轮询一次
// 设置超时,5分钟后停止轮询
setTimeout(() => {
clearInterval(pollInterval);
}, 5 * 60 * 1000);
}
// 重置 Qwen OAuth UI 到初始状态
resetQwenOAuthUI() {
const urlInput = document.getElementById('qwen-oauth-url');
const content = document.getElementById('qwen-oauth-content');
const status = document.getElementById('qwen-oauth-status');
// 清空并隐藏授权链接输入框
if (urlInput) {
urlInput.value = '';
}
// 隐藏整个授权链接内容区域
if (content) {
content.style.display = 'none';
}
// 清空状态显示
if (status) {
status.textContent = '';
status.style.color = '';
status.className = '';
}
}
// ===== iFlow OAuth 相关方法 =====
// 开始 iFlow OAuth 流程
async startIflowOAuth() {
try {
const response = await this.makeRequest('/iflow-auth-url?is_webui=1');
const authUrl = response.url;
const state = this.extractStateFromUrl(authUrl);
// 显示授权链接
const urlInput = document.getElementById('iflow-oauth-url');
const content = document.getElementById('iflow-oauth-content');
const status = document.getElementById('iflow-oauth-status');
if (urlInput) {
urlInput.value = authUrl;
}
if (content) {
content.style.display = 'block';
}
if (status) {
status.textContent = i18n.t('auth_login.iflow_oauth_status_waiting');
status.style.color = 'var(--warning-text)';
}
// 开始轮询认证状态
this.startIflowOAuthPolling(state);
} catch (error) {
this.showNotification(`${i18n.t('auth_login.iflow_oauth_start_error')} ${error.message}`, 'error');
}
}
// 打开 iFlow 授权链接
openIflowLink() {
const urlInput = document.getElementById('iflow-oauth-url');
if (urlInput && urlInput.value) {
window.open(urlInput.value, '_blank');
}
}
// 复制 iFlow 授权链接
async copyIflowLink() {
const urlInput = document.getElementById('iflow-oauth-url');
if (urlInput && urlInput.value) {
try {
await navigator.clipboard.writeText(urlInput.value);
this.showNotification('链接已复制到剪贴板', 'success');
} catch (error) {
// 降级方案:使用传统的复制方法
urlInput.select();
document.execCommand('copy');
this.showNotification('链接已复制到剪贴板', 'success');
}
}
}
// 开始轮询 iFlow OAuth 状态
startIflowOAuthPolling(state) {
if (!state) {
this.showNotification('无法获取认证状态参数', 'error');
return;
}
const pollInterval = setInterval(async () => {
try {
const response = await this.makeRequest(`/get-auth-status?state=${encodeURIComponent(state)}`);
const status = response.status;
const statusElement = document.getElementById('iflow-oauth-status');
if (status === 'ok') {
// 认证成功
clearInterval(pollInterval);
// 隐藏授权链接相关内容,恢复到初始状态
this.resetIflowOAuthUI();
// 显示成功通知
this.showNotification(i18n.t('auth_login.iflow_oauth_status_success'), 'success');
// 刷新认证文件列表
this.loadAuthFiles();
} else if (status === 'error') {
// 认证失败
clearInterval(pollInterval);
const errorMessage = response.error || 'Unknown error';
// 显示错误信息
if (statusElement) {
statusElement.textContent = `${i18n.t('auth_login.iflow_oauth_status_error')} ${errorMessage}`;
statusElement.style.color = 'var(--error-text)';
}
this.showNotification(`${i18n.t('auth_login.iflow_oauth_status_error')} ${errorMessage}`, 'error');
// 3秒后重置UI,让用户能够重新开始
setTimeout(() => {
this.resetIflowOAuthUI();
}, 3000);
} else if (status === 'wait') {
// 继续等待
if (statusElement) {
statusElement.textContent = i18n.t('auth_login.iflow_oauth_status_waiting');
statusElement.style.color = 'var(--warning-text)';
}
}
} catch (error) {
clearInterval(pollInterval);
const statusElement = document.getElementById('iflow-oauth-status');
if (statusElement) {
statusElement.textContent = `${i18n.t('auth_login.iflow_oauth_polling_error')} ${error.message}`;
statusElement.style.color = 'var(--error-text)';
}
this.showNotification(`${i18n.t('auth_login.iflow_oauth_polling_error')} ${error.message}`, 'error');
// 3秒后重置UI,让用户能够重新开始
setTimeout(() => {
this.resetIflowOAuthUI();
}, 3000);
}
}, 2000); // 每2秒轮询一次
// 设置超时,5分钟后停止轮询
setTimeout(() => {
clearInterval(pollInterval);
}, 5 * 60 * 1000);
}
// 重置 iFlow OAuth UI 到初始状态
resetIflowOAuthUI() {
const urlInput = document.getElementById('iflow-oauth-url');
const content = document.getElementById('iflow-oauth-content');
const status = document.getElementById('iflow-oauth-status');
// 清空并隐藏授权链接输入框
if (urlInput) {
urlInput.value = '';
}
// 隐藏整个授权链接内容区域
if (content) {
content.style.display = 'none';
}
// 清空状态显示
if (status) {
status.textContent = '';
status.style.color = '';
status.className = '';
}
}
// ===== 使用统计相关方法 =====
// 初始化图表变量
requestsChart = null;
tokensChart = null;
currentUsageData = null;
// 加载使用统计
async loadUsageStats() {
try {
const response = await this.makeRequest('/usage');
const usage = response?.usage || null;
this.currentUsageData = usage;
if (!usage) {
throw new Error('usage payload missing');
}
// 更新概览卡片
this.updateUsageOverview(usage);
// 读取当前图表周期
const requestsHourActive = document.getElementById('requests-hour-btn')?.classList.contains('active');
const tokensHourActive = document.getElementById('tokens-hour-btn')?.classList.contains('active');
const requestsPeriod = requestsHourActive ? 'hour' : 'day';
const tokensPeriod = tokensHourActive ? 'hour' : 'day';
// 初始化图表(使用当前周期)
this.initializeRequestsChart(requestsPeriod);
this.initializeTokensChart(tokensPeriod);
// 更新API详细统计表格
this.updateApiStatsTable(usage);
} catch (error) {
console.error('加载使用统计失败:', error);
this.currentUsageData = null;
// 清空概览数据
['total-requests', 'success-requests', 'failed-requests', 'total-tokens'].forEach(id => {
const el = document.getElementById(id);
if (el) el.textContent = '-';
});
// 清空图表
if (this.requestsChart) {
this.requestsChart.destroy();
this.requestsChart = null;
}
if (this.tokensChart) {
this.tokensChart.destroy();
this.tokensChart = null;
}
const tableElement = document.getElementById('api-stats-table');
if (tableElement) {
tableElement.innerHTML = `<div class="no-data-message">${i18n.t('usage_stats.loading_error')}: ${error.message}</div>`;
}
}
}
// 更新使用统计概览
updateUsageOverview(data) {
const safeData = data || {};
document.getElementById('total-requests').textContent = safeData.total_requests ?? 0;
document.getElementById('success-requests').textContent = safeData.success_count ?? 0;
document.getElementById('failed-requests').textContent = safeData.failure_count ?? 0;
document.getElementById('total-tokens').textContent = safeData.total_tokens ?? 0;
}
// 初始化图表
initializeCharts() {
const requestsHourActive = document.getElementById('requests-hour-btn')?.classList.contains('active');
const tokensHourActive = document.getElementById('tokens-hour-btn')?.classList.contains('active');
this.initializeRequestsChart(requestsHourActive ? 'hour' : 'day');
this.initializeTokensChart(tokensHourActive ? 'hour' : 'day');
}
// 初始化请求趋势图表
initializeRequestsChart(period = 'day') {
const ctx = document.getElementById('requests-chart');
if (!ctx) return;
// 销毁现有图表
if (this.requestsChart) {
this.requestsChart.destroy();
}
const data = this.getRequestsChartData(period);
this.requestsChart = new Chart(ctx, {
type: 'line',
data: data,
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
display: false
}
},
scales: {
x: {
title: {
display: true,
text: i18n.t(period === 'hour' ? 'usage_stats.by_hour' : 'usage_stats.by_day')
}
},
y: {
beginAtZero: true,
title: {
display: true,
text: i18n.t('usage_stats.requests_count')
}
}
},
elements: {
line: {
borderColor: '#3b82f6',
backgroundColor: 'rgba(59, 130, 246, 0.1)',
fill: true,
tension: 0.4
},
point: {
backgroundColor: '#3b82f6',
borderColor: '#ffffff',
borderWidth: 2,
radius: 4
}
}
}
});
}
// 初始化Token使用趋势图表
initializeTokensChart(period = 'day') {
const ctx = document.getElementById('tokens-chart');
if (!ctx) return;
// 销毁现有图表
if (this.tokensChart) {
this.tokensChart.destroy();
}
const data = this.getTokensChartData(period);
this.tokensChart = new Chart(ctx, {
type: 'line',
data: data,
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
display: false
}
},
scales: {
x: {
title: {
display: true,
text: i18n.t(period === 'hour' ? 'usage_stats.by_hour' : 'usage_stats.by_day')
}
},
y: {
beginAtZero: true,
title: {
display: true,
text: i18n.t('usage_stats.tokens_count')
}
}
},
elements: {
line: {
borderColor: '#10b981',
backgroundColor: 'rgba(16, 185, 129, 0.1)',
fill: true,
tension: 0.4
},
point: {
backgroundColor: '#10b981',
borderColor: '#ffffff',
borderWidth: 2,
radius: 4
}
}
}
});
}
// 获取请求图表数据
getRequestsChartData(period) {
if (!this.currentUsageData) {
return { labels: [], datasets: [{ data: [] }] };
}
let dataSource, labels, values;
if (period === 'hour') {
dataSource = this.currentUsageData.requests_by_hour || {};
labels = Array.from({ length: 24 }, (_, i) => i.toString().padStart(2, '0'));
values = labels.map(hour => dataSource[hour] || 0);
} else {
dataSource = this.currentUsageData.requests_by_day || {};
labels = Object.keys(dataSource).sort();
values = labels.map(day => dataSource[day] || 0);
}
return {
labels: labels,
datasets: [{
data: values
}]
};
}
// 获取Token图表数据
getTokensChartData(period) {
if (!this.currentUsageData) {
return { labels: [], datasets: [{ data: [] }] };
}
let dataSource, labels, values;
if (period === 'hour') {
dataSource = this.currentUsageData.tokens_by_hour || {};
labels = Array.from({ length: 24 }, (_, i) => i.toString().padStart(2, '0'));
values = labels.map(hour => dataSource[hour] || 0);
} else {
dataSource = this.currentUsageData.tokens_by_day || {};
labels = Object.keys(dataSource).sort();
values = labels.map(day => dataSource[day] || 0);
}
return {
labels: labels,
datasets: [{
data: values
}]
};
}
// 切换请求图表时间周期
switchRequestsPeriod(period) {
// 更新按钮状态
document.getElementById('requests-hour-btn').classList.toggle('active', period === 'hour');
document.getElementById('requests-day-btn').classList.toggle('active', period === 'day');
// 更新图表数据
if (this.requestsChart) {
const newData = this.getRequestsChartData(period);
this.requestsChart.data = newData;
this.requestsChart.options.scales.x.title.text = i18n.t(period === 'hour' ? 'usage_stats.by_hour' : 'usage_stats.by_day');
this.requestsChart.update();
}
}
// 切换Token图表时间周期
switchTokensPeriod(period) {
// 更新按钮状态
document.getElementById('tokens-hour-btn').classList.toggle('active', period === 'hour');
document.getElementById('tokens-day-btn').classList.toggle('active', period === 'day');
// 更新图表数据
if (this.tokensChart) {
const newData = this.getTokensChartData(period);
this.tokensChart.data = newData;
this.tokensChart.options.scales.x.title.text = i18n.t(period === 'hour' ? 'usage_stats.by_hour' : 'usage_stats.by_day');
this.tokensChart.update();
}
}
// 更新API详细统计表格
updateApiStatsTable(data) {
const container = document.getElementById('api-stats-table');
if (!container) return;
const apis = data.apis || {};
if (Object.keys(apis).length === 0) {
container.innerHTML = `<div class="no-data-message">${i18n.t('usage_stats.no_data')}</div>`;
return;
}
let tableHtml = `
<table class="stats-table">
<thead>
<tr>
<th>${i18n.t('usage_stats.api_endpoint')}</th>
<th>${i18n.t('usage_stats.requests_count')}</th>
<th>${i18n.t('usage_stats.tokens_count')}</th>
<th>${i18n.t('usage_stats.success_rate')}</th>
<th>${i18n.t('usage_stats.models')}</th>
</tr>
</thead>
<tbody>
`;
Object.entries(apis).forEach(([endpoint, apiData]) => {
const totalRequests = apiData.total_requests || 0;
const successCount = apiData.success_count ?? null;
const successRate = successCount !== null && totalRequests > 0
? Math.round((successCount / totalRequests) * 100)
: null;
// 构建模型详情
let modelsHtml = '';
if (apiData.models && Object.keys(apiData.models).length > 0) {
modelsHtml = '<div class="model-details">';
Object.entries(apiData.models).forEach(([modelName, modelData]) => {
const modelRequests = modelData.total_requests ?? 0;
const modelTokens = modelData.total_tokens ?? 0;
modelsHtml += `
<div class="model-item">
<span class="model-name">${modelName}</span>
<span>${modelRequests} 请求 / ${modelTokens} tokens</span>
</div>
`;
});
modelsHtml += '</div>';
}
tableHtml += `
<tr>
<td>${endpoint}</td>
<td>${totalRequests}</td>
<td>${apiData.total_tokens || 0}</td>
<td>${successRate !== null ? successRate + '%' : '-'}</td>
<td>${modelsHtml || '-'}</td>
</tr>
`;
});
tableHtml += '</tbody></table>';
container.innerHTML = tableHtml;
}
showModal() {
const modal = document.getElementById('modal');
if (modal) {
modal.style.display = 'block';
}
}
// 关闭模态框
closeModal() {
document.getElementById('modal').style.display = 'none';
}
detectApiBaseFromLocation() {
try {
const { protocol, hostname, port } = window.location;
const normalizedPort = port ? `:${port}` : '';
return this.normalizeBase(`${protocol}//${hostname}${normalizedPort}`);
} catch (error) {
console.warn('无法从当前地址检测 API 基础地址,使用默认设置', error);
return this.normalizeBase(this.apiBase || 'http://localhost:8317');
}
}
updateLoginConnectionInfo() {
const connectionUrlElement = document.getElementById('login-connection-url');
const customInput = document.getElementById('login-api-base');
if (connectionUrlElement) {
connectionUrlElement.textContent = this.apiBase || '-';
}
if (customInput && customInput !== document.activeElement) {
customInput.value = this.apiBase || '';
}
}
addModelField(wrapperId, model = {}) {
const wrapper = document.getElementById(wrapperId);
if (!wrapper) return;
const row = document.createElement('div');
row.className = 'model-input-row';
row.innerHTML = `
<div class="input-group">
<input type="text" class="model-name-input" placeholder="${i18n.t('ai_providers.openai_model_name_placeholder')}" value="${model.name ? this.escapeHtml(model.name) : ''}">
<input type="text" class="model-alias-input" placeholder="${i18n.t('ai_providers.openai_model_alias_placeholder')}" value="${model.alias ? this.escapeHtml(model.alias) : ''}">
<button type="button" class="btn btn-small btn-danger model-remove-btn"><i class="fas fa-trash"></i></button>
</div>
`;
const removeBtn = row.querySelector('.model-remove-btn');
if (removeBtn) {
removeBtn.addEventListener('click', () => {
wrapper.removeChild(row);
});
}
wrapper.appendChild(row);
}
populateModelFields(wrapperId, models = []) {
const wrapper = document.getElementById(wrapperId);
if (!wrapper) return;
wrapper.innerHTML = '';
if (!models.length) {
this.addModelField(wrapperId);
return;
}
models.forEach(model => this.addModelField(wrapperId, model));
}
collectModelInputs(wrapperId) {
const wrapper = document.getElementById(wrapperId);
if (!wrapper) return [];
const rows = Array.from(wrapper.querySelectorAll('.model-input-row'));
const models = [];
rows.forEach(row => {
const nameInput = row.querySelector('.model-name-input');
const aliasInput = row.querySelector('.model-alias-input');
const name = nameInput ? nameInput.value.trim() : '';
const alias = aliasInput ? aliasInput.value.trim() : '';
if (name) {
const model = { name };
if (alias) {
model.alias = alias;
}
models.push(model);
}
});
return models;
}
renderOpenAIModelBadges(models) {
if (!models || models.length === 0) {
return '';
}
return `
<div class="provider-models">
${models.map(model => `
<span class="provider-model-tag">
<span class="model-name">${this.escapeHtml(model.name || '')}</span>
${model.alias ? `<span class="model-alias">${this.escapeHtml(model.alias)}</span>` : ''}
</span>
`).join('')}
</div>
`;
}
validateOpenAIProviderInput(name, baseUrl, models) {
if (!name || !baseUrl) {
this.showNotification(i18n.t('notification.openai_provider_required'), 'error');
return false;
}
const invalidModel = models.find(model => !model.name);
if (invalidModel) {
this.showNotification(i18n.t('notification.openai_model_name_required'), 'error');
return false;
}
return true;
}
}
// 全局管理器实例
let manager;
// 尝试自动加载根目录 Logo(支持多种常见文件名/扩展名)
function setupSiteLogo() {
const img = document.getElementById('site-logo');
const loginImg = document.getElementById('login-logo');
if (!img && !loginImg) return;
const inlineLogo = typeof window !== 'undefined' ? window.__INLINE_LOGO__ : null;
if (inlineLogo) {
if (img) {
img.src = inlineLogo;
img.style.display = 'inline-block';
}
if (loginImg) {
loginImg.src = inlineLogo;
loginImg.style.display = 'inline-block';
}
return;
}
const candidates = [
'../logo.svg', '../logo.png', '../logo.jpg', '../logo.jpeg', '../logo.webp', '../logo.gif',
'logo.svg', 'logo.png', 'logo.jpg', 'logo.jpeg', 'logo.webp', 'logo.gif',
'/logo.svg', '/logo.png', '/logo.jpg', '/logo.jpeg', '/logo.webp', '/logo.gif'
];
let idx = 0;
const tryNext = () => {
if (idx >= candidates.length) return;
const test = new Image();
test.onload = () => {
if (img) {
img.src = test.src;
img.style.display = 'inline-block';
}
if (loginImg) {
loginImg.src = test.src;
loginImg.style.display = 'inline-block';
}
};
test.onerror = () => {
idx++;
tryNext();
};
test.src = candidates[idx];
};
tryNext();
}
// 页面加载完成后初始化
document.addEventListener('DOMContentLoaded', () => {
// 初始化国际化
i18n.init();
setupSiteLogo();
manager = new CLIProxyManager();
});
</script>
<script>window.__INLINE_LOGO__ = "data:image/jpeg;base64,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";</script>
</body>
</html>