diff --git a/cursor-tokens.7z b/cursor-tokens.7z deleted file mode 100644 index e6cfa0c..0000000 Binary files a/cursor-tokens.7z and /dev/null differ diff --git a/static/api.html b/static/api.html new file mode 100644 index 0000000..2debe72 --- /dev/null +++ b/static/api.html @@ -0,0 +1,1945 @@ + + + + + + + API 测试工具 + + + + + + +
+
+
+ + +
+ +
+ + 检查中... +
+ +
+ + + +
+
+
+ + +
+ +
+ + +
+ + +
+
+ + +
+ +
+ +
+ +
+
+ + +
+
+ + + + +
+
+ + +
+ + +
+
+ + + +
+ + +
+
+
+ 等待请求... + +
+ +
+ +
+
+ 响应内容将在这里显示 +
+
+ + +
+
+
+ + +
+
+
+
+ + +
+ +
+

请求参数

+
+
+ + +
+
+ + +
+
+ + +
+
+ +
+ + +
+
+ +
+ +
+
+ + + +
+
+
+
+ + +
+
+ 版本: - + 运行时间: - + 最后请求: - +
+
+ 总请求数: 0 +
+
+ + + + \ No newline at end of file diff --git a/static/build_key.html b/static/build_key.html new file mode 100644 index 0000000..9087363 --- /dev/null +++ b/static/build_key.html @@ -0,0 +1,1668 @@ + + + + + + + 构建 Key + + + + + + + +

Key 构建器

+ +
+ +
+ + +
+ + +
+

API 认证

+

+ 输入用于调用构建Key服务的认证密钥 +

+
+ + +
此密钥用于认证对构建Key服务的访问(可选字段)
+
+
+ + +
+

认证信息

+
+

输入认证所需的令牌和校验信息

+ +
+ + +
JWT 格式的认证令牌
+
+ +
+ + +
64个十六进制字符(0-9, a-f, A-F)
+
+ +
+ + +
64个十六进制字符(0-9, a-f, A-F)
+
+ +
+ + +
64个十六进制字符(0-9, a-f, A-F)
+
+
+
+ + +
+

配置信息

+
+

设置配置版本和会话标识

+ +
+ +
+ + +
+
标准 UUID 格式,先填写其他项后最后再点击"获取"按钮获取
+
+ +
+ + +
标准 UUID 格式
+
+ +
+ + +
可选,通常不需要填写
+
+
+
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+
+ + + + + + + diff --git a/static/config.html b/static/config.html new file mode 100644 index 0000000..5e07235 --- /dev/null +++ b/static/config.html @@ -0,0 +1,428 @@ + + + + + + + 配置管理 + + + + + + +

配置管理

+ +
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + + +
+
+ +
+ + + + \ No newline at end of file diff --git a/static/logs.html b/static/logs.html new file mode 100644 index 0000000..78b4db4 --- /dev/null +++ b/static/logs.html @@ -0,0 +1,2529 @@ + + + + + + + 请求日志查看 + + + + + + + +

请求日志查看

+ +
+
+ + +
+ + +
+ +
+

📅 时间范围

+
+
+ + +
+
+ + +
+
+
+ +
+ + + + +
+
+
+ + +
+

👤 用户筛选

+
+
+ + +
+
+ + +
+
+ + +
+
+
+ + +
+

🤖 模型筛选

+
+
+ + +
+
+ + +
+
+ + +
+
+
+ + +
+

📊 性能筛选

+
+
+ +
+ + ~ + +
+
+
+ +
+ + ~ + +
+
+
+
+ + +
+

🔧 其他选项

+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+ + +
+ + +
+
+ +
+
+ + +
+
+ + +
+
+
+ +
+
+
+

总请求数

+
-
+
+
+

成功请求

+
-
+
+
+

失败请求

+
-
+
+
+

最后更新

+
-
+
+
+ + +
+ +
+ +
+ + + + + + + + + + + + + + + + +
序号时间模型Token信息对话用时输入/输出流式状态错误信息
+
+ + +
+
+ 共 0 条记录, 每页 + + 条 + +
+
+ + 第 1 页 + +
+ 跳转到第 + + 页 + +
+
+
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/static/proxies.html b/static/proxies.html new file mode 100644 index 0000000..9d844f7 --- /dev/null +++ b/static/proxies.html @@ -0,0 +1,2014 @@ + + + + + + + 代理信息管理 + + + + + + + +

代理信息管理

+ +
+
+ + +
+
+ + +
+
+
+ +
+ +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+
+
+ +
+ + + + +
+
+
+
+ + +
+
+ +
+ +
+ + + + + + + + + + + +
代理名称代理类型代理地址
+ + +
+ +
+
+ 已选择: 0 个 + 共 0 个代理 +
+
+
+ + + + + + + + + + + + + + + + + +
+ + + + \ No newline at end of file diff --git a/static/shared-styles.css b/static/shared-styles.css new file mode 100644 index 0000000..e103137 --- /dev/null +++ b/static/shared-styles.css @@ -0,0 +1,528 @@ +:root { + /* 基础颜色变量 */ + --primary-color: #2196f3; + --primary-dark: #1976d2; + --primary-color-alpha: rgba(33, 150, 243, 0.1); + --success-color: #4caf50; + --error-color: #f44336; + --background-color: #f5f5f5; + --card-background: #ffffff; + --text-primary: #333333; + --text-secondary: #757575; + --border-color: #e0e0e0; + --disabled-bg: #f5f5f5; + + /* 布局变量 */ + --border-radius: 8px; + --spacing: 20px; + + /* 动画变量 */ + --transition-fast: 0.2s; + --transition-slow: 0.3s; +} + +/* 暗色模式 */ +@media (prefers-color-scheme: dark) { + :root { + --primary-color: #90caf9; + --primary-dark: #64b5f6; + --background-color: #121212; + --card-background: #1e1e1e; + --text-primary: #e0e0e0; + --text-secondary: #9e9e9e; + --border-color: #404040; + --disabled-bg: #2d2d2d; + color-scheme: dark; + } +} + +/* 基础样式 */ +html { + scroll-behavior: smooth; + box-sizing: border-box; +} + +*, +*:before, +*:after { + box-sizing: inherit; +} + +body { + font-family: + system-ui, + -apple-system, + BlinkMacSystemFont, + "Segoe UI", + Roboto, + sans-serif; + max-width: 1200px; + margin: 0 auto; + padding: var(--spacing); + background: var(--background-color); + color: var(--text-primary); + line-height: 1.6; +} + +/* 容器样式 */ +.container { + background: var(--card-background); + padding: var(--spacing); + border-radius: var(--border-radius); + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + margin-bottom: var(--spacing); + transition: transform var(--transition-fast); +} + +.container:hover { + transform: translateY(-2px); +} + +/* 标题样式 */ +h1, +h2, +h3 { + color: var(--text-primary); + margin-top: 0; + line-height: 1.2; +} + +/* 表单元素样式 */ +.form-group { + margin-bottom: 20px; +} + +/* 标签样式 */ +label { + display: block; + margin-bottom: 8px; + font-weight: 500; + color: var(--text-primary); +} + +input, +select, +textarea, +.form-control { + width: 100%; + padding: 10px 12px; + border: 1px solid var(--border-color); + border-radius: 4px; + background: var(--card-background); + color: var(--text-primary); + font-size: 14px; + line-height: 1.5; + transition: all var(--transition-fast); + appearance: none; +} + +input[type="checkbox"] { + width: auto; + margin-right: 8px; + cursor: pointer; + appearance: auto; +} + +input[type="checkbox"] + label { + cursor: pointer; + color: var(--text-primary); + user-select: none; +} + +input:hover, +select:hover, +textarea:hover, +.form-control:hover { + border-color: var(--primary-color); +} + +input:focus, +select:focus, +textarea:focus, +.form-control:focus { + border-color: var(--primary-color); + box-shadow: 0 0 0 2px var(--primary-color-alpha); + outline: none; +} + +/* 禁用状态 */ +input:disabled, +select:disabled, +textarea:disabled, +.form-control:disabled { + background-color: var(--disabled-bg); + border-color: var(--border-color); + cursor: not-allowed; + opacity: 0.7; +} + +/* 错误状态 */ +input.error, +select.error, +textarea.error, +.form-control.error { + border-color: var(--error-color); +} + +/* Select 特殊样式 */ +select { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23757575'%3E%3Cpath d='M7 10l5 5 5-5H7z'/%3E%3C/svg%3E"); + background-repeat: no-repeat; + background-position: right 8px center; + background-size: 20px; + padding-right: 36px; +} + +/* Textarea 特殊样式 */ +textarea { + min-height: 150px; + resize: vertical; + font-family: monospace; + line-height: 1.4; +} + +/* 按钮基础样式 */ +button { + display: inline-flex; + align-items: center; + justify-content: center; + min-height: 44px; + padding: 8px 24px; + border: none; + border-radius: var(--border-radius); + background: var(--primary-color); + color: white; + font-size: 16px; + font-weight: 500; + text-align: center; + text-decoration: none; + cursor: pointer; + transition: all var(--transition-fast); + user-select: none; + -webkit-tap-highlight-color: transparent; +} + +/* 按钮状态 */ +button:hover { + background: var(--primary-dark); + transform: translateY(-1px); + box-shadow: 0 4px 12px var(--primary-color-alpha); +} + +button:active { + transform: translateY(1px); +} + +button:disabled { + background: var(--disabled-bg); + color: var(--text-secondary); + cursor: not-allowed; + transform: none; + box-shadow: none; +} + +/* 次要按钮样式 */ +button.secondary { + background: transparent; + border: 1px solid var(--primary-color); + color: var(--primary-color); +} + +button.secondary:hover { + background: var(--primary-color-alpha); + border-color: var(--primary-dark); + color: var(--primary-dark); +} + +button.danger { + background: var(--error-color); + border: none; +} + +button.danger:hover { + background: #d32f2f; + /* 深红色 */ + box-shadow: 0 4px 12px rgba(244, 67, 54, 0.2); +} + +/* 激活状态的按钮 */ +button.active { + background: var(--primary-dark); + box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.1); + transform: translateY(1px); +} + +button.secondary.active { + background: var(--primary-color); + color: white; + border-color: var(--primary-dark); +} + +/* 按钮组 */ +.button-group { + display: flex; + gap: 10px; + flex-wrap: wrap; + justify-content: flex-end; + align-items: flex-end; + margin: 0; +} + +/* 按钮组中的按钮间距调整 */ +.button-group button { + height: 38px; + min-width: 100px; + white-space: nowrap; +} + +.button-group button .context-menu-shortcut { + margin-left: 5px; + opacity: 0.7; + font-size: 12px; +} + +/* 消息容器 - 固定在顶部中间 */ +.message-container { + position: fixed; + top: 20px; + left: 50%; + transform: translateX(-50%); + z-index: 9999; + display: flex; + flex-direction: column; + align-items: center; + pointer-events: none; + /* 允许点击穿透 */ +} + +/* 单个消息样式 */ +.message { + padding: 12px 20px; + border-radius: 4px; + background: var(--card-background); + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); + margin-bottom: 10px; + pointer-events: auto; + /* 允许消息本身可以交互 */ + min-width: 300px; + max-width: 500px; + display: flex; + align-items: center; + transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1); + animation: messageIn 0.3s ease-in-out; +} + +.message.success { + background: #f0f9eb; + border: 1px solid #e1f3d8; +} + +.message.error { + background: #fef0f0; + border: 1px solid #fde2e2; +} + +@keyframes messageIn { + 0% { + opacity: 0; + transform: translateY(-20px); + } + + 100% { + opacity: 1; + transform: translateY(0); + } +} + +@keyframes messageOut { + 0% { + opacity: 1; + transform: translateY(0); + } + + 100% { + opacity: 0; + transform: translateY(-20px); + } +} + +/* 深色模式适配 */ +@media (prefers-color-scheme: dark) { + .message { + background: #2c2c2c; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); + } + + .message.success { + background: #294929; + border-color: #1c321c; + } + + .message.error { + background: #4d2c2c; + border-color: #321c1c; + } +} + +/* 表格样式 */ +table { + width: 100%; + border-collapse: collapse; + margin-top: var(--spacing); + background: var(--card-background); + border-radius: var(--border-radius); + overflow: hidden; +} + +th, +td { + padding: 12px; + text-align: left; + border-bottom: 1px solid var(--text-secondary); +} + +th { + background: var(--primary-color); + color: white; + font-weight: 500; +} + +tr:nth-child(even) { + background: rgba(0, 0, 0, 0.02); +} + +tr:hover { + background: rgba(0, 0, 0, 0.04); +} + +/* 辅助类 */ +.visually-hidden { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + border: 0; +} + +.text-center { + text-align: center; +} + +.help-text { + margin-top: 4px; + font-size: 14px; + color: var(--text-secondary); +} + +.error-text { + color: var(--error-color); +} + +.mt-0 { + margin-top: 0; +} + +.mb-0 { + margin-bottom: 0; +} + +/* 托盘消息容器 */ +.toast-container { + position: fixed; + bottom: 20px; + right: 20px; + display: flex; + flex-direction: column; + gap: 10px; + z-index: 1000; + max-width: 350px; + max-height: 80vh; + overflow-y: hidden; + padding-top: 10px; + padding-bottom: 10px; + padding-right: 5px; +} + +.toast { + background: var(--card-background); + color: var(--text-primary); + padding: 10px 16px; + border-radius: var(--border-radius); + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); + opacity: 0; + transform: translateY(20px); + transition: + opacity 0.4s cubic-bezier(0.25, 0.8, 0.25, 1), + transform 0.4s cubic-bezier(0.25, 0.8, 0.25, 1); + position: relative; + min-width: 200px; + margin-left: auto; + will-change: transform, opacity; + pointer-events: auto; +} + +.toast.info { + border-left: 4px solid #2196f3; +} + +.toast.error { + background: #f44336; + color: white; +} + +.toast.success { + background: #4caf50; + color: white; +} + +.toast.warning { + background: #ff9800; + color: white; +} + +.toast.show { + opacity: 1; + transform: translateY(0); +} + +/* 响应式设计 */ +@media (max-width: 768px) { + :root { + --spacing: 16px; + } + + body { + padding: 10px; + } + + .button-group { + flex-direction: column; + } + + button { + width: 100%; + padding: 12px 20px; + } + + input, + select, + textarea, + .form-control { + font-size: 16px; + padding: 14px 16px; + } + + table { + display: block; + overflow-x: auto; + -webkit-overflow-scrolling: touch; + } + + th, + td { + white-space: nowrap; + } +} diff --git a/static/shared.js b/static/shared.js new file mode 100644 index 0000000..c84bc8c --- /dev/null +++ b/static/shared.js @@ -0,0 +1,562 @@ +// Token 管理功能 +/** + * 保存认证令牌到本地存储 + * @param {string} token - 要保存的认证令牌 + * @returns {void} + */ +function saveAuthToken(token) { + const expiryTime = new Date().getTime() + 24 * 60 * 60 * 1000; // 24小时后过期 + localStorage.setItem("authToken", token); + localStorage.setItem("authTokenExpiry", expiryTime); +} + +/** + * 获取存储的认证令牌 + * @returns {string|null} 如果令牌有效则返回令牌,否则返回 null + */ +function getAuthToken() { + const token = localStorage.getItem("authToken"); + const expiry = localStorage.getItem("authTokenExpiry"); + + if (!token || !expiry) { + return null; + } + + if (new Date().getTime() > parseInt(expiry)) { + localStorage.removeItem("authToken"); + localStorage.removeItem("authTokenExpiry"); + return null; + } + + return token; +} + +// 消息显示功能 +/** + * 在指定元素中显示消息 + * @param {string} elementId - 目标元素的 ID + * @param {string} text - 要显示的消息文本 + * @param {boolean} [isError=false] - 是否为错误消息 + * @returns {void} + */ +function showMessage(elementId, text, isError = false) { + let msg = document.getElementById(elementId); + + // 如果消息元素不存在,创建一个新的 + if (!msg) { + msg = document.createElement("div"); + msg.id = elementId; + document.body.appendChild(msg); + } + + msg.className = `floating-message ${isError ? "error" : "success"}`; + msg.innerHTML = text.replace(/\n/g, "
"); +} + +// 确保消息容器存在 +/** + * 确保消息容器存在于 DOM 中 + * @returns {HTMLElement} 消息容器元素 + */ +function ensureMessageContainer() { + let container = document.querySelector(".message-container"); + if (!container) { + container = document.createElement("div"); + container.className = "message-container"; + document.body.appendChild(container); + } + return container; +} + +/** + * 显示全局消息提示 + * @param {string} text - 要显示的消息文本 + * @param {boolean} [isError=false] - 是否为错误消息 + * @param {number} [timeout=3000] - 消息显示时长(毫秒) + * @returns {void} + */ +function showGlobalMessage(text, isError = false, timeout = 3000) { + const container = ensureMessageContainer(); + + const msgElement = document.createElement("div"); + msgElement.className = `message ${isError ? "error" : "success"}`; + msgElement.textContent = text; + + container.appendChild(msgElement); + + // 设置淡出动画和移除 + setTimeout(() => { + msgElement.style.animation = "messageOut 0.3s ease-in-out"; + setTimeout(() => { + msgElement.remove(); + // 如果容器为空,也移除容器 + if (container.children.length === 0) { + container.remove(); + } + }, 300); + }, timeout); +} + +// Token 输入框自动填充和事件绑定 +function initializeTokenHandling(inputId) { + // 直接尝试填充,如果DOM未准备好会在事件中再试一次 + const tryFillToken = () => { + const tokenInput = document.getElementById(inputId); + if (tokenInput) { + const authToken = getAuthToken(); + if (authToken) { + tokenInput.value = authToken; + } + + // 绑定change事件 + tokenInput.addEventListener("change", (e) => { + if (e.target.value) { + saveAuthToken(e.target.value); + } else { + localStorage.removeItem("authToken"); + localStorage.removeItem("authTokenExpiry"); + } + }); + + return true; + } + return false; + }; + + // 立即尝试执行 + if (!tryFillToken()) { + // 如果元素还不存在,等待DOM加载完成 + if (document.readyState === 'loading') { + document.addEventListener("DOMContentLoaded", tryFillToken); + } else { + // DOM已加载但元素不存在,可能需要等待一下 + setTimeout(tryFillToken, 0); + } + } +} + +// API 请求通用处理 +async function makeAuthenticatedRequest(url, options = {}) { + const tokenId = options.tokenId || "authToken"; + const token = document.getElementById(tokenId).value; + + if (!token) { + showGlobalMessage("请输入 AUTH_TOKEN", true); + return null; + } + + if (!/^[A-Za-z0-9\-._~+/]+=*$/.test(cleanToken)) { + showGlobalMessage("TOKEN格式无效,请检查是否包含特殊字符", true); + return null; + } + + const defaultOptions = { + method: "POST", + headers: { + Authorization: `Bearer ${token}`, + "Content-Type": "application/json", + }, + }; + + try { + const response = await fetch(url, { ...defaultOptions, ...options }); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + return await response.json(); + } catch (error) { + showGlobalMessage(`请求失败: ${error.message}`, true); + return null; + } +} + +/** + * 从字符串解析布尔值 + * @param {string} str - 要解析的字符串 + * @param {boolean|null} defaultValue - 解析失败时的默认值 + * @returns {boolean|null} 解析结果,如果无法解析则返回默认值 + */ +function parseBooleanFromString(str, defaultValue = null) { + if (typeof str !== "string") { + return defaultValue; + } + + const lowercaseStr = str.toLowerCase().trim(); + + if (lowercaseStr === "true" || lowercaseStr === "1") { + return true; + } else if (lowercaseStr === "false" || lowercaseStr === "0") { + return false; + } else { + return defaultValue; + } +} + +/** + * 将布尔值转换为字符串 + * @param {boolean|undefined|null} value - 要转换的布尔值 + * @param {string} defaultValue - 转换失败时的默认值 + * @returns {string} 转换结果,如果输入无效则返回默认值 + */ +function parseStringFromBoolean(value, defaultValue = null) { + if (typeof value !== "boolean") { + return defaultValue; + } + + return value ? "true" : "false"; +} + +/** + * 将会员类型代码转换为显示名称 + * @param {string|null} type - 会员类型代码,如 'free_trial', 'pro', 'free', 'enterprise' 等 + * @returns {string} 格式化后的会员类型显示名称 + * @example + * formatMembershipType('free_trial') // 返回 'Pro Trial' + * formatMembershipType('pro') // 返回 'Pro' + * formatMembershipType(null) // 返回 '-' + * formatMembershipType('custom_type') // 返回 'Custom Type' + */ +function formatMembershipType(type) { + if (!type) return "-"; + switch (type) { + case "free_trial": + return "Pro Trial"; + case "pro": + return "Pro"; + case "free": + return "Free"; + case "enterprise": + return "Business"; + default: + return type + .split("_") + .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) + .join(" "); + } +} + +// 复制文本功能 +/** + * 复制文本到剪贴板 + * @param {string} text - 要复制的文本 + * @param {Object} [options={}] - 配置选项 + * @param {boolean} [options.showMessage=true] - 是否显示复制结果消息 + * @param {string} [options.successMessage='已复制到剪贴板'] - 复制成功时的消息 + * @param {string} [options.errorMessage='复制失败,请手动复制'] - 复制失败时的消息 + * @param {Function} [options.onSuccess] - 复制成功时的回调函数 + * @param {Function} [options.onError] - 复制失败时的回调函数 + * @param {HTMLElement} [options.sourceElement] - 触发复制的源元素(用于显示临时状态) + * @returns {Promise} 返回复制是否成功 + * @example + * // 基础用法 + * copyToClipboard('Hello World'); + * + * // 自定义消息 + * copyToClipboard('代理地址', { + * successMessage: '代理地址已复制', + * errorMessage: '无法复制代理地址' + * }); + * + * // 带回调函数 + * copyToClipboard('敏感信息', { + * showMessage: false, + * onSuccess: () => console.log('复制成功'), + * onError: (err) => console.error('复制失败:', err) + * }); + * + * // 与按钮配合使用 + * const button = document.getElementById('copyBtn'); + * copyToClipboard('文本内容', { sourceElement: button }); + */ +async function copyToClipboard(text, options = {}) { + const { + showMessage = true, + successMessage = "已复制到剪贴板", + errorMessage = "复制失败,请手动复制", + onSuccess, + onError, + sourceElement, + } = options; + + // 验证输入 + if (typeof text !== "string") { + console.error("copyToClipboard: 文本必须是字符串类型"); + if (showMessage) { + showGlobalMessage("无效的复制内容", true); + } + if (onError) { + onError(new Error("Invalid text type")); + } + return false; + } + + // 如果文本为空,给出警告 + if (!text.trim()) { + console.warn("copyToClipboard: 尝试复制空文本"); + if (showMessage) { + showGlobalMessage("没有可复制的内容", true); + } + if (onError) { + onError(new Error("Empty text")); + } + return false; + } + + try { + // 优先使用现代 Clipboard API + if (navigator.clipboard && window.isSecureContext) { + await navigator.clipboard.writeText(text); + handleCopySuccess(); + return true; + } else { + // 降级到传统方法 + const success = fallbackCopyToClipboard(text); + if (success) { + handleCopySuccess(); + return true; + } else { + throw new Error("Fallback copy failed"); + } + } + } catch (error) { + console.error("复制到剪贴板失败:", error); + + if (showMessage) { + showGlobalMessage(errorMessage, true); + } + + if (onError) { + onError(error); + } + + return false; + } + + // 处理复制成功 + function handleCopySuccess() { + if (showMessage) { + showGlobalMessage(successMessage); + } + + if (onSuccess) { + onSuccess(); + } + + // 如果提供了源元素,可以添加临时的视觉反馈 + if (sourceElement) { + addTemporaryClass(sourceElement, "copied", 2000); + } + } +} + +/** + * 传统的复制方法(用于不支持 Clipboard API 的浏览器) + * @private + * @param {string} text - 要复制的文本 + * @returns {boolean} 是否复制成功 + */ +function fallbackCopyToClipboard(text) { + // 创建临时文本区域 + const textArea = document.createElement("textarea"); + + // 设置样式使其不可见但可复制 + textArea.value = text; + textArea.style.position = "fixed"; + textArea.style.top = "0"; + textArea.style.left = "0"; + textArea.style.width = "2em"; + textArea.style.height = "2em"; + textArea.style.padding = "0"; + textArea.style.border = "none"; + textArea.style.outline = "none"; + textArea.style.boxShadow = "none"; + textArea.style.background = "transparent"; + textArea.style.opacity = "0"; + textArea.style.pointerEvents = "none"; + + // 防止移动设备上的缩放 + textArea.style.fontSize = "12pt"; + + document.body.appendChild(textArea); + + try { + // 选择文本 + textArea.select(); + textArea.setSelectionRange(0, text.length); + + // 执行复制 + const successful = document.execCommand("copy"); + + // 清理 + document.body.removeChild(textArea); + + return successful; + } catch (error) { + console.error("传统复制方法失败:", error); + // 确保清理 + if (document.body.contains(textArea)) { + document.body.removeChild(textArea); + } + return false; + } +} + +/** + * 为元素临时添加 CSS 类 + * @private + * @param {HTMLElement} element - 目标元素 + * @param {string} className - 要添加的类名 + * @param {number} duration - 持续时间(毫秒) + */ +function addTemporaryClass(element, className, duration) { + if (!element || !className) return; + + element.classList.add(className); + setTimeout(() => { + element.classList.remove(className); + }, duration); +} + +/** + * 复制表格单元格内容 + * @param {HTMLElement} cell - 表格单元格元素 + * @param {Object} [options={}] - 复制选项(同 copyToClipboard) + * @returns {Promise} 是否复制成功 + * @example + * // 在表格单元格点击事件中使用 + * td.onclick = () => copyTableCellContent(td); + */ +async function copyTableCellContent(cell, options = {}) { + if (!cell) { + console.error("copyTableCellContent: 未提供有效的单元格元素"); + return false; + } + + // 获取纯文本内容(去除 HTML 标签) + const text = cell.textContent || cell.innerText || ""; + + return copyToClipboard(text.trim(), { + ...options, + sourceElement: cell, + }); +} + +/** + * 创建带复制功能的按钮 + * @param {string} text - 要复制的文本 + * @param {Object} [options={}] - 按钮配置选项 + * @param {string} [options.buttonText='复制'] - 按钮文本 + * @param {string} [options.buttonClass='copy-button'] - 按钮CSS类 + * @param {string} [options.copiedText='已复制'] - 复制成功后的按钮文本 + * @param {number} [options.resetDelay=2000] - 按钮文本重置延迟(毫秒) + * @returns {HTMLButtonElement} 创建的按钮元素 + * @example + * // 创建一个复制按钮 + * const copyBtn = createCopyButton('要复制的文本', { + * buttonText: '复制密钥', + * copiedText: '✓ 已复制' + * }); + * document.getElementById('container').appendChild(copyBtn); + */ +function createCopyButton(text, options = {}) { + const { + buttonText = "复制", + buttonClass = "copy-button", + copiedText = "已复制", + resetDelay = 2000, + } = options; + + const button = document.createElement("button"); + button.textContent = buttonText; + button.className = buttonClass; + button.type = "button"; + + button.addEventListener("click", async () => { + const originalText = button.textContent; + + const success = await copyToClipboard(text, { + sourceElement: button, + showMessage: true, + }); + + if (success) { + button.textContent = copiedText; + button.disabled = true; + + setTimeout(() => { + button.textContent = originalText; + button.disabled = false; + }, resetDelay); + } + }); + + return button; +} + +/** + * 检查剪贴板 API 是否可用 + * @returns {boolean} 是否支持 Clipboard API + * @example + * if (isClipboardSupported()) { + * console.log('浏览器支持现代剪贴板 API'); + * } + */ +function isClipboardSupported() { + return !!(navigator.clipboard && window.isSecureContext); +} + +/** + * 从剪贴板读取文本(需要用户权限) + * @param {Object} [options={}] - 配置选项 + * @param {boolean} [options.showMessage=true] - 是否显示结果消息 + * @param {Function} [options.onSuccess] - 读取成功时的回调 + * @param {Function} [options.onError] - 读取失败时的回调 + * @returns {Promise} 剪贴板中的文本,失败时返回 null + * @example + * const text = await readFromClipboard(); + * if (text) { + * console.log('剪贴板内容:', text); + * } + */ +async function readFromClipboard(options = {}) { + const { showMessage = true, onSuccess, onError } = options; + + if (!isClipboardSupported()) { + const error = new Error("浏览器不支持剪贴板 API"); + if (showMessage) { + showGlobalMessage("浏览器不支持读取剪贴板", true); + } + if (onError) { + onError(error); + } + return null; + } + + try { + const text = await navigator.clipboard.readText(); + + if (onSuccess) { + onSuccess(text); + } + + return text; + } catch (error) { + console.error("读取剪贴板失败:", error); + + if (showMessage) { + if (error.name === "NotAllowedError") { + showGlobalMessage("需要您的许可才能读取剪贴板", true); + } else { + showGlobalMessage("无法读取剪贴板内容", true); + } + } + + if (onError) { + onError(error); + } + + return null; + } +} diff --git a/static/tokens.html b/static/tokens.html new file mode 100644 index 0000000..f6eec8f --- /dev/null +++ b/static/tokens.html @@ -0,0 +1,3743 @@ + + + + + + + Token 信息管理 + + + + + + + +

Token 信息管理

+ +
+
+ + +
+
+ + +
+
+
+ + +
+
+ + +
+
+ +
+ + + +
+
+
+ +
+ + + +
+
+
+ +
+
+
+ + +
+
+ + +
+
+
+ + + + +
+
+
+ + +
+
+ +
+ +
+ + + + + + + + + + + + + + +
令牌会员类型用量试用剩余时区代理
+
+
📁
+

没有找到Token

+

请添加Token或更改筛选条件

+ +
+ +
+
+ +
+
已选择: 0 个项目
+
共 0 个Token
+
+
+ + +
+
+ 切换状态 +
+
+ 启用 + 0 + +
+
+ 禁用 + 0 + +
+
+
+
+ 查看详情 + Enter +
+
+ 重命名 + F2 +
+
+ 刷新Profile + F5 +
+
+ 刷新Token + Ctrl+U +
+
+ 刷新Config Version + F6 +
+
+ 生成Key + Ctrl+G +
+
+ 复制Token + Ctrl+C +
+
+ 设置时区 +
+
+ 设置代理 +
+
+ 未指定 + 0 + +
+
+ +
+
+
+
+ 删除 + Delete +
+
+ + +
+
+

Token详情

+ +
+
+ +
+
+ + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + \ No newline at end of file