Files
codex-market-app/renderer/index.html
T
mac 45ee76e3c8 feat: 路由不再开机自动接管 + 保存分组即写直连配置 + 官方登录完整备份 (v1.5.16)
- 启动不再自动 router.start():只想用其它功能的用户不再被接管
- 新增 applyDirectToCodex():保存 responses 中转分组即把直连配置写进
  config.toml+auth.json(非本地路由),重启 Codex 即用;本地路由降级为
  '启用本地路由(高级)'显式选项
- chat 协议转换分组(DeepSeek/GLM/Kimi)保存时只提示需启用路由,不自动起
- 新增 snapshotOfficialLogin():首次接管前完整快照官方 auth.json(含OAuth
  tokens)+config.toml(router.json加密存+落盘副本);restoreDirect 优先用它
  100%还原官方登录态,并清理 td-direct 残段
- setActive 按 isRunning 分流:路由在跑走本地路由秒切,否则写直连需重启
- 隔离测试:登录态21/21 + Mac custom回归9/9 全绿
- 版本 1.5.15 → 1.5.16

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-16 22:36:56 +08:00

544 lines
28 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="Content-Security-Policy"
content="default-src 'self'; style-src 'self' 'unsafe-inline'; script-src 'self'; img-src 'self' data:; media-src 'self' data: blob:;" />
<title>TD工具箱</title>
<link rel="stylesheet" href="vendor/drawflow.min.css" />
<link rel="stylesheet" href="styles.css" />
</head>
<body>
<!-- 顶部品牌区 -->
<header class="topbar">
<div class="brand">
<div class="logo" aria-hidden="true">
<img src="logo.png" alt="tokendun" width="40" height="40" />
</div>
<div class="brand-text">
<div class="brand-name">TD工具箱</div>
<div class="brand-sub">tokendun 工具箱 · 技能 / 插件 · 路由 · 代理</div>
</div>
</div>
<a class="promo" href="#" data-open="https://api.tokendun.cc" title="官方推荐中转 · api.tokendun.cc">
<span class="promo-badge">官方推荐</span>
<span class="promo-text">推荐中转 · <b>安全 · 稳定 · 可靠</b></span>
<span class="promo-arrow">↗</span>
</a>
<nav class="tabs" role="tablist">
<button class="tab active" data-tab="market" role="tab">技能市场</button>
<button class="tab" data-tab="plugins" role="tab">插件市场</button>
<button class="tab" data-tab="canvas" role="tab">创意画布</button>
<button class="tab" data-tab="router" role="tab">路由中转</button>
<button class="tab" data-tab="proxy" role="tab">全局代理</button>
<button class="tab" data-tab="settings" role="tab">设置</button>
</nav>
</header>
<main class="content">
<!-- ① 技能商店 -->
<section class="panel active" id="tab-market">
<div class="store">
<!-- 左侧分类导航 -->
<aside class="store-nav">
<div class="store-nav-head">
<span class="store-nav-title">浏览分类</span>
<span class="store-nav-total" id="store-total">—</span>
</div>
<div class="store-nav-list" id="store-nav"></div>
</aside>
<!-- 右侧内容 -->
<div class="store-main">
<div class="store-head">
<div class="store-crumb">
<span class="muted">技能商店</span>
<span class="store-crumb-sep">/</span>
<span id="store-cat-title">全部技能</span>
</div>
<div class="store-tools">
<input id="market-search" class="search" type="search" placeholder="搜索技能 / 作者 / 功能…" />
<div class="segmented" id="store-sort">
<button class="seg active" data-sort="hot">热门</button>
<button class="seg" data-sort="downloads">下载量</button>
<button class="seg" data-sort="stars">星标</button>
</div>
</div>
</div>
<p class="muted store-sub" id="store-cat-sub">全部技能 · 通用 SKILL.md 标准,适配 Claude · Codex · OpenClaw</p>
<div id="market-grid" class="grid store-grid"></div>
<div id="store-more" class="store-more hidden"></div>
<div id="market-empty" class="empty hidden"></div>
</div>
</div>
</section>
<!-- ② 插件市场(tokendun 自研插件 + 内置工具) -->
<section class="panel" id="tab-plugins">
<div class="panel-head">
<div>
<h2>插件市场</h2>
<p class="muted">tokendun 官方自研插件与内置工具</p>
</div>
<div class="head-actions">
<input id="plugin-search" class="search" type="search" placeholder="搜索插件 / 作者…" />
<button id="plugin-refresh" class="btn ghost">刷新</button>
</div>
</div>
<!-- 内置工具 -->
<div class="tools-title">内置工具</div>
<div class="tools-grid">
<div class="tool-card">
<div class="tool-top"><span class="tool-ico">🗂️</span><div class="tool-name">会话记录</div></div>
<div class="tool-desc">浏览、搜索、导出本机 Codex 的全部历史对话。支持一键备份 / 导入;切换账号后还能把历史对话「同步认领」回当前账号,让侧栏重新显示。</div>
<div class="tool-foot"><button class="btn primary small" data-open-panel="sessions">打开</button></div>
</div>
<div class="tool-card">
<div class="tool-top"><span class="tool-ico">🧠</span><div class="tool-name">会话记忆</div></div>
<div class="tool-desc">自动把历史会话摘要成「长期记忆」并写入项目 HANDOFF,让 AI 跨会话记住上下文。可按项目开关,检索每次的决策 / 下一步 / 涉及文件。</div>
<div class="tool-foot"><button class="btn primary small" data-open-panel="memory">打开</button></div>
</div>
<div class="tool-card">
<div class="tool-top"><span class="tool-ico">🎤</span><div class="tool-name">语音输入</div></div>
<div class="tool-desc">给 Codex 输入框注入麦克风按钮,说话即转文字填入。需由工具箱重启 Codex 一次以启用调试通道(登录态保留)。</div>
<div class="tool-foot"><button class="btn primary small" data-open-panel="voice">打开</button></div>
</div>
</div>
<!-- 已安装插件 -->
<div class="tools-title">已安装插件 <span id="plugin-installed-count" class="muted small"></span></div>
<div id="plugin-installed-grid" class="grid"></div>
<div id="plugin-installed-empty" class="empty hidden"></div>
<!-- 自研插件 -->
<div class="tools-title">官方插件</div>
<div id="plugin-grid" class="grid"></div>
<div id="plugin-empty" class="empty hidden"></div>
</section>
<!-- 语音输入(注入 Codex 输入框麦克风按钮) -->
<section class="panel" id="tab-voice">
<div class="panel-head">
<div>
<h2><button class="btn ghost small back-btn" data-back-plugins>← 插件市场</button> 语音输入</h2>
<p class="muted">由工具箱用 CDP 连接 Codex 桌面版,往输入框注入麦克风按钮;录音经中转站 Whisper 转文字并自动填入。</p>
</div>
<div class="head-actions">
<span id="voice-dot" class="voice-dot" data-state="unknown"></span>
<span id="voice-phase-text" class="muted small">未连接</span>
<button id="voice-connect-btn" class="btn primary">连接</button>
<button id="voice-disconnect-btn" class="btn ghost hidden">断开</button>
</div>
</div>
<div class="voice-body">
<div class="voice-card">
<div class="voice-card-title">状态</div>
<div id="voice-detail" class="muted small">点击「连接」开始。若 Codex 正在运行,会提示你确认重启 Codex 以启用调试端口。</div>
</div>
<div class="voice-card">
<div class="voice-card-title">转写分组 / 模型</div>
<div class="voice-row">
<span class="muted small">当前转写来源:</span>
<strong id="voice-stt-name">—</strong>
<span class="muted small">·</span>
<strong id="voice-stt-model">—</strong>
<button id="voice-refresh-stt" class="btn ghost small">刷新</button>
<button id="voice-probe-stt" class="btn ghost small">诊断接口</button>
</div>
<div id="voice-probe-result" class="muted small"></div>
<div class="muted small">在「路由中转」里勾选分组的「支持语音转文字」并填转写模型(如 whisper-1 / speech-01)。优先用当前激活分组,否则用任一勾选 STT 的分组。</div>
</div>
<div class="voice-card">
<div class="voice-card-title">使用说明</div>
<ol class="voice-ol">
<li>点击「连接」。若 Codex 在运行且未带调试端口,会弹窗提示重启(当前窗口会关闭一次,登录态保留)。</li>
<li>连接成功后,Codex 输入框旁出现 🎤 按钮。点击 → 授权麦克风权限 → 说话 → 再次点击停止。</li>
<li>转写完成后文字自动填入输入框。首次需在「系统设置 → 隐私 → 麦克风」允许 ChatGPT。</li>
<li>macOS 误杀进程风险已规避:仅在确认后重启。Codex 升级后若按钮消失,可能需等待工具箱适配。</li>
</ol>
</div>
</div>
</section>
<!-- ②.5 创意画布(Drawflow 节点画布) -->
<section class="panel" id="tab-canvas">
<div class="cv-toolbar">
<div class="cv-toolbar-left">
<h2 class="cv-title">🎨 创意画布</h2>
<span class="pc-badge cv-status" id="cv-status">检测中…</span>
</div>
<div class="cv-toolbar-right">
<span class="cv-add-label">添加节点</span>
<button class="btn small" data-cv-add="text">📝 文本</button>
<button class="btn small" data-cv-add="image">🖼️ 文生图</button>
<button class="btn small" data-cv-add="video">🎬 图生视频</button>
<button class="btn small" data-cv-add="grokVideo">🎞️ Grok视频</button>
<button class="btn small" data-cv-add="tts">🔊 TTS配音</button>
<button class="btn small ghost" id="cv-models-btn">🔄 拉取模型</button>
<button class="btn small ghost" id="cv-seed-btn">✨ 载入示例</button>
<button class="btn small primary" id="cv-full-btn">⛶ 全屏</button>
</div>
</div>
<div class="cv-stage">
<div id="drawflow"></div>
<div class="cv-zoom">
<button class="cv-zoom-btn" id="cv-zoom-in" title="放大 (Ctrl+滚轮)">+</button>
<button class="cv-zoom-btn cv-zoom-level" id="cv-zoom-reset" title="点击重置为 100%">100%</button>
<button class="cv-zoom-btn" id="cv-zoom-out" title="缩小 (Ctrl+滚轮)">-</button>
</div>
<div class="cv-mask hidden" id="cv-mask">
<div class="cv-mask-box">
<div class="cv-mask-ico">🔌</div>
<div class="cv-mask-title">中转未就绪</div>
<p class="cv-mask-text" id="cv-mask-text">请先在『路由中转』配置并激活一个中转分组</p>
<button class="btn primary small" id="cv-goto-router">前往路由中转</button>
</div>
</div>
</div>
</section>
<!-- ③ 会话记录 -->
<section class="panel" id="tab-sessions">
<div class="panel-head">
<div>
<h2><button class="btn ghost small back-btn" data-back-plugins>← 插件市场</button> 会话记录</h2>
<p class="muted">直接读取本机 <code>~/.codex/sessions</code>,全量模式会同时读取 <code>archived_sessions</code> 和子任务会话</p>
</div>
<div class="head-actions">
<input id="sess-search" class="search" type="search" placeholder="搜索标题 / 项目…" />
<span id="sess-scope" class="muted small">当前会话</span>
<button id="sess-refresh" class="btn ghost">刷新</button>
<button id="sess-fetch-all" class="btn ghost">获取全部记录</button>
<button id="sess-import" class="btn ghost">导入备份…</button>
<button id="sess-backup" class="btn">备份全部…</button>
</div>
</div>
<div class="sync-banner">
<div class="sync-text">
<strong>🔄 同步会话到当前账号</strong>
<span class="muted small">切换账号后历史对话在 Codex 里不显示?点这里把 sessions 与 archived_sessions 的全部对话"认领"到当前账号。<b>需先完全退出 Codex。</b></span>
<div class="sync-progress hidden" id="sync-progress">
<div class="sync-progress-bar"><div class="sync-progress-fill" id="sync-progress-fill"></div></div>
<span class="sync-progress-label small muted" id="sync-progress-label"></span>
</div>
</div>
<button id="sync-btn" class="btn primary">一键同步</button>
</div>
<div class="sess-layout">
<div class="sess-list" id="sess-list"></div>
<div class="sess-reader" id="sess-reader">
<div class="empty" id="sess-empty">← 左侧选一条对话查看</div>
<div class="sess-conv hidden" id="sess-conv">
<div class="sess-conv-head">
<div class="sess-conv-title" id="sess-conv-title"></div>
<button id="sess-export" class="btn small">导出为 Markdown</button>
</div>
<div class="sess-messages" id="sess-messages"></div>
</div>
</div>
</div>
</section>
<!-- ③.5 会话记忆 -->
<section class="panel" id="tab-memory">
<div class="panel-head">
<div>
<h2><button class="btn ghost small back-btn" data-back-plugins>← 插件市场</button> 会话记忆</h2>
<p class="muted">自动把每次 Codex 会话浓缩成「交接摘要」:存入本地记忆库,并写到项目根目录的 <code>HANDOFF.md</code>。新开会话说「继续」,Codex 会先读它再接着干。</p>
</div>
<div class="head-actions">
<input id="mem-search" class="search" type="search" placeholder="搜索主题 / 摘要 / 决策…" />
<button id="mem-refresh" class="btn ghost">刷新</button>
<button id="mem-toggle" class="btn primary">—</button>
</div>
</div>
<div class="card action-card">
<div class="row between">
<div class="field">
<span>状态</span>
<div><span class="pc-badge" id="mem-state">—</span> <span class="muted small" id="mem-engine"></span></div>
</div>
<span class="muted small" id="mem-count"></span>
</div>
<p class="muted small" style="margin:6px 0 0">
工作方式:会话静默几分钟后自动摘要(优先走「路由中转」当前分组的 LLM,失败退化为本地提取)。
项目根目录会生成 <code>HANDOFF.md</code>(状态快照),并在 <code>AGENTS.md</code> 里加一段「先读 HANDOFF」引导(带标记,可随时删)。
</p>
</div>
<div class="panel-head" style="margin-top:14px">
<div><h3>项目</h3><p class="muted">按项目开关写入(关闭后记忆库照常记录,但不再更新该项目的 HANDOFF.md / AGENTS.md)</p></div>
</div>
<div id="mem-projects" class="checklist"></div>
<div class="panel-head" style="margin-top:14px">
<div><h3>历史摘要</h3><p class="muted">全部存在本机 SQLite(memory.db),可随时检索</p></div>
<select id="mem-project-filter" class="grp-select" style="margin-top:0;max-width:280px"><option value="">全部项目</option></select>
</div>
<div id="mem-list"></div>
<div id="mem-empty" class="empty hidden"></div>
</section>
<!-- ④ 路由中转 -->
<section class="panel" id="tab-router">
<div class="panel-head">
<div>
<h2>路由中转</h2>
<p class="muted">Codex 直连本软件本地路由 → 转发到"当前分组"的中转 API。在此秒切分组,<b>无需重启 Codex</b>。</p>
</div>
</div>
<div class="card action-card">
<div class="row between">
<div class="field">
<span>本地路由地址(Codex 连这里)</span>
<div><code id="router-addr">—</code> <span class="pc-badge" id="router-state">未启动</span></div>
</div>
<div class="btn-group">
<button id="router-apply" class="btn primary">启用本地路由(高级)</button>
<button id="router-apply-claude" class="btn">接入 Claude Code</button>
<button id="router-toggle" class="btn">启动</button>
</div>
</div>
<div class="row">
<label class="field grow">
<span>路由 Key(已自动写入 Codex)</span>
<input id="router-key" type="text" readonly />
</label>
<label class="field">
<span>端口</span>
<input id="router-port" type="number" style="width:100px" />
</label>
<button id="router-save-port" class="btn ghost" style="align-self:flex-end">改端口</button>
</div>
<p class="muted small">点「一键配置到 Codex」后,Codex 的 config.toml 会自动指向本地路由(只需一次)。之后切分组即时生效。</p>
<p class="muted small">「接入 Claude Code」会把 <code>~/.claude/settings.json</code> 的 <code>ANTHROPIC_BASE_URL</code> 指向本地路由(只需一次),须搭配 <b>Anthropic 格式</b>的中转分组。之后同样切分组即时生效。</p>
</div>
<div class="panel-head" style="margin-top:18px">
<div><h3>中转分组</h3><p class="muted">每个分组:名称 + API 地址 + 密钥 +(可选)模型。选中谁就转发到谁。</p></div>
<button id="grp-add" class="btn">+ 新增分组</button>
</div>
<div id="router-groups" class="checklist"></div>
<!-- 分组编辑弹窗 -->
<div id="grp-modal" class="modal-overlay hidden">
<div class="modal">
<div class="modal-head"><h3 id="grp-modal-title">新增分组</h3><button class="modal-close" id="grp-modal-close">✕</button></div>
<div class="modal-body">
<label class="field grow"><span>分组名称</span><input id="grp-name" placeholder="如:TokenDun 高速组" /></label>
<label class="field grow" style="margin-top:10px">
<span>接口类型</span>
<select id="grp-mode" class="grp-select" style="margin-top:0">
<option value="responses">Responses 直连(OpenAI / TokenDun 等 Codex 原生)</option>
<option value="chat">Chat 补全 · 自动转换(国产:DeepSeek / GLM / Kimi / 通义)</option>
</select>
</label>
<label class="field grow" style="margin-top:10px"><span>API 地址(base_url)</span><input id="grp-url" placeholder="https://api.xxx.com" /></label>
<label class="field grow" style="margin-top:10px"><span>API 密钥</span><input id="grp-key" type="password" placeholder="sk-…" /></label>
<label class="field grow" style="margin-top:10px;flex-direction:row;align-items:center;gap:8px;cursor:pointer">
<input id="grp-codex-image" type="checkbox" style="width:auto;margin:0" />
<span style="margin:0">在 Codex 中用于生图(写入 gen-image 技能,需支持 OpenAI 图片接口)</span>
</label>
<label class="field grow" style="margin-top:10px;flex-direction:row;align-items:center;gap:8px;cursor:pointer">
<input id="grp-stt" type="checkbox" style="width:auto;margin:0" />
<span style="margin:0">支持语音转文字(语音输入插件用此分组转写)</span>
</label>
<label class="field grow" style="margin-top:8px">
<span>语音转写模型(勾选 STT 后填,如 whisper-1 / speech-01)</span>
<input id="grp-stt-model" placeholder="whisper-1" />
</label>
<div class="field grow" style="margin-top:10px">
<span>默认模型(可选,填了则强制用它)</span>
<div class="row" style="gap:8px; align-items:center">
<input id="grp-model" placeholder="如 gpt-5.5,可留空" style="flex:1" />
<button id="grp-fetch-models" class="btn ghost" type="button">获取模型</button>
</div>
<select id="grp-model-list" class="grp-select hidden"><option value="">— 从下拉选择模型 —</option></select>
<span class="muted small" id="grp-model-hint"></span>
</div>
<div class="field grow" style="margin-top:10px">
<span>上下文窗口 tokens(国产模型建议填,Codex 据此自动压缩)</span>
<div class="row" style="gap:6px; flex-wrap:wrap; align-items:center">
<input id="grp-ctx" type="number" placeholder="如 131072" style="flex:1; min-width:120px" />
<button type="button" class="btn small ghost" data-ctx="65536">64K</button>
<button type="button" class="btn small ghost" data-ctx="131072">128K</button>
<button type="button" class="btn small ghost" data-ctx="204800">200K</button>
</div>
<span class="muted small">DeepSeek≈64K/128K · GLM≈128K · Kimi≈128K/200K · 通义≈128K。留空则用 Codex 默认(可能过大导致超限)</span>
</div>
<div class="row between" style="margin-top:16px">
<span></span>
<button id="grp-save" class="btn primary">保存</button>
</div>
</div>
</div>
</div>
</section>
<!-- ⑤ 全局代理 -->
<section class="panel" id="tab-proxy">
<div class="panel-head">
<div>
<h2>全局代理</h2>
<p class="muted">填入 SOCKS5 代理,一键把<b>整台电脑</b>(全局 TUN)或<b>系统</b>的网络切到代理上。支持要密码的 SOCKS5。</p>
</div>
</div>
<div class="card action-card">
<label class="field grow">
<span>SOCKS5 代理地址</span>
<input id="proxy-str" type="text" placeholder="host:port:user:pass 例如 1.2.3.4:1080:用户名:密码" />
</label>
<div class="row between">
<span class="muted small" id="proxy-parsed">支持格式:<code>host:port:user:pass</code> / <code>host:port</code> / <code>socks5://user:pass@host:port</code></span>
<div class="btn-group">
<button id="proxy-save" class="btn ghost">保存</button>
<button id="proxy-test" class="btn">测试连接</button>
</div>
</div>
<div id="proxy-test-out" class="muted small" style="margin-top:6px"></div>
</div>
<div class="card action-card">
<div class="row between">
<div class="field">
<span>当前状态</span>
<div><span class="pc-badge" id="proxy-state">未开启</span> <span class="muted small" id="proxy-state-detail"></span></div>
</div>
<div class="btn-group">
<button id="proxy-start-global" class="btn primary">启动全局代理(整机)</button>
<button id="proxy-start-system" class="btn">启动系统代理</button>
<button id="proxy-stop" class="btn ghost hidden">关闭代理</button>
</div>
</div>
<div class="proxy-modes">
<div class="pm-item">
<b>🌐 全局代理(整机 TUN)</b>
<span class="muted small">创建虚拟网卡接管<b>所有</b>程序的流量(含命令行、各类 App)。macOS/Windows 启动时会弹一次<b>系统管理员密码</b>——创建网卡必需,和 Clash 一样。</span>
</div>
<div class="pm-item">
<b>⚙️ 系统代理</b>
<span class="muted small">只设操作系统代理,<b>不需要管理员密码</b>。浏览器和多数 App 生效;不认系统代理的程序(部分命令行)不走。</span>
</div>
</div>
</div>
<div class="card action-card">
<p class="muted small" style="margin:0">
<b>说明</b>:本功能内置 sing-box 引擎在本地做中转,因此即使 SOCKS5 需要账号密码也能正常用于系统代理(操作系统本身不发密码,靠本地中转补上)。关闭软件会自动还原网络设置。
</p>
</div>
</section>
<!-- 设置 -->
<section class="panel" id="tab-settings">
<div class="panel-head"><div><h2>设置</h2><p class="muted">密钥用系统安全存储加密后保存在本机</p></div></div>
<div class="card action-card">
<label class="field grow">
<span>默认 GitHub Token</span>
<input id="set-github" type="password" placeholder="ghp_…(安装私有仓库 / 上传时复用)" />
</label>
<div class="row between">
<span class="muted small">保存后,上传 / 拉取页会自动带出</span>
<button id="btn-save-secret" class="btn primary">保存</button>
</div>
</div>
<div class="card action-card">
<label class="field grow">
<span>🔑 管理员密钥(上架插件到市场需要,普通用户留空)</span>
<input id="set-admin" type="password" placeholder="仅管理员填写 · 用于上架市场" />
</label>
<div class="row between">
<span class="muted small">没有此密钥的用户只能备份到自己仓库、安装市场插件,<b>不能上架到市场</b>,防止恶意插件。</span>
<button id="btn-save-admin" class="btn primary">保存</button>
</div>
</div>
<div class="card action-card">
<div class="row between">
<div class="field">
<span>软件版本</span>
<div><b id="app-version">—</b> <span class="muted small" id="update-info"></span></div>
</div>
<button id="btn-check-update" class="btn">检查更新</button>
</div>
</div>
<div class="card action-card">
<div class="row between">
<div class="field">
<span>🧹 强制清除本机 Codex</span>
<p class="muted small" style="margin:0;max-width:520px">
自动识别 Windows / macOS 上 Codex 的安装位置(数据目录、可执行文件、npm 全局包等)。
更新后中转失效时,可在此彻底清干净,再重新安装。<b style="color:var(--danger)">删除不可撤销</b>,
会清掉配置、登录凭证、会话与插件。
</p>
</div>
<button id="btn-purge-scan" class="btn">检测安装位置</button>
</div>
<div id="purge-list" class="checklist" style="margin:0"></div>
<div class="row between" id="purge-actions" style="display:none">
<span class="muted small">将删除上方全部已勾选的位置</span>
<button id="btn-purge-do" class="btn danger">彻底清除 Codex</button>
</div>
</div>
</section>
</main>
<!-- 运行日志抽屉 -->
<div class="logbar">
<button id="log-toggle" class="log-toggle">运行日志 <span id="log-caret">▲</span></button>
<div id="log-panel" class="log-panel hidden"><pre id="log-out"></pre></div>
</div>
<!-- 版权 / 使用说明 常驻页脚 -->
<footer class="footer-notice">
<span class="fn-text">本市场收录的插件版权归各自作者(含 OpenAI)所有,仅供已获授权用户备份 · 还原使用。</span>
<span class="fn-links">
<button class="fn-link" id="open-usage">📖 使用说明</button>
<button class="fn-link" id="open-notice">📄 版权声明</button>
</span>
</footer>
<!-- Toast -->
<div id="toast" class="toast hidden"></div>
<!-- 说明弹窗 -->
<div id="modal-overlay" class="modal-overlay hidden">
<div class="modal" role="dialog" aria-modal="true">
<div class="modal-head">
<h3 id="modal-title"></h3>
<button class="modal-close" id="modal-close" aria-label="关闭">✕</button>
</div>
<div class="modal-body" id="modal-body"></div>
</div>
</div>
<!-- 更新弹窗 -->
<div id="update-modal" class="modal-overlay hidden">
<div class="modal update-modal" role="dialog" aria-modal="true">
<div class="update-head">
<div class="update-ico">🚀</div>
<div>
<h3 id="update-title">发现新版本</h3>
<p class="muted small" id="update-ver"></p>
</div>
<span class="update-badge" id="update-badge"></span>
</div>
<div class="update-notes" id="update-notes"></div>
<div class="update-foot" id="update-foot"></div>
</div>
</div>
<script src="vendor/drawflow.min.js"></script>
<script src="app.js"></script>
</body>
</html>