DocFlow 实现与复用调查
DocFlow 功能与接线盘点
按编辑器、协同、AI、基础设施、导出与工程检查所查快照的接线范围。
本页整理自 2026-10-04 的 DocFlow 静态调查材料。原材料没有提供仓库 URL、分支或提交哈希;正文中的路径、行号、计数与依赖版本均指向该未锚定快照。本轮整理读取了归档报告,未取得原工作副本、启动产品或验证外部后端。下文的“复核”指原材料记载的复核,前端路径存在与运行可用性分别判断。
功能全景
实现程度图例(判定口径:依赖外部后端本身不影响判定——整个产品的后端都在仓库外,前端有效入口及处理路径完整即计 ✅,不据此判断外部后端或运行效果;⚠️ 仅表示前端交付的是功能子集或混合真实/假数据):
- ✅ 材料记录了前端实现与接线,运行可用性未验证(带"复核"者表示该结论经逐条代码原材料复核;个别功能的子项例外时在说明列以 🚫 单独标出)
- ⚠️ 部分实现:仅功能子集可用,或同一功能内混合真实实现与假数据/占位
- 🚫 死代码 / 未接线:代码已写好,但所查快照的构建配置下不可达或无效果(未注册、无调用方、入口被注释,或仅为无绑定的装饰 UI)
- 🔶 演示级实现
- ❌ 仅依赖声明 / 仅文档宣称,无实现
编辑器核心(/docs/[room])
| 功能 | 程度 | 说明与证据 |
|---|---|---|
| 块式文档结构 | ✅ 复核 | Document(content 'block+',extensions/Document/Document.ts:4)+ Heading 1-6 + 段落 + TrailingNode |
| 列表(有序/无序/任务) | ✅ 复核 | TaskItem 嵌套 extension-kit.ts:80-82;slash 菜单 SlashCommand/groups.ts:3-195 |
| 折叠块 Details | ✅ 复核 | Details.configure({ persist: true }),extension-kit.ts:106-113 |
| 引用 / 水平线 | ✅ 复核 | slash Style 组(groups.ts:3-195) |
| 代码块 | ✅ | CodeBlockLowlight 二次封装:lowlight common(约 40 语言)、``` 输入规则带语言别名(js→javascript 等)、语言下拉/复制/折叠/换行、theme 属性(CodeBlock/CodeBlock.ts:12,61-97,266-271、CodeBlockComponent.tsx:243,320);Mod-/ 按语言切换注释、Tab/Shift-Tab 两空格缩进(CodeBlock.ts:104-243)、Mod+A 仅选代码(SelectOnlyCode.ts:16-24) |
| 表格 | ✅ 复核 | 官方 TableKit,resizable: true(extension-kit.ts:318-322) |
| 图片体系 | ✅ 复核 | 三节点:ImageUpload 占位上传块 / ImageBlock(宽、对齐、alt)/ 表格内缩略 TableImage;拖放粘贴统一走 FileHandler,先插 base64 预览再上传后按 src 回查替换为服务器 URL(图片节点与上传) |
| 数学公式 | ✅ 复核 | 官方 Mathematics(KaTeX,自定义 \R \N \Z \Q \C 宏)+ 自研 MathLiveExtension(Ctrl/Cmd+M 弹 MathLive 可视化编辑器,插 inlineMath)+ mathMigration 匿名扩展把旧 $...$ 文本防抖迁移为数学节点(extension-kit.ts:342-370、MathLiveEditor/MathLiveExtension.ts:69-74) |
| YouTube 视频 | ✅ 复核 | 官方 Youtube.extend + openYoutubeDialog 命令与对话框(Youtube/Youtube.ts:45,49-83) |
| 目录 TOC | ✅ | 双实现:可插入正文的 tableOfContentsNode 原子节点(读官方 TableOfContents storage,TableOfContentsNode.tsx:85-121)+ 页面悬浮 FloatingToc(rAF 节流高亮,docs/_components/FloatingToc/index.tsx:24-44) |
| emoji | ✅ 复核 | 官方 Emoji 扩展 + 自研 emojiSuggestion 建议(extensions/index.ts:50、extension-kit.ts:309-312) |
| @Mention | ✅ | 自研 inline 原子节点(char '@'),候选来自好友接口带时间缓存、失败返回空数组(Mention/mention.ts:39,79-85,170-194、suggestion.tsx:60-96) |
| slash 命令菜单 | ✅ | 自研:基于 @tiptap/suggestion,char '/'、startOfLine、仅根深度段落触发;Style/Insert 分组 + 别名过滤(h1/img/hr/todo 等);ReactRenderer 渲染 Popover、滚动重定位、键盘导航(SlashCommand.ts:16-36,53-93,94-246、groups.ts:3-195) |
| 文本气泡菜单 | ✅ 复核 | 自研 CustomBubbleMenu(floating-ui computePosition + flip/shift/autoUpdate,TextMenu/BubbleMenu.tsx:2,121-141,170-203);功能:块类型切换、字体/字号、B/I/U/S/行内代码/代码块、链接、多色高亮、文字颜色、上下标、四种对齐(TextMenu.tsx:88-247) |
| Link / ImageBlock 气泡菜单 | ✅ 复核 | 用官方 BubbleMenu(@tiptap/react/menus);ImageBlockMenu 提供对齐 + 宽度滑杆(LinkMenu.tsx:3、ImageBlockMenu/index.tsx:1,15-38) |
| 块拖拽排序 | ✅ 复核 | 编辑器内:官方 drag-handle-react 的 DragHandle 包住 ContentItemMenu(+ 添加块 / AI 续写 / GripVertical 弹出清除格式/复制/复制块/删除,ContentItemMenu.tsx:1,37-90);侧栏组件库:原生 HTML5 DnD(dataTransfer 'application/x-block-type')+ 自研 DragHandler 插件策略工厂(DragHandler.ts:34-96,258-269、BlocksTab.tsx:204-207);dnd-kit 仅用于侧栏文件树排序(块拖拽机制) |
| 搜索替换 | ✅ | 自研 SearchAndReplace 扩展(decoration 高亮 + storage)+ SearchPanel(大小写开关、上一处/下一处、替换/全部替换),Ctrl/Cmd+F 打开(SearchAndReplace.ts:44-60、SearchPanel.tsx:20-35、page.tsx:119-147,210-223) |
| 粘贴增强 | ✅ | MarkdownPaste(micromark-gfm/mdast 解析 Markdown 纯文本粘贴为 PM 节点 + clipboardTextSerializer 反向输出 Markdown)+ JsonPaste(监听剪贴板 text/json 整体替换文档)(Markdown 转换;page.tsx:168-204 copy 时写入 text/json 与 docflow-reference) |
| AI 建议 diff mark | ✅ 复核 | AgentSuggestion(added 绿 / deleted 红),accept/reject 命令按 suggestionId 批量处理(建议接受与拒绝) |
| 评论(Comment) | 🚫 | mark 与 Mod-Shift-m 快捷键已实现(Comment/Comment.ts:217)但未注册进 ExtensionKit,编辑器内不生效;与 HTTP 权限等级 COMMENT 的关系见 协同权限边界 注记 |
| 图注(Figure/Figcaption) | 🚫 | Figure 节点已实现但未注册;仅注册的 Figcaption 缺少父容器,图注结构无法形成(extensions/index.ts:43,59 仅导出;ExtensionKit 数组无二者) |
| B 站视频块 | 🚫 | packages/bilibili/src/Bilibili.ts:77 有完整扩展实现,但 ExtensionKit 未注册;仅 styles/index.css:23 引入其样式、next.config.ts:13 加入 transpilePackages |
| 甘特图 | ❌ | 仅 package.json:145 声明 frappe-gantt 依赖 + styles/index.css:18 引入其 CSS,全 src 无任何 Gantt 组件/扩展(grep -ri gantt 仅命中该 CSS) |
| 流程图 / 音频块 / mermaid | ❌ | schema 无此类节点(grep audio/flowchart/gantt/mermaid 于扩展注册表无命中) |
| 侧栏组件库部分块类型 | ⚠️ | BlocksTab 展示 14 种块(BlocksTab.tsx:94-189),DragHandler 策略表只覆盖 10 种(DragHandler.ts:258-269);video/calendar 等走默认段落策略,audio/ai 生成的节点类型 schema 中不存在,点击插入处理器仍是 TODO console.log(BlocksTab.tsx:209-212) |
实时协同
| 功能 | 程度 | 说明与证据 |
|---|---|---|
| Yjs 文档协同 | ✅ 复核 | 客户端 HTTP 鉴权 → new Y.Doc() → y-indexeddb 本地恢复 → HocuspocusProvider 连 WS;房间名 = documentId;useCollaboration.ts:45-47,54-56,70-74、useDocumentPermission.ts:39,59-65 |
| Tiptap 绑定 | ✅ 复核 | Collaboration.configure({ document, field: 'content' }) + CollaborationCaret.configure({ provider, user }),仅在 IndexedDB+服务端均同步后条件加入(page.tsx:102-108;useCollaboration.ts:145) |
| 多人光标 / 在线成员 | ✅ 复核 | awareness setLocalStateField('user') + 'update' 遍历 getStates() 过滤自己 → DocumentHeader 头像列表(useCollaboration.ts:106-140、DocumentHeader/index.tsx:27-31,51-53) |
| 只读权限 | ✅ | 服务端经 Hocuspocus stateless {type:'server:permission', readOnly} 下发权威只读标志(优先于 HTTP VIEW/COMMENT 兜底);editor.setEditable(!isReadOnly, false) 动态切换不重建实例(useCollaboration.ts:78-92、page.tsx:85-89,156-165);认证失败 toast "已使用本地缓存打开";权限等级全集与 COMMENT 等级在所查快照的构建配置下的实际效果见 协同权限边界 注记 |
| 本地持久化 | ✅ 复核 | y-indexeddb,库名 tiptap-collaborative-<documentId>;注释明言"先加载本地快照以最小化 WS diff"(useCollaboration.ts:38,45-47) |
| 离线编辑 | ✅ 复核 | 未配置 WS_URL 时 provider=null 且直接标 isServerSynced=true,以 IndexedDB 数据离线打开;重连增量同步;冲突处理完全依赖 Yjs CRDT,仓库无自定义冲突代码(grep conflict 仅命中 AI 建议 mark 的 schema excludes 检测,useDocumentEdit.ts:65-82) |
| 历史版本(快照) | ✅ 复核 | useEditorHistory:自动快照默认 5 分钟、内容(state vector 哈希)变化才存、卸载时存;snapshotService 用 Y.snapshot/encodeSnapshot 存浏览器 IndexedDB 库 docflow-snapshots,每文档上限 50 份自动清理,恢复用 createDocFromSnapshot 重建后替换 content。纯本地实现,换设备无法取得本机历史快照(useEditorHistory.ts:155-204、services/snapshot/index.ts:11,94-130,149-194,247-254) |
| 快照 UI | ✅ | HistoryPanel:创建(带描述)/恢复/删除/清空,含四个确认对话框组件(docs/_components/HistoryPanel/index.tsx:41-100) |
| workflow 画布协同 | 🔶 | 第二处独立协同:Y.Map nodes/edges 同步 ReactFlow、awareness 画布光标;但 URL 硬编码 wss://flow.codecrack.cn、房间固定 'flow-room'、用户名/颜色随机、无 IndexedDB、awareness 靠 setTimeout(100ms) 延迟初始化——演示级(canvas.tsx:44-73,131-146,152-193、cursors-layer.tsx:20-53) |
| Hocuspocus 服务端 | ❌ | @hocuspocus/server 仅 package.json:52-53 声明(lockfile 实装 3.4.4),全仓库零 import;服务端在未开源的 api.codecrack.cn(基础设施调查) |
| y-webrtc(P2P) | ❌ | package.json 声明 ^10.3.0,零 import;实际传输层只有 HocuspocusProvider 的 WebSocket |
AI 能力
| 功能 | 程度 | 说明与证据 |
|---|---|---|
| 编辑器 Agent 智能编辑 | ✅ 复核 | AgentEditPanel:自然语言指令 + editor.getJSON() 全量文档 POST /api/v1/collaboration/agent/edit(timeout 120s),SSE 回 thinking/intent/anchor/proposal;按"理解意图→定位位置→生成内容"三步展示(services/collaboration/index.ts:89-130、useDocumentEdit.ts:281-284、page.tsx:36-39,324)。注:"LangGraph 三节点"系前端注释自述(index.ts:107),后端不在仓库无法从所查仓库直接核实 |
| diff 式提案落地 | ✅ 复核 | 4 种操作(append_to_doc/insert_after/insert_before/replace),insertContentAt 注入 agentSuggestion mark(added 绿/deleted 红),逐条/批量接受拒绝(useDocumentEdit.ts:99-174,392-440、AgentSuggestion/index.ts:69-182) |
| 独立 AI 聊天 | ✅ 复核 | /chat-ai:前端组装 system(默认一句话"你是一个专业、礼貌、高效的 AI 助理。",constants.ts:36)+ 全部历史 + 新消息,附 11 个采样参数(top_p/top_k/max_tokens/temperature/frequency_penalty/min_p/stop/n/enable_thinking/thinking_budget/enable_web_search)(useChat.ts:134-155) |
| 流式输出 | ✅ 复核 | OpenAI 兼容 chunk(delta.content + delta.reasoning_content 深度思考);自研 streamPost:fetch + getReader + TextDecoder 按行解析 data: JSON、跳过 [DONE](client.ts:1019-1238,1243-1279、type.ts:112-131);新会话 ID 从 Session-Id 响应头提取后 replaceState 换 URL(chat-ai/index.ts:220-227、chat-ai/[id]/page.tsx:31-38) |
| 模型配置 | ✅ 复核 | Model 列表从 /api/v1/chat/models 动态拉取;MaxTokens/Temperature/Top-P/Top-K/Frequency Penalty/Min-P/N/Stop/System Prompt 在 ConfigFields(ConfigFields.tsx:100-266);深度思考/联网搜索开关在聊天界面底部(ChatAIPanels.tsx:495-504,669,ConfigFields.tsx:7 注释自述移位);双模型并排对比(ModelConfigModal.tsx:108-142、ChatInterface.tsx:364-366)。请求不含任何用户 API Key——模型由后端统一代理 |
| 会话管理 | ✅ | 分页列表/加载更多/删除/改标题/详情 + 跨组件事件广播;新会话用 13 位时间戳临时 ID,初始消息经 sessionStorage 中转,后端返回真实 id 后 replaceState(useConversations.ts:105-215、chat-ai/[id]/page.tsx:46-62) |
| 流式渲染节流 | ✅ | 双重优化:useChat 5ms 缓冲批量 flush(useChat.ts:158-253);useDocumentEdit ref 累积 + setTimeout(0) 合并渲染(useDocumentEdit.ts:201-231) |
| 知识库 | ⚠️ | 仅管理 UI:创建/列表/删除/上传文件/添加 URL 到 /api/v1/ai/knowledge*(KnowledgeDocumentList.tsx:50,90,126、CreateKnowledgeDialog.tsx:91);且 AiApi 与 KnowledgeApi 重复封装同组端点、两个页面混用。计 ⚠️ 而非 ✅ 的原因:前端交付的只是管理子集,核心消费场景(检索问答)在前端零消费方(见下 RAG 行)——图例按前端接线范围计;外部后端依赖单独说明 |
| RAG 检索增强 | ❌ | 前端零实现:无 embedding/向量库/检索代码;带 useKnowledgeBase/knowledgeIds 的问答 API(AiApi.Question)无调用方;README.md:91 "续写用 RAG 检索相关段落"在前端无对应物(后端不在仓库,无法核实) |
| 编辑器内头脑风暴/润色/续写 | ❌ | README.md:15 重点宣传;服务层 ChatAiApi.Brainstorm/Polish/Autocomplete 与 AiApi.ContinueWriting 定义齐全但全库零调用方,仅遗留 styles/partials/ai-brainstorm.css:1-9 |
| 用户 API Key 设置 | 🚫 | 设置页 UI 可交互、能把 siliconflow/minimax 两个 provider 的 key 写入 localStorage 键 docflow_api_keys(api-key-settings.tsx:53-58,83-98);但无任何代码读取该键或把 key 附加到 AI 请求——"用自配 key 调 AI"的功能目标未达成,属无绑定的装饰 UI |
| AI 播客 | ✅ | 独立功能:上传简历(PDF/MD/DOC/DOCX)生成面试播客,3 种面试官人设 × 8 个 MiniMax TTS 音色(UploadResumeCard.tsx:58、services/podcast/index.ts:15-55) |
| 孤儿 AI 接口 | 🚫 | StreamAgent / TextToImage / GeneratePodcastAsync 等仅类型与方法定义,无 UI 消费;request.sse 通道仅被这三个无调用方的接口使用 |
| @azure/core-sse 的 sseStream | 🚫 | 依赖已装、client.ts:902-1008 实现完整,但全库无调用方——实际生效的流式只有 streamPost 一条通道 |
后端与基础设施(前端侧可见部分)
| 功能 | 程度 | 说明与证据 |
|---|---|---|
| 业务后端 | ❌ | 仓库内不存在(仓库外依赖 证据链);真实后端为未开源 api.codecrack.cn |
| Next.js 服务端能力 | ✅ 复核 | 几乎为零:src/app/api 仅 health 一个静态 route;AGENTS.md:15 明文 "no API routes";README.md:25 声称的 Server Actions 不属实——全仓库无一处 'use server' |
| 路由守卫 | ✅ | src/proxy.ts:113-130 按 auth_token cookie 做存在性/过期的服务端守卫与重定向,matcher 仅覆盖 /docs、/dashboard、/chat-ai、/rooms;与 WS 协同无关 |
| 登录鉴权 | ✅ 复核 | GitHub OAuth(跳 ${SERVER_URL}/api/v1/auth/github + code 回调)+ 邮箱验证码/密码双通道;token 存 auth_token/refresh_token cookie;401 时自动用 refresh_token + X-CSRF-Token 调 /api/v1/auth/refresh 并重放排队请求(client.ts:131-205,244-253,285-289);WS 连接携带 token: getAuthToken()(useCollaboration.ts:74) |
| 文件上传 | ✅ 复核 | 完整分块协议,全走外部 API:check-file 哈希秒传 / chunk-info 断点续传 / chunk 分块 / complete-file 合并 / avatar 直传(services/upload/index.ts:16,21-31,38-50,64-101,122-150,187-206);视频转写下载 /api/v1/video/*、图片元数据 /api/v1/image/:hash |
| 匿名行为分析 | ✅ | fingerprintjs 生成 visitorId + UA 解析(OS/浏览器/分辨率/时区)+ 页面停留心跳,sendBeacon 上报 /api/v1/trace(useAnalysis.ts:2,20,99,114-133,171-175);与鉴权无关 |
| Sentry | ✅ | 三端接入:client 硬编码 DSN + replay + 采样 1(instrumentation-client.ts:7-29)、server 仅生产采样 0.1(sentry.server.config.ts:7-29)、edge config + onRequestError(instrumentation.ts:13、next.config.ts:83-88);请求层逐步 addBreadcrumb/captureException |
| Datadog | 🚫 | browser-logs 仅在 LiveKit 调试组件(rooms/_components/Debug.tsx:16-43),需 NEXT_PUBLIC_DATADOG_CLIENT_TOKEN/SITE 两个未配置的 env;且该 Debug 组件本身已被注释出页面(PageClientImpl.tsx:28,371)——代码不可达 |
| Prometheus/Grafana、ELK、MinIO、RabbitMQ | ❌ | 仅 README 文案(README.md:45-63),无代码/依赖/编排;logstash 一词连 README 都未出现 |
导入导出与周边功能
| 功能 | 程度 | 说明与证据 |
|---|---|---|
| Markdown 粘贴导入 | ✅ 复核 | 手写实现(非官方 @tiptap/markdown):micromark-gfm + mdast → PM 节点;且两处高度重复的实现(Markdown 转换) |
| Markdown 复制序列化 | ✅ 复核 | MarkdownPaste 重写 clipboardTextSerializer 反向输出 Markdown(MarkdownPaste.ts:38-40,395-611) |
| JSON 整文粘贴/复制 | ✅ | copy 时写 text/json + docflow-reference(page.tsx:168-204);JsonPaste 监听 text/json 整体替换文档(JsonPaste.ts:12-34) |
| Word(.docx)导出 | ✅ 复核 | tiptap-extension-export-docx 的源码 fork,18 个文件(17 converter + index)、convertNode 14 个 case,基于 docx@9.5.1;imageBlock 先归一化为 image;浏览器端 Blob 下载(Word 导出) |
| PDF 导出 | ✅ 复核 | @zumer/snapdom DOM→canvas(过滤 BUTTON、jpeg 0.95)+ jspdf 按 A4 分页,60s 超时(document-export/pdf.ts:42,51-89) |
| 复制到公众号 | ✅ 复核 | juice CSS 内联后以 ClipboardItem 写 text/html + text/plain(document-actions.tsx:60-101) |
| 导出入口 | ✅ 复核 | 文档页"操作"菜单:复制到公众号/导出PDF/导出Word/发布到博客(document-actions.tsx:143,165-177) |
| Word 导入 | ❌ | mammoth 在 package.json:156 声明(lockfile 实解析 1.11.0),源代码零引用;知识库/播客上传仅把 .doc/.docx FormData 发给后端 |
| xlsx 导出 | ❌ | xlsx@0.18.5 仅用于文件图标颜色映射(utils/format/file-type.ts:19) |
| 导出图片 | ❌ | 无此功能(仅有复制图片 blob 与 TableImage 缩略节点) |
| 博客发布/展示 | ✅ | 公开博客:ISR(revalidate=3600)+ generateStaticParams(前 50 篇)+ OG/twitter/canonical;服务端拉数据 dangerouslySetInnerHTML 渲染;编辑器 getHTML 经 zod 校验后发布(blog/page.tsx:14,30、blog/[id]/page.tsx:161、use-blog-publish.ts:27-60) |
| LiveKit 音视频 | ✅ 复核 | /rooms 完整度较高:后端 quick-create/join 签发 token → base64 进 URL ?c= 跳转;房间页 384 行实现 Room 配置(adaptiveStream/dynacast/singlePeerConnection/simulcast)、E2EE(worker + URL hash 密码)、GridLayout、头像 overlay、自动开摄像头麦克风、屏幕共享、录制指示、低功耗优化(周边交付链路)。但四个子项为 🚫 死代码:<Chat/> 被注释(PageClientImpl.tsx:369,仅剩 ChatToggle)、SettingsMenu/CameraSettings(BackgroundBlur)/MicrophoneSettings(useKrispNoiseFilter)未被任何页面渲染——krisp 降噪、背景虚化、设备菜单所查快照中未接线 |
| dashboard/contacts | ⚠️ | 列表渲染硬编码 MOCK_CONTACTS 假数据(ContactMemberList.tsx:8,28),但添加好友/好友请求走真实 API(AddContactDialog.tsx:18) |
| dashboard 其他 | ⚠️ | knowledge/podcast/workflow(ReactFlow)/organizations 各有页面;messages 是 ComingSoonPage 占位(dashboard/messages/page.tsx:1-8) |
| 深色模式 | 🚫 复核 | tailwind 配置 + 约 50 个文件写 dark: 类 + useDarkMode hook 实现完整,但全仓库无任何组件引用该 hook、root layout 无主题初始化脚本;设置页"暗色主题"复选框(无绑定,settings/page.tsx:73-82)与 SettingsTab 主题下拉(本地 state,SettingsTab.tsx:19-35)都是不生效的装饰 UI |
| 国际化 | ❌ | 无 i18n 依赖与 useTranslation,UI 文案硬编码中文;tinykeys 仅注册在被注释的 Debug 面板(Shift+D),实际未启用 |
工程化
| 项 | 程度 | 说明与证据 |
|---|---|---|
| Monorepo 任务编排 | ✅ | turbo.json:5-49 定义 build/dev/type-check/lint/test/clean;transformer 依赖 alert+bilibili 先构建 |
| Git hooks | ⚠️ | husky 仅 pre-commit(跑 pnpm lint/format:ci/type-check);commitlint 配置存在但无 commit-msg hook 强制 |
| 测试 | ❌ | 全仓库无 .spec/.test 文件、无 playwright.config、无 test script;vitest/@vitest/* 与 @playwright/test 均为未使用的 devDependencies(package.json:224-229) |
| Docker | ✅ | 四阶段构建(node:24-alpine、corepack pnpm@10.28.2、npmmirror 源、非 root、HEALTHCHECK 打 /api/health)、CMD next start(Dockerfile:1-91,107) |
| docker-compose | ✅ | 仅 1 个前端服务 ghcr.io/xun082/docflow:latest,无数据库/后端/监控编排 |
| CI | ✅ | build(推多 arch 镜像)/ deploy(定时 SSH 拉镜像)/ lint / preview 四个 workflow |
最后更新于