@lobehub/chat 1.76.0 → 1.77.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/Dockerfile +3 -2
- package/Dockerfile.database +3 -1
- package/Dockerfile.pglite +3 -1
- package/changelog/v1.json +18 -0
- package/locales/ar/common.json +12 -1
- package/locales/ar/error.json +10 -0
- package/locales/ar/models.json +12 -6
- package/locales/ar/setting.json +28 -0
- package/locales/bg-BG/common.json +12 -1
- package/locales/bg-BG/error.json +10 -0
- package/locales/bg-BG/models.json +12 -6
- package/locales/bg-BG/setting.json +28 -0
- package/locales/de-DE/common.json +12 -1
- package/locales/de-DE/error.json +10 -0
- package/locales/de-DE/models.json +12 -6
- package/locales/de-DE/setting.json +28 -0
- package/locales/en-US/common.json +12 -1
- package/locales/en-US/error.json +10 -0
- package/locales/en-US/models.json +12 -6
- package/locales/en-US/setting.json +28 -0
- package/locales/es-ES/common.json +12 -1
- package/locales/es-ES/error.json +10 -0
- package/locales/es-ES/models.json +12 -6
- package/locales/es-ES/setting.json +28 -0
- package/locales/fa-IR/common.json +12 -1
- package/locales/fa-IR/error.json +10 -0
- package/locales/fa-IR/models.json +12 -6
- package/locales/fa-IR/setting.json +28 -0
- package/locales/fr-FR/common.json +12 -1
- package/locales/fr-FR/error.json +10 -0
- package/locales/fr-FR/models.json +12 -6
- package/locales/fr-FR/setting.json +28 -0
- package/locales/it-IT/common.json +12 -1
- package/locales/it-IT/error.json +10 -0
- package/locales/it-IT/models.json +12 -6
- package/locales/it-IT/setting.json +28 -0
- package/locales/ja-JP/common.json +12 -1
- package/locales/ja-JP/error.json +10 -0
- package/locales/ja-JP/models.json +12 -6
- package/locales/ja-JP/setting.json +28 -0
- package/locales/ko-KR/common.json +12 -1
- package/locales/ko-KR/error.json +10 -0
- package/locales/ko-KR/models.json +12 -6
- package/locales/ko-KR/setting.json +28 -0
- package/locales/nl-NL/common.json +12 -1
- package/locales/nl-NL/error.json +10 -0
- package/locales/nl-NL/models.json +12 -6
- package/locales/nl-NL/setting.json +28 -0
- package/locales/pl-PL/common.json +12 -1
- package/locales/pl-PL/error.json +10 -0
- package/locales/pl-PL/models.json +12 -6
- package/locales/pl-PL/setting.json +28 -0
- package/locales/pt-BR/common.json +12 -1
- package/locales/pt-BR/error.json +10 -0
- package/locales/pt-BR/models.json +12 -6
- package/locales/pt-BR/setting.json +28 -0
- package/locales/ru-RU/common.json +12 -1
- package/locales/ru-RU/error.json +10 -0
- package/locales/ru-RU/models.json +12 -6
- package/locales/ru-RU/setting.json +28 -0
- package/locales/tr-TR/common.json +12 -1
- package/locales/tr-TR/error.json +10 -0
- package/locales/tr-TR/models.json +12 -6
- package/locales/tr-TR/setting.json +28 -0
- package/locales/vi-VN/common.json +12 -1
- package/locales/vi-VN/error.json +10 -0
- package/locales/vi-VN/models.json +12 -6
- package/locales/vi-VN/setting.json +28 -0
- package/locales/zh-CN/common.json +12 -1
- package/locales/zh-CN/error.json +10 -0
- package/locales/zh-CN/models.json +12 -6
- package/locales/zh-CN/setting.json +28 -0
- package/locales/zh-TW/common.json +12 -1
- package/locales/zh-TW/error.json +10 -0
- package/locales/zh-TW/models.json +12 -6
- package/locales/zh-TW/setting.json +28 -0
- package/package.json +1 -1
- package/src/app/[variants]/(main)/(mobile)/me/data/features/Category.tsx +1 -1
- package/src/app/[variants]/(main)/chat/features/Migration/UpgradeButton.tsx +2 -1
- package/src/app/[variants]/(main)/settings/common/features/Common.tsx +0 -44
- package/src/app/[variants]/(main)/settings/hooks/useCategory.tsx +40 -14
- package/src/app/[variants]/(main)/settings/storage/Advanced.tsx +133 -0
- package/src/app/[variants]/(main)/settings/storage/IndexedDBStorage.tsx +55 -0
- package/src/app/[variants]/(main)/settings/storage/page.tsx +17 -0
- package/src/app/[variants]/(main)/settings/tts/features/const.tsx +4 -0
- package/src/components/GroupIcon/index.tsx +25 -0
- package/src/components/IndexCard/index.tsx +143 -0
- package/src/components/ProgressItem/index.tsx +75 -0
- package/src/config/aiModels/openai.ts +10 -0
- package/src/database/repositories/dataExporter/index.test.ts +330 -0
- package/src/database/repositories/dataExporter/index.ts +216 -0
- package/src/database/repositories/dataImporter/__tests__/fixtures/agents.json +65 -0
- package/src/database/repositories/dataImporter/__tests__/fixtures/agentsToSessions.json +541 -0
- package/src/database/repositories/dataImporter/__tests__/fixtures/topic.json +269 -0
- package/src/database/repositories/dataImporter/__tests__/fixtures/userSettings.json +18 -0
- package/src/database/repositories/dataImporter/__tests__/fixtures/with-client-id.json +778 -0
- package/src/database/repositories/dataImporter/__tests__/index.test.ts +120 -880
- package/src/database/repositories/dataImporter/deprecated/__tests__/index.test.ts +940 -0
- package/src/database/repositories/dataImporter/deprecated/index.ts +326 -0
- package/src/database/repositories/dataImporter/index.ts +684 -289
- package/src/features/DataImporter/ImportDetail.tsx +203 -0
- package/src/features/DataImporter/SuccessResult.tsx +22 -6
- package/src/features/DataImporter/_deprecated.ts +43 -0
- package/src/features/DataImporter/config.ts +21 -0
- package/src/features/DataImporter/index.tsx +112 -31
- package/src/features/DevPanel/PostgresViewer/DataTable/index.tsx +6 -0
- package/src/features/User/UserPanel/useMenu.tsx +0 -35
- package/src/features/User/__tests__/useMenu.test.tsx +0 -2
- package/src/locales/default/common.ts +11 -0
- package/src/locales/default/error.ts +10 -0
- package/src/locales/default/setting.ts +28 -0
- package/src/server/routers/lambda/exporter.ts +25 -0
- package/src/server/routers/lambda/importer.ts +19 -3
- package/src/server/routers/lambda/index.ts +2 -0
- package/src/services/config.ts +80 -135
- package/src/services/export/_deprecated.ts +155 -0
- package/src/services/export/client.ts +15 -0
- package/src/services/export/index.ts +6 -0
- package/src/services/export/server.ts +9 -0
- package/src/services/export/type.ts +5 -0
- package/src/services/import/_deprecated.ts +42 -1
- package/src/services/import/client.test.ts +1 -1
- package/src/services/import/client.ts +30 -1
- package/src/services/import/server.ts +70 -2
- package/src/services/import/type.ts +10 -0
- package/src/store/global/initialState.ts +1 -0
- package/src/types/export.ts +11 -0
- package/src/types/exportConfig.ts +2 -0
- package/src/types/importer.ts +15 -0
- package/src/types/user/settings/tts.ts +1 -1
- package/src/utils/client/exportFile.ts +21 -0
- package/vitest.config.ts +1 -1
- package/src/utils/config.ts +0 -109
- /package/src/database/repositories/dataImporter/{__tests__ → deprecated/__tests__}/fixtures/messages.json +0 -0
@@ -0,0 +1,326 @@
|
|
1
|
+
import { sql } from 'drizzle-orm';
|
2
|
+
import { and, eq, inArray } from 'drizzle-orm/expressions';
|
3
|
+
|
4
|
+
import {
|
5
|
+
agents,
|
6
|
+
agentsToSessions,
|
7
|
+
messagePlugins,
|
8
|
+
messageTranslates,
|
9
|
+
messages,
|
10
|
+
sessionGroups,
|
11
|
+
sessions,
|
12
|
+
topics,
|
13
|
+
} from '@/database/schemas';
|
14
|
+
import { LobeChatDatabase } from '@/database/type';
|
15
|
+
import { ImportResult } from '@/services/import/_deprecated';
|
16
|
+
import { ImporterEntryData } from '@/types/importer';
|
17
|
+
import { sanitizeUTF8 } from '@/utils/sanitizeUTF8';
|
18
|
+
|
19
|
+
export class DeprecatedDataImporterRepos {
|
20
|
+
private userId: string;
|
21
|
+
private db: LobeChatDatabase;
|
22
|
+
|
23
|
+
/**
|
24
|
+
* The version of the importer that this module supports
|
25
|
+
*/
|
26
|
+
supportVersion = 7;
|
27
|
+
|
28
|
+
constructor(db: LobeChatDatabase, userId: string) {
|
29
|
+
this.userId = userId;
|
30
|
+
this.db = db;
|
31
|
+
}
|
32
|
+
|
33
|
+
importData = async (data: ImporterEntryData) => {
|
34
|
+
if (data.version > this.supportVersion) throw new Error('Unsupported version');
|
35
|
+
|
36
|
+
let sessionGroupResult: ImportResult = { added: 0, errors: 0, skips: 0 };
|
37
|
+
let sessionResult: ImportResult = { added: 0, errors: 0, skips: 0 };
|
38
|
+
let topicResult: ImportResult = { added: 0, errors: 0, skips: 0 };
|
39
|
+
let messageResult: ImportResult = { added: 0, errors: 0, skips: 0 };
|
40
|
+
|
41
|
+
let sessionGroupIdMap: Record<string, string> = {};
|
42
|
+
let sessionIdMap: Record<string, string> = {};
|
43
|
+
let topicIdMap: Record<string, string> = {};
|
44
|
+
|
45
|
+
await this.db.transaction(async (trx) => {
|
46
|
+
// import sessionGroups
|
47
|
+
if (data.sessionGroups && data.sessionGroups.length > 0) {
|
48
|
+
const query = await trx.query.sessionGroups.findMany({
|
49
|
+
where: and(
|
50
|
+
eq(sessionGroups.userId, this.userId),
|
51
|
+
inArray(
|
52
|
+
sessionGroups.clientId,
|
53
|
+
data.sessionGroups.map(({ id }) => id),
|
54
|
+
),
|
55
|
+
),
|
56
|
+
});
|
57
|
+
|
58
|
+
sessionGroupResult.skips = query.length;
|
59
|
+
|
60
|
+
const mapArray = await trx
|
61
|
+
.insert(sessionGroups)
|
62
|
+
.values(
|
63
|
+
data.sessionGroups.map(({ id, createdAt, updatedAt, ...res }) => ({
|
64
|
+
...res,
|
65
|
+
clientId: id,
|
66
|
+
createdAt: new Date(createdAt),
|
67
|
+
updatedAt: new Date(updatedAt),
|
68
|
+
userId: this.userId,
|
69
|
+
})),
|
70
|
+
)
|
71
|
+
.onConflictDoUpdate({
|
72
|
+
set: { updatedAt: new Date() },
|
73
|
+
target: [sessionGroups.clientId, sessionGroups.userId],
|
74
|
+
})
|
75
|
+
.returning({ clientId: sessionGroups.clientId, id: sessionGroups.id });
|
76
|
+
|
77
|
+
sessionGroupResult.added = mapArray.length - query.length;
|
78
|
+
|
79
|
+
sessionGroupIdMap = Object.fromEntries(mapArray.map(({ clientId, id }) => [clientId, id]));
|
80
|
+
}
|
81
|
+
|
82
|
+
// import sessions
|
83
|
+
if (data.sessions && data.sessions.length > 0) {
|
84
|
+
const query = await trx.query.sessions.findMany({
|
85
|
+
where: and(
|
86
|
+
eq(sessions.userId, this.userId),
|
87
|
+
inArray(
|
88
|
+
sessions.clientId,
|
89
|
+
data.sessions.map(({ id }) => id),
|
90
|
+
),
|
91
|
+
),
|
92
|
+
});
|
93
|
+
|
94
|
+
sessionResult.skips = query.length;
|
95
|
+
|
96
|
+
const mapArray = await trx
|
97
|
+
.insert(sessions)
|
98
|
+
.values(
|
99
|
+
data.sessions.map(({ id, createdAt, updatedAt, group, ...res }) => ({
|
100
|
+
...res,
|
101
|
+
clientId: id,
|
102
|
+
createdAt: new Date(createdAt),
|
103
|
+
groupId: group ? sessionGroupIdMap[group] : null,
|
104
|
+
updatedAt: new Date(updatedAt),
|
105
|
+
userId: this.userId,
|
106
|
+
})),
|
107
|
+
)
|
108
|
+
.onConflictDoUpdate({
|
109
|
+
set: { updatedAt: new Date() },
|
110
|
+
target: [sessions.clientId, sessions.userId],
|
111
|
+
})
|
112
|
+
.returning({ clientId: sessions.clientId, id: sessions.id });
|
113
|
+
|
114
|
+
// get the session client-server id map
|
115
|
+
sessionIdMap = Object.fromEntries(mapArray.map(({ clientId, id }) => [clientId, id]));
|
116
|
+
|
117
|
+
// update added count
|
118
|
+
sessionResult.added = mapArray.length - query.length;
|
119
|
+
|
120
|
+
const shouldInsertSessionAgents = data.sessions
|
121
|
+
// filter out existing session, only insert new ones
|
122
|
+
.filter((s) => query.every((q) => q.clientId !== s.id));
|
123
|
+
|
124
|
+
// 只有当需要有新的 session 时,才会插入 agent
|
125
|
+
if (shouldInsertSessionAgents.length > 0) {
|
126
|
+
const agentMapArray = await trx
|
127
|
+
.insert(agents)
|
128
|
+
.values(
|
129
|
+
shouldInsertSessionAgents.map(({ config, meta }) => ({
|
130
|
+
...config,
|
131
|
+
...meta,
|
132
|
+
userId: this.userId,
|
133
|
+
})),
|
134
|
+
)
|
135
|
+
.returning({ id: agents.id });
|
136
|
+
|
137
|
+
await trx.insert(agentsToSessions).values(
|
138
|
+
shouldInsertSessionAgents.map(({ id }, index) => ({
|
139
|
+
agentId: agentMapArray[index].id,
|
140
|
+
sessionId: sessionIdMap[id],
|
141
|
+
userId: this.userId,
|
142
|
+
})),
|
143
|
+
);
|
144
|
+
}
|
145
|
+
}
|
146
|
+
|
147
|
+
// import topics
|
148
|
+
if (data.topics && data.topics.length > 0) {
|
149
|
+
const skipQuery = await trx.query.topics.findMany({
|
150
|
+
where: and(
|
151
|
+
eq(topics.userId, this.userId),
|
152
|
+
inArray(
|
153
|
+
topics.clientId,
|
154
|
+
data.topics.map(({ id }) => id),
|
155
|
+
),
|
156
|
+
),
|
157
|
+
});
|
158
|
+
topicResult.skips = skipQuery.length;
|
159
|
+
|
160
|
+
const mapArray = await trx
|
161
|
+
.insert(topics)
|
162
|
+
.values(
|
163
|
+
data.topics.map(({ id, createdAt, updatedAt, sessionId, favorite, ...res }) => ({
|
164
|
+
...res,
|
165
|
+
clientId: id,
|
166
|
+
createdAt: new Date(createdAt),
|
167
|
+
favorite: Boolean(favorite),
|
168
|
+
sessionId: sessionId ? sessionIdMap[sessionId] : null,
|
169
|
+
updatedAt: new Date(updatedAt),
|
170
|
+
userId: this.userId,
|
171
|
+
})),
|
172
|
+
)
|
173
|
+
.onConflictDoUpdate({
|
174
|
+
set: { updatedAt: new Date() },
|
175
|
+
target: [topics.clientId, topics.userId],
|
176
|
+
})
|
177
|
+
.returning({ clientId: topics.clientId, id: topics.id });
|
178
|
+
|
179
|
+
topicIdMap = Object.fromEntries(mapArray.map(({ clientId, id }) => [clientId, id]));
|
180
|
+
|
181
|
+
topicResult.added = mapArray.length - skipQuery.length;
|
182
|
+
}
|
183
|
+
|
184
|
+
// import messages
|
185
|
+
if (data.messages && data.messages.length > 0) {
|
186
|
+
// 1. find skip ones
|
187
|
+
console.time('find messages');
|
188
|
+
const skipQuery = await trx.query.messages.findMany({
|
189
|
+
where: and(
|
190
|
+
eq(messages.userId, this.userId),
|
191
|
+
inArray(
|
192
|
+
messages.clientId,
|
193
|
+
data.messages.map(({ id }) => id),
|
194
|
+
),
|
195
|
+
),
|
196
|
+
});
|
197
|
+
console.timeEnd('find messages');
|
198
|
+
|
199
|
+
messageResult.skips = skipQuery.length;
|
200
|
+
|
201
|
+
// filter out existing messages, only insert new ones
|
202
|
+
const shouldInsertMessages = data.messages.filter((s) =>
|
203
|
+
skipQuery.every((q) => q.clientId !== s.id),
|
204
|
+
);
|
205
|
+
|
206
|
+
// 2. insert messages
|
207
|
+
if (shouldInsertMessages.length > 0) {
|
208
|
+
const inertValues = shouldInsertMessages.map(
|
209
|
+
({ id, extra, createdAt, updatedAt, sessionId, topicId, content, ...res }) => ({
|
210
|
+
...res,
|
211
|
+
clientId: id,
|
212
|
+
content: sanitizeUTF8(content),
|
213
|
+
createdAt: new Date(createdAt),
|
214
|
+
model: extra?.fromModel,
|
215
|
+
parentId: null,
|
216
|
+
provider: extra?.fromProvider,
|
217
|
+
sessionId: sessionId ? sessionIdMap[sessionId] : null,
|
218
|
+
topicId: topicId ? topicIdMap[topicId] : null, // 暂时设为 NULL
|
219
|
+
updatedAt: new Date(updatedAt),
|
220
|
+
userId: this.userId,
|
221
|
+
}),
|
222
|
+
);
|
223
|
+
|
224
|
+
console.time('insert messages');
|
225
|
+
const BATCH_SIZE = 100; // 每批次插入的记录数
|
226
|
+
|
227
|
+
for (let i = 0; i < inertValues.length; i += BATCH_SIZE) {
|
228
|
+
const batch = inertValues.slice(i, i + BATCH_SIZE);
|
229
|
+
await trx.insert(messages).values(batch);
|
230
|
+
}
|
231
|
+
|
232
|
+
console.timeEnd('insert messages');
|
233
|
+
|
234
|
+
const messageIdArray = await trx
|
235
|
+
.select({ clientId: messages.clientId, id: messages.id })
|
236
|
+
.from(messages)
|
237
|
+
.where(
|
238
|
+
and(
|
239
|
+
eq(messages.userId, this.userId),
|
240
|
+
inArray(
|
241
|
+
messages.clientId,
|
242
|
+
data.messages.map(({ id }) => id),
|
243
|
+
),
|
244
|
+
),
|
245
|
+
);
|
246
|
+
|
247
|
+
const messageIdMap = Object.fromEntries(
|
248
|
+
messageIdArray.map(({ clientId, id }) => [clientId, id]),
|
249
|
+
);
|
250
|
+
|
251
|
+
// 3. update parentId for messages
|
252
|
+
console.time('execute updates parentId');
|
253
|
+
const parentIdUpdates = shouldInsertMessages
|
254
|
+
.filter((msg) => msg.parentId) // 只处理有 parentId 的消息
|
255
|
+
.map((msg) => {
|
256
|
+
if (messageIdMap[msg.parentId as string])
|
257
|
+
return sql`WHEN ${messages.clientId} = ${msg.id} THEN ${messageIdMap[msg.parentId as string]} `;
|
258
|
+
|
259
|
+
return undefined;
|
260
|
+
})
|
261
|
+
.filter(Boolean);
|
262
|
+
|
263
|
+
if (parentIdUpdates.length > 0) {
|
264
|
+
await trx
|
265
|
+
.update(messages)
|
266
|
+
.set({
|
267
|
+
parentId: sql`CASE ${sql.join(parentIdUpdates)} END`,
|
268
|
+
})
|
269
|
+
.where(
|
270
|
+
inArray(
|
271
|
+
messages.clientId,
|
272
|
+
data.messages.map((msg) => msg.id),
|
273
|
+
),
|
274
|
+
);
|
275
|
+
|
276
|
+
// if needed, you can print the sql and params
|
277
|
+
// const SQL = updateQuery.toSQL();
|
278
|
+
// console.log('sql:', SQL.sql);
|
279
|
+
// console.log('params:', SQL.params);
|
280
|
+
}
|
281
|
+
console.timeEnd('execute updates parentId');
|
282
|
+
|
283
|
+
// 4. insert message plugins
|
284
|
+
const pluginInserts = shouldInsertMessages.filter((msg) => msg.plugin);
|
285
|
+
if (pluginInserts.length > 0) {
|
286
|
+
await trx.insert(messagePlugins).values(
|
287
|
+
pluginInserts.map((msg) => ({
|
288
|
+
apiName: msg.plugin?.apiName,
|
289
|
+
arguments: msg.plugin?.arguments,
|
290
|
+
id: messageIdMap[msg.id],
|
291
|
+
identifier: msg.plugin?.identifier,
|
292
|
+
state: msg.pluginState,
|
293
|
+
toolCallId: msg.tool_call_id,
|
294
|
+
type: msg.plugin?.type,
|
295
|
+
userId: this.userId,
|
296
|
+
})),
|
297
|
+
);
|
298
|
+
}
|
299
|
+
|
300
|
+
// 5. insert message translate
|
301
|
+
const translateInserts = shouldInsertMessages.filter((msg) => msg.extra?.translate);
|
302
|
+
if (translateInserts.length > 0) {
|
303
|
+
await trx.insert(messageTranslates).values(
|
304
|
+
translateInserts.map((msg) => ({
|
305
|
+
id: messageIdMap[msg.id],
|
306
|
+
...msg.extra?.translate,
|
307
|
+
userId: this.userId,
|
308
|
+
})),
|
309
|
+
);
|
310
|
+
}
|
311
|
+
|
312
|
+
// TODO: 未来需要处理 TTS 和图片的插入 (目前存在 file 的部分,不方便处理)
|
313
|
+
}
|
314
|
+
|
315
|
+
messageResult.added = shouldInsertMessages.length;
|
316
|
+
}
|
317
|
+
});
|
318
|
+
|
319
|
+
return {
|
320
|
+
messages: messageResult,
|
321
|
+
sessionGroups: sessionGroupResult,
|
322
|
+
sessions: sessionResult,
|
323
|
+
topics: topicResult,
|
324
|
+
};
|
325
|
+
};
|
326
|
+
}
|