Files

831 lines
46 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!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="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-heading">
<div class="store-title-row">
<h2 id="store-cat-title">全部技能</h2>
<span class="store-crumb-sep">/</span>
<span class="store-count" id="store-count">—</span>
</div>
<p class="muted store-sub" id="store-cat-sub">通用 SKILL.md 标准,适配 Claude · Codex · OpenClaw</p>
</div>
<div class="store-tools">
<input id="market-search" class="search" type="search" placeholder="搜索技能 / 作者 / 功能…" />
<div class="segmented" id="store-sort" aria-label="技能排序方式">
<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>
<div class="store-filter-row" aria-label="快捷分类">
<div class="store-filter-label">分类</div>
<div class="store-category-chips" id="store-category-chips"></div>
</div>
<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">
<div class="codex-link-status" title="Codex 注入状态:语音输入、Token 用量和左侧任务行的彻底删除按钮都依赖此连接">
<span id="codex-link-dot" class="voice-dot" data-state="unknown"></span>
<span class="codex-link-label">Codex</span>
<span id="codex-link-phase-text" class="muted small">未连接</span>
<button class="btn ghost small" id="codex-link-connect-btn" data-voice-link-connect>连接并注入</button>
<button class="btn ghost small hidden" id="codex-link-disconnect-btn" data-voice-link-disconnect>断开</button>
</div>
<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">给 ChatGPT/Codex 输入框注入麦克风按钮,说话即转文字填入。需由工具箱重启 ChatGPT 一次以启用调试通道(登录态保留)。</div>
<div class="tool-foot"><button class="btn primary small" id="voice-enable-card-btn" data-voice-enable data-enable-label="启用" data-enabled-label="已启用">启用</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">用一段话描述项目需求,自动拆解出一支带约束、能协作的 AI 员工团队(含总控 + 契约 + 文件边界 + 协同规范),打包成 Codex / Claude Code 可直接用的配置 zip。</div>
<div class="tool-foot"><button class="btn primary small" data-open-panel="team">打开</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">上传图片或视频,AI 自动反推出能复现它的生图提示词。中英双语、七维拆解、负面提示词、关键词标签,直接用你的中转分组运行。</div>
<div class="tool-foot"><button class="btn primary small" data-open-panel="prompt">打开</button></div>
</div>
<div class="tool-card">
<div class="tool-top"><span class="tool-ico">⚡</span><div class="tool-name">Token 用量</div></div>
<div class="tool-desc">在 Codex 输入框下方、模型选择左侧显示每日 Token 累计消耗量。复用语音 CDP 连接自动注入,无需额外配置。每天 24 点自动清零。</div>
<div class="tool-foot"><button class="btn primary small" data-open-panel="token">打开</button></div>
</div>
<div class="tool-card" id="codex-installer-card">
<div class="tool-top"><span class="tool-ico">⬇</span><div class="tool-name">安装 Codex</div></div>
<div class="tool-desc">Windows / Mac 版远程下载并自动校验安装包,不把大体积安装文件塞进工具箱。下载和安装过程会显示进度。</div>
<div class="tool-status muted small" id="codex-installer-status">正在读取安装器信息…</div>
<div class="installer-progress hidden" id="codex-installer-progress-wrap">
<div class="installer-progress-bar" id="codex-installer-progress-bar"></div>
</div>
<div class="installer-cancel-row hidden" id="codex-installer-cancel-row">
<button class="btn ghost small" id="codex-installer-cancel-btn">取消下载</button>
</div>
<div class="tool-foot multi">
<button class="btn ghost small" id="codex-win-open-url-btn">浏览器打开 Windows</button>
<button class="btn primary small" id="codex-win-install-btn">Windows 一键安装</button>
<button class="btn ghost small" id="codex-mac-open-url-btn">浏览器打开 Mac</button>
<button class="btn primary small" id="codex-mac-install-btn">Mac 一键安装</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 连接 ChatGPT/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-enable-btn" class="btn primary" data-voice-enable data-enable-label="启用语音输入" data-enabled-label="已启用">启用语音输入</button>
<button id="voice-disable-btn" class="btn ghost hidden" data-voice-disable>关闭语音输入</button>
</div>
</div>
<div class="voice-body">
<div class="voice-card">
<div class="voice-card-title">状态</div>
<div id="voice-detail" class="muted small">点击「启用语音输入」开始。若 ChatGPT 正在运行,会提示你确认重启 ChatGPT 以启用调试端口。</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>点击「启用语音输入」。若 ChatGPT 在运行且未带调试端口,会弹窗提示重启(当前窗口会关闭一次,登录态保留)。</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">
<button class="cv-nav-dot" type="button" title="画布"></button>
<h2 class="cv-title">创意画布</h2>
<button class="cv-project-name" id="cv-projects-btn" type="button" title="打开画布记录">未命名画布</button>
<span class="pc-badge cv-status" id="cv-status">检测中…</span>
</div>
<div class="cv-toolbar-right">
<button class="btn small ghost" id="cv-new-btn">+ 新建画布</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-tool-rail" aria-label="添加节点">
<button class="cv-rail-btn" type="button" data-cv-add="text" title="文本">T</button>
<button class="cv-rail-btn" type="button" data-cv-add="image" title="文生图">▧</button>
<button class="cv-rail-btn" type="button" data-cv-add="video" title="图生视频">▣</button>
<button class="cv-rail-btn" type="button" data-cv-add="grokVideo" title="Grok视频">◆</button>
<button class="cv-rail-btn" type="button" data-cv-add="tts" title="TTS配音">♪</button>
</div>
<div class="cv-stage">
<div class="cv-project-picker hidden" id="cv-project-picker" role="dialog" aria-label="画布记录">
<div class="cv-project-picker-head">
<div><b>画布记录</b><span>自动保存到本机,生成素材也会缓存</span></div>
<button class="cv-project-close" id="cv-project-close" type="button" title="关闭">×</button>
</div>
<div class="cv-project-actions">
<button class="btn small primary" id="cv-project-create" type="button">+ 新建画布</button>
</div>
<div class="cv-project-list" id="cv-project-list"></div>
</div>
<div id="drawflow"></div>
<div class="cv-empty-hint" id="cv-empty-hint">
<span>✳ 双击</span>
<b>画布任意空白处,自由生成</b>
</div>
<div class="cv-quick-menu hidden" id="cv-quick-menu">
<div class="cv-quick-title">新建节点</div>
<button type="button" data-cv-quick-add="text"><span>T</span>文本生成</button>
<button type="button" data-cv-quick-add="image"><span>▧</span>图片生成</button>
<button type="button" data-cv-quick-add="video"><span>▣</span>视频生成</button>
<button type="button" data-cv-quick-add="grokVideo"><span>◆</span>Grok 视频</button>
<button type="button" data-cv-quick-add="tts"><span>♪</span>音频 / TTS</button>
</div>
<div class="cv-zoom">
<button class="cv-zoom-btn" id="cv-zoom-out" title="缩小">-</button>
<input id="cv-zoom-range" class="cv-zoom-range" type="range" min="50" max="160" step="5" value="100" aria-label="画布缩放" />
<button class="cv-zoom-btn cv-zoom-level" id="cv-zoom-reset" title="点击重置为 100%">100%</button>
<button class="cv-zoom-btn" id="cv-zoom-in" title="放大">+</button>
</div>
<div class="cv-minimap" aria-hidden="true">
<div class="cv-minimap-view"><span></span></div>
</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>
<!-- 员工团队:需求 → AI 员工团队 → 打包 zip -->
<section class="panel" id="tab-team">
<div class="tm-wrap">
<!-- 左侧:需求输入 + 配置 -->
<div class="tm-input-col">
<h2 class="tm-title"><button class="btn ghost small back-btn" data-back-plugins>← 插件市场</button> 生成 AI 员工团队</h2>
<p class="tm-sub">用一段话描述项目需求,自动拆解出一支带约束、能协作的 AI 员工团队(含总控),打包成 Codex / Claude Code 可直接用的配置。</p>
<textarea id="tm-req" class="tm-req" placeholder="例如:做一个电商后台管理系统,需要商品管理、订单处理、库存、用户权限和数据看板,前端要好看,后端要稳。"></textarea>
<div class="tm-field">
<label class="tm-label">目标工具</label>
<div class="tm-chips" id="tm-tool">
<button class="tm-chip active" data-tool="codex">Codex</button>
<button class="tm-chip" data-tool="claude">Claude Code</button>
</div>
<div class="tm-hint" id="tm-tool-hint">生成 .toml 员工 + 总控(调度 / 审核)+ AGENTS.md</div>
</div>
<div class="tm-field">
<label class="tm-label">团队生成模型(仅用于生成配置)</label>
<div class="tm-row">
<select class="tm-select" id="tm-group"></select>
<button class="btn small ghost" id="tm-fetch">🔄 拉取模型</button>
</div>
<select class="tm-select" id="tm-model" style="margin-top:8px"></select>
<div class="tm-hint" id="tm-model-hint">只负责把需求转换成团队配置;它不会决定生成后 Agent 执行任务使用的模型</div>
</div>
<div class="tm-field">
<label class="tm-label">总控执行模型(生成后实际运行)</label>
<div class="tm-row">
<select class="tm-select" id="tm-orch-group"></select>
<button class="btn small ghost" id="tm-orch-fetch">🔄 拉取模型</button>
</div>
<select class="tm-select" id="tm-orch-model" style="margin-top:8px"></select>
<div class="tm-hint" id="tm-orch-hint">总控负责调度、设计、审计、验收和多模态任务;可以和团队生成模型不同</div>
</div>
<div class="tm-field">
<label class="tm-label">子 Agent 执行模型</label>
<div class="tm-row">
<select class="tm-select" id="tm-agent-group"></select>
<button class="btn small ghost" id="tm-agent-fetch">🔄 拉取模型</button>
</div>
<input id="tm-agent-model" class="tm-text" list="tm-agent-model-list" style="margin-top:2px" placeholder="选择或填写 DeepSeek 模型,如 deepseek-v4-flash" />
<datalist id="tm-agent-model-list"></datalist>
<div class="tm-hint" id="tm-agent-hint">子 Agent 负责编码、测试和文档;模型名会写入员工配置,并自动登记到对应路由分组</div>
</div>
<button id="tm-gen" class="btn primary tm-gen" disabled>生成员工团队</button>
</div>
<!-- 右侧:结果 -->
<div class="tm-result-col">
<div class="tm-result-head">
<span class="tm-result-title">员工团队</span>
<button class="btn small primary" id="tm-download" hidden>⬇ 打包下载 .zip</button>
</div>
<div class="tm-result-body">
<div class="tm-empty" id="tm-empty">
<div class="tm-empty-ico">👤→👥</div>
<p class="tm-empty-title">描述需求,自动组建 AI 员工团队</p>
<p class="tm-empty-sub">拆解职责 · 定义契约 · 划分文件边界 · 生成协同规范</p>
</div>
<div class="tm-loading" id="tm-loading" hidden>
<div class="tm-spinner"></div>
<div class="tm-loading-text" id="tm-loading-text">正在分析需求、拆解团队…</div>
</div>
<div class="tm-content" id="tm-content" hidden>
<div class="tm-team-head" id="tm-team-head"></div>
<div id="tm-team-list"></div>
</div>
<div class="tm-error" id="tm-error" hidden></div>
</div>
</div>
</div>
</section>
<!-- ②.⑥ 提示词提取(图片 → 反推生图提示词) -->
<section class="panel" id="tab-prompt">
<div class="panel-head">
<div>
<h2><button class="btn ghost small back-btn" data-back-plugins>← 插件市场</button> 提示词提取</h2>
<p class="muted">上传图片或视频,AI 自动分析九个视觉维度,反推出能复现它的中英双语生图提示词。直接用你的中转分组运行。</p>
</div>
</div>
<div class="pm-wrap">
<!-- 左侧:上传 + 设置 -->
<div class="pm-input-col">
<div class="pm-dropzone" id="pm-dropzone">
<input type="file" id="pm-fileInput" accept="image/*,video/*" hidden />
<div class="pm-dz-empty" id="pm-dzEmpty">
<div class="pm-dz-icon">📤</div>
<div class="pm-dz-title">拖拽图片 / 视频到这里</div>
<div class="pm-dz-sub">或点击选择 · JPG / PNG / WebP / MP4</div>
</div>
<div class="pm-dz-preview" id="pm-dzPreview" hidden>
<img id="pm-previewImg" alt="预览" />
<button class="pm-dz-clear" id="pm-clearBtn" title="移除">✕</button>
</div>
</div>
<div class="pm-field">
<label class="pm-label">分析模型(中转分组)</label>
<div class="pm-row">
<select class="pm-select" id="pm-group"></select>
<button class="btn small ghost" id="pm-fetch">🔄 拉取模型</button>
</div>
<select class="pm-select" id="pm-model" style="margin-top:8px"></select>
<div class="pm-hint" id="pm-model-hint">先在『路由中转』配置好分组,再选具体模型(需要视觉/多模态模型)</div>
</div>
<button id="pm-analyze" class="btn primary pm-gen" disabled>开始分析</button>
</div>
<!-- 右侧:结果(分析完成后动态渲染,平时只展示空状态) -->
<div class="pm-result-col">
<div class="pm-result-body" id="pm-result-body">
<div class="pm-empty" id="pm-empty">
<div class="pm-empty-ico">🖼️→📝</div>
<p class="pm-empty-title">上传素材,反推它的生图提示词</p>
<p class="pm-empty-sub">精准分析九个视觉维度,输出可直接复现的双语提示词</p>
<div class="pm-empty-feats"><span>七维拆解</span><span>中英双语</span><span>负面提示词</span><span>关键词标签</span></div>
</div>
</div>
</div>
</div>
</section>
<!-- ②.⑦ Token 用量(Codex 输入框下方工具栏进度条) -->
<section class="panel" id="tab-token">
<div class="panel-head">
<div>
<h2><button class="btn ghost small back-btn" data-back-plugins>← 插件市场</button> Token 用量</h2>
<p class="muted">在 Codex 输入框下方、模型选择左侧显示 Token 累计消耗条。复用语音 CDP 连接自动注入,每天 24 点自动清零。</p>
</div>
<div class="head-actions">
<span id="tk-dot" class="voice-dot" data-state="unknown"></span>
<span id="tk-phase-text" class="muted small">未连接</span>
</div>
</div>
<div class="tk-body">
<!-- 今日用量 Hero -->
<div class="tk-card hero">
<div class="tk-hero-num" id="tk-total">—</div>
<div class="tk-hero-label">今日累计消耗</div>
<div class="tk-breakdown" id="tk-breakdown">
<span>输入 <b id="tk-input">0</b></span>
<span>输出 <b id="tk-output">0</b></span>
<span>缓存 <b id="tk-cached">0</b></span>
<span>估算 <b id="tk-estimate">0</b></span>
<span>未知 <b id="tk-unknown">0</b></span>
</div>
<div class="tk-hero-date" id="tk-date"></div>
</div>
<!-- Codex 效果预览 -->
<div class="tk-card">
<div class="tk-card-title">Codex 输入框下方效果</div>
<div class="tk-preview">
<div class="tk-preview-bar" id="tk-preview-bar">
<div class="tk-preview-meter">
<div class="tk-preview-track">
<div class="tk-preview-segments" id="tk-preview-segments">
<i class="tk-preview-seg"></i><i class="tk-preview-seg"></i><i class="tk-preview-seg"></i><i class="tk-preview-seg"></i>
<i class="tk-preview-seg"></i><i class="tk-preview-seg"></i><i class="tk-preview-seg"></i><i class="tk-preview-seg"></i>
</div>
<span class="tk-preview-badge"><span class="tk-preview-check">✓</span><span id="tk-preview-badge-text">0 token</span></span>
<span class="tk-preview-cap"></span>
</div>
</div>
</div>
<div class="tk-preview-input">输入消息…</div>
</div>
</div>
<!-- 操作 -->
<div class="tk-card">
<div class="tk-card-title">操作</div>
<div class="tk-row">
<button id="tk-refresh" class="btn ghost">🔄 刷新用量</button>
<button id="tk-reset" class="btn danger small">↺ 手动清零</button>
</div>
<div class="muted small" style="margin-top:8px">
用量数据保存在 Codex 页面 localStorage 中,跨天自动清零。<br />需要先在「插件市场」主页连接 Codex(Token 脚本随连接一起注入)。
</div>
<div class="tk-diag muted small" id="tk-diag"></div>
</div>
<div class="tk-card">
<div class="tk-card-title">工作原理</div>
<ol class="voice-ol">
<li>语音连接 Codex 时,Token 监控脚本自动注入同一个 CDP 连接。</li>
<li>主进程优先读取 Codex 本地 session 的 <code>token_count</code> 累计快照,网络 <code>usage</code> 仅作为兼容兜底。</li>
<li>用量条挂在输入框下方工具栏,优先放在模型选择左侧。</li>
</ol>
</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">直接读取所选 Codex 数据目录的 <code>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-clear-all" class="btn danger">一键清除</button>
<button id="sess-import" class="btn ghost">导入备份…</button>
<button id="sess-backup" class="btn">备份全部…</button>
</div>
</div>
<div class="session-data-dir" id="session-data-dir">
<span class="muted small">Codex 会话数据目录</span>
<code id="session-data-dir-path">读取中…</code>
<button id="session-data-dir-select" class="btn ghost small">选择目录…</button>
<button id="session-data-dir-reset" class="btn ghost small hidden">恢复默认</button>
</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>
<div class="sess-conv-actions">
<button id="sess-delete" class="btn small danger">永久删除</button>
<button id="sess-export" class="btn small">导出为 Markdown</button>
</div>
</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>
<p class="muted small" style="margin:6px 0 0">正在监视:<code id="mem-data-dir-path">读取中…</code></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>
<button id="router-refresh-codex" class="btn ghost" title="仅当需要同步 Codex 顶部模型选择器时重启 Codex">刷新 Codex 显示</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 地址 + 密钥 +(可选)模型。按协议分两栏:Codex/GPT 与 Claude Code 各选一个,<b>两者可同时激活一起用</b>。</p></div>
<button id="grp-add" class="btn">+ 新增分组</button>
</div>
<div id="router-groups" class="checklist"></div>
<!-- 默认账号配置详情 -->
<div id="default-config-modal" class="modal-overlay hidden">
<div class="modal default-config-modal">
<div class="modal-head"><h3>默认账号配置(只读)</h3><button class="modal-close" id="default-config-close">✕</button></div>
<div class="modal-body">
<div id="default-config-summary" class="default-config-summary"></div>
<label class="field grow" style="margin-top:12px">
<span>当前 ~/.codex/config.toml</span>
<textarea id="default-config-current" class="config-textarea" readonly></textarea>
</label>
<label class="field grow" style="margin-top:12px">
<span>默认分组保存的原始快照(切回默认时参考)</span>
<textarea id="default-config-snapshot" class="config-textarea" readonly></textarea>
</label>
<div class="row between" style="margin-top:12px">
<span class="muted small">auth.json 仅展示脱敏摘要,不回显密钥或登录 token。</span>
<div class="btn-group">
<button type="button" class="btn ghost small" id="default-config-refresh">刷新</button>
<button type="button" class="btn small" id="default-config-copy">复制当前 config</button>
</div>
</div>
</div>
</div>
</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>
<option value="claude">Claude Code(接入 Claude Code CLI,写入 ~/.claude/settings.json)</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>
<div class="grp-purpose">
<label class="grp-purpose-pill">
<input id="grp-codex-image" type="checkbox" />
<span>生图专用</span>
</label>
<label class="grp-purpose-pill">
<input id="grp-codex-video" type="checkbox" />
<span>视频专用</span>
</label>
<label class="grp-purpose-pill">
<input id="grp-stt" type="checkbox" />
<span>语音专用</span>
</label>
<label class="grp-purpose-pill" title="只用于当前 DeepSeek 分组的附件识别,不会替换对话模型">
<input id="grp-multimodal" type="checkbox" />
<span>启用 MiniMax 多模态</span>
</label>
</div>
<label class="field grow hidden" id="grp-stt-model-field" style="margin-top:8px">
<span>语音专用模型(如 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" id="grp-1m-field" style="margin-top:10px; display:none">
<span>1M 长上下文(勾选支持百万上下文的模型;转发这些模型时自动带 1M beta 头)</span>
<div id="grp-1m-list" class="grp-1m-list"></div>
<span class="muted small">仅对本身支持 1M 的模型有效(如 GPT/GLM/Claude 的 1M 版)。勾选后经此分组转发该模型即解锁 1M 窗口。</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="field grow claudedesk-block" id="grp-claudedesk" style="margin-top:14px; border-top:1px solid var(--border); padding-top:14px">
<span>接入 Claude 桌面 App(第三方推理)</span>
<div class="muted small" id="grp-cd-hint" style="margin:2px 0 8px">把该分组一键写入 Claude 桌面 App,重启 App 即用你的中转。仅支持 Anthropic 协议分组。</div>
<div class="row" style="gap:8px; flex-wrap:wrap; align-items:center">
<button type="button" class="btn primary small" id="grp-cd-apply">⚡ 一键接入</button>
<button type="button" class="btn ghost small" id="grp-cd-export">导出配置文件</button>
<button type="button" class="btn ghost small" id="grp-cd-copy">复制配置</button>
<button type="button" class="btn small" id="grp-cd-restore" style="border-color:var(--danger); color:var(--danger)">↩ 还原官方登录</button>
</div>
<div class="muted small" id="grp-cd-status" style="margin-top:8px"></div>
</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-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="canvas-inputs.js"></script>
<script src="app.js"></script>
</body>
</html>