@lobehub/chat 1.76.1 → 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 +25 -0
- package/changelog/v1.json +9 -0
- package/locales/ar/common.json +12 -1
- package/locales/ar/error.json +10 -0
- package/locales/ar/models.json +9 -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 +9 -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 +9 -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 +9 -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 +9 -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 +9 -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 +9 -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 +9 -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 +9 -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 +9 -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 +9 -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 +9 -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 +9 -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 +9 -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 +9 -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 +9 -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 +9 -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 +9 -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/components/GroupIcon/index.tsx +25 -0
- package/src/components/IndexCard/index.tsx +143 -0
- package/src/components/ProgressItem/index.tsx +75 -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/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
|
+
}
|