协同编辑(中文译文)
原始 DeepWiki 页面:https://deepwiki.com/open-webui/open-webui/9.3-collaborative-editing
翻译时间:2026-06-09T16:09:53.805Z
翻译模型:deepseek-chat
原文字符数:9505
项目:Open WebUI (open-webui)
---
协作编辑
相关源文件
以下文件为本 Wiki 页面的生成提供了上下文:
backend/open_webui/socket/main.pybackend/open_webui/socket/utils.pybackend/open_webui/tasks.pybackend/open_webui/test/util/test_redis.pybackend/open_webui/utils/rate_limit.pybackend/open_webui/utils/redis.pysrc/lib/components/icons/AdjustmentsHorizontalOutline.sveltesrc/lib/components/icons/ArrowUpLeft.sveltesrc/lib/components/notes/NoteEditor.sveltesrc/lib/components/notes/NoteEditor/Chat.sveltesrc/lib/components/notes/NoteEditor/Chat/Message.sveltesrc/lib/components/notes/NoteEditor/Chat/Messages.sveltesrc/lib/components/notes/NoteEditor/Controls.sveltesrc/lib/components/notes/NotePanel.svelte
目的与范围
本文档介绍 Open WebUI 富文本编辑器组件中内置的实时协作编辑系统。该系统允许多个用户同时编辑同一文档,并利用无冲突复制数据类型(CRDT)自动解决冲突。变更通过 WebSocket 连接实时同步,并显示远程用户的光标和选区等视觉指示。
该系统主要用于笔记系统(NoteEditor.svelte),支持多用户在共享笔记上进行协作 src/lib/components/notes/NoteEditor.svelte:188-196。
---
架构概览
协作编辑系统集成了三个层次:
- CRDT 层:
yjs提供Y.DocCRDT 结构,支持无需加锁的并发编辑。 - 传输层:
SocketIOCollaborationProvider通过socket.io-client管理 WebSocket 通信。 - 编辑器集成层:
y-prosemirror将 ProseMirror 文档状态绑定到 Yjs 的Y.Doc。
系统组件图
graph TB
subgraph ClientA["客户端 A 浏览器 (RichTextInput.svelte)"]
EditorA["editor: Editor<br/>(Tiptap/ProseMirror)"]
YDocA["ydoc: Y.Doc<br/>(Yjs CRDT)"]
ProviderA["provider:<br/>SocketIOCollaborationProvider"]
SocketA["socket: Socket<br/>(socket.io-client)"]
end
subgraph ClientB["客户端 B 浏览器 (RichTextInput.svelte)"]
EditorB["editor: Editor<br/>(Tiptap/ProseMirror)"]
YDocB["ydoc: Y.Doc<br/>(Yjs CRDT)"]
ProviderB["provider:<br/>SocketIOCollaborationProvider"]
SocketB["socket: Socket<br/>(socket.io-client)"]
end
subgraph Backend["backend/open_webui/socket/main.py"]
SocketIO["AsyncServer<br/>(python-socketio)"]
YDocManager["YdocManager<br/>(Redis 后端)"]
Redis["REDIS<br/>(get_redis_connection)"]
end
EditorA -->|"ProseMirror 事务"| YDocA
YDocA -->|"Y.updates"| ProviderA
ProviderA --> SocketA
SocketA <-->|"WebSocket 事件<br/>'document-update'"| SocketIO
EditorB -->|"ProseMirror 事务"| YDocB
YDocB -->|"Y.updates"| ProviderB
ProviderB --> SocketB
SocketB <-->|"WebSocket 事件<br/>'document-update'"| SocketIO
SocketIO <--> YDocManager
YDocManager <--> Redis
来源: backend/open_webui/socket/main.py:73-84、backend/open_webui/socket/main.py:167-170、backend/open_webui/socket/utils.py:124-135、src/lib/components/notes/NoteEditor.svelte:37-41
---
基于 Yjs 的 CRDT 基础
Yjs 是一种 CRDT 实现,能够实现共享数据结构的无冲突同步。在 Open WebUI 中,Yjs 维护一个共享的文档表示,多个编辑器可以同时修改而无需显式协调。
初始化条件
当 RichTextInput.svelte 组件满足特定条件时,协作系统会初始化:
| 变量 | 来源 | 用途 |
|---|---|---|
collaboration | 组件属性 | 启用协作模式 |
documentId | 组件属性 | 唯一的 Y.Doc 标识符(通常是笔记 ID) |
socket | $socket 存储 | Socket.IO 连接实例 |
user | $user 存储 | 当前用户,用于感知状态 |
提供者(provider)采用动态导入,以避免在非协作模式下加载协作依赖。在后端,pycrdt 用于 Python 环境中的 CRDT 操作 backend/open_webui/socket/main.py:10。
来源: backend/open_webui/socket/main.py:10、src/lib/components/notes/NoteEditor.svelte:37-41、src/lib/components/notes/NoteEditor.svelte:103-106
---
后端管理:YdocManager
后端使用 backend/open_webui/socket/utils.py 中定义的 YdocManager 处理文档更新和持久化。它同时支持内存存储和 Redis 后端存储 backend/open_webui/socket/utils.py:137-151。
压缩策略
为防止更新列表无限增长,YdocManager 在更新数量超过 COMPACTION_THRESHOLD(500)时,会执行滚动压缩策略 backend/open_webui/socket/utils.py:125。
# backend/open_webui/socket/utils.py:152-166
async def _compact_updates_redis(self, document_id: str):
"""滚动压缩:将最旧的一半更新压缩为一个快照。"""
redis_key = f'{self._redis_key_prefix}:{document_id}:updates'
all_updates = await self._redis.lrange(redis_key, 0, -1)
if len(all_updates) <= 1:
return
mid = len(all_updates) // 2
ydoc = Y.Doc()
for raw in all_updates[:mid]:
ydoc.apply_update(bytes(json.loads(raw)))
snapshot = json.dumps(list(ydoc.get_update()))
pipe = self._redis.pipeline()
pipe.delete(redis_key)
pipe.rpush(redis_key, snapshot, *all_updates[mid:])
await pipe.execute()
来源: backend/open_webui/socket/utils.py:124-166、backend/open_webui/socket/main.py:167-170
---
Tiptap 编辑器集成
RichTextInput.svelte 组件将 Yjs 与 ProseMirror 的文档模型集成。启用协作时,编辑器的初始内容从提供者同步,而非使用本地状态。
协作数据流
sequenceDiagram
participant User as "用户 (NoteEditor.svelte)"
participant Socket as "socket (Socket.IO)"
participant Srv as "main.py (Socket 服务器)"
participant YMgr as "YdocManager (utils.py)"
participant Redis as "Redis (存储)"
User->>Socket: emit('join-note', {note_id})
Socket->>Srv: @sio.on('join-note')
Srv->>YMgr: get_updates(document_id)
YMgr->>Redis: LRANGE updates
Redis-->>YMgr: 二进制更新
YMgr-->>Srv: 合并后的更新
Srv-->>Socket: emit('note-events', updates)
Socket-->>User: noteEventHandler(updates)
来源: src/lib/components/notes/NoteEditor.svelte:188-196、backend/open_webui/socket/main.py:167-170、backend/open_webui/socket/utils.py:179-188
---
初始化与生命周期
协作系统遵循特定的初始化顺序,以确保在开始编辑前完成正确的同步。
生命周期管理
在 NoteEditor.svelte 中,当用户通过 Socket.IO 加入笔记会话时,协作开始:
// src/lib/components/notes/NoteEditor.svelte:188-196
if (note?.write_access) {
$socket?.emit('join-note', {
note_id: id,
auth: {
token: localStorage.token
}
});
$socket?.on('note-events', noteEventHandler);
}
后端 YdocManager 负责将这些更新持久化到 Redis,确保新用户加入时,能通过 get_updates 获取完整的文档历史 backend/open_webui/socket/utils.py:179-188。
来源: src/lib/components/notes/NoteEditor.svelte:188-196、backend/open_webui/socket/utils.py:137-151
---
状态同步机制
Yjs 实现了一种基于操作冲突解决的 CRDT。Y.Doc 结构使用向量时钟维护因果顺序,确保无论网络延迟如何,都能实现确定性收敛。
同步流程
- 本地编辑:用户在 TipTap 编辑器中做出更改。
- CRDT 更新:更改被转换为二进制 Yjs 更新。
- 传输:客户端通过 Socket.IO 发出
document-update事件。 - 后端处理:
YdocManager.append_to_updates将更新保存到 Redisbackend/open_webui/socket/utils.py:137-144。 - 广播:服务器将更新广播给同一文档房间内的所有其他客户端。
- 远程集成:其他客户端接收更新并将其应用到本地的
Y.Doc。
如果 Redis 中的更新数量过大,将触发 _compact_updates_redis 将历史记录压缩为单个快照 backend/open_webui/socket/utils.py:152-166。
来源: backend/open_webui/socket/utils.py:137-166、backend/open_webui/socket/main.py:167-170