AtomCode WebUI SPA 前端架构分析
持续深度分析 — 嵌入的 React/Preact SPA、组件树、本地化、事件系统 2026-06-23
1. SPA 技术栈
| 技术 | 详情 |
|---|---|
| 框架 | Preact (React 兼容,从 JS 代码中 $(...) 语法和 Hooks API 确认) |
| CSS | 单一 CSS 文件 assets/index-DQAxDfIC.css |
| JS | 单一 JS 文件 assets/index-CM83LMnS.js |
| Markdown | marked 库 (J.setOptions({gfm:true})) + DOMPurify (Mr.sanitize()) |
| 语法高亮 | 无 Prism/highlight.js(使用服务器端 Markdown 渲染) |
| 构建工具 | Vite 类构建工具(基于 asset hash 命名) |
2. 组件树架构
App (root)
│
├── AppSidebar (gi)
│ ├── SessionList (gi 内部)
│ │ ├── SessionItem (各会话项)
│ │ │ ├── session-name (会话名)
│ │ │ ├── cwd-breadcrumb (工作目录面包屑)
│ │ │ └── resume-button (恢复按钮)
│ │ └── "No recent projects. Use /cd <path> to switch."
│ │
│ ├── DirBrowser (ka — 目录浏览器组件)
│ │ ├── cwd-prefix / dir-breadcrumb (路径导航)
│ │ ├── dir-item (目录项,显示 d: 前缀)
│ │ └── file-item (文件项,显示 f: 前缀)
│ │
│ └── PermissionDecision (ti — 权限决策弹窗)
│ ├── "action: allow" / "action: block"
│ ├── tool_name 显示
│ └── 确认/拒绝按钮
│
├── Header (头栏)
│ └── HeaderRemoteBtn (远程连接按钮,ghost-btn)
│ └── svg qr icon
│
├── ChatBody (Hr)
│ ├── ChatLanding (Na — 欢迎页面)
│ │ ├── "Welcome to AtomCode. Pick an option to get started:"
│ │ └── 选项列表
│ │
│ ├── MessageList (消息列表)
│ │ ├── assistant-message-content (.markdown-root)
│ │ │ └── code-block-wrapper (pre > code + copy-button)
│ │ └── user-message
│ │
│ └── InputArea (聊天输入)
│ ├── SlashPopover (斜杠命令弹出)
│ │ ├── slash-row (斜杠命令项)
│ │ │ ├── slash-name (/命令名)
│ │ │ └── slash-desc (命令描述)
│ │
│ ├── MessageInput (textarea)
│ │ ├── onInput, onKeyDown, onPaste
│ │ └── placeholder (本地化)
│ │
│ ├── AttachmentInsert (Zr)
│ │ ├── onInsert, onPickFile, onAddImages
│ │ └── 图片预览
│ │
│ ├── SyncToggle (btn-sync)
│ │ ├── "Attach to live webui session (/sync off to detach)"
│ │ └── aria-pressed 状态
│ │
│ └── InputFooter
│ └── 状态指标
│
├── Panels (覆盖层)
│ ├── ThemePanel (Sa) — 主题选择
│ ├── LanguagePanel (Ca) — 语言选择
│ ├── ModelPanel (wa) — 模型/提供商选择
│ ├── RemotePanel (Da) — Tunnel QR + 远程访问
│ └── ProviderConfigPanel — 提供商配置
│ ├── provider-edit-btn
│ └── 配置表单 (API key, base_url, model, etc.)
│
└── Dialogs (模态)
├── SessionRename (Ki) — 会话重命名
└── SessionDelete (qi) — 会话删除确认3. 本地化系统
3.1 支持的语言
| 语言代码 | 语言 |
|---|---|
en | 英语 |
zh-CN | 简体中文 |
zh-TW | 繁体中文 |
zh-HK | 繁体中文(香港) |
zh | 中文 |
3.2 本地化消息示例
从二进制提取的示例:
javascript
// 英语 (en)
{
"header.menu": "...", // 头部菜单
"settings.menuRemote": "Remote",
"chat.inputPlaceholder": "Start your conversation...",
"chat.stop": "Stop",
"chat.send": "Send",
"chat.queue": "Queue message (sent after the current turn finishes)",
"chat.queued": "Queued",
"chat.removeQueued": "Remove",
"chat.error": "[Error: {msg}]",
"chat.connError": "[Connection error: {msg}]",
"attach.menu": "Add",
"attach.image": "Upload image",
"attach.file": "Upload file",
"attach.skill": "Choose skill",
"attach.removeImage": "Remove image",
// ...
}4. Markdown 渲染
4.1 渲染器配置
javascript
J.setOptions({ gfm: true, breaks: false });
class CustomRenderer {
code(code, language) {
// Pre > Code block + copy button
return `<div class="code-block-wrapper">
<pre><code class="language-${lang}">${escapedCode}</code></pre>
<button class="copy-button" data-copy="...">Copy</button>
</div>`;
}
}4.2 支持的内容格式
| 格式 | 支持 |
|---|---|
| GFM Markdown | ✅ (gfm=true) |
| mermaid 图表 | ✅ (从 CSS 检出) |
| SVG | ✅ |
| HTML | ✅ (经过 DOMPurify 消毒) |
| XML | ✅ |
| 行内代码 | ✅ |
| 代码块 + 复制 | ✅ |
4.3 XSS 保护
javascript
// DOMPurify 配置
Mr.sanitize(html, {
ADD_ATTR: ["data-copy"] // 允许复制按钮属性
});5. 状态管理
5.1 Hooks 使用模式
javascript
// Session state
const sessionId = useState(id)
// CWD state
const cwd = useState(path)
// Active session tracking
const activeSession = useState(session)
// Toggle states (sync, etc.)
const [syncEnabled, toggleSync] = useState(false)
// Autocomplete state
const [input, setInput] = useState("")
const [suggestions, setSuggestions] = useState([])
const [selectedIndex, setSelectedIndex] = useState(0)5.2 设置面板切换
javascript
// 每个设置面板由全局状态 d 控制
d === `theme` → 显示 ThemePanel
d === `language` → 显示 LanguagePanel
d === `model` → 显示 ModelPanel
d === `remote` → 显示 RemotePanel
// 会话重命名状态
w === `rename` → 显示 RenameDialog
w === `delete` → 显示 DeleteDialog6. API 通信
6.1 认证头
javascript
function G() {
let t = getAuthToken(); // 从存储/内存获取 token
return t ? { "Authorization": `Bearer ${t}` } : {};
}
// 备用: Yi() 函数等效6.2 API 调用模式
javascript
// GET 请求
async function Pe() {
return (await fetch(`/sessions`, { headers: G() })).json();
}
// POST + 错误处理
async function Fe(e, t, n) {
let r = await fetch(`/projects/${encodeURIComponent(e)}/sessions/${encodeURIComponent(t)}/rename`, {
method: `PATCH`,
headers: { "Content-Type": `application/json`, ...G() },
body: JSON.stringify({ name: n })
});
if (!r.ok) throw Error(`rename failed: ${r.status}`);
}7. 代码复制功能
javascript
// 复制按钮处理
onClick(e) {
let button = e.target?.closest(`.copy-button`);
if (button?.dataset.copy) {
navigator.clipboard?.writeText(
decodeURIComponent(button.dataset.copy)
).catch(() => {});
// 按钮文字 原文本 → "Copied" → 1.2 秒后恢复
let original = button.textContent;
button.textContent = `Copied`;
setTimeout(() => { button.textContent = original }, 1200);
}
}8. SPA 嵌入分析
SPA 文件:
assets/index-CM83LMnS.js → 嵌入在 atomcode.bin 二进制中
assets/index-DQAxDfIC.css → 嵌入在 atomcode.bin 二进制中
加载方式:
- Daemon WebUI 服务在端口 13457
- 从 URL /assets/ 提供嵌入的 JS/CSS
- 所有前端逻辑通过单个 JS 包运行 (~100KB+ 压缩后)9. 新覆盖的盲区
| # | 之前未覆盖的领域 | 状态 | 发现 |
|---|---|---|---|
| 1 | 前端技术栈 | ✅ | Preact + GFM Markdown + DOMPurify |
| 2 | 完整组件树 | ✅ | 20+ 组件,4 层深度 |
| 3 | 本地化系统 | ✅ | 5 种语言 + 本地化消息键 |
| 4 | Markdown 渲染流程 | ✅ | 自定义渲染器 + XSS 保护 |
| 5 | 状态管理 | ✅ | Preact hooks + 面板切换机制 |
| 6 | 代码复制 | ✅ | clipboard API + "Copied" 反馈 |
| 7 | API 通信模式 | ✅ | Auth header + 错误处理模式 |
| 8 | 嵌入方式 | ✅ | 2 个 asset 文件嵌入 binary |
本报告由持续分析循环生成
逆向命令索引
bash
# 从 daemon 提取 SPA JS
curl -s http://localhost:13457/assets/index-*.js -o /tmp/spa.js 2>/dev/null
wc -c /tmp/spa.js 2>/dev/null
# 提取 API 端点
grep -oP '"[a-z]+/[a-z_-]+"' /tmp/spa.js 2>/dev/null | sort -u | head -15