@lobehub/lobehub 2.0.0-next.79 → 2.0.0-next.80
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 +17 -0
- package/changelog/v1.json +5 -0
- package/docs/development/database-schema.dbml +17 -13
- package/package.json +1 -1
- package/packages/database/migrations/0046_add_parent_id.sql +15 -0
- package/packages/database/migrations/meta/0046_snapshot.json +7879 -0
- package/packages/database/migrations/meta/_journal.json +7 -0
- package/packages/database/src/core/migrations.json +23 -26
- package/packages/database/src/models/document.ts +5 -6
- package/packages/database/src/models/file.ts +5 -5
- package/packages/database/src/repositories/knowledge/index.test.ts +1 -1
- package/packages/database/src/schemas/file.ts +81 -1
- package/packages/database/src/schemas/index.ts +0 -1
- package/packages/database/src/schemas/rag.ts +32 -2
- package/packages/database/src/schemas/relations.ts +2 -3
- package/packages/database/src/schemas/topic.ts +1 -1
- package/src/server/routers/lambda/file.ts +23 -2
- package/src/services/file/index.ts +10 -1
- package/packages/database/src/schemas/document.ts +0 -105
|
@@ -322,6 +322,13 @@
|
|
|
322
322
|
"when": 1762911968658,
|
|
323
323
|
"tag": "0045_add_tool_intervention",
|
|
324
324
|
"breakpoints": true
|
|
325
|
+
},
|
|
326
|
+
{
|
|
327
|
+
"idx": 46,
|
|
328
|
+
"version": "7",
|
|
329
|
+
"when": 1763453175961,
|
|
330
|
+
"tag": "0046_add_parent_id",
|
|
331
|
+
"breakpoints": true
|
|
325
332
|
}
|
|
326
333
|
],
|
|
327
334
|
"version": "6"
|
|
@@ -223,10 +223,7 @@
|
|
|
223
223
|
"hash": "9646161fa041354714f823d726af27247bcd6e60fa3be5698c0d69f337a5700b"
|
|
224
224
|
},
|
|
225
225
|
{
|
|
226
|
-
"sql": [
|
|
227
|
-
"DROP TABLE \"user_budgets\";",
|
|
228
|
-
"\nDROP TABLE \"user_subscriptions\";"
|
|
229
|
-
],
|
|
226
|
+
"sql": ["DROP TABLE \"user_budgets\";", "\nDROP TABLE \"user_subscriptions\";"],
|
|
230
227
|
"bps": true,
|
|
231
228
|
"folderMillis": 1729699958471,
|
|
232
229
|
"hash": "7dad43a2a25d1aec82124a4e53f8d82f8505c3073f23606c1dc5d2a4598eacf9"
|
|
@@ -298,9 +295,7 @@
|
|
|
298
295
|
"hash": "845a692ceabbfc3caf252a97d3e19a213bc0c433df2689900135f9cfded2cf49"
|
|
299
296
|
},
|
|
300
297
|
{
|
|
301
|
-
"sql": [
|
|
302
|
-
"ALTER TABLE \"messages\" ADD COLUMN \"reasoning\" jsonb;"
|
|
303
|
-
],
|
|
298
|
+
"sql": ["ALTER TABLE \"messages\" ADD COLUMN \"reasoning\" jsonb;"],
|
|
304
299
|
"bps": true,
|
|
305
300
|
"folderMillis": 1737609172353,
|
|
306
301
|
"hash": "2cb36ae4fcdd7b7064767e04bfbb36ae34518ff4bb1b39006f2dd394d1893868"
|
|
@@ -515,9 +510,7 @@
|
|
|
515
510
|
"hash": "a7ccf007fd185ff922823148d1eae6fafe652fc98d2fd2793f84a84f29e93cd1"
|
|
516
511
|
},
|
|
517
512
|
{
|
|
518
|
-
"sql": [
|
|
519
|
-
"ALTER TABLE \"ai_providers\" ADD COLUMN \"config\" jsonb;"
|
|
520
|
-
],
|
|
513
|
+
"sql": ["ALTER TABLE \"ai_providers\" ADD COLUMN \"config\" jsonb;"],
|
|
521
514
|
"bps": true,
|
|
522
515
|
"folderMillis": 1749309388370,
|
|
523
516
|
"hash": "39cea379f08ee4cb944875c0b67f7791387b508c2d47958bb4cd501ed1ef33eb"
|
|
@@ -635,9 +628,7 @@
|
|
|
635
628
|
"hash": "1ba9b1f74ea13348da98d6fcdad7867ab4316ed565bf75d84d160c526cdac14b"
|
|
636
629
|
},
|
|
637
630
|
{
|
|
638
|
-
"sql": [
|
|
639
|
-
"ALTER TABLE \"agents\" ADD COLUMN IF NOT EXISTS \"virtual\" boolean DEFAULT false;"
|
|
640
|
-
],
|
|
631
|
+
"sql": ["ALTER TABLE \"agents\" ADD COLUMN IF NOT EXISTS \"virtual\" boolean DEFAULT false;"],
|
|
641
632
|
"bps": true,
|
|
642
633
|
"folderMillis": 1759116400580,
|
|
643
634
|
"hash": "433ddae88e785f2db734e49a4c115eee93e60afe389f7919d66e5ba9aa159a37"
|
|
@@ -687,17 +678,13 @@
|
|
|
687
678
|
"hash": "4bdc6505797d7a33b622498c138cfd47f637239f6905e1c484cd01d9d5f21d6b"
|
|
688
679
|
},
|
|
689
680
|
{
|
|
690
|
-
"sql": [
|
|
691
|
-
"ALTER TABLE \"user_settings\" ADD COLUMN IF NOT EXISTS \"image\" jsonb;"
|
|
692
|
-
],
|
|
681
|
+
"sql": ["ALTER TABLE \"user_settings\" ADD COLUMN IF NOT EXISTS \"image\" jsonb;"],
|
|
693
682
|
"bps": true,
|
|
694
683
|
"folderMillis": 1760108430562,
|
|
695
684
|
"hash": "ce09b301abb80f6563abc2f526bdd20b4f69bae430f09ba2179b9e3bfec43067"
|
|
696
685
|
},
|
|
697
686
|
{
|
|
698
|
-
"sql": [
|
|
699
|
-
"ALTER TABLE \"documents\" ADD COLUMN IF NOT EXISTS \"editor_data\" jsonb;"
|
|
700
|
-
],
|
|
687
|
+
"sql": ["ALTER TABLE \"documents\" ADD COLUMN IF NOT EXISTS \"editor_data\" jsonb;"],
|
|
701
688
|
"bps": true,
|
|
702
689
|
"folderMillis": 1761554153406,
|
|
703
690
|
"hash": "bf2f21293e90e11cf60a784cf3ec219eafa95f7545d7d2f9d1449c0b0949599a"
|
|
@@ -777,19 +764,29 @@
|
|
|
777
764
|
"hash": "923ccbdf46c32be9a981dabd348e6923b4a365444241e9b8cc174bf5b914cbc5"
|
|
778
765
|
},
|
|
779
766
|
{
|
|
780
|
-
"sql": [
|
|
781
|
-
"ALTER TABLE \"agents\" ADD COLUMN IF NOT EXISTS \"market_identifier\" text;\n"
|
|
782
|
-
],
|
|
767
|
+
"sql": ["ALTER TABLE \"agents\" ADD COLUMN IF NOT EXISTS \"market_identifier\" text;\n"],
|
|
783
768
|
"bps": true,
|
|
784
769
|
"folderMillis": 1762870034882,
|
|
785
770
|
"hash": "4178aacb4b8892b7fd15d29209bbf9b1d1f9d7c406ba796f27542c0bcd919680"
|
|
786
771
|
},
|
|
787
772
|
{
|
|
788
|
-
"sql": [
|
|
789
|
-
"ALTER TABLE \"message_plugins\" ADD COLUMN IF NOT EXISTS \"intervention\" jsonb;\n"
|
|
790
|
-
],
|
|
773
|
+
"sql": ["ALTER TABLE \"message_plugins\" ADD COLUMN IF NOT EXISTS \"intervention\" jsonb;\n"],
|
|
791
774
|
"bps": true,
|
|
792
775
|
"folderMillis": 1762911968658,
|
|
793
776
|
"hash": "552a032cc0e595277232e70b5f9338658585bafe9481ae8346a5f322b673a68b"
|
|
777
|
+
},
|
|
778
|
+
{
|
|
779
|
+
"sql": [
|
|
780
|
+
"ALTER TABLE \"documents\" ALTER COLUMN \"id\" SET DATA TYPE varchar(255);",
|
|
781
|
+
"\nALTER TABLE \"documents\" ADD COLUMN \"parent_id\" varchar(255);",
|
|
782
|
+
"\nALTER TABLE \"files\" ADD COLUMN \"parent_id\" varchar(255);",
|
|
783
|
+
"\nALTER TABLE \"documents\" ADD CONSTRAINT \"documents_parent_id_documents_id_fk\" FOREIGN KEY (\"parent_id\") REFERENCES \"public\".\"documents\"(\"id\") ON DELETE set null ON UPDATE no action;",
|
|
784
|
+
"\nALTER TABLE \"files\" ADD CONSTRAINT \"files_parent_id_documents_id_fk\" FOREIGN KEY (\"parent_id\") REFERENCES \"public\".\"documents\"(\"id\") ON DELETE set null ON UPDATE no action;",
|
|
785
|
+
"\nCREATE INDEX \"documents_parent_id_idx\" ON \"documents\" USING btree (\"parent_id\");",
|
|
786
|
+
"\nCREATE INDEX \"files_parent_id_idx\" ON \"files\" USING btree (\"parent_id\");"
|
|
787
|
+
],
|
|
788
|
+
"bps": true,
|
|
789
|
+
"folderMillis": 1763453175961,
|
|
790
|
+
"hash": "6cfc00744de6a8f4d60b793673911bb740f9a50661663e28b843e5adae08f94a"
|
|
794
791
|
}
|
|
795
|
-
]
|
|
792
|
+
]
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { and, desc, eq } from 'drizzle-orm';
|
|
2
2
|
|
|
3
|
-
import { LobeChatDatabase } from '../type';
|
|
4
|
-
|
|
5
3
|
import { DocumentItem, NewDocument, documents } from '../schemas';
|
|
4
|
+
import { LobeChatDatabase } from '../type';
|
|
6
5
|
|
|
7
6
|
export class DocumentModel {
|
|
8
7
|
private userId: string;
|
|
@@ -13,13 +12,13 @@ export class DocumentModel {
|
|
|
13
12
|
this.db = db;
|
|
14
13
|
}
|
|
15
14
|
|
|
16
|
-
create = async (params: Omit<NewDocument, 'userId'>) => {
|
|
17
|
-
const
|
|
15
|
+
create = async (params: Omit<NewDocument, 'userId'>): Promise<DocumentItem> => {
|
|
16
|
+
const result = (await this.db
|
|
18
17
|
.insert(documents)
|
|
19
18
|
.values({ ...params, userId: this.userId })
|
|
20
|
-
.returning();
|
|
19
|
+
.returning()) as DocumentItem[];
|
|
21
20
|
|
|
22
|
-
return result
|
|
21
|
+
return result[0]!;
|
|
23
22
|
};
|
|
24
23
|
|
|
25
24
|
delete = async (id: string) => {
|
|
@@ -43,8 +43,8 @@ export class FileModel {
|
|
|
43
43
|
params: Omit<NewFile, 'id' | 'userId'> & { id?: string; knowledgeBaseId?: string },
|
|
44
44
|
insertToGlobalFiles?: boolean,
|
|
45
45
|
trx?: Transaction,
|
|
46
|
-
) => {
|
|
47
|
-
const executeInTransaction = async (tx: Transaction) => {
|
|
46
|
+
): Promise<{ id: string }> => {
|
|
47
|
+
const executeInTransaction = async (tx: Transaction): Promise<FileItem> => {
|
|
48
48
|
if (insertToGlobalFiles) {
|
|
49
49
|
await tx.insert(globalFiles).values({
|
|
50
50
|
creator: this.userId,
|
|
@@ -56,12 +56,12 @@ export class FileModel {
|
|
|
56
56
|
});
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
-
const result = await tx
|
|
59
|
+
const result = (await tx
|
|
60
60
|
.insert(files)
|
|
61
61
|
.values({ ...params, userId: this.userId })
|
|
62
|
-
.returning();
|
|
62
|
+
.returning()) as FileItem[];
|
|
63
63
|
|
|
64
|
-
const item = result[0]
|
|
64
|
+
const item = result[0]!;
|
|
65
65
|
|
|
66
66
|
if (params.knowledgeBaseId) {
|
|
67
67
|
await tx.insert(knowledgeBaseFiles).values({
|
|
@@ -3,7 +3,7 @@ import { FilesTabs } from '@lobechat/types';
|
|
|
3
3
|
import { beforeEach, describe, expect, it } from 'vitest';
|
|
4
4
|
|
|
5
5
|
import { getTestDB } from '../../models/__tests__/_util';
|
|
6
|
-
import { NewDocument, documents } from '../../schemas/
|
|
6
|
+
import { NewDocument, documents } from '../../schemas/file';
|
|
7
7
|
import { NewFile, files } from '../../schemas/file';
|
|
8
8
|
import { users } from '../../schemas/user';
|
|
9
9
|
import { LobeChatDatabase } from '../../type';
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
/* eslint-disable sort-keys-fix/sort-keys-fix */
|
|
2
|
-
import { FileSource } from '@lobechat/types';
|
|
3
2
|
import {
|
|
4
3
|
boolean,
|
|
5
4
|
index,
|
|
@@ -14,6 +13,9 @@ import {
|
|
|
14
13
|
} from 'drizzle-orm/pg-core';
|
|
15
14
|
import { createInsertSchema } from 'drizzle-zod';
|
|
16
15
|
|
|
16
|
+
import { LobeDocumentPage } from '@/types/document';
|
|
17
|
+
import { FileSource } from '@/types/files';
|
|
18
|
+
|
|
17
19
|
import { idGenerator } from '../utils/idGenerator';
|
|
18
20
|
import { accessedAt, createdAt, timestamps } from './_helpers';
|
|
19
21
|
import { asyncTasks } from './asyncTask';
|
|
@@ -35,6 +37,77 @@ export const globalFiles = pgTable('global_files', {
|
|
|
35
37
|
export type NewGlobalFile = typeof globalFiles.$inferInsert;
|
|
36
38
|
export type GlobalFileItem = typeof globalFiles.$inferSelect;
|
|
37
39
|
|
|
40
|
+
/**
|
|
41
|
+
* 文档表 - 存储文件内容或网页搜索结果
|
|
42
|
+
*/
|
|
43
|
+
// @ts-ignore
|
|
44
|
+
export const documents = pgTable(
|
|
45
|
+
'documents',
|
|
46
|
+
{
|
|
47
|
+
id: varchar('id', { length: 255 })
|
|
48
|
+
.$defaultFn(() => idGenerator('documents', 16))
|
|
49
|
+
.primaryKey(),
|
|
50
|
+
|
|
51
|
+
// 基本信息
|
|
52
|
+
title: text('title'),
|
|
53
|
+
content: text('content'),
|
|
54
|
+
|
|
55
|
+
// Special type: custom/folder
|
|
56
|
+
fileType: varchar('file_type', { length: 255 }).notNull(),
|
|
57
|
+
filename: text('filename'),
|
|
58
|
+
|
|
59
|
+
// 统计信息
|
|
60
|
+
totalCharCount: integer('total_char_count').notNull(),
|
|
61
|
+
totalLineCount: integer('total_line_count').notNull(),
|
|
62
|
+
|
|
63
|
+
// 元数据
|
|
64
|
+
metadata: jsonb('metadata').$type<Record<string, any>>(),
|
|
65
|
+
|
|
66
|
+
// 页面/块数据
|
|
67
|
+
pages: jsonb('pages').$type<LobeDocumentPage[]>(),
|
|
68
|
+
|
|
69
|
+
// 来源类型
|
|
70
|
+
sourceType: text('source_type', { enum: ['file', 'web', 'api'] }).notNull(),
|
|
71
|
+
source: text('source').notNull(), // 文件路径或网页URL
|
|
72
|
+
|
|
73
|
+
// 关联文件(可选)
|
|
74
|
+
// Forward reference to files table defined below
|
|
75
|
+
// eslint-disable-next-line @typescript-eslint/no-use-before-define
|
|
76
|
+
// @ts-expect-error - files is defined later in this file, forward reference is valid at runtime
|
|
77
|
+
// eslint-disable-next-line @typescript-eslint/no-use-before-define
|
|
78
|
+
fileId: text('file_id').references(() => files.id, { onDelete: 'set null' }),
|
|
79
|
+
|
|
80
|
+
// 父文档(用于文件夹层级结构)
|
|
81
|
+
// @ts-ignore
|
|
82
|
+
parentId: varchar('parent_id', { length: 255 }).references(() => documents.id, {
|
|
83
|
+
onDelete: 'set null',
|
|
84
|
+
}),
|
|
85
|
+
|
|
86
|
+
// 用户关联
|
|
87
|
+
userId: text('user_id')
|
|
88
|
+
.references(() => users.id, { onDelete: 'cascade' })
|
|
89
|
+
.notNull(),
|
|
90
|
+
clientId: text('client_id'),
|
|
91
|
+
|
|
92
|
+
editorData: jsonb('editor_data').$type<Record<string, any>>(),
|
|
93
|
+
|
|
94
|
+
// 时间戳
|
|
95
|
+
...timestamps,
|
|
96
|
+
},
|
|
97
|
+
(table) => [
|
|
98
|
+
index('documents_source_idx').on(table.source),
|
|
99
|
+
index('documents_file_type_idx').on(table.fileType),
|
|
100
|
+
index('documents_file_id_idx').on(table.fileId),
|
|
101
|
+
index('documents_parent_id_idx').on(table.parentId),
|
|
102
|
+
uniqueIndex('documents_client_id_user_id_unique').on(table.clientId, table.userId),
|
|
103
|
+
],
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
export type NewDocument = typeof documents.$inferInsert;
|
|
107
|
+
export type DocumentItem = typeof documents.$inferSelect;
|
|
108
|
+
export const insertDocumentSchema = createInsertSchema(documents);
|
|
109
|
+
|
|
110
|
+
// @ts-ignore
|
|
38
111
|
export const files = pgTable(
|
|
39
112
|
'files',
|
|
40
113
|
{
|
|
@@ -60,6 +133,12 @@ export const files = pgTable(
|
|
|
60
133
|
url: text('url').notNull(),
|
|
61
134
|
source: text('source').$type<FileSource>(),
|
|
62
135
|
|
|
136
|
+
// 父文档(用于文件夹层级结构)
|
|
137
|
+
// @ts-ignore
|
|
138
|
+
parentId: varchar('parent_id', { length: 255 }).references(() => documents.id, {
|
|
139
|
+
onDelete: 'set null',
|
|
140
|
+
}),
|
|
141
|
+
|
|
63
142
|
clientId: text('client_id'),
|
|
64
143
|
metadata: jsonb('metadata'),
|
|
65
144
|
chunkTaskId: uuid('chunk_task_id').references(() => asyncTasks.id, { onDelete: 'set null' }),
|
|
@@ -72,6 +151,7 @@ export const files = pgTable(
|
|
|
72
151
|
(table) => {
|
|
73
152
|
return {
|
|
74
153
|
fileHashIdx: index('file_hash_idx').on(table.fileHash),
|
|
154
|
+
parentIdIdx: index('files_parent_id_idx').on(table.parentId),
|
|
75
155
|
clientIdUnique: uniqueIndex('files_client_id_user_id_unique').on(
|
|
76
156
|
table.clientId,
|
|
77
157
|
table.userId,
|
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
integer,
|
|
5
5
|
jsonb,
|
|
6
6
|
pgTable,
|
|
7
|
+
primaryKey,
|
|
7
8
|
text,
|
|
8
9
|
uniqueIndex,
|
|
9
10
|
uuid,
|
|
@@ -11,8 +12,8 @@ import {
|
|
|
11
12
|
vector,
|
|
12
13
|
} from 'drizzle-orm/pg-core';
|
|
13
14
|
|
|
14
|
-
import { timestamps } from './_helpers';
|
|
15
|
-
import { files } from './file';
|
|
15
|
+
import { createdAt, timestamps } from './_helpers';
|
|
16
|
+
import { documents, files } from './file';
|
|
16
17
|
import { users } from './user';
|
|
17
18
|
|
|
18
19
|
export const chunks = pgTable(
|
|
@@ -86,3 +87,32 @@ export const embeddings = pgTable(
|
|
|
86
87
|
|
|
87
88
|
export type NewEmbeddingsItem = typeof embeddings.$inferInsert;
|
|
88
89
|
export type EmbeddingsSelectItem = typeof embeddings.$inferSelect;
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* 文档块表 - 将文档内容分割成块并关联到 chunks 表,用于向量检索
|
|
93
|
+
* 注意:此表可选,如果已经使用 pages 字段存储了文档块,可以不需要此表
|
|
94
|
+
*/
|
|
95
|
+
export const documentChunks = pgTable(
|
|
96
|
+
'document_chunks',
|
|
97
|
+
{
|
|
98
|
+
documentId: varchar('document_id', { length: 30 })
|
|
99
|
+
.references(() => documents.id, { onDelete: 'cascade' })
|
|
100
|
+
.notNull(),
|
|
101
|
+
|
|
102
|
+
chunkId: uuid('chunk_id')
|
|
103
|
+
.references(() => chunks.id, { onDelete: 'cascade' })
|
|
104
|
+
.notNull(),
|
|
105
|
+
|
|
106
|
+
pageIndex: integer('page_index'),
|
|
107
|
+
|
|
108
|
+
userId: text('user_id')
|
|
109
|
+
.references(() => users.id, { onDelete: 'cascade' })
|
|
110
|
+
.notNull(),
|
|
111
|
+
|
|
112
|
+
createdAt: createdAt(),
|
|
113
|
+
},
|
|
114
|
+
(t) => [primaryKey({ columns: [t.documentId, t.chunkId] })],
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
export type NewDocumentChunk = typeof documentChunks.$inferInsert;
|
|
118
|
+
export type DocumentChunkItem = typeof documentChunks.$inferSelect;
|
|
@@ -6,11 +6,10 @@ import { createdAt } from './_helpers';
|
|
|
6
6
|
import { agents, agentsFiles, agentsKnowledgeBases } from './agent';
|
|
7
7
|
import { asyncTasks } from './asyncTask';
|
|
8
8
|
import { chatGroups, chatGroupsAgents } from './chatGroup';
|
|
9
|
-
import {
|
|
10
|
-
import { files, knowledgeBases } from './file';
|
|
9
|
+
import { documents, files, knowledgeBases } from './file';
|
|
11
10
|
import { generationBatches, generationTopics, generations } from './generation';
|
|
12
11
|
import { messageGroups, messages, messagesFiles } from './message';
|
|
13
|
-
import { chunks, unstructuredChunks } from './rag';
|
|
12
|
+
import { chunks, documentChunks, unstructuredChunks } from './rag';
|
|
14
13
|
import { sessionGroups, sessions } from './session';
|
|
15
14
|
import { threads, topicDocuments, topics } from './topic';
|
|
16
15
|
import { users } from './user';
|
|
@@ -6,7 +6,7 @@ import { createInsertSchema } from 'drizzle-zod';
|
|
|
6
6
|
import { idGenerator } from '../utils/idGenerator';
|
|
7
7
|
import { createdAt, timestamps, timestamptz } from './_helpers';
|
|
8
8
|
import { chatGroups } from './chatGroup';
|
|
9
|
-
import { documents } from './
|
|
9
|
+
import { documents } from './file';
|
|
10
10
|
import { sessions } from './session';
|
|
11
11
|
import { users } from './user';
|
|
12
12
|
|
|
@@ -66,7 +66,23 @@ export const fileRouter = router({
|
|
|
66
66
|
const item = await ctx.fileModel.findById(input.id);
|
|
67
67
|
if (!item) throw new TRPCError({ code: 'BAD_REQUEST', message: 'File not found' });
|
|
68
68
|
|
|
69
|
-
return {
|
|
69
|
+
return {
|
|
70
|
+
chunkTaskId: item.chunkTaskId,
|
|
71
|
+
clientId: item.clientId,
|
|
72
|
+
createdAt: item.createdAt,
|
|
73
|
+
embeddingTaskId: item.embeddingTaskId,
|
|
74
|
+
fileHash: item.fileHash,
|
|
75
|
+
fileType: item.fileType,
|
|
76
|
+
id: item.id,
|
|
77
|
+
metadata: item.metadata,
|
|
78
|
+
name: item.name,
|
|
79
|
+
parentId: item.parentId,
|
|
80
|
+
size: item.size,
|
|
81
|
+
source: item.source,
|
|
82
|
+
updatedAt: item.updatedAt,
|
|
83
|
+
url: await ctx.fileService.getFullFileUrl(item.url),
|
|
84
|
+
userId: item.userId,
|
|
85
|
+
};
|
|
70
86
|
}),
|
|
71
87
|
|
|
72
88
|
getFileItemById: fileProcedure
|
|
@@ -92,15 +108,20 @@ export const fileRouter = router({
|
|
|
92
108
|
const chunkCount = await ctx.chunkModel.countByFileId(input.id);
|
|
93
109
|
|
|
94
110
|
return {
|
|
95
|
-
...item,
|
|
96
111
|
chunkCount,
|
|
97
112
|
chunkingError: chunkingTask?.error,
|
|
98
113
|
chunkingStatus: chunkingTask?.status as AsyncTaskStatus,
|
|
114
|
+
createdAt: item.createdAt,
|
|
99
115
|
embeddingError: embeddingTask?.error,
|
|
100
116
|
embeddingStatus: embeddingTask?.status as AsyncTaskStatus,
|
|
117
|
+
fileType: item.fileType,
|
|
101
118
|
finishEmbedding: embeddingTask?.status === AsyncTaskStatus.Success,
|
|
119
|
+
id: item.id,
|
|
102
120
|
metadata: item.metadata as Record<string, any> | null | undefined,
|
|
121
|
+
name: item.name,
|
|
122
|
+
size: item.size,
|
|
103
123
|
sourceType: 'file' as const,
|
|
124
|
+
updatedAt: item.updatedAt,
|
|
104
125
|
url: await ctx.fileService.getFullFileUrl(item.url!),
|
|
105
126
|
};
|
|
106
127
|
}),
|
|
@@ -27,7 +27,16 @@ export class FileService {
|
|
|
27
27
|
throw new Error('file not found');
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
return {
|
|
30
|
+
return {
|
|
31
|
+
createdAt: item.createdAt,
|
|
32
|
+
id: item.id,
|
|
33
|
+
name: item.name,
|
|
34
|
+
size: item.size,
|
|
35
|
+
source: item.source,
|
|
36
|
+
type: item.fileType,
|
|
37
|
+
updatedAt: item.updatedAt,
|
|
38
|
+
url: item.url,
|
|
39
|
+
};
|
|
31
40
|
};
|
|
32
41
|
|
|
33
42
|
removeFile = async (id: string): Promise<void> => {
|
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
/* eslint-disable sort-keys-fix/sort-keys-fix */
|
|
2
|
-
import type { LobeDocumentPage } from '@lobechat/types';
|
|
3
|
-
import {
|
|
4
|
-
index,
|
|
5
|
-
integer,
|
|
6
|
-
jsonb,
|
|
7
|
-
pgTable,
|
|
8
|
-
primaryKey,
|
|
9
|
-
text,
|
|
10
|
-
uniqueIndex,
|
|
11
|
-
uuid,
|
|
12
|
-
varchar,
|
|
13
|
-
} from 'drizzle-orm/pg-core';
|
|
14
|
-
import { createInsertSchema } from 'drizzle-zod';
|
|
15
|
-
|
|
16
|
-
import { idGenerator } from '../utils/idGenerator';
|
|
17
|
-
import { createdAt, timestamps } from './_helpers';
|
|
18
|
-
import { files } from './file';
|
|
19
|
-
import { chunks } from './rag';
|
|
20
|
-
import { users } from './user';
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* 文档表 - 存储文件内容或网页搜索结果
|
|
24
|
-
*/
|
|
25
|
-
export const documents = pgTable(
|
|
26
|
-
'documents',
|
|
27
|
-
{
|
|
28
|
-
id: varchar('id', { length: 30 })
|
|
29
|
-
.$defaultFn(() => idGenerator('documents', 16))
|
|
30
|
-
.primaryKey(),
|
|
31
|
-
|
|
32
|
-
// 基本信息
|
|
33
|
-
title: text('title'),
|
|
34
|
-
content: text('content'),
|
|
35
|
-
fileType: varchar('file_type', { length: 255 }).notNull(),
|
|
36
|
-
filename: text('filename'),
|
|
37
|
-
|
|
38
|
-
// 统计信息
|
|
39
|
-
totalCharCount: integer('total_char_count').notNull(),
|
|
40
|
-
totalLineCount: integer('total_line_count').notNull(),
|
|
41
|
-
|
|
42
|
-
// 元数据
|
|
43
|
-
metadata: jsonb('metadata').$type<Record<string, any>>(),
|
|
44
|
-
|
|
45
|
-
// 页面/块数据
|
|
46
|
-
pages: jsonb('pages').$type<LobeDocumentPage[]>(),
|
|
47
|
-
|
|
48
|
-
// 来源类型
|
|
49
|
-
sourceType: text('source_type', { enum: ['file', 'web', 'api'] }).notNull(),
|
|
50
|
-
source: text('source').notNull(), // 文件路径或网页URL
|
|
51
|
-
|
|
52
|
-
// 关联文件(可选)
|
|
53
|
-
fileId: text('file_id').references(() => files.id, { onDelete: 'set null' }),
|
|
54
|
-
|
|
55
|
-
// 用户关联
|
|
56
|
-
userId: text('user_id')
|
|
57
|
-
.references(() => users.id, { onDelete: 'cascade' })
|
|
58
|
-
.notNull(),
|
|
59
|
-
clientId: text('client_id'),
|
|
60
|
-
|
|
61
|
-
editorData: jsonb('editor_data').$type<Record<string, any>>(),
|
|
62
|
-
|
|
63
|
-
// 时间戳
|
|
64
|
-
...timestamps,
|
|
65
|
-
},
|
|
66
|
-
(table) => [
|
|
67
|
-
index('documents_source_idx').on(table.source),
|
|
68
|
-
index('documents_file_type_idx').on(table.fileType),
|
|
69
|
-
index('documents_file_id_idx').on(table.fileId),
|
|
70
|
-
uniqueIndex('documents_client_id_user_id_unique').on(table.clientId, table.userId),
|
|
71
|
-
],
|
|
72
|
-
);
|
|
73
|
-
|
|
74
|
-
export type NewDocument = typeof documents.$inferInsert;
|
|
75
|
-
export type DocumentItem = typeof documents.$inferSelect;
|
|
76
|
-
export const insertDocumentSchema = createInsertSchema(documents);
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* 文档块表 - 将文档内容分割成块并关联到 chunks 表,用于向量检索
|
|
80
|
-
* 注意:此表可选,如果已经使用 pages 字段存储了文档块,可以不需要此表
|
|
81
|
-
*/
|
|
82
|
-
export const documentChunks = pgTable(
|
|
83
|
-
'document_chunks',
|
|
84
|
-
{
|
|
85
|
-
documentId: varchar('document_id', { length: 30 })
|
|
86
|
-
.references(() => documents.id, { onDelete: 'cascade' })
|
|
87
|
-
.notNull(),
|
|
88
|
-
|
|
89
|
-
chunkId: uuid('chunk_id')
|
|
90
|
-
.references(() => chunks.id, { onDelete: 'cascade' })
|
|
91
|
-
.notNull(),
|
|
92
|
-
|
|
93
|
-
pageIndex: integer('page_index'),
|
|
94
|
-
|
|
95
|
-
userId: text('user_id')
|
|
96
|
-
.references(() => users.id, { onDelete: 'cascade' })
|
|
97
|
-
.notNull(),
|
|
98
|
-
|
|
99
|
-
createdAt: createdAt(),
|
|
100
|
-
},
|
|
101
|
-
(t) => [primaryKey({ columns: [t.documentId, t.chunkId] })],
|
|
102
|
-
);
|
|
103
|
-
|
|
104
|
-
export type NewDocumentChunk = typeof documentChunks.$inferInsert;
|
|
105
|
-
export type DocumentChunkItem = typeof documentChunks.$inferSelect;
|