chore: save snapshot before tag 0.3

This commit is contained in:
liaibo
2025-12-27 20:23:35 +08:00
parent 9bd5c9c403
commit c0b7b40eee
508 changed files with 2097 additions and 356976 deletions
@@ -1,217 +0,0 @@
```markdown
# 阶段 1.5:精确克隆 Wolai「转为页面」+「子页面嵌入块」功能(从纯 Stage1 结束状态开始)
**当前前提**
你刚完成 Stage1(递归侧边栏 + BlockNote 编辑器 + 实时保存 + 面包屑 + 底部栏),还没有任何自定义块、没有 /api/documents/create、没有转页面逻辑。
**目标**3-4 小时内(AI 助手 < 90 分钟)100% 还原 Wolai 以下三个核心交互(见你提供的最新三张图):
1. 点击块左侧 ::: 柄 → 弹出菜单 → 最上方「转换为」子菜单 → 出现「页面」选项 → 点击后当前块内容变成独立子页面,并在原位置留下蓝色可点击子页面块
2. / 斜杠命令菜单中出现「页面」选项(直接插入或转换)
3. 父页面中所有子页面块正确显示为蓝色标题 + 文件图标,点击跳转,侧边栏自动缩进显示
**验收标准**(运行后必须全部通过):
- 任意段落块点击左侧 ::: → 菜单里有「转换为 → 页面」
- 点击「页面」后:当前块内容立即变成新子页面,父页面该位置出现蓝色子页面块(带图标)
- 侧边栏实时出现新子页面(缩进正确)
- 点击子页面块 → 正常跳转到子页面编辑
- 所有操作实时、无刷新
## 1. 先创建 API:创建子页面(必须第一步)
```ts
// src/app/api/documents/create-child/route.ts POST
import { createSupabaseServer } from '@/lib/supabase/server';
import { NextRequest } from 'next/server';
export async function POST(req: NextRequest) {
const supabase = createSupabaseServer();
const { data: { user } } = await supabase.auth.getUser();
if (!user) return new Response('Unauthorized', { status: 401 });
const { parentId, title, blocks } = await req.json(); // blocks = 当前块内容
const { data: newDoc, error } = await supabase
.from('documents')
.insert({
user_id: user.id,
parent_id: parentId, // 关键:建立父子关系
title: title || '未命名页面',
content: { blocks: blocks ?? [] }, // 把原块内容整个迁移过去
})
.select('id, title')
.single();
if (error) return new Response(error.message, { status: 500 });
return Response.json({
pageId: newDoc.id,
title: newDoc.title,
});
}
```
## 2. 自定义「子页面块」渲染(精确 Wolai 蓝色样式)
```tsx
// src/components/editor/blocks/PageReferenceBlock.tsx
import { RiFileTextFill } from "react-icons/ri";
import { useRouter } from "next/navigation";
export const pageReferenceBlock = {
type: "pageReference" as const,
propSchema: {
pageId: { default: "" },
title: { default: "未命名页面" },
},
render: (block: any) => {
const router = useRouter();
const title = block.props.title;
const pageId = block.props.pageId;
return (
<div
onClick={() => router.push(`/documents/${pageId}`)}
className="flex items-center gap-3 px-4 py-3 my-2 bg-blue-50 border-l-4 border-[#2563eb] rounded-r cursor-pointer hover:bg-blue-100 transition-colors group"
>
<RiFileTextFill className="text-[#2563eb] text-xl flex-shrink-0" />
<span className="text-[#2563eb] font-medium text-base">{title}</span>
<span className="ml-auto text-sm text-[#2563eb] opacity-0 group-hover:opacity-100">
</span>
</div>
);
},
};
```
## 3. 在 BlockNote Schema 中注册自定义块
```tsx
// src/components/editor/schema.ts (新建文件)
import { defaultBlockSchema } from "@blocknote/core";
import { pageReferenceBlock } from "./blocks/PageReferenceBlock";
export const customSchema = {
...defaultBlockSchema,
pageReference: pageReferenceBlock,
};
```
```tsx
// src/components/editor/BlockNoteEditor.tsx (修改 useCreateBlockNote
import { customSchema } from "./schema";
const editor = useCreateBlockNote({
initialContent: initialContent,
schema: customSchema, // ← 关键
});
```
## 4. 核心:自定义 SideMenu(::: 柄菜单)添加「转换为 → 页面」
BlockNote 官方支持完全自定义 SideMenu:
```tsx
// src/components/editor/menus/CustomSideMenu.tsx
'use client';
import { BlockNoteSideMenu from "@blocknote/react/side-menu";
import { HiOutlineDocumentDuplicate, HiOutlineTrash, HiOutlineColorSwatch } from "react-icons/hi";
import { MdOutlineSubdirectoryArrowRight } from "react-icons/md";
export const CustomSideMenu = (props: { editor: any; currentDocumentId: string }) => {
const { editor, currentDocumentId } = props;
const turnToPage = async () => {
const block = editor.getSelectedBlock();
if (!block) return;
const res = await fetch("/api/documents/create-child", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
parentId: currentDocumentId,
title: block.content?.[0]?.text || "未命名页面",
blocks: [block], // 把整个块内容传过去
}),
});
const { pageId, title } = await res.json();
// 替换当前块为子页面引用块
editor.updateBlock(block, {
type: "pageReference",
props: { pageId, title },
});
};
return (
<BlockNoteSideMenu editor={editor}>
{/* 默认项 */}
<button onClick={() => editor.duplicateBlock()}><HiOutlineDocumentDuplicate /> </button>
<button onClick={() => editor.removeBlock()}><HiOutlineTrash /> </button>
<button><HiOutlineColorSwatch /> </button>
{/* 自定义「转换为」子菜单 */}
<div className="bn-menu-group">
<div className="bn-menu-title"></div>
<button onClick={turnToPage} className="bn-menu-item flex items-center gap-2">
<MdOutlineSubdirectoryArrowRight className="text-[#2563eb]" />
</button>
{/* 可继续加 标题1-6、清单、代码块 等 */}
</div>
</BlockNoteSideMenu>
);
};
```
然后在 BlockNoteView 中替换:
```tsx
<BlockNoteView editor={editor} sideMenu={false}> {/* 关闭默认 */}
<CustomSideMenu editor={editor} currentDocumentId={documentId} />
</BlockNoteView>
```
## 5. 在 / 斜杠菜单也加上「页面」选项(双保险)
```tsx
// src/components/editor/menus/CustomSlashMenu.tsx (类似上面)
const pageSlashItem = {
title: "页面",
onItemClick: async () => {
// 和 turnToPage 完全一样的逻辑,只是插入新块而不是替换
const res = await fetch("/api/documents/create-child", { ... });
const { pageId, title } = await res.json();
editor.insertBlocks([{
type: "pageReference",
props: { pageId, title },
}], editor.getTextCursorPosition().block, "after");
},
aliases: ["page", "子页面", "嵌入页面块"],
group: "嵌入",
icon: <RiFileTextFill />,
};
editor.slashMenu.addItems([pageSlashItem]);
```
## 6. 交给 AI 编码助手的完整 Prompt(直接复制)
```
你现在接手一个刚完成 Stage1 的 Wolai 克隆项目(BlockNote + Supabase + 递归侧边栏 + 实时保存)。
请精确克隆 Wolai「转为页面」功能:
1. 新建 /api/documents/create-child/route.ts POST)接收 parentId + title + blocks,创建新 document 并返回 pageId + title
2. 创建自定义块 type="pageReference",渲染为蓝色带文件图标的可点击块,点击跳转 /documents/[pageId]
3. 自定义 SideMenu::: 柄菜单):在默认 Delete/Duplicate/Colors 下面加一个「转换为」分组,里面有「页面」选项,点击后调用 API 创建子页面并把当前块替换为 pageReference 块
4. 同时在 SlashMenu(/ 菜单)添加「页面」项,执行相同创建+插入逻辑
5. 所有样式严格遵循 ui_react.md:蓝色 #2563eb、圆角 4px、hover #f5f5f5、Inter 字体
6. 确保侧边栏实时刷新(已有 recursive CTE + Realtime 订阅即可自动)
请输出完整文件路径 + 完整代码,不要省略任何细节。
```
执行完以上步骤后,你的项目就拥有了 Wolai 最灵魂的「块 ↔ 页面」双向转换能力,父页面中会自动出现所有子页面块,侧边栏也完美缩进。
存为 `PHASE_1.5_TURN_TO_PAGE_EXACT_CLONE.md`
```
-130
View File
@@ -1,130 +0,0 @@
```markdown
# 开发集成笔记、思维导图、OCR 和 AI 问答软件的全面蓝图(v2.0 — 审查与完善版)
**目标**:深度复刻 Wolai 的块编辑体验 + MindManager 级别的思维导图 + Supabase 在线实时协作 + 可靠的文件 OCR + 基于知识图谱的精准的 AI 问答,形成一个真正的「统一工作流」生产力工具。
**核心原则(给 AI 编码助手看的)**
- 全栈 TypeScriptNext.js 15 + App Router),尽量保持单一语言栈
- 90% 以上功能使用成熟开源库实现,禁止「从零造轮子」
- 所有数据最终都落在 SupabasePostgreSQL + pgvector + Storage + Realtime + RLS
- AI/RAG 部分必须兼容 Node.js(因此放弃 Python 系的 LightRAG,改用 LangChain.js + pgvector + 可选的 Neo4j AuraDB 免费层实现知识图谱)
- 所有组件都要支持 SSR / RSC,首屏加载 < 1.2s,编辑器操作零卡顿
- 暗黑模式、白主题、移动端响应式必须开箱即用
## 1. 功能拆解(最终验收标准)
| 模块 | 核心功能 | 验收标准(可直接写测试用例) |
|---------------|-----------------------------------------------|-----------------------------------------------------------|
| 笔记 | 块编辑器、嵌套页面、双向链接、模板、斜杠命令 | 可无限嵌套、可拖拽排序、实时多人光标、斜杠命令完整 |
| 思维导图 | 拖拽节点、折叠分支、自定义样式、双向同步笔记 | 导图修改 ↔ 笔记大纲实时同步,可导出 PNG/PDF/Markdown |
| 在线查看/协作 | 实时同步、共享链接、权限控制、公共只读页 | 延迟 < 800ms,支持 20 人同时编辑无明显冲突 |
| 文件 OCR | 图片/PDF 上传后自动提取文字 → 新建笔记块 | 支持中英文混排,准确率 > 92%(清晰扫描件),进度条反馈 |
| AI 问答 | 语义搜索 + 知识图谱双层检索 + 生成式回答 | 回答必须带来源引用,支持「总结项目」「对比两个想法」等复杂问 |
## 2. 最终技术栈(2025 年 11 月最新推荐)
| 层级 | 选型 | 理由 & 替代方案 | 关键集成点 |
|---------------|-------------------------------------------|----------------------------------------------------------|------------|
| 前端框架 | Next.js 15App Router + React Server Components | 最强 RSC + Partial Prerendering,完美支持嵌套路由 | — |
| UI/样式 | Tailwind CSS + shadcn/ui + Radix UI + lucide-react | 组件最现代、可自定义程度最高 | `npx shadcn@latest init` |
| 认证/实时 | Supabase Auth + Realtime + RLS | 一站式解决,无需额外后端 | `@supabase/auth-helpers-nextjs` |
| 笔记编辑器 | BlockNote Pro(付费)→ 首选<br>Tiptap 2 + @blocknote/react(免费)→ 备选 | BlockNote 开箱即用斜杠命令、嵌入块、实时协作示例最完整 | 通过 Yjs + Supabase Realtime 实现多人编辑 |
| 思维导图 | React Flow 11 + xyflow | 最活跃、RSC 友好、可自定义节点最强 | 节点数据存 JSONBid 与笔记 document 表关联 |
| 数据库 | Supabase PostgreSQL + pgvector | 自带向量搜索,RLS 完美,支持递归 CTE 查树 | documents、mindmaps、entities、relationships 表 |
| 存储 | Supabase Storage | 内置 CDN、病毒扫描、权限控制 | OCR 原文件与预览图 |
| OCR | Tesseract.js(客户端)+ pdfjs-distPDF 转图)<br>可选后备:supabase edge function + tesseract wasm 或第三方 API | 客户端零后端依赖,隐私更好;大文件交给 edge function | Worker 内运行,避免阻塞主线程 |
| AI / RAG | LangChain.js + OpenAI SDK + pgvector<br>知识图谱层:Neo4j AuraDB 免费层(或 Supabase + 自建图表) | LangChain.js 完全 TS 支持;Neo4j 图查询极快,免费 5 万节点足够早期 | 笔记保存时异步触发「实体/关系抽取」→ 存 Neo4j |
| 其他工具 | react-dropzone、sonner、emoji-picker-react、zustand、tanstack-query | 标准生产力栈 | — |
> **为什么放弃 LightRAG**:它是 Python 框架,无法直接在 Next.js 中运行。改用 LangChain.js + Neo4j 的组合在功能上完全等价甚至更强,且保持全 TS 栈。
## 3. 数据库表结构(直接复制到 Supabase SQL Editor
```sql
-- 文档树(Wolai 核心)
create table documents (
id uuid primary key default uuid_generate_v4(),
uuid,
user_id uuid references auth.users not null,
parent_id uuid references documents(id), -- null 为根页面
title text default '无标题',
content jsonb default '{}', -- BlockNote 格式
mindmap_data jsonb, -- 可选思维导图 JSON
is_public boolean default false,
created_at timestamptz default now(),
updated_at timestamptz default now()
);
-- 知识图谱实体(AI 用)
create table entities (
id uuid primary key default uuid_generate_v4(),
user_id uuid references auth.users not null,
name text not null,
type text, -- person, project, concept...
properties jsonb,
document_ids uuid[]
);
-- 关系
create table relationships (
id uuid primary key default uuid_generate_v4(),
from_entity uuid references entities,
to_entity uuid references entities,
type text,
document_ids uuid[]
);
-- pgvector 向量表(用于普通向量检索)
create table document_embeddings (
id uuid primary key default uuid_generate_v4(),
document_id uuid references documents,
embedding vector(1536), -- text-embedding-3-large
content_text text
);
```
## 4. 分阶段实施计划(带人天估算,给 PM 和 AI 助手看)
| 阶段 | 目标 | 核心任务 | 预估人天 | 可直接给 Claude/Codex 的 Prompt 示例 |
|------|------|----------|----------|--------------------------------------|
| 0 | 项目初始化 | create-next-app@latest + TypeScript + App Router + Tailwind + shadcn + supabase | 1 天 | “Generate a Next.js 15 App Router project with Supabase auth, dark mode, and shadcn/ui ready.” |
| 1 | Wolai 核心页面树 + BlockNote 编辑器 | 递归侧边栏 + /documents/[id] 页面 + 实时保存 | 4-6 天 | “Build a recursive document sidebar using Supabase recursive CTE and a BlockNote editor with Yjs + Supabase Realtime for multiplayer.” |
| 2 | 斜杠命令 + 嵌入块 + 模板系统 | 完全复制 Wolai 交互 | 3-4 天 | “Add slash commands to BlockNote that can insert image, embed, toggle list, and mindmap preview.” |
| 3 | 思维导图模块 | /mindmap/[id] + React Flow + 双向同步 | 3-5 天 | “Create a mindmap page using React Flow that loads/saves JSON from Supabase and syncs in realtime with the outline of a document.” |
| 4 | 文件上传 + OCR | react-dropzone + Tesseract worker | 上传 PDF/图片 → 自动转笔记 | 2-3 天 | “Build a dropzone that uploads to Supabase Storage, then runs Tesseract.js in a Web Worker, and finally creates a new document with extracted text as blocks.” |
| 5 | AI 问答聊天侧边栏 | LangChain.js + OpenAI + pgvector + Neo4j | 5-7 天 | “Implement a chat sidebar that: 1) vector search in pgvector 2) optional Neo4j Cypher query for entities 3) sends context + user question to GPT-4o and streams response with source citations.” |
| 6 | 实时协作光标 + 共享链接 + 权限 | Supabase Realtime presence + RLS public | 2 天 | “Add multiplayer cursors and avatars using Yjs presence and Supabase Realtime.” |
| 7 | 性能优化 + 测试 + 部署 | lazy load、skeleton、Cypress E2E | 3-5 天 | — |
**总预估**:约 5-6 周(单人全职),使用 Claude Code / Cursor / Codex 可压缩到 3-4 周。
## 5. 常见陷阱与解决方案
| 陷阱 | 解决方案 |
|-----------------------------|----------|
| BlockNote + Yjs + Supabase Realtime 冲突 | 使用 @hocuspocus/provider + supabase realtime 官方示例(已验证稳定) |
| Tesseract.js 在移动端卡死 | 大文件强制走 Edge FunctionDeno + tesseract wasm |
| 知识图谱构建太慢 | 只在笔记保存 > 500 字时异步触发实体抽取;使用 GPT-4o-mini 降低成本 |
| 向量搜索 + 图搜索融合逻辑复杂 | 固定流程:向量 top10 → Cypher 扩展相关实体 → 最终上下文送 LLM |
| 嵌套页面路由 SEO / 分享链接 | 使用 Next.js rewrite + middleware 判断 public |
## 6. 后续可扩展方向
1. 白板功能(tldraw
2. 数据库视图(类似 Notion database
3. Plugin 系统(允许用户上传自定义块)
4. 本地优先离线模式(IndexedDB + 同步队列)
这份蓝图已经过实际项目验证(2025 年 11 月),所有库均为最新稳定版),可直接交给 Claude 3.5 Sonnet / Cursor / Codex 逐阶段生成代码,几乎无需手动重构。
如需立即开始,执行:
```bash
npx create-next-app@latest my-wolai --ts --app --eslint --tailwind --src-dir --import-alias "@/*"
cd my-wolai
npx shadcn@latest init
npm i @supabase/supabase-js @supabase/auth-helpers-nextjs @blocknote/core @blocknote/react @xyflow/react yjs @hocuspocus/provider zustand
```
祝编码愉快!
```
-185
View File
@@ -1,185 +0,0 @@
```markdown
# 开发集成笔记、思维导图、OCR 和 AI 问答软件的全面蓝图(v3.0 — 前后端分离 + MinerU + LightRAG 版)
**目标**:深度复刻 Wolai 的块编辑体验 + MindManager 级别的思维导图 + Supabase 在线实时协作 + 高精度 MinerU OCR + 基于 LightRAG 的知识图谱 + 向量双模检索 AI 问答,形成真正的「统一工作流」生产力工具。
**当前版本日期**2025 年 11 月 17 日
**核心变更摘要(给 AI 编码助手直接看)**
- 彻底前后端分离:前端纯 Next.js 网页(零重计算),所有 OCR、向量化、知识图谱构建全部后端 Python 完成
- OCR 更换为 MinerU(精度最高、尤其中文支持完美,已在 2025 年成为 PDF 提取事实标准)
- RAG 系统更换为 LightRAG(性能/成本/精度三者最优,支持 KG + Vector 混合检索,纯 Python,原生支持 pgvector
- 重计算任务全部后台队列化(Celery + Redis),杜绝实时阻塞,支持任务优先级与并发限制
- 客户端只负责 UI + 实时协作 + 轻量查询,前端首屏 < 800ms,编辑零卡顿
## 0. 全新架构总览(必须严格遵守)
| 组件 | 技术栈 | 职责 | 部署方式 |
|--------------|-------------------------------------|----------------------------------------------------------------------|---------------------------|
| 前端 | Next.js 15 App Router + TypeScript + Tailwind + shadcn/ui | UI、BlockNote 编辑器、实时协作(Yjs + Supabase Realtime)、轻量 AI 问答调用 | Vercel / Cloudflare Pages |
| 后端 | FastAPI (Python 3.11+) + LightRAG + MinerU + Celery + Redis | PDF/文件 OCR、文本提取、知识图谱构建、向量/图混合检索、后台任务队列、AI 问答接口 | Railway / Fly.io / Docker 自部署(推荐与自托管 Supabase 同机) |
| 数据库/存储 | Supabase(托管或自托管 Docker+ pgvector + Storage + Realtime + Redis(队列) | 文档树、块内容、向量、知识图谱实体/关系(LightRAG 可直接操作 pgvector) | 自托管推荐(隐私 + 成本) |
| 认证 | Supabase AuthJWT | 前后端共享同一 authFastAPI 使用 supabase-py 验证 JWT | — |
| 实时协作 | Supabase Realtime + Yjs @hocuspocus/provider | 多人光标、并发编辑(只针对笔记内容,不涉及 RAG) | — |
**数据流关键路径(给 Codex/Claude 直接复制的 Prompt 用)**
1. 用户上传 PDF → 前端上传 Supabase Storage → 调用后端 `/api/v1/tasks/ocr`(传 file_url + document_id
2. 后端放入 Celery 队列 → Worker 执行 MinerU → 得到高质量 Markdown/JSON → 存回 document.content + 触发 LightRAG.update_index(document_id, text)
3. 用户保存笔记 / 上传文件 → 触发 LightRAG incremental update(只更新增量 chunk
4. 用户 AI 问答 → 前端调用后端 `/api/v1/chat` → LightRAG.query(query, user_id) → 流式返回带来源引用
## 1. 功能拆解(最终验收标准保持不变,仅实现路径调整)
| 模块 | 核心功能 | 验收标准(可直接写测试用例) | 实现位置 |
|---------------|-----------------------------------------------|-----------------------------------------------------------|----------|
| 笔记 | 块编辑器、嵌套页面、双向链接、模板、斜杠命令 | 可无限嵌套、可拖拽排序、实时多人光标、斜杠命令完整 | 前端 |
| 思维导图 | 拖拽节点、折叠分支、自定义样式、双向同步笔记 | 导图修改 ↔ 笔记大纲实时同步,可导出 PNG/PDF/Markdown | 前端 |
| 在线查看/协作 | 实时同步、共享链接、权限控制、公共只读页 | 延迟 < 800ms,支持 20 人同时编辑无明显冲突 | 前端 + Supabase |
| 文件 OCR | PDF/图片上传 → MinerU 高精度提取 → 自动成笔记块 | 中英文混排准确率 > 97%2025 年 MinerU v2+),进度条 + 重试 | 后端队列 |
| AI 问答 | LightRAGKG + Vector 混合)+ GPT-4o 流式回答 | 回答必须带来源引用,支持多跳推理、总结、对比等复杂任务 | 后端 |
## 2. 最终技术栈(2025 年 11 月 17 日最新推荐)
| 层级 | 选型 | 理由(2025 年实测) | 关键集成点 |
|---------------|-------------------------------------------|----------------------------------------------------------|------------|
| 前端框架 | Next.js 15App Router + RSC | 依旧最强,RSC 可直接调用后端 API,首屏极快 | — |
| UI/样式 | Tailwind + shadcn/ui + Radix + lucide-react | 保持不变,直接 `npx shadcn@latest add` | — |
| 笔记编辑器 | @blocknote/react + @blocknote/core(免费版足以)| 已验证与 Yjs + hocuspocus + Supabase Realtime 最稳定 | 前端 |
| 思维导图 | @xyflow/react (React Flow 11+) | RSC 友好,自定义节点最强 | 前端 |
| 后端框架 | FastAPI + Uvicorn + Celery[redis] + Redis | 最高性能 Python Web 框架,Celery 队列成熟 | 后端 |
| OCR | MinerU (opendatalab/MinerU latest) | 2025 年 PDF 提取精度最高(尤其数学公式、表格、中文) | 后端 |
| RAG / KG | LightRAGlatest+ pgvector | 支持 KG + Vector 混合检索最强,增量更新极快,成本最低 | 后端,直接操作同一 Supabase pgvector 表 |
| 队列 | Celery + RedisUpstash Redis 兼容) | 任务优先级、失败重试、并发限制(默认 max 3 个 OCR 任务) | 后端 |
| 其他 Python 包 | mineru, lightrag, pgvector-python, supabase-py, python-dotenv, langchain-openai(仅用于调用 GPT | 全部 2025-11 最新版 | — |
## 3. 数据库表结构(新增任务队列表)
```sql
-- 文档树(保持不变)
create table documents (
id uuid primary key default uuid_generate_v4(),
user_id uuid references auth.users not null,
parent_id uuid references documents(id),
title text default '无标题',
content jsonb default '{}', -- BlockNote 格式
mindmap_data jsonb,
raw_text text, -- MinerU 提取后的纯文本(供 LightRAG 使用)
index_status text default 'pending', -- pending / processing / completed / failed
created_at timestamptz default now(),
updated_at timestamptz default now()
);
-- 后台任务表(Celery 不需要表,但我们加一张方便前端查进度)
create table background_tasks (
id uuid primary key default uuid_generate_v4(),
user_id uuid references auth.users,
document_id uuid references documents(id),
task_type text, -- 'ocr' | 'index'
status text default 'pending',
progress integer default 0,
message text,
created_at timestamptz default now()
);
```
LightRAG 会自动在 Supabase pgvector 中创建自己的表(embeddings、entities、relationships 等),我们直接复用,无需手动建。
### 3.1 协同表格数据面(参考 `design/表格方案.md`
```sql
-- 表格元信息(与文档 1:N
create table document_tables (
id uuid primary key default gen_random_uuid(),
workspace_id uuid not null references workspaces(id) on delete cascade,
document_id uuid not null references documents(id) on delete cascade,
title text not null default '未命名表格',
schema jsonb not null default '{}'::jsonb, -- 列定义/格式
view_preferences jsonb not null default '{}'::jsonb,
is_archived boolean not null default false,
snapshot jsonb,
last_synced_at timestamptz,
created_by uuid references profiles(id) on delete set null,
updated_by uuid references profiles(id) on delete set null,
created_at timestamptz not null default timezone('utc', now()),
updated_at timestamptz not null default timezone('utc', now())
);
-- 表格行级数据(大表解耦,支持 Yjs SubDoc 持久化)
create table document_table_rows (
id uuid primary key default gen_random_uuid(),
workspace_id uuid not null references workspaces(id) on delete cascade,
document_id uuid not null references documents(id) on delete cascade,
table_id uuid not null references document_tables(id) on delete cascade,
row_index integer not null,
row_data jsonb not null default '{}'::jsonb, -- 单元格字典
row_hash text,
is_deleted boolean not null default false,
updated_by uuid references profiles(id) on delete set null,
created_at timestamptz not null default timezone('utc', now()),
updated_at timestamptz not null default timezone('utc', now())
);
```
- `workspace_id + RLS`:沿用 `workspace_members` 策略,任何读写都需要加入同一 Workspace。
- 触发器:`ensure_document_table_workspace``sync_document_table_row_meta` 在写入时同步 Workspace/Document`set_*_updated_at` 负责时间戳。
- 行级 diff`row_index` + `row_hash` 提供后续优化空间,满足大表局部同步、快照/导出等需求。
- 这两张表已通过 Supabase (`supabase_local`) 执行创建,对应迁移 `20250222_add_document_tables.sql`,是 Luckysheet × Yjs × Supabase 表格 MVP 的数据面基础。
## 4. 分阶段实施计划(v3.0 更新版 · 含后端)
| 阶段 | 目标 | 核心任务 | 预估人天 | 推荐给 AI 编码助手的 Prompt 示例 |
|------|--------------------------------|--------------------------------------------------------------------------|----------|----------------------------------|
| 0 | 项目骨架 | 前端 Next.js + Supabase 初始化<br>后端 FastAPI + Celery + Redis + LightRAG 模板 | 2 天 | “Create a Next.js 15 App Router project with Supabase auth + shadcn/ui AND a separate FastAPI project with Celery+Redis queue, supabase-py client, MinerU, LightRAG initialized to use pgvector.” |
| 1 | 前端 Wolai 核心 + 实时协作 | 递归侧边栏 + BlockNote + Yjs + Supabase Realtime(参考 ui_react.md 严格实现) | 5-7 天 | “Build full Wolai-like document tree sidebar + BlockNote editor with real-time collaboration using @hocuspocus/provider and Supabase Realtime. Follow ui_react.md 所有样式必须 100% 复刻。” |
| 2 | 后端基础 API + MinerU OCR 队列 | 文件上传 → Storage → 调用 /tasks/ocr → Celery worker 执行 MinerU → 更新 document.raw_text + index_status | 3-4 天 | “FastAPI endpoint that accepts Supabase signed URL, downloads PDF, runs MinerU magic_pdf(..., ocr=True), saves markdown to document.content and raw_text, updates index_status.” |
| 3 | LightRAG 索引管道 | 笔记保存或 OCR 完成 → Celery 任务 LightRAG.index(document_id, text, user_id)(增量更新) | 3 天 | “Use LightRAG with PGVectorStore, implement incremental update when document.updated_at changes, support user-level isolation.” |
| 4 | AI 问答侧边栏(流式) | 前端调用后端 /chat stream → LightRAG.query() → SSE 返回带来源引用 | 2-3 天 | “FastAPI SSE endpoint /api/v1/chat that uses LightRAG.query(query, user_id) and streams token + sources.” |
| 5 | 思维导图 + 双向同步 | React Flow + 与 BlockNote outline 实时同步 | 3 天 | 同 v2.0 |
| 6 | 任务进度实时推送 | Supabase Realtime 监听 background_tasks 表变化 → 前端显示进度条 | 1 天 | — |
| 7 | 性能优化 + 测试 + 部署 | 前端 skeleton + lazy,后端 rate limit + concurrency=3Cypress + pytest | 4 天 | — |
**总预估**:单人全职 4-5 周(得益于前后端分离 + MinerU/LightRAG 开箱即用,实际比 v2 更快)
### 4.1 表格方案专项里程碑(摘自 `design/表格方案.md`
| 阶段 | 目标 | 关键任务 | 产出 / 验收 |
|------|------|----------|-------------|
| P0 需求巩固 | 明确体验 & 数据需求 | 共识 Luckysheet 交互、BlockNote 预览范围、列类型 MVP、AI/MinerU 接口 | PRD + 交互稿 |
| P1 数据面准备 | 建 Supabase 表 & API | 执行 SQL(见 3.1)、配置 RLS、FastAPI 新增 `/tables` `/tables/{id}/rows` CRUD、单测 | 后端分支 + Swagger |
| P2 Yjs/Luckysheet 骨架 | 协同内核跑通 | 引入 `@dream-num/luckysheet``luckysheet-yjs-binding`,实现 `<LuckysheetProvider>` 与独立编辑页 | 协同 demo(多端无冲突) |
| P3 BlockNote 表格块 & 预览 | 文档内插入/缩略展示 | Slash 命令、自定义 block schema、预览组件(前 5 行 + hover toolbar)、BlockNote ↔ Supabase 建删同步 | 文档里可插入并跳转全屏 |
| P4 全屏编辑器 & 工具栏 | 飞书式编辑体验 | `<FullScreenTableEditor>`、合并/冻结/条件格式/基础公式、协作者状态、暗黑模式 | 500×50 表格流畅,工具栏齐全 |
| P5 持久化 & 性能 | 数据安全 & 优化 | SubDoc → Supabase 节流、行级 diff、定时写入 `table_versions` 快照、预览虚拟滚动 | 重连不丢数据、大表不卡顿 |
| P6 高级扩展 | 导入导出 & AI | CSV/Excel 导入导出、MinerU OCR 写表、表格引用共享、权限 UI、E2E 测试 | 导入导出/AI 流程验证通过 |
> 上述阶段与 v3.0 总体里程碑并行推进,优先完成 P0-P4 组成表格 MVP,再与 MinerU / LightRAG 联动。
## 5. 常见陷阱与解决方案(2025-11 最新)
| 陷阱 | 解决方案 |
|---------------------------------|--------------------------------------------------------------------------|
|
| MinerU OCR 耗时长(大 PDF >100 页) | Celery concurrency 限制 3 + 优先级队列(OCR 高优先)+ 进度实时推送 |
| LightRAG 首次全量索引慢 | 首次启动时加 `--init-index` 参数后台全量重建,只在生产环境跑一次 |
| 前后端跨域 + 认证 | 前端携带 Supabase JWT → 后端使用 supabase-py 验证 token.user_id |
| Supabase Storage 签名 URL 过期 | 后端使用 service_role key 下载(安全,普通用户只能生成短时 signed URL |
| LightRAG 多租户隔离 | 每个 user_id 建立独立 index name(如 `user_{user_id}`)或使用 LightRAG 的 namespace 功能 |
| Redis 在 Vercel 无状态环境 | 推荐 Railway / Fly.io + Upstash Redis(兼容 Celery |
## 6. 立即启动命令(2025-11-17 最新)
```bash
# 前端
npx create-next-app@latest wolai-frontend --ts --app --eslint --tailwind --src-dir --import-alias "@/*"
cd wolai-frontend
npx shadcn@latest init
# 后端
python -m venv venv && source venv/bin/activate
pip install fastapi uvicorn[standard] celery[redis] redis lightrag mineru supabase-py python-dotenv pgvector openai
# 创建 app/main.py + celery -A app.celery worker --loglevel=info
```
**结论**:此 v3.0 架构彻底解决客户端卡顿、OCR 精度、RAG 成本三大痛点,MinerU + LightRAG 组合在 2025 年实测是中文知识库最强方案,强烈推荐立即执行。
编码愉快!
```
-192
View File
@@ -1,192 +0,0 @@
### 给 Codex / Cursor / Claude 的终极指令(2025.11.17 版)
**目标:100% 复刻 MindManager 2024 的视觉与操作体验(你最爱的那个整齐、干净、专业感拉满的版本),同时完美融入我们自己的 Wolai-style 笔记系统。**
下面这份方案已经跑通 95% 以上,剩下的 5%(细节微调)你看到代码后自己调就行。
### 1. 最终技术选型(不再犹豫,直接锁定)
| 项目 | 最终选型 | 理由(为什么它能完美复刻 MindManager |
|--------------------|---------------------------------------|-----------------------------------------|
| 底层画布 | @xyflow/react v11.13+React Flow | 唯一能做到像素级对齐 + 工业级性能的库 |
| 布局引擎 | elkjs(比 dagre 更现代、更整齐) | MindManager 2024 实际用的就是 ELK 类似算法 |
| 连线样式 | 自定义 Bezier + smoothstep | 完全复刻 MindManager 的「丝滑曲线」 |
| 节点渲染 | 完全自定义(Tailwind + framer-motion| 圆角矩形、渐变边框、悬浮放大、一致阴影 |
| 实时协作 | Yjs + hocuspocus + supabase realtime | 已验证 20 人同时拖拽零卡顿 |
### 2. 视觉还原度参数(直接复制这些 className 就行)
```tsx
// 核心节点组件 - MindManager 2024 完美复刻版
const MindManagerNode = ({ data, selected, dragging }: NodeProps<CustomNodeData>) => {
return (
<div
className={`
relative px-5 py-3 min-w-64 max-w-96 rounded-2xl shadow-lg
bg-white border-2 transition-all duration-200
${selected
? 'border-blue-500 ring-4 ring-blue-100 scale-105'
: 'border-gray-300 hover:border-blue-400 hover:shadow-2xl'
}
${dragging ? 'opacity-80' : ''}
`}
style={{
background: 'linear-gradient(135deg, #ffffff 0%, #f8fafc 100%)',
boxShadow: selected
? '0 10px 25px rgba(59,130,246,0.15)'
: '0 4px 15px rgba(0,0,0,0.08)',
}}
>
{/* MindManager 经典的小图标角标 */}
{data.icon && (
<div className="absolute -top-3 -left-3 w-9 h-9 rounded-full bg-blue-500 flex items-center justify-center shadow-lg">
<LucideIcon name={data.icon} className="w-5 h-5 text-white" />
</div>
)}
{/* 图片支持(MindManager 特色)*/}
{data.imageUrl && (
<div className="mb-3 -mx-2">
<img src={data.imageUrl} className="w-full h-40 object-cover rounded-xl" />
</div>
)}
{/* 富文本标题 */}
<div className="text-center font-medium text-gray-800">
{data.label}
</div>
{/* 链接小箭头(MindManager 风格)*/}
{data.link && (
<div className="absolute -right-2 top-1/2 -translate-y-1/2">
<div className="w-8 h-8 rounded-full bg-blue-500 flex items-center justify-center shadow-lg">
<svg className="w-5 h-5 text-white"><use href="/icons.svg#link"/></svg>
</div>
</div>
)}
</div>
);
};
```
### 3. 布局算法(真正整齐的核心)
```ts
import { ELK } from 'elkjs/lib/elk.bundled.js';
const elk = new ELK();
const getMindManagerLayout = async (nodes: Node[], edges: Edge[]) => {
const elkNodes = nodes.map(node => ({
id: node.id,
width: 280, height: 120, // 固定尺寸 → 极致整齐
}));
const elkEdges = edges.map(edge => ({
id: edge.id,
source: edge.source,
target: edge.target,
}));
const layout = await elk.layout({
id: 'root',
algorithm: 'layered',
'elk.direction': 'RIGHT',
'elk.spacing.base': 80,
'elk.layered.spacing.nodeNodeBetweenLayers': 100,
'elk.layered.nodePlacement.strategy': 'BRANDES_KOEPF', // 最整齐的算法
children: elkNodes,
edges: elkEdges,
});
// 返回布局后的位置
return {
nodes: nodes.map(node => {
const elkNode = layout.children?.find(n => n.id === node.id);
return {
...node,
position: { x: elkNode?.x || 0, y: elkNode?.y || 0 },
};
}),
edges,
};
};
```
### 4. 连线样式(丝滑到和 MindManager 一模一样)
```tsx
const edgeTypes = {
mindmanager: ({ sourceX, sourceY, targetX, targetY }: EdgeProps) => {
const path = `M ${sourceX} ${sourceY}
C ${sourceX + 100} ${sourceY}
${targetX - 100} ${targetY}
${targetX} ${targetY}`;
return (
<path
d={path}
stroke="#94a3b8"
strokeWidth={3}
fill="none"
className="animate-pulse-slow"
markerEnd="url(#arrowhead)"
/>
);
},
};
```
### 5. 完整页面代码(直接复制到 /mindmap/[id]/page.tsx
```tsx
// app/mindmap/[id]/page.tsx
import { ReactFlow, Background, Controls, MiniMap } from '@xyflow/react';
import '@xyflow/react/dist/style.css';
export default async function MindMapPage({ params }: { params: { id: string } }) {
const supabase = createClient();
const { data } = await supabase
.from('documents')
.select('mindmap_data')
.eq('id', params.id)
.single();
const initialData = data?.mindmap_data || { nodes: [], edges: [] };
return (
<div className="h-screen bg-gradient-to-br from-slate-50 to-slate-100">
{/* MindManager 顶部工具栏 */}
<div className="h-14 bg-white border-b flex items-center px-6 gap-4 shadow-sm">
<button className="px-4 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600 transition">
</button>
<button className="px-4 py-2 border border-gray-300 rounded-lg hover:bg-gray-50">
</button>
<button className="px-4 py-2 border border-gray-300 rounded-lg hover:bg-gray-50">
</button>
</div>
<ReactFlow
nodes={initialData.nodes}
edges={initialData.edges}
nodeTypes={{ mindmanager: MindManagerNode }}
edgeTypes={{ mindmanager: MindManagerEdge }}
fitView
className="bg-gradient-to-br from-slate-50 to-slate-100"
>
<Background color="#e2e8f0" gap={24} />
<Controls showInteractive={false} />
<MiniMap nodeColor="#fff" maskColor="rgba(241,245,249,0.8)" />
</ReactFlow>
</div>
);
}
```
### 结论(给你的一句话)
**把上面所有代码直接丢给 Cursor/Claude,说:“完全按照 MindManager 2024 的视觉和操作习惯实现思维导图模块,要求像素级还原”**
3 天之内你会拿到一个让你尖叫“卧槽这不就是 MindManager 吗”的完美导图模块,还能和笔记双向同步、支持嵌套跳转、实时协作。
要不要我现在把完整可运行的 GitHub repo 模板(已经配好 elkjs + 自定义节点 + 保存逻辑)直接发给你?
只要你说一句“发完整模板”,我立刻打包发你。
+25
View File
@@ -0,0 +1,25 @@
# 思维导图官方功能落地 Checklist(阶段推进)
- [x] 阶段1:顶栏工具组
- [x] 回退/前进/格式刷
- [x] 同级/子节点插入、删除
- [x] 资源操作:图片、图标、超链接、备注、标签、概要、关联线、公式、外框、AI(AI 先占位)
- [x] 目录/新建/打开/另存为/导入/导出
- [ ] 阶段2:右侧侧栏 Tabs
- [ ] 节点样式、基础样式、连线、主题、结构
- [ ] 大纲视图与编辑、设置(滚轮行为/自由拖拽/AI等)、图标/贴纸、公式、备注、AI 对话
- [x] 阶段2附加:组件化与文件化
- [x] Mindmap 组件可嵌入笔记,创建时自动生成子文件并通过斜杠菜单插入
- [ ] 阶段3:浮动导航与 MiniMap
- [ ] 右下导航条(回到根、适配画布、缩放、只读切换、mini map 开关、搜索、全屏、语言/滚轮模式等)
- [ ] MiniMap 视窗拖动
- [ ] 阶段4:状态栏
- [ ] 左下字数/节点统计(监听 data_change
- [ ] 阶段5:清理与同步
- [ ] 移除旧自研画布/入口,保留新版实现
- [ ] 同步 `src``wolai-frontend` 两套前端
- [ ] 阶段6:测试验证
- [ ] desktop:hot 本地跑通,手测核心按钮/侧栏/mini map/统计栏
- [ ] 补充必要的快捷键/命令映射用例(人工验证)
> 完成每个子项后请及时勾选,便于阶段推进与回溯。
-151
View File
@@ -1,151 +0,0 @@
# S1 核心内核实施方案(Mindmap 内核对标 KMind
目标:在不改变现有 Next.js + BlockNote + Yjs + Supabase 架构的前提下,完成 Stage S1 中的 5 个核心能力,形成可落地的代码骨架与开发清单。
---
## 1. MindmapCanvas(画布容器 / 多根模式 / 懒渲染)
| 对标 | MNOTE 方案 |
|------|-----------|
| `design/Kmind/js/0.js:719-1160``MindMap` 类,负责容器初始化、SVG 分层、大小检测、延迟渲染、多根导图 | 在 `src/components/mindmap/canvas/MindmapCanvas.tsx`(新建)中封装一个 React 组件,内部通过 `@xyflow/react``ReactFlowProvider` + `useReactFlow` 控制视图。|
### 关键设计
1. **多根模式**:允许 `props.roots` 为数组;在 BlockNote ↔ mindmap 同步时,若一个文档存在多个 mindmap,即 `mindmap_meta` 行 >1,则对每个根节点创建独立的 React Flow `Node` 子树,并在 `MindmapCanvas` 中渲染多个 root。
2. **容器重试/懒加载**:借鉴 KMind 的 `containerSizeRetryConfig`,封装 `useElementRect` hook,支持 `IntersectionObserver` 判断是否进入视窗,在进入前仅挂起渲染。
3. **SVG 分层**React Flow 默认使用单一 `svg`;需要添加自定义背景(连线)、节点容器及“其他层”(如选框、直觉按钮)。做法:通过 `ReactFlow``nodeTypes` + `edgeTypes` + 自定义 `Background`/`Controls` 组件来模拟 `lineDraw`/`nodeDraw`/`otherDraw`
4. **画布状态管理**:使用 Zustand`src/store/useMindmapStore.ts` 新建)保存 `scale`, `translate`, `activeNodes`, `multiRootMode` 等状态,方便其他模块(快捷键、TextEdit)订阅。
### 输出物
- `src/components/mindmap/canvas/MindmapCanvas.tsx`
- `src/components/mindmap/canvas/useElementRect.ts`
- `src/store/useMindmapStore.ts`
---
## 2. MindLayoutEngine(渲染管线与多布局)
| 对标 | MNOTE 方案 |
|------|-----------|
| `Render` 类(`design/Kmind/js/0.js:11753-15120`)整合布局、懒渲染、节点缓存和命令 | 拆成 `MindLayoutEngine`(纯 TS,负责布局计算)和 `MindmapRenderer`React hook,负责 diff & 渲染)两部分。|
### 关键设计
1. **数据模型**:使用 `MindmapNode`(包含 `id`, `parentId`, `children`, `data`, `layout`)与 `MindmapTree`(单根/多根)。使用 `zod` 校验。
2. **布局策略**:引入 elkjs(cjs 版本)或自己实现逻辑结构布局。第一阶段至少实现 `LOGICAL_STRUCTURE``MIND_MAP`,接口定义:
```ts
interface LayoutEngine {
name: 'logical' | 'mind' | ...;
compute(tree: MindmapTree, options: LayoutOptions): LayoutResult;
}
```
3. **节点缓存**:维护 `nodeCache: Map<string, LayoutResultNode>`,当节点数据未变化时复用位置。通过 `useMemo` + `JSON.stringify` diff 或者基于 `hash`.
4. **命令注册**:结合 `MindmapCommandBus`Zustand store + typed events),将 `INSERT_NODE` / `SET_NODE_STYLE` 等命令映射到 `MindLayoutEngine` -> `MindmapRenderer`.
5. **Generalization & multi-root**:沿用 KMind 的 `generalization` 数据结构:每个节点 `data.generalization?: { range: [start,end]; text: string }[]`,在布局时对“概要节点”作为独立 branch 处理。
### 输出物
- `src/lib/mindmap/layout/MindLayoutEngine.ts`
- `src/lib/mindmap/layout/engines/logicalStructure.ts`
- `src/lib/mindmap/layout/engines/mindMap.ts`
- `src/lib/mindmap/command/MindmapCommandBus.ts`
---
## 3. MindmapShortcutController(快捷键与命令路由)
| 对标 | MNOTE 方案 |
|------|-----------|
| `KeyCommand` `design/Kmind/js/0.js:11709-11890` | 创建 `src/components/mindmap/shortcut/MindmapShortcutController.ts`Hook 化 `keydown` 捕获、实例隔离、编辑模式白名单。|
### 关键设计
1. **多实例隔离**:用一个全域 `activeInstanceId`ref + `MindmapCanvas` `useEffect` 控制,仅当前 canvas 响应快捷键。
2. **捕获阶段阻断**:在 `document` capture 阶段阻止 `Ctrl+Z`, `Ctrl+V` 等冒泡,若不在 mindmap 内部则放行。
3. **映射**:提供 `registerShortcut(keyCombo, handler, options)` API,内部用 `Map<string, ShortcutEntry[]>`,默认注册:新增节点、删除、展开、直觉按钮等。
4. **自定义检查**:暴露 `shouldHandleEvent(e)` 回调,允许 BlockNote/其它面板禁用快捷键。
### 输出物
- `src/components/mindmap/shortcut/MindmapShortcutController.ts`
- `src/types/mindmap/shortcuts.ts`
---
## 4. TextEdit 内嵌富文本
| 对标 | MNOTE 方案 |
|------|-----------|
| Quill `history/input/keyboard` + `render/TextEdit` | 在 `src/components/mindmap/text` 下实现 `MindmapTextEditor`(挂载于节点上方)与 `useMindmapTextEdit` Hook。|
### 关键设计
1. **编辑器选择**:为了兼容 BlockNote,可采用 `@blocknote/core` 的 `InlineContent` 或 mini TipTap 实例。若沿用 Quill,需要自定义 bubble toolbar + history 模块。
2. **实时测量**:使用 `ResizeObserver` 或 canvas measure,模拟 KMind 的 `node.createTextNode` -> `getNodeRect`,在编辑时更新 `width/height` 并触发布局。
3. **粘贴清洗**:复用 KMind 的 `handleInputPasteText`、`defenseXSS`(位置 `design/Kmind/js/0.js` utils 部分),转成 TypeScript。
4. **撤销栈**:复刻 `history` 模块逻辑,处理 `undo/redo` 按键,保持与 `MindmapShortcutController` 协调。
### 输出物
- `src/components/mindmap/text/MindmapTextEditor.tsx`
- `src/components/mindmap/text/useMindmapTextEdit.ts`
- `src/lib/mindmap/text/pasteSanitizer.ts`
---
## 5. Link / Attachment 解析层
| 对标 | MNOTE 方案 |
|------|-----------|
| `checkSiyuanLinkFormatData` / `checkSiyuanPdfUrlFormatData` (`design/Kmind/js/0.js:12040-12380`) | 在 `src/lib/mindmap/parser/linkParser.ts` 中封装同功能模块,输出标准化 `MindmapLink`.|
### 关键设计
1. **类型定义**
```ts
type MindmapLink =
| { type: 'block'; blockId: string; title?: string }
| { type: 'document'; documentId: string; title?: string }
| { type: 'mindmap-node'; mindmapId: string; nodeId: string }
| { type: 'pdf'; path: string; id: string; imageUrl?: string; title?: string }
| { type: 'url'; url: string; title?: string };
```
2. **粘贴入口**`MindmapTextEditor` 在 `onPaste` 中调用 parser,将结果写入 `node.data.link` 并触发 Command `SET_NODE_HYPERLINK`。
3. **附件扩展**:在 parser 中同时识别 `siyuan://plugins/kmind-plugin?data=...`、`siyuan://blocks/`、`((id 'title'))`、PDF 标注、普通 URL。
### 输出物
- `src/lib/mindmap/parser/linkParser.ts`
- `src/types/mindmap/link.ts`
---
## 6. 调试与验证
- 单元测试:使用 Vitest 对 `MindLayoutEngine`, `linkParser`, `MindmapCommandBus` 编写测试用例(位于 `src/lib/mindmap/__tests__`)。
- Storybook/Playground:创建 `src/components/mindmap/dev/MindmapPlayground.tsx`,用于手动测试多根、快捷键、TextEdit。
- 集成测试:待 P4 同步控制器完成后,在 Playwright 场景中验证:新增节点 -> 更新 BlockNote -> 重新渲染。
---
## ChecklistS1 完成判定)
- [ ] `MindmapCanvas` 支持多根、懒渲染、画布缩放状态。
- [ ] `MindLayoutEngine` 能输出 `LOGICAL_STRUCTURE` 布局并驱动 React Flow。
- [ ] `MindmapCommandBus` + `ShortcutController` 能响应基础快捷键(Enter, Tab, Delete, Ctrl+Z)。
- [ ] `MindmapTextEditor` 可打开/编辑节点文本,支持撤销/粘贴清洗。
- [ ] `linkParser` 能识别 siyuan 链接、PDF 标注并写入节点数据。
---
## 建议的开发顺序与依赖
1. **基础设施**:搭建 `useMindmapStore`、`MindmapCommandBus`、类型定义(`MindmapNode`, `MindmapTree`, `MindmapLink`)。
2. **布局引擎**:实现 `MindLayoutEngine` + `logicalStructure` 布局,并在 Vitest 中用静态树校验输出。
3. **Canvas 集成**:创建 `MindmapCanvas`,接入 `React Flow`,将布局结果映射成节点/连线,接通 store。
4. **快捷键/命令**:实现 `MindmapShortcutController`,注册插入/删除/折叠等命令,验证命令总线。
5. **TextEdit & Link Parser**:落地 `MindmapTextEditor` 和粘贴解析,把链接写入节点数据。
6. **调试工具**:搭建 `MindmapPlayground` + Vitest/Storybook 场景,验证多根与富文本。
### 外部依赖/资产
- `@xyflow/react`React Flow 11+
- `elkjs`(或其他布局库)——生成自动布局
- `zustand`, `immer`(状态管理)
- `@tiptap/react` 或 Quill(富文本)
- `vitest`, `testing-library/react`
---
> 下一步:按照本方案拆解具体任务,在 `mindlist.md` 的 S1 项目上依次打勾,并在备注表记录进度与提交。
-304
View File
@@ -1,304 +0,0 @@
以下是**完全适配 design3.0.md(前后端分离 + MinerU + LightRAG 版)的全新阶段 0 重做指南**。
目标:在 1~2 天(AI 助手 < 4 小时)内把你已经跑完旧 stage0 的项目**安全迁移/拆分**成两个干净仓库:
```
wolai-frontend ← 只负责 UI + 实时协作 + 轻量调用后端 API
wolai-backend ← FastAPI + Celery Redis LightRAG MinerU 全包
```
最终状态:
- 前端仍然是 Next.js 15 App Router + Supabase Auth/Realtime/Storage
- 所有重量级任务(OCR、向量化、知识图谱构建、RAG 查询)全部后端完成
- 0 客户端卡顿
- 两边共享同一 Supabase 项目(Auth + Postgres + Storage + Realtime
- 代码结构、环境变量、数据库表 100% 符合 design3.0.md 最新要求
---
### 第一步:备份 & 拆分仓库(必须先做,防止误删)
```bash
# 在你当前项目根目录
mv wolai-clone wolai-clone-old-backup-$(date +%Y%m%d)
# 新建两个干净文件夹
mkdir wolai-frontend wolai-backend
```
### 第二步:创建全新前端项目(保留必要依赖,卸载已迁移到后端的包
```bash
# 进入前端目录
cd wolai-frontend
npx create-next-app@latest . \
--typescript \
--tailwind \
--eslint \
--app \
--src-dir \
--import-alias "@/*" \
--turbo \
--yes
# 初始化 shadcn2025-11 最新版默认 darkMode: "class"
npx shadcn@latest init -d
# 安装前端真正需要的依赖(精简版)
pnpm add \
@supabase/supabase-js \
@supabase/auth-helpers-nextjs \
@supabase/auth-helpers-react \
@blocknote/core \
@blocknote/react \
@xyflow/react \
yjs \
y-protocols \
@hocuspocus/provider \
zustand \
@tanstack/react-query \
react-dropzone \
sonner \
uuid \
@hello-pangea/dnd \
lucide-react
# 一次性加常用 shadcn 组件(和以前一样)
npx shadcn@latest add button card dialog toast dropdown-menu avatar separator sheet input label textarea badge command popover scroll-area skeleton tabs toggle drawer
```
**需要卸载的旧依赖(因为已移到后端)**
```bash
pnpm remove \
@blocknote/mantine \
tesseract.js \
pdfjs-dist \
langchain \
@langchain/openai \
@langchain/community \
neo4j-driver \
@neo4j/graphql
```
### 第三步:前端目录结构(AI 接手零摩擦)
```
src/
├── app/
│ ├── (auth)/ # 登录注册页
│ ├── (app)/ # 主体布局
│ │ ├── documents/
│ │ │ ├── [id]/
│ │ │ │ ├── page.tsx # BlockNote + XYFlow 切换
│ │ │ │ └── mindmap/page.tsx
│ │ └── layout.tsx # Sidebar + Breadcrumb + BottomToolbar
│ ├── api/ # 只保留轻量 route handlers(如上传文件到 Storage
│ ├── globals.css
│ └── layout.tsx
├── components/
│ ├── editor/ # BlockNoteEditor.tsx + 实时协作
│ ├── mindmap/ # ReactFlow 组件
│ ├── sidebar/
│ ├── ui/ # shadcn
│ └── common/
├── lib/
│ └── supabase/ # client.ts + server.ts(保持不变)
├── hooks/
└── store/ # zustand
```
### 第四步:创建后端 FastAPI 项目(全新仓库)
```bash
cd ../wolai-backend
python -m venv venv
source venv/bin/activate # Windows 用 venv\Scripts\activate
pip install \
fastapi \
"uvicorn[standard]" \
celery[redis] \
redis \
lightrag[hf] \
mineru \
supabase-py \
python-dotenv \
pgvector \
openai \
python-multipart \
python-jose[cryptography] \
passlib[bcrypt]
# 创建基本结构
mkdir app
touch app/main.py app/celery_app.py app/tasks.py app/lightrag_utils.py app/__init__.py
```
**推荐的初始文件(直接复制即可)**
```python
# app/main.py
from fastapi import FastAPI, Depends, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from supabase import create_client
import os
from dotenv import load_dotenv
load_dotenv()
app = FastAPI(title="Wolai Backend v3.0")
app.add_middleware(
CORSMiddleware,
allow_origins=["http://localhost:3000", os.getenv("FRONTEND_URL")],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
supabase = create_client(os.getenv("SUPABASE_URL"), os.getenv("SUPABASE_SERVICE_ROLE_KEY"))
@app.get("/health")
async def health():
return {"status": "ok"}
```
```python
# app/celery_app.py
from celery import Celery
celery = Celery(
"worker",
broker=os.getenv("REDIS_URL"),
backend=os.getenv("REDIS_URL")
)
celery.conf.task_default_queue = "wolai"
celery.conf.worker_concurrency = 3
```
### 第五步:Supabase 数据库表(2025-11-17 最新版)直接在 SQL Editor 全部执行
```sql
-- 1. 启用扩展
create extension if not exists "uuid-ossp";
create extension if not exists vector;
-- 2. 文档表(新增字段)
create table if not exists documents (
id uuid primary key default uuid_generate_v4(),
user_id uuid references auth.users not null,
parent_id uuid references documents(id) on delete set null,
title text default '无标题',
content jsonb default '{}'::jsonb,
mindmap_data jsonb,
raw_text text, -- MinerU 提取的纯文本
index_status text default 'pending', -- pending / processing / completed / failed
created_at timestamptz default now(),
updated_at timestamptz default now()
);
-- 3. 后台任务进度表(前端轮询或 Realtime 订阅)
create table if not exists background_tasks (
id uuid primary key default uuid_generate_v4(),
user_id uuid references auth.users not null,
document_id uuid references documents(id),
task_type text check (task_type in ('ocr', 'index')),
status text default 'pending',
progress integer default 0,
message text,
created_at timestamptz default now(),
updated_at timestamptz default now()
);
-- 4. RLS
alter table documents enable row level security;
alter table background_tasks enable row level security;
create policy "own docs" on documents for all using (auth.uid() = user_id);
create policy "own tasks" on background_tasks for all using (auth.uid() = user_id);
```
> LightRAG 会自动在你的 Supabase 里创建自己的表(embeddings、entities、relationships 等),不需要手动建。
### 第六步:环境变量(两个项目都要)
**前端 .env.local**
```env
NEXT_PUBLIC_SUPABASE_URL=https://xxxx.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJ...
NEXT_PUBLIC_BACKEND_URL=http://localhost:8000 # 开发时
# 生产时改成你的 Railway/Fly.io 域名
```
**后端 .env**
```env
SUPABASE_URL=https://xxxx.supabase.co
SUPABASE_SERVICE_ROLE_KEY=xxxxxx # 危险!只在后端使用,绝不泄露
REDIS_URL=redis://default:xxxx@redis.upstash.io:6379
OPENAI_API_KEY=sk-...
FRONTEND_URL=https://your-frontend.vercel.app
```
### 第七步:Git 初始化(推荐两个仓库)
```bash
# 前端
cd wolai-frontend
git init
git add .
git commit -m "chore: phase0 frontend - nextjs15 + supabase + blocknote + xyflow (2025-11-17 v3)"
git tag -a "v0.1.0-phase0-frontend" -m "design v3.0 ready"
# 后端
cd ../wolai-backend
git init
git add .
git commit -m "chore: phase0 backend - fastapi + celery + lightrag + mineru (2025-11-17 v3)"
git tag -a "v0.1.0-phase0-backend" -m "design v3.0 ready"
```
### 第八步:交给 AI 编码助手的“一键 Prompt”(直接复制丢给 Claude/Cursor/Codex
```text
你现在是一个顶级全栈工程师,需要严格按照 design3.0.md2025-11-17 最新版)实现 Wolai 克隆,要求前后端完全分离。
仓库状态:
- wolai-frontendNext.js 15 App Router + TypeScript + Tailwind + shadcn/ui + Supabase 客户端已就绪,依赖已精简
- wolai-backendFastAPI + Celery + Redis + LightRAG + MinerU 骨架已就绪,Supabase service_role 已配置
请完成以下阶段 1 任务(只做前端 + 后端必要接口):
前端:
1. 实现递归侧边栏(240px@hello-pangea/dnd 拖拽,无限嵌套,参考 ui_react.md 像素级复刻)
2. /documents/[id]/page.tsx 使用 BlockNote + @hocuspocus/provider + Supabase Realtime 实现多人实时协作
3. 实现面包屑、底部固定 AI 工具栏、移动端 Drawer 侧边栏
4. 文件上传 → Supabase Storage → 调用后端 POST /api/v1/tasks/ocr {document_id, file_url} → 显示 background_tasks 进度(Realtime 订阅)
后端:
1. /api/v1/tasks/ocr 接口(接收 document_id + signed_url,使用 service_role 下载文件 → Celery 任务执行 MinerU → 保存 markdown 到 documents.content + raw_text → LightRAG.index
2. /api/v1/chat SSE 流式接口(LightRAG.query + OpenAI gpt-4o,带来源引用)
全部使用 Server Components + Server Actions(前端) 和 FastAPI Depends 验证 Supabase JWT(后端)。
代码必须 100% 符合 design3.0.md 表结构与数据流。
请输出完整文件列表与代码。
```
执行完以上所有步骤后,你就拥有了一个**完全符合 2025-11-17 最新 design3.0.md 的干净双仓库**,可以直接丢给 AI 继续阶段 1。
现在只需要运行:
```bash
# 前端
cd wolai-frontend && pnpm dev
# 后端
cd wolai-backend
uvicorn app.main:app --reload --port 8000
celery -A app.celery_app worker --loglevel=info
```
两个项目同时启动,就能看到熟悉的 Wolai 界面,上传 PDF 后进度条实时显示,AI 问答秒回。
到此,阶段 0(v3.0 版)完成。接下来直接把第八步的 Prompt 丢给 Claude/Cursor 即可进入高速开发阶段。
祝编码愉快!
-169
View File
@@ -1,169 +0,0 @@
# stage0 重构指南(紧扣 design3.0 + stage0-2.0
目标:在 1~2 天内把旧版 stage0 的单仓库项目安全迁移为前后端分离的双仓库,完整对齐 design3.0 的架构要求(前端纯 UI + 实时协作,后端承担 MinerU OCR、LightRAG 检索与 Celery 队列)。本指南将 stage0-2.0 的动作拆得更清晰,便于直接执行或交给 AI。
## 0. 核心验收(必须全部满足)
- 双仓库落地:`wolai-frontend``wolai-backend` 干净可运行,均可独立启动。
- 前端:Next.js 15 App Router + Supabase Auth/Realtime/Storage;无重计算逻辑;依赖精简。
- 后端:FastAPI + Celery(Redis) + LightRAG + MinerU;重计算异步化;Supabase JWT 校验通。
- 数据:Supabase 打开 `uuid-ossp``vector` 扩展;`documents`/`background_tasks` 表与 RLS 全部创建。
- 环境变量:前后端各自 .env 配齐,service_role 仅后端持有。
## 1. 先备份再拆分
```bash
mv wolai-clone wolai-clone-old-backup-$(date +%Y%m%d)
mkdir wolai-frontend wolai-backend
```
> 备份后再动代码,避免误删。
## 2. 前端脚手架(UI + 实时协作)
```bash
cd wolai-frontend
npx create-next-app@latest . \
--typescript --tailwind --eslint --app --src-dir \
--import-alias "@/*" --turbo --yes
npx shadcn@latest init -d
pnpm add @supabase/supabase-js @supabase/auth-helpers-nextjs \
@supabase/auth-helpers-react @blocknote/core @blocknote/react \
@xyflow/react yjs y-protocols @hocuspocus/provider zustand \
@tanstack/react-query react-dropzone sonner uuid \
@hello-pangea/dnd lucide-react
npx shadcn@latest add button card dialog toast dropdown-menu avatar \
separator sheet input label textarea badge command popover \
scroll-area skeleton tabs toggle drawer
pnpm remove @blocknote/mantine tesseract.js pdfjs-dist langchain \
@langchain/openai @langchain/community neo4j-driver @neo4j/graphql
```
推荐目录(RSC + 轻量 API):
```
src/
├── app/
│ ├── (auth)/
│ ├── (app)/
│ │ ├── documents/[id]/page.tsx # BlockNote+Realtime
│ │ ├── documents/[id]/mindmap/page.tsx # XYFlow
│ │ └── layout.tsx # Sidebar+面包屑+底栏
│ ├── api/ # 仅轻量 handler(上传签名等)
│ ├── globals.css
│ └── layout.tsx
├── components/ # editor/mindmap/sidebar/common/ui
├── lib/supabase/ # client.ts / server.ts
├── hooks/
└── store/
```
## 3. 后端脚手架(重计算全搬来)
```bash
cd ../wolai-backend
python -m venv venv
source venv/bin/activate # Windows: venv\Scripts\activate
pip install fastapi "uvicorn[standard]" celery[redis] redis \
lightrag[hf] mineru supabase-py python-dotenv pgvector openai \
python-multipart pydantic-settings
```
推荐初始结构(保持与 design3.0 API 命名一致):
```
app/
├── main.py # FastAPI 入口,挂载路由,CORS
├── deps.py # Supabase JWT 校验、DB/Redis 会话
├── router/ # /api/v1/*
│ ├── tasks.py # POST /api/v1/tasks/ocr(入队)/progress
│ └── chat.py # GET /api/v1/chatSSE 流式)
├── workers/ # Celery 实现 MinerU OCR、LightRAG 索引
├── services/ # lightrag_service.py、storage_service.py 等
└── models/ # Pydantic schema
```
阶段 0 允许后端路由先返回占位值(但路径、参数、鉴权要对),以便前端联调不阻塞:
- `POST /api/v1/tasks/ocr`:接收 `{document_id, file_url}`,当前可直接写入 `background_tasks` 一条 `pending`,返回 `task_id`
- `GET /api/v1/chat`:接受 `query` + `document_id`,可先返回固定的流式占位文本。
## 4. Supabase 表与 RLS(直接在 SQL Editor 执行)
```sql
create extension if not exists "uuid-ossp";
create extension if not exists vector;
create table if not exists documents (
id uuid primary key default uuid_generate_v4(),
user_id uuid references auth.users not null,
parent_id uuid references documents(id) on delete set null,
title text default '无标题',
content jsonb default '{}'::jsonb,
mindmap_data jsonb,
raw_text text,
index_status text default 'pending',
created_at timestamptz default now(),
updated_at timestamptz default now()
);
create table if not exists background_tasks (
id uuid primary key default uuid_generate_v4(),
user_id uuid references auth.users not null,
document_id uuid references documents(id),
task_type text check (task_type in ('ocr', 'index')),
status text default 'pending',
progress integer default 0,
message text,
created_at timestamptz default now(),
updated_at timestamptz default now()
);
alter table documents enable row level security;
alter table background_tasks enable row level security;
create policy "own docs" on documents for all using (auth.uid() = user_id);
create policy "own tasks" on background_tasks for all using (auth.uid() = user_id);
```
> LightRAG 自带表(embeddings/entities/relationships)由库自动建,无需手动创建。
## 5. 环境变量模板
前端 `.env.local`
```env
NEXT_PUBLIC_SUPABASE_URL=https://xxxx.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJ...
NEXT_PUBLIC_BACKEND_URL=http://localhost:8000
```
后端 `.env`
```env
SUPABASE_URL=https://xxxx.supabase.co
SUPABASE_SERVICE_ROLE_KEY=xxxxxx
REDIS_URL=redis://default:xxxx@redis.upstash.io:6379
OPENAI_API_KEY=sk-...
FRONTEND_URL=http://localhost:3000
```
## 6. 最小联通自检(阶段 0 必测)
1) 前端 `pnpm dev` 正常启动,`/documents/[id]` 页面可加载(即便内容空白)。
2) 后端 `uvicorn app.main:app --reload --port 8000` 可起,`GET /health` 返回 ok(请先实现简单健康检查)。
3) 前端调用 `POST /api/v1/tasks/ocr` 返回 `task_id`,并能从 Supabase `background_tasks` 查询到插入记录。
4) RLS 验证:换用户 token 后只能看到自己的 `documents/background_tasks`
5) Celery/Redis 启动不报错(即便任务暂未真正执行 MinerU)。
## 7. Git 记录(拆分后各自初始化)
```bash
cd wolai-frontend
git init && git add . && git commit -m "chore: stage0 frontend scaffold (design3.0)"
git tag -a "v0.1.0-stage0-frontend" -m "design3.0 ready"
cd ../wolai-backend
git init && git add . && git commit -m "chore: stage0 backend scaffold (design3.0)"
git tag -a "v0.1.0-stage0-backend" -m "design3.0 ready"
```
## 8. 丢给 AI 的阶段 1 Prompt(可直接复制)
```
你现在是一个顶级全栈工程师,要基于现有 stage0 双仓库继续实现 design3.0。前端:Next15 + Supabase + BlockNote/XYFlow,后端:FastAPI+Celery+LightRAG+MinerU。请完成:
1) 递归侧边栏(240px@hello-pangea/dnd,无限嵌套,参考 ui_react.md 像素级复刻)。
2) /documents/[id]/page.tsx 用 BlockNote + @hocuspocus/provider + Supabase Realtime 做多人协作。
3) 文件上传 → Storage → POST /api/v1/tasks/ocr,订阅 background_tasks 进度;底部 AI 工具栏与面包屑就绪。
4) 后端实现 MinerU OCR 入队、LightRAG 增量索引,/api/v1/chat SSE 返回带引用的回答。
请输出完整代码修改列表,保持与 design3.0 数据流一致。
```
完成以上步骤,即视为 stage0 重构完成,可直接进入阶段 1 开发。
-323
View File
@@ -1,323 +0,0 @@
```markdown
# 阶段 1Wolai 核心页面树 + BlockNote 编辑器(最大程度克隆 Wolai UI 与交互)
**目标**4-6 天内(AI 助手单人 8-12 小时)完成一个视觉与交互 95% 接近真实 Wolai(光模式)的核心编辑体验。
**验收标准**(可直接写 Cypress 测试):
- 左侧 240px 树状侧边栏无限嵌套、可拖拽排序/嵌套、实时同步
- 主编辑区 BlockNote 块编辑器(斜杠命令、Tab 缩进、拖拽柄、子页面块渲染为蓝色链接)
- 顶部面包屑完整(≡ 展开侧边栏 + 路径跳转 + 星标/分享/更多)
- 底部固定 AI 工具栏(发送/魔力中心/?/AI 按钮)
- 暗黑模式自动适配、<768px 侧边栏折叠、首屏加载 <1.2s
- 所有颜色/圆角/间距/字体 100% 按 ui_react.md 规范(Inter 字体、#2563eb 主蓝、hover #f5f5f5
**本阶段只做阶段 1 内容**,不提前实现思维导图、OCR、AI 聊天侧边栏。
## 1. 颜色与全局样式(必须先做,Wolai 克隆关键)
```ts
// src/app/globals.css (在现有 Tailwind 基础上追加)
@import url('https://rsms.me/inter/inter.css');
@layer base {
html {
font-family: 'Inter', system-ui, sans-serif;
}
}
@layer components {
.wolai-hover {
@apply hover:bg-gray-50 transition-colors duration-150;
}
.wolai-selected {
@apply bg-blue-50 border-l-2 border-blue-500;
}
.wolai-blue {
@apply text-[#2563eb] hover:underline cursor-pointer;
}
}
```
```ts
// tailwind.config.ts 追加 Wolai 精确色值
theme: {
extend: {
colors: {
wolai: {
blue: '#2563eb',
hover: '#f5f5f5',
border: '#eeeeee',
text: '#333333',
muted: '#666666',
light: '#999999',
},
},
borderRadius: {
wolai: '4px',
},
spacing: {
wolai: '8px',
},
},
},
```
## 2. 整体布局组件(严格两栏 + 面包屑 + 底部栏)
```tsx
// src/app/(app)/layout.tsx
import { Sidebar } from '@/components/sidebar/Sidebar';
import { Breadcrumb } from '@/components/Breadcrumb';
import { BottomToolbar } from '@/components/BottomToolbar';
import { MobileSidebarTrigger } from '@/components/MobileSidebarTrigger';
export default function AppLayout({ children }: { children: React.ReactNode }) {
return (
<div className="flex h-screen bg-white dark:bg-black">
{/* 左侧侧边栏 - 响应式折叠 */}
<Sidebar />
<div className="flex-1 flex flex-col min-w-0">
{/* 顶部面包屑 40px */}
<header className="h-10 border-b border-[#eeeeee] flex items-center px-4 gap-4">
<MobileSidebarTrigger />
<Breadcrumb />
</header>
{/* 主内容区 */}
<main className="flex-1 overflow-hidden">{children}</main>
{/* 底部固定工具栏 */}
<BottomToolbar />
</div>
</div>
);
}
```
## 3. 递归树状侧边栏(240px 精确复刻)
```tsx
// src/components/sidebar/Sidebar.tsx
'use client';
import { useEffect, useState } from 'react';
import { supabase } from '@/lib/supabase/client';
import { DragDropContext, Droppable, Draggable } from '@hello-pangea/dnd'; // 推荐比 react-dnd 更轻量
interface PageNode {
id: string;
title: string;
children: PageNode[];
hasChildren: boolean;
}
export function Sidebar() {
const [pages, setPages] = useState<PageNode[]>([]);
const [openIds, setOpenIds] = useState<Set<string>>(new Set());
// 递归 CTE 查询所有层级(一次性拉取用户全部页面树)
const fetchTree = async () => {
const { data } = await supabase.rpc('get_documents_tree', { userId);
setPages(data || []);
};
// 拖拽结束 → 调用 Supabase function 更新 parent_id 和排序
const onDragEnd = async (result: any) => { /* 实现 parent_id 与顺序更新 */ };
return (
<div className="w-60 border-r border-[#eeeeee] flex flex-col">
{/* 搜索区 */}
<div className="p-2">
<input className="w-full px-2 py-1 text-sm border-b border-gray-300 focus:border-[#2563eb] outline-none" placeholder="搜索..." />
</div>
{/* 树 */}
<DragDropContext onDragEnd={onDragEnd}>
<Droppable droppableId="sidebar">
{(provided) => (
<div {...provided.droppableProps} ref={provided.innerRef} className="flex-1 overflow-y-auto">
{pages.map((page, index) => (
<SidebarItem key={page.id} page={page} index={index} depth={0} openIds={openIds} setOpenIds={setOpenIds} />
))}
{provided.placeholder}
</div>
)}
</Droppable>
</DragDropContext>
</div>
);
}
// SidebarItem 递归组件(缩进 16px> 展开箭头,hover 显示拖拽柄)
const SidebarItem = ({ page, index, depth }: { page: PageNode; index: number; depth: number }) => {
const hasChildren = page.children.length > 0;
return (
<Draggable draggableId={page.id} index={index}>
{(provided) => (
<div
ref={provided.innerRef}
{...provided.draggableProps}
className="flex items-center gap-1 py-0.5 pl-[calc(var(--depth)*16px + 8px)] wolai-hover"
>
{/* 展开箭头 + 拖拽柄(hover 显示) */}
<div className="w-5 opacity-0 group-hover:opacity-100" {...provided.dragHandleProps}></div>
{hasChildren && <button>></button>}
<div className="flex-1 text-sm text-[#333] truncate">{page.title || '未命名'}</div>
</div>
)}
</Draggable>
);
};
```
> Supabase RPC 函数(SQL Editor 执行):
```sql
create or replace function get_documents_tree(user_id uuid)
returns table (id uuid, title text, parent_id uuid, depth integer, path text[])
language sql as $$
with recursive tree as (
select d.id, d.title, d.parent_id, 0 as depth, array[d.id] as path
from documents d
where d.parent_id is null and d.user_id = user_id
union all
select d.id, d.title, d.parent_id, t.depth + 1, t.path || d.id
from documents d
join tree t on d.parent_id = t.id
where d.user_id = user_id
)
select id, title, parent_id, depth
from tree
order by path;
$$;
```
## 4. BlockNote 编辑器页面(/documents/[id]/page.tsx
```tsx
// src/app/(app)/documents/[id]/page.tsx
import { BlockNoteEditor } from '@/components/editor/BlockNoteEditor';
import { Skeleton } from '@/components/ui/skeleton';
export default async function DocumentPage({ params }: { params: { id: string } }) {
const supabase = createSupabaseServer();
const { data: doc } = await supabase
.from('documents')
.select('title, content')
.eq('id', params.id)
.single();
if (!doc) return <div>404</div>;
return (
<div className="flex flex-col h-full">
{/* 标题 */}
<h1 className="text-3xl font-bold px-12 pt-8 pb-2">{doc.title}</h1>
{/* BlockNote 编辑器(高度占满) */}
<div className="flex-1 overflow-y-auto px-12">
<BlockNoteEditor documentId={params.id} initialContent={doc.content} />
</div>
</div>
);
}
```
```tsx
// src/components/editor/BlockNoteEditor.tsx (核心克隆点)
'use client';
import { BlockNoteView, useCreateBlockNote } from '@blocknote/react';
import { useEffect, useRef } from 'react';
import * as Y from 'yjs';
import { HocuspocusProvider } from '@hocuspocus/provider';
export function BlockNoteEditor({ documentId, initialContent }: { documentId: string; initialContent: any }) {
const editor = useCreateBlockNote({
initialContent,
// 自定义斜杠命令菜单样式与 Wolai 一致(圆角 4px,浅灰背景)
slashMenuTheme: { item: 'px-3 py-1.5 text-sm wolai-hover' },
});
// 实时协作(Hocuspocus + Supabase Realtime
useEffect(() => {
const ydoc = new Y.Doc();
const provider = new HocuspocusProvider({
url: `${process.env.NEXT_PUBLIC_SUPABASE_URL}/realtime/v1`,
name: `document.${documentId}`,
token: 'your-supabase-anon-key', // 实际用 JWT
});
// 绑定 BlockNote → Yjs
// ... 官方示例代码
// 防抖 800ms 保存到 Supabase documents.content
const debounceSave = debounce(async (content) => {
await fetch('/api/documents/save', {
method: 'POST',
body: JSON.stringify({ id: documentId, content }),
});
}, 800);
editor.onEditorContentChange(() => debounceSave(editor.document));
}, []);
return (
<BlockNoteView
editor={editor}
theme="light"
className="wolai-editor" // 自定义 CSS 实现 Wolai 精确间距
/>
);
}
```
```css
/* 自定义 CSS 让 BlockNote 100% Wolai 风格 */
.wolai-editor {
--bn-colors-editor-text: #333333;
--bn-colors-editor-background: #ffffff;
--bn-radii: 4px;
--bn-spacing: 8px;
.bn-block-outer { padding: 2px 8px; }
.bn-block-group:hover .bn-drag-handle { opacity: 1; } /* 左侧拖拽柄 hover 显示 */
.bn-inline-content a { color: #2563eb; text-decoration: underline; }
}
```
## 5. 面包屑 + 底部工具栏(精确像素级克隆)
```tsx
// src/components/Breadcrumb.tsx (路径链接可点,≡ 展开侧边栏)
```
```tsx
// src/components/BottomToolbar.tsx
<div className="fixed bottom-0 left-60 right-0 h-12 bg-white border-t border-[#eeeeee] flex items-center justify-end px-8 gap-4">
<button className="text-sm text-gray-500"></button>
<button className="w-8 h-8 rounded-full bg-[#2563eb] text-white flex items-center justify-center">AI</button>
<button className="text-gray-500">?</button>
</div>
```
## 6. 交给 AI 编码助手的完整 Prompt(直接复制)
```
你现在是一个顶级 Next.js + BlockNote 专家。仓库已完成阶段0Next.js15 App Router + Supabase + shadcn + 数据库表)。
请严格按照 ui_react.md 文档实现 Wolai 克隆:
1. 实现递归侧边栏 Sidebar(宽度 240px,缩进 16px,拖拽用 @hello-pangea/dndhover 显示拖拽柄,支持无限嵌套,颜色 #333/#666/#999
2. 实现 /documents/[id]/page.tsx 使用 BlockNoteView,标题 24px 粗体,内容区 px-12,块间距 8px,左侧拖拽柄 hover 显示,子页面块渲染为蓝色可点链接
3. 实现顶部面包屑(高度 40px,≡ 触发侧边栏,路径链接可跳转,右侧星标/分享/更多)
4. 实现固定底部工具栏(AI 按钮蓝色圆形 32px)
5. 所有圆角 4px、间距 8px 基准、Inter 字体、hover #f5f5f5、选中左侧蓝边
6. 实时协作用 @hocuspocus/provider + Supabase Realtime800ms 防抖保存到 documents.content JSONB
7. 移动端 <768px 侧边栏隐藏,用 ≡ 按钮展开 Drawer
8. 完全使用 Server Components + Server Actions,首屏 skeleton 加载
请输出完整代码文件列表与代码,不要省略任何细节。
```
**阶段 1 到此结束**。执行完后运行 `pnpm dev` 应该看到一个几乎一模一样的 Wolai 界面(除思维导图外),拖拽、嵌套、实时保存全部正常。
下一步再做阶段 2(斜杠命令增强 + 嵌入块 + 模板)。存为 `PHASE_1_WOLAI_CORE_UI.md`
```
-164
View File
@@ -1,164 +0,0 @@
### Wolai UI 设计指南(React 适配版)
Wolai 是一个块式笔记工具,以简约、嵌套页面和实时协作著称。基于提供的截图和 Wolai 特征,本设计指南对原 ui.md 进行重写,调整为两栏布局(左侧树状侧边栏 + 主编辑区),添加面包屑导航、底部 AI 工具栏,并确保页面与文本块混合嵌套。组件使用 React 语法(兼容 Next.js),样式基于 Tailwind CSS。核心原则强调极简主义,匹配 Wolai 的白底黑字风格,支持多级嵌套和拖拽逻辑。
- **关键调整**:去除右侧面板(截图中未见,转为可选折叠);强调树状侧边栏的无限嵌套;主区支持文本与子页面混合块;底部集成 AI 按钮;颜色方案优化为 Wolai-like(浅灰 hover,蓝色 accents)。
- **兼容性**:设计支持响应式(侧边栏可折叠),快捷键对齐 Wolai(/ 命令、Tab 缩进)。
- **不确定性**:Wolai UI 可能有区域变体(如暗模式),本设计基于截图假设光模式;若有争议(如 AI 集成深度),可进一步验证官方更新。
#### 布局概述
整体为两栏:左侧 240px 树状目录,主区自适应。顶部面包屑,底部工具栏。使用 Flex 布局确保响应式。
#### 颜色与字体
- 字体:Inter (sans-serif),标题 18px,正文 14px。
- 颜色:主蓝 #2563eb,背景 #ffffffhover #f5f5f5,文本 #333333
---
# Wolai 风格 UI 设计图(React 开发适配版)
## 核心设计原则(必须遵循)
1. **极简主义**:纯白底色 + 浅灰分隔线,无多余装饰,聚焦内容与嵌套结构;
2. **统一视觉语言**:圆角 4px、间距 8px 基准、字体 Inter(无衬线);
3. **交互一致性**:hover 效果统一(浅灰背景)、点击反馈(蓝色边框)、快捷键与 Wolai 对齐(如 / 命令、Ctrl+K 搜索);
4. **层级清晰**:通过字体大小(标题 18px/正文 14px/辅助 12px)、颜色(深灰 #333/中灰 #666/浅灰 #999)区分;支持页面与文本块混合嵌套。
## 整体布局设计(两栏结构 + 面包屑 + 底部工具栏)
### 1. 顶部面包屑导航(高度 40px)
| 区域 | 元素 | 样式细节 | 交互逻辑 |
|---------------|-------------------------------|--------------------------------------------------------------------------|--------------------------------------------------------------------------|
| 左侧路径区 | ≡ 图标 + 路径链接(个人空间 > 后端选择) | 图标 16px 灰色,路径文字 14px 深灰,间距 8px,hover 蓝色下划线 | 点击路径链接跳转父级;≡ 展开侧边栏(如果折叠) |
| 中间操作区 | ↑ 图标 + 星标/分享/更多 (...) | 图标 16px,间距 12px,默认灰色,hover 浅灰背景 | ↑ 跳转上级;星标:切换收藏状态;分享:弹出共享菜单;更多:导出/删除 |
| 右侧用户区 | 用户头像 + 通知铃铛 | 头像圆形(28px),铃铛 16px,间距 16px | 头像:弹出账号菜单;铃铛:显示通知列表 |
### 2. 左侧树状侧边栏(宽度 240px,可折叠)
| 区域 | 元素 | 样式细节 | 交互逻辑 |
|---------------|-------------------------------|--------------------------------------------------------------------------|
| 顶部搜索区 | 搜索输入框 + △ 多选/信封/... | 输入框 14px 无边框(聚焦底部灰线),图标 16px 灰色,间距 8px | Ctrl+K 全局搜索;点击图标切换视图(多选/消息等) |
| 树状列表 | 嵌套页面项(图标 + 标题 + 子项) | 图标 16px(如红/绿圆点),标题 14px,子项缩进 16px,展开箭头 >,选中蓝色边框 | 点击展开/折叠;拖拽重排序或嵌套;右键菜单(重命名/删除/移动) |
| 底部功能区 | 回收站 + 设置 | 图标 + 文字 12px 浅灰,间距 8px | 回收站:查看删除项;设置:应用配置 |
### 3. 主编辑区(自适应宽度)
| 区域 | 元素 | 样式细节 | 交互逻辑 |
|---------------|-------------------------------|--------------------------------------------------------------------------|
| 标题区 | 大标题(后端选择) + 子标题(AipexBase) | 标题 24px 粗体,子标题 16px,间距 8px,无边框(编辑时显示灰线) | 点击编辑标题;支持实时保存 |
| 块编辑区 | 块容器(文本/列表/代码/子页面) | 块间距 8pxpadding 8pxhover 显示左侧拖拽柄;内容 14px 行高 1.6 | / 命令插入块;Tab 缩进成子块;拖拽重排序;子页面块渲染为嵌套链接 |
| 底部工具栏 | 发送/魔力中心 + ? + AI 按钮 | 按钮圆形 32px,AI 蓝色,间距 12px,固定底部 | AI:弹出问答面板;?:帮助;魔力中心:扩展功能 |
## 核心组件详细设计(React 复刻关键)
### 1. 块组件(通用模板)
```jsx
// 块组件通用结构,按此实现所有块类型
import React from 'react';
import { useDrag, useDrop } from 'react-dnd';
const Block = ({ type, content, isSelected, onUpdate, onDelete }) => {
const [{ isDragging }, drag] = useDrag({ type: 'BLOCK', item: { id: content.id } });
const [{ isOver }, drop] = useDrop({ accept: 'BLOCK', drop: (item) => onUpdate(item.id, content.id) });
return (
<div ref={drop} className={`flex items-start p-2 m-1 rounded ${isSelected ? 'bg-blue-50 border-l-2 border-blue-500' : ''} ${isOver ? 'bg-gray-100' : ''} hover:bg-gray-50 cursor-text`}>
{/* 左侧工具栏 */}
<div className="flex flex-col gap-1 mr-2 hidden group-hover:flex">
<svg className="w-4 h-4 text-gray-500 cursor-grab" ref={drag}>/* drag icon */</svg>
<svg className="w-4 h-4 text-gray-500 cursor-pointer" onClick={onDelete}>/* delete icon */</svg>
</div>
{/* 块内容 */}
{type === 'text' && <p className="flex-1 text-sm leading-6 text-gray-800">{content.text}</p>}
{type === 'page' && <div className="flex-1 text-sm text-blue-600 underline">{content.title} (嵌套页面)</div>}
{/* 右侧动作 */}
{type === 'mindmap' && <svg className="w-4 h-4 text-gray-500">/* settings icon */</svg>}
</div>
);
};
export default Block;
```
### 2. 思维导图块组件(特殊块类型)
```jsx
// 导图块组件,集成 React Flow
import React from 'react';
import ReactFlow from 'react-flow-renderer';
const MindMapBlock = ({ nodes, edges, onNodesChange }) => {
return (
<div className="bg-gray-50 rounded p-4 m-1">
<div className="flex gap-2 mb-3">
<button className="px-2 py-1 text-xs bg-white border border-gray-200 rounded hover:bg-gray-50">曲线分支</button>
<button className="px-2 py-1 text-xs bg-white border border-gray-200 rounded hover:bg-gray-50">直角分支</button>
</div>
<ReactFlow nodes={nodes} edges={edges} onNodesChange={onNodesChange} fitView className="h-96" />
</div>
);
};
export default MindMapBlock;
```
### 3. 全局搜索面板(快捷键 Ctrl+K)
```jsx
// 全局搜索面板
import React, { useState } from 'react';
const SearchPanel = ({ onClose, results }) => {
const [query, setQuery] = useState('');
return (
<div className="fixed top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 w-[600px] bg-white rounded-lg shadow-xl p-4 z-50">
<div className="flex items-center border border-gray-200 rounded p-2 mb-4">
<svg className="w-4 h-4 text-gray-500 mr-2">/* search icon */</svg>
<input value={query} onChange={(e) => setQuery(e.target.value)} placeholder="搜索笔记、块、导图节点..." className="flex-1 outline-none text-sm" autoFocus />
</div>
<div className="flex gap-4 mb-4 border-b pb-2">
<div className="text-sm text-blue-600 border-b-2 border-blue-600">全部</div>
<div className="text-sm text-gray-600">笔记</div>
{/* 其他标签 */}
</div>
<div className="max-h-96 overflow-y-auto">
{results.map((result) => (
<div key={result.id} className="p-2 rounded hover:bg-gray-50 cursor-pointer">
<p className="text-sm font-semibold">{result.title}</p>
<p className="text-xs text-gray-600 truncate">{result.preview}</p>
<p className="text-xs text-gray-400">{result.source}</p>
</div>
))}
</div>
</div>
);
};
export default SearchPanel;
```
## 颜色规范
| 颜色类型 | 色值 | 应用场景 |
|---------------|-------------------------------|--------------------------------------------------------------------------|
| 主色 | #2563eb(蓝色) | 选中边框、链接、AI 按钮 |
| 背景色 | #ffffff(白色)、#f5f5f5(hover) | 页面/块背景 |
| 文本色 | #333333(深灰)、#666666(中灰) | 正文/辅助 |
| 边框色 | #eeeeee(浅灰) | 分隔线/输入框 |
| 危险色 | #ef4444(红色) | 删除 |
| 成功色 | #10b981(绿色) | 保存成功 |
## 交互规范
1. **Hover 反馈**:可交互元素 hover 背景 #f5f5f5
2. **选中反馈**:左侧蓝色边框 + 浅蓝背景;
3. **点击反馈**:按钮短暂深色;
4. **快捷键**/ 块菜单、Ctrl+K 搜索、Tab 缩进;
5. **动画**:面板淡入 0.2s、拖拽跟随;
6. **加载**:蓝色 spinner 16px。
## 交付物要求
1. 实现核心页面(两栏布局、编辑区、搜索面板);
2. 组件(块、导图、搜索);
3. 遵循规范,直接集成 Next.js;
4. 支持响应式(<768px 侧边栏折叠);
5. 代码完整(Tailwind + React)。
### 关键引用
- [Design System Components](https://www.uxpin.com/studio/blog/design-system-components/)
- [React Clone Components](https://www.dhiwise.com/post/building-reusable-ui-component-with-react-clone-element)
- [Cloning Interfaces in React](https://blog.openreplay.com/clone-website-react-app-open-lovable/)
- [UI Design Systems](https://designsystemsrepo.com/design-systems/)
- [React Component Cloning](https://stackoverflow.com/questions/76492080/cloning-functional-components-in-react)
-207
View File
@@ -1,207 +0,0 @@
# Wolai-clone 思维导图方案(v3.1)——复刻 MindManager 体验并与 BlockNote 大纲双向同步
## 0. 背景与目标
- v3.0 架构已确定使用 Next.js + BlockNote + Yjs,需新增思维导图模块,实现与笔记大纲/块树实时联动,体验对齐 MindManager/Wolai/Kmind。
- 核心诉求:**极致整齐的自动布局**、**节点富内容(图片/富文本/图标/link)**、**跨文档/跨导图的链接能力**、**与笔记大纲双向同步**。
- 设计方向:继续使用 @xyflow/reactReact Flow 11+)作为画布内核,自研布局 & 样式层,数据完全托管在 Supabasedocuments 表)与独立 mindmap 表中。
## 1. 体验原则
1. **MindManager 级布局**:节点水平/垂直间距固定,主干左右对称;支持“经典曲线”“直角线”两种连线;节点宽度随内容自动适配,最多两行后溢出省略。
2. **富内容节点**:每个节点可包含标题、富文本摘要、图标、emoji、标签、封面图片;支持 Markdown/快捷键编辑。
3. **多级链接**:节点可跳转到:
- 当前文档某块(blockId)→ 通过 `block://{id}` 链接并高亮目标块。
- 其他笔记(documentId)→ 打开对应页面。
- 其他导图的节点(mindmapId+nodeId)→ 支持导图嵌套导航。
4. **实时同步**:导图结构映射到笔记大纲(Tree);改导图 = 改大纲;在 BlockNote 中增删标题块 = 导图自动更新节点。
5. **Wolai/Kmind 外观**:圆角矩形 + 轻投影,hover 显示手型 + 菜单,选中高亮;暗黑/亮色一致。支持缩放、平移、迷你地图。
## 2. 技术架构
```
BlockNote (Y.Doc: document_tree)
├─ outline nodes ↔ mindmap nodes (双向映射表)
Mindmap SubDoc (Y.Doc: mindmap_{document_id})
React Flow 画布 + 自研 MindLayout Engine
Supabase tables:
documents (content jsonb)
mindmap_meta (id, document_id, layout_prefs, theme)
mindmap_nodes (id, mindmap_id, block_id, parent_id, data jsonb, position cache)
```
- 每份文档默认有一份 mindmap(也支持多个 mindmap);mindmap 数据存于 `mindmap_nodes`,并同步缓存到 BlockNote content(便于离线/导出)。
- 协作层使用 Yjs SubDoc:当打开导图时订阅 `mindmap_{document_id}`React Flow 节点/边与 SubDoc 同步,保持毫秒级更新。
## 3. 视觉规格(融合 MindManager 细节)
- **节点样式**
- 尺寸:`min-w 240px`,默认 280px,最大 384px;高度随内容自动撑开,不超过两行,超出显示省略。
- 圆角 24px、2px 边框;选中 `border-blue-500 ring-4 ring-blue-100 scale-105`,未选中 `border-slate-300 hover:border-blue-400 hover:shadow-2xl`
- 背景渐变 `linear-gradient(135deg,#ffffff 0%,#f8fafc 100%)`,阴影 `0 4px 15px rgba(0,0,0,0.08)`;选中提升到 `0 10px 25px rgba(59,130,246,0.15)`
- 左上角支持图标徽章(36px 圆形),顶部可插入封面(最大高 160px),右侧有链接指示器。
- 示例 className
```tsx
const base = `
relative px-5 py-3 min-w-60 max-w-96 rounded-2xl
bg-white border-2 shadow-lg transition-all duration-200
`;
const active = 'border-blue-500 ring-4 ring-blue-100 scale-105';
const idle = 'border-slate-300 hover:border-blue-400 hover:shadow-2xl';
```
- **连线样式**
- MindManager 曲线:`M sx sy C sx+100 sy targetX-100 targetY targetX targetY`,颜色 `#94a3b8`,宽度 3px。
- 备选直角线(smoothstep)满足 Kmind 用户;末端 arrowhead 半径 6px。
- **画布与工具栏**
- 画布背景 `bg-gradient-to-br from-slate-50 to-slate-100`,使用 React Flow `Background` gap 24px。
- 顶部 56px 工具栏,包含自动排版、主题切换、导出、折叠/展开、缩放重置;工具栏按钮遵循 `px-4 py-2 rounded-lg` 规范。
## 4. 对齐 MindManager 的布局策略
1. **层级布局**:根节点居中,一级节点左右分布;根据用户设置(左/右/双向)动态计算。
2. **自动间距**:使用自研 `MindLayout Engine`(基于 DAG 布局),输入节点树 + 样式参数,输出绝对坐标;支持:
- 同层节点纵向等距排列。
- 子节点根据最长文本宽度自动算水平偏移,保证连线整齐。
- 支持节点折叠/展开,折叠时子树隐藏但保留布局缓存。
3. **连线样式**:提供“曲线(贝塞尔)”“直角线”两种;线条宽度、颜色可继承主题。
4. **对齐辅助**:拖动节点时展示辅助线,松手后自动吸附至推荐位置;支持 `Shift+拖拽` 只在水平或垂直方向移动。
## 5. 节点内容设计
字段结构(存储于 `mindmap_nodes.data`):
```json
{
"title": "节点标题",
"richText": "<p>富文本</p>",
"icon": "mdi-lightbulb",
"emoji": "💡",
"tags": ["优先级:高", "待讨论"],
"image": { "url": "...", "width": 200, "height": 120 },
"link": {
"type": "block" | "document" | "mindmap-node" | "url",
"targetId": "block_xxx / doc_xxx / mindmap_y/node_z",
"title": "跳转提示"
},
"status": "todo | doing | done",
"collapsed": false
}
```
- 富文本编辑器使用 `@tiptap/react` 迷你实例或 BlockNote 内嵌 mini editor,支持粗体/斜体/高亮/超链接。
- 图片可从 Supabase Storage 选择或粘贴上传,节点支持设置小型封面或内嵌图片。
- 节点图标:内置 MindManager 风格图标库(优先级、进度、标记),同时支持 emoji。
## 6. 链接和导航
| 类型 | 格式 | 交互 |
|------|------|------|
| 块链接 | `block://{blockId}` | hover 显示块摘要,点击在文档中滚动并高亮该块。 |
| 文档链接 | `doc://{documentId}` | 打开对应页面,可在侧边栏预览。 |
| 导图节点 | `mind://{mindmapId}/{nodeId}` | 若当前导图即目标,则定位;否则打开目标导图并定位。 |
| 外部 URL | `https://...` | 新标签页打开。 |
- 链接编辑面板提供快速搜索(块/页面/导图节点),并记录最近使用项。
- 支持「一键转块」:把导图节点转换为 BlockNote 内的块,或把块拖入导图生成节点。
## 7. 大纲与导图的双向同步
### 数据映射
- 维护 `outlineNodeId ↔ mindmapNodeId` 映射表,存储在 `mindmap_nodes.block_id` 字段。
- BlockNote 标题块(H1~H4)与导图层级对应;非标题块可作为节点备注(richText)。
### 同步策略
1. **导图 → 大纲**
- 新建节点:创建对应 BlockNote 标题块(Yjs 操作),插入到同级相应位置。
- 删除节点:删除/归档对应块。
- 拖动节点:更新块的父级和排序(BlockNote reorder API)。
2. **大纲 → 导图**
- 在大纲中新建/调整标题块时,触发同步 Hook 更新 mindmap SubDoc。
- 对于仅存在于导图的节点,可标记 `detached=true`,大纲不显示;用户可手动“附着”到大纲。
### 冲突处理
- 通过 Yjs transaction + `lastWriterWins` 策略;若同一节点同时被导图与大纲修改,变更合并后派生 diff,在 UI 中提示“有更新,点击同步”。
- 提供“锁定导图”开关,当导图处于演示模式时暂停同步,避免误差。
## 8. 前端组件分层
1. `MindmapCanvas`:封装 React Flow + 主题/布局;负责渲染节点、连线、背景、缩放器。
2. `MindLayoutEngine`:输入节点树返回位置/尺寸,支持缓存与局部更新。
3. `MindNodeCard`:节点内容组件,内嵌富文本、标签、图片、链接、hover toolbar。
4. `MindLinkPanel`:统一链接管理,搜索块/文档/导图节点。
5. `OutlineSyncController`:监听 BlockNote、导图 SubDoc,执行同步动作。
6. `MindHistorySidebar`:展示导图版本历史/快照(可依赖 Supabase table versions)。
## 9. 数据库 & API
```sql
create table mindmap_meta (
id uuid primary key default uuid_generate_v4(),
document_id uuid references documents(id),
title text,
layout_prefs jsonb, -- 左/右布局、连线样式、主题
theme text default 'wolai-light',
created_by uuid references auth.users,
created_at timestamptz default now(),
updated_at timestamptz default now()
);
create table mindmap_nodes (
id uuid primary key default uuid_generate_v4(),
mindmap_id uuid references mindmap_meta(id) on delete cascade,
parent_id uuid references mindmap_nodes(id),
block_id uuid, -- 可为空(纯导图节点)
order_index integer,
data jsonb,
cached_position jsonb,
created_at timestamptz default now(),
updated_at timestamptz default now()
);
```
- FastAPI Endpoints
- `GET /mindmaps/{id}`:返回 meta + nodes + outline mapping。
- `POST /mindmaps`:创建新导图(可附加到文档)。
- `PATCH /mindmaps/{id}`:更新布局/主题。
- `POST /mindmaps/{id}/nodes`、`PATCH /.../{nodeId}`、`DELETE ...`:供批量导入/AI/脚本使用。
- `POST /mindmaps/{id}/export`:导出为 MMAP、OPML、PNG、SVG。
## 10. 实施计划
| 阶段 | 目标 | 核心任务 | 输出 |
|------|------|----------|------|
| P0 需求确认(0.5d) | 对齐视觉规范 & 交互 | - 与设计确认节点样式、主题变量、连线风格<br>- 列出与大纲同步的详细规则/边界<br>- 明确链接搜索/跳转需求 | 更新 PRD + Figma 草图 |
| P1 数据与 API1.5d | 建表 + 后端接口 | - Supabase 建 `mindmap_meta`/`mindmap_nodes` + RLS<br>- FastAPI mindmap CRUD + 导出占位<br>- 单元测试覆盖 | 后端 PR + Swagger |
| P2 前端内核(2d | React Flow + 布局引擎 | - 封装 `MindLayoutEngine`,实现左右对称/直角/曲线<br>- 节点/连线主题实现,支持折叠/缩放 | 画布 demo |
| P3 节点富内容(2d) | 节点编辑能力 | - 富文本/图标/图片/标签/状态组件<br>- 链接面板 + 搜索块/文档/导图节点<br>- 粘贴逻辑(从 MindManager/文本导入) | 节点交互完成 |
| P4 同步控制器(2d) | 导图 ↔ 大纲 | - 建立 block ↔ node 映射 SubDoc<br>- 实现双向增删改同步/冲突提示<br>- E2E 测试(Cypress + Playwright 多窗口) | 同步稳定 |
| P5 打磨 & 导出(1.5d | 性能/体验 | - 虚拟化/懒加载,支持 2k+ 节点<br>- 导出 PNG/SVG/OPML<br>- 快捷键、迷你地图、历史记录 | 发布候选 |
## 11. 验收标准
- [ ] 导图布局与 MindManager 对齐:节点间距统一,拖动自动吸附。
- [ ] 节点支持图片/富文本/图标/标签/链接,复制 MindManager 内容可无损粘贴。
- [ ] 节点链接在不同类型 (block/doc/mind/node) 下跳转准确,带 hover 预览。
- [ ] 与 BlockNote 大纲实时同步,双向修改 200ms 内可见,无冲突。
- [ ] 多人协作时节点光标、拖拽、折叠状态实时共享,无明显抖动。
- [ ] 支持导出 PNG/SVG/OPML/MMAP(后续),可导入 MindManager 文件(可放在扩展阶段)。
---
## 12. 落地提示
- P2 阶段可直接引入 `grok` 方案中的 `MindManagerNode`/`MindManagerEdge` 作为主题模板,结合 Tailwind CSS 变量封装成 `MindNodeCard` 多主题体系。
- 布局计算使用 elkjs`algorithm=layered`、`elk.layered.nodePlacement.strategy=BRANDES_KOEPF`),封装 `getMindManagerLayout` 并缓存,确保自动排版按钮在 200ms 内完成 500+ 节点重新布局。
- 同步控制器上线前,编写 OPML/MMAP → `mindmap_nodes` 的导入脚本,用真实 MindManager 数据验证字段与链接策略,并准备导图 ↔ 大纲操作回放工具以排查协作冲突。
此方案在保留 MindManager 用户习惯的同时,借助 Supabase + React Flow 构建可扩展的思维导图系统,并与 BlockNote 文档深度联动。按 P0-P5 推进,可在 1.5~2 周内完成 MVP 并持续迭代高级功能。
-20
View File
@@ -1,20 +0,0 @@
__核心架构:__ 新表格块 `OnlineTableBlock` 将仅在 Blocknote 文档中存储一个 `tableId`,实际表格数据和协同编辑将通过 `tableId` 关联 Supabase 表 (`document_tables` / `document_table_rows`),并由 Luckysheet 在全屏模式下处理。
__数据模型 (`src/types/online-table.ts`)__ 定义表格元数据和支持的列类型(文本、数字、货币、日期、选项、复选、公式、引用)。
__阶段一:Blocknote 集成与数据准备__
| 步骤 | 目标 | 涉及文件 | 详细说明 | | :--- | :--- | :--- | :--- | | 1.1 | 创建数据模型 | `wolai-frontend/src/types/online-table.ts` | 定义表格的 TS 类型,包括列定义 (`TableSchema` / `ColumnType`) 和行数据结构。 | | 1.2 | 创建服务逻辑 | `wolai-frontend/src/lib/online-table.ts` | 创建用于调用后端 API 创建新表格的函数 `createOnlineTable(documentId: string)`。 | | 1.3 | 注册自定义块 | `wolai-frontend/src/components/editor/blocks/OnlineTableBlock.tsx` | 定义 `onlineTableBlock` 的 Block Spec,属性为 `{ tableId: string }`。 | | 1.4 | 更新 Schema | `wolai-frontend/src/components/editor/schema.ts` | 导入并注册 `onlineTableBlock`。 | | 1.5 | 集成 Slash Menu | `wolai-frontend/src/components/editor/menus/CustomSlashMenu.tsx` | 添加“在线表格”菜单项,点击后调用 `createOnlineTable` 并插入 `{ type: 'onlineTable', props: { tableId: newId } }` 块。 |
__阶段二:紧凑模式(内联视图)实现__
| 步骤 | 目标 | 涉及文件 | 详细说明 | | :--- | :--- | :--- | :--- | | 2.1 | 创建预览组件 | `wolai-frontend/src/components/online-table/CompactTablePreview.tsx` | 实现一个轻量级组件,用于查询 Supabase 获取表格元数据和前 N 行数据(N=3或5),并以紧凑模式渲染。 | | 2.2 | 实现轻编辑 UI | `CompactTablePreview.tsx` | 实现列宽拖拽、单元格批量粘贴的输入逻辑(可能需要依赖一个小型网格库或自建简化网格)。 | | 2.3 | 实现 Hover Toolbar | `CompactTablePreview.tsx` | 鼠标悬停时显示工具栏,包含“进入全屏”按钮和“插入/删除行列”的轻编辑操作。 | | 2.4 | 渲染 Block | `OnlineTableBlock.tsx` | 在 Blocknote 中使用 `CompactTablePreview` 组件。 |
__阶段三:全屏模式(Luckysheet 完整体验)实现__
| 步骤 | 目标 | 涉及文件 | 详细说明 | | :--- | :--- | :--- | :--- | | 3.1 | 创建全屏组件 | `wolai-frontend/src/components/online-table/FullScreenTableEditor.tsx` | 封装 Luckysheet 和 Yjs 绑定,确保全屏模式下加载指定 `tableId` 的协同数据。 | | 3.2 | 实现模式切换 | `OnlineTableBlock.tsx` / `CompactTablePreview.tsx` | 实现双击或点击工具栏按钮时,通过 Next.js 路由或 Modal 导航到 `/tables/[tableId]/edit` (或打开全屏 Modal)。 | | 3.3 | 增强编辑功能 | `FullScreenTableEditor.tsx` | 配置 Luckysheet,实现冻结首行列、批量填充、组合快捷键、单元格格式和多种列类型支持。 |
__阶段四:后端 API 完善__
| 步骤 | 目标 | 涉及文件 | 详细说明 | | :--- | :--- | :--- | :--- | | 4.1 | 数据库表确认 | N/A | 确认 Supabase 中的 `document_tables``document_table_rows` 已存在。 | | 4.2 | 实现 CRUD API | `wolai-backend/app/api/tables.py` | 实现表格的创建、获取元数据、获取行数据的 API,供前端服务调用。 |
-179
View File
@@ -1,179 +0,0 @@
# Wolai-clone 协同表格方案(v3.1)——Luckysheet 体验 × Supabase 数据底座
## 0. 背景与目标
- 当前 v3.0 架构已经完成 BlockNote + Yjs + Hocuspocus + Supabase Realtime 的基础协同能力,内置块类型仅支持「简单表格」,无法满足飞书/钉钉/金山文档级交互。
- 新版本需要让用户在文档任意位置插入「在线表格块」,可在文档内预览、可全屏编辑、可多人实时协作,并支持 Excel 级功能(公式、条件格式、批量粘贴等)。
- 同时,考虑到未来要把表格作为独立数据对象(多处引用、导出、AI/OCR 写入等),必须把表格数据与 BlockNote 主文档解耦,避免大表拖垮文档 CRDT。
## 1. 设计原则
1. **用户心智优先**:界面与交互最大程度贴近 Excel/WPS/飞书表格,复制粘贴零障碍。
2. **数据解耦**BlockNote 块只保存 `tableId` 与视图参数,真实表格数据落在 Supabase + 独立 Yjs SubDoc,确保性能、复用与权限扩展。
3. **协同一致性**:复用现有 Yjs/Hocuspocus/Supabase Realtime 通道,内嵌/全屏共享同一数据源,支持多人光标与冲突解决。
4. **渐进增强**:先满足核心编辑体验,再分阶段接入高级能力(导入导出、公式库、与 MinerU/AI 的互通)。
## 2. 技术选型与理由
| 维度 | 方案 | 理由 |
|------------------|--------------------------------|------|
| 表格 UI/编辑器 | Luckysheet (@dream-num) | Excel 级 UI/工具栏/公式/条件格式,复制粘贴兼容 WPS/飞书/钉钉,支持 headless 嵌入。 |
| 数据渲染内核 | Luckysheet + 虚拟滚动 | 经过大规模验证,500×100 也能稳定运行。 |
| 实时协作 | Yjs SubDoc + luckysheet-yjs-binding | 官方 binding,直接复用现有 Hocuspocus Provider;每张表格一个 SubDoc,避免主文档体积膨胀。 |
| 数据持久化 | Supabase Postgres (`document_tables`, `document_table_rows`) + 定时快照 | 让表格成为独立实体,可复用/引用/授权,同时方便后台 API、导出和 AI 写入。 |
| 内联预览 | 自研 React 轻量预览组件 + 行列摘要 | 保持文档流畅,默认展示前 5 行,支持 hover 工具条。 |
| 全屏编辑 | `<FullScreenTableEditor>` + Luckysheet | 提供 Excel 式工具栏、状态栏、协作者指示、暗黑模式。 |
| 公式引擎 | Luckysheet 内置 + HyperFormula(后续增强) | 先满足 SUM/IF 等高频场景,再按需升级。 |
> 放弃:自研 Data Grid(周期长)、Handsontable(商业授权)、AG-Grid(过重)、Tiptap Table(功能不足)。
## 3. 架构概览
```
┌───────────────────────────┐
│ Next.js 前端 │
│ ┌───────────────────────┐ │
│ │ BlockNote (文档 Y.Doc) │ │
│ │ └─ TableBlock │ │
│ │ attrs: {tableId} │ │
│ └───────────────────────┘ │
│ │引用 │
│ ┌───────────────────────┐ │
│ │ LuckysheetProvider │ │
│ │ └─ Y.SubDoc(tableId)│ │
│ │ └─ Supabase Realtime│ │
│ └───────────────────────┘ │
└────────────┬──────────────┘
│REST/RPC
┌────────────▼──────────────┐
│ FastAPI 后端 + Supabase │
│ ├─ document_tables │
│ ├─ document_table_rows │
│ ├─ RLS / Realtime 触发器 │
│ └─ 导出/导入/AI 接口 │
└───────────────────────────┘
```
- **BlockNote 主文档**:仅存 `tableId`、标题、视图配置等轻量信息。
- **表格 Y.SubDoc**:按需加载;Luckysheet 与之绑定,实现实时协作。
- **Supabase 数据**:提供持久化/查询/权限/导出能力;Yjs 定时把最新快照写入数据库,同时监听数据库变化以支持非 Web 客户端。
## 4. 数据模型
```sql
create table document_tables (
id uuid primary key default gen_random_uuid(),
workspace_id uuid not null references workspaces(id) on delete cascade,
document_id uuid not null references documents(id) on delete cascade,
title text not null default '未命名表格',
schema jsonb not null default '{}'::jsonb, -- 列定义/格式/宽度
view_preferences jsonb not null default '{}'::jsonb,
is_archived boolean not null default false,
snapshot jsonb,
last_synced_at timestamptz,
created_by uuid references profiles(id) on delete set null,
updated_by uuid references profiles(id) on delete set null,
created_at timestamptz not null default timezone('utc', now()),
updated_at timestamptz not null default timezone('utc', now())
);
create table document_table_rows (
id uuid primary key default gen_random_uuid(),
workspace_id uuid not null references workspaces(id) on delete cascade,
document_id uuid not null references documents(id) on delete cascade,
table_id uuid not null references document_tables(id) on delete cascade,
row_index integer not null, -- 稳定排序键
row_data jsonb not null default '{}'::jsonb, -- 单元格字典,按列 key 存储
row_hash text,
is_deleted boolean not null default false,
updated_by uuid references profiles(id) on delete set null,
created_at timestamptz not null default timezone('utc', now()),
updated_at timestamptz not null default timezone('utc', now())
);
```
- ✅(2025-02-22`supabase_local` 已执行建表 + 索引 + 触发器 + RLS,迁移文件:`supabase/migrations/20250222_add_document_tables.sql`
- RLS:沿用 `workspace_members``document_tables`/`document_table_rows` 在写入前通过触发器校验 workspace/document 一致,所有读写均需加入同一 workspace。
- 每张表格的实时编辑依赖 Yjs SubDoc;后台任务(导出 CSV/Excel、AI 填表、MinerU OCR 转表)通过 REST API 操作数据库。
- BlockNote 块示例:
```json
{
"id": "block_xxx",
"type": "table",
"props": {
"tableId": "d3d7-...",
"displayMode": "compact",
"previewRows": 5
}
}
```
## 5. 功能模块拆解
1. **块插入与预览**
- 斜杠命令 `/table`、工具栏按钮、AI 建议等入口。
- 默认创建 10×5 空表,预览显示前 5 行 + 列头,hover 显示快速操作(新建/全屏/复制链接)。
2. **全屏编辑器**
- Excel 风格工具栏(行列操作、合并、筛选、条件格式、公式插入、单元格格式)。
- 协作者状态:顶部显示头像、单元格边框高亮。
- 暗黑模式、移动端强制全屏。
3. **数据同步**
- Luckysheet ↔ Y.SubDoc:官方 binding。
- SubDoc ↔ Supabase:节流(1-2s)后写入 `document_tables`/`document_table_rows`;大表行级 diff。
- 后端提供导出 CSV/Excel、批量导入 API。
4. **高级能力**
- 条件格式模版、冻结行列、查看历史版本。
- 允许多个文档引用同一 `tableId`(只读/编辑权限可控)。
- AI/ MinerU 写表:后端接口 `/api/v1/tables/{id}/rows` 支持 append/merge。
## 6. 实施计划
| 阶段 | 时间 | 目标 | 关键任务 | 产出 / 验收 |
|------|------|------|----------|-------------|
| P0 需求巩固 | 0.5d | 明确体验 & 数据需求 | - 联合设计/产品确认预览、高级功能优先级<br>- 定义列类型 MVP(文本/数字/货币/日期/选择/复选/公式/引用)<br>- 确认与 MinerU/AI 的接口需求 | 更新 PRD + 交互稿 |
| P1 数据面准备 | 1d | 建 Supabase 表 & API | - 执行 SQL 建表 + RLS + 触发器(Supabase Realtime 通知)<br>- FastAPI 增加 `/tables` CRUD、`/tables/{id}/rows` 批量读写<br>- 单元测试 + API 文档 | 后端分支 + Swagger |
| P2 Yjs/Luckysheet 骨架 | 2d | 协同内核跑通 | - 引入 @dream-num/luckysheet、luckysheet-yjs-binding<br>- 实现 `<LuckysheetProvider>`,封装 SubDoc 生命周期、连接状态提示<br>- 支持单独打开表格编辑页(不依赖 BlockNote) | 可共享链接、协同无冲突 |
| P3 BlockNote 表格块 & 预览 | 2d | 文档内插入与缩略展示 | - 自定义 block schema + slash command<br>- 预览组件(前5行、hover toolbar、loading/错误态)<br>- 表格块和 Supabase 数据绑定(创建时写表、删除时软删) | 文档里可插入表格并跳转全屏 |
| P4 全屏编辑器 & 工具栏 | 3d | 飞书式编辑体验 | - `<FullScreenTableEditor>`:工具栏、状态栏、协作者列表<br>- 支持插入/删除/合并/冻结/条件格式/基础公式<br>- 完善暗黑/移动端 UI | 500×50 表格流畅、多端一致 |
| P5 持久化 & 性能 | 2d | 数据安全 & 优化 | - SubDoc → Supabase 节流、row-level diff<br>- 历史快照(定时写入 table_versions<br>- 预览模式虚拟滚动、全屏懒加载资源 | 刷新不丢数据、大表不卡顿 |
| P6 高级扩展 | 2d+ | 导入导出 & AI | - CSV/Excel 导入导出<br>- MinerU OCR 输出自动转表 + 写入<br>- 表格引用共享、权限控制 UI | E2E 测试 + 文档 |
> 若时间紧,可先完成 P0~P4 形成可用 MVP,之后再迭代 P5/P6。
**进展速记(2025-02-22**
- ✅ P1「执行 SQL 建表 + RLS + 触发器」已完成(详见上文数据模型 & 迁移文件),剩余 `/tables`/`/tables/{id}/rows` API + 单元测试待开发。
- ✅ P2「Luckysheet Provider + Yjs SubDoc 骨架」:Next.js 侧封装 `LuckysheetCanvas`,复用 Hocuspocus 通道,表格编辑自动广播到 Y.Doc 与 Supabase snapshot。
- ✅ P3「BlockNote 表格块 & 预览」:自定义 `tableBlock` + slash `/table`,支持在线创建、缩略展示、重命名、全屏入口。
- ✅ P4「全屏编辑器」:落地 `<TableEditorOverlay>`,集成 Luckysheet 工具栏、保存状态、协同提示,500×50 表格实测流畅。
## 7. 依赖与工具
```bash
pnpm add @dream-num/luckysheet luckysheet-yjs-binding @hocuspocus/provider yjs y-protocols zustand lucide-react
# 后端
pip install fastapi supabase-py psycopg[binary] uvicorn[standard]
```
- 设计稿:Figma(飞书表格参考)。
- 协同测试:Cypress + Playwright(多客户端场景)。
## 8. 验收清单
- [ ] `/table` 插入后出现缩略预览,支持命名/重命名。
- [ ] 多端同时编辑同一表,光标位置、单元格内容同步延迟 < 800ms。
- [ ] 刷新或重连后表格数据一致,Supabase 中有对应记录。
- [ ] 全屏编辑支持:插入/删除行列、调整列宽、冻结、合并、条件格式、公式 (SUM/AVERAGE/IF)。
- [ ] 支持从 Excel/WPS/飞书/钉钉直接粘贴,保留行列和基础格式。
- [ ] CSV/Excel 导出可用,至少支持 UTF-8。
- [ ] 移动端默认全屏,横屏提示可编辑。
## 9. 后续路线(与整体架构衔接)
1. **多视图**:基于同一 `tableId` 派生看板、画廊,复用 Supabase 数据。
2. **AI Assist**:支持「生成表格」「自动补数据」「智能公式建议」,后端复用 LightRAG + GPT-4o。
3. **OCR 写表**:MinerU 解析复杂表格后直接写入 `document_table_rows`,用户可在 Luckysheet 中二次确认。
4. **权限与共享**: 表格可独立分享/只读/协作,并在后台记录操作日志。
此方案兼顾用户熟悉的 Excel 体验与 Supabase 数据化能力,为后续多视图、AI、OCR 等模块铺平道路。接下来可按 P0~P4 推进 MVP,实现飞书级表格块体验。*** End Patch