笔记文件附件(中文译文)
原始 DeepWiki 页面:https://deepwiki.com/open-webui/open-webui/9.6-file-attachments-in-notes
翻译时间:2026-06-09T16:10:23.706Z
翻译模型:deepseek-chat
原文字符数:20870
项目:Open WebUI (open-webui)
---
笔记中的文件附件
相关源文件
以下文件被用作生成此 wiki 页面的上下文:
backend/open_webui/routers/files.pybackend/open_webui/routers/knowledge.pysrc/lib/apis/files/index.tssrc/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 笔记功能中的文件附件系统。内容涵盖文件的上传、存储、协作者间同步,以及与笔记中 AI 功能的集成。文件附件使用户能够通过文档、图片、音频和视频文件来丰富笔记内容,并自动提取内容以支持 AI 增强的工作流程。
关于笔记编辑器的富文本编辑功能,请参阅 9.1 TipTap 编辑器架构。关于笔记内容的协作编辑,请参阅 9.3 协作编辑。关于使用文件内容的 AI 集成功能,请参阅 9.4 笔记中的 AI 集成。
---
文件附件架构
笔记中的文件附件系统由多个关键组件组成,它们协同工作以提供无缝的文件管理体验。
系统实体关系
graph TB
User["用户"]
NoteEditor["NoteEditor.svelte<br/>(主组件)"]
Controls["Controls.svelte<br/>(设置面板)"]
Chat["Chat.svelte<br/>(AI 助手)"]
UploadAPI["uploadFile()<br/>src/lib/apis/files/index.ts"]
GetFileAPI["getFileById()<br/>src/lib/apis/files/index.ts"]
UpdateNoteAPI["updateNoteById()<br/>src/lib/apis/notes.ts"]
FileStorage[("文件存储<br/>(S3/本地)")]
ContentExtraction["内容提取<br/>服务"]
SQLDatabase[("SQL 数据库<br/>NoteTable / FilesTable")]
SocketIO["Socket.IO<br/>实时同步"]
User -->|"拖放 / 选择"| NoteEditor
NoteEditor -->|"upload_file_handler()<br/>backend/open_webui/routers/files.py"| UploadAPI
UploadAPI -->|"存储文件"| FileStorage
UploadAPI -->|"process_uploaded_file()<br/>backend/open_webui/routers/files.py"| ContentExtraction
UploadAPI -->|"返回元数据"| NoteEditor
NoteEditor -->|"getFileById()"| GetFileAPI
GetFileAPI -->|"检索文件 + 内容"| FileStorage
GetFileAPI -->|"返回文件对象"| NoteEditor
NoteEditor -->|"changeDebounceHandler()"| UpdateNoteAPI
UpdateNoteAPI -->|"保存 note.data.files"| SQLDatabase
NoteEditor -->|"files 数组"| Controls
NoteEditor -->|"files 数组"| Chat
NoteEditor -->|"emit('join-note')"| SocketIO
SocketIO -->|"noteEventHandler()"| NoteEditor
SocketIO -.->|"同步文件"| SQLDatabase
Controls -->|"显示/移除"| User
Chat -->|"file.file.data.content"| ContentExtraction
来源:src/lib/components/notes/NoteEditor.svelte:101-182,src/lib/components/notes/NoteEditor/Controls.svelte:1-40,src/lib/components/notes/NoteEditor/Chat.svelte:1-70,backend/open_webui/routers/files.py:105-175,backend/open_webui/routers/files.py:200-210
---
文件数据结构
附加到笔记的文件遵循特定的数据结构,用于跟踪上传状态、元数据和内容。
文件对象模式
| 字段 | 类型 | 描述 | |
|---|---|---|---|
id | string \ | null | 后端分配的唯一文件标识符 backend/open_webui/routers/files.py:177 |
itemId | string | 上传期间的临时 UUID src/lib/components/notes/NoteEditor.svelte:410-411 | |
type | string | 文件类型('file' 或 'image')src/lib/components/notes/NoteEditor.svelte:413-415 | |
file | object \ | string | 文件元数据和提取的内容 backend/open_webui/models/files.py:32-38 |
name | string | 原始文件名 backend/open_webui/models/files.py:32-38 | |
url | string | 文件 URL 或 ID 引用 | |
size | number | 文件大小(字节)backend/open_webui/models/files.py:32-38 | |
collection_name | string | 关联的知识库集合 | |
content_type | string | MIME 类型 backend/open_webui/models/files.py:32-38 | |
status | string | 上传状态('uploading' 或 'uploaded') | |
error | string | 上传失败时的错误消息 src/lib/components/notes/NoteEditor.svelte:471-477 |
存储位置
文件存储在笔记的数据结构中:
note.data.files- 持久化到数据库的文件对象数组src/lib/components/notes/NoteEditor.svelte:110-118- 每次通过
updateNoteById()更新笔记时都会包含文件src/lib/components/notes/NoteEditor.svelte:209-218
来源:src/lib/components/notes/NoteEditor.svelte:408-420,src/lib/components/notes/NoteEditor.svelte:209-218,backend/open_webui/models/files.py:32-38
---
文件上传流程
文件上传工作流处理文件选择、内容提取以及与协作者的同步。
代码到逻辑的映射
sequenceDiagram
participant User
participant NoteEditor as "NoteEditor.svelte"
participant uploadFileAPI as "uploadFile()<br/>src/lib/apis/files/index.ts"
participant FilesRouter as "files.py<br/>@router.post('/')"
participant ProcessFileFunc as "process_uploaded_file()<br/>files.py"
participant ContentExtraction as "内容提取引擎"
participant Storage as "存储提供者"
participant Database as "FilesTable / NoteTable"
participant SocketIO as "Socket.IO"
participant Collaborator
Note over NoteEditor,Collaborator: 两个用户加入笔记房间
User->>NoteEditor: 拖放 / 选择文件
NoteEditor->>NoteEditor: "uploadFileHandler(file)"
NoteEditor->>NoteEditor: "创建临时 fileItem<br/>(itemId=uuidv4())"
NoteEditor->>NoteEditor: "files.push(fileItem)<br/>(status='uploading')"
NoteEditor->>NoteEditor: "打开 Controls 面板"
alt 音频/视频文件
NoteEditor->>NoteEditor: "设置 metadata.language<br/>(来自 settings.audio.stt.language)"
end
NoteEditor->>uploadFileAPI: "uploadFile(token, file, metadata)"
uploadFileAPI->>FilesRouter: "POST /files/"
FilesRouter->>Storage: "保存文件到磁盘/云"
FilesRouter->>ProcessFileFunc: "process_uploaded_file(request, file, file_path, file_item, metadata, user, db)"
ProcessFileFunc->>ContentExtraction: "提取文本内容(例如,转录音频、OCR 图片)"
ContentExtraction-->>ProcessFileFunc: "提取的内容"
ProcessFileFunc->>Database: "用内容和状态更新 file_item"
FilesRouter-->>uploadFileAPI: "{id, collection_name, error}"
uploadFileAPI->>NoteEditor: "文件元数据和处理状态"
NoteEditor->>NoteEditor: "fileItem.status='uploaded'<br/>fileItem.file=file<br/>fileItem.id=id"
NoteEditor->>Database: "updateNoteById(token, id,<br/>{data: {files}})"
Database-->>NoteEditor: "成功"
NoteEditor->>SocketIO: "笔记更新事件"
SocketIO->>Collaborator: "noteEventHandler()"
Collaborator->>Collaborator: "同步 files 数组"
来源:src/lib/components/notes/NoteEditor.svelte:407-478,src/lib/components/notes/NoteEditor.svelte:201-219,backend/open_webui/routers/files.py:105-175,backend/open_webui/routers/files.py:200-210,src/lib/apis/files/index.ts:4-95
上传实现
uploadFileHandler() 函数实现了完整的上传工作流:
- 创建临时项:生成一个 UUID 用于上传期间的跟踪
src/lib/components/notes/NoteEditor.svelte:410-411。 - 添加到文件数组:立即显示状态为 'uploading' 的文件
src/lib/components/notes/NoteEditor.svelte:419-420。 - 打开 Controls 面板:自动显示设置面板以便查看
src/lib/components/notes/NoteEditor.svelte:429-434。 - 提取元数据:对于音频/视频,包含 STT 语言偏好
src/lib/components/notes/NoteEditor.svelte:438-446。 - 上传文件:调用
uploadFile()API 并传入文件和元数据src/lib/components/notes/NoteEditor.svelte:449-449。这会调用后端路由POST /files/src/lib/apis/files/index.ts:23-30。 - 内容提取:后端的
process_uploaded_file()函数根据文件类型和配置的引擎处理内容提取(例如,音频使用transcribe(),其他使用process_file())backend/open_webui/routers/files.py:105-175。 - 检索完整文件:调用
getFileById()获取包含内容的完整文件对象src/lib/components/notes/NoteEditor.svelte:460-463。 - 更新状态:将状态更改为 'uploaded' 并填充文件数据
src/lib/components/notes/NoteEditor.svelte:467-468。 - 持久化到笔记:通过
changeDebounceHandler()防抖保存到note.data.filessrc/lib/components/notes/NoteEditor.svelte:201-219。
来源:src/lib/components/notes/NoteEditor.svelte:407-478,src/lib/components/notes/NoteEditor.svelte:201-219,backend/open_webui/routers/files.py:105-175,src/lib/apis/files/index.ts:4-95
---
内容提取
文件在上传期间会自动进行内容提取,以支持 AI 功能。
提取能力
内容提取服务处理各种文件类型:
| 文件类型 | 提取方法 | 输出 |
|---|---|---|
| 音频 | transcribe() 函数 backend/open_webui/routers/files.py:128-134 | 转录文本 |
| 其他 | process_file() 函数 backend/open_webui/routers/files.py:139-144 | 纯文本 |
| 图片 | OCR(通过 process_file()) | 文本内容 |
| 纯文本 | 直接读取(通过 process_file()) | 文本内容 |
_is_text_file() 辅助函数 backend/open_webui/routers/files.py:70-89 用于检测被错误标记的文本文件(例如,被错误识别为视频的 .ts 文件),并通过读取一个数据块并验证 UTF-8 编码来确保它们被作为纯文本处理 backend/open_webui/routers/files.py:76-88。
访问提取的内容
提取的内容可以通过文件对象结构访问:
// 内容嵌套在 file.file.data.content 中
const extractedText = file?.file?.data?.content ?? '无法提取内容';
此内容用于两个主要场景:
- AI 聊天上下文:文件为关于笔记的 AI 对话提供上下文
src/lib/components/notes/NoteEditor/Chat.svelte:168-169。 - 知识库:文件可以添加到知识库中用于 RAG 查询
backend/open_webui/routers/knowledge.py:23-28(通过process_file)。
来源:src/lib/components/notes/NoteEditor.svelte:448-449,src/lib/components/notes/NoteEditor/Chat.svelte:166-169,backend/open_webui/routers/files.py:70-89,backend/open_webui/routers/files.py:128-134,backend/open_webui/routers/files.py:139-144
---
文件显示与管理
文件在 Controls 面板中显示,并根据文件类型进行不同的渲染。
Controls 面板显示
Controls.svelte 组件在两个部分中渲染文件:
graph LR
FilesArray["files[] 数组"]
RegularFiles["常规文件<br/>filter(type !== 'image')"]
ImageFiles["图片文件<br/>filter(type === 'image' || content_type.startsWith('image/'))"]
FileItem["FileItem 组件<br/>(带关闭按钮)"]
ImageComp["图片组件<br/>(缩略图,可关闭)"]
FilesArray --> RegularFiles
FilesArray --> ImageFiles
RegularFiles --> FileItem
ImageFiles --> ImageComp
FileItem -->|"on:dismiss"| RemoveFile["从 files[] 中移除"]
ImageComp -->|"onDismiss"| RemoveFile
RemoveFile -->|"onUpdate(files)"| UpdateNote["调用 changeDebounceHandler()"]
来源:src/lib/components/notes/NoteEditor/Controls.svelte:40-103
文件组件特性
常规文件(src/lib/components/notes/NoteEditor/Controls.svelte:45-68):
- 显示名称、大小和类型。
- 显示上传进度指示器
src/lib/components/notes/NoteEditor/Controls.svelte:56-56。 - 可通过 X 按钮关闭
src/lib/components/notes/NoteEditor/Controls.svelte:57-63。 - 紧凑的小型布局。
图片文件(src/lib/components/notes/NoteEditor/Controls.svelte:70-84):
- 14x14 缩略图,带圆角
src/lib/components/notes/NoteEditor/Controls.svelte:74-74。 - 采用 flex wrap 布局排列
src/lib/components/notes/NoteEditor/Controls.svelte:70-70。 - 可关闭的覆盖按钮
src/lib/components/notes/NoteEditor/Controls.svelte:76-82。 - 使用 object-cover 缩放。
文件移除
移除文件会触发一个多步骤流程:
- 从
files数组中过滤掉该文件src/lib/components/notes/NoteEditor/Controls.svelte:59-59。 - 调用
onUpdate(files)回调src/lib/components/notes/NoteEditor/Controls.svelte:62-62。 changeDebounceHandler()自动持久化到数据库src/lib/components/notes/NoteEditor.svelte:201-219。- 防抖更新防止过多的 API 调用(200ms 延迟)。
来源:src/lib/components/notes/NoteEditor/Controls.svelte:40-103,src/lib/components/notes/NoteEditor.svelte:201-219
---
协作文件同步
文件附件通过 Socket.IO 在所有编辑同一笔记的协作者之间自动同步。
Socket.IO 集成
当加载具有写权限的笔记时(src/lib/components/notes/NoteEditor.svelte:184-192):
- 加入笔记房间:使用笔记 ID 和认证令牌发送
join-note事件src/lib/components/notes/NoteEditor.svelte:189-194。 - 注册处理程序:通过
noteEventHandler()订阅note-eventssrc/lib/components/notes/NoteEditor.svelte:195-195。 - 实时更新:接收来自其他协作者的文件更改。
- 自动同步:文件数组更新会传播到所有已连接的用户。
同步生命周期
sequenceDiagram
participant UserA as "用户 A"
participant EditorA as "NoteEditor A"
participant Socket as "Socket.IO"
participant Database as "NoteTable"
participant EditorB as "NoteEditor B"
participant UserB as "用户 B"
Note over EditorA,EditorB: 两个用户加入笔记房间
EditorA->>Socket: "emit('join-note', {note_id, auth})"
EditorB->>Socket: "emit('join-note', {note_id, auth})"
Socket-->>EditorA: "on('note-events', handler)"
Socket-->>EditorB: "on('note-events', handler)"
UserA->>EditorA: "上传文件"
EditorA->>EditorA: "files.push(newFile)"
EditorA->>Database: "updateNoteById({data: {files}})"
Database-->>EditorA: "成功"
EditorA->>Socket: "笔记更新事件"
Socket->>EditorB: "noteEventHandler()"
EditorB->>EditorB: "同步 files 数组"
EditorB->>UserB: "显示新文件"
来源:src/lib/components/notes/NoteEditor.svelte:184-196,src/lib/components/notes/NoteEditor.svelte:201-219
---
与 AI 聊天的集成
文件附件与笔记中的 AI 聊天功能深度集成,为 AI 驱动的对话提供上下文。
上下文注入
Chat.svelte 组件构建一个包含文件内容的系统提示(src/lib/components/notes/NoteEditor/Chat.svelte:156-170):
system = `你是一个有用的助手...
<notes>${note?.data?.content?.md ?? ''}</notes>
<context>${files.map(file =>
`${file.name}: ${file?.file?.data?.content ?? '无法提取内容'}\n`
).join('')}</context>
<selection>${selectedContent?.text}</selection>`;
文档编辑模式
当 AI 编辑启用时(editEnabled = true),文件通过 DEFAULT_DOCUMENT_EDITOR_PROMPT 为文档增强提供补充上下文(src/lib/components/notes/NoteEditor/Chat.svelte:83-102)。
文件内容使用
| 模式 | 文件内容用途 | 提示类型 |
|---|---|---|
| 编辑模式 | 用文件信息补充笔记内容 | 文档编辑器提示 |
| 聊天模式 | 回答关于文件和笔记内容的问题 | 通用助手提示 |
来源:src/lib/components/notes/NoteEditor/Chat.svelte:156-194,src/lib/components/notes/NoteEditor/Chat.svelte:83-102
---
错误处理与边界情况
文件附件系统包含针对各种失败场景的健壮错误处理。
上传错误处理
空文件验证(src/lib/components/notes/NoteEditor.svelte:422-426):
if (fileItem.size == 0) {
toast.error($i18n.t('无法上传空文件。'));
return null;
}
上传失败恢复(src/lib/components/notes/NoteEditor.svelte:471-477):
- 显示包含异常消息的错误提示。
- 从文件数组中移除临时文件项。
- 防止部分文件对象持久化。
后端错误警告(src/lib/components/notes/NoteEditor.svelte:454-457):
if (uploadedFile.error) {
console.warn('文件上传警告:', uploadedFile.error);
toast.warning(uploadedFile.error);
}
内容提取回退
当内容提取失败时:
- 文件仍附加到笔记。
file?.file?.data?.content返回 null 或 undefined。- AI 聊天使用回退文本:
'无法提取内容'src/lib/components/notes/NoteEditor/Chat.svelte:168。 - 用户仍然可以访问和下载原始文件。
来源:src/lib/components/notes/NoteEditor.svelte:422-477,src/lib/components/notes/NoteEditor/Chat.svelte:168
---
防抖持久化
文件更改使用防抖机制持久化到数据库,以优化性能。
防抖实现
changeDebounceHandler() 函数实现了 200ms 的防抖(src/lib/components/notes/NoteEditor.svelte:201-219):
let debounceTimeout: NodeJS.Timeout | null = null;
const changeDebounceHandler = () => {
if (debounceTimeout) {
clearTimeout(debounceTimeout);
}
debounceTimeout = setTimeout(async () => {
const res = await updateNoteById(localStorage.token, id, {
title: note?.title === '' ? $i18n.t('无标题') : note.title,
data: {
files: files
},
access_grants: note?.access_grants ?? []
});
}, 200);
};
自动触发
防抖处理程序在以下情况下自动调用:
- 通过
uploadFileHandler()上传文件时src/lib/components/notes/NoteEditor.svelte:477。 - 通过 Controls 面板关闭移除文件时
src/lib/components/notes/NoteEditor/Controls.svelte:62。 - 手动修改文件数组时。
- 笔记标题发生更改时。
来源:src/lib/components/notes/NoteEditor.svelte:201-219,src/lib/components/notes/NoteEditor.svelte:477,src/lib/components/notes/NoteEditor/Controls.svelte:62
---
API 集成点
文件附件在其生命周期中与多个后端 API 端点交互。
文件上传 API
函数:uploadFile(token, file, metadata, process) 位置:src/lib/apis/files/index.ts src/lib/apis/files/index.ts:4-95 参数:
token:认证令牌。file:用户选择的文件对象。metadata:可选对象,包含用于 STT 的language。process:布尔标志,用于触发提取。
文件检索 API
函数:getFileById(token, id) 位置:src/lib/apis/files/index.ts src/lib/apis/files/index.ts:216-245 返回:包含提取文本内容的文件对象 src/lib/components/notes/NoteEditor.svelte:460-463。
笔记更新 API
函数:updateNoteById(token, id, payload) 负载结构:
{
title: string,
data: {
files: Array<FileObject>
},
access_grants: Array<AccessGrant>
}
来源:src/lib/components/notes/NoteEditor.svelte:27-28,src/lib/components/notes/NoteEditor.svelte:449,src/lib/components/notes/NoteEditor.svelte:460-463,src/lib/components/notes/NoteEditor.svelte:209-218,src/lib/apis/files/index.ts:4-95