@lobehub/chat 1.71.4 → 1.72.0
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 +50 -0
- package/changelog/v1.json +18 -0
- package/docs/developer/database-schema.dbml +16 -0
- package/package.json +3 -3
- package/src/database/client/db.ts +14 -8
- package/src/database/client/migrations.json +62 -0
- package/src/database/migrations/0017_add_user_id_to_tables.sql +225 -0
- package/src/database/migrations/meta/0017_snapshot.json +3858 -0
- package/src/database/migrations/meta/_journal.json +7 -0
- package/src/database/{server/models → models}/__tests__/_test_template.ts +2 -2
- package/src/database/models/__tests__/_util.ts +12 -0
- package/src/database/{server/models → models}/__tests__/agent.test.ts +6 -5
- package/src/database/{server/models → models}/__tests__/aiModel.test.ts +5 -4
- package/src/database/{server/models → models}/__tests__/aiProvider.test.ts +5 -4
- package/src/database/{server/models → models}/__tests__/asyncTask.test.ts +5 -4
- package/src/database/{server/models → models}/__tests__/chunk.test.ts +25 -21
- package/src/database/{server/models → models}/__tests__/file.test.ts +19 -5
- package/src/database/{server/models → models}/__tests__/knowledgeBase.test.ts +9 -4
- package/src/database/{server/models → models}/__tests__/message.test.ts +625 -29
- package/src/database/{server/models → models}/__tests__/plugin.test.ts +5 -4
- package/src/database/{server/models → models}/__tests__/session.test.ts +23 -20
- package/src/database/{server/models → models}/__tests__/sessionGroup.test.ts +5 -4
- package/src/database/{server/models → models}/__tests__/topic.test.ts +5 -4
- package/src/database/repositories/dataImporter/index.ts +3 -0
- package/src/database/schemas/file.ts +38 -32
- package/src/database/schemas/message.ts +21 -0
- package/src/database/schemas/relations.ts +10 -0
- package/src/database/server/models/__tests__/nextauth.test.ts +2 -0
- package/src/database/server/models/__tests__/user.test.ts +13 -1
- package/src/database/server/models/chunk.ts +5 -1
- package/src/database/server/models/file.ts +6 -3
- package/src/database/server/models/message.ts +29 -12
- package/src/database/server/models/session.ts +1 -0
- package/src/features/ShareModal/ShareImage/index.tsx +27 -11
- package/src/hooks/useImgToClipboard.ts +29 -0
- package/src/hooks/useScreenshot.ts +53 -40
- package/src/services/file/client.test.ts +2 -1
- package/src/services/message/client.test.ts +3 -3
- package/src/services/session/client.test.ts +5 -3
- package/src/types/message/base.ts +7 -0
- package/vitest.server.config.ts +1 -1
- package/src/database/server/models/user.test.ts +0 -58
- /package/src/database/{server/models → models}/__tests__/fixtures/embedding.ts +0 -0
@@ -1,12 +1,13 @@
|
|
1
1
|
// @vitest-environment node
|
2
2
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
3
3
|
|
4
|
-
import {
|
4
|
+
import { LobeChatDatabase } from '@/database/type';
|
5
5
|
|
6
|
-
import { NewInstalledPlugin, userInstalledPlugins, users } from '
|
7
|
-
import { PluginModel } from '
|
6
|
+
import { NewInstalledPlugin, userInstalledPlugins, users } from '../../schemas';
|
7
|
+
import { PluginModel } from '../../server/models/plugin';
|
8
|
+
import { getTestDB } from './_util';
|
8
9
|
|
9
|
-
|
10
|
+
const serverDB: LobeChatDatabase = await getTestDB();
|
10
11
|
|
11
12
|
const userId = 'plugin-db';
|
12
13
|
const pluginModel = new PluginModel(serverDB, userId);
|
@@ -3,6 +3,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
3
3
|
|
4
4
|
import { DEFAULT_AGENT_CONFIG } from '@/const/settings';
|
5
5
|
import { getTestDBInstance } from '@/database/server/core/dbForTest';
|
6
|
+
import { LobeChatDatabase } from '@/database/type';
|
6
7
|
import { idGenerator } from '@/database/utils/idGenerator';
|
7
8
|
|
8
9
|
import {
|
@@ -15,10 +16,11 @@ import {
|
|
15
16
|
sessions,
|
16
17
|
topics,
|
17
18
|
users,
|
18
|
-
} from '
|
19
|
-
import { SessionModel } from '
|
19
|
+
} from '../../schemas';
|
20
|
+
import { SessionModel } from '../../server/models/session';
|
21
|
+
import { getTestDB } from './_util';
|
20
22
|
|
21
|
-
|
23
|
+
const serverDB: LobeChatDatabase = await getTestDB();
|
22
24
|
|
23
25
|
const userId = 'session-user';
|
24
26
|
const sessionModel = new SessionModel(serverDB, userId);
|
@@ -236,8 +238,8 @@ describe('SessionModel', () => {
|
|
236
238
|
]);
|
237
239
|
|
238
240
|
await serverDB.insert(agentsToSessions).values([
|
239
|
-
{ agentId: 'agent-1', sessionId: '1' },
|
240
|
-
{ agentId: 'agent-2', sessionId: '2' },
|
241
|
+
{ agentId: 'agent-1', sessionId: '1', userId },
|
242
|
+
{ agentId: 'agent-2', sessionId: '2', userId },
|
241
243
|
]);
|
242
244
|
|
243
245
|
const result = await sessionModel.queryByKeyword('hello');
|
@@ -265,8 +267,8 @@ describe('SessionModel', () => {
|
|
265
267
|
]);
|
266
268
|
|
267
269
|
await serverDB.insert(agentsToSessions).values([
|
268
|
-
{ agentId: 'agent-1', sessionId: '1' },
|
269
|
-
{ agentId: 'agent-2', sessionId: '2' },
|
270
|
+
{ agentId: 'agent-1', sessionId: '1', userId },
|
271
|
+
{ agentId: 'agent-2', sessionId: '2', userId },
|
270
272
|
]);
|
271
273
|
|
272
274
|
const result = await sessionModel.queryByKeyword('keyword');
|
@@ -288,9 +290,9 @@ describe('SessionModel', () => {
|
|
288
290
|
]);
|
289
291
|
|
290
292
|
await serverDB.insert(agentsToSessions).values([
|
291
|
-
{ agentId: '1', sessionId: '1' },
|
292
|
-
{ agentId: '2', sessionId: '2' },
|
293
|
-
{ agentId: '3', sessionId: '3' },
|
293
|
+
{ agentId: '1', sessionId: '1', userId },
|
294
|
+
{ agentId: '2', sessionId: '2', userId },
|
295
|
+
{ agentId: '3', sessionId: '3', userId },
|
294
296
|
]);
|
295
297
|
|
296
298
|
const result = await sessionModel.queryByKeyword('keyword');
|
@@ -361,7 +363,8 @@ describe('SessionModel', () => {
|
|
361
363
|
const result = await sessionModel.batchCreate(sessions);
|
362
364
|
|
363
365
|
// 断言结果
|
364
|
-
|
366
|
+
// pglite return affectedRows while postgres return rowCount
|
367
|
+
expect((result as any).affectedRows || result.rowCount).toEqual(2);
|
365
368
|
});
|
366
369
|
|
367
370
|
it.skip('should set group to default if group does not exist', async () => {
|
@@ -391,7 +394,7 @@ describe('SessionModel', () => {
|
|
391
394
|
.insert(sessions)
|
392
395
|
.values({ id: '1', userId, type: 'agent', title: 'Original Session', pinned: true });
|
393
396
|
await trx.insert(agents).values({ id: 'agent-1', userId, model: 'gpt-3.5-turbo' });
|
394
|
-
await trx.insert(agentsToSessions).values({ agentId: 'agent-1', sessionId: '1' });
|
397
|
+
await trx.insert(agentsToSessions).values({ agentId: 'agent-1', sessionId: '1', userId });
|
395
398
|
});
|
396
399
|
|
397
400
|
// 调用 duplicate 方法
|
@@ -801,9 +804,9 @@ describe('SessionModel', () => {
|
|
801
804
|
|
802
805
|
// Link agents to sessions
|
803
806
|
await trx.insert(agentsToSessions).values([
|
804
|
-
{ sessionId: '1', agentId: 'a1' },
|
805
|
-
{ sessionId: '2', agentId: 'a2' },
|
806
|
-
{ sessionId: '3', agentId: 'a3' },
|
807
|
+
{ sessionId: '1', agentId: 'a1', userId },
|
808
|
+
{ sessionId: '2', agentId: 'a2', userId },
|
809
|
+
{ sessionId: '3', agentId: 'a3', userId },
|
807
810
|
]);
|
808
811
|
|
809
812
|
// Create topics (different counts for ranking)
|
@@ -863,9 +866,9 @@ describe('SessionModel', () => {
|
|
863
866
|
]);
|
864
867
|
|
865
868
|
await trx.insert(agentsToSessions).values([
|
866
|
-
{ sessionId: '1', agentId: 'a1' },
|
867
|
-
{ sessionId: '2', agentId: 'a2' },
|
868
|
-
{ sessionId: '3', agentId: 'a3' },
|
869
|
+
{ sessionId: '1', agentId: 'a1', userId },
|
870
|
+
{ sessionId: '2', agentId: 'a2', userId },
|
871
|
+
{ sessionId: '3', agentId: 'a3', userId },
|
869
872
|
]);
|
870
873
|
|
871
874
|
await trx.insert(topics).values([
|
@@ -899,8 +902,8 @@ describe('SessionModel', () => {
|
|
899
902
|
]);
|
900
903
|
|
901
904
|
await trx.insert(agentsToSessions).values([
|
902
|
-
{ sessionId: '1', agentId: 'a1' },
|
903
|
-
{ sessionId: '2', agentId: 'a2' },
|
905
|
+
{ sessionId: '1', agentId: 'a1', userId },
|
906
|
+
{ sessionId: '2', agentId: 'a2', userId },
|
904
907
|
]);
|
905
908
|
|
906
909
|
// No topics created
|
@@ -2,12 +2,13 @@
|
|
2
2
|
import { eq } from 'drizzle-orm/expressions';
|
3
3
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
4
4
|
|
5
|
-
import {
|
5
|
+
import { LobeChatDatabase } from '@/database/type';
|
6
6
|
|
7
|
-
import { sessionGroups, users } from '
|
8
|
-
import { SessionGroupModel } from '
|
7
|
+
import { sessionGroups, users } from '../../schemas';
|
8
|
+
import { SessionGroupModel } from '../../server/models/sessionGroup';
|
9
|
+
import { getTestDB } from './_util';
|
9
10
|
|
10
|
-
|
11
|
+
const serverDB: LobeChatDatabase = await getTestDB();
|
11
12
|
|
12
13
|
const userId = 'session-group-model-test-user-id';
|
13
14
|
const sessionGroupModel = new SessionGroupModel(serverDB, userId);
|
@@ -1,12 +1,13 @@
|
|
1
1
|
import { eq, inArray } from 'drizzle-orm/expressions';
|
2
2
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
3
3
|
|
4
|
-
import {
|
4
|
+
import { LobeChatDatabase } from '@/database/type';
|
5
5
|
|
6
|
-
import { messages, sessions, topics, users } from '
|
7
|
-
import { CreateTopicParams, TopicModel } from '
|
6
|
+
import { messages, sessions, topics, users } from '../../schemas';
|
7
|
+
import { CreateTopicParams, TopicModel } from '../../server/models/topic';
|
8
|
+
import { getTestDB } from './_util';
|
8
9
|
|
9
|
-
|
10
|
+
const serverDB: LobeChatDatabase = await getTestDB();
|
10
11
|
|
11
12
|
const userId = 'topic-user-test';
|
12
13
|
const sessionId = 'topic-session';
|
@@ -138,6 +138,7 @@ export class DataImporterRepos {
|
|
138
138
|
shouldInsertSessionAgents.map(({ id }, index) => ({
|
139
139
|
agentId: agentMapArray[index].id,
|
140
140
|
sessionId: sessionIdMap[id],
|
141
|
+
userId: this.userId,
|
141
142
|
})),
|
142
143
|
);
|
143
144
|
}
|
@@ -291,6 +292,7 @@ export class DataImporterRepos {
|
|
291
292
|
state: msg.pluginState,
|
292
293
|
toolCallId: msg.tool_call_id,
|
293
294
|
type: msg.plugin?.type,
|
295
|
+
userId: this.userId,
|
294
296
|
})),
|
295
297
|
);
|
296
298
|
}
|
@@ -302,6 +304,7 @@ export class DataImporterRepos {
|
|
302
304
|
translateInserts.map((msg) => ({
|
303
305
|
id: messageIdMap[msg.id],
|
304
306
|
...msg.extra?.translate,
|
307
|
+
userId: this.userId,
|
305
308
|
})),
|
306
309
|
);
|
307
310
|
}
|
@@ -1,6 +1,7 @@
|
|
1
1
|
/* eslint-disable sort-keys-fix/sort-keys-fix */
|
2
2
|
import {
|
3
3
|
boolean,
|
4
|
+
index,
|
4
5
|
integer,
|
5
6
|
jsonb,
|
6
7
|
pgTable,
|
@@ -23,7 +24,9 @@ export const globalFiles = pgTable('global_files', {
|
|
23
24
|
size: integer('size').notNull(),
|
24
25
|
url: text('url').notNull(),
|
25
26
|
metadata: jsonb('metadata'),
|
26
|
-
|
27
|
+
creator: text('creator')
|
28
|
+
.references(() => users.id, { onDelete: 'set null' })
|
29
|
+
.notNull(),
|
27
30
|
createdAt: createdAt(),
|
28
31
|
accessedAt: accessedAt(),
|
29
32
|
});
|
@@ -31,31 +34,38 @@ export const globalFiles = pgTable('global_files', {
|
|
31
34
|
export type NewGlobalFile = typeof globalFiles.$inferInsert;
|
32
35
|
export type GlobalFileItem = typeof globalFiles.$inferSelect;
|
33
36
|
|
34
|
-
export const files = pgTable(
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
.references(() => users.id, { onDelete: 'cascade' })
|
41
|
-
.notNull(),
|
42
|
-
fileType: varchar('file_type', { length: 255 }).notNull(),
|
43
|
-
fileHash: varchar('file_hash', { length: 64 }).references(() => globalFiles.hashId, {
|
44
|
-
onDelete: 'no action',
|
45
|
-
}),
|
46
|
-
name: text('name').notNull(),
|
47
|
-
size: integer('size').notNull(),
|
48
|
-
url: text('url').notNull(),
|
49
|
-
|
50
|
-
metadata: jsonb('metadata'),
|
51
|
-
chunkTaskId: uuid('chunk_task_id').references(() => asyncTasks.id, { onDelete: 'set null' }),
|
52
|
-
embeddingTaskId: uuid('embedding_task_id').references(() => asyncTasks.id, {
|
53
|
-
onDelete: 'set null',
|
54
|
-
}),
|
37
|
+
export const files = pgTable(
|
38
|
+
'files',
|
39
|
+
{
|
40
|
+
id: text('id')
|
41
|
+
.$defaultFn(() => idGenerator('files'))
|
42
|
+
.primaryKey(),
|
55
43
|
|
56
|
-
|
57
|
-
})
|
44
|
+
userId: text('user_id')
|
45
|
+
.references(() => users.id, { onDelete: 'cascade' })
|
46
|
+
.notNull(),
|
47
|
+
fileType: varchar('file_type', { length: 255 }).notNull(),
|
48
|
+
fileHash: varchar('file_hash', { length: 64 }).references(() => globalFiles.hashId, {
|
49
|
+
onDelete: 'no action',
|
50
|
+
}),
|
51
|
+
name: text('name').notNull(),
|
52
|
+
size: integer('size').notNull(),
|
53
|
+
url: text('url').notNull(),
|
54
|
+
|
55
|
+
metadata: jsonb('metadata'),
|
56
|
+
chunkTaskId: uuid('chunk_task_id').references(() => asyncTasks.id, { onDelete: 'set null' }),
|
57
|
+
embeddingTaskId: uuid('embedding_task_id').references(() => asyncTasks.id, {
|
58
|
+
onDelete: 'set null',
|
59
|
+
}),
|
58
60
|
|
61
|
+
...timestamps,
|
62
|
+
},
|
63
|
+
(table) => {
|
64
|
+
return {
|
65
|
+
fileHashIdx: index('file_hash_idx').on(table.fileHash),
|
66
|
+
};
|
67
|
+
},
|
68
|
+
);
|
59
69
|
export type NewFile = typeof files.$inferInsert;
|
60
70
|
export type FileItem = typeof files.$inferSelect;
|
61
71
|
|
@@ -97,19 +107,15 @@ export const knowledgeBaseFiles = pgTable(
|
|
97
107
|
.references(() => files.id, { onDelete: 'cascade' })
|
98
108
|
.notNull(),
|
99
109
|
|
100
|
-
|
101
|
-
|
102
|
-
|
110
|
+
userId: text('user_id')
|
111
|
+
.references(() => users.id, { onDelete: 'cascade' })
|
112
|
+
.notNull(),
|
103
113
|
|
104
114
|
createdAt: createdAt(),
|
105
115
|
},
|
106
116
|
(t) => ({
|
107
117
|
pk: primaryKey({
|
108
|
-
columns: [
|
109
|
-
t.knowledgeBaseId,
|
110
|
-
t.fileId,
|
111
|
-
// t.userId
|
112
|
-
],
|
118
|
+
columns: [t.knowledgeBaseId, t.fileId],
|
113
119
|
}),
|
114
120
|
}),
|
115
121
|
);
|
@@ -95,6 +95,9 @@ export const messagePlugins = pgTable('message_plugins', {
|
|
95
95
|
identifier: text('identifier'),
|
96
96
|
state: jsonb('state'),
|
97
97
|
error: jsonb('error'),
|
98
|
+
userId: text('user_id')
|
99
|
+
.references(() => users.id, { onDelete: 'cascade' })
|
100
|
+
.notNull(),
|
98
101
|
});
|
99
102
|
|
100
103
|
export type MessagePluginItem = typeof messagePlugins.$inferSelect;
|
@@ -107,6 +110,9 @@ export const messageTTS = pgTable('message_tts', {
|
|
107
110
|
contentMd5: text('content_md5'),
|
108
111
|
fileId: text('file_id').references(() => files.id, { onDelete: 'cascade' }),
|
109
112
|
voice: text('voice'),
|
113
|
+
userId: text('user_id')
|
114
|
+
.references(() => users.id, { onDelete: 'cascade' })
|
115
|
+
.notNull(),
|
110
116
|
});
|
111
117
|
|
112
118
|
export const messageTranslates = pgTable('message_translates', {
|
@@ -116,6 +122,9 @@ export const messageTranslates = pgTable('message_translates', {
|
|
116
122
|
content: text('content'),
|
117
123
|
from: text('from'),
|
118
124
|
to: text('to'),
|
125
|
+
userId: text('user_id')
|
126
|
+
.references(() => users.id, { onDelete: 'cascade' })
|
127
|
+
.notNull(),
|
119
128
|
});
|
120
129
|
|
121
130
|
// if the message contains a file
|
@@ -129,6 +138,9 @@ export const messagesFiles = pgTable(
|
|
129
138
|
messageId: text('message_id')
|
130
139
|
.notNull()
|
131
140
|
.references(() => messages.id, { onDelete: 'cascade' }),
|
141
|
+
userId: text('user_id')
|
142
|
+
.references(() => users.id, { onDelete: 'cascade' })
|
143
|
+
.notNull(),
|
132
144
|
},
|
133
145
|
(t) => ({
|
134
146
|
pk: primaryKey({ columns: [t.fileId, t.messageId] }),
|
@@ -142,6 +154,9 @@ export const messageQueries = pgTable('message_queries', {
|
|
142
154
|
.notNull(),
|
143
155
|
rewriteQuery: text('rewrite_query'),
|
144
156
|
userQuery: text('user_query'),
|
157
|
+
userId: text('user_id')
|
158
|
+
.references(() => users.id, { onDelete: 'cascade' })
|
159
|
+
.notNull(),
|
145
160
|
embeddingsId: uuid('embeddings_id').references(() => embeddings.id, { onDelete: 'set null' }),
|
146
161
|
});
|
147
162
|
|
@@ -154,6 +169,9 @@ export const messageQueryChunks = pgTable(
|
|
154
169
|
queryId: uuid('query_id').references(() => messageQueries.id, { onDelete: 'cascade' }),
|
155
170
|
chunkId: uuid('chunk_id').references(() => chunks.id, { onDelete: 'cascade' }),
|
156
171
|
similarity: numeric('similarity', { precision: 6, scale: 5 }),
|
172
|
+
userId: text('user_id')
|
173
|
+
.references(() => users.id, { onDelete: 'cascade' })
|
174
|
+
.notNull(),
|
157
175
|
},
|
158
176
|
(t) => ({
|
159
177
|
pk: primaryKey({ columns: [t.chunkId, t.messageId, t.queryId] }),
|
@@ -168,6 +186,9 @@ export const messageChunks = pgTable(
|
|
168
186
|
{
|
169
187
|
messageId: text('message_id').references(() => messages.id, { onDelete: 'cascade' }),
|
170
188
|
chunkId: uuid('chunk_id').references(() => chunks.id, { onDelete: 'cascade' }),
|
189
|
+
userId: text('user_id')
|
190
|
+
.references(() => users.id, { onDelete: 'cascade' })
|
191
|
+
.notNull(),
|
171
192
|
},
|
172
193
|
(t) => ({
|
173
194
|
pk: primaryKey({ columns: [t.chunkId, t.messageId] }),
|
@@ -11,6 +11,7 @@ import { messages, messagesFiles } from './message';
|
|
11
11
|
import { chunks, unstructuredChunks } from './rag';
|
12
12
|
import { sessionGroups, sessions } from './session';
|
13
13
|
import { threads, topics } from './topic';
|
14
|
+
import { users } from './user';
|
14
15
|
|
15
16
|
export const agentsToSessions = pgTable(
|
16
17
|
'agents_to_sessions',
|
@@ -21,6 +22,9 @@ export const agentsToSessions = pgTable(
|
|
21
22
|
sessionId: text('session_id')
|
22
23
|
.notNull()
|
23
24
|
.references(() => sessions.id, { onDelete: 'cascade' }),
|
25
|
+
userId: text('user_id')
|
26
|
+
.references(() => users.id, { onDelete: 'cascade' })
|
27
|
+
.notNull(),
|
24
28
|
},
|
25
29
|
(t) => ({
|
26
30
|
pk: primaryKey({ columns: [t.agentId, t.sessionId] }),
|
@@ -36,6 +40,9 @@ export const filesToSessions = pgTable(
|
|
36
40
|
sessionId: text('session_id')
|
37
41
|
.notNull()
|
38
42
|
.references(() => sessions.id, { onDelete: 'cascade' }),
|
43
|
+
userId: text('user_id')
|
44
|
+
.references(() => users.id, { onDelete: 'cascade' })
|
45
|
+
.notNull(),
|
39
46
|
},
|
40
47
|
(t) => ({
|
41
48
|
pk: primaryKey({ columns: [t.fileId, t.sessionId] }),
|
@@ -48,6 +55,9 @@ export const fileChunks = pgTable(
|
|
48
55
|
fileId: varchar('file_id').references(() => files.id, { onDelete: 'cascade' }),
|
49
56
|
chunkId: uuid('chunk_id').references(() => chunks.id, { onDelete: 'cascade' }),
|
50
57
|
createdAt: createdAt(),
|
58
|
+
userId: text('user_id')
|
59
|
+
.references(() => users.id, { onDelete: 'cascade' })
|
60
|
+
.notNull(),
|
51
61
|
},
|
52
62
|
(t) => ({
|
53
63
|
pk: primaryKey({ columns: [t.fileId, t.chunkId] }),
|
@@ -16,9 +16,11 @@ import {
|
|
16
16
|
users,
|
17
17
|
} from '@/database/schemas';
|
18
18
|
import { getTestDBInstance } from '@/database/server/core/dbForTest';
|
19
|
+
import { LobeChatDatabase } from '@/database/type';
|
19
20
|
import { LobeNextAuthDbAdapter } from '@/libs/next-auth/adapter';
|
20
21
|
|
21
22
|
let serverDB = await getTestDBInstance();
|
23
|
+
|
22
24
|
let nextAuthAdapter = LobeNextAuthDbAdapter(serverDB);
|
23
25
|
|
24
26
|
const userId = 'user-db';
|
@@ -1,15 +1,17 @@
|
|
1
|
+
import { TRPCError } from '@trpc/server';
|
1
2
|
import dayjs from 'dayjs';
|
2
3
|
import { eq } from 'drizzle-orm/expressions';
|
3
4
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
4
5
|
|
5
6
|
import { INBOX_SESSION_ID } from '@/const/session';
|
6
7
|
import { getTestDBInstance } from '@/database/server/core/dbForTest';
|
8
|
+
import { LobeChatDatabase } from '@/database/type';
|
7
9
|
import { KeyVaultsGateKeeper } from '@/server/modules/KeyVaultsEncrypt';
|
8
10
|
import { UserGuide, UserPreference } from '@/types/user';
|
9
11
|
|
10
12
|
import { UserSettingsItem, userSettings, users } from '../../../schemas';
|
11
13
|
import { SessionModel } from '../session';
|
12
|
-
import { UserModel } from '../user';
|
14
|
+
import { UserModel, UserNotFoundError } from '../user';
|
13
15
|
|
14
16
|
let serverDB = await getTestDBInstance();
|
15
17
|
|
@@ -408,3 +410,13 @@ describe('UserModel', () => {
|
|
408
410
|
});
|
409
411
|
});
|
410
412
|
});
|
413
|
+
|
414
|
+
describe('UserNotFoundError', () => {
|
415
|
+
it('should extend TRPCError with correct code and message', () => {
|
416
|
+
const error = new UserNotFoundError();
|
417
|
+
|
418
|
+
expect(error).toBeInstanceOf(TRPCError);
|
419
|
+
expect(error.code).toBe('UNAUTHORIZED');
|
420
|
+
expect(error.message).toBe('user not found');
|
421
|
+
});
|
422
|
+
});
|
@@ -31,7 +31,11 @@ export class ChunkModel {
|
|
31
31
|
|
32
32
|
const result = await trx.insert(chunks).values(params).returning();
|
33
33
|
|
34
|
-
const fileChunksData = result.map((chunk) => ({
|
34
|
+
const fileChunksData = result.map((chunk) => ({
|
35
|
+
chunkId: chunk.id,
|
36
|
+
fileId,
|
37
|
+
userId: this.userId,
|
38
|
+
}));
|
35
39
|
|
36
40
|
if (fileChunksData.length > 0) {
|
37
41
|
await trx.insert(fileChunks).values(fileChunksData);
|
@@ -33,6 +33,7 @@ export class FileModel {
|
|
33
33
|
const result = await this.db.transaction(async (trx) => {
|
34
34
|
if (insertToGlobalFiles) {
|
35
35
|
await trx.insert(globalFiles).values({
|
36
|
+
creator: this.userId,
|
36
37
|
fileType: params.fileType,
|
37
38
|
hashId: params.fileHash!,
|
38
39
|
metadata: params.metadata,
|
@@ -49,9 +50,11 @@ export class FileModel {
|
|
49
50
|
const item = result[0];
|
50
51
|
|
51
52
|
if (params.knowledgeBaseId) {
|
52
|
-
await trx
|
53
|
-
.
|
54
|
-
|
53
|
+
await trx.insert(knowledgeBaseFiles).values({
|
54
|
+
fileId: item.id,
|
55
|
+
knowledgeBaseId: params.knowledgeBaseId,
|
56
|
+
userId: this.userId,
|
57
|
+
});
|
55
58
|
}
|
56
59
|
|
57
60
|
return item;
|
@@ -21,6 +21,7 @@ import {
|
|
21
21
|
CreateMessageParams,
|
22
22
|
MessageItem,
|
23
23
|
ModelRankItem,
|
24
|
+
NewMessageQueryParams,
|
24
25
|
UpdateMessageParams,
|
25
26
|
} from '@/types/message';
|
26
27
|
import { merge } from '@/utils/merge';
|
@@ -28,7 +29,6 @@ import { today } from '@/utils/time';
|
|
28
29
|
|
29
30
|
import {
|
30
31
|
MessagePluginItem,
|
31
|
-
NewMessageQuery,
|
32
32
|
chunks,
|
33
33
|
embeddings,
|
34
34
|
fileChunks,
|
@@ -458,13 +458,14 @@ export class MessageModel {
|
|
458
458
|
state: pluginState,
|
459
459
|
toolCallId: message.tool_call_id,
|
460
460
|
type: plugin?.type,
|
461
|
+
userId: this.userId,
|
461
462
|
});
|
462
463
|
}
|
463
464
|
|
464
465
|
if (files && files.length > 0) {
|
465
466
|
await trx
|
466
467
|
.insert(messagesFiles)
|
467
|
-
.values(files.map((file) => ({ fileId: file, messageId: id })));
|
468
|
+
.values(files.map((file) => ({ fileId: file, messageId: id, userId: this.userId })));
|
468
469
|
}
|
469
470
|
|
470
471
|
if (fileChunks && fileChunks.length > 0 && ragQueryId) {
|
@@ -474,6 +475,7 @@ export class MessageModel {
|
|
474
475
|
messageId: id,
|
475
476
|
queryId: ragQueryId,
|
476
477
|
similarity: chunk.similarity?.toString(),
|
478
|
+
userId: this.userId,
|
477
479
|
})),
|
478
480
|
);
|
479
481
|
}
|
@@ -491,8 +493,11 @@ export class MessageModel {
|
|
491
493
|
return this.db.insert(messages).values(messagesToInsert);
|
492
494
|
};
|
493
495
|
|
494
|
-
createMessageQuery = async (params:
|
495
|
-
const result = await this.db
|
496
|
+
createMessageQuery = async (params: NewMessageQueryParams) => {
|
497
|
+
const result = await this.db
|
498
|
+
.insert(messageQueries)
|
499
|
+
.values({ ...params, userId: this.userId })
|
500
|
+
.returning();
|
496
501
|
|
497
502
|
return result[0];
|
498
503
|
};
|
@@ -504,7 +509,9 @@ export class MessageModel {
|
|
504
509
|
if (imageList && imageList.length > 0) {
|
505
510
|
await trx
|
506
511
|
.insert(messagesFiles)
|
507
|
-
.values(
|
512
|
+
.values(
|
513
|
+
imageList.map((file) => ({ fileId: file.id, messageId: id, userId: this.userId })),
|
514
|
+
);
|
508
515
|
}
|
509
516
|
|
510
517
|
return trx
|
@@ -547,7 +554,7 @@ export class MessageModel {
|
|
547
554
|
|
548
555
|
// If the message does not exist in the translate table, insert it
|
549
556
|
if (!result) {
|
550
|
-
return this.db.insert(messageTranslates).values({ ...translate, id });
|
557
|
+
return this.db.insert(messageTranslates).values({ ...translate, id, userId: this.userId });
|
551
558
|
}
|
552
559
|
|
553
560
|
// or just update the existing one
|
@@ -561,9 +568,13 @@ export class MessageModel {
|
|
561
568
|
|
562
569
|
// If the message does not exist in the translate table, insert it
|
563
570
|
if (!result) {
|
564
|
-
return this.db
|
565
|
-
.
|
566
|
-
|
571
|
+
return this.db.insert(messageTTS).values({
|
572
|
+
contentMd5: tts.contentMd5,
|
573
|
+
fileId: tts.file,
|
574
|
+
id,
|
575
|
+
userId: this.userId,
|
576
|
+
voice: tts.voice,
|
577
|
+
});
|
567
578
|
}
|
568
579
|
|
569
580
|
// or just update the existing one
|
@@ -618,13 +629,19 @@ export class MessageModel {
|
|
618
629
|
.where(and(eq(messages.userId, this.userId), inArray(messages.id, ids)));
|
619
630
|
|
620
631
|
deleteMessageTranslate = async (id: string) =>
|
621
|
-
this.db
|
632
|
+
this.db
|
633
|
+
.delete(messageTranslates)
|
634
|
+
.where(and(eq(messageTranslates.id, id), eq(messageTranslates.userId, this.userId)));
|
622
635
|
|
623
636
|
deleteMessageTTS = async (id: string) =>
|
624
|
-
this.db
|
637
|
+
this.db
|
638
|
+
.delete(messageTTS)
|
639
|
+
.where(and(eq(messageTTS.id, id), eq(messageTTS.userId, this.userId)));
|
625
640
|
|
626
641
|
deleteMessageQuery = async (id: string) =>
|
627
|
-
this.db
|
642
|
+
this.db
|
643
|
+
.delete(messageQueries)
|
644
|
+
.where(and(eq(messageQueries.id, id), eq(messageQueries.userId, this.userId)));
|
628
645
|
|
629
646
|
deleteMessagesBySession = async (sessionId?: string | null, topicId?: string | null) =>
|
630
647
|
this.db
|
@@ -1,10 +1,12 @@
|
|
1
|
-
import { Form, type FormItemProps } from '@lobehub/ui';
|
1
|
+
import { Form, type FormItemProps, Icon } from '@lobehub/ui';
|
2
2
|
import { Button, Segmented, Switch } from 'antd';
|
3
|
+
import { CopyIcon } from 'lucide-react';
|
3
4
|
import { memo, useState } from 'react';
|
4
5
|
import { useTranslation } from 'react-i18next';
|
5
6
|
import { Flexbox } from 'react-layout-kit';
|
6
7
|
|
7
8
|
import { FORM_STYLE } from '@/const/layoutTokens';
|
9
|
+
import { useImgToClipboard } from '@/hooks/useImgToClipboard';
|
8
10
|
import { useIsMobile } from '@/hooks/useIsMobile';
|
9
11
|
import { ImageType, imageTypeOptions, useScreenshot } from '@/hooks/useScreenshot';
|
10
12
|
import { useSessionStore } from '@/store/session';
|
@@ -32,7 +34,9 @@ const ShareImage = memo<{ mobile?: boolean }>(({ mobile }) => {
|
|
32
34
|
title: currentAgentTitle,
|
33
35
|
width: mobile ? 720 : undefined,
|
34
36
|
});
|
35
|
-
|
37
|
+
const { loading: copyLoading, onCopy } = useImgToClipboard({
|
38
|
+
width: mobile ? 720 : undefined,
|
39
|
+
});
|
36
40
|
const settings: FormItemProps[] = [
|
37
41
|
{
|
38
42
|
children: <Switch />,
|
@@ -66,15 +70,27 @@ const ShareImage = memo<{ mobile?: boolean }>(({ mobile }) => {
|
|
66
70
|
const isMobile = useIsMobile();
|
67
71
|
|
68
72
|
const button = (
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
73
|
+
<>
|
74
|
+
<Button
|
75
|
+
block
|
76
|
+
icon={<Icon icon={CopyIcon} />}
|
77
|
+
loading={copyLoading}
|
78
|
+
onClick={() => onCopy()}
|
79
|
+
size={isMobile ? undefined : 'large'}
|
80
|
+
type={'primary'}
|
81
|
+
>
|
82
|
+
{t('copy', { ns: 'common' })}
|
83
|
+
</Button>
|
84
|
+
<Button
|
85
|
+
block
|
86
|
+
loading={loading}
|
87
|
+
onClick={onDownload}
|
88
|
+
size={isMobile ? undefined : 'large'}
|
89
|
+
variant={'filled'}
|
90
|
+
>
|
91
|
+
{t('shareModal.download')}
|
92
|
+
</Button>
|
93
|
+
</>
|
78
94
|
);
|
79
95
|
|
80
96
|
return (
|