Files
grok2api/app/api/pages/embedded_assets.py

18 lines
119 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""内嵌页面资源,用于 Serverless 环境回退。"""
EMBEDDED_ASSETS = {
"public/pages/login.html": "<!DOCTYPE html>\n<html lang=\"zh-CN\">\n\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover, interactive-widget=overlays-content\">\n <title>Grok2API - Public</title>\n <link rel=\"icon\" href=\"/static/common/img/favicon/favicon.ico\">\n <link rel=\"manifest\" href=\"/manifest.webmanifest\">\n <meta name=\"theme-color\" content=\"#fafafa\">\n <meta name=\"mobile-web-app-capable\" content=\"yes\">\n <meta name=\"apple-mobile-web-app-capable\" content=\"yes\">\n <meta name=\"apple-mobile-web-app-status-bar-style\" content=\"default\">\n <meta name=\"apple-mobile-web-app-title\" content=\"Grok2API\">\n <link rel=\"apple-touch-icon\" href=\"/static/public/pwa/apple-touch-icon.png\">\n <script src=\"/static/vendor/tailwind/tailwindcss-cdn.js\"></script>\n <link href=\"/static/vendor/geist/geist-sans.css\" rel=\"stylesheet\">\n <link href=\"/static/vendor/geist/geist-mono.css\" rel=\"stylesheet\">\n <script>\n tailwind.config = {\n theme: {\n extend: {\n fontFamily: { sans: ['Geist Sans', 'sans-serif'] },\n colors: {\n dark: '#000000',\n light: '#ffffff',\n subtle: '#666666',\n border: '#e5e5e5'\n }\n }\n }\n }\n </script>\n <script src=\"/static/common/js/theme.js?v=0.1.0\"></script>\n <link href=\"/static/common/css/common.css?v=0.3.7\" rel=\"stylesheet\">\n <link href=\"/static/common/css/login.css?v=0.3.0\" rel=\"stylesheet\">\n <link href=\"/static/common/css/toast.css\" rel=\"stylesheet\">\n</head>\n\n<body class=\"login-body\">\n <div class=\"login-bg\" aria-hidden=\"true\"></div>\n\n <div class=\"login-shell\">\n <div class=\"login-card\">\n <div class=\"login-brand\">\n <a href=\"https://github.com/chenyme/grok2api\" target=\"_blank\" rel=\"noopener\" class=\"login-brand-link\">\n <svg class=\"login-github-icon\" viewBox=\"0 0 24 24\" aria-hidden=\"true\">\n <path\n d=\"M12 2C6.48 2 2 6.58 2 12.26c0 4.5 2.87 8.32 6.84 9.67.5.1.68-.22.68-.48 0-.24-.01-.86-.01-1.69-2.78.62-3.37-1.37-3.37-1.37-.45-1.17-1.11-1.48-1.11-1.48-.91-.64.07-.63.07-.63 1.01.07 1.54 1.06 1.54 1.06.9 1.58 2.36 1.12 2.94.86.09-.67.35-1.12.63-1.38-2.22-.26-4.56-1.14-4.56-5.07 0-1.12.39-2.04 1.03-2.76-.1-.26-.45-1.3.1-2.71 0 0 .84-.27 2.75 1.05A9.3 9.3 0 0 1 12 6.79c.85 0 1.7.12 2.5.36 1.9-1.32 2.74-1.05 2.74-1.05.55 1.41.2 2.45.1 2.71.64.72 1.03 1.64 1.03 2.76 0 3.94-2.34 4.81-4.58 5.06.36.32.68.95.68 1.92 0 1.38-.01 2.49-.01 2.83 0 .26.18.58.69.48A10.05 10.05 0 0 0 22 12.26C22 6.58 17.52 2 12 2Z\" />\n </svg>\n <span>Grok2API</span>\n </a>\n </div>\n <div class=\"login-title\">Public Access</div>\n <div class=\"login-subtitle\">请输入 Public Key(如未配置可直接进入)</div>\n\n <div class=\"login-form\">\n <input type=\"password\" id=\"public-key-input\" placeholder=\"Public Key\" class=\"geist-input login-input\">\n <button onclick=\"login()\" class=\"geist-button login-button\">进入</button>\n </div>\n\n <div class=\"mt-3 text-xs text-[var(--accents-4)]\">\n <a href=\"/admin/login\" class=\"hover:text-black\">进入管理后台</a>\n </div>\n </div>\n </div>\n\n <div id=\"app-footer\"></div>\n\n <script src=\"/static/common/js/admin-auth.js?v=0.3.0\"></script>\n <script src=\"/static/common/js/toast.js\"></script>\n <script src=\"/static/common/js/footer.js?v=0.3.0\"></script>\n <script src=\"/static/common/js/pwa.js?v=0.1.1\"></script>\n <script src=\"/static/public/js/login.js?v=0.3.0\"></script>\n</body>\n\n</html>\n",
"public/pages/imagine.html": "<!DOCTYPE html>\n<html lang=\"zh-CN\">\n\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\"\n content=\"width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover\">\n <title>Grok2API - Imagine 瀑布流</title>\n <link rel=\"icon\" href=\"/static/common/img/favicon/favicon.ico\">\n <link rel=\"manifest\" href=\"/manifest.webmanifest\">\n <meta name=\"theme-color\" content=\"#fafafa\">\n <meta name=\"mobile-web-app-capable\" content=\"yes\">\n <meta name=\"apple-mobile-web-app-capable\" content=\"yes\">\n <meta name=\"apple-mobile-web-app-status-bar-style\" content=\"default\">\n <meta name=\"apple-mobile-web-app-title\" content=\"Grok2API\">\n <link rel=\"apple-touch-icon\" href=\"/static/public/pwa/apple-touch-icon.png\">\n <script src=\"/static/vendor/tailwind/tailwindcss-cdn.js\"></script>\n <link href=\"/static/vendor/geist/geist-sans.css\" rel=\"stylesheet\">\n <link href=\"/static/vendor/geist/geist-mono.css\" rel=\"stylesheet\">\n <script src=\"/static/common/js/theme.js?v=0.1.0\"></script>\n <link href=\"/static/common/css/common.css?v=0.3.8\" rel=\"stylesheet\">\n <link href=\"/static/public/css/imagine.css?v=0.5.1\" rel=\"stylesheet\">\n <link href=\"/static/common/css/toast.css\" rel=\"stylesheet\">\n</head>\n\n<body class=\"public-page min-h-screen flex flex-col\" style=\"background-color: var(--bg);\">\n <div id=\"toast-container\" class=\"toast-container\"></div>\n <div id=\"app-header\"></div>\n\n <main class=\"flex-1 container mx-auto max-w-5xl px-4 sm:px-6 py-5 sm:py-8 fade-in\">\n <div class=\"space-y-4 sm:space-y-6\">\n <div>\n <h2 class=\"text-2xl font-semibold tracking-tight\">Imagine 瀑布流</h2>\n <p class=\"text-[var(--accents-4)] mt-1 text-sm\">通过 WebSocket 持续生成图片,实时展示 base64 瀑布流。</p>\n </div>\n\n <div class=\"h-px bg-[var(--border)]\"></div>\n\n <div class=\"imagine-top-grid\">\n <div class=\"imagine-card imagine-card-collapsible settings-card\">\n <div class=\"card-content\" id=\"settingsContent\">\n <div class=\"card-title\">生成设置</div>\n <div class=\"settings-grid\">\n <div class=\"settings-block settings-block-prompt\">\n <div class=\"settings-prompt\">\n <label class=\"field-label\" for=\"promptInput\">提示词</label>\n <textarea id=\"promptInput\" class=\"geist-input imagine-textarea\"\n placeholder=\"描述你想要的画面,例如:未来城市霓虹雨夜,广角摄影\"></textarea>\n </div>\n </div>\n <div class=\"settings-block settings-block-duo\">\n <div class=\"toggle-row\">\n <div class=\"toggle-text\">\n <div class=\"toggle-title\">自动跟随</div>\n <div class=\"toggle-desc\">滚动到最新</div>\n </div>\n <label class=\"toggle\">\n <input id=\"autoScrollToggle\" type=\"checkbox\" checked>\n <span class=\"toggle-track\"></span>\n </label>\n </div>\n <div class=\"toggle-row\">\n <div class=\"toggle-text\">\n <div class=\"toggle-title\">自动保存</div>\n <div class=\"toggle-desc\">自动保存图片</div>\n </div>\n <label class=\"toggle\">\n <input id=\"autoDownloadToggle\" type=\"checkbox\">\n <span class=\"toggle-track\"></span>\n </label>\n </div>\n </div>\n <div class=\"settings-block settings-block-row2\">\n <div class=\"settings-row\">\n <div class=\"settings-field\">\n <label class=\"field-label\" for=\"ratioSelect\">宽高比</label>\n <select id=\"ratioSelect\" class=\"geist-input\">\n <option value=\"2:3\" selected>2:3 竖图</option>\n <option value=\"1:1\">1:1 方图</option>\n <option value=\"3:2\">3:2 横图</option>\n <option value=\"16:9\">16:9 宽屏</option>\n <option value=\"9:16\">9:16 竖屏</option>\n </select>\n </div>\n <div class=\"settings-field\">\n <label class=\"field-label\" for=\"concurrentSelect\">并发数量</label>\n <select id=\"concurrentSelect\" class=\"geist-input\">\n <option value=\"1\" selected>1 个任务</option>\n <option value=\"2\">2 个任务</option>\n <option value=\"3\">3 个任务</option>\n </select>\n </div>\n </div>\n </div>\n <div class=\"settings-block settings-block-row2b settings-block-single\">\n <div class=\"toggle-row toggle-row-fill\">\n <div class=\"toggle-text\">\n <div class=\"toggle-title\">自动过滤</div>\n <div class=\"toggle-desc\">过滤不达标图片</div>\n </div>\n <label class=\"toggle\">\n <input id=\"autoFilterToggle\" type=\"checkbox\">\n <span class=\"toggle-track\"></span>\n </label>\n </div>\n </div>\n <div class=\"settings-block settings-block-row3\">\n <div class=\"settings-row\">\n <div class=\"settings-field\">\n <label class=\"field-label\" for=\"nsfwSelect\">是否 NSFW</label>\n <select id=\"nsfwSelect\" class=\"geist-input\">\n <option value=\"true\">开启</option>\n <option value=\"false\">关闭</option>\n </select>\n </div>\n <div class=\"settings-folder\">\n <label class=\"field-label\">保存位置</label>\n <button id=\"selectFolderBtn\" class=\"geist-button-outline folder-select-btn\" disabled>\n <svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\"\n stroke-width=\"2\">\n <path d=\"M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z\"></path>\n </svg>\n <span id=\"folderPath\">浏览器默认位置</span>\n </button>\n </div>\n </div>\n </div>\n <div class=\"settings-block settings-block-row3b settings-block-single\">\n <div class=\"toggle-row toggle-row-fill\">\n <div class=\"toggle-text\">\n <div class=\"toggle-title\">反向新增</div>\n <div class=\"toggle-desc\">最新显示在上方</div>\n </div>\n <label class=\"toggle\">\n <input id=\"reverseInsertToggle\" type=\"checkbox\">\n <span class=\"toggle-track\"></span>\n </label>\n </div>\n </div>\n </div>\n </div>\n </div>\n\n <div class=\"imagine-card imagine-card-collapsible\">\n <div class=\"status-header-row\" id=\"statusToggle\">\n <div class=\"card-title\">连接状态</div>\n <span id=\"statusText\" class=\"status-text\">未连接</span>\n <svg class=\"collapse-icon\" width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\"\n stroke-width=\"2\">\n <polyline points=\"6 9 12 15 18 9\"></polyline>\n </svg>\n </div>\n <div class=\"card-content\" id=\"statusContent\">\n <div class=\"meta-grid\">\n <div class=\"meta-item mode-item\">\n <div class=\"meta-label\">连接模式</div>\n <div class=\"mode-switch\" role=\"group\" aria-label=\"Connection mode\">\n <button class=\"mode-btn\" data-mode=\"auto\" type=\"button\">Auto</button>\n <button class=\"mode-btn\" data-mode=\"ws\" type=\"button\">WS</button>\n <button class=\"mode-btn\" data-mode=\"sse\" type=\"button\">SSE</button>\n </div>\n </div>\n <div class=\"meta-item\">\n <div class=\"meta-label\">图片数量</div>\n <div id=\"countValue\" class=\"meta-value\">0</div>\n </div>\n <div class=\"meta-item\">\n <div class=\"meta-label\">活跃任务</div>\n <div id=\"activeValue\" class=\"meta-value\">0</div>\n </div>\n <div class=\"meta-item\">\n <div class=\"meta-label\">平均耗时</div>\n <div id=\"latencyValue\" class=\"meta-value\">-</div>\n </div>\n </div>\n </div>\n </div>\n </div>\n\n <div class=\"imagine-card\" style=\"overflow: hidden;\">\n <div class=\"waterfall-header\">\n <div class=\"card-title\">瀑布流</div>\n <div class=\"waterfall-actions\">\n </div>\n </div>\n <div id=\"emptyState\" class=\"imagine-empty\">\n <div class=\"empty-title\">等待连接中</div>\n <div class=\"empty-subtitle\">启动任务后,生成的图片会自动汇聚到这里</div>\n </div>\n <div id=\"waterfall\" class=\"waterfall\"></div>\n </div>\n </main>\n\n <!-- 图片放大预览 -->\n <div id=\"lightbox\" class=\"image-lightbox\">\n <button class=\"lightbox-close\" id=\"closeLightbox\">\n <svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\"></line>\n <line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\"></line>\n </svg>\n </button>\n <button class=\"lightbox-nav lightbox-prev\" id=\"lightboxPrev\">\n <svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <polyline points=\"15 18 9 12 15 6\"></polyline>\n </svg>\n </button>\n <button class=\"lightbox-nav lightbox-next\" id=\"lightboxNext\">\n <svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <polyline points=\"9 18 15 12 9 6\"></polyline>\n </svg>\n </button>\n <img id=\"lightboxImg\" src=\"\" alt=\"\">\n <div class=\"lightbox-editor\">\n <div class=\"lightbox-edit-layout\">\n <div class=\"lightbox-edit-left\">\n <div class=\"lightbox-edit-main\">\n <textarea id=\"lightboxEditInput\" class=\"lightbox-edit-input\"\n placeholder=\"输入编辑提示词,例如:保持构图不变,把天空改成黄昏\"></textarea>\n <button id=\"lightboxEditSend\" class=\"lightbox-edit-send\" type=\"button\" disabled>发送编辑</button>\n </div>\n <div id=\"lightboxEditProgressWrap\" class=\"lightbox-edit-progress-wrap\">\n <div id=\"lightboxEditProgressBar\" class=\"lightbox-edit-progress-bar\"></div>\n </div>\n <div id=\"lightboxEditProgressText\" class=\"lightbox-edit-progress-text\">编辑中 0%</div>\n </div>\n <aside class=\"lightbox-history-panel\">\n <div class=\"lightbox-history-head\">\n <div class=\"lightbox-history-title\">编辑历史</div>\n <div id=\"lightboxHistoryCount\" class=\"lightbox-history-count\">0 条</div>\n </div>\n <div id=\"lightboxHistoryEmpty\" class=\"lightbox-history-empty\">当前图片还没有编辑记录</div>\n <div id=\"lightboxHistoryList\" class=\"lightbox-history-list\"></div>\n </aside>\n </div>\n </div>\n </div>\n\n <div id=\"floatingActions\" class=\"floating-actions\">\n <button id=\"startBtn\" class=\"geist-button-outline text-xs px-3 gap-1 border-0 hover:bg-gray-100\">\n <svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <polygon points=\"5 3 19 12 5 21 5 3\" />\n </svg>\n 开始\n </button>\n <button id=\"stopBtn\" class=\"geist-button-outline text-xs px-3 gap-1 border-0 hover:bg-gray-100 hidden\">\n <svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <rect x=\"5\" y=\"5\" width=\"14\" height=\"14\" />\n </svg>\n 停止\n </button>\n <span class=\"toolbar-sep\"></span>\n <button id=\"clearBtn\" class=\"geist-button-outline text-xs px-3 gap-1 border-0 hover:bg-red-100\">\n <svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <path\n d=\"M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16\" />\n </svg>\n 清空\n </button>\n <span class=\"toolbar-sep\"></span>\n <button id=\"batchDownloadBtn\"\n class=\"batch-download-btn geist-button-outline text-xs px-3 gap-1 border-0 hover:bg-gray-100\" title=\"批量下载\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <path d=\"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4\"></path>\n <polyline points=\"7 10 12 15 17 10\"></polyline>\n <line x1=\"12\" y1=\"15\" x2=\"12\" y2=\"3\"></line>\n </svg>\n <span>批量</span>\n </button>\n <div id=\"selectionToolbar\" class=\"selection-toolbar hidden\">\n <button id=\"toggleSelectAllBtn\"\n class=\"geist-button-outline text-xs px-3 gap-1 border-0 hover:bg-gray-100\">全选</button>\n <button id=\"downloadSelectedBtn\" class=\"geist-button-outline text-xs px-3 gap-1 border-0 hover:bg-gray-100\">下载\n <span id=\"selectedCount\" class=\"selected-count\">0</span></button>\n </div>\n </div>\n\n <!-- 移动端生成设置遮罩(Bottom Sheet) -->\n <div id=\"imSettingsOverlay\" class=\"im-settings-overlay\"></div>\n\n <!-- 移动端底部 Sticky 操作栏 -->\n <div id=\"imStickyBar\" class=\"im-sticky-bar\">\n <div class=\"im-sticky-bar-inner\">\n <!-- 左侧:齿轮(生成设置) -->\n <button class=\"im-icon-btn\" id=\"imSettingsBtn\" type=\"button\" title=\"生成设置\" aria-label=\"生成设置\">\n <svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"\n stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <circle cx=\"12\" cy=\"12\" r=\"3\"/>\n <path d=\"M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z\"/>\n </svg>\n </button>\n <div class=\"im-icon-divider\"></div>\n <!-- 图标区 -->\n <div class=\"im-icon-group\">\n <!-- 清空 -->\n <button class=\"im-icon-btn im-icon-btn--danger\" id=\"imClearBtn\" type=\"button\" title=\"清空图片\" aria-label=\"清空\">\n <svg width=\"17\" height=\"17\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"\n stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <polyline points=\"3 6 5 6 21 6\"/><path d=\"M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6\"/>\n <path d=\"M10 11v6M14 11v6\"/><path d=\"M9 6V4h6v2\"/>\n </svg>\n </button>\n <!-- 批量下载全部(layers 图标,与下载选中区分) -->\n <button class=\"im-icon-btn\" id=\"imBatchBtn\" type=\"button\" title=\"批量下载全部\" aria-label=\"批量下载\">\n <svg width=\"17\" height=\"17\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"\n stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <polygon points=\"12 2 2 7 12 12 22 7 12 2\"/>\n <polyline points=\"2 17 12 22 22 17\"/>\n <polyline points=\"2 12 12 17 22 12\"/>\n </svg>\n </button>\n <!-- 全选 -->\n <button class=\"im-icon-btn\" id=\"imSelectAllBtn\" type=\"button\" title=\"全选\" aria-label=\"全选\">\n <svg width=\"17\" height=\"17\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"\n stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <polyline points=\"9 11 12 14 22 4\"/>\n <path d=\"M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11\"/>\n </svg>\n </button>\n <!-- 下载选中(带数字角标) -->\n <button class=\"im-icon-btn im-download-selected-btn\" id=\"imDownloadSelectedBtn\" type=\"button\"\n title=\"下载选中\" aria-label=\"下载选中\">\n <svg width=\"17\" height=\"17\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"\n stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4\"/>\n <polyline points=\"7 10 12 15 17 10\"/><line x1=\"12\" y1=\"15\" x2=\"12\" y2=\"3\"/>\n </svg>\n <span id=\"imSelectedBadge\" class=\"im-selected-badge\">0</span>\n </button>\n </div>\n <!-- 右侧:主按钮(开始/停止) -->\n <button id=\"imStartStopBtn\" class=\"geist-button im-main-btn\" type=\"button\">\n <svg class=\"im-btn-icon-play\" width=\"13\" height=\"13\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\"\n stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <polygon points=\"5 3 19 12 5 21 5 3\"/>\n </svg>\n <svg class=\"im-btn-icon-stop\" width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\"\n stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" style=\"display:none\">\n <rect x=\"5\" y=\"5\" width=\"14\" height=\"14\"/>\n </svg>\n <span class=\"im-btn-label\">开始</span>\n </button>\n </div>\n </div>\n\n <div id=\"app-footer\"></div>\n\n <script src=\"/static/common/js/admin-auth.js?v=0.3.0\"></script>\n <script src=\"/static/common/js/parentpost-memory.js?v=0.1.0\"></script>\n <script src=\"/static/common/js/public-header.js?v=0.3.3\"></script>\n <script src=\"/static/common/js/footer.js?v=0.3.0\"></script>\n <script src=\"/static/common/js/toast.js\"></script>\n <script src=\"/static/common/js/prompt-enhancer.js?v=0.1.15\"></script>\n <script src=\"/static/common/js/pwa.js?v=0.1.1\"></script>\n <script src=\"https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js\"></script>\n <script src=\"/static/public/js/imagine.js?v=0.5.0\"></script>\n</body>\n\n</html>",
"public/pages/voice.html": "<!DOCTYPE html>\n<html lang=\"zh-CN\">\n\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover, interactive-widget=overlays-content\">\n <title>Grok2API - LiveKit 陪聊</title>\n <link rel=\"icon\" href=\"/static/common/img/favicon/favicon.ico\">\n <link rel=\"manifest\" href=\"/manifest.webmanifest\">\n <meta name=\"theme-color\" content=\"#fafafa\">\n <meta name=\"mobile-web-app-capable\" content=\"yes\">\n <meta name=\"apple-mobile-web-app-capable\" content=\"yes\">\n <meta name=\"apple-mobile-web-app-status-bar-style\" content=\"default\">\n <meta name=\"apple-mobile-web-app-title\" content=\"Grok2API\">\n <link rel=\"apple-touch-icon\" href=\"/static/public/pwa/apple-touch-icon.png\">\n <script src=\"/static/vendor/tailwind/tailwindcss-cdn.js\"></script>\n <link href=\"/static/vendor/geist/geist-sans.css\" rel=\"stylesheet\">\n <link href=\"/static/vendor/geist/geist-mono.css\" rel=\"stylesheet\">\n <script src=\"/static/common/js/theme.js?v=0.1.0\"></script>\n <link href=\"/static/common/css/common.css?v=0.3.7\" rel=\"stylesheet\">\n <link href=\"/static/public/css/voice.css?v=0.3.1\" rel=\"stylesheet\">\n <link href=\"/static/common/css/toast.css\" rel=\"stylesheet\">\n <script src=\"https://cdn.jsdelivr.net/npm/livekit-client@2.11.4/dist/livekit-client.umd.min.js\"></script>\n</head>\n\n<body class=\"public-page min-h-screen flex flex-col\" style=\"background-color: var(--bg);\">\n <div id=\"toast-container\" class=\"toast-container\"></div>\n <div id=\"app-header\"></div>\n\n <main class=\"space-y-4 sm:space-y-6 flex-1 container mx-auto max-w-5xl px-4 sm:px-6 py-5 sm:py-8 fade-in\">\n <div class=\"space-y-4 sm:space-y-6\">\n <div class=\"flex flex-col gap-4 md:flex-row md:items-center md:justify-between\">\n <div>\n <h2 class=\"text-2xl font-semibold tracking-tight\">LiveKit 陪聊</h2>\n <p class=\"text-[var(--accents-4)] mt-1 text-sm\">LiveKit 语音会话,连接 Grok Voice。</p>\n </div>\n <div class=\"voice-actions\">\n <button id=\"startBtn\" class=\"geist-button gap-2\">\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"\n stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <polygon points=\"5 3 19 12 5 21 5 3\" />\n </svg>\n 开始会话\n </button>\n <button id=\"stopBtn\" class=\"geist-button-outline gap-2 hidden\">\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"\n stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <rect x=\"5\" y=\"5\" width=\"14\" height=\"14\" />\n </svg>\n 停止会话\n </button>\n </div>\n </div>\n\n <div class=\"h-px bg-[var(--border)] my-6\"></div>\n\n <div class=\"voice-grid\">\n <section class=\"voice-primary\">\n <div class=\"voice-card\">\n <div class=\"card-title\">连接设置</div>\n <div class=\"grid grid-cols-1 md:grid-cols-2 gap-4\">\n <div>\n <label class=\"field-label\" for=\"voiceSelect\">声音 (Voice)</label>\n <select id=\"voiceSelect\" class=\"geist-input\">\n <option value=\"ara\" selected>Ara (Upbeat Female)</option>\n <option value=\"eve\">Eve (Soothing Female)</option>\n <option value=\"leo\">Leo (British Male)</option>\n <option value=\"rex\">Rex (Calm Male)</option>\n <option value=\"sal\">Sal (Smooth Male)</option>\n <option value=\"gork\">Gork (Lazy Male)</option>\n </select>\n </div>\n\n <div>\n <label class=\"field-label\" for=\"personalitySelect\">个性 (Personality)</label>\n <select id=\"personalitySelect\" class=\"geist-input\">\n <option value=\"assistant\" selected>Assistant (Default)</option>\n <option value=\"custom\">Custom</option>\n <option value=\"therapist\">Therapist</option>\n <option value=\"storyteller\">Storyteller</option>\n <option value=\"kids_story_time\">Kids Story Time</option>\n <option value=\"kids_trivia_game\">Kids Trivia Game</option>\n <option value=\"meditation\">Meditation</option>\n <option value=\"doc\">Grok \"Doc\"</option>\n <option value=\"unhinged\">Unhinged 18+</option>\n <option value=\"sexy\">Sexy 18+</option>\n <option value=\"motivation\">Motivation 18+</option>\n <option value=\"conspiracy\">Conspiracy</option>\n <option value=\"romantic\">Romantic 18+</option>\n <option value=\"argumentative\">Argumentative 18+</option>\n </select>\n </div>\n\n <div class=\"md:col-span-2\">\n <div class=\"flex items-center justify-between\">\n <label class=\"field-label\" for=\"speedRange\">语速</label>\n <span class=\"range-value\"><span id=\"speedValue\">1.0</span>x</span>\n </div>\n <input type=\"range\" id=\"speedRange\" class=\"range-input\" min=\"0.5\" max=\"2.0\" step=\"0.1\" value=\"1.0\">\n </div>\n </div>\n <div class=\"voice-hint mt-3\">提示:Voice / Personality 会在建立连接前发送到服务端。</div>\n </div>\n\n <div class=\"voice-card\">\n <div class=\"voice-log-header\">\n <div class=\"card-title\">会话日志</div>\n <div class=\"flex items-center gap-2\">\n <button id=\"clearLogBtn\" class=\"geist-button-outline text-xs px-3\">清空</button>\n <button id=\"copyLogBtn\" class=\"geist-button-outline text-xs px-3\">复制</button>\n </div>\n </div>\n <div id=\"log\" class=\"voice-log\"></div>\n </div>\n </section>\n\n <aside class=\"voice-side\">\n <div class=\"voice-card\">\n <div class=\"voice-log-header voice-status-header\">\n <div class=\"card-title\">会话状态</div>\n <span id=\"statusText\" class=\"status-text\">未连接</span>\n </div>\n\n <div class=\"meta-grid\">\n <div class=\"meta-item\">\n <div class=\"meta-label\">Voice</div>\n <div id=\"statusVoice\" class=\"meta-value\">-</div>\n </div>\n <div class=\"meta-item\">\n <div class=\"meta-label\">Personality</div>\n <div id=\"statusPersonality\" class=\"meta-value\">-</div>\n </div>\n <div class=\"meta-item\">\n <div class=\"meta-label\">Speed</div>\n <div id=\"statusSpeed\" class=\"meta-value\">-</div>\n </div>\n <div class=\"meta-item\">\n <div class=\"meta-label\">LiveKit</div>\n <div class=\"meta-value\">wss://livekit.grok.com</div>\n </div>\n </div>\n\n <div class=\"visualizer\" id=\"visualizer\"></div>\n </div>\n\n <div class=\"voice-card\">\n <div class=\"card-title\">音频输出</div>\n <div class=\"voice-hint\">订阅到的音轨会自动插入此区域。</div>\n <div id=\"audioRoot\" class=\"mt-3\"></div>\n </div>\n </aside>\n </div>\n </div>\n </main>\n\n <div class=\"mobile-fab\" data-mobile-fab>\n <div class=\"mobile-fab-panel\">\n <div class=\"mobile-fab-actions\">\n <button class=\"geist-button\" data-proxy-click=\"#startBtn\" type=\"button\">开始会话</button>\n <button class=\"geist-button-outline\" data-proxy-click=\"#stopBtn\" type=\"button\">停止会话</button>\n <button class=\"geist-button-outline\" data-proxy-click=\"#clearLogBtn\" type=\"button\">清空日志</button>\n <button class=\"geist-button-outline\" data-proxy-click=\"#copyLogBtn\" type=\"button\">复制日志</button>\n </div>\n </div>\n <button class=\"mobile-fab-toggle\" data-fab-toggle type=\"button\" aria-label=\"打开操作菜单\">\n <svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <path d=\"M12 5v14M5 12h14\" />\n </svg>\n </button>\n </div>\n\n <div id=\"app-footer\"></div>\n\n <script src=\"/static/common/js/admin-auth.js?v=0.3.0\"></script>\n <script src=\"/static/common/js/public-header.js?v=0.3.3\"></script>\n <script src=\"/static/common/js/footer.js?v=0.3.0\"></script>\n <script src=\"/static/common/js/mobile-fab.js?v=0.1.1\"></script>\n <script src=\"/static/common/js/toast.js\"></script>\n <script src=\"/static/common/js/pwa.js?v=0.1.1\"></script>\n <script src=\"/static/public/js/voice.js?v=0.3.3\"></script>\n</body>\n\n</html>\n",
"public/pages/video.html": "<!DOCTYPE html>\n<html lang=\"zh-CN\">\n\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\"\n content=\"width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover, interactive-widget=overlays-content\">\n <title>Grok2API - Video 视频工作台</title>\n <link rel=\"icon\" href=\"/static/common/img/favicon/favicon.ico\">\n <link rel=\"manifest\" href=\"/manifest.webmanifest\">\n <meta name=\"theme-color\" content=\"#fafafa\">\n <meta name=\"mobile-web-app-capable\" content=\"yes\">\n <meta name=\"apple-mobile-web-app-capable\" content=\"yes\">\n <meta name=\"apple-mobile-web-app-status-bar-style\" content=\"default\">\n <meta name=\"apple-mobile-web-app-title\" content=\"Grok2API\">\n <link rel=\"apple-touch-icon\" href=\"/static/public/pwa/apple-touch-icon.png\">\n <script src=\"/static/vendor/tailwind/tailwindcss-cdn.js\"></script>\n <link href=\"/static/vendor/geist/geist-sans.css\" rel=\"stylesheet\">\n <link href=\"/static/vendor/geist/geist-mono.css\" rel=\"stylesheet\">\n <script src=\"/static/common/js/theme.js?v=0.1.0\"></script>\n <link href=\"/static/common/css/common.css?v=0.3.7\" rel=\"stylesheet\">\n <link href=\"/static/public/css/video.css?v=0.4.22\" rel=\"stylesheet\">\n <link href=\"/static/common/css/toast.css\" rel=\"stylesheet\">\n</head>\n\n<body class=\"public-page min-h-screen flex flex-col\" style=\"background-color: var(--bg);\">\n <div id=\"toast-container\" class=\"toast-container\"></div>\n <div id=\"app-header\"></div>\n\n <main class=\"flex-1 container mx-auto max-w-6xl px-4 sm:px-6 py-5 sm:py-8 fade-in\">\n <div class=\"space-y-4 sm:space-y-6\">\n <div class=\"video-header-row\">\n <div>\n <h2 class=\"text-2xl font-semibold tracking-tight\">Video 视频工作台</h2>\n <p class=\"text-[var(--accents-4)] mt-1 text-sm\">生成短视频,支持参考图、多种预设风格与视频拼接。</p>\n </div>\n </div>\n\n <div class=\"h-px bg-[var(--border)]\"></div>\n\n <div class=\"video-main-grid\">\n <div class=\"video-main-left\">\n <div class=\"video-card\">\n <div class=\"video-workbench-shell\">\n <div class=\"video-workbench-left\">\n <div id=\"refDropZone\" class=\"video-workbench-panel\">\n <div class=\"panel-title\">当前画面</div>\n <div class=\"upload-row\">\n <button id=\"selectImageFileBtn\" class=\"geist-button-outline\" type=\"button\">添加参考图</button>\n <span id=\"imageFileName\" class=\"file-name\">已添加 0/7 张</span>\n </div>\n <input id=\"imageUrlInput\" class=\"geist-input hidden\" placeholder=\"https://... 或 data:image/...\">\n <input id=\"imageFileInput\" class=\"ref-file-input\" type=\"file\" accept=\"image/*\" multiple>\n <div id=\"referenceStrip\" class=\"reference-strip\">\n <div class=\"reference-empty\">可上传 / 粘贴 / 拖拽参考图(最多 7 张)</div>\n </div>\n <div class=\"parent-quick-row\">\n <label class=\"field-label\" for=\"parentPostInput\">粘贴 parentPostId(全局记忆)</label>\n <div class=\"parent-quick-input-wrap\">\n <input id=\"parentPostInput\" class=\"geist-input\" placeholder=\"粘贴 parentPostId 或包含它的 URL\">\n <button id=\"applyParentBtn\" class=\"geist-button-outline text-xs px-3\" type=\"button\">使用</button>\n </div>\n </div>\n <div id=\"referencePreview\" class=\"preview-shell\">\n <div id=\"currentGallery\" class=\"current-gallery hidden\"></div>\n <div id=\"previewEmpty\" class=\"preview-empty\">请先添加参考图</div>\n <div id=\"dropHint\" class=\"drop-hint\">支持拖拽图片到此处,或按 Ctrl+V 粘贴图片</div>\n </div>\n <div class=\"meta-grid ref-meta-grid\">\n <div class=\"meta-item\">\n <div class=\"meta-label\">当前 parentPostId</div>\n <div id=\"currentParentId\" class=\"meta-value\">-</div>\n </div>\n <div class=\"meta-item\">\n <div class=\"meta-label\">当前模式</div>\n <div id=\"currentMode\" class=\"meta-value\">upload</div>\n </div>\n </div>\n <div class=\"field video-prompt-field\">\n <label class=\"field-label\" for=\"promptInput\">视频提示词</label>\n <div class=\"video-prompt-wrap\">\n <div id=\"promptRichInput\" class=\"prompt-rich-input\" contenteditable=\"true\" data-placeholder=\"例如:@Image 1 站在中间,@Image 2 从左侧走入\"></div>\n <div id=\"referenceMentionMenu\" class=\"reference-mention-menu hidden\"></div>\n <textarea id=\"promptInput\" class=\"geist-input video-textarea prompt-textarea-proxy\" data-prompt-enhancer=\"off\" aria-hidden=\"true\" tabindex=\"-1\"\n placeholder=\"例如:@Image 1 站在中间,@Image 2 从左侧走入\"></textarea>\n </div>\n </div>\n </div>\n </div>\n </div>\n </div>\n <div id=\"editPanel\" class=\"video-card edit-panel\">\n <div class=\"video-preview-header\">\n <div class=\"card-title\">视频延长工作区</div>\n <div class=\"preview-actions\">\n <button id=\"pickCachedVideoBtn\" class=\"geist-button-outline text-xs px-3\">选择视频</button>\n <button id=\"uploadWorkVideoBtn\" class=\"geist-button-outline text-xs px-3\" type=\"button\">上传视频</button>\n <input id=\"workVideoFileInput\" class=\"ref-file-input\" type=\"file\" accept=\"video/*\">\n <button id=\"clearBtn\" class=\"geist-button-outline text-xs px-3\">清空</button>\n </div>\n </div>\n <div id=\"editHint\" class=\"video-empty\">请先选择或生成一个视频,工作区会始终展示当前视频。</div>\n <div id=\"editBody\">\n <div class=\"meta-grid edit-meta-grid\">\n <div class=\"meta-item\">\n <div class=\"meta-label\">当前工作视频</div>\n <div id=\"mergeVideoA\" class=\"meta-value\">-</div>\n </div>\n <div class=\"meta-item\">\n <div class=\"meta-label\">历史数量</div>\n <div id=\"historyCount\" class=\"meta-value\">0</div>\n </div>\n </div>\n <div class=\"edit-preview-wrap\">\n <video id=\"editVideo\" controls preload=\"metadata\" playsinline webkit-playsinline></video>\n </div>\n <div class=\"edit-slider-wrap\">\n <input id=\"editTimeline\" class=\"timeline-delete-zone\" data-delete-side=\"right\" type=\"range\" min=\"0\"\n max=\"100000\" step=\"1\" value=\"0\">\n <div class=\"progress-meta\">\n <span id=\"editTimeText\">00:00.000</span>\n <span id=\"editDurationText\">总时长 -</span>\n </div>\n </div>\n <div class=\"meta-grid edit-meta-grid\">\n <div class=\"meta-item\">\n <div class=\"meta-label\">frame_index</div>\n <div id=\"editFrameIndex\" class=\"meta-value\">-</div>\n </div>\n <div class=\"meta-item\">\n <div class=\"meta-label\">timestamp_ms</div>\n <div id=\"editTimestampMs\" class=\"meta-value\">-</div>\n </div>\n <div class=\"meta-item\">\n <div class=\"meta-label\">extend_post_id</div>\n <div id=\"editExtendPostId\" class=\"meta-value\">-</div>\n </div>\n </div>\n <div class=\"settings-block prompt-block edit-prompt-block\">\n <label class=\"field-label\" for=\"editPromptInput\">延长提示词</label>\n <textarea id=\"editPromptInput\" class=\"geist-input video-textarea\"\n placeholder=\"描述后续要延长的画面内容(留空则自然延续)\"></textarea>\n </div>\n <div class=\"preview-actions splice-action-row\">\n <button id=\"spliceBtn\" class=\"geist-button gap-2\">\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"\n stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M5 12h14\" />\n <path d=\"M12 5v14\" />\n </svg>\n <span>延长视频</span>\n </button>\n </div>\n </div>\n </div>\n </div>\n\n <aside class=\"video-main-right\">\n <div class=\"video-card\">\n <div class=\"status-header\">\n <div class=\"card-title\">运行状态</div>\n <span id=\"statusText\" class=\"status-text\">未连接</span>\n </div>\n <div class=\"progress-wrap\">\n <div id=\"progressBar\" class=\"progress-bar\">\n <div id=\"progressFill\" class=\"progress-fill\"></div>\n </div>\n <div class=\"progress-meta\">\n <span id=\"progressText\">0%</span>\n <span id=\"durationValue\">耗时 -</span>\n </div>\n </div>\n <div class=\"meta-grid\">\n <div class=\"meta-item\">\n <div class=\"meta-label\">比例</div>\n <div id=\"aspectValue\" class=\"meta-value\">-</div>\n </div>\n <div class=\"meta-item\">\n <div class=\"meta-label\">时长</div>\n <div id=\"lengthValue\" class=\"meta-value\">-</div>\n </div>\n <div class=\"meta-item\">\n <div class=\"meta-label\">分辨率</div>\n <div id=\"resolutionValue\" class=\"meta-value\">-</div>\n </div>\n <div class=\"meta-item\">\n <div class=\"meta-label\">预设</div>\n <div id=\"presetValue\" class=\"meta-value\">-</div>\n </div>\n <div class=\"meta-item\">\n <div class=\"meta-label\">并发</div>\n <div id=\"countValue\" class=\"meta-value\">1</div>\n </div>\n </div>\n </div>\n\n <div class=\"video-card video-settings-card\">\n <div class=\"card-title\">生成参数</div>\n <div class=\"video-settings-grid\">\n <div class=\"settings-block\">\n <label class=\"field-label\" for=\"ratioSelect\">画面比例</label>\n <select id=\"ratioSelect\" class=\"geist-input\">\n <option value=\"3:2\" selected>3:2 横构图</option>\n <option value=\"2:3\">2:3 竖构图</option>\n <option value=\"16:9\">16:9 宽屏</option>\n <option value=\"9:16\">9:16 竖屏</option>\n <option value=\"1:1\">1:1 方形</option>\n </select>\n </div>\n <div class=\"settings-block\">\n <label class=\"field-label\" for=\"lengthSelect\">视频时长</label>\n <select id=\"lengthSelect\" class=\"geist-input\">\n <option value=\"6\" selected>6 秒</option>\n <option value=\"10\">10 秒</option>\n <option value=\"15\">15 秒</option>\n </select>\n </div>\n <div class=\"settings-block\">\n <label class=\"field-label\" for=\"resolutionSelect\">分辨率</label>\n <select id=\"resolutionSelect\" class=\"geist-input\">\n <option value=\"480p\" selected>480p</option>\n <option value=\"720p\">720p</option>\n </select>\n </div>\n <div class=\"settings-block\">\n <label class=\"field-label\" for=\"presetSelect\">风格预设</label>\n <select id=\"presetSelect\" class=\"geist-input\">\n <option value=\"normal\" selected>Normal</option>\n <option value=\"fun\">Fun</option>\n <option value=\"spicy\">Spicy</option>\n <option value=\"custom\">Custom</option>\n </select>\n </div>\n <div class=\"settings-block\">\n <label class=\"field-label\" for=\"concurrentSelect\">并发视频数</label>\n <select id=\"concurrentSelect\" class=\"geist-input\">\n <option value=\"1\" selected>1 路</option>\n <option value=\"2\">2 路</option>\n <option value=\"3\">3 路</option>\n <option value=\"4\">4 路</option>\n </select>\n </div>\n <div class=\"settings-block\">\n <label class=\"field-label\">&nbsp;</label>\n <button id=\"clearImageFileBtn\" class=\"geist-button-outline video-side-btn\" type=\"button\">清除参考图</button>\n </div>\n </div>\n <div class=\"settings-actions settings-actions-side\">\n <button id=\"startBtn\" class=\"geist-button gap-2\">\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"\n stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <polygon points=\"5 3 19 12 5 21 5 3\" />\n </svg>\n 开始生成\n </button>\n <button id=\"stopBtn\" class=\"geist-button-outline gap-2 hidden\">\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"\n stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <rect x=\"5\" y=\"5\" width=\"14\" height=\"14\" />\n </svg>\n 停止\n </button>\n </div>\n </div>\n\n <aside class=\"video-card history-panel\">\n <div class=\"panel-head\">\n <div class=\"card-title\">历史视频</div>\n </div>\n <div id=\"videoEmpty\" class=\"video-empty\">生成视频与延长视频都会在这里保存,点击可替换到工作区。</div>\n <div id=\"videoStage\" class=\"video-stage hidden\"></div>\n </aside>\n </aside>\n </div>\n\n <div id=\"cacheVideoModal\" class=\"modal-overlay hidden\">\n <div class=\"modal-content modal-lg\">\n <div class=\"modal-header\">\n <h3 class=\"modal-title\">选择缓存视频</h3>\n <button id=\"closeCacheVideoModalBtn\" class=\"modal-close\">\n <svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\"></line>\n <line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\"></line>\n </svg>\n </button>\n </div>\n <div id=\"cacheVideoList\" class=\"cache-video-list\"></div>\n </div>\n </div>\n </div>\n </main>\n\n <div id=\"referenceLightbox\" class=\"reference-lightbox hidden\" aria-hidden=\"true\">\n <button id=\"closeReferenceLightboxBtn\" class=\"reference-lightbox-close\" type=\"button\" aria-label=\"关闭预览\">×</button>\n <div class=\"reference-lightbox-backdrop\"></div>\n <div class=\"reference-lightbox-dialog\">\n <img id=\"referenceLightboxImg\" class=\"reference-lightbox-image\" alt=\"参考图放大预览\">\n </div>\n </div>\n\n <div id=\"app-footer\"></div>\n\n <!-- 移动端底部悬浮操作栏 -->\n <div id=\"mobileActionBar\" class=\"mobile-action-bar\">\n <div class=\"mobile-action-bar-inner\">\n <!-- 左:齿轮按钮,点击弹出生成参数 -->\n <button id=\"mobileSettingsBtn\" class=\"mobile-settings-btn\" type=\"button\" aria-label=\"生成参数\">\n <svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"\n stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <circle cx=\"12\" cy=\"12\" r=\"3\"/>\n <path d=\"M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z\"/>\n </svg>\n </button>\n <!-- 右:操作区,根据上下文滑动切换 -->\n <div class=\"mobile-action-ops\">\n <!-- 生成模式(默认):开始/停止一体切换按钮 -->\n <div id=\"mobileBarGenerate\" class=\"mobile-action-slot mobile-action-slot--active\">\n <button id=\"mobileStartBtn\" class=\"geist-button mobile-action-btn gap-2\" type=\"button\">\n <svg class=\"mobile-gen-play\" width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"\n stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <polygon points=\"5 3 19 12 5 21 5 3\" />\n </svg>\n <svg class=\"mobile-gen-stop\" width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"\n stroke-linecap=\"round\" stroke-linejoin=\"round\" style=\"display:none\">\n <rect x=\"5\" y=\"5\" width=\"14\" height=\"14\" />\n </svg>\n <span class=\"mobile-gen-label\">开始生成</span>\n </button>\n </div>\n <!-- 延长模式(滚到延长区时) -->\n <div id=\"mobileBarSplice\" class=\"mobile-action-slot\">\n <button id=\"mobileSpliceBtn\" class=\"geist-button mobile-action-btn gap-2\" type=\"button\">\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"\n stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M5 12h14\" />\n <path d=\"M12 5v14\" />\n </svg>\n 延长视频\n </button>\n </div>\n </div>\n </div>\n </div>\n <!-- 生成参数 Sheet 遮罩 -->\n <div id=\"mobileSettingsOverlay\" class=\"mobile-settings-overlay\"></div>\n\n <script src=\"/static/common/js/admin-auth.js?v=0.3.0\"></script>\n <script src=\"/static/common/js/parentpost-memory.js?v=0.1.0\"></script>\n <script src=\"/static/common/js/public-header.js?v=0.3.3\"></script>\n <script src=\"/static/common/js/footer.js?v=0.3.0\"></script>\n <script src=\"/static/common/js/toast.js\"></script>\n <script src=\"/static/common/js/prompt-enhancer.js?v=0.1.15\"></script>\n <script src=\"/static/common/js/pwa.js?v=0.1.1\"></script>\n <script src=\"/static/public/js/video.js?v=0.6.4\"></script>\n</body>\n\n</html>\n",
"public/pages/chat.html": "<!DOCTYPE html>\n<html lang=\"zh-CN\">\n\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover, interactive-widget=overlays-content\">\n <title>Grok2API - Chat 聊天</title>\n <link rel=\"icon\" href=\"/static/common/img/favicon/favicon.ico\">\n <link rel=\"manifest\" href=\"/manifest.webmanifest\">\n <meta name=\"theme-color\" content=\"#fafafa\">\n <meta name=\"mobile-web-app-capable\" content=\"yes\">\n <meta name=\"apple-mobile-web-app-capable\" content=\"yes\">\n <meta name=\"apple-mobile-web-app-status-bar-style\" content=\"default\">\n <meta name=\"apple-mobile-web-app-title\" content=\"Grok2API\">\n <link rel=\"apple-touch-icon\" href=\"/static/public/pwa/apple-touch-icon.png\">\n <script src=\"https://cdn.tailwindcss.com\"></script>\n <script src=\"/static/common/js/theme.js?v=0.1.0\"></script>\n <link href=\"https://cdn.jsdelivr.net/npm/geist@1.0.0/dist/fonts/geist-sans/style.css\" rel=\"stylesheet\">\n <link href=\"https://cdn.jsdelivr.net/npm/geist@1.0.0/dist/fonts/geist-mono/style.css\" rel=\"stylesheet\">\n <link href=\"/static/common/css/common.css?v=1.5.0\" rel=\"stylesheet\">\n <link href=\"/static/public/css/chat.css?v=0.1.34\" rel=\"stylesheet\">\n <link href=\"/static/common/css/toast.css\" rel=\"stylesheet\">\n</head>\n\n<body class=\"min-h-screen flex flex-col\" style=\"background-color: var(--bg);\">\n <div id=\"toast-container\" class=\"toast-container\"></div>\n <div id=\"app-header\"></div>\n\n <main class=\"flex-1 container mx-auto max-w-5xl px-6 py-8 fade-in chat-main\">\n <div class=\"chat-layout\">\n <aside class=\"chat-sidebar\" id=\"chatSidebar\">\n <div class=\"sidebar-header\">\n <span class=\"sidebar-title\">会话</span>\n <div class=\"sidebar-actions\">\n <button id=\"newChatBtn\" class=\"sidebar-action-btn icon-btn\" type=\"button\" title=\"新增\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"\n stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <line x1=\"12\" y1=\"5\" x2=\"12\" y2=\"19\"></line>\n <line x1=\"5\" y1=\"12\" x2=\"19\" y2=\"12\"></line>\n </svg>\n </button>\n <button id=\"collapseSidebarBtn\" class=\"sidebar-action-btn icon-btn\" type=\"button\" title=\"折叠\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"\n stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <polyline points=\"15 6 9 12 15 18\"></polyline>\n </svg>\n </button>\n </div>\n </div>\n <div class=\"sidebar-list\" id=\"sessionList\"></div>\n </aside>\n <div class=\"sidebar-overlay\" id=\"sidebarOverlay\"></div>\n <button id=\"sidebarExpandBtn\" class=\"sidebar-expand-btn icon-btn\" type=\"button\" title=\"展开\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"\n stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <polyline points=\"9 6 15 12 9 18\"></polyline>\n </svg>\n </button>\n <div class=\"chat-shell\">\n <div class=\"chat-title\">\n <div style=\"display:flex;align-items:center;gap:10px;\">\n <button id=\"sidebarToggle\" class=\"sidebar-toggle\" type=\"button\" title=\"会话列表\">\n <svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <line x1=\"3\" y1=\"6\" x2=\"21\" y2=\"6\"></line>\n <line x1=\"3\" y1=\"12\" x2=\"21\" y2=\"12\"></line>\n <line x1=\"3\" y1=\"18\" x2=\"21\" y2=\"18\"></line>\n </svg>\n </button>\n <div>\n <h2 class=\"chat-title-text\">Chat 聊天</h2>\n <p class=\"chat-subtitle\">通过 chat 接口与 Grok 进行会话聊天</p>\n </div>\n </div>\n <span id=\"statusText\" class=\"status-text\">就绪</span>\n </div>\n\n <div id=\"chatLog\" class=\"chat-thread\">\n <div id=\"emptyState\" class=\"chat-empty\">输入一条消息开始对话。</div>\n </div>\n\n <div class=\"composer-shell\">\n <input id=\"fileInput\" class=\"file-input\" type=\"file\" multiple />\n <div class=\"composer-meta\">\n <div id=\"fileBadge\" class=\"file-badge hidden\"></div>\n <div id=\"modelPicker\" class=\"model-chip model-picker\">\n <button id=\"modelPickerBtn\" class=\"model-picker-btn\" type=\"button\" aria-haspopup=\"listbox\" aria-expanded=\"false\">\n <span id=\"modelPickerLabel\" class=\"model-picker-label\">加载中...</span>\n <svg class=\"model-picker-arrow\" width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"\n stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <polyline points=\"6 9 12 15 18 9\"></polyline>\n </svg>\n </button>\n <div id=\"modelPickerMenu\" class=\"model-picker-menu hidden\" role=\"listbox\"></div>\n <select id=\"modelSelect\" class=\"model-select-native\" aria-hidden=\"true\" tabindex=\"-1\"></select>\n </div>\n </div>\n <div class=\"composer-bottom\">\n <div class=\"composer-input\">\n <button id=\"attachBtn\" class=\"icon-btn\" type=\"button\" title=\"上传文件\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\"\n stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M21.44 11.05l-9.19 9.19a5 5 0 0 1-7.07-7.07l9.9-9.9a3.5 3.5 0 0 1 4.95 4.95l-9.9 9.9a2 2 0 0 1-2.83-2.83l8.49-8.49\"></path>\n </svg>\n </button>\n <textarea id=\"promptInput\" class=\"composer-textarea\" placeholder=\"询问任何内容\"></textarea>\n <div class=\"composer-actions\">\n <div class=\"popover hidden\">\n <button id=\"settingsToggle\" class=\"icon-btn\" type=\"button\" title=\"设置\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\"\n stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <circle cx=\"12\" cy=\"12\" r=\"3\"></circle>\n <path\n d=\"M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09a1.65 1.65 0 0 0-1-1.51 1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09a1.65 1.65 0 0 0 1.51-1 1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06a1.65 1.65 0 0 0 1.82.33h.01A1.65 1.65 0 0 0 9 4.6V4a2 2 0 0 1 4 0v.09c0 .69.4 1.31 1.02 1.58.52.23 1.12.2 1.6-.07l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06c-.45.45-.58 1.12-.33 1.72.25.6.82 1 1.51 1H21a2 2 0 0 1 0 4h-.09c-.69 0-1.31.4-1.51 1Z\">\n </path>\n </svg>\n </button>\n <div id=\"settingsPanel\" class=\"settings-panel hidden\">\n <div class=\"settings-grid\">\n <div class=\"settings-block\">\n <label class=\"field-label\" for=\"reasoningSelect\">推理强度</label>\n <select id=\"reasoningSelect\" class=\"geist-input\">\n <option value=\"\">跟随配置</option>\n <option value=\"none\">关闭</option>\n <option value=\"minimal\">Minimal</option>\n <option value=\"low\">Low</option>\n <option value=\"medium\">Medium</option>\n <option value=\"high\">High</option>\n <option value=\"xhigh\">XHigh</option>\n </select>\n </div>\n <div class=\"settings-block\">\n <label class=\"field-label\" for=\"tempRange\">温度</label>\n <div class=\"range-row\">\n <input id=\"tempRange\" type=\"range\" min=\"0\" max=\"2\" step=\"0.05\" value=\"0.8\">\n <span id=\"tempValue\" class=\"range-value\">0.8</span>\n </div>\n </div>\n <div class=\"settings-block\">\n <label class=\"field-label\" for=\"topPRange\">Top P</label>\n <div class=\"range-row\">\n <input id=\"topPRange\" type=\"range\" min=\"0\" max=\"1\" step=\"0.05\" value=\"0.95\">\n <span id=\"topPValue\" class=\"range-value\">0.95</span>\n </div>\n </div>\n <div class=\"settings-block settings-block-full\">\n <label class=\"field-label\" for=\"systemInput\">System Prompt</label>\n <textarea id=\"systemInput\" class=\"geist-input chat-input\" placeholder=\"可选,设置系统提示词\"></textarea>\n </div>\n </div>\n </div>\n </div>\n <button id=\"sendBtn\" class=\"send-btn\" type=\"button\" aria-label=\"Send\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"\n stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M22 2L11 13\"></path>\n <path d=\"M22 2L15 22L11 13L2 9L22 2Z\"></path>\n </svg>\n </button>\n </div>\n </div>\n </div>\n </div>\n </div>\n </div>\n </main>\n\n <div id=\"app-footer\"></div>\n\n <script src=\"/static/common/js/admin-auth.js?v=1.5.2\"></script>\n <script src=\"/static/common/js/public-header.js?v=1.5.2\"></script>\n <script src=\"/static/common/js/footer.js?v=1.5.2\"></script>\n <script src=\"/static/common/js/toast.js\"></script>\n <script src=\"/static/public/js/chat.js?v=1.5.2\"></script>\n</body>\n\n</html>\n\n",
"public/pages/nsfw.html": "<!DOCTYPE html>\n<html lang=\"zh-CN\">\n\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover, interactive-widget=overlays-content\">\n <title>Grok2API - NSFW 工作台</title>\n <link rel=\"icon\" href=\"/static/common/img/favicon/favicon.ico\">\n <link rel=\"manifest\" href=\"/manifest.webmanifest\">\n <meta name=\"theme-color\" content=\"#fafafa\">\n <meta name=\"mobile-web-app-capable\" content=\"yes\">\n <meta name=\"apple-mobile-web-app-capable\" content=\"yes\">\n <meta name=\"apple-mobile-web-app-status-bar-style\" content=\"default\">\n <meta name=\"apple-mobile-web-app-title\" content=\"Grok2API\">\n <link rel=\"apple-touch-icon\" href=\"/static/public/pwa/apple-touch-icon.png\">\n <script src=\"/static/vendor/tailwind/tailwindcss-cdn.js\"></script>\n <link href=\"/static/vendor/geist/geist-sans.css\" rel=\"stylesheet\">\n <link href=\"/static/vendor/geist/geist-mono.css\" rel=\"stylesheet\">\n <script src=\"/static/common/js/theme.js?v=0.1.0\"></script>\n <link href=\"/static/common/css/common.css?v=0.3.7\" rel=\"stylesheet\">\n <link href=\"/static/common/css/toast.css\" rel=\"stylesheet\">\n <link href=\"/static/public/css/nsfw.css?v=0.4.2\" rel=\"stylesheet\">\n</head>\n\n<body class=\"public-page min-h-screen flex flex-col\" style=\"background-color: var(--bg);\">\n <div id=\"toast-container\" class=\"toast-container\"></div>\n <div id=\"app-header\"></div>\n\n <main class=\"flex-1 container mx-auto max-w-6xl px-4 sm:px-6 py-5 sm:py-8 fade-in\">\n <div class=\"space-y-4 sm:space-y-6\">\n <div class=\"page-head\">\n <div>\n <h2 class=\"text-2xl font-semibold tracking-tight\">NSFW 工作台</h2>\n <p class=\"text-[var(--accents-4)] mt-1 text-sm\">先生成 6 张候选图,再选中 1 张按 parentPostId 并行生成视频。</p>\n </div>\n <div class=\"status-stack\">\n <div id=\"imageStatusText\" class=\"status-chip\">候选图:未开始</div>\n <div id=\"videoStatusText\" class=\"status-chip\">视频:未开始</div>\n </div>\n </div>\n\n <div class=\"h-px bg-[var(--border)]\"></div>\n\n <div class=\"top-grid\">\n <div class=\"panel\">\n <div class=\"panel-title\">生成参数</div>\n <div class=\"form-grid\">\n <div class=\"field field-wide\">\n <label class=\"field-label\" for=\"imagePromptInput\">图片提示词</label>\n <textarea id=\"imagePromptInput\" class=\"geist-input form-textarea\" placeholder=\"描述你要生成的 NSFW 候选图\"></textarea>\n </div>\n <div class=\"field field-wide\">\n <label class=\"field-label\" for=\"videoPromptInput\">视频提示词(可选)</label>\n <textarea id=\"videoPromptInput\" class=\"geist-input form-textarea\" placeholder=\"留空会按 spicy 模式处理;填写会走 custom 模式\"></textarea>\n </div>\n <div class=\"field\">\n <label class=\"field-label\" for=\"ratioSelect\">画面比例</label>\n <select id=\"ratioSelect\" class=\"geist-input\">\n <option value=\"16:9\" selected>16:9</option>\n <option value=\"9:16\">9:16</option>\n <option value=\"3:2\">3:2</option>\n <option value=\"2:3\">2:3</option>\n <option value=\"1:1\">1:1</option>\n </select>\n </div>\n <div class=\"field\">\n <label class=\"field-label\" for=\"videoParallelSelect\">并行视频数</label>\n <select id=\"videoParallelSelect\" class=\"geist-input\">\n <option value=\"1\">1</option>\n <option value=\"2\">2</option>\n <option value=\"3\">3</option>\n <option value=\"4\" selected>4</option>\n </select>\n </div>\n <div class=\"field\">\n <label class=\"field-label\" for=\"resolutionSelect\">分辨率</label>\n <select id=\"resolutionSelect\" class=\"geist-input\">\n <option value=\"480p\" selected>480p</option>\n <option value=\"720p\">720p</option>\n </select>\n </div>\n <div class=\"field\">\n <label class=\"field-label\" for=\"videoLengthSelect\">视频时长</label>\n <select id=\"videoLengthSelect\" class=\"geist-input\">\n <option value=\"6\" selected>6 秒</option>\n <option value=\"10\">10 秒</option>\n <option value=\"15\">15 秒</option>\n </select>\n </div>\n <div class=\"field\">\n <label class=\"field-label\" for=\"nsfwSelect\">NSFW</label>\n <select id=\"nsfwSelect\" class=\"geist-input\">\n <option value=\"true\" selected>开启</option>\n <option value=\"false\">关闭</option>\n </select>\n </div>\n </div>\n </div>\n\n <div class=\"panel\">\n <div class=\"panel-title\">操作</div>\n <div class=\"action-grid\">\n <button id=\"generateBatchBtn\" class=\"geist-button text-xs px-3\">生成 6 张</button>\n <button id=\"stopBatchBtn\" class=\"geist-button-outline text-xs px-3\">停止候选</button>\n <button id=\"nextBatchBtn\" class=\"geist-button-outline text-xs px-3\">再来一批</button>\n <button id=\"clearImagesBtn\" class=\"geist-button-outline text-xs px-3\">清空图片</button>\n <button id=\"startVideoBtn\" class=\"geist-button text-xs px-3\">选中图生视频</button>\n <button id=\"stopVideoBtn\" class=\"geist-button-outline text-xs px-3\">中断视频</button>\n <button id=\"clearVideosBtn\" class=\"geist-button-outline text-xs px-3 action-wide\">清空视频结果</button>\n </div>\n <div id=\"selectedMeta\" class=\"selected-meta\">未选择候选图</div>\n </div>\n </div>\n\n <div class=\"panel\">\n <div class=\"panel-head\">\n <div class=\"panel-title\">候选图瀑布流</div>\n <div id=\"imageCount\" class=\"head-meta\">0 张</div>\n </div>\n <div id=\"candidateEmpty\" class=\"empty-state\">点击“生成 6 张”开始</div>\n <div id=\"candidateWaterfall\" class=\"candidate-waterfall\"></div>\n </div>\n\n <div class=\"panel\">\n <div class=\"panel-head\">\n <div class=\"panel-title\">视频结果</div>\n <div id=\"videoCount\" class=\"head-meta\">0 个任务</div>\n </div>\n <div id=\"videoEmpty\" class=\"empty-state\">选中候选图后可并行生成 1~4 个视频</div>\n <div id=\"videoResults\" class=\"video-results\"></div>\n </div>\n </div>\n </main>\n\n <!-- 移动端设置遮罩(Bottom Sheet) -->\n <div id=\"nsfwSettingsOverlay\" class=\"nsfw-settings-overlay\"></div>\n\n <!-- 移动端双行 Sticky Bar -->\n <div id=\"nsfwStickyBar\" class=\"nsfw-sticky-bar\">\n <!-- 第一行:候选图操作:左⚙ + 右主按钮 -->\n <div class=\"nsfw-sticky-row nsfw-sticky-row--image\">\n <button class=\"nsfw-icon-btn\" id=\"nsfwSettingsBtn\" type=\"button\" title=\"生成参数\" aria-label=\"生成参数\">\n <svg width=\"17\" height=\"17\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"\n stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <circle cx=\"12\" cy=\"12\" r=\"3\"/>\n <path d=\"M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z\"/>\n </svg>\n </button>\n <!-- 右侧操作区:🗑清图 + 主按钮(与第二行 nsfw-video-actions 垂直对齐) -->\n <div class=\"nsfw-image-actions\">\n <button class=\"nsfw-icon-btn nsfw-icon-btn--danger\" id=\"imClearImagesBtn\" type=\"button\" title=\"清空候选图\">\n <svg width=\"15\" height=\"15\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"\n stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <polyline points=\"3 6 5 6 21 6\"/><path d=\"M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6\"/>\n <path d=\"M10 11v6M14 11v6\"/><path d=\"M9 6V4h6v2\"/>\n </svg>\n </button>\n <!-- 主按钮:生成6张 / 再来6张 / 停止候选 -->\n <button id=\"imGenerateBtn\" class=\"geist-button nsfw-main-btn\" type=\"button\">\n <svg class=\"nsfw-btn-play\" width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\"\n stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <polygon points=\"5 3 19 12 5 21 5 3\"/>\n </svg>\n <svg class=\"nsfw-btn-stop\" width=\"11\" height=\"11\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\"\n stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" style=\"display:none\">\n <rect x=\"5\" y=\"5\" width=\"14\" height=\"14\"/>\n </svg>\n <span class=\"nsfw-btn-label\">生成 6 张</span>\n </button>\n </div>\n </div>\n\n <!-- 行分隔线 -->\n <div class=\"nsfw-sticky-row-sep\"></div>\n\n <!-- 第二行:视频操作 -->\n <div class=\"nsfw-sticky-row nsfw-sticky-row--video\" id=\"nsfwVideoRow\">\n <!-- 左:已选图摘要 -->\n <div class=\"nsfw-selected-label\" id=\"nsfwSelectedLabel\">未选择候选图</div>\n <!-- 右:清空视频垃圾桶 + 生成视频主按钮 -->\n <div class=\"nsfw-video-actions\">\n <!-- 清空视频结果 垃圾桶 -->\n <button class=\"nsfw-icon-btn nsfw-icon-btn--danger\" id=\"imClearVideosBtn\" type=\"button\" title=\"清空视频结果\">\n <svg width=\"15\" height=\"15\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"\n stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <polyline points=\"3 6 5 6 21 6\"/><path d=\"M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6\"/>\n <path d=\"M10 11v6M14 11v6\"/><path d=\"M9 6V4h6v2\"/>\n </svg>\n </button>\n <!-- 生成视频/中断视频 主按钮 -->\n <button id=\"imStartVideoBtn\" class=\"geist-button nsfw-main-btn nsfw-main-btn--video\" type=\"button\" disabled>\n <svg class=\"nsfw-btn-play\" width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\"\n stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <polygon points=\"5 3 19 12 5 21 5 3\"/>\n </svg>\n <svg class=\"nsfw-btn-stop\" width=\"11\" height=\"11\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\"\n stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" style=\"display:none\">\n <rect x=\"5\" y=\"5\" width=\"14\" height=\"14\"/>\n </svg>\n <span class=\"nsfw-btn-label\">生成视频</span>\n </button>\n </div>\n </div>\n </div>\n\n\n <div id=\"lightbox\" class=\"image-lightbox\">\n <button class=\"lightbox-close\" id=\"closeLightbox\">\n <svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\"></line>\n <line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\"></line>\n </svg>\n </button>\n <button class=\"lightbox-nav lightbox-prev\" id=\"lightboxPrev\">\n <svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <polyline points=\"15 18 9 12 15 6\"></polyline>\n </svg>\n </button>\n <button class=\"lightbox-nav lightbox-next\" id=\"lightboxNext\">\n <svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <polyline points=\"9 18 15 12 9 6\"></polyline>\n </svg>\n </button>\n <img id=\"lightboxImg\" src=\"\" alt=\"\">\n <div class=\"lightbox-editor\">\n <div class=\"lightbox-edit-layout\">\n <div class=\"lightbox-edit-left\">\n <div class=\"lightbox-edit-main\">\n <textarea id=\"lightboxEditInput\" class=\"lightbox-edit-input\" placeholder=\"输入编辑提示词,例如:保持构图不变,把人物姿态改为站立\"></textarea>\n <button id=\"lightboxEditSend\" class=\"lightbox-edit-send\" type=\"button\" disabled>发送编辑</button>\n </div>\n <div id=\"lightboxEditProgressWrap\" class=\"lightbox-edit-progress-wrap\">\n <div id=\"lightboxEditProgressBar\" class=\"lightbox-edit-progress-bar\"></div>\n </div>\n <div id=\"lightboxEditProgressText\" class=\"lightbox-edit-progress-text\">编辑中 0%</div>\n </div>\n <aside class=\"lightbox-history-panel\">\n <div class=\"lightbox-history-head\">\n <div class=\"lightbox-history-title\">编辑历史</div>\n <div id=\"lightboxHistoryCount\" class=\"lightbox-history-count\">0 条</div>\n </div>\n <div id=\"lightboxHistoryEmpty\" class=\"lightbox-history-empty\">当前图片还没有编辑记录</div>\n <div id=\"lightboxHistoryList\" class=\"lightbox-history-list\"></div>\n </aside>\n </div>\n </div>\n </div>\n\n <div id=\"app-footer\"></div>\n\n <script src=\"/static/common/js/admin-auth.js?v=0.3.0\"></script>\n <script src=\"/static/common/js/parentpost-memory.js?v=0.1.0\"></script>\n <script src=\"/static/common/js/public-header.js?v=0.3.3\"></script>\n <script src=\"/static/common/js/footer.js?v=0.3.0\"></script>\n <script src=\"/static/common/js/toast.js\"></script>\n <script src=\"/static/common/js/prompt-enhancer.js?v=0.1.15\"></script>\n <script src=\"/static/common/js/pwa.js?v=0.1.1\"></script>\n <script src=\"/static/public/js/nsfw.js?v=0.3.7\"></script>\n</body>\n\n</html>\n",
"public/pages/imagine_workbench.html": "<!DOCTYPE html>\n<html lang=\"zh-CN\">\n\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover, interactive-widget=overlays-content\">\n <title>Grok2API - 图片编辑工作台</title>\n <link rel=\"icon\" href=\"/static/common/img/favicon/favicon.ico\">\n <link rel=\"manifest\" href=\"/manifest.webmanifest\">\n <meta name=\"theme-color\" content=\"#fafafa\">\n <meta name=\"mobile-web-app-capable\" content=\"yes\">\n <meta name=\"apple-mobile-web-app-capable\" content=\"yes\">\n <meta name=\"apple-mobile-web-app-status-bar-style\" content=\"default\">\n <meta name=\"apple-mobile-web-app-title\" content=\"Grok2API\">\n <link rel=\"apple-touch-icon\" href=\"/static/public/pwa/apple-touch-icon.png\">\n <script src=\"/static/vendor/tailwind/tailwindcss-cdn.js\"></script>\n <link href=\"/static/vendor/geist/geist-sans.css\" rel=\"stylesheet\">\n <link href=\"/static/vendor/geist/geist-mono.css\" rel=\"stylesheet\">\n <script src=\"/static/common/js/theme.js?v=0.1.0\"></script>\n <link href=\"/static/common/css/common.css?v=0.3.7\" rel=\"stylesheet\">\n <link href=\"/static/common/css/toast.css\" rel=\"stylesheet\">\n <link href=\"/static/public/css/imagine_workbench.css?v=0.2.4\" rel=\"stylesheet\">\n</head>\n\n<body class=\"public-page min-h-screen flex flex-col\" style=\"background-color: var(--bg);\">\n <div id=\"toast-container\" class=\"toast-container\"></div>\n <div id=\"app-header\"></div>\n\n <main class=\"flex-1 container mx-auto max-w-6xl px-4 sm:px-6 py-5 sm:py-8 fade-in\">\n <div class=\"space-y-4 sm:space-y-6\">\n <div class=\"page-head\">\n <div>\n <h2 class=\"text-2xl font-semibold tracking-tight\">图片编辑工作台</h2>\n <p class=\"text-[var(--accents-4)] mt-1 text-sm\">\n 支持上传/粘贴/拖拽最多 5 张参考图进行编辑;编辑后沿用上一次输出的 parentPostId 继续链路。\n </p>\n </div>\n <div id=\"editStatusText\" class=\"status-chip\">未开始</div>\n </div>\n\n <div class=\"h-px bg-[var(--border)]\"></div>\n\n <div class=\"workbench-grid\">\n <section class=\"panel\">\n <div class=\"panel-title\">当前画面</div>\n\n <div class=\"upload-row\">\n <input id=\"seedImageInput\" class=\"hidden\" type=\"file\" accept=\"image/*\" multiple>\n <button id=\"selectSeedBtn\" type=\"button\" class=\"geist-button-outline\">添加参考图</button>\n <span id=\"seedFileName\" class=\"file-name\">已添加 0/5 张</span>\n </div>\n\n <div id=\"referenceStrip\" class=\"reference-strip\">\n <div class=\"reference-empty\">可上传 / 粘贴 / 拖拽参考图(最多 5 张)</div>\n </div>\n\n <div class=\"parent-quick-row\">\n <label class=\"field-label\" for=\"parentPostInput\">粘贴 parentPostId(全局记忆)</label>\n <div class=\"parent-quick-input-wrap\">\n <input id=\"parentPostInput\" class=\"geist-input\" placeholder=\"粘贴 parentPostId 或包含它的 URL\">\n <button id=\"applyParentBtn\" type=\"button\" class=\"geist-button-outline\">使用</button>\n </div>\n </div>\n\n <div id=\"previewShell\" class=\"preview-shell\">\n <div id=\"currentGallery\" class=\"current-gallery hidden\"></div>\n <div id=\"previewEmpty\" class=\"preview-empty\">请先添加参考图</div>\n <div id=\"dropHint\" class=\"drop-hint\">支持拖拽图片到此处,或按 Ctrl+V 粘贴图片</div>\n </div>\n\n <div class=\"meta-grid\">\n <div class=\"meta-item\">\n <div class=\"meta-label\">当前 parentPostId</div>\n <div id=\"currentParentId\" class=\"meta-value\">-</div>\n </div>\n <div class=\"meta-item\">\n <div class=\"meta-label\">当前模式</div>\n <div id=\"currentMode\" class=\"meta-value\">upload</div>\n </div>\n </div>\n\n <div class=\"field\">\n <label class=\"field-label\" for=\"editPromptInput\">编辑提示词</label>\n <div class=\"prompt-editor-shell\">\n <div id=\"editPromptRichInput\" class=\"prompt-rich-input\" contenteditable=\"true\" data-placeholder=\"例如:@Image 1 在左侧,@Image 2 在右侧,两人合照\"></div>\n <div id=\"editPromptMentionMenu\" class=\"prompt-mention-menu hidden\"></div>\n <textarea id=\"editPromptInput\" class=\"geist-input form-textarea prompt-textarea-proxy\" data-prompt-enhancer=\"off\" aria-hidden=\"true\" tabindex=\"-1\" placeholder=\"例如:@Image 1 在左侧,@Image 2 在右侧,两人合照\"></textarea>\n </div>\n </div>\n\n <div class=\"action-row\">\n <button id=\"submitEditBtn\" type=\"button\" class=\"geist-button\">执行编辑</button>\n <button id=\"resetCycleBtn\" type=\"button\" class=\"geist-button-outline\">重置链路</button>\n <button id=\"clearHistoryBtn\" type=\"button\" class=\"geist-button-outline\">清空历史</button>\n </div>\n\n <div id=\"editProgressWrap\" class=\"edit-progress-wrap\">\n <div id=\"editProgressBar\" class=\"edit-progress-bar\"></div>\n </div>\n <div id=\"editProgressText\" class=\"edit-progress-text\">编辑中 0%</div>\n </section>\n\n <aside class=\"panel\">\n <div class=\"panel-head\">\n <div class=\"panel-title\">编辑历史</div>\n <div id=\"historyCount\" class=\"head-meta\">0 条</div>\n </div>\n <div id=\"historyEmpty\" class=\"empty-state\">执行编辑后会在这里记录历史版本</div>\n <div id=\"historyList\" class=\"history-list\"></div>\n </aside>\n </div>\n </div>\n </main>\n\n <!-- 移动端底部 Sticky 操作栏 -->\n <div id=\"wbStickyBar\" class=\"wb-sticky-bar\">\n <div class=\"wb-sticky-bar-inner\">\n <!-- 左侧:图标按钮区 -->\n <div class=\"wb-icon-group\">\n <!-- 添加参考图 -->\n <button class=\"wb-icon-btn\" id=\"wbAddRefBtn\" type=\"button\" title=\"添加参考图\" aria-label=\"添加参考图\">\n <svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"\n stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <rect x=\"3\" y=\"3\" width=\"18\" height=\"18\" rx=\"2\" ry=\"2\"/>\n <circle cx=\"8.5\" cy=\"8.5\" r=\"1.5\"/>\n <polyline points=\"21 15 16 10 5 21\"/>\n </svg>\n </button>\n <!-- 使用 ID -->\n <button class=\"wb-icon-btn\" id=\"wbUseIdBtn\" type=\"button\" title=\"粘贴并使用 parentPostId\" aria-label=\"使用ID\">\n <svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"\n stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71\"/>\n <path d=\"M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71\"/>\n </svg>\n </button>\n <!-- 重置链路 -->\n <button class=\"wb-icon-btn\" id=\"wbResetBtn\" type=\"button\" title=\"重置链路\" aria-label=\"重置链路\">\n <svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"\n stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <polyline points=\"1 4 1 10 7 10\"/>\n <path d=\"M3.51 15a9 9 0 1 0 .49-3.67\"/>\n </svg>\n </button>\n <!-- 清空历史 -->\n <button class=\"wb-icon-btn wb-icon-btn--danger\" id=\"wbClearHistoryBtn\" type=\"button\" title=\"清空历史\" aria-label=\"清空历史\">\n <svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"\n stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <polyline points=\"3 6 5 6 21 6\"/>\n <path d=\"M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6\"/>\n <path d=\"M10 11v6M14 11v6\"/>\n <path d=\"M9 6V4h6v2\"/>\n </svg>\n </button>\n </div>\n <!-- 右侧:主操作按钮 -->\n <button id=\"wbSubmitBtn\" class=\"geist-button wb-submit-btn\" type=\"button\">\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"\n stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <polygon points=\"5 3 19 12 5 21 5 3\"/>\n </svg>\n 执行编辑\n </button>\n </div>\n </div>\n\n <div id=\"app-footer\"></div>\n\n <script src=\"/static/common/js/admin-auth.js?v=0.3.0\"></script>\n <script src=\"/static/common/js/parentpost-memory.js?v=0.1.0\"></script>\n <script src=\"/static/common/js/public-header.js?v=0.3.3\"></script>\n <script src=\"/static/common/js/footer.js?v=0.3.0\"></script>\n <script src=\"/static/common/js/toast.js\"></script>\n <script src=\"/static/common/js/prompt-enhancer.js?v=0.1.15\"></script>\n <script src=\"/static/common/js/pwa.js?v=0.1.1\"></script>\n <script src=\"/static/public/js/imagine_workbench.js?v=0.2.1\"></script>\n</body>\n\n</html>\n",
"public/manifest.webmanifest": "{\n \"name\": \"Grok2API\",\n \"short_name\": \"Grok2API\",\n \"description\": \"Grok2API 功能工作台\",\n \"start_url\": \"/login\",\n \"scope\": \"/\",\n \"display\": \"standalone\",\n \"background_color\": \"#fafafa\",\n \"theme_color\": \"#fafafa\",\n \"icons\": [\n {\n \"src\": \"/static/public/pwa/icon-192.png\",\n \"sizes\": \"192x192\",\n \"type\": \"image/png\",\n \"purpose\": \"any\"\n },\n {\n \"src\": \"/static/public/pwa/icon-512.png\",\n \"sizes\": \"512x512\",\n \"type\": \"image/png\",\n \"purpose\": \"any maskable\"\n }\n ]\n}\n",
"public/sw.js": "const CACHE_PREFIX = \"grok2api-pwa-\";\nconst CACHE_NAME = `${CACHE_PREFIX}v2`;\n\nself.addEventListener(\"install\", (event) => {\n event.waitUntil(self.skipWaiting());\n});\n\nself.addEventListener(\"activate\", (event) => {\n event.waitUntil(\n (async () => {\n const keys = await caches.keys();\n await Promise.all(\n keys\n .filter((key) => key.startsWith(CACHE_PREFIX) && key !== CACHE_NAME)\n .map((key) => caches.delete(key))\n );\n await self.clients.claim();\n })()\n );\n});\n\nself.addEventListener(\"fetch\", (event) => {\n const request = event.request;\n if (request.method !== \"GET\") return;\n\n const url = new URL(request.url);\n if (url.origin !== self.location.origin) return;\n\n const cacheablePathPrefixes = [\n \"/static/\",\n \"/login\",\n \"/imagine\",\n \"/video\",\n \"/voice\",\n \"/nsfw\",\n \"/imagine-workbench\",\n \"/manifest.webmanifest\"\n ];\n const isCacheable = cacheablePathPrefixes.some((prefix) => url.pathname.startsWith(prefix));\n if (!isCacheable) return;\n\n event.respondWith(\n (async () => {\n try {\n const response = await fetch(request);\n const cache = await caches.open(CACHE_NAME);\n cache.put(request, response.clone());\n return response;\n } catch (error) {\n const cached = await caches.match(request);\n if (cached) return cached;\n throw error;\n }\n })()\n );\n});\n",
"admin/pages/login.html": "<!DOCTYPE html>\n<html lang=\"zh-CN\">\n\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover, interactive-widget=overlays-content\">\n <title>Grok2API - 登录</title>\n <link rel=\"icon\" href=\"/static/common/img/favicon/favicon.ico\">\n <script src=\"/static/vendor/tailwind/tailwindcss-cdn.js\"></script>\n <link href=\"/static/vendor/geist/geist-sans.css\" rel=\"stylesheet\">\n <link href=\"/static/vendor/geist/geist-mono.css\" rel=\"stylesheet\">\n <script>\n tailwind.config = {\n theme: {\n extend: {\n fontFamily: { sans: ['Geist Sans', 'sans-serif'] },\n colors: {\n dark: '#000000',\n light: '#ffffff',\n subtle: '#666666',\n border: '#e5e5e5'\n }\n }\n }\n }\n </script>\n <script src=\"/static/common/js/theme.js?v=0.1.0\"></script>\n <link href=\"/static/common/css/common.css?v=0.3.7\" rel=\"stylesheet\">\n <link href=\"/static/common/css/login.css?v=0.3.0\" rel=\"stylesheet\">\n <link href=\"/static/common/css/toast.css\" rel=\"stylesheet\">\n</head>\n\n<body class=\"login-body\">\n <div class=\"login-bg\" aria-hidden=\"true\"></div>\n\n <div class=\"login-shell\">\n <div class=\"login-card\">\n <div class=\"login-brand\">\n <a href=\"https://github.com/chenyme/grok2api\" target=\"_blank\" rel=\"noopener\" class=\"login-brand-link\">\n <svg class=\"login-github-icon\" viewBox=\"0 0 24 24\" aria-hidden=\"true\">\n <path\n d=\"M12 2C6.48 2 2 6.58 2 12.26c0 4.5 2.87 8.32 6.84 9.67.5.1.68-.22.68-.48 0-.24-.01-.86-.01-1.69-2.78.62-3.37-1.37-3.37-1.37-.45-1.17-1.11-1.48-1.11-1.48-.91-.64.07-.63.07-.63 1.01.07 1.54 1.06 1.54 1.06.9 1.58 2.36 1.12 2.94.86.09-.67.35-1.12.63-1.38-2.22-.26-4.56-1.14-4.56-5.07 0-1.12.39-2.04 1.03-2.76-.1-.26-.45-1.3.1-2.71 0 0 .84-.27 2.75 1.05A9.3 9.3 0 0 1 12 6.79c.85 0 1.7.12 2.5.36 1.9-1.32 2.74-1.05 2.74-1.05.55 1.41.2 2.45.1 2.71.64.72 1.03 1.64 1.03 2.76 0 3.94-2.34 4.81-4.58 5.06.36.32.68.95.68 1.92 0 1.38-.01 2.49-.01 2.83 0 .26.18.58.69.48A10.05 10.05 0 0 0 22 12.26C22 6.58 17.52 2 12 2Z\" />\n </svg>\n <span>Grok2API</span>\n </a>\n </div>\n <div class=\"login-title\">管理后台</div>\n <div class=\"login-subtitle\">请输入后台密码以继续</div>\n\n <div class=\"login-form\">\n <input type=\"password\" id=\"api-key-input\" placeholder=\"后台密码 (app_key)\" class=\"geist-input login-input\">\n <button onclick=\"login()\" class=\"geist-button login-button\">继续</button>\n </div>\n </div>\n </div>\n\n <div id=\"app-footer\"></div>\n\n <script src=\"/static/common/js/admin-auth.js?v=0.3.0\"></script>\n <script src=\"/static/common/js/toast.js\"></script>\n <script src=\"/static/common/js/footer.js?v=0.3.0\"></script>\n <script src=\"/static/admin/js/login.js?v=0.3.0\"></script>\n</body>\n\n</html>\n",
"admin/pages/config.html": "<!DOCTYPE html>\n<html lang=\"zh-CN\">\n\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover, interactive-widget=overlays-content\">\n <title>Grok2API - 配置管理</title>\n <link rel=\"icon\" href=\"/static/common/img/favicon/favicon.ico\">\n <script src=\"/static/vendor/tailwind/tailwindcss-cdn.js\"></script>\n <link href=\"/static/vendor/geist/geist-sans.css\" rel=\"stylesheet\">\n <link href=\"/static/vendor/geist/geist-mono.css\" rel=\"stylesheet\">\n <script src=\"/static/common/js/theme.js?v=0.1.0\"></script>\n <link href=\"/static/common/css/common.css?v=0.3.7\" rel=\"stylesheet\">\n <link href=\"/static/admin/css/config.css?v=0.3.0\" rel=\"stylesheet\">\n <link href=\"/static/common/css/toast.css\" rel=\"stylesheet\">\n</head>\n\n<body class=\"min-h-screen flex flex-col\" style=\"background-color: var(--bg);\">\n\n <div id=\"app-header\"></div>\n\n <main class=\"space-y-6 flex-1 container mx-auto max-w-5xl px-4 sm:px-6 py-6 sm:py-8 fade-in\">\n <div class=\"space-y-6\">\n <div class=\"flex flex-col gap-3 md:flex-row md:items-center md:justify-between\">\n <div>\n <h2 class=\"text-2xl font-semibold tracking-tight\">配置管理</h2>\n <p class=\"text-[var(--accents-4)] mt-1 text-sm\">管理 API 密钥及系统参数设置。</p>\n </div>\n <button onclick=\"saveConfig()\" id=\"save-btn\" class=\"geist-button gap-2 w-full md:w-auto justify-center\">\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"\n stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z\" />\n <polyline points=\"17 21 17 13 7 13 7 21\" />\n <polyline points=\"7 3 7 8 15 8\" />\n </svg>\n 保存\n </button>\n </div>\n\n <div class=\"h-px bg-[var(--border)] my-6\"></div>\n\n <div id=\"config-container\" class=\"space-y-8\">\n <div class=\"text-center py-12 text-[var(--accents-4)]\">加载中...</div>\n </div>\n\n </div>\n </main>\n <div id=\"app-footer\"></div>\n\n <script src=\"/static/common/js/admin-auth.js?v=0.3.0\"></script>\n <script src=\"/static/common/js/header.js?v=0.3.1\"></script>\n <script src=\"/static/common/js/footer.js?v=0.3.0\"></script>\n <script src=\"/static/common/js/toast.js\"></script>\n <script src=\"/static/admin/js/config.js?v=0.3.0\"></script>\n</body>\n\n</html>\n",
"admin/pages/cache.html": "<!DOCTYPE html>\n<html lang=\"zh-CN\">\n\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover, interactive-widget=overlays-content\">\n <title>Grok2API - 缓存管理</title>\n <link rel=\"icon\" href=\"/static/common/img/favicon/favicon.ico\">\n <script src=\"/static/vendor/tailwind/tailwindcss-cdn.js\"></script>\n <link href=\"/static/vendor/geist/geist-sans.css\" rel=\"stylesheet\">\n <link href=\"/static/vendor/geist/geist-mono.css\" rel=\"stylesheet\">\n <script src=\"/static/common/js/theme.js?v=0.1.0\"></script>\n <link href=\"/static/common/css/common.css?v=0.3.7\" rel=\"stylesheet\">\n <link href=\"/static/admin/css/cache.css?v=0.3.0\" rel=\"stylesheet\">\n <link href=\"/static/common/css/toast.css\" rel=\"stylesheet\">\n</head>\n\n<body class=\"min-h-screen flex flex-col\" style=\"background-color: var(--bg);\">\n <div id=\"app-header\"></div>\n\n <main class=\"space-y-6 flex-1 container mx-auto max-w-5xl px-4 sm:px-6 py-6 sm:py-8 fade-in\">\n <div class=\"space-y-6\">\n <div class=\"flex flex-col gap-3 md:flex-row md:items-center md:justify-between\">\n <div>\n <h2 class=\"text-2xl font-semibold tracking-tight\">缓存管理</h2>\n <p class=\"text-[var(--accents-4)] mt-1 text-sm\">管理本地资源与在线资产缓存。</p>\n </div>\n </div>\n\n <div class=\"h-px bg-[var(--border)] my-6\"></div>\n\n <div class=\"grid grid-cols-1 md:grid-cols-3 gap-4\">\n <div class=\"stat-card cache-card\" data-type=\"image\">\n <div class=\"flex items-start justify-between\">\n <div>\n <div class=\"cache-stat-label\">本地图片</div>\n <div class=\"cache-stat-value\">\n <span id=\"img-count\">0</span>\n <span class=\"text-xs text-[var(--accents-4)] ml-1\">个文件</span>\n </div>\n </div>\n <div class=\"text-right\">\n <div id=\"img-size\" class=\"text-xs font-mono text-[var(--accents-4)]\">0 MB</div>\n <button onclick=\"clearCache('image')\" class=\"cache-action-btn mt-3\" title=\"清空缓存\">\n <svg class=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"\n d=\"M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16\" />\n </svg>\n </button>\n </div>\n </div>\n </div>\n\n <div class=\"stat-card cache-card\" data-type=\"video\">\n <div class=\"flex items-start justify-between\">\n <div>\n <div class=\"cache-stat-label\">本地视频</div>\n <div class=\"cache-stat-value\">\n <span id=\"video-count\">0</span>\n <span class=\"text-xs text-[var(--accents-4)] ml-1\">个文件</span>\n </div>\n </div>\n <div class=\"text-right\">\n <div id=\"video-size\" class=\"text-xs font-mono text-[var(--accents-4)]\">0 MB</div>\n <button onclick=\"clearCache('video')\" class=\"cache-action-btn mt-3\" title=\"清空缓存\">\n <svg class=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"\n d=\"M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16\" />\n </svg>\n </button>\n </div>\n </div>\n </div>\n\n <div class=\"stat-card cache-card\" data-type=\"online\">\n <div class=\"flex items-start justify-between\">\n <div>\n <div class=\"cache-stat-label\">在线资产</div>\n <div class=\"cache-stat-value\">\n <span id=\"online-count\">0</span>\n <span class=\"text-xs text-[var(--accents-4)] ml-1\">个文件</span>\n </div>\n <div id=\"online-status\" class=\"text-xs text-green-600 mt-1\">连接正常</div>\n <div id=\"online-last-clear\" class=\"text-xs text-[var(--accents-4)] mt-1\"></div>\n </div>\n </div>\n </div>\n </div>\n\n <div id=\"local-cache-lists\" class=\"space-y-4 hidden\">\n <div id=\"local-image-list\" class=\"cache-list hidden\">\n <div class=\"rounded-lg overflow-hidden bg-white overflow-x-auto\">\n <table class=\"geist-table min-w-[600px]\">\n <thead>\n <tr>\n <th class=\"w-10 text-center\">\n <input id=\"local-image-select-all\" type=\"checkbox\" class=\"checkbox\" onchange=\"toggleLocalSelectAll('image', this)\">\n </th>\n <th class=\"w-108 text-left\">文件</th>\n <th class=\"w-32 text-left\">大小</th>\n <th class=\"w-48 text-left\">时间</th>\n <th class=\"w-24 text-center\">操作</th>\n </tr>\n </thead>\n <tbody id=\"local-image-body\"></tbody>\n </table>\n </div>\n </div>\n <div id=\"local-video-list\" class=\"cache-list hidden\">\n <div class=\"rounded-lg overflow-hidden bg-white overflow-x-auto\">\n <table class=\"geist-table min-w-[600px]\">\n <thead>\n <tr>\n <th class=\"w-10 text-center\">\n <input id=\"local-video-select-all\" type=\"checkbox\" class=\"checkbox\" onchange=\"toggleLocalSelectAll('video', this)\">\n </th>\n <th class=\"w-108 text-left\">文件</th>\n <th class=\"w-32 text-left\">大小</th>\n <th class=\"w-48 text-left\">时间</th>\n <th class=\"w-24 text-center\">操作</th>\n </tr>\n </thead>\n <tbody id=\"local-video-body\"></tbody>\n </table>\n </div>\n </div>\n </div>\n\n <div id=\"online-assets-table\" class=\"rounded-lg overflow-hidden bg-white mb-4 overflow-x-auto\">\n <table class=\"geist-table min-w-[800px]\">\n <thead>\n <tr>\n <th class=\"w-10 text-center\">\n <input id=\"select-all\" type=\"checkbox\" class=\"checkbox\" onchange=\"toggleSelectAll(this)\">\n </th>\n <th class=\"w-48 text-left\">Token</th>\n <th class=\"w-32 text-center\">类型</th>\n <th class=\"w-32 text-center\">资产数</th>\n <th class=\"w-42 text-left\">上次清空时间</th>\n <th class=\"w-24 text-center\">操作</th>\n </tr>\n </thead>\n <tbody id=\"account-table-body\"></tbody>\n </table>\n <div id=\"account-empty\" class=\"hidden table-empty\">暂无可用账号</div>\n </div>\n\n </div>\n </div>\n </main>\n <div id=\"app-footer\"></div>\n\n <div id=\"batch-actions\"\n class=\"fixed bottom-8 left-1/2 -translate-x-1/2 z-20 bg-white border border-[var(--border)] rounded-full px-3 py-2 flex items-center shadow-lg gap-3 cursor-move select-none active:cursor-grabbing whitespace-nowrap\">\n <div class=\"text-sm font-medium flex items-center gap-2\">\n <span class=\"text-[var(--accents-5)] text-xs\">已选择</span>\n <span class=\"bg-black text-white text-xs px-1.5 py-0.5 rounded-full\" id=\"selected-count\">0</span>\n <span class=\"text-[var(--accents-5)] text-xs\">项</span>\n </div>\n <span class=\"toolbar-sep\"></span>\n <div class=\"batch-action-buttons flex items-center gap-1\">\n <button id=\"btn-load-stats\" onclick=\"handleLoadClick()\"\n class=\"geist-button-outline text-xs px-3 gap-1 border-0 hover:bg-gray-100\">\n 加载\n </button>\n <button id=\"btn-delete-assets\" onclick=\"handleDeleteClick()\" class=\"geist-button-danger text-xs px-3\">\n 清理\n </button>\n </div>\n <div id=\"batch-progress\" class=\"hidden text-xs text-[var(--accents-5)] flex items-center gap-2\">\n <span class=\"toolbar-sep\"></span>\n <span id=\"batch-progress-text\"></span>\n <button id=\"btn-pause-action\" type=\"button\" class=\"batch-link hidden\">暂停</button>\n <button id=\"btn-stop-action\" type=\"button\" class=\"batch-link hidden\">终止</button>\n </div>\n </div>\n\n <dialog id=\"confirm-dialog\" class=\"confirm-dialog\">\n <div class=\"confirm-dialog-body\">\n <div class=\"confirm-dialog-title\">请确认</div>\n <div id=\"confirm-message\" class=\"confirm-dialog-message\"></div>\n <div class=\"confirm-dialog-actions\">\n <button id=\"confirm-cancel\" type=\"button\" class=\"geist-button-outline text-xs px-3\">取消</button>\n <button id=\"confirm-ok\" type=\"button\" class=\"geist-button-danger text-xs px-3\">确定</button>\n </div>\n </div>\n </dialog>\n\n <dialog id=\"failure-dialog\" class=\"confirm-dialog\">\n <div class=\"confirm-dialog-body\">\n <div class=\"confirm-dialog-title\">失败详情</div>\n <div id=\"failure-list\" class=\"failure-list\"></div>\n <div class=\"confirm-dialog-actions\">\n <button id=\"failure-close\" type=\"button\" class=\"geist-button-outline text-xs px-3\">关闭</button>\n <button id=\"failure-retry\" type=\"button\" class=\"geist-button text-xs px-3\">重试失败项</button>\n </div>\n </div>\n </dialog>\n\n <script src=\"/static/common/js/admin-auth.js?v=0.3.0\"></script>\n <script src=\"/static/common/js/batch-sse.js?v=0.3.0\"></script>\n <script src=\"/static/common/js/header.js?v=0.3.1\"></script>\n <script src=\"/static/common/js/footer.js?v=0.3.0\"></script>\n <script src=\"/static/common/js/toast.js\"></script>\n <script src=\"/static/admin/js/cache.js?v=0.3.0\"></script>\n <script src=\"/static/common/js/draggable.js\"></script>\n</body>\n\n</html>\n",
"admin/pages/token.html": "<!DOCTYPE html>\n<html lang=\"zh-CN\">\n\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover, interactive-widget=overlays-content\">\n <title>Grok2API - Token 管理</title>\n <link rel=\"icon\" href=\"/static/common/img/favicon/favicon.ico\">\n <script src=\"/static/vendor/tailwind/tailwindcss-cdn.js\"></script>\n <link href=\"/static/vendor/geist/geist-sans.css\" rel=\"stylesheet\">\n <link href=\"/static/vendor/geist/geist-mono.css\" rel=\"stylesheet\">\n <script src=\"/static/common/js/theme.js?v=0.1.0\"></script>\n <link href=\"/static/common/css/common.css?v=0.3.7\" rel=\"stylesheet\">\n <link href=\"/static/admin/css/token.css?v=0.3.0\" rel=\"stylesheet\">\n <link href=\"/static/common/css/toast.css\" rel=\"stylesheet\">\n</head>\n\n<body class=\"min-h-screen flex flex-col\" style=\"background-color: var(--bg);\">\n\n <!-- Toast Container -->\n <div id=\"toast-container\" class=\"toast-container\"></div>\n <div id=\"app-header\"></div>\n\n <!-- Main Content -->\n <main class=\"space-y-6 flex-1 container mx-auto max-w-5xl px-4 sm:px-6 py-6 sm:py-8 fade-in\">\n\n <div class=\"space-y-6\">\n <div class=\"flex flex-col gap-3 md:flex-row md:items-center md:justify-between\">\n <div>\n <h2 class=\"text-2xl font-semibold tracking-tight\">Token 列表</h2>\n <p class=\"text-[var(--accents-4)] mt-1 text-sm\">管理 Grok2API 的 Token 服务号池。</p>\n </div>\n <div class=\"flex w-full flex-wrap items-center gap-2 md:w-auto md:gap-3\">\n <button onclick=\"openImportModal()\" class=\"geist-button-outline gap-2\">\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"\n stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4\"></path>\n <polyline points=\"17 8 12 3 7 8\"></polyline>\n <line x1=\"12\" y1=\"3\" x2=\"12\" y2=\"15\"></line>\n </svg>\n 导入\n </button>\n <button onclick=\"addToken()\" class=\"geist-button gap-2\">\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <path d=\"M12 5v14M5 12h14\" />\n </svg>\n 添加\n </button>\n </div>\n </div>\n\n <div class=\"h-px bg-[var(--border)] my-6\"></div>\n\n <!-- Statistics -->\n <div class=\"grid grid-cols-2 md:grid-cols-4 gap-3 sm:gap-4 mb-6\" id=\"stats-container\">\n <!-- Row 1 -->\n <div class=\"stat-card\">\n <div class=\"stat-value\" id=\"stat-total\">-</div>\n <div class=\"stat-label\">Token 总数</div>\n </div>\n <div class=\"stat-card\">\n <div class=\"stat-value text-green-600\" id=\"stat-active\">-</div>\n <div class=\"stat-label\">Token 正常</div>\n </div>\n <div class=\"stat-card\">\n <div class=\"stat-value text-orange-500\" id=\"stat-cooling\">-</div>\n <div class=\"stat-label\">Token 限流</div>\n </div>\n <div class=\"stat-card\">\n <div class=\"stat-value text-red-600\" id=\"stat-invalid\">-</div>\n <div class=\"stat-label\">Token 失效</div>\n </div>\n\n <!-- Row 2 -->\n <div class=\"stat-card\">\n <div class=\"stat-value\" id=\"stat-chat-quota\">-</div>\n <div class=\"stat-label\">Chat 剩余</div>\n </div>\n <div class=\"stat-card\">\n <div class=\"stat-value text-blue-600\" id=\"stat-image-quota\">-</div>\n <div class=\"stat-label\">Image 剩余</div>\n </div>\n <div class=\"stat-card\">\n <div class=\"stat-value text-gray-400\" id=\"stat-video-quota\">-</div>\n <div class=\"stat-label\">Video 剩余</div>\n </div>\n <div class=\"stat-card\">\n <div class=\"stat-value\" id=\"stat-total-calls\">-</div>\n <div class=\"stat-label\">总调用次数</div>\n </div>\n </div>\n\n <!-- Tab Filter (with ARIA) -->\n <div class=\"flex items-center gap-4 mb-4 border-b border-[var(--border)] overflow-x-auto hide-scrollbar\"\n role=\"tablist\" aria-label=\"Token 状态筛选\" id=\"status-tabs\">\n <button class=\"tab-item active\" role=\"tab\" aria-selected=\"true\"\n data-filter=\"all\" onclick=\"filterByStatus('all')\">\n 全部 <span class=\"badge\" id=\"tab-count-all\">0</span>\n </button>\n <button class=\"tab-item\" role=\"tab\" aria-selected=\"false\"\n data-filter=\"active\" onclick=\"filterByStatus('active')\">\n 正常 <span class=\"badge badge-green\" id=\"tab-count-active\">0</span>\n </button>\n <button class=\"tab-item\" role=\"tab\" aria-selected=\"false\"\n data-filter=\"cooling\" onclick=\"filterByStatus('cooling')\">\n 限流 <span class=\"badge badge-orange\" id=\"tab-count-cooling\">0</span>\n </button>\n <button class=\"tab-item\" role=\"tab\" aria-selected=\"false\"\n data-filter=\"expired\" onclick=\"filterByStatus('expired')\">\n 失效 <span class=\"badge badge-red\" id=\"tab-count-expired\">0</span>\n </button>\n <span class=\"tab-sep\"></span>\n <button class=\"tab-item\" role=\"tab\" aria-selected=\"false\"\n data-filter=\"nsfw\" onclick=\"filterByStatus('nsfw')\">\n 已开 NSFW <span class=\"badge badge-purple\" id=\"tab-count-nsfw\">0</span>\n </button>\n <button class=\"tab-item\" role=\"tab\" aria-selected=\"false\"\n data-filter=\"no-nsfw\" onclick=\"filterByStatus('no-nsfw')\">\n 未开 NSFW <span class=\"badge badge-gray\" id=\"tab-count-no-nsfw\">0</span>\n </button>\n </div>\n\n <!-- Token Table -->\n <div class=\"rounded-lg overflow-hidden bg-white mb-4 overflow-x-auto\">\n <table class=\"geist-table min-w-[800px]\">\n <thead>\n <tr>\n <th class=\"w-10\"><input type=\"checkbox\" id=\"select-all\" class=\"checkbox\" onclick=\"toggleSelectAll()\"></th>\n <th class=\"w-48 text-left\">Token</th>\n <th class=\"w-24\">类型</th>\n <th class=\"w-24\">状态</th>\n <th class=\"w-20\">额度</th>\n <th class=\"text-left\">备注</th>\n <th class=\"w-32 text-center\">操作</th>\n </tr>\n </thead>\n <tbody id=\"token-table-body\">\n <!-- Dynamic Rows -->\n </tbody>\n </table>\n <div id=\"loading\" class=\"text-center py-12 text-[var(--accents-4)]\">加载中...</div>\n <div id=\"empty-state\" class=\"hidden table-empty\">\n 暂无 Token,请点击右上角导入或添加。\n </div>\n </div>\n\n <!-- Pagination -->\n <div class=\"pagination-bar\">\n <div id=\"pagination-info\" class=\"text-xs text-[var(--accents-5)]\">第 0 / 0 页 · 共 0 条</div>\n <div class=\"flex items-center gap-2\">\n <button class=\"geist-button-outline text-xs px-3\" onclick=\"selectVisibleAll()\">全选本页</button>\n <button class=\"geist-button-outline text-xs px-3\" onclick=\"selectAllFiltered()\">全选全部</button>\n <button class=\"geist-button-outline text-xs px-3\" onclick=\"clearAllSelection()\">取消全选</button>\n <button id=\"page-prev\" class=\"geist-button-outline text-xs px-3\" onclick=\"goPrevPage()\">上一页</button>\n <button id=\"page-next\" class=\"geist-button-outline text-xs px-3\" onclick=\"goNextPage()\">下一页</button>\n <select id=\"page-size\" class=\"geist-input h-[30px]\" onchange=\"changePageSize()\">\n <option value=\"20\">20 / 页</option>\n <option value=\"50\" selected>50 / 页</option>\n <option value=\"100\">100 / 页</option>\n <option value=\"200\">200 / 页</option>\n </select>\n </div>\n </div>\n\n <!-- Batch Actions Bar (Floating Bottom) -->\n\n </div>\n </main>\n <div id=\"app-footer\"></div>\n\n <!-- Batch Actions Bar (Floating Bottom) -->\n <div id=\"batch-actions\"\n class=\"fixed bottom-8 left-1/2 -translate-x-1/2 z-20 bg-white border border-[var(--border)] rounded-full px-3 py-2 flex items-center shadow-lg gap-3 cursor-move select-none active:cursor-grabbing whitespace-nowrap\">\n <div class=\"text-sm font-medium flex items-center gap-2\">\n <span class=\"text-[var(--accents-5)] text-xs\">已选择</span>\n <span class=\"bg-black text-white text-xs px-1.5 py-0.5 rounded-full\" id=\"selected-count\">0</span>\n <span class=\"text-[var(--accents-5)] text-xs\">项</span>\n </div>\n <span class=\"toolbar-sep\"></span>\n <div class=\"batch-action-buttons flex items-center gap-1\">\n <button id=\"btn-batch-export\" onclick=\"batchExport()\"\n class=\"geist-button-outline text-xs px-3 gap-1 border-0 hover:bg-gray-100\">\n 导出\n </button>\n <button id=\"btn-batch-update\" onclick=\"batchUpdate()\"\n class=\"geist-button-outline text-xs px-3 gap-1 border-0 hover:bg-gray-100 justify-center\">\n 刷新\n </button>\n <button id=\"btn-batch-nsfw\" onclick=\"batchEnableNSFW()\"\n class=\"geist-button-outline text-xs px-3 gap-1 border-0 hover:bg-gray-100 justify-center\" disabled>\n NSFW\n </button>\n <button id=\"btn-batch-disable\" onclick=\"batchDisableSelected()\"\n class=\"geist-button-outline text-xs px-3 gap-1 border-0 hover:bg-gray-100 justify-center\" disabled>\n 禁用\n </button>\n <button id=\"btn-batch-enable\" onclick=\"batchEnableSelected()\"\n class=\"geist-button-outline text-xs px-3 gap-1 border-0 hover:bg-gray-100 justify-center\" disabled>\n 启用\n </button>\n <button id=\"btn-batch-delete\" onclick=\"batchDelete()\" class=\"geist-button-danger text-xs px-3\">\n 删除\n </button>\n </div>\n <div id=\"batch-progress\" class=\"hidden text-xs text-[var(--accents-5)] flex items-center gap-2\">\n <span class=\"toolbar-sep\"></span>\n <span id=\"batch-progress-text\"></span>\n <button id=\"btn-pause-action\" type=\"button\" class=\"batch-link hidden\" onclick=\"toggleBatchPause()\">暂停</button>\n <button id=\"btn-stop-action\" type=\"button\" class=\"batch-link hidden\" onclick=\"stopBatchRefresh()\">终止</button>\n </div>\n </div>\n\n\n <!-- Import Modal -->\n <div id=\"import-modal\" class=\"modal-overlay hidden\">\n <div class=\"modal-content modal-lg\" id=\"import-modal-content\">\n <div class=\"modal-header\">\n <h3 class=\"modal-title\">批量导入 Token</h3>\n <button onclick=\"closeImportModal()\" class=\"modal-close\">\n <svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\"></line>\n <line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\"></line>\n </svg>\n </button>\n </div>\n <div class=\"space-y-4\">\n <div>\n <label class=\"modal-label mb-1 block\">目标 Pool</label>\n <select id=\"import-pool\" class=\"geist-input\">\n <option value=\"ssoBasic\">ssoBasic</option>\n <option value=\"ssoSuper\">ssoSuper</option>\n </select>\n </div>\n <div>\n <label class=\"modal-label mb-1 block\">Token 列表 (每行一个)</label>\n <textarea id=\"import-text\" class=\"geist-input font-mono h-48\"\n placeholder=\"粘贴 Token,一行一个...\"></textarea>\n </div>\n <div class=\"flex justify-end gap-2 pt-2\">\n <button onclick=\"closeImportModal()\" class=\"geist-button-outline text-xs px-3\">取消</button>\n <button onclick=\"submitImport()\" class=\"geist-button text-xs px-3\">开始导入</button>\n </div>\n </div>\n </div>\n </div>\n\n <!-- Edit Modal -->\n <div id=\"edit-modal\" class=\"modal-overlay hidden\">\n <div class=\"modal-content modal-md\" id=\"edit-modal-content\">\n <div class=\"modal-header\">\n <h3 class=\"modal-title\">编辑 Token</h3>\n <button onclick=\"closeEditModal()\" class=\"modal-close\">\n <svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\"></line>\n <line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\"></line>\n </svg>\n </button>\n </div>\n <div class=\"space-y-4\">\n <input type=\"hidden\" id=\"edit-original-token\">\n <input type=\"hidden\" id=\"edit-original-pool\">\n <div>\n <label class=\"modal-label mb-1 block\">Token</label>\n <input type=\"text\" id=\"edit-token-display\" class=\"geist-input font-mono bg-gray-50 text-gray-500\"\n disabled>\n </div>\n <div>\n <label class=\"modal-label mb-1 block\">类型</label>\n <select id=\"edit-pool\" class=\"geist-input\">\n <option value=\"ssoBasic\">ssoBasic</option>\n <option value=\"ssoSuper\">ssoSuper</option>\n </select>\n </div>\n <div>\n <label class=\"modal-label mb-1 block\">额度</label>\n <input type=\"number\" id=\"edit-quota\" class=\"geist-input\" min=\"0\">\n </div>\n <div>\n <label class=\"modal-label mb-1 block\">备注</label>\n <input type=\"text\" id=\"edit-note\" class=\"geist-input\" placeholder=\"可选备注\" maxlength=\"50\">\n </div>\n <div class=\"flex justify-end gap-2 pt-2\">\n <button onclick=\"closeEditModal()\" class=\"geist-button-outline text-xs px-3\">取消</button>\n <button onclick=\"saveEdit()\" class=\"geist-button text-xs px-3\">保存</button>\n </div>\n </div>\n </div>\n </div>\n\n <div id=\"confirm-dialog\" class=\"modal-overlay confirm-dialog hidden\">\n <div class=\"modal-content modal-md\">\n <div class=\"confirm-dialog-body\">\n <div class=\"confirm-dialog-title\">请确认</div>\n <div id=\"confirm-message\" class=\"confirm-dialog-message\"></div>\n <div class=\"confirm-dialog-actions\">\n <button id=\"confirm-cancel\" type=\"button\" class=\"geist-button-outline text-xs px-3\">取消</button>\n <button id=\"confirm-ok\" type=\"button\" class=\"geist-button-danger text-xs px-3\">确定</button>\n </div>\n </div>\n </div>\n </div>\n\n <script src=\"/static/common/js/draggable.js\"></script>\n <script src=\"/static/common/js/admin-auth.js?v=0.3.0\"></script>\n <script src=\"/static/common/js/batch-sse.js?v=0.3.0\"></script>\n <script src=\"/static/common/js/header.js?v=0.3.1\"></script>\n <script src=\"/static/common/js/footer.js?v=0.3.0\"></script>\n <script src=\"/static/common/js/toast.js\"></script>\n <script src=\"/static/admin/js/token.js?v=0.3.0\"></script>\n</body>\n\n</html>\n"
}