18 lines
119 KiB
Python
18 lines
119 KiB
Python
"""内嵌页面资源,用于 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\"> </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"
|
||
}
|