把 AI 生成的 HTML 当 Markdown 来管:Web-Doc 自托管文档站实践

把 AI 生成的 HTML 当 Markdown 来管:Web-Doc 自托管文档站实践

一个开源项目的诞生故事:当 AI 越来越会写 HTML,我们却没有一个像样的地方"存放"它们。

项目地址:https://github.com/IcedSoul/web-doc


一、为什么会有这个项目

最近一年,我用大模型生成 HTML 单页的频率越来越高:

  • 给同事讲一个技术方案,让 AI 直接生成一份带交互的 HTML 总结页;
  • 做产品 demo,让 AI 写一个静态原型;
  • 整理学习笔记,让 AI 把一段视频/论文翻译成可交互的可视化页面;
  • 做活动方案,让 AI 出一份带样式的、能直接发给老板看的"网页版 PPT"。

但很快我就遇到一个尴尬的问题:这些 HTML 文档没地方放。

  • 丢在 ChatGPT 对话里 → 找不到、不能再生成;
  • 存成本地 .html 文件 → 多文件(带图片、CSS)就乱了,分享要发压缩包;
  • 推到 GitHub Pages → 重,每次还要 commit;
  • 用 Notion / 飞书 → 它们不渲染 HTML,最多放个截图。

我想要的其实是:

像管 Markdown 笔记一样管 HTML,有树形目录所见即所得改完即刷新一键分享 ,最好还能让 AI 直接在里面写

于是有了这个开源项目 ------ Web-Doc:一个自托管、单二进制的 HTML 文档站。

仓库结构很轻:

复制代码
web-doc/
├── apps/api/   # Go (Gin + GORM + Postgres) 后端
├── apps/web/   # React 19 + Vite + TS 前端
├── deploy/nginx/
└── docker-compose.full.yml

二、核心设计:一个文档 = 磁盘上的一个目录

很多类似工具会把 HTML 存进数据库的 text 字段,看起来简单,但一旦涉及多文件(index.html + assets/ + 图片)就立刻笨重。

Web-Doc 的选择非常"老派",但也非常顺手:

复制代码
storage/docs/
├── 1a2b3c4d/                # 文档 A:单文件
│   └── index.html
├── 5e6f7a8b/                # 文档 B:多文件(zip 上传)
│   ├── index.html
│   ├── style.css
│   ├── app.js
│   └── assets/cover.png
└── ...

数据库(PostgreSQL via GORM)只存元数据:节点(文件夹/文档)、归属用户、分享 Token、AI 设置、Prompt 模板、MCP Token。真正的内容永远在磁盘上 ,可以直接 cd 进去用 vim 改,也可以 tar 一下搬家。

这个选择带来的好处会在后面 AI 流式写入、热更新两个特性中体现得淋漓尽致。


三、三个让我自己天天用的特性

特性 1:沙箱化预览 + 文件变更热更新

预览不是把 HTML "塞进" 一个 iframe,而是直接挂到一条独立路径上:

复制代码
GET /d/{docId}/index.html
GET /d/{docId}/assets/cover.png

前端 iframe 加了 sandbox 属性,相当于让用户写的 JS 跑在一个隔离的"小宇宙"里 ------ 它出不来,污染不了主站。

特性 2:AI 流式生成,边写边落盘

很多 AI 写 HTML 的工具是"等生成完,整段渲染"。Web-Doc 走得更激进 ------ AI 输出的 token 直接落到磁盘上

复制代码
用户点击「AI 生成」
      │
      ▼
POST /api/ai/generate   (流式 SSE)
      │
      ▼
Go 服务调用 OpenAI 兼容协议(DeepSeek / Kimi / GLM / Qwen / OpenRouter ...)
      │
      ▼
每收到一段 chunk → 追加写入 storage/docs/{id}/index.html
      │
      ▼
fsnotify 触发 → WebSocket 推送 → iframe 重载(节流 ~300ms)

实际效果:你看着模型一行一行把 <div> 写出来,预览框里 UI 一块一块地长出来,像在看一段延时摄影。

技术上的关键点:

  • 配置完全 per-user:Base URL / API Key / Model / System Prompt / Temperature / MaxTokens 全部独立存在数据库;
  • 兼容 OpenAI Chat Completions 协议,所以国内外几乎所有模型都能直接接;
  • 两种模式:生成新文档改写当前文档
  • 节流 ~300ms 刷新预览,避免模型吐字快时浏览器卡死。

特性 3:内置 MCP Server ------ 让 Agent 直接管文档

这是我自己最爱的部分。Web-Doc 内置了一个 Model Context Protocol 端点:

复制代码
POST /mcp     # JSON-RPC 2.0 over Streamable HTTP,Bearer Token 鉴权

暴露的工具集:

工具 用途
list_documents 列出整棵文档树
get_document 看某个文档的元信息和文件清单
create_document 新建文档/文件夹(可附初始 HTML)
delete_document 递归删除
read_document_file 读文档下任意文本文件
upload_html 写/覆盖单文件
upload_zip_base64 用 zip 整体替换文档

这意味着:任何支持 MCP 的 Agent(Claude Desktop、Cursor、CodeBuddy ...)都可以把 Web-Doc 当作自己的"知识库写入端"。


四、一个真实案例:用 Claude + MCP 自动整理周报

讲个我前两周真实跑通的工作流。

场景

我每周需要交一份周报,内容包括:本周完成、下周计划、风险项。素材散落在文档、Git commit、群聊里。

步骤

1. 在 Web-Doc 里建一个文件夹 周报/

2. 在 Web-Doc 设置里签发一个 MCP Token:

UI → 用户菜单 → MCP Token → "新建",得到一个 wdmcp_xxx 的 Bearer。

3. 把这个 MCP 配置注册到 Claude Desktop:

json 复制代码
{
  "mcpServers": {
    "web-doc": {
      "url": "http://localhost:8787/mcp",
      "headers": {
        "Authorization": "Bearer wdmcp_xxx"
      }
    }
  }
}

4. 对 AI Agent 说:

"我把这周做的事粘给你,同时参考下代码和git commit,帮我生成一份带 Tailwind 样式的 HTML 周报,标题用'Week 20 周报',然后用 create_document 工具放到我 Web-Doc 的 周报 文件夹下。"

Claude 会:

  • list_documents 找到 周报/ 节点的 id
  • 生成 HTML 内容;
  • create_document 创建子节点,parentId 指向 周报/initial_html 是渲染好的页面。

5. 浏览器刷新 Web-Doc:周报已经躺在树里了。

6. 一键分享:

UI 上点"分享" → 拿到 /s/{token} 短链,发到群里。链接打开是干净的、无 app chrome 的预览页,老板看着像一个正经网站。

这个流程闭环之后,我做周报的"组织/排版"成本几乎降到 0,剩下的全是"想清楚自己这周干了什么"。


五、第二个案例:用 AI 改写一个静态原型

这次完全不出 Web-Doc:

  1. 我先粘了一段从设计稿截图里 OCR 出来的文字进 AI 生成对话框,让它生成一个登录页 HTML;
  2. 流式生成的过程中,右边 iframe 一边长出按钮、一边长出表单;
  3. 看完不满意,点"改写当前文档 "模式,对它说: "把按钮改成 primary 紫色 + 圆角 12px,输入框加 focus ring。"
  4. Monaco 编辑器里 HTML 一行行变化,iframe 同步重载;
  5. 觉得 OK,⌘S 落盘(其实流式写入早就落盘了,这一步只是保险);
  6. 直接拖一张 logo 图到这个文档对应的目录里(或在 UI 上传),fsnotify 监听到新文件,预览也会刷;
  7. <img src="logo.png"> → 完成。

整个过程:没切窗口、没开 VSCode、没 npm install、没 deploy。


六、技术栈与一些工程选择

选型 理由
后端 Go + Gin + GORM + Postgres 单二进制 / 易自托管 / 文件 IO + 长连接友好
文件监听 fsnotify 跨平台、稳定、几乎无依赖
实时推送 gorilla/websocket 与 fsnotify 配合做热更新
AI OpenAI 兼容协议(流式 SSE) 一份代码兼容十几家模型
前端 React 19 + Vite + TS 现代、热模块替换香
编辑器 Monaco 与 VSCode 同源体验
拖拽 dnd-kit 树形结构里同级排序 + 跨层移动
状态 Zustand 比 Redux 轻得多
UI TailwindCSS + shadcn/ui 默认就好看
部署 单 Go 二进制 / Docker Compose / Nginx 适合自托管

几个值得一提的工程细节:

  1. 路径穿越防护 :所有 read/write 路径都拒绝 .. 与绝对路径;
  2. ZIP 上传白名单:只允许 html/js/css/png/jpg/svg/woff2/...;
  3. 单文档 50 MB 上限:避免被人滥用;
  4. iframe sandbox:把用户 JS 关进笼子;
  5. JWT 多用户隔离:每个用户的文档、AI 设置、Prompt、MCP Token 都互不可见;
  6. WEBDOC_DISABLE_REGISTER=1:可关闭注册,做"私有云"很方便。

七、自托管,三种姿势

bash 复制代码
# A. 本地开发(最快,需 Node 18+ / Go 1.21+ / Postgres)
npm run install:all
npm run dev          # api:8787 · web:5173

# B. 单二进制
npm run build
WEBDOC_WEB_ROOT=$(pwd)/apps/web/dist npm start

# C. Docker Compose 一把梭
docker compose -f docker-compose.full.yml up -d

文档存在 docs 这个 named volume 里,数据库存在 pgdata。换机器把这两个目录 tar 走就行,没有任何隐式状态。


八、它适合谁

  • 经常让 AI 生成 HTML、却找不到地方存的人;
  • 想做轻量"原型站"、不想为每个 demo 单独搭工程的设计师/PM;
  • 有自托管偏好、不想让笔记类内容跑去三方云的开发者;
  • 想给自己的 Agent 一个"写文档"工具的 AI 折腾爱好者;
  • 不想再为"分享一个 HTML 文件"打包压缩包的人。

九、还能往哪走

短期 roadmap 里我考虑的几个点:

  • 文档版本/快照(基于目录的 git-like 增量);
  • 协作编辑(CRDT,可能上 Yjs);
  • 评论与 review 流;
  • 更细粒度的分享权限(密码、过期);
  • 接入更多 Agent 平台的 MCP 注册指引。

十、结语

Web-Doc 的本质思想其实很朴素:

AI 生成的内容是一等公民,不应该被锁在某个聊天框里。

我希望它能像 Obsidian 之于 Markdown 一样,成为 HTML 文档的"本地+自托管+AI 原生"的一个起点。

如果你也在被"AI 生成的 HTML 满天飞"困扰,欢迎试用、提 issue、提 PR。

仓库web-doc (Go + React,单二进制可跑)
License:详见仓库

相关推荐
之歆4 小时前
DAY_13DOM操作完全指南DOM基础API与节点操作(上)
开发语言·前端·javascript·ecmascript
zhoumeina994 小时前
如何保证不同位置切换合成底图的渲染顺序
java·前端·javascript
带娃的IT创业者4 小时前
Anthropic收购Stainless:AI Agent时代的连接革命
人工智能·ai agent·anthropic·mcp·收购·stainless
海上彼尚5 小时前
Nodejs也能写Agent - 3.基础篇 - Tools 与 Tool Calling
前端·人工智能·后端·node.js
用户125758524365 小时前
GoFrame + Vue3 后台管理框架,CRUD 代码生成器一键搭 RBAC 权限系统
前端
七十二時_阿川5 小时前
Electron 如何自定义菜单?这篇帮你实现原生体验!
前端·electron
七十二時_阿川5 小时前
Electron App 速查表:生命周期事件、方法、平台差异
前端·electron
七十二時_阿川5 小时前
Electron 多显示器开发?这篇帮你搞定屏幕坐标与窗口定位!
前端·electron
七十二時_阿川5 小时前
Electron Tray API 详解:托盘图标、右键菜单、气泡通知
前端·electron