Spaces:
Paused
Paused
| <html lang="zh-CN"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Augment2Api-Panel</title> | |
| <link rel="icon" href="../static/augment.svg" type="image/svg+xml"> | |
| <link rel="alternate icon" href="../static/augment.svg" type="image/x-icon"> | |
| <style> | |
| :root { | |
| --primary-color: #4a6cf7; | |
| --primary-hover: #3a5ce4; | |
| --bg-color: #f5f7fa; | |
| --card-bg: #ffffff; | |
| --text-color: #333333; | |
| --text-secondary: #6c757d; | |
| --border-color: #e9ecef; | |
| --header-bg: #ffffff; | |
| --header-color: #333333; | |
| --sidebar-bg: #ffffff; | |
| --sidebar-color: #333333; | |
| --sidebar-hover: #f0f4ff; | |
| --sidebar-active: #e6edff; | |
| --footer-bg: #ffffff; | |
| --footer-color: #6c757d; | |
| --success-color: #28a745; | |
| --error-color: #dc3545; | |
| --warning-color: #ffc107; | |
| --radius: 8px; | |
| --shadow: 0 2px 10px rgba(0, 0, 0, 0.05); | |
| --transition: all 0.3s ease; | |
| } | |
| html, body { | |
| height: 100%; | |
| margin: 0; | |
| padding: 0; | |
| overflow: hidden; | |
| } | |
| body { | |
| display: flex; | |
| flex-direction: column; | |
| background-color: var(--bg-color); | |
| color: var(--text-color); | |
| font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; | |
| line-height: 1.6; | |
| } | |
| .container { | |
| display: flex; | |
| flex-direction: column; | |
| height: 100vh; | |
| width: 100%; | |
| max-width: 100%; | |
| margin: 0; | |
| padding: 0; | |
| } | |
| header { | |
| background-color: var(--header-bg); | |
| color: var(--header-color); | |
| padding: 8px 20px; | |
| box-shadow: var(--shadow); | |
| z-index: 10; | |
| border-bottom: 1px solid var(--border-color); | |
| } | |
| .header-content { | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| width: 100%; | |
| height: 40px; /* 固定高度 */ | |
| } | |
| .header-content h1 { | |
| font-size: 18px; | |
| margin: 0; | |
| font-weight: 600; | |
| } | |
| .logout-btn { | |
| background-color: var(--primary-color); | |
| color: white; | |
| border: none; | |
| border-radius: var(--radius); | |
| padding: 6px 12px; | |
| cursor: pointer; | |
| font-size: 14px; | |
| transition: var(--transition); | |
| display: flex; | |
| align-items: center; | |
| gap: 5px; | |
| } | |
| .logout-btn:hover { | |
| background-color: var(--primary-hover); | |
| } | |
| .dashboard { | |
| display: flex; | |
| flex: 1; | |
| height: calc(100vh - 100px); | |
| overflow: hidden; | |
| margin: 0; | |
| padding: 15px; | |
| gap: 15px; | |
| } | |
| .sidebar { | |
| width: 200px; | |
| height: 100%; | |
| background-color: var(--sidebar-bg); | |
| color: var(--sidebar-color); | |
| transition: width 0.3s ease; | |
| border-radius: var(--radius); | |
| box-shadow: var(--shadow); | |
| overflow: hidden; | |
| display: flex; | |
| flex-direction: column; | |
| } | |
| .sidebar.collapsed { | |
| width: 80px; | |
| } | |
| .sidebar.collapsed .sidebar-header h3 { | |
| display: none; | |
| } | |
| .sidebar-header { | |
| padding: 15px; | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| border-bottom: 1px solid var(--border-color); | |
| } | |
| .sidebar-header h3 { | |
| margin: 0; | |
| color: var(--text-color); | |
| font-size: 16px; | |
| font-weight: 600; | |
| white-space: nowrap; | |
| } | |
| .toggle-btn { | |
| background: transparent; | |
| border: none; | |
| color: var(--text-secondary); | |
| cursor: pointer; | |
| font-size: 16px; | |
| padding: 5px; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| transition: transform 0.3s, background-color 0.2s; | |
| border-radius: 4px; | |
| } | |
| .toggle-btn:hover { | |
| background-color: #f1f1f1; | |
| } | |
| .sidebar.collapsed .toggle-btn { | |
| transform: rotate(180deg); | |
| } | |
| .sidebar-menu { | |
| display: flex; | |
| flex-direction: column; | |
| padding: 10px 0; | |
| flex: 1; | |
| } | |
| .menu-item { | |
| padding: 10px 15px; | |
| display: flex; | |
| align-items: center; | |
| cursor: pointer; | |
| transition: var(--transition); | |
| white-space: nowrap; | |
| border-radius: 4px; | |
| margin: 2px 8px; | |
| } | |
| .menu-item:hover { | |
| background-color: var(--sidebar-hover); | |
| color: var(--primary-color); | |
| } | |
| .menu-item.active { | |
| background-color: var(--sidebar-active); | |
| color: var(--primary-color); | |
| font-weight: 500; | |
| } | |
| .menu-item i { | |
| font-size: 18px; | |
| margin-right: 12px; | |
| } | |
| .sidebar.collapsed .menu-item { | |
| padding: 10px 8px; | |
| justify-content: center; | |
| } | |
| .sidebar.collapsed .menu-text { | |
| display: none; | |
| } | |
| .sidebar.collapsed .menu-item i { | |
| margin-right: 0; | |
| font-size: 20px; | |
| } | |
| .sidebar.collapsed .sidebar-header { | |
| justify-content: center; | |
| padding: 15px 5px; | |
| } | |
| /* 主内容区样式优化 */ | |
| .main-content { | |
| flex: 1; | |
| height: 100%; | |
| overflow: hidden; | |
| display: flex; | |
| flex-direction: column; | |
| background-color: var(--card-bg); | |
| border-radius: var(--radius); | |
| box-shadow: var(--shadow); | |
| } | |
| .content-panel { | |
| padding: 20px; | |
| display: none; | |
| flex: 1; | |
| overflow-y: auto; | |
| height: 100%; | |
| flex-direction: column; | |
| } | |
| .content-panel.active { | |
| display: flex; | |
| flex-direction: column; | |
| } | |
| /* 添加token列表容器样式,使其可滚动 */ | |
| .token-list-container { | |
| max-height: calc(100vh - 250px); | |
| overflow-y: auto; | |
| overflow-x: hidden; | |
| padding-right: 10px; | |
| position: relative; | |
| } | |
| /* 添加列表加载效果 */ | |
| .token-list-loading { | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| padding: 20px; | |
| color: var(--text-secondary); | |
| } | |
| .token-list-loading .spinner { | |
| width: 20px; | |
| height: 20px; | |
| border: 2px solid var(--border-color); | |
| border-top-color: var(--primary-color); | |
| border-radius: 50%; | |
| animation: spin 1s linear infinite; | |
| margin-right: 10px; | |
| } | |
| @keyframes spin { | |
| to { transform: rotate(360deg); } | |
| } | |
| /* 面板标题样式优化 */ | |
| .panel-title { | |
| display: flex; | |
| align-items: center; | |
| margin-bottom: 20px; | |
| padding-bottom: 12px; | |
| border-bottom: 1px solid var(--border-color); | |
| } | |
| .panel-title h2 { | |
| margin: 0 0 0 10px; | |
| font-size: 18px; | |
| font-weight: 600; | |
| color: var(--text-color); | |
| } | |
| .panel-title i { | |
| font-size: 20px; | |
| color: var(--primary-color); | |
| } | |
| .panel-actions { | |
| margin-left: auto; | |
| display: flex; | |
| gap: 10px; | |
| } | |
| /* 按钮样式统一 */ | |
| button { | |
| background-color: var(--primary-color); | |
| color: white; | |
| border: none; | |
| border-radius: var(--radius); | |
| padding: 8px 12px; | |
| cursor: pointer; | |
| font-size: 14px; | |
| transition: var(--transition); | |
| display: flex; | |
| align-items: center; | |
| gap: 5px; | |
| } | |
| button:hover { | |
| background-color: var(--primary-hover); | |
| } | |
| button.secondary { | |
| background-color: transparent; | |
| color: var(--text-color); | |
| border: 1px solid var(--border-color); | |
| } | |
| button.secondary:hover { | |
| background-color: #f8f9fa; | |
| } | |
| button.secondary i { | |
| color: var(--text-color); | |
| } | |
| /* Token列表样式优化 */ | |
| .token-list { | |
| display: flex; | |
| flex-direction: column; | |
| gap: 10px; | |
| margin-bottom: 15px; | |
| } | |
| .token-item { | |
| border: 1px solid var(--border-color); | |
| border-radius: var(--radius); | |
| overflow: hidden; | |
| transition: var(--transition); | |
| margin-bottom: 10px; | |
| } | |
| .token-item:hover { | |
| box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05); | |
| } | |
| .token-header { | |
| display: flex; | |
| align-items: center; | |
| padding: 12px 15px; | |
| background-color: #f8f9fa; | |
| cursor: pointer; | |
| } | |
| .token-number { | |
| width: 30px; | |
| font-weight: 500; | |
| color: var(--text-secondary); | |
| } | |
| .token-summary { | |
| flex: 1; | |
| font-family: monospace; | |
| color: var(--text-color); | |
| white-space: nowrap; | |
| overflow: hidden; | |
| text-overflow: ellipsis; | |
| font-size: 13px; | |
| display: flex; | |
| align-items: center; | |
| gap: 8px; | |
| } | |
| .token-remark { | |
| background-color: #e3f2fd; | |
| color: #1976d2; | |
| padding: 2px 8px; | |
| border-radius: 4px; | |
| font-size: 12px; | |
| font-family: system-ui; | |
| cursor: pointer; | |
| border: 1px solid transparent; | |
| transition: all 0.2s; | |
| } | |
| .token-remark:hover { | |
| border-color: #1976d2; | |
| } | |
| .token-remark.empty { | |
| background-color: #f5f5f5; | |
| color: #9e9e9e; | |
| } | |
| .token-remark input { | |
| background: none; | |
| border: none; | |
| outline: none; | |
| font-size: inherit; | |
| font-family: inherit; | |
| color: inherit; | |
| width: 100%; | |
| min-width: 100px; | |
| } | |
| .token-usage-count { | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| color: var(--text-color); | |
| font-size: 13px; | |
| font-weight: 500; | |
| margin-left: 10px; | |
| } | |
| /* 根据使用次数变化颜色 - 只应用于数字 */ | |
| .token-usage-count .low { | |
| color: #28a745; /* 绿色 - 使用次数少 */ | |
| } | |
| .token-usage-count .medium { | |
| color: #ffc107; /* 黄色 - 使用次数中等 */ | |
| } | |
| .token-usage-count .high { | |
| color: #dc3545; /* 红色 - 使用次数多 */ | |
| } | |
| .token-toggle { | |
| margin-left: 10px; | |
| transition: transform 0.3s; | |
| } | |
| .token-toggle.open i { | |
| transform: rotate(180deg); | |
| } | |
| .token-details { | |
| padding: 0; | |
| max-height: 0; | |
| overflow: hidden; | |
| transition: all 0.3s ease; | |
| background-color: #ffffff; | |
| } | |
| .token-details.open { | |
| padding: 15px; | |
| max-height: 200px; | |
| border-top: 1px solid var(--border-color); | |
| overflow-y: auto; | |
| } | |
| .token-label { | |
| font-weight: 500; | |
| margin-bottom: 5px; | |
| color: var(--text-secondary); | |
| font-size: 13px; | |
| } | |
| .token-display { | |
| padding: 8px 10px; | |
| background-color: #f8f9fa; | |
| border-radius: 4px; | |
| font-family: monospace; | |
| margin-bottom: 10px; | |
| word-break: break-all; | |
| font-size: 13px; | |
| overflow-x: auto; | |
| } | |
| .token-actions { | |
| display: flex; | |
| justify-content: flex-end; | |
| margin-top: 10px; | |
| } | |
| .delete-token { | |
| background-color: var(--error-color); | |
| } | |
| .delete-token:hover { | |
| background-color: #c82333; | |
| } | |
| /* 分页控件样式优化 */ | |
| .pagination-container { | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| padding: 15px 0; | |
| border-top: 1px solid var(--border-color); | |
| } | |
| .pagination-btn { | |
| background-color: transparent; | |
| border: 1px solid var(--border-color); | |
| color: var(--text-color); | |
| border-radius: var(--radius); | |
| padding: 6px 10px; | |
| margin: 0 5px; | |
| cursor: pointer; | |
| transition: var(--transition); | |
| } | |
| .pagination-btn:hover:not([disabled]) { | |
| background-color: var(--primary-color); | |
| color: white; | |
| border-color: var(--primary-color); | |
| } | |
| .pagination-btn[disabled] { | |
| opacity: 0.5; | |
| cursor: not-allowed; | |
| } | |
| #page-info { | |
| margin: 0 15px; | |
| font-size: 14px; | |
| color: var(--text-secondary); | |
| } | |
| .page-size-select { | |
| margin-left: 15px; | |
| padding: 6px 8px; | |
| border-radius: var(--radius); | |
| border: 1px solid var(--border-color); | |
| background-color: white; | |
| color: var(--text-color); | |
| font-size: 14px; | |
| cursor: pointer; | |
| } | |
| /* 页脚样式优化 */ | |
| footer { | |
| text-align: center; | |
| padding: 10px 0; | |
| background-color: var(--footer-bg); | |
| color: var(--footer-color); | |
| font-size: 13px; | |
| border-top: 1px solid var(--border-color); | |
| } | |
| footer a { | |
| color: var(--primary-color); | |
| text-decoration: none; | |
| } | |
| footer a:hover { | |
| text-decoration: underline; | |
| } | |
| /* 响应式设计优化 */ | |
| @media (max-width: 768px) { | |
| .dashboard { | |
| flex-direction: column; | |
| height: auto; | |
| padding: 10px; | |
| } | |
| .sidebar { | |
| width: 100% ; | |
| margin-bottom: 15px; | |
| max-height: 200px; | |
| } | |
| .main-content { | |
| height: calc(100vh - 300px); | |
| overflow-y: auto; | |
| } | |
| .content-panel { | |
| padding: 15px; | |
| max-height: none; | |
| } | |
| .sidebar.collapsed .menu-text { | |
| display: inline; | |
| } | |
| .toggle-btn { | |
| display: none; | |
| } | |
| } | |
| @media (max-width: 1200px) { | |
| .token-display { | |
| max-width: 100%; | |
| white-space: nowrap; | |
| } | |
| } | |
| @media (min-width: 1201px) { | |
| .token-display { | |
| white-space: normal; | |
| } | |
| } | |
| .token-details.open .token-display { | |
| white-space: normal; | |
| word-break: break-all; | |
| } | |
| /* 添加Token面板样式优化 */ | |
| .auth-steps { | |
| display: flex; | |
| flex-direction: column; | |
| gap: 25px; | |
| padding-bottom: 20px; | |
| } | |
| .step { | |
| background-color: #f8f9fa; | |
| border-radius: var(--radius); | |
| padding: 20px; | |
| border: 1px solid var(--border-color); | |
| } | |
| .step h3 { | |
| display: flex; | |
| align-items: center; | |
| margin-top: 0; | |
| margin-bottom: 15px; | |
| font-size: 16px; | |
| font-weight: 600; | |
| } | |
| .step-number { | |
| display: inline-flex; | |
| align-items: center; | |
| justify-content: center; | |
| width: 24px; | |
| height: 24px; | |
| background-color: var(--primary-color); | |
| color: white; | |
| border-radius: 50%; | |
| margin-right: 10px; | |
| font-size: 14px; | |
| } | |
| .step p { | |
| margin-bottom: 15px; | |
| color: var(--text-secondary); | |
| } | |
| textarea { | |
| width: 100%; | |
| padding: 10px; | |
| border: 1px solid var(--border-color); | |
| border-radius: var(--radius); | |
| font-family: monospace; | |
| min-height: 100px; | |
| margin-bottom: 10px; | |
| resize: vertical; | |
| } | |
| .error { | |
| color: var(--error-color); | |
| margin: 10px 0; | |
| display: none; | |
| } | |
| .success { | |
| color: var(--success-color); | |
| margin: 10px 0; | |
| display: none; | |
| } | |
| button:not(.secondary):not(.pagination-btn):not(.toggle-btn) i { | |
| color: white; | |
| } | |
| /* 刷新按钮加载动画 */ | |
| .refresh-btn, #check-all-tokens { | |
| position: relative; | |
| } | |
| .refresh-btn i, #check-all-tokens i { | |
| transition: transform 0.3s ease; | |
| } | |
| .refresh-btn.loading i, #check-all-tokens.loading i { | |
| animation: spin 1s linear infinite; | |
| } | |
| @keyframes spin { | |
| 0% { transform: rotate(0deg); } | |
| 100% { transform: rotate(360deg); } | |
| } | |
| /* 禁用状态 */ | |
| .refresh-btn.loading, #check-all-tokens.loading { | |
| pointer-events: none; | |
| opacity: 0.7; | |
| } | |
| /* 检测结果样式 */ | |
| .check-result { | |
| color: var(--success-color); | |
| background-color: rgba(40, 167, 69, 0.1); | |
| border: 1px solid var(--success-color); | |
| border-radius: var(--radius); | |
| padding: 10px 15px; | |
| margin: 10px 0; | |
| display: none; | |
| } | |
| /* 弹出输入框样式 */ | |
| .remark-input-modal { | |
| position: fixed; | |
| top: 0; | |
| left: 0; | |
| width: 100%; | |
| height: 100%; | |
| background-color: rgba(0, 0, 0, 0.5); | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| z-index: 1000; | |
| } | |
| .remark-input-container { | |
| background-color: white; | |
| padding: 20px; | |
| border-radius: var(--radius); | |
| box-shadow: var(--shadow); | |
| width: 250px; | |
| } | |
| .remark-input-container h3 { | |
| margin: 0 0 15px 0; | |
| font-size: 16px; | |
| color: var(--text-color); | |
| } | |
| .remark-input-container input { | |
| width: 100%; | |
| padding: 8px 10px; | |
| border: 1px solid var(--border-color); | |
| border-radius: 4px; | |
| margin-bottom: 15px; | |
| font-size: 14px; | |
| box-sizing: border-box; | |
| } | |
| .remark-input-container .char-count { | |
| font-size: 12px; | |
| color: var(--text-secondary); | |
| margin-bottom: 15px; | |
| text-align: right; | |
| } | |
| .remark-input-actions { | |
| display: flex; | |
| justify-content: flex-end; | |
| gap: 10px; | |
| } | |
| /* Token模糊化样式 */ | |
| .token-blur { | |
| filter: blur(3px); | |
| transition: all 0.3s ease; | |
| } | |
| .token-blur:hover { | |
| filter: blur(0); | |
| } | |
| /* 删除背景颜色变化样式 */ | |
| #toggle-token-visibility.active i { | |
| color: var(--primary-color); | |
| } | |
| /* 冷却状态图标样式 */ | |
| .cool-status { | |
| color: #1e88e5; | |
| font-size: 18px; | |
| margin-left: 5px; | |
| vertical-align: middle; | |
| } | |
| .cool-status-tooltip { | |
| position: relative; | |
| display: inline-block; | |
| } | |
| .cool-status-tooltip .tooltip-text { | |
| visibility: hidden; | |
| width: 200px; | |
| background-color: #333; | |
| color: #fff; | |
| text-align: center; | |
| border-radius: 6px; | |
| padding: 5px; | |
| position: absolute; | |
| z-index: 1; | |
| bottom: 125%; | |
| left: 50%; | |
| margin-left: -100px; | |
| opacity: 0; | |
| transition: opacity 0.3s; | |
| font-size: 12px; | |
| } | |
| .cool-status-tooltip:hover .tooltip-text { | |
| visibility: visible; | |
| opacity: 1; | |
| } | |
| </style> | |
| <!-- 添加图标 --> | |
| <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.0/font/bootstrap-icons.css"> | |
| </head> | |
| <body> | |
| <div class="container"> | |
| <header> | |
| <div class="header-content"> | |
| <h1>Augment面板|v1.0.6</h1> | |
| <button id="logout-btn" class="logout-btn"> | |
| <i class="bi bi-box-arrow-right"></i> 登出 | |
| </button> | |
| </div> | |
| </header> | |
| <div class="dashboard"> | |
| <!-- 左侧导航栏 --> | |
| <div class="sidebar" id="sidebar"> | |
| <div class="sidebar-header"> | |
| <h3>面板功能导航</h3> | |
| <button id="toggle-sidebar" class="toggle-btn"> | |
| <i class="bi bi-chevron-left"></i> | |
| </button> | |
| </div> | |
| <div class="sidebar-menu"> | |
| <div class="menu-item active" data-target="token-list-panel"> | |
| <i class="bi bi-list-ul"></i> | |
| <span class="menu-text">Token列表</span> | |
| </div> | |
| <div class="menu-item" data-target="token-add-panel"> | |
| <i class="bi bi-plus-circle"></i> | |
| <span class="menu-text">添加Token</span> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- 右侧主内容区 --> | |
| <div class="main-content" id="main-content"> | |
| <!-- Token列表面板 --> | |
| <div class="content-panel active" id="token-list-panel"> | |
| <div class="panel-title"> | |
| <i class="bi bi-key-fill"></i> | |
| <h2>Token列表</h2> | |
| <div class="panel-actions"> | |
| <button id="toggle-token-visibility" class="secondary"> | |
| <i class="bi bi-eye-slash"></i> 隐藏Token | |
| </button> | |
| <button id="refresh-token" class="refresh-btn"> | |
| <i class="bi bi-arrow-clockwise"></i> 刷新列表 | |
| </button> | |
| <button id="check-all-tokens"><i class="bi bi-shield-check btn-icon"></i> <span class="btn-text">批量检测</span></button> | |
| </div> | |
| </div> | |
| <!-- 添加可滚动容器 --> | |
| <div class="token-list-container"> | |
| <div id="token-list">加载中...</div> | |
| </div> | |
| <!-- 分页控件 --> | |
| <div class="pagination-container" id="pagination-container"> | |
| <button class="pagination-btn" id="prev-page" disabled><i class="bi bi-chevron-left"></i></button> | |
| <span id="page-info">第 <span id="current-page">1</span> 页,共 <span id="total-pages">1</span> 页</span> | |
| <button class="pagination-btn" id="next-page"><i class="bi bi-chevron-right"></i></button> | |
| <select id="page-size" class="page-size-select"> | |
| <option value="10">10条/页</option> | |
| <option value="20">20条/页</option> | |
| <option value="50">50条/页</option> | |
| </select> | |
| </div> | |
| </div> | |
| <!-- 添加Token面板 --> | |
| <div class="content-panel" id="token-add-panel"> | |
| <div class="panel-title"> | |
| <i class="bi bi-shield-lock-fill"></i> | |
| <h2>授权获取Token</h2> | |
| </div> | |
| <div class="auth-steps"> | |
| <div class="step"> | |
| <h3><span class="step-number">1</span> 获取授权地址</h3> | |
| <p>点击下方按钮获取授权地址,然后在浏览器中打开该地址进行授权。</p> | |
| <button id="get-auth-url"><i class="bi bi-link-45deg" class="btn-icon"></i> <span class="btn-text">获取授权地址</span></button> | |
| <div id="auth-url" class="token-display" style="display: none;"></div> | |
| </div> | |
| <div class="step"> | |
| <h3><span class="step-number">2</span> 提交授权响应</h3> | |
| <p>完成授权后,将获得的授权响应粘贴到下面的文本框中:</p> | |
| <textarea id="auth-response" placeholder='{"code":"_000baec407c57c4bf9xxxxxxxxxxxxxx","state":"0uXxxxxxxxx","tenant_url":"https://dxx.api.augmentcode.com/"}'></textarea> | |
| <div id="validation-message" class="error"></div> | |
| <button id="submit-auth"><i class="bi bi-check2-circle" class="btn-text"></i> <span class="btn-text">获取Token</span></button> | |
| <div id="submit-result" class="success"></div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- 添加页脚 --> | |
| <footer> | |
| <a href="https://linux.do/u/bifang/summary" target="_blank">开发者:彼方</a> | <a href="https://2api-docs.pages.dev/page/augment2api/func-intro" target="_blank">文档中心</a> | |
| </footer> | |
| </div> | |
| <script> | |
| // 检查会话是否有效 | |
| function checkSession() { | |
| // 从Cookie中获取token | |
| const cookies = document.cookie.split(';'); | |
| let token = null; | |
| for (let i = 0; i < cookies.length; i++) { | |
| const cookie = cookies[i].trim(); | |
| if (cookie.startsWith('auth_token=')) { | |
| token = cookie.substring('auth_token='.length); | |
| break; | |
| } | |
| } | |
| if (!token) { | |
| window.location.href = '/login'; | |
| return false; | |
| } | |
| // 将token添加到所有API请求中 | |
| return token; | |
| } | |
| // 页面加载时检查会话 | |
| const authToken = checkSession(); | |
| if (!authToken) { | |
| // 如果没有有效会话,不继续执行后续代码 | |
| throw new Error('No valid session'); | |
| } | |
| // 为所有fetch请求添加认证头 | |
| const originalFetch = window.fetch; | |
| window.fetch = function(url, options = {}) { | |
| // 创建新的options对象,避免修改原始对象 | |
| const newOptions = { ...options }; | |
| // 确保headers对象存在 | |
| newOptions.headers = newOptions.headers || {}; | |
| // 如果是对象形式,转换为Headers对象 | |
| if (!(newOptions.headers instanceof Headers)) { | |
| const headers = new Headers(newOptions.headers); | |
| headers.append('X-Auth-Token', authToken); | |
| newOptions.headers = headers; | |
| } else { | |
| newOptions.headers.append('X-Auth-Token', authToken); | |
| } | |
| return originalFetch(url, newOptions); | |
| }; | |
| document.addEventListener('DOMContentLoaded', function() { | |
| // 侧边栏切换 | |
| const sidebar = document.getElementById('sidebar'); | |
| const toggleBtn = document.getElementById('toggle-sidebar'); | |
| const menuItems = document.querySelectorAll('.menu-item'); | |
| const contentPanels = document.querySelectorAll('.content-panel'); | |
| // 分页变量 | |
| let currentPage = 1; | |
| let pageSize = 10; | |
| let allTokens = []; | |
| // 侧边栏折叠/展开 | |
| toggleBtn.addEventListener('click', function() { | |
| sidebar.classList.toggle('collapsed'); | |
| }); | |
| // 菜单项切换 | |
| menuItems.forEach(item => { | |
| item.addEventListener('click', function() { | |
| // 移除所有菜单项的active类 | |
| menuItems.forEach(i => i.classList.remove('active')); | |
| // 为当前点击的菜单项添加active类 | |
| this.classList.add('active'); | |
| // 获取目标面板ID | |
| const targetId = this.getAttribute('data-target'); | |
| // 隐藏所有内容面板 | |
| contentPanels.forEach(panel => { | |
| panel.classList.remove('active'); | |
| }); | |
| // 显示目标面板 | |
| document.getElementById(targetId).classList.add('active'); | |
| }); | |
| }); | |
| // 分页功能 | |
| const prevPageBtn = document.getElementById('prev-page'); | |
| const nextPageBtn = document.getElementById('next-page'); | |
| const currentPageSpan = document.getElementById('current-page'); | |
| const totalPagesSpan = document.getElementById('total-pages'); | |
| const pageSizeSelect = document.getElementById('page-size'); | |
| // 页面大小变化 | |
| pageSizeSelect.addEventListener('change', function() { | |
| pageSize = parseInt(this.value); | |
| currentPage = 1; | |
| fetchCurrentToken(); | |
| }); | |
| // 上一页 | |
| prevPageBtn.addEventListener('click', function() { | |
| if (currentPage > 1) { | |
| currentPage--; | |
| fetchCurrentToken(); | |
| } | |
| }); | |
| // 下一页 | |
| nextPageBtn.addEventListener('click', function() { | |
| currentPage++; | |
| fetchCurrentToken(); | |
| }); | |
| // 添加节流功能,避免短时间内多次请求 | |
| function throttle(func, delay) { | |
| let lastCall = 0; | |
| return function(...args) { | |
| const now = new Date().getTime(); | |
| if (now - lastCall < delay) { | |
| return; | |
| } | |
| lastCall = now; | |
| return func.apply(this, args); | |
| }; | |
| } | |
| // 添加简单的缓存机制 | |
| const tokenCache = { | |
| data: {}, | |
| timestamp: 0, | |
| ttl: 10000, // 缓存有效期10秒 | |
| get: function(key) { | |
| const now = new Date().getTime(); | |
| if (this.data[key] && (now - this.timestamp < this.ttl)) { | |
| return this.data[key]; | |
| } | |
| return null; | |
| }, | |
| set: function(key, data) { | |
| this.data[key] = data; | |
| this.timestamp = new Date().getTime(); | |
| } | |
| }; | |
| // 修改获取当前Token列表函数,使用后端分页 | |
| const fetchCurrentToken = throttle(function() { | |
| // 显示加载动画 | |
| const refreshBtn = document.getElementById('refresh-token'); | |
| const tokenListElement = document.getElementById('token-list'); | |
| refreshBtn.classList.add('loading'); | |
| // 构造缓存键 | |
| const cacheKey = `tokens_${currentPage}_${pageSize}`; | |
| // 检查缓存 | |
| const cachedData = tokenCache.get(cacheKey); | |
| if (cachedData && !forceFresh) { | |
| // 使用缓存数据 | |
| allTokens = cachedData.tokens || []; | |
| renderTokenList(cachedData.total || 0, cachedData.total_pages || 1); | |
| refreshBtn.classList.remove('loading'); | |
| return; | |
| } | |
| // 重置强制刷新标志 | |
| forceFresh = false; | |
| // 设置超时处理 | |
| const timeoutId = setTimeout(() => { | |
| refreshBtn.classList.remove('loading'); | |
| tokenListElement.innerHTML = '<div class="error" style="display:block;">请求超时,请重试</div>'; | |
| }, 10000); // 10秒超时 | |
| // 添加性能标记 | |
| const startTime = performance.now(); | |
| fetch(`/api/tokens?page=${currentPage}&page_size=${pageSize}`) | |
| .then(response => { | |
| if (!response.ok) { | |
| throw new Error(`HTTP error! status: ${response.status}`); | |
| } | |
| return response.json(); | |
| }) | |
| .then(data => { | |
| clearTimeout(timeoutId); // 清除超时定时器 | |
| // 记录加载时间 | |
| const loadTime = performance.now() - startTime; | |
| console.log(`Token列表加载耗时: ${loadTime.toFixed(2)}ms`); | |
| if (data.status === 'success') { | |
| // 缓存结果 | |
| tokenCache.set(cacheKey, data); | |
| // 使用后端返回的token列表 | |
| allTokens = data.tokens || []; | |
| // 更新分页信息 | |
| const totalItems = data.total || 0; | |
| const totalPages = data.total_pages || 1; | |
| currentPage = data.page || 1; | |
| // 渲染token列表和分页控件 | |
| renderTokenList(totalItems, totalPages); | |
| } else { | |
| tokenListElement.innerHTML = | |
| '<div class="error" style="display:block;">获取Token列表失败: ' + (data.error || '未知错误') + '</div>'; | |
| } | |
| }) | |
| .catch(error => { | |
| clearTimeout(timeoutId); // 清除超时定时器 | |
| tokenListElement.innerHTML = | |
| '<div class="error" style="display:block;">请求失败: ' + error.message + '</div>'; | |
| }) | |
| .finally(() => { | |
| refreshBtn.classList.remove('loading'); | |
| }); | |
| }, 300); // 300ms节流 | |
| // 添加强制刷新标记 | |
| let forceFresh = false; | |
| // 刷新Token按钮事件 | |
| document.getElementById('refresh-token').addEventListener('click', function() { | |
| forceFresh = true; // 强制刷新,忽略缓存 | |
| fetchCurrentToken(); | |
| }); | |
| // 修改渲染token列表函数,使用后端返回的分页信息 | |
| function renderTokenList(totalItems, totalPages) { | |
| const tokenListElement = document.getElementById('token-list'); | |
| // 如果没有token | |
| if (totalItems === 0) { | |
| tokenListElement.innerHTML = '<div class="no-tokens">暂无可用Token,请点击"添加Token"获取</div>'; | |
| // 隐藏分页控件 | |
| document.getElementById('pagination-container').style.display = 'none'; | |
| return; | |
| } | |
| // 显示分页控件 | |
| document.getElementById('pagination-container').style.display = 'flex'; | |
| // 更新分页信息显示 | |
| totalPagesSpan.textContent = totalPages; | |
| currentPageSpan.textContent = currentPage; | |
| // 更新分页按钮状态 | |
| prevPageBtn.disabled = currentPage === 1; | |
| nextPageBtn.disabled = currentPage === totalPages; | |
| // 检查token数量,如果过多则使用分批渲染 | |
| const useBatchRendering = allTokens.length > 50; | |
| // 优化:使用文档片段减少DOM重绘次数 | |
| const fragment = document.createDocumentFragment(); | |
| const tokenList = document.createElement('div'); | |
| tokenList.className = 'token-list'; | |
| fragment.appendChild(tokenList); | |
| // 清空现有内容并显示加载中 | |
| tokenListElement.innerHTML = ''; | |
| if (useBatchRendering) { | |
| // 先显示加载状态 | |
| const loadingEl = document.createElement('div'); | |
| loadingEl.className = 'token-list-loading'; | |
| loadingEl.innerHTML = '<div class="spinner"></div> <span>正在加载Token列表...</span>'; | |
| tokenListElement.appendChild(loadingEl); | |
| // 使用requestAnimationFrame和分批处理来渲染大列表 | |
| setTimeout(() => { | |
| renderTokensBatch(tokenList, 0, 20); | |
| }, 10); | |
| function renderTokensBatch(container, startIdx, batchSize) { | |
| // 移除加载状态 | |
| const loadingEl = tokenListElement.querySelector('.token-list-loading'); | |
| if (loadingEl) { | |
| loadingEl.remove(); | |
| } | |
| // 添加当前批次的token | |
| const endIdx = Math.min(startIdx + batchSize, allTokens.length); | |
| for (let i = startIdx; i < endIdx; i++) { | |
| const tokenItem = createTokenItem(allTokens[i], i); | |
| container.appendChild(tokenItem); | |
| } | |
| // 如果还有更多token待渲染,安排下一批 | |
| if (endIdx < allTokens.length) { | |
| // 添加临时"加载更多"指示器 | |
| if (startIdx === 0) { // 只在第一批后添加DOM | |
| tokenListElement.appendChild(fragment); | |
| } | |
| setTimeout(() => { | |
| requestAnimationFrame(() => { | |
| renderTokensBatch(container, endIdx, batchSize); | |
| }); | |
| }, 0); | |
| } else { | |
| // 全部渲染完成,如果是第一批,添加到DOM | |
| if (startIdx === 0) { | |
| tokenListElement.appendChild(fragment); | |
| } | |
| // 应用Token可见性 | |
| if (!tokenVisible) { | |
| applyTokenVisibility(); | |
| } | |
| } | |
| } | |
| } else { | |
| // 直接渲染所有token | |
| allTokens.forEach((tokenInfo, index) => { | |
| const tokenItem = createTokenItem(tokenInfo, index); | |
| tokenList.appendChild(tokenItem); | |
| }); | |
| // 一次性替换DOM内容 | |
| tokenListElement.appendChild(fragment); | |
| // 应用Token可见性 | |
| if (!tokenVisible) { | |
| applyTokenVisibility(); | |
| } | |
| } | |
| // 辅助函数:创建token项元素 | |
| function createTokenItem(tokenInfo, index) { | |
| // 计算在当前页中的索引 | |
| const displayIndex = index + 1 + (currentPage - 1) * pageSize; | |
| // 获取使用次数并设置样式类 | |
| const chatUsageCount = tokenInfo.chat_usage_count || 0; | |
| const agentUsageCount = tokenInfo.agent_usage_count || 0; | |
| let usageClass = ''; | |
| // 根据CHAT和AGENT模式的使用次数来确定样式类 | |
| if (chatUsageCount < 1000 && agentUsageCount < 20) { | |
| usageClass = 'low'; | |
| } else if (chatUsageCount < 2000 && agentUsageCount < 40) { | |
| usageClass = 'medium'; | |
| } else { | |
| usageClass = 'high'; | |
| } | |
| const tokenItem = document.createElement('div'); | |
| tokenItem.className = 'token-item'; | |
| tokenItem.dataset.index = index; | |
| tokenItem.innerHTML = ` | |
| <div class="token-header" data-index="${index}"> | |
| <div class="token-number">${displayIndex}</div> | |
| <div class="token-summary"> | |
| ${tokenInfo.token} | |
| <span class="token-remark${!tokenInfo.remark ? ' empty' : ''}" data-token="${tokenInfo.token}" data-remark="${tokenInfo.remark || ''}">${tokenInfo.remark || '添加备注'}</span> | |
| ${tokenInfo.in_cool ? ` | |
| <span class="cool-status-tooltip"> | |
| <i class="bi bi-snow cool-status"></i> | |
| <span class="tooltip-text">冷却中,直到: ${new Date(tokenInfo.cool_end).toLocaleString()}</span> | |
| </span>` : ''} | |
| </div> | |
| <div class="token-usage-count"> | |
| CHAT使用: <span class="${usageClass}">${chatUsageCount}</span> 次 | AGENT使用: <span class="${usageClass}">${agentUsageCount}</span> 次 | |
| </div> | |
| <div class="token-toggle"><i class="bi bi-chevron-down"></i></div> | |
| </div> | |
| <div class="token-details"> | |
| <div class="token-label">Token:</div> | |
| <div class="token-display">${tokenInfo.token}</div> | |
| <div class="token-label">租户URL:</div> | |
| <div class="token-display">${tokenInfo.tenant_url}</div> | |
| <div class="token-actions"> | |
| <button class="delete-token" data-token="${tokenInfo.token}"> | |
| <i class="bi bi-trash"></i> 删除 | |
| </button> | |
| </div> | |
| </div> | |
| `; | |
| return tokenItem; | |
| } | |
| // 使用事件委托处理所有点击事件 | |
| if (!document.querySelector('.token-list').hasEventListener) { | |
| document.querySelector('.token-list').addEventListener('click', function(e) { | |
| // 处理备注点击 | |
| const remarkElement = e.target.closest('.token-remark'); | |
| if (remarkElement) { | |
| e.stopPropagation(); // 阻止事件冒泡到header | |
| const token = remarkElement.dataset.token; | |
| const currentRemark = remarkElement.dataset.remark; | |
| // 创建弹出层 | |
| const modal = document.createElement('div'); | |
| modal.className = 'remark-input-modal'; | |
| modal.innerHTML = ` | |
| <div class="remark-input-container"> | |
| <h3>编辑备注</h3> | |
| <input type="text" maxlength="30" placeholder="请输入备注(30字以内)" value="${currentRemark}"> | |
| <div class="char-count"><span>${currentRemark.length}</span>/30</div> | |
| <div class="remark-input-actions"> | |
| <button class="secondary" onclick="this.closest('.remark-input-modal').remove()">取消</button> | |
| <button class="save-remark" data-token="${token}">保存</button> | |
| </div> | |
| </div> | |
| `; | |
| document.body.appendChild(modal); | |
| // 获取输入框并聚焦 | |
| const input = modal.querySelector('input'); | |
| input.focus(); | |
| // 更新字符计数 | |
| input.addEventListener('input', () => { | |
| const count = input.value.length; | |
| modal.querySelector('.char-count span').textContent = count; | |
| }); | |
| // 点击背景关闭弹窗 | |
| modal.addEventListener('click', (e) => { | |
| if (e.target === modal) { | |
| modal.remove(); | |
| } | |
| }); | |
| // 处理保存按钮点击 | |
| modal.querySelector('.save-remark').addEventListener('click', async function() { | |
| const token = this.dataset.token; | |
| const newRemark = input.value.trim(); | |
| try { | |
| const response = await fetch(`/api/token/${token}/remark`, { | |
| method: 'PUT', | |
| headers: { | |
| 'Content-Type': 'application/json' | |
| }, | |
| body: JSON.stringify({ remark: newRemark }) | |
| }); | |
| const data = await response.json(); | |
| if (data.status === 'success') { | |
| // 更新所有具有相同token的备注元素 | |
| document.querySelectorAll(`.token-remark[data-token="${token}"]`).forEach(el => { | |
| el.textContent = newRemark || '添加备注'; | |
| el.dataset.remark = newRemark; | |
| if (newRemark) { | |
| el.classList.remove('empty'); | |
| } else { | |
| el.classList.add('empty'); | |
| } | |
| }); | |
| // 关闭弹窗 | |
| modal.remove(); | |
| } else { | |
| alert('更新备注失败: ' + (data.error || '未知错误')); | |
| } | |
| } catch (error) { | |
| alert('请求失败: ' + error.message); | |
| } | |
| }); | |
| return; // 阻止后续处理 | |
| } | |
| // 处理折叠/展开 | |
| const headerElement = e.target.closest('.token-header'); | |
| if (headerElement && !e.target.closest('.token-remark')) { | |
| const tokenItem = headerElement.closest('.token-item'); | |
| const details = tokenItem.querySelector('.token-details'); | |
| const toggle = tokenItem.querySelector('.token-toggle'); | |
| details.classList.toggle('open'); | |
| toggle.classList.toggle('open'); | |
| } | |
| }); | |
| // 标记已添加过事件监听器 | |
| document.querySelector('.token-list').hasEventListener = true; | |
| } | |
| } | |
| // 为token列表添加事件委托,只处理删除按钮 | |
| document.getElementById('token-list').addEventListener('click', function(e) { | |
| // 检查点击的是否是删除按钮 | |
| if (e.target.closest('.delete-token')) { | |
| const deleteBtn = e.target.closest('.delete-token'); | |
| const token = deleteBtn.getAttribute('data-token'); | |
| if (confirm('确定要删除此Token吗?')) { | |
| // 发送删除请求 | |
| fetch(`/api/token/${encodeURIComponent(token)}`, { | |
| method: 'DELETE' | |
| }) | |
| .then(response => response.json()) | |
| .then(data => { | |
| if (data.status === 'success') { | |
| // 刷新token列表 | |
| fetchCurrentToken(); | |
| } else { | |
| alert('删除失败: ' + (data.error || '未知错误')); | |
| } | |
| }) | |
| .catch(error => { | |
| alert('请求失败: ' + error.message); | |
| }); | |
| } | |
| } | |
| }); | |
| // 添加登出处理逻辑 | |
| document.getElementById('logout-btn').addEventListener('click', function() { | |
| if(confirm('确定要登出吗?')) { | |
| fetch('/api/logout', { | |
| method: 'POST', | |
| headers: { | |
| 'X-Auth-Token': authToken | |
| } | |
| }) | |
| .then(response => response.json()) | |
| .then(data => { | |
| if(data.status === 'success') { | |
| // 清除Cookie | |
| document.cookie = "auth_token=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT"; | |
| // 重定向到登录页 | |
| window.location.href = '/login'; | |
| } else { | |
| alert('登出失败: ' + (data.error || '未知错误')); | |
| } | |
| }) | |
| .catch(error => { | |
| alert('登出请求失败: ' + error.message); | |
| }); | |
| } | |
| }); | |
| // 批量检测token | |
| document.getElementById('check-all-tokens').addEventListener('click', function() { | |
| const button = this; | |
| button.classList.add('loading'); | |
| fetch('/api/check-tokens') | |
| .then(response => response.json()) | |
| .then(data => { | |
| if(data.status === 'success') { | |
| // 创建或获取检测结果显示元素 | |
| let checkResult = document.querySelector('.check-result'); | |
| if(!checkResult) { | |
| checkResult = document.createElement('div'); | |
| checkResult.className = 'check-result'; | |
| document.querySelector('.panel-title').after(checkResult); | |
| } | |
| // 显示检测结果 | |
| checkResult.textContent = `检测完成! 共检测 ${data.total} 个Token,更新 ${data.updated} 个Token租户地址,禁用 ${data.disabled} 个无效Token`; | |
| checkResult.style.display = 'block'; | |
| // 如果有更新或禁用,则刷新token列表 | |
| if(data.updated > 0 || data.disabled > 0) { | |
| fetchCurrentToken(); | |
| } | |
| // 5秒后隐藏提示 | |
| setTimeout(() => { | |
| checkResult.style.display = 'none'; | |
| }, 5000); | |
| } else { | |
| alert('检测失败: ' + (data.error || '未知错误')); | |
| } | |
| }) | |
| .catch(error => { | |
| alert('请求失败: ' + error.message); | |
| }) | |
| .finally(() => { | |
| button.classList.remove('loading'); | |
| }); | |
| }); | |
| // 获取授权地址按钮事件 | |
| document.getElementById('get-auth-url').addEventListener('click', function() { | |
| const button = this; | |
| const btnText = button.querySelector('.btn-text'); | |
| const originalText = btnText.textContent; | |
| // 显示加载状态 | |
| button.disabled = true; | |
| btnText.textContent = '获取中...'; | |
| // 请求授权地址 | |
| fetch('/auth') | |
| .then(response => response.json()) | |
| .then(data => { | |
| if (data.authorize_url) { | |
| // 显示授权地址 | |
| const authUrlElement = document.getElementById('auth-url'); | |
| authUrlElement.textContent = data.authorize_url; | |
| authUrlElement.style.display = 'block'; | |
| } else { | |
| alert('获取授权地址失败: ' + (data.error || '未知错误')); | |
| } | |
| }) | |
| .catch(error => { | |
| alert('请求失败: ' + error.message); | |
| }) | |
| .finally(() => { | |
| // 恢复按钮状态 | |
| button.disabled = false; | |
| btnText.textContent = originalText; | |
| }); | |
| }); | |
| // 验证JSON格式 | |
| function isValidJSON(str) { | |
| try { | |
| JSON.parse(str); | |
| return true; | |
| } catch (e) { | |
| return false; | |
| } | |
| } | |
| // 验证授权响应格式 | |
| function validateAuthResponse(response) { | |
| try { | |
| const data = JSON.parse(response); | |
| // 检查必要字段 | |
| if (!data.code || !data.state || !data.tenant_url) { | |
| return { valid: false, message: '缺少必要字段 (code, state, tenant_url)' }; | |
| } | |
| return { valid: true, data: data }; | |
| } catch (e) { | |
| return { valid: false, message: 'JSON格式无效: ' + e.message }; | |
| } | |
| } | |
| // 提交授权响应按钮事件 | |
| document.getElementById('submit-auth').addEventListener('click', function() { | |
| const button = this; | |
| const btnText = button.querySelector('.btn-text'); | |
| const originalText = btnText.textContent; | |
| const responseText = document.getElementById('auth-response').value.trim(); | |
| const validationMessage = document.getElementById('validation-message'); | |
| const submitResult = document.getElementById('submit-result'); | |
| // 重置消息 | |
| validationMessage.style.display = 'none'; | |
| submitResult.style.display = 'none'; | |
| // 验证输入 | |
| if (!responseText) { | |
| validationMessage.textContent = '请输入授权响应'; | |
| validationMessage.style.display = 'block'; | |
| return; | |
| } | |
| // 验证JSON格式 | |
| if (!isValidJSON(responseText)) { | |
| validationMessage.textContent = 'JSON格式无效,请检查输入'; | |
| validationMessage.style.display = 'block'; | |
| return; | |
| } | |
| // 验证授权响应格式 | |
| const validation = validateAuthResponse(responseText); | |
| if (!validation.valid) { | |
| validationMessage.textContent = validation.message; | |
| validationMessage.style.display = 'block'; | |
| return; | |
| } | |
| // 显示加载状态 | |
| button.disabled = true; | |
| btnText.textContent = '处理中...'; | |
| // 提交授权响应 | |
| fetch('/callback', { | |
| method: 'POST', | |
| headers: { | |
| 'Content-Type': 'application/json' | |
| }, | |
| body: responseText | |
| }) | |
| .then(response => response.json()) | |
| .then(data => { | |
| if (data.status === 'success') { | |
| // 显示成功消息 | |
| submitResult.textContent = 'Token获取成功!'; | |
| submitResult.style.display = 'block'; | |
| // 清空输入框 | |
| document.getElementById('auth-response').value = ''; | |
| // 刷新Token列表 | |
| setTimeout(() => { | |
| fetchCurrentToken(); | |
| // 切换到Token列表面板 | |
| document.querySelector('.menu-item[data-target="token-list-panel"]').click(); | |
| }, 1000); | |
| } else { | |
| // 显示错误消息但不影响样式 | |
| validationMessage.textContent = '获取Token失败: ' + (data.error || '未知错误'); | |
| validationMessage.style.display = 'block'; | |
| } | |
| }) | |
| .catch(error => { | |
| // 显示错误消息但不影响样式 | |
| validationMessage.textContent = '请求失败: ' + error.message; | |
| validationMessage.style.display = 'block'; | |
| }) | |
| .finally(() => { | |
| // 恢复按钮状态 | |
| button.disabled = false; | |
| btnText.textContent = originalText; | |
| }); | |
| }); | |
| // 初始加载token列表 | |
| fetchCurrentToken(); | |
| // 添加Token隐藏/显示功能 | |
| let tokenVisible = true; | |
| const toggleTokenVisibilityBtn = document.getElementById('toggle-token-visibility'); | |
| toggleTokenVisibilityBtn.addEventListener('click', function() { | |
| tokenVisible = !tokenVisible; | |
| // 更新按钮状态和文本 | |
| if (tokenVisible) { | |
| this.innerHTML = '<i class="bi bi-eye-slash"></i> 隐藏Token'; | |
| this.classList.remove('active'); | |
| } else { | |
| this.innerHTML = '<i class="bi bi-eye"></i> 显示Token'; | |
| this.classList.add('active'); | |
| } | |
| // 应用模糊效果到所有Token展示区域 | |
| applyTokenVisibility(); | |
| }); | |
| // 应用Token可见性设置到列表中 | |
| function applyTokenVisibility() { | |
| // 获取所有Token显示元素 | |
| const tokenElements = document.querySelectorAll('.token-summary, .token-display'); | |
| tokenElements.forEach(element => { | |
| if (tokenVisible) { | |
| element.classList.remove('token-blur'); | |
| } else { | |
| element.classList.add('token-blur'); | |
| } | |
| }); | |
| } | |
| // 在渲染Token列表后应用可见性设置 | |
| const originalRenderTokenList = renderTokenList; | |
| renderTokenList = function(totalItems, totalPages) { | |
| originalRenderTokenList(totalItems, totalPages); | |
| // 如果当前状态是隐藏的,则应用模糊效果 | |
| if (!tokenVisible) { | |
| applyTokenVisibility(); | |
| } | |
| }; | |
| }); | |
| </script> | |
| </body> | |
| </html> |