Files
codex-market-app/renderer/index.html
T
2026-07-15 18:01:56 +08:00

487 lines
24 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>
<!-- 已安装插件 -->
<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>
<!-- ②.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">一键配置到 Codex</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>
<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>