@lobehub/lobehub 2.0.0-next.53 → 2.0.0-next.55
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +52 -0
- package/changelog/v1.json +18 -0
- package/locales/ar/common.json +1 -0
- package/locales/ar/file.json +85 -2
- package/locales/bg-BG/common.json +1 -0
- package/locales/bg-BG/file.json +85 -2
- package/locales/de-DE/common.json +1 -0
- package/locales/de-DE/file.json +85 -2
- package/locales/en-US/common.json +1 -0
- package/locales/en-US/file.json +85 -2
- package/locales/es-ES/common.json +1 -0
- package/locales/es-ES/file.json +85 -2
- package/locales/fa-IR/common.json +1 -0
- package/locales/fa-IR/file.json +85 -2
- package/locales/fr-FR/common.json +1 -0
- package/locales/fr-FR/file.json +85 -2
- package/locales/it-IT/common.json +1 -0
- package/locales/it-IT/file.json +85 -2
- package/locales/ja-JP/common.json +1 -0
- package/locales/ja-JP/file.json +85 -2
- package/locales/ko-KR/common.json +1 -0
- package/locales/ko-KR/file.json +85 -2
- package/locales/nl-NL/common.json +1 -0
- package/locales/nl-NL/file.json +85 -2
- package/locales/pl-PL/common.json +1 -0
- package/locales/pl-PL/file.json +85 -2
- package/locales/pt-BR/common.json +1 -0
- package/locales/pt-BR/file.json +85 -2
- package/locales/ru-RU/common.json +1 -0
- package/locales/ru-RU/file.json +85 -2
- package/locales/tr-TR/common.json +1 -0
- package/locales/tr-TR/file.json +85 -2
- package/locales/vi-VN/common.json +1 -0
- package/locales/vi-VN/file.json +85 -2
- package/locales/zh-CN/common.json +1 -0
- package/locales/zh-CN/file.json +85 -2
- package/locales/zh-TW/common.json +1 -0
- package/locales/zh-TW/file.json +85 -2
- package/package.json +1 -1
- package/packages/database/src/models/__tests__/file.test.ts +94 -29
- package/packages/database/src/models/file.ts +15 -4
- package/packages/database/src/repositories/knowledge/index.test.ts +300 -0
- package/packages/database/src/repositories/knowledge/index.ts +420 -0
- package/packages/model-bank/src/aiModels/aihubmix.ts +1 -0
- package/packages/model-bank/src/aiModels/google.ts +9 -5
- package/packages/model-bank/src/aiModels/openai.ts +2 -35
- package/packages/model-bank/src/aiModels/openrouter.ts +1 -0
- package/packages/model-bank/src/aiModels/vertexai.ts +2 -0
- package/packages/model-bank/src/types/aiModel.ts +15 -2
- package/packages/model-runtime/src/core/usageConverters/index.ts +1 -0
- package/packages/model-runtime/src/core/usageConverters/utils/resolveImageSinglePrice.ts +34 -0
- package/packages/types/src/document/index.ts +14 -2
- package/packages/types/src/files/index.ts +2 -0
- package/packages/types/src/files/list.ts +10 -0
- package/packages/types/src/llm.ts +1 -1
- package/src/app/[variants]/(main)/image/@menu/features/ConfigPanel/components/ModelSelect/ImageModelItem.tsx +93 -0
- package/src/app/[variants]/(main)/image/@menu/features/ConfigPanel/components/{ModelSelect.tsx → ModelSelect/index.tsx} +17 -2
- package/src/app/[variants]/(main)/knowledge/KnowledgeRouter.tsx +2 -1
- package/src/app/[variants]/(main)/knowledge/components/KnowledgeBaseItem/index.tsx +0 -2
- package/src/app/[variants]/(main)/knowledge/hooks/useFileCategory.ts +6 -3
- package/src/app/[variants]/(main)/knowledge/routes/KnowledgeBaseDetail/index.tsx +2 -2
- package/src/app/[variants]/(main)/knowledge/routes/KnowledgeBaseDetail/menu/{MenuItems.tsx → CategoryMenu.tsx} +3 -3
- package/src/app/[variants]/(main)/knowledge/routes/KnowledgeBaseDetail/menu/Menu.tsx +2 -2
- package/src/app/[variants]/(main)/knowledge/routes/KnowledgeHome/index.tsx +40 -18
- package/src/app/[variants]/(main)/knowledge/routes/KnowledgeHome/layout/Container.tsx +1 -1
- package/src/app/[variants]/(main)/knowledge/routes/KnowledgeHome/menu/CategoryMenu.tsx +148 -0
- package/src/app/[variants]/(main)/knowledge/routes/KnowledgeHome/menu/KnowledgeBase.tsx +20 -7
- package/src/components/FileIcon/index.tsx +3 -1
- package/src/features/ChatInput/ActionBar/Knowledge/index.tsx +2 -2
- package/src/features/Conversation/Messages/Assistant/index.tsx +7 -1
- package/src/features/FileSidePanel/index.tsx +1 -1
- package/src/features/KnowledgeBaseModal/AssignKnowledgeBase/Item/MasonryItem.tsx +80 -0
- package/src/features/KnowledgeBaseModal/AssignKnowledgeBase/Item/MasonryItemWrapper.tsx +27 -0
- package/src/features/KnowledgeBaseModal/AssignKnowledgeBase/List.tsx +104 -23
- package/src/features/KnowledgeBaseModal/AssignKnowledgeBase/MasonrySkeleton.tsx +62 -0
- package/src/features/KnowledgeBaseModal/AssignKnowledgeBase/index.tsx +3 -2
- package/src/features/KnowledgeBaseModal/CreateNew/CreateForm.tsx +1 -1
- package/src/features/KnowledgeManager/DocumentExplorer/DocumentActions.tsx +111 -0
- package/src/features/KnowledgeManager/DocumentExplorer/DocumentEditor.tsx +723 -0
- package/src/features/KnowledgeManager/DocumentExplorer/DocumentEditorPlaceholder.tsx +169 -0
- package/src/features/KnowledgeManager/DocumentExplorer/DocumentListItem.tsx +148 -0
- package/src/features/KnowledgeManager/DocumentExplorer/DocumentListSkeleton.tsx +39 -0
- package/src/features/KnowledgeManager/DocumentExplorer/NoteEditorModal.tsx +348 -0
- package/src/features/KnowledgeManager/DocumentExplorer/RenamePopover.tsx +163 -0
- package/src/features/KnowledgeManager/DocumentExplorer/index.tsx +318 -0
- package/src/features/{FileManager/FileList → KnowledgeManager/FileExplorer}/FileListItem/index.tsx +48 -9
- package/src/features/KnowledgeManager/FileExplorer/MasonryFileItem/DefaultFileItem.tsx +149 -0
- package/src/features/KnowledgeManager/FileExplorer/MasonryFileItem/ImageFileItem.tsx +245 -0
- package/src/features/KnowledgeManager/FileExplorer/MasonryFileItem/MarkdownFileItem.tsx +232 -0
- package/src/features/KnowledgeManager/FileExplorer/MasonryFileItem/NoteFileItem.tsx +230 -0
- package/src/features/KnowledgeManager/FileExplorer/MasonryFileItem/index.tsx +398 -0
- package/src/features/KnowledgeManager/FileExplorer/ToolBar/ViewSwitcher.tsx +45 -0
- package/src/features/{FileManager/FileList → KnowledgeManager/FileExplorer}/index.tsx +68 -16
- package/src/features/KnowledgeManager/Header/AddButton.tsx +107 -0
- package/src/features/KnowledgeManager/Header/NewNoteButton.tsx +33 -0
- package/src/features/{FileManager → KnowledgeManager}/Header/index.tsx +3 -9
- package/src/features/KnowledgeManager/Home/RecentDocumentCard.tsx +116 -0
- package/src/features/KnowledgeManager/Home/RecentDocuments.tsx +77 -0
- package/src/features/KnowledgeManager/Home/RecentFileCard.tsx +121 -0
- package/src/features/KnowledgeManager/Home/RecentFiles.tsx +73 -0
- package/src/features/KnowledgeManager/Home/RecentFilesSkeleton.tsx +83 -0
- package/src/features/KnowledgeManager/Home/UploadEntries.tsx +208 -0
- package/src/features/KnowledgeManager/Home/index.tsx +221 -0
- package/src/features/KnowledgeManager/index.tsx +75 -0
- package/src/features/Portal/FilePreview/Body/index.tsx +1 -1
- package/src/features/Portal/FilePreview/Header.tsx +1 -1
- package/src/locales/default/common.ts +1 -0
- package/src/locales/default/file.ts +85 -2
- package/src/locales/default/tool.ts +8 -0
- package/src/server/routers/lambda/__tests__/file.test.ts +85 -6
- package/src/server/routers/lambda/document.ts +57 -0
- package/src/server/routers/lambda/file.ts +72 -0
- package/src/server/routers/lambda/knowledge.ts +94 -0
- package/src/server/services/document/index.ts +103 -0
- package/src/services/document/index.ts +44 -0
- package/src/services/file/index.ts +5 -3
- package/src/store/aiInfra/slices/aiProvider/__tests__/action.test.ts +125 -229
- package/src/store/aiInfra/slices/aiProvider/action.ts +113 -33
- package/src/store/chat/slices/builtinTool/actions/localSystem.ts +1 -1
- package/src/store/file/initialState.ts +6 -1
- package/src/store/file/slices/chat/action.ts +3 -3
- package/src/store/file/slices/document/action.ts +359 -0
- package/src/store/file/slices/document/index.ts +3 -0
- package/src/store/file/slices/document/initialState.ts +22 -0
- package/src/store/file/slices/document/selectors.ts +25 -0
- package/src/store/file/slices/fileManager/action.test.ts +16 -9
- package/src/store/file/slices/fileManager/action.ts +11 -11
- package/src/store/file/store.ts +3 -0
- package/src/store/global/initialState.ts +3 -1
- package/src/tools/interventions.ts +3 -5
- package/src/tools/local-system/Intervention/MoveLocalFiles/MoveFileItem.tsx +56 -0
- package/src/tools/local-system/Intervention/MoveLocalFiles/index.tsx +26 -0
- package/src/tools/local-system/Intervention/RunCommand/index.tsx +1 -2
- package/src/tools/local-system/Intervention/index.ts +11 -0
- package/src/tools/local-system/Render/MoveLocalFiles/MoveFileItem.tsx +56 -0
- package/src/tools/local-system/Render/MoveLocalFiles/index.tsx +26 -0
- package/src/tools/local-system/Render/index.ts +21 -0
- package/src/tools/renders.ts +6 -24
- package/src/tools/web-browsing/Render/index.ts +13 -0
- package/src/app/[variants]/(main)/knowledge/routes/KnowledgeHome/menu/FileMenu.tsx +0 -75
- package/src/features/FileManager/FileList/MasonryFileItem/index.tsx +0 -582
- package/src/features/FileManager/index.tsx +0 -36
- /package/src/features/{FileManager/FileList/ToolBar → KnowledgeBaseModal/AssignKnowledgeBase}/ViewSwitcher.tsx +0 -0
- /package/src/features/{FileManager → KnowledgeManager}/ChunkDrawer/ChunkList/ChunkItem.tsx +0 -0
- /package/src/features/{FileManager → KnowledgeManager}/ChunkDrawer/ChunkList/index.tsx +0 -0
- /package/src/features/{FileManager → KnowledgeManager}/ChunkDrawer/Content.tsx +0 -0
- /package/src/features/{FileManager → KnowledgeManager}/ChunkDrawer/Loading/index.tsx +0 -0
- /package/src/features/{FileManager → KnowledgeManager}/ChunkDrawer/SimilaritySearchList/Item.tsx +0 -0
- /package/src/features/{FileManager → KnowledgeManager}/ChunkDrawer/SimilaritySearchList/index.tsx +0 -0
- /package/src/features/{FileManager → KnowledgeManager}/ChunkDrawer/index.tsx +0 -0
- /package/src/features/{FileManager/FileList → KnowledgeManager/FileExplorer}/EmptyStatus.tsx +0 -0
- /package/src/features/{FileManager/FileList → KnowledgeManager/FileExplorer}/FileListItem/ChunkTag.tsx +0 -0
- /package/src/features/{FileManager/FileList → KnowledgeManager/FileExplorer}/FileListItem/DropdownMenu.tsx +0 -0
- /package/src/features/{FileManager/FileList → KnowledgeManager/FileExplorer}/FileSkeleton.tsx +0 -0
- /package/src/features/{FileManager/FileList → KnowledgeManager/FileExplorer}/MasonryFileItem/MasonryItemWrapper.tsx +0 -0
- /package/src/features/{FileManager/FileList → KnowledgeManager/FileExplorer}/MasonrySkeleton.tsx +0 -0
- /package/src/features/{FileManager/FileList → KnowledgeManager/FileExplorer}/ToolBar/Config.tsx +0 -0
- /package/src/features/{FileManager/FileList → KnowledgeManager/FileExplorer}/ToolBar/MultiSelectActions.tsx +0 -0
- /package/src/features/{FileManager/FileList → KnowledgeManager/FileExplorer}/ToolBar/index.tsx +0 -0
- /package/src/features/{FileManager/FileList → KnowledgeManager/FileExplorer}/useCheckTaskStatus.ts +0 -0
- /package/src/features/{FileManager → KnowledgeManager}/Header/FilesSearchBar.tsx +0 -0
- /package/src/features/{FileManager → KnowledgeManager}/Header/TogglePanelButton.tsx +0 -0
- /package/src/features/{FileManager → KnowledgeManager}/Header/UploadFileButton.tsx +0 -0
- /package/src/features/{FileManager → KnowledgeManager}/UploadDock/Item.tsx +0 -0
- /package/src/features/{FileManager → KnowledgeManager}/UploadDock/index.tsx +0 -0
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
export default {
|
|
2
|
-
|
|
2
|
+
addKnowledge: '添加知识',
|
|
3
|
+
addPage: '创建文稿',
|
|
4
|
+
desc: '管理你的工作、学习与生活知识。',
|
|
3
5
|
detail: {
|
|
4
6
|
basic: {
|
|
5
7
|
createdAt: '创建时间',
|
|
@@ -21,6 +23,44 @@ export default {
|
|
|
21
23
|
embeddingStatus: '向量化',
|
|
22
24
|
},
|
|
23
25
|
},
|
|
26
|
+
documentEditor: {
|
|
27
|
+
addIcon: '添加图标',
|
|
28
|
+
autoSaveMessage: '文档会自动保存,无需手动保存',
|
|
29
|
+
chooseIcon: '选择图标',
|
|
30
|
+
deleteConfirm: {
|
|
31
|
+
content: '即将删除该文档,删除后将不可恢复,请谨慎操作。',
|
|
32
|
+
title: '删除文档',
|
|
33
|
+
},
|
|
34
|
+
deleteError: '删除文档失败',
|
|
35
|
+
deleteSuccess: '文档删除成功',
|
|
36
|
+
editedAt: '最后编辑于 {{time}}',
|
|
37
|
+
editedBy: '最后编辑者 {{name}}',
|
|
38
|
+
editorPlaceholder: '输入文档内容,按 / 打开命令菜单',
|
|
39
|
+
empty: {
|
|
40
|
+
createNewDocument: '创建新文档',
|
|
41
|
+
title: '选择一个文档以开始',
|
|
42
|
+
uploadMarkdown: '上传 Markdown 文件',
|
|
43
|
+
},
|
|
44
|
+
linkCopied: '链接已复制',
|
|
45
|
+
menu: {
|
|
46
|
+
copyLink: '复制链接',
|
|
47
|
+
exportDocument: '导出文档',
|
|
48
|
+
importDocument: '导入文档',
|
|
49
|
+
pin: '置顶文档',
|
|
50
|
+
},
|
|
51
|
+
saving: '保存中...',
|
|
52
|
+
titlePlaceholder: '无标题',
|
|
53
|
+
wordCount: '{{wordCount}} 字',
|
|
54
|
+
},
|
|
55
|
+
documentList: {
|
|
56
|
+
copyContent: '复制全文',
|
|
57
|
+
documentCount: '共 {{count}} 个文档',
|
|
58
|
+
duplicate: '创建副本',
|
|
59
|
+
empty: '暂无文档,点击上方按钮创建你的第一篇文档',
|
|
60
|
+
noResults: '未找到匹配的文档',
|
|
61
|
+
selectNote: '选择一个文档开始编辑',
|
|
62
|
+
untitled: '无标题',
|
|
63
|
+
},
|
|
24
64
|
empty: '暂无已上传文件/文件夹',
|
|
25
65
|
header: {
|
|
26
66
|
actions: {
|
|
@@ -28,8 +68,43 @@ export default {
|
|
|
28
68
|
uploadFile: '上传文件',
|
|
29
69
|
uploadFolder: '上传文件夹',
|
|
30
70
|
},
|
|
71
|
+
newDocumentButton: '新建文档',
|
|
72
|
+
newNoteDialog: {
|
|
73
|
+
cancel: '取消',
|
|
74
|
+
editTitle: '编辑文档',
|
|
75
|
+
emptyContent: '文档内容不能为空',
|
|
76
|
+
loadError: '加载文档失败,请重试',
|
|
77
|
+
loading: '加载中...',
|
|
78
|
+
save: '保存',
|
|
79
|
+
saveError: '保存文档失败,请重试',
|
|
80
|
+
saveSuccess: '文档保存成功',
|
|
81
|
+
title: '新建文档',
|
|
82
|
+
updateSuccess: '文档更新成功',
|
|
83
|
+
},
|
|
31
84
|
uploadButton: '上传',
|
|
32
85
|
},
|
|
86
|
+
home: {
|
|
87
|
+
getStarted: '开始使用',
|
|
88
|
+
greeting: '开始',
|
|
89
|
+
quickActions: '快捷操作',
|
|
90
|
+
recentDocuments: '最近文档',
|
|
91
|
+
recentFiles: '最近文件',
|
|
92
|
+
subtitle: '欢迎使用知识库,从这里开始管理你的文档和文档',
|
|
93
|
+
uploadEntries: {
|
|
94
|
+
files: {
|
|
95
|
+
title: '上传文件',
|
|
96
|
+
},
|
|
97
|
+
folder: {
|
|
98
|
+
title: '上传文件夹',
|
|
99
|
+
},
|
|
100
|
+
knowledgeBase: {
|
|
101
|
+
title: '新建知识库',
|
|
102
|
+
},
|
|
103
|
+
newDocument: {
|
|
104
|
+
title: '新建文档',
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
},
|
|
33
108
|
knowledgeBase: {
|
|
34
109
|
list: {
|
|
35
110
|
confirmRemoveKnowledgeBase:
|
|
@@ -39,6 +114,10 @@ export default {
|
|
|
39
114
|
new: '新建知识库',
|
|
40
115
|
title: '知识库',
|
|
41
116
|
},
|
|
117
|
+
menu: {
|
|
118
|
+
allDocuments: '全部文档',
|
|
119
|
+
allFiles: '全部文件',
|
|
120
|
+
},
|
|
42
121
|
networkError: '获取知识库失败,请检测网络连接后重试',
|
|
43
122
|
notSupportGuide: {
|
|
44
123
|
desc: '当前部署实例为客户端数据库模式,无法使用文件管理功能。请切换到<1>服务端数据库部署模式</1>,或直接使用 <3>LobeChat Cloud</3>',
|
|
@@ -62,12 +141,16 @@ export default {
|
|
|
62
141
|
downloadFile: '下载文件',
|
|
63
142
|
unsupportedFileAndContact: '此文件格式暂不支持在线预览,如有预览诉求,欢迎<1>反馈给我们</1>',
|
|
64
143
|
},
|
|
144
|
+
searchDocumentPlaceholder: '搜索文档',
|
|
65
145
|
searchFilePlaceholder: '搜索文件',
|
|
66
146
|
tab: {
|
|
67
|
-
all: '
|
|
147
|
+
all: '全部',
|
|
68
148
|
audios: '语音',
|
|
69
149
|
documents: '文档',
|
|
150
|
+
home: '首页',
|
|
70
151
|
images: '图片',
|
|
152
|
+
moreTypes: '更多类型',
|
|
153
|
+
pages: '文稿',
|
|
71
154
|
videos: '视频',
|
|
72
155
|
websites: '网页',
|
|
73
156
|
},
|
|
@@ -17,6 +17,14 @@ export default {
|
|
|
17
17
|
localFiles: {
|
|
18
18
|
file: '文件',
|
|
19
19
|
folder: '文件夹',
|
|
20
|
+
moveFiles: {
|
|
21
|
+
itemsMoved: '已移动 {{count}} 个项目:',
|
|
22
|
+
itemsMoved_one: '已移动 {{count}} 个项目:',
|
|
23
|
+
itemsMoved_other: '已移动 {{count}} 个项目:',
|
|
24
|
+
itemsToMove: '{{count}} 个项目待移动:',
|
|
25
|
+
itemsToMove_one: '{{count}} 个项目待移动:',
|
|
26
|
+
itemsToMove_other: '{{count}} 个项目待移动:',
|
|
27
|
+
},
|
|
20
28
|
open: '打开',
|
|
21
29
|
openFile: '打开文件',
|
|
22
30
|
openFolder: '打开文件夹',
|
|
@@ -39,13 +39,21 @@ function createCallerWithCtx(partialCtx: any = {}) {
|
|
|
39
39
|
delete: vi.fn(),
|
|
40
40
|
};
|
|
41
41
|
|
|
42
|
+
const knowledgeRepo = {
|
|
43
|
+
query: vi.fn().mockResolvedValue([]),
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const documentModel = {};
|
|
47
|
+
|
|
42
48
|
const ctx = {
|
|
43
49
|
serverDB: {} as any,
|
|
44
50
|
userId: 'test-user',
|
|
45
51
|
asyncTaskModel,
|
|
46
52
|
chunkModel,
|
|
53
|
+
documentModel,
|
|
47
54
|
fileModel,
|
|
48
55
|
fileService,
|
|
56
|
+
knowledgeRepo,
|
|
49
57
|
...partialCtx,
|
|
50
58
|
};
|
|
51
59
|
|
|
@@ -58,18 +66,24 @@ vi.mock('@/config/db', () => ({
|
|
|
58
66
|
},
|
|
59
67
|
}));
|
|
60
68
|
|
|
69
|
+
const mockAsyncTaskFindByIds = vi.fn();
|
|
70
|
+
const mockAsyncTaskFindById = vi.fn();
|
|
71
|
+
const mockAsyncTaskDelete = vi.fn();
|
|
72
|
+
const mockChunkCountByFileIds = vi.fn();
|
|
73
|
+
const mockChunkCountByFileId = vi.fn();
|
|
74
|
+
|
|
61
75
|
vi.mock('@/database/models/asyncTask', () => ({
|
|
62
76
|
AsyncTaskModel: vi.fn(() => ({
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
77
|
+
delete: mockAsyncTaskDelete,
|
|
78
|
+
findById: mockAsyncTaskFindById,
|
|
79
|
+
findByIds: mockAsyncTaskFindByIds,
|
|
66
80
|
})),
|
|
67
81
|
}));
|
|
68
82
|
|
|
69
83
|
vi.mock('@/database/models/chunk', () => ({
|
|
70
84
|
ChunkModel: vi.fn(() => ({
|
|
71
|
-
countByFileId:
|
|
72
|
-
countByFileIds:
|
|
85
|
+
countByFileId: mockChunkCountByFileId,
|
|
86
|
+
countByFileIds: mockChunkCountByFileIds,
|
|
73
87
|
})),
|
|
74
88
|
}));
|
|
75
89
|
|
|
@@ -85,14 +99,28 @@ vi.mock('@/database/models/file', () => ({
|
|
|
85
99
|
})),
|
|
86
100
|
}));
|
|
87
101
|
|
|
102
|
+
const mockFileServiceGetFullFileUrl = vi.fn();
|
|
103
|
+
|
|
88
104
|
vi.mock('@/server/services/file', () => ({
|
|
89
105
|
FileService: vi.fn(() => ({
|
|
90
|
-
getFullFileUrl: vi.fn(),
|
|
91
106
|
deleteFile: vi.fn(),
|
|
92
107
|
deleteFiles: vi.fn(),
|
|
108
|
+
getFullFileUrl: mockFileServiceGetFullFileUrl,
|
|
93
109
|
})),
|
|
94
110
|
}));
|
|
95
111
|
|
|
112
|
+
const mockKnowledgeRepoQuery = vi.fn().mockResolvedValue([]);
|
|
113
|
+
|
|
114
|
+
vi.mock('@/database/repositories/knowledge', () => ({
|
|
115
|
+
KnowledgeRepo: vi.fn(() => ({
|
|
116
|
+
query: mockKnowledgeRepoQuery,
|
|
117
|
+
})),
|
|
118
|
+
}));
|
|
119
|
+
|
|
120
|
+
vi.mock('@/database/models/document', () => ({
|
|
121
|
+
DocumentModel: vi.fn(() => ({})),
|
|
122
|
+
}));
|
|
123
|
+
|
|
96
124
|
describe('fileRouter', () => {
|
|
97
125
|
let ctx: any;
|
|
98
126
|
let caller: any;
|
|
@@ -169,6 +197,57 @@ describe('fileRouter', () => {
|
|
|
169
197
|
});
|
|
170
198
|
});
|
|
171
199
|
|
|
200
|
+
describe('getKnowledgeItems', () => {
|
|
201
|
+
it('should return knowledge items with files and documents', async () => {
|
|
202
|
+
const knowledgeItems = [
|
|
203
|
+
{
|
|
204
|
+
...mockFile,
|
|
205
|
+
chunkTaskId: 'chunk-1',
|
|
206
|
+
embeddingTaskId: 'emb-1',
|
|
207
|
+
id: 'file-1',
|
|
208
|
+
sourceType: 'file' as const,
|
|
209
|
+
},
|
|
210
|
+
{
|
|
211
|
+
editorData: { content: 'test' },
|
|
212
|
+
id: 'doc-1',
|
|
213
|
+
name: 'Document 1',
|
|
214
|
+
sourceType: 'document' as const,
|
|
215
|
+
},
|
|
216
|
+
];
|
|
217
|
+
|
|
218
|
+
mockKnowledgeRepoQuery.mockResolvedValue(knowledgeItems);
|
|
219
|
+
mockChunkCountByFileIds.mockResolvedValue([{ count: 10, id: 'file-1' }]);
|
|
220
|
+
mockAsyncTaskFindByIds
|
|
221
|
+
.mockResolvedValueOnce([{ error: null, id: 'chunk-1', status: AsyncTaskStatus.Success }])
|
|
222
|
+
.mockResolvedValueOnce([{ error: null, id: 'emb-1', status: AsyncTaskStatus.Success }]);
|
|
223
|
+
mockFileServiceGetFullFileUrl.mockResolvedValue('https://example.com/test-url');
|
|
224
|
+
|
|
225
|
+
const result = await caller.getKnowledgeItems({});
|
|
226
|
+
|
|
227
|
+
expect(result).toHaveLength(2);
|
|
228
|
+
expect(result[0]).toMatchObject({
|
|
229
|
+
chunkCount: 10,
|
|
230
|
+
chunkingStatus: AsyncTaskStatus.Success,
|
|
231
|
+
embeddingStatus: AsyncTaskStatus.Success,
|
|
232
|
+
finishEmbedding: true,
|
|
233
|
+
id: 'file-1',
|
|
234
|
+
sourceType: 'file',
|
|
235
|
+
url: 'https://example.com/test-url',
|
|
236
|
+
});
|
|
237
|
+
expect(result[1]).toMatchObject({
|
|
238
|
+
chunkCount: null,
|
|
239
|
+
chunkingError: null,
|
|
240
|
+
chunkingStatus: null,
|
|
241
|
+
editorData: { content: 'test' },
|
|
242
|
+
embeddingError: null,
|
|
243
|
+
embeddingStatus: null,
|
|
244
|
+
finishEmbedding: false,
|
|
245
|
+
id: 'doc-1',
|
|
246
|
+
name: 'Document 1',
|
|
247
|
+
});
|
|
248
|
+
});
|
|
249
|
+
});
|
|
250
|
+
|
|
172
251
|
describe('removeFile', () => {
|
|
173
252
|
it('should do nothing when file not found', async () => {
|
|
174
253
|
ctx.fileModel.delete.mockResolvedValue(null);
|
|
@@ -21,6 +21,38 @@ const documentProcedure = authedProcedure.use(serverDatabase).use(async (opts) =
|
|
|
21
21
|
});
|
|
22
22
|
|
|
23
23
|
export const documentRouter = router({
|
|
24
|
+
createDocument: documentProcedure
|
|
25
|
+
.input(
|
|
26
|
+
z.object({
|
|
27
|
+
content: z.string().optional(),
|
|
28
|
+
editorData: z.string(),
|
|
29
|
+
fileType: z.string().optional(),
|
|
30
|
+
knowledgeBaseId: z.string().optional(),
|
|
31
|
+
metadata: z.record(z.any()).optional(),
|
|
32
|
+
title: z.string(),
|
|
33
|
+
}),
|
|
34
|
+
)
|
|
35
|
+
.mutation(async ({ ctx, input }) => {
|
|
36
|
+
// Parse editorData from JSON string to object
|
|
37
|
+
const editorData = JSON.parse(input.editorData);
|
|
38
|
+
return ctx.documentService.createDocument({
|
|
39
|
+
...input,
|
|
40
|
+
editorData,
|
|
41
|
+
});
|
|
42
|
+
}),
|
|
43
|
+
|
|
44
|
+
deleteDocument: documentProcedure
|
|
45
|
+
.input(z.object({ id: z.string() }))
|
|
46
|
+
.mutation(async ({ ctx, input }) => {
|
|
47
|
+
return ctx.documentService.deleteDocument(input.id);
|
|
48
|
+
}),
|
|
49
|
+
|
|
50
|
+
getDocumentById: documentProcedure
|
|
51
|
+
.input(z.object({ id: z.string() }))
|
|
52
|
+
.query(async ({ ctx, input }) => {
|
|
53
|
+
return ctx.documentService.getDocumentById(input.id);
|
|
54
|
+
}),
|
|
55
|
+
|
|
24
56
|
parseFileContent: documentProcedure
|
|
25
57
|
.input(
|
|
26
58
|
z.object({
|
|
@@ -33,4 +65,29 @@ export const documentRouter = router({
|
|
|
33
65
|
|
|
34
66
|
return lobeDocument;
|
|
35
67
|
}),
|
|
68
|
+
|
|
69
|
+
queryDocuments: documentProcedure.query(async ({ ctx }) => {
|
|
70
|
+
return ctx.documentService.queryDocuments();
|
|
71
|
+
}),
|
|
72
|
+
|
|
73
|
+
updateDocument: documentProcedure
|
|
74
|
+
.input(
|
|
75
|
+
z.object({
|
|
76
|
+
content: z.string().optional(),
|
|
77
|
+
editorData: z.string().optional(),
|
|
78
|
+
id: z.string(),
|
|
79
|
+
metadata: z.record(z.any()).optional(),
|
|
80
|
+
rawData: z.string().optional(),
|
|
81
|
+
title: z.string().optional(),
|
|
82
|
+
}),
|
|
83
|
+
)
|
|
84
|
+
.mutation(async ({ ctx, input }) => {
|
|
85
|
+
const { id, editorData: editorDataString, ...params } = input;
|
|
86
|
+
// Parse editorData from JSON string to object if present
|
|
87
|
+
const editorData = editorDataString ? JSON.parse(editorDataString) : undefined;
|
|
88
|
+
return ctx.documentService.updateDocument(id, {
|
|
89
|
+
...params,
|
|
90
|
+
editorData,
|
|
91
|
+
});
|
|
92
|
+
}),
|
|
36
93
|
});
|
|
@@ -4,7 +4,9 @@ import { z } from 'zod';
|
|
|
4
4
|
import { serverDBEnv } from '@/config/db';
|
|
5
5
|
import { AsyncTaskModel } from '@/database/models/asyncTask';
|
|
6
6
|
import { ChunkModel } from '@/database/models/chunk';
|
|
7
|
+
import { DocumentModel } from '@/database/models/document';
|
|
7
8
|
import { FileModel } from '@/database/models/file';
|
|
9
|
+
import { KnowledgeRepo } from '@/database/repositories/knowledge';
|
|
8
10
|
import { authedProcedure, router } from '@/libs/trpc/lambda';
|
|
9
11
|
import { serverDatabase } from '@/libs/trpc/lambda/middleware';
|
|
10
12
|
import { FileService } from '@/server/services/file';
|
|
@@ -18,8 +20,10 @@ const fileProcedure = authedProcedure.use(serverDatabase).use(async (opts) => {
|
|
|
18
20
|
ctx: {
|
|
19
21
|
asyncTaskModel: new AsyncTaskModel(ctx.serverDB, ctx.userId),
|
|
20
22
|
chunkModel: new ChunkModel(ctx.serverDB, ctx.userId),
|
|
23
|
+
documentModel: new DocumentModel(ctx.serverDB, ctx.userId),
|
|
21
24
|
fileModel: new FileModel(ctx.serverDB, ctx.userId),
|
|
22
25
|
fileService: new FileService(ctx.serverDB, ctx.userId),
|
|
26
|
+
knowledgeRepo: new KnowledgeRepo(ctx.serverDB, ctx.userId),
|
|
23
27
|
},
|
|
24
28
|
});
|
|
25
29
|
});
|
|
@@ -95,6 +99,8 @@ export const fileRouter = router({
|
|
|
95
99
|
embeddingError: embeddingTask?.error,
|
|
96
100
|
embeddingStatus: embeddingTask?.status as AsyncTaskStatus,
|
|
97
101
|
finishEmbedding: embeddingTask?.status === AsyncTaskStatus.Success,
|
|
102
|
+
metadata: item.metadata as Record<string, any> | null | undefined,
|
|
103
|
+
sourceType: 'file' as const,
|
|
98
104
|
url: await ctx.fileService.getFullFileUrl(item.url!),
|
|
99
105
|
};
|
|
100
106
|
}),
|
|
@@ -132,6 +138,7 @@ export const fileRouter = router({
|
|
|
132
138
|
embeddingError: embeddingTask?.error ?? null,
|
|
133
139
|
embeddingStatus: embeddingTask?.status as AsyncTaskStatus,
|
|
134
140
|
finishEmbedding: embeddingTask?.status === AsyncTaskStatus.Success,
|
|
141
|
+
sourceType: 'file' as const,
|
|
135
142
|
url: await ctx.fileService.getFullFileUrl(item.url!),
|
|
136
143
|
} as FileListItem;
|
|
137
144
|
resultFiles.push(fileItem);
|
|
@@ -140,6 +147,71 @@ export const fileRouter = router({
|
|
|
140
147
|
return resultFiles;
|
|
141
148
|
}),
|
|
142
149
|
|
|
150
|
+
getKnowledgeItems: fileProcedure.input(QueryFileListSchema).query(async ({ ctx, input }) => {
|
|
151
|
+
const knowledgeItems = await ctx.knowledgeRepo.query(input);
|
|
152
|
+
|
|
153
|
+
// Process files (add chunk info and async task status)
|
|
154
|
+
const fileItems = knowledgeItems.filter((item) => item.sourceType === 'file');
|
|
155
|
+
const fileIds = fileItems.map((item) => item.id);
|
|
156
|
+
const chunks = await ctx.chunkModel.countByFileIds(fileIds);
|
|
157
|
+
|
|
158
|
+
const chunkTaskIds = fileItems.map((item) => item.chunkTaskId).filter(Boolean) as string[];
|
|
159
|
+
const chunkTasks = await ctx.asyncTaskModel.findByIds(chunkTaskIds, AsyncTaskType.Chunking);
|
|
160
|
+
|
|
161
|
+
const embeddingTaskIds = fileItems
|
|
162
|
+
.map((item) => item.embeddingTaskId)
|
|
163
|
+
.filter(Boolean) as string[];
|
|
164
|
+
const embeddingTasks = await ctx.asyncTaskModel.findByIds(
|
|
165
|
+
embeddingTaskIds,
|
|
166
|
+
AsyncTaskType.Embedding,
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
// Combine all items with their metadata
|
|
170
|
+
const resultItems = [] as any[];
|
|
171
|
+
for (const item of knowledgeItems) {
|
|
172
|
+
if (item.sourceType === 'file') {
|
|
173
|
+
const chunkTask = item.chunkTaskId
|
|
174
|
+
? chunkTasks.find((task) => task.id === item.chunkTaskId)
|
|
175
|
+
: null;
|
|
176
|
+
const embeddingTask = item.embeddingTaskId
|
|
177
|
+
? embeddingTasks.find((task) => task.id === item.embeddingTaskId)
|
|
178
|
+
: null;
|
|
179
|
+
|
|
180
|
+
resultItems.push({
|
|
181
|
+
...item,
|
|
182
|
+
chunkCount: chunks.find((chunk) => chunk.id === item.id)?.count ?? null,
|
|
183
|
+
chunkingError: chunkTask?.error ?? null,
|
|
184
|
+
chunkingStatus: chunkTask?.status as AsyncTaskStatus,
|
|
185
|
+
editorData: null,
|
|
186
|
+
embeddingError: embeddingTask?.error ?? null,
|
|
187
|
+
embeddingStatus: embeddingTask?.status as AsyncTaskStatus,
|
|
188
|
+
finishEmbedding: embeddingTask?.status === AsyncTaskStatus.Success,
|
|
189
|
+
url: await ctx.fileService.getFullFileUrl(item.url!),
|
|
190
|
+
} as FileListItem);
|
|
191
|
+
} else {
|
|
192
|
+
// Document item - no chunk processing needed, includes editorData
|
|
193
|
+
const documentItem = {
|
|
194
|
+
...item,
|
|
195
|
+
chunkCount: null,
|
|
196
|
+
chunkingError: null,
|
|
197
|
+
chunkingStatus: null,
|
|
198
|
+
embeddingError: null,
|
|
199
|
+
embeddingStatus: null,
|
|
200
|
+
finishEmbedding: false,
|
|
201
|
+
} as FileListItem;
|
|
202
|
+
console.log('[API getKnowledgeItems] Processing document:', {
|
|
203
|
+
editorDataPreview: item.editorData ? JSON.stringify(item.editorData).slice(0, 100) : null,
|
|
204
|
+
hasEditorData: !!item.editorData,
|
|
205
|
+
id: item.id,
|
|
206
|
+
name: item.name,
|
|
207
|
+
});
|
|
208
|
+
resultItems.push(documentItem);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return resultItems;
|
|
213
|
+
}),
|
|
214
|
+
|
|
143
215
|
removeAllFiles: fileProcedure.mutation(async ({ ctx }) => {
|
|
144
216
|
return ctx.fileModel.clear();
|
|
145
217
|
}),
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { AsyncTaskModel } from '@/database/models/asyncTask';
|
|
2
|
+
import { ChunkModel } from '@/database/models/chunk';
|
|
3
|
+
import { DocumentModel } from '@/database/models/document';
|
|
4
|
+
import { FileModel } from '@/database/models/file';
|
|
5
|
+
import { KnowledgeRepo } from '@/database/repositories/knowledge';
|
|
6
|
+
import { authedProcedure, router } from '@/libs/trpc/lambda';
|
|
7
|
+
import { serverDatabase } from '@/libs/trpc/lambda/middleware';
|
|
8
|
+
import { FileService } from '@/server/services/file';
|
|
9
|
+
import { AsyncTaskStatus, AsyncTaskType } from '@/types/asyncTask';
|
|
10
|
+
import { FileListItem, QueryFileListSchema } from '@/types/files';
|
|
11
|
+
|
|
12
|
+
const knowledgeProcedure = authedProcedure.use(serverDatabase).use(async (opts) => {
|
|
13
|
+
const { ctx } = opts;
|
|
14
|
+
|
|
15
|
+
return opts.next({
|
|
16
|
+
ctx: {
|
|
17
|
+
asyncTaskModel: new AsyncTaskModel(ctx.serverDB, ctx.userId),
|
|
18
|
+
chunkModel: new ChunkModel(ctx.serverDB, ctx.userId),
|
|
19
|
+
documentModel: new DocumentModel(ctx.serverDB, ctx.userId),
|
|
20
|
+
fileModel: new FileModel(ctx.serverDB, ctx.userId),
|
|
21
|
+
fileService: new FileService(ctx.serverDB, ctx.userId),
|
|
22
|
+
knowledgeRepo: new KnowledgeRepo(ctx.serverDB, ctx.userId),
|
|
23
|
+
},
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
export const knowledgeRouter = router({
|
|
28
|
+
getKnowledgeItems: knowledgeProcedure.input(QueryFileListSchema).query(async ({ ctx, input }) => {
|
|
29
|
+
const knowledgeItems = await ctx.knowledgeRepo.query(input);
|
|
30
|
+
|
|
31
|
+
// Process files (add chunk info and async task status)
|
|
32
|
+
const fileItems = knowledgeItems.filter((item) => item.sourceType === 'file');
|
|
33
|
+
const fileIds = fileItems.map((item) => item.id);
|
|
34
|
+
const chunks = await ctx.chunkModel.countByFileIds(fileIds);
|
|
35
|
+
|
|
36
|
+
const chunkTaskIds = fileItems.map((item) => item.chunkTaskId).filter(Boolean) as string[];
|
|
37
|
+
const chunkTasks = await ctx.asyncTaskModel.findByIds(chunkTaskIds, AsyncTaskType.Chunking);
|
|
38
|
+
|
|
39
|
+
const embeddingTaskIds = fileItems
|
|
40
|
+
.map((item) => item.embeddingTaskId)
|
|
41
|
+
.filter(Boolean) as string[];
|
|
42
|
+
const embeddingTasks = await ctx.asyncTaskModel.findByIds(
|
|
43
|
+
embeddingTaskIds,
|
|
44
|
+
AsyncTaskType.Embedding,
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
// Combine all items with their metadata
|
|
48
|
+
const resultItems = [] as any[];
|
|
49
|
+
for (const item of knowledgeItems) {
|
|
50
|
+
if (item.sourceType === 'file') {
|
|
51
|
+
const chunkTask = item.chunkTaskId
|
|
52
|
+
? chunkTasks.find((task) => task.id === item.chunkTaskId)
|
|
53
|
+
: null;
|
|
54
|
+
const embeddingTask = item.embeddingTaskId
|
|
55
|
+
? embeddingTasks.find((task) => task.id === item.embeddingTaskId)
|
|
56
|
+
: null;
|
|
57
|
+
|
|
58
|
+
resultItems.push({
|
|
59
|
+
...item,
|
|
60
|
+
chunkCount: chunks.find((chunk) => chunk.id === item.id)?.count ?? null,
|
|
61
|
+
chunkingError: chunkTask?.error ?? null,
|
|
62
|
+
chunkingStatus: chunkTask?.status as AsyncTaskStatus,
|
|
63
|
+
editorData: null,
|
|
64
|
+
embeddingError: embeddingTask?.error ?? null,
|
|
65
|
+
embeddingStatus: embeddingTask?.status as AsyncTaskStatus,
|
|
66
|
+
finishEmbedding: embeddingTask?.status === AsyncTaskStatus.Success,
|
|
67
|
+
url: await ctx.fileService.getFullFileUrl(item.url!),
|
|
68
|
+
} as FileListItem);
|
|
69
|
+
} else {
|
|
70
|
+
// Document item - no chunk processing needed, includes editorData
|
|
71
|
+
const documentItem = {
|
|
72
|
+
...item,
|
|
73
|
+
chunkCount: null,
|
|
74
|
+
chunkingError: null,
|
|
75
|
+
chunkingStatus: null,
|
|
76
|
+
embeddingError: null,
|
|
77
|
+
embeddingStatus: null,
|
|
78
|
+
finishEmbedding: false,
|
|
79
|
+
} as FileListItem;
|
|
80
|
+
console.log('[API getKnowledgeItems] Processing document:', {
|
|
81
|
+
editorDataPreview: item.editorData ? JSON.stringify(item.editorData).slice(0, 100) : null,
|
|
82
|
+
hasEditorData: !!item.editorData,
|
|
83
|
+
id: item.id,
|
|
84
|
+
name: item.name,
|
|
85
|
+
});
|
|
86
|
+
resultItems.push(documentItem);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return resultItems;
|
|
91
|
+
}),
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
export type KnowledgeRouter = typeof knowledgeRouter;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { LobeChatDatabase } from '@lobechat/database';
|
|
2
|
+
import { DocumentItem } from '@lobechat/database/schemas';
|
|
2
3
|
import { loadFile } from '@lobechat/file-loaders';
|
|
3
4
|
import debug from 'debug';
|
|
4
5
|
|
|
@@ -15,14 +16,116 @@ export class DocumentService {
|
|
|
15
16
|
private fileModel: FileModel;
|
|
16
17
|
private documentModel: DocumentModel;
|
|
17
18
|
private fileService: FileService;
|
|
19
|
+
private db: LobeChatDatabase;
|
|
18
20
|
|
|
19
21
|
constructor(db: LobeChatDatabase, userId: string) {
|
|
20
22
|
this.userId = userId;
|
|
23
|
+
this.db = db;
|
|
21
24
|
this.fileModel = new FileModel(db, userId);
|
|
22
25
|
this.fileService = new FileService(db, userId);
|
|
23
26
|
this.documentModel = new DocumentModel(db, userId);
|
|
24
27
|
}
|
|
25
28
|
|
|
29
|
+
/**
|
|
30
|
+
* Create a document
|
|
31
|
+
*/
|
|
32
|
+
async createDocument(params: {
|
|
33
|
+
content?: string;
|
|
34
|
+
editorData: Record<string, any>;
|
|
35
|
+
fileType?: string;
|
|
36
|
+
knowledgeBaseId?: string;
|
|
37
|
+
metadata?: Record<string, any>;
|
|
38
|
+
rawData?: string;
|
|
39
|
+
title: string;
|
|
40
|
+
}): Promise<DocumentItem> {
|
|
41
|
+
const {
|
|
42
|
+
content,
|
|
43
|
+
editorData,
|
|
44
|
+
title,
|
|
45
|
+
fileType = 'custom/document',
|
|
46
|
+
metadata,
|
|
47
|
+
knowledgeBaseId,
|
|
48
|
+
} = params;
|
|
49
|
+
|
|
50
|
+
// Calculate character and line counts
|
|
51
|
+
const totalCharCount = content?.length || 0;
|
|
52
|
+
const totalLineCount = content?.split('\n').length || 0;
|
|
53
|
+
|
|
54
|
+
const document = await this.documentModel.create({
|
|
55
|
+
content,
|
|
56
|
+
editorData,
|
|
57
|
+
fileId: knowledgeBaseId ? null : undefined,
|
|
58
|
+
fileType,
|
|
59
|
+
filename: title,
|
|
60
|
+
metadata,
|
|
61
|
+
pages: undefined,
|
|
62
|
+
source: 'document',
|
|
63
|
+
sourceType: 'api',
|
|
64
|
+
title,
|
|
65
|
+
totalCharCount,
|
|
66
|
+
totalLineCount,
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
return document;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Query all documents
|
|
74
|
+
*/
|
|
75
|
+
async queryDocuments() {
|
|
76
|
+
return this.documentModel.query();
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Get document by ID
|
|
81
|
+
*/
|
|
82
|
+
async getDocumentById(id: string) {
|
|
83
|
+
return this.documentModel.findById(id);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Delete document
|
|
88
|
+
*/
|
|
89
|
+
async deleteDocument(id: string) {
|
|
90
|
+
return this.documentModel.delete(id);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Update document
|
|
95
|
+
*/
|
|
96
|
+
async updateDocument(
|
|
97
|
+
id: string,
|
|
98
|
+
params: {
|
|
99
|
+
content?: string;
|
|
100
|
+
editorData?: Record<string, any>;
|
|
101
|
+
metadata?: Record<string, any>;
|
|
102
|
+
title?: string;
|
|
103
|
+
},
|
|
104
|
+
) {
|
|
105
|
+
const updates: any = {};
|
|
106
|
+
|
|
107
|
+
if (params.content !== undefined) {
|
|
108
|
+
updates.content = params.content;
|
|
109
|
+
updates.totalCharCount = params.content.length;
|
|
110
|
+
updates.totalLineCount = params.content.split('\n').length;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (params.editorData !== undefined) {
|
|
114
|
+
updates.editorData = params.editorData;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (params.title !== undefined) {
|
|
118
|
+
updates.title = params.title;
|
|
119
|
+
updates.filename = params.title;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (params.metadata !== undefined) {
|
|
123
|
+
updates.metadata = params.metadata;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return this.documentModel.update(id, updates);
|
|
127
|
+
}
|
|
128
|
+
|
|
26
129
|
/**
|
|
27
130
|
* 解析文件内容
|
|
28
131
|
*
|