@lobehub/chat 0.120.5 → 0.121.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/.github/ISSUE_TEMPLATE/2_feature_request_cn.yml +1 -1
- package/CHANGELOG.md +50 -0
- package/README.md +9 -9
- package/README.zh-CN.md +9 -9
- package/docs/Development/Feature-Development-Frontend.zh-CN.md +126 -0
- package/docs/Development/Feature-Development.zh-CN.md +664 -83
- package/docs/Development/{State-Management/State-Management-Intro.zh-CN.md → State-Management-Intro.zh-CN.md} +9 -9
- package/docs/Home.md +1 -0
- package/docs/_Sidebar.md +1 -0
- package/locales/ar/chat.json +13 -1
- package/locales/ar/common.json +1 -0
- package/locales/de-DE/chat.json +13 -1
- package/locales/de-DE/common.json +1 -0
- package/locales/en-US/chat.json +13 -1
- package/locales/en-US/common.json +1 -0
- package/locales/es-ES/chat.json +13 -1
- package/locales/es-ES/common.json +1 -0
- package/locales/fr-FR/chat.json +13 -1
- package/locales/fr-FR/common.json +1 -0
- package/locales/it-IT/chat.json +13 -1
- package/locales/it-IT/common.json +1 -0
- package/locales/ja-JP/chat.json +13 -1
- package/locales/ja-JP/common.json +1 -0
- package/locales/ko-KR/chat.json +13 -1
- package/locales/ko-KR/common.json +1 -0
- package/locales/nl-NL/chat.json +13 -1
- package/locales/nl-NL/common.json +1 -0
- package/locales/pl-PL/chat.json +13 -1
- package/locales/pl-PL/common.json +1 -0
- package/locales/pt-BR/chat.json +13 -1
- package/locales/pt-BR/common.json +1 -0
- package/locales/ru-RU/chat.json +13 -1
- package/locales/ru-RU/common.json +1 -0
- package/locales/tr-TR/chat.json +13 -1
- package/locales/tr-TR/common.json +1 -0
- package/locales/vi-VN/chat.json +13 -1
- package/locales/vi-VN/common.json +1 -0
- package/locales/zh-CN/chat.json +13 -1
- package/locales/zh-CN/common.json +1 -0
- package/locales/zh-TW/chat.json +13 -1
- package/locales/zh-TW/common.json +1 -0
- package/package.json +4 -6
- package/src/app/chat/(desktop)/features/ChatInput/Footer/index.tsx +18 -5
- package/src/app/chat/(desktop)/features/ChatInput/index.tsx +1 -1
- package/src/app/chat/features/ChatHeader/ShareButton/index.tsx +0 -1
- package/src/app/chat/features/ChatHeader/ShareButton/style.ts +0 -1
- package/src/app/chat/features/Migration/Failed.tsx +2 -0
- package/src/app/chat/features/SessionListContent/CollapseGroup/Actions.tsx +115 -0
- package/src/app/chat/features/SessionListContent/CollapseGroup/index.tsx +6 -0
- package/src/app/chat/features/SessionListContent/DefaultMode.tsx +63 -23
- package/src/app/chat/features/SessionListContent/List/AddButton.tsx +3 -3
- package/src/app/chat/features/SessionListContent/List/Item/Actions.tsx +89 -35
- package/src/app/chat/features/SessionListContent/List/Item/index.tsx +35 -15
- package/src/app/chat/features/SessionListContent/List/index.tsx +4 -2
- package/src/app/chat/features/SessionListContent/Modals/ConfigGroupModal/GroupItem.tsx +84 -0
- package/src/app/chat/features/SessionListContent/Modals/ConfigGroupModal/index.tsx +79 -0
- package/src/app/chat/features/SessionListContent/Modals/CreateGroupModal.tsx +63 -0
- package/src/app/chat/features/SessionListContent/Modals/RenameGroupModal.tsx +50 -0
- package/src/app/chat/features/SessionListContent/SearchMode.tsx +1 -1
- package/src/app/settings/common/Common.tsx +5 -1
- package/src/components/FullscreenLoading/index.tsx +1 -1
- package/src/database/core/__tests__/db-upgrade.test.ts +42 -0
- package/src/database/core/db.ts +26 -4
- package/src/database/core/schemas.ts +14 -1
- package/src/database/core/types/db.ts +2 -0
- package/src/database/models/__tests__/message.test.ts +42 -0
- package/src/database/models/__tests__/plugin.test.ts +77 -0
- package/src/database/models/__tests__/session.test.ts +202 -63
- package/src/database/models/__tests__/sessionGroup.test.ts +195 -0
- package/src/database/models/__tests__/topic.test.ts +76 -1
- package/src/database/models/message.ts +7 -8
- package/src/database/models/session.ts +103 -7
- package/src/database/models/sessionGroup.ts +76 -0
- package/src/database/schemas/session.ts +1 -0
- package/src/database/schemas/sessionGroup.ts +8 -0
- package/src/features/ChatInput/ActionBar/Clear.tsx +2 -1
- package/src/features/Conversation/Actions/Assistant.tsx +1 -1
- package/src/features/Conversation/Actions/Function.tsx +2 -2
- package/src/features/Conversation/Actions/User.tsx +1 -1
- package/src/features/Conversation/hooks/useChatListActionsBar.tsx +3 -3
- package/src/features/PluginDevModal/index.tsx +3 -0
- package/src/features/PluginStore/PluginItem/Action.tsx +2 -0
- package/src/locales/default/chat.ts +13 -1
- package/src/locales/default/common.ts +1 -0
- package/src/migrations/FromV2ToV3/fixtures/input-v2-session.json +72 -0
- package/src/migrations/FromV2ToV3/fixtures/output-v3-from-v1.json +203 -0
- package/src/migrations/FromV2ToV3/fixtures/output-v3.json +74 -0
- package/src/migrations/FromV2ToV3/index.ts +30 -0
- package/src/migrations/FromV2ToV3/migrations.test.ts +42 -0
- package/src/migrations/FromV2ToV3/types/v3.ts +27 -0
- package/src/migrations/index.ts +7 -1
- package/src/services/__tests__/session.test.ts +198 -7
- package/src/services/config.ts +23 -7
- package/src/services/session.ts +67 -14
- package/src/store/global/slices/common/action.test.ts +130 -0
- package/src/store/global/slices/common/action.ts +14 -0
- package/src/store/global/slices/common/initialState.ts +6 -2
- package/src/store/global/slices/common/selectors.ts +1 -4
- package/src/store/global/slices/settings/__snapshots__/selectors.test.ts.snap +83 -0
- package/src/store/global/slices/settings/action.ts +2 -2
- package/src/store/global/slices/settings/selectors.test.ts +132 -182
- package/src/store/global/store.ts +7 -0
- package/src/store/session/initialState.ts +5 -30
- package/src/store/session/selectors.ts +2 -1
- package/src/store/session/slices/session/action.test.ts +56 -0
- package/src/store/session/slices/session/action.ts +13 -9
- package/src/store/session/slices/session/helpers.ts +2 -3
- package/src/store/session/slices/session/initialState.ts +31 -23
- package/src/store/session/slices/session/selectors/index.ts +0 -1
- package/src/store/session/slices/session/selectors/list.ts +11 -18
- package/src/store/session/slices/sessionGroup/action.ts +54 -0
- package/src/store/session/slices/sessionGroup/initialState.ts +5 -0
- package/src/store/session/slices/sessionGroup/selectors.ts +15 -0
- package/src/store/session/store.ts +9 -2
- package/src/styles/antdOverride.ts +0 -7
- package/src/types/exportConfig.ts +3 -1
- package/src/types/session.ts +27 -3
- package/src/store/session/slices/session/selectors/export.ts +0 -31
- /package/docs/Development/{Feature-Development.md → Feature-Development-Frontend.md} +0 -0
- /package/docs/Development/{State-Management/State-Management-Intro.md → State-Management-Intro.md} +0 -0
- /package/docs/Development/{State-Management/Selectors.md → State-Management-Selectors.md} +0 -0
- /package/docs/Development/{State-Management/Selectors.zh-CN.md → State-Management-Selectors.zh-CN.md} +0 -0
|
@@ -25,6 +25,7 @@ describe('TopicModel', () => {
|
|
|
25
25
|
// Clean up the database after each test
|
|
26
26
|
await TopicModel.clearTable();
|
|
27
27
|
});
|
|
28
|
+
|
|
28
29
|
describe('create', () => {
|
|
29
30
|
it('should create a topic record', async () => {
|
|
30
31
|
const result = await TopicModel.create(topicData);
|
|
@@ -395,7 +396,7 @@ describe('TopicModel', () => {
|
|
|
395
396
|
it('should fail if the database transaction fails', async () => {
|
|
396
397
|
// 强制数据库事务失败,例如通过在复制过程中抛出异常
|
|
397
398
|
const dbTransactionFailedError = new Error('DB transaction failed');
|
|
398
|
-
vi.spyOn(TopicModel['db'], 'transaction').mockImplementation((async () => {
|
|
399
|
+
const spyOn = vi.spyOn(TopicModel['db'], 'transaction').mockImplementation((async () => {
|
|
399
400
|
throw dbTransactionFailedError;
|
|
400
401
|
}) as any);
|
|
401
402
|
|
|
@@ -403,6 +404,7 @@ describe('TopicModel', () => {
|
|
|
403
404
|
await expect(TopicModel.duplicateTopic(originalTopic.id)).rejects.toThrow(
|
|
404
405
|
dbTransactionFailedError,
|
|
405
406
|
);
|
|
407
|
+
spyOn.mockRestore();
|
|
406
408
|
});
|
|
407
409
|
|
|
408
410
|
it('should not create partial duplicates if the process fails at some point', async () => {
|
|
@@ -422,4 +424,77 @@ describe('TopicModel', () => {
|
|
|
422
424
|
expect(messages).toHaveLength(originalMessages.length); // 只有原始消息
|
|
423
425
|
});
|
|
424
426
|
});
|
|
427
|
+
|
|
428
|
+
describe('clearTable', () => {
|
|
429
|
+
it('should clear the table', async () => {
|
|
430
|
+
// Create a topic to ensure the table is not empty
|
|
431
|
+
await TopicModel.create(topicData);
|
|
432
|
+
|
|
433
|
+
// Clear the table
|
|
434
|
+
await TopicModel.clearTable();
|
|
435
|
+
|
|
436
|
+
// Verify the table is empty
|
|
437
|
+
const topics = await TopicModel.queryAll();
|
|
438
|
+
expect(topics).toHaveLength(0);
|
|
439
|
+
});
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
describe('update', () => {
|
|
443
|
+
it('should update a topic', async () => {
|
|
444
|
+
// Create a topic
|
|
445
|
+
const createdTopic = await TopicModel.create(topicData);
|
|
446
|
+
|
|
447
|
+
// Update the topic
|
|
448
|
+
const newTitle = 'Updated Title';
|
|
449
|
+
await TopicModel.update(createdTopic.id, { title: newTitle });
|
|
450
|
+
|
|
451
|
+
// Verify the topic is updated
|
|
452
|
+
const updatedTopic = await TopicModel.findById(createdTopic.id);
|
|
453
|
+
expect(updatedTopic.title).toBe(newTitle);
|
|
454
|
+
});
|
|
455
|
+
});
|
|
456
|
+
|
|
457
|
+
describe('batchDelete', () => {
|
|
458
|
+
it('should batch delete topics', async () => {
|
|
459
|
+
// Create multiple topics
|
|
460
|
+
const topic1 = await TopicModel.create(topicData);
|
|
461
|
+
const topic2 = await TopicModel.create(topicData);
|
|
462
|
+
|
|
463
|
+
await TopicModel.create(topicData);
|
|
464
|
+
|
|
465
|
+
const ids = [topic1.id, topic2.id];
|
|
466
|
+
// Batch delete the topics
|
|
467
|
+
await TopicModel.batchDelete(ids);
|
|
468
|
+
|
|
469
|
+
expect(await TopicModel.table.count()).toEqual(1);
|
|
470
|
+
});
|
|
471
|
+
});
|
|
472
|
+
|
|
473
|
+
describe('queryAll', () => {
|
|
474
|
+
it('should query all topics', async () => {
|
|
475
|
+
// Create multiple topics
|
|
476
|
+
await TopicModel.batchCreate([topicData, topicData]);
|
|
477
|
+
|
|
478
|
+
// Query all topics
|
|
479
|
+
const topics = await TopicModel.queryAll();
|
|
480
|
+
|
|
481
|
+
// Verify all topics are queried
|
|
482
|
+
expect(topics).toHaveLength(2);
|
|
483
|
+
});
|
|
484
|
+
});
|
|
485
|
+
|
|
486
|
+
describe('queryByKeyword', () => {
|
|
487
|
+
it('should query topics by keyword', async () => {
|
|
488
|
+
// Create a topic with a unique title
|
|
489
|
+
const uniqueTitle = 'Unique Title';
|
|
490
|
+
await TopicModel.create({ ...topicData, title: uniqueTitle });
|
|
491
|
+
|
|
492
|
+
// Query topics by the unique title
|
|
493
|
+
const topics = await TopicModel.queryByKeyword(uniqueTitle);
|
|
494
|
+
|
|
495
|
+
// Verify the correct topic is queried
|
|
496
|
+
expect(topics).toHaveLength(1);
|
|
497
|
+
expect(topics[0].title).toBe(uniqueTitle);
|
|
498
|
+
});
|
|
499
|
+
});
|
|
425
500
|
});
|
|
@@ -132,7 +132,7 @@ class _MessageModel extends BaseModel {
|
|
|
132
132
|
|
|
133
133
|
/**
|
|
134
134
|
* Deletes multiple messages based on the assistantId and optionally the topicId.
|
|
135
|
-
* If topicId is not provided, it deletes messages where topicId is undefined.
|
|
135
|
+
* If topicId is not provided, it deletes messages where topicId is undefined or null.
|
|
136
136
|
* If topicId is provided, it deletes messages with that specific topicId.
|
|
137
137
|
*
|
|
138
138
|
* @param {string} sessionId - The identifier of the assistant associated with the messages.
|
|
@@ -142,13 +142,12 @@ class _MessageModel extends BaseModel {
|
|
|
142
142
|
async batchDelete(sessionId: string, topicId: string | undefined): Promise<void> {
|
|
143
143
|
// If topicId is specified, use both assistantId and topicId as the filter criteria in the query.
|
|
144
144
|
// Otherwise, filter by assistantId and require that topicId is undefined.
|
|
145
|
-
const query =
|
|
146
|
-
topicId
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
.and((message) => message.topicId === undefined);
|
|
145
|
+
const query = !!topicId
|
|
146
|
+
? this.table.where({ sessionId, topicId }) // Use a compound index
|
|
147
|
+
: this.table
|
|
148
|
+
.where('sessionId')
|
|
149
|
+
.equals(sessionId)
|
|
150
|
+
.and((message) => !message.topicId);
|
|
152
151
|
|
|
153
152
|
// Retrieve a collection of message IDs that satisfy the criteria
|
|
154
153
|
const messageIds = await query.primaryKeys();
|
|
@@ -3,9 +3,16 @@ import { DeepPartial } from 'utility-types';
|
|
|
3
3
|
import { DEFAULT_AGENT_LOBE_SESSION } from '@/const/session';
|
|
4
4
|
import { BaseModel } from '@/database/core';
|
|
5
5
|
import { DBModel } from '@/database/core/types/db';
|
|
6
|
+
import { SessionGroupModel } from '@/database/models/sessionGroup';
|
|
6
7
|
import { DB_Session, DB_SessionSchema } from '@/database/schemas/session';
|
|
7
8
|
import { LobeAgentConfig } from '@/types/agent';
|
|
8
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
ChatSessionList,
|
|
11
|
+
LobeAgentSession,
|
|
12
|
+
LobeSessions,
|
|
13
|
+
SessionDefaultGroup,
|
|
14
|
+
SessionGroupId,
|
|
15
|
+
} from '@/types/session';
|
|
9
16
|
import { merge } from '@/utils/merge';
|
|
10
17
|
import { uuid } from '@/utils/uuid';
|
|
11
18
|
|
|
@@ -20,7 +27,21 @@ class _SessionModel extends BaseModel {
|
|
|
20
27
|
}
|
|
21
28
|
|
|
22
29
|
async batchCreate(sessions: LobeAgentSession[]) {
|
|
23
|
-
|
|
30
|
+
const DB_Sessions = await Promise.all(
|
|
31
|
+
sessions.map(async (s) => {
|
|
32
|
+
if (s.group && s.group !== SessionDefaultGroup.Default) {
|
|
33
|
+
// Check if the group exists in the SessionGroup table
|
|
34
|
+
const groupExists = await SessionGroupModel.findById(s.group);
|
|
35
|
+
// If the group does not exist, set it to default
|
|
36
|
+
if (!groupExists) {
|
|
37
|
+
s.group = SessionDefaultGroup.Default;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return this.mapToDB_Session(s);
|
|
41
|
+
}),
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
return this._batchAdd<DB_Session>(DB_Sessions, { idGenerator: uuid });
|
|
24
45
|
}
|
|
25
46
|
|
|
26
47
|
async query({
|
|
@@ -29,21 +50,66 @@ class _SessionModel extends BaseModel {
|
|
|
29
50
|
}: { current?: number; pageSize?: number } = {}): Promise<LobeSessions> {
|
|
30
51
|
const offset = current * pageSize;
|
|
31
52
|
|
|
32
|
-
|
|
53
|
+
const items: DBModel<DB_Session>[] = await this.table
|
|
54
|
+
.orderBy('updatedAt')
|
|
55
|
+
.reverse()
|
|
56
|
+
.offset(offset)
|
|
57
|
+
.limit(pageSize)
|
|
58
|
+
.toArray();
|
|
59
|
+
|
|
60
|
+
return this.mapToAgentSessions(items);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async queryWithGroups(): Promise<ChatSessionList> {
|
|
64
|
+
const groups = await SessionGroupModel.query();
|
|
65
|
+
const customGroups = await this.queryByGroupIds(groups.map((item) => item.id));
|
|
66
|
+
const defaultItems = await this.querySessionsByGroupId(SessionDefaultGroup.Default);
|
|
67
|
+
const pinnedItems = await this.getPinnedSessions();
|
|
68
|
+
|
|
69
|
+
const all = await this.query();
|
|
70
|
+
return {
|
|
71
|
+
all,
|
|
72
|
+
customGroup: groups.map((group) => ({
|
|
73
|
+
...group,
|
|
74
|
+
children: customGroups[group.id],
|
|
75
|
+
})),
|
|
76
|
+
default: defaultItems,
|
|
77
|
+
pinned: pinnedItems,
|
|
78
|
+
};
|
|
33
79
|
}
|
|
34
80
|
|
|
35
81
|
/**
|
|
36
82
|
* get sessions by group
|
|
37
83
|
* @param group
|
|
38
84
|
*/
|
|
39
|
-
async
|
|
40
|
-
|
|
85
|
+
async querySessionsByGroupId(group: SessionGroupId): Promise<LobeSessions> {
|
|
86
|
+
const items: DBModel<DB_Session>[] = await this.table
|
|
87
|
+
.where('group')
|
|
88
|
+
.equals(group)
|
|
89
|
+
.and((session) => !session.pinned)
|
|
90
|
+
.reverse()
|
|
91
|
+
.sortBy('updatedAt');
|
|
92
|
+
|
|
93
|
+
return this.mapToAgentSessions(items);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async queryByGroupIds(groups: string[]) {
|
|
97
|
+
const pools = groups.map(async (id) => {
|
|
98
|
+
return [id, await this.querySessionsByGroupId(id)] as const;
|
|
99
|
+
});
|
|
100
|
+
const groupItems = await Promise.all(pools);
|
|
101
|
+
|
|
102
|
+
return Object.fromEntries(groupItems);
|
|
41
103
|
}
|
|
42
104
|
|
|
43
105
|
async update(id: string, data: Partial<DB_Session>) {
|
|
44
106
|
return super._update(id, data);
|
|
45
107
|
}
|
|
46
108
|
|
|
109
|
+
async updatePinned(id: string, pinned: boolean) {
|
|
110
|
+
return this.update(id, { pinned: pinned ? 1 : 0 });
|
|
111
|
+
}
|
|
112
|
+
|
|
47
113
|
async updateConfig(id: string, data: DeepPartial<LobeAgentConfig>) {
|
|
48
114
|
const session = await this.findById(id);
|
|
49
115
|
if (!session) return;
|
|
@@ -52,6 +118,7 @@ class _SessionModel extends BaseModel {
|
|
|
52
118
|
|
|
53
119
|
return this.update(id, { config });
|
|
54
120
|
}
|
|
121
|
+
|
|
55
122
|
/**
|
|
56
123
|
* Delete a session , also delete all messages and topic associated with it.
|
|
57
124
|
*/
|
|
@@ -148,13 +215,13 @@ class _SessionModel extends BaseModel {
|
|
|
148
215
|
]);
|
|
149
216
|
|
|
150
217
|
// Retrieve unique sessions by IDs
|
|
151
|
-
const
|
|
218
|
+
const items: DBModel<DB_Session>[] = await this.table
|
|
152
219
|
.where('id')
|
|
153
220
|
.anyOf([...combinedSessionIds])
|
|
154
221
|
.toArray();
|
|
155
222
|
|
|
156
223
|
console.timeEnd('queryByKeyword');
|
|
157
|
-
return
|
|
224
|
+
return this.mapToAgentSessions(items);
|
|
158
225
|
}
|
|
159
226
|
|
|
160
227
|
async duplicate(id: string, newTitle?: string) {
|
|
@@ -165,6 +232,35 @@ class _SessionModel extends BaseModel {
|
|
|
165
232
|
|
|
166
233
|
return this._add(newSession, uuid());
|
|
167
234
|
}
|
|
235
|
+
|
|
236
|
+
async getPinnedSessions(): Promise<LobeSessions> {
|
|
237
|
+
const items: DBModel<DB_Session>[] = await this.table
|
|
238
|
+
.where('pinned')
|
|
239
|
+
.equals(1)
|
|
240
|
+
.reverse()
|
|
241
|
+
.sortBy('updatedAt');
|
|
242
|
+
|
|
243
|
+
return this.mapToAgentSessions(items);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
private mapToDB_Session(session: LobeAgentSession): DBModel<DB_Session> {
|
|
247
|
+
return {
|
|
248
|
+
...session,
|
|
249
|
+
group: session.group || SessionDefaultGroup.Default,
|
|
250
|
+
pinned: session.pinned ? 1 : 0,
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
private DB_SessionToAgentSession(session: DBModel<DB_Session>) {
|
|
255
|
+
return {
|
|
256
|
+
...session,
|
|
257
|
+
pinned: !!session.pinned,
|
|
258
|
+
} as LobeAgentSession;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
private mapToAgentSessions(session: DBModel<DB_Session>[]) {
|
|
262
|
+
return session.map((item) => this.DB_SessionToAgentSession(item));
|
|
263
|
+
}
|
|
168
264
|
}
|
|
169
265
|
|
|
170
266
|
export const SessionModel = new _SessionModel();
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { BaseModel } from '@/database/core';
|
|
2
|
+
import { DB_SessionGroup, DB_SessionGroupSchema } from '@/database/schemas/sessionGroup';
|
|
3
|
+
import { SessionGroups } from '@/types/session';
|
|
4
|
+
import { nanoid } from '@/utils/uuid';
|
|
5
|
+
|
|
6
|
+
class _SessionGroupModel extends BaseModel {
|
|
7
|
+
constructor() {
|
|
8
|
+
super('sessionGroups', DB_SessionGroupSchema);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
async create(name: string, sort?: number, id = nanoid()) {
|
|
12
|
+
return this._add({ name, sort }, id);
|
|
13
|
+
}
|
|
14
|
+
async batchCreate(groups: SessionGroups) {
|
|
15
|
+
return this._batchAdd(groups, { idGenerator: nanoid });
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async findById(id: string): Promise<DB_SessionGroup> {
|
|
19
|
+
return this.table.get(id);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async update(id: string, data: Partial<DB_SessionGroup>) {
|
|
23
|
+
return super._update(id, data);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async delete(id: string, removeGroupItem: boolean = false) {
|
|
27
|
+
this.db.sessions.toCollection().modify((session) => {
|
|
28
|
+
// update all session associated with the sessionGroup to default
|
|
29
|
+
if (session.group === id) session.group = 'default';
|
|
30
|
+
});
|
|
31
|
+
if (!removeGroupItem) {
|
|
32
|
+
return this.table.delete(id);
|
|
33
|
+
} else {
|
|
34
|
+
return this.db.sessions.where('group').equals(id).delete();
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async query(): Promise<SessionGroups> {
|
|
39
|
+
const allGroups = await this.table.toArray();
|
|
40
|
+
|
|
41
|
+
// 自定义排序,先按 sort 存在与否分组,然后分别排序
|
|
42
|
+
return allGroups.sort((a, b) => {
|
|
43
|
+
// 如果两个项都有 sort,则按 sort 排序
|
|
44
|
+
if (a.sort !== undefined && b.sort !== undefined) {
|
|
45
|
+
// 如果sort 一样,按时间倒序排序
|
|
46
|
+
if (a.sort === b.sort) return b.createdAt - a.createdAt;
|
|
47
|
+
|
|
48
|
+
return a.sort - b.sort;
|
|
49
|
+
}
|
|
50
|
+
// 如果 a 有 sort 而 b 没有,则 a 排在前面
|
|
51
|
+
if (a.sort !== undefined) {
|
|
52
|
+
return -1;
|
|
53
|
+
}
|
|
54
|
+
// 如果 b 有 sort 而 a 没有,则 b 排在前面
|
|
55
|
+
if (b.sort !== undefined) {
|
|
56
|
+
return 1;
|
|
57
|
+
}
|
|
58
|
+
// 如果两个项都没有 sort,则按 createdAt 倒序排序
|
|
59
|
+
return b.createdAt - a.createdAt;
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async updateOrder(sortMap: { id: string; sort: number }[]) {
|
|
64
|
+
return this.db.transaction('rw', this.table, async () => {
|
|
65
|
+
for (const { id, sort } of sortMap) {
|
|
66
|
+
await this.table.update(id, { sort });
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async clear() {
|
|
72
|
+
this.table.clear();
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export const SessionGroupModel = new _SessionGroupModel();
|
|
@@ -5,10 +5,10 @@ import { useChatListActionsBar } from '../hooks/useChatListActionsBar';
|
|
|
5
5
|
import { RenderAction } from '../types';
|
|
6
6
|
|
|
7
7
|
export const FunctionActionsBar: RenderAction = memo(({ onActionClick }) => {
|
|
8
|
-
const { regenerate,
|
|
8
|
+
const { regenerate, delAndRegenerate, del } = useChatListActionsBar();
|
|
9
9
|
return (
|
|
10
10
|
<ActionIconGroup
|
|
11
|
-
dropdownMenu={[regenerate,
|
|
11
|
+
dropdownMenu={[regenerate, delAndRegenerate, del]}
|
|
12
12
|
items={[regenerate]}
|
|
13
13
|
onActionClick={onActionClick}
|
|
14
14
|
type="ghost"
|
|
@@ -11,7 +11,7 @@ export const UserActionsBar: RenderAction = memo(({ onActionClick }) => {
|
|
|
11
11
|
|
|
12
12
|
return (
|
|
13
13
|
<ActionIconGroup
|
|
14
|
-
dropdownMenu={[edit, copy,
|
|
14
|
+
dropdownMenu={[edit, copy, divider, tts, translate, divider, regenerate, del]}
|
|
15
15
|
items={[regenerate, edit]}
|
|
16
16
|
onActionClick={onActionClick}
|
|
17
17
|
type="ghost"
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ActionIconGroupItems } from '@lobehub/ui/es/ActionIconGroup';
|
|
2
|
-
import { Copy, Edit,
|
|
2
|
+
import { Copy, Edit, ListRestart, RotateCcw, Trash } from 'lucide-react';
|
|
3
3
|
import { useMemo } from 'react';
|
|
4
4
|
import { useTranslation } from 'react-i18next';
|
|
5
5
|
|
|
@@ -29,7 +29,7 @@ export const useChatListActionsBar = (): ChatListActionsBar => {
|
|
|
29
29
|
label: t('delete', { defaultValue: 'Delete' }),
|
|
30
30
|
},
|
|
31
31
|
delAndRegenerate: {
|
|
32
|
-
icon:
|
|
32
|
+
icon: ListRestart,
|
|
33
33
|
key: 'delAndRegenerate',
|
|
34
34
|
label: t('messageAction.delAndRegenerate', {
|
|
35
35
|
defaultValue: 'Delete and regenerate',
|
|
@@ -45,7 +45,7 @@ export const useChatListActionsBar = (): ChatListActionsBar => {
|
|
|
45
45
|
label: t('edit', { defaultValue: 'Edit' }),
|
|
46
46
|
},
|
|
47
47
|
regenerate: {
|
|
48
|
-
icon:
|
|
48
|
+
icon: RotateCcw,
|
|
49
49
|
key: 'regenerate',
|
|
50
50
|
label: t('regenerate', { defaultValue: 'Regenerate' }),
|
|
51
51
|
},
|
|
@@ -41,10 +41,13 @@ const DevModal = memo<DevModalProps>(
|
|
|
41
41
|
<Flexbox flex={1} gap={12} horizontal justify={'flex-end'}>
|
|
42
42
|
{isEditMode ? (
|
|
43
43
|
<Popconfirm
|
|
44
|
+
arrow={false}
|
|
45
|
+
cancelText={t('cancel', { ns: 'common' })}
|
|
44
46
|
okButtonProps={{
|
|
45
47
|
danger: true,
|
|
46
48
|
type: 'primary',
|
|
47
49
|
}}
|
|
50
|
+
okText={t('ok', { ns: 'common' })}
|
|
48
51
|
onConfirm={() => {
|
|
49
52
|
onDelete?.();
|
|
50
53
|
message.success(t('dev.deleteSuccess'));
|
|
@@ -69,7 +69,9 @@ const Actions = memo<ActionsProps>(({ identifier, type }) => {
|
|
|
69
69
|
label: (
|
|
70
70
|
<Popconfirm
|
|
71
71
|
arrow={false}
|
|
72
|
+
cancelText={t('cancel', { ns: 'common' })}
|
|
72
73
|
okButtonProps={{ danger: true }}
|
|
74
|
+
okText={t('ok', { ns: 'common' })}
|
|
73
75
|
onConfirm={() => {
|
|
74
76
|
unInstallPlugin(identifier);
|
|
75
77
|
}}
|
|
@@ -7,6 +7,7 @@ export default {
|
|
|
7
7
|
confirmClearCurrentMessages: '即将清空当前会话消息,清空后将无法找回,请确认你的操作',
|
|
8
8
|
confirmRemoveSessionItemAlert: '即将删除该助手,删除后该将无法找回,请确认你的操作',
|
|
9
9
|
defaultAgent: '自定义助手',
|
|
10
|
+
defaultList: '默认列表',
|
|
10
11
|
defaultSession: '自定义助手',
|
|
11
12
|
duplicateTitle: '{{title}} 副本',
|
|
12
13
|
historyRange: '历史范围',
|
|
@@ -36,7 +37,18 @@ export default {
|
|
|
36
37
|
roleAndArchive: '角色与记录',
|
|
37
38
|
searchAgentPlaceholder: '搜索助手和对话...',
|
|
38
39
|
sendPlaceholder: '输入聊天内容...',
|
|
39
|
-
|
|
40
|
+
sessionGroup: {
|
|
41
|
+
config: '分组管理',
|
|
42
|
+
confirmRemoveGroupAlert: '即将删除该分组,删除后该分组的助手将移动到默认列表,请确认你的操作',
|
|
43
|
+
createGroup: '添加新分组',
|
|
44
|
+
createSuccess: '创建成功',
|
|
45
|
+
inputPlaceholder: '请输入分组名称...',
|
|
46
|
+
moveGroup: '移动到分组',
|
|
47
|
+
newGroup: '新分组',
|
|
48
|
+
rename: '重命名分组',
|
|
49
|
+
renameSuccess: '重命名成功',
|
|
50
|
+
tooLong: '分组名称长度需在 1-20 之内',
|
|
51
|
+
},
|
|
40
52
|
shareModal: {
|
|
41
53
|
download: '下载截图',
|
|
42
54
|
imageType: '图片格式',
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
{
|
|
2
|
+
"exportType": "sessions",
|
|
3
|
+
"state": {
|
|
4
|
+
"sessions": [
|
|
5
|
+
{
|
|
6
|
+
"config": {
|
|
7
|
+
"displayMode": "chat",
|
|
8
|
+
"historyCount": 1,
|
|
9
|
+
"model": "gpt-3.5-turbo",
|
|
10
|
+
"params": {
|
|
11
|
+
"frequency_penalty": 0,
|
|
12
|
+
"presence_penalty": 0,
|
|
13
|
+
"temperature": 0.6,
|
|
14
|
+
"top_p": 1,
|
|
15
|
+
"max_tokens": 2200
|
|
16
|
+
},
|
|
17
|
+
"plugins": [],
|
|
18
|
+
"systemRole": "你是一名 Postgresql 的数据库专家。用户是一名数据库小白,你需要使用简单直白的方式告诉用户如何使用 ostgresql 和它相应的 orm 工具 primsa",
|
|
19
|
+
"enableCompressThreshold": false,
|
|
20
|
+
"enableMaxTokens": false,
|
|
21
|
+
"inputTemplate": ""
|
|
22
|
+
},
|
|
23
|
+
"createdAt": 1698463356269,
|
|
24
|
+
"id": "06cc3e20-e870-4099-a619-c07a849d742d",
|
|
25
|
+
"meta": {
|
|
26
|
+
"avatar": "🐘",
|
|
27
|
+
"backgroundColor": "rgba(0,0,0,0)",
|
|
28
|
+
"title": "Postgresql 指南",
|
|
29
|
+
"description": "PostgreSQL 数据库和 Prisma ORM 工具的简单使用指南",
|
|
30
|
+
"tags": ["数据库", "PostgreSQL", "教程", "ORM", "Prisma"]
|
|
31
|
+
},
|
|
32
|
+
"type": "agent",
|
|
33
|
+
"updatedAt": 1698463356269,
|
|
34
|
+
"group": "pinned"
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
"config": {
|
|
38
|
+
"displayMode": "chat",
|
|
39
|
+
"historyCount": 1,
|
|
40
|
+
"model": "gpt-3.5-turbo",
|
|
41
|
+
"params": {
|
|
42
|
+
"frequency_penalty": 0,
|
|
43
|
+
"presence_penalty": 0,
|
|
44
|
+
"temperature": 0.6,
|
|
45
|
+
"top_p": 1,
|
|
46
|
+
"max_tokens": 2200
|
|
47
|
+
},
|
|
48
|
+
"plugins": [],
|
|
49
|
+
"systemRole": "你是一名 Postgresql 的数据库专家。用户是一名数据库小白,你需要使用简单直白的方式告诉用户如何使用 ostgresql 和它相应的 orm 工具 primsa",
|
|
50
|
+
"enableCompressThreshold": false,
|
|
51
|
+
"enableMaxTokens": false,
|
|
52
|
+
"inputTemplate": ""
|
|
53
|
+
},
|
|
54
|
+
"createdAt": 1698463356269,
|
|
55
|
+
"id": "06cc3e20-e870-4098-a619-c07a849d74dd",
|
|
56
|
+
"meta": {
|
|
57
|
+
"avatar": "🐘",
|
|
58
|
+
"backgroundColor": "rgba(0,0,0,0)",
|
|
59
|
+
"title": "Postgresql 指南",
|
|
60
|
+
"description": "PostgreSQL 数据库和 Prisma ORM 工具的简单使用指南",
|
|
61
|
+
"tags": ["数据库", "PostgreSQL", "教程", "ORM", "Prisma"]
|
|
62
|
+
},
|
|
63
|
+
"type": "agent",
|
|
64
|
+
"updatedAt": 1698463356269,
|
|
65
|
+
"group": "default"
|
|
66
|
+
}
|
|
67
|
+
],
|
|
68
|
+
"topics": [],
|
|
69
|
+
"messages": []
|
|
70
|
+
},
|
|
71
|
+
"version": 2
|
|
72
|
+
}
|