@lobehub/chat 0.105.2 → 0.107.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/README.md +18 -0
- package/README.zh-CN.md +18 -0
- package/docs/Environment-Variable.md +4 -2
- package/docs/Environment-Variable.zh-CN.md +4 -2
- package/locales/en_US/common.json +19 -0
- package/locales/en_US/migration.json +45 -0
- package/locales/en_US/setting.json +7 -7
- package/locales/ja_JP/common.json +19 -0
- package/locales/ja_JP/migration.json +45 -0
- package/locales/ja_JP/setting.json +2 -2
- package/locales/ko_KR/common.json +19 -0
- package/locales/ko_KR/migration.json +45 -0
- package/locales/ko_KR/setting.json +2 -2
- package/locales/ru_RU/common.json +19 -0
- package/locales/ru_RU/migration.json +45 -0
- package/locales/ru_RU/setting.json +2 -2
- package/locales/zh_CN/common.json +19 -0
- package/locales/zh_CN/migration.json +45 -0
- package/locales/zh_CN/setting.json +1 -1
- package/locales/zh_TW/common.json +19 -0
- package/locales/zh_TW/migration.json +45 -0
- package/locales/zh_TW/setting.json +2 -2
- package/package.json +2 -1
- package/src/app/api/openai/chat/createChatCompletion.test.ts +4 -4
- package/src/app/api/openai/chat/createChatCompletion.ts +4 -10
- package/src/app/api/openai/createBizOpenAI/index.ts +1 -1
- package/src/app/api/openai/errorResponse.test.ts +1 -1
- package/src/app/chat/(desktop)/features/ChatInput/Footer/index.tsx +2 -1
- package/src/app/chat/(mobile)/features/ChatInput/SendButton.tsx +2 -2
- package/src/app/chat/features/ChatHeader/ShareButton/Inner.tsx +2 -5
- package/src/app/chat/features/ChatHeader/ShareButton/index.tsx +2 -2
- package/src/app/chat/features/ChatInput/ActionBar/Clear.tsx +2 -2
- package/src/app/chat/features/ChatInput/ActionBar/ModelSwitch.tsx +1 -1
- package/src/app/chat/features/ChatInput/ActionBar/Token/TokenTag.tsx +8 -2
- package/src/app/chat/features/ChatInput/InputAreaInner/index.tsx +2 -2
- package/src/app/chat/features/ChatInput/STT/index.tsx +2 -2
- package/src/app/chat/features/ChatInput/Topic/index.tsx +2 -2
- package/src/app/chat/features/ChatInput/useSend.ts +3 -3
- package/src/app/chat/features/Conversation/ChatList/Actions/index.ts +2 -2
- package/src/app/chat/features/Conversation/ChatList/Error/ApiKeyForm.tsx +2 -2
- package/src/app/chat/features/Conversation/ChatList/Error/InvalidAccess.tsx +2 -2
- package/src/app/chat/features/Conversation/ChatList/Error/Plugin/PluginSettings.tsx +2 -2
- package/src/app/chat/features/Conversation/ChatList/Extras/Assistant.test.tsx +4 -4
- package/src/app/chat/features/Conversation/ChatList/Extras/Assistant.tsx +30 -31
- package/src/app/chat/features/Conversation/ChatList/Extras/TTS.tsx +2 -2
- package/src/app/chat/features/Conversation/ChatList/Extras/Translate.tsx +2 -2
- package/src/app/chat/features/Conversation/ChatList/Extras/User.tsx +8 -5
- package/src/app/chat/features/Conversation/ChatList/Extras/index.ts +3 -3
- package/src/app/chat/features/Conversation/ChatList/Extras/type.ts +5 -0
- package/src/app/chat/features/Conversation/ChatList/Messages/Assistant.tsx +10 -6
- package/src/app/chat/features/Conversation/ChatList/Messages/Default.tsx +7 -3
- package/src/app/chat/features/Conversation/ChatList/Messages/Function.tsx +7 -6
- package/src/app/chat/features/Conversation/ChatList/Messages/index.ts +7 -7
- package/src/app/chat/features/Conversation/ChatList/Plugins/Render/StandaloneType/Iframe.tsx +6 -6
- package/src/app/chat/features/Conversation/ChatList/index.tsx +16 -24
- package/src/app/chat/features/Conversation/ScrollAnchor/index.tsx +2 -2
- package/src/app/chat/features/Conversation/index.tsx +3 -3
- package/src/app/chat/features/Conversation/useInitConversation.ts +43 -0
- package/src/app/chat/features/Migration/ExportConfigButton.tsx +33 -0
- package/src/app/chat/features/Migration/Failed.tsx +115 -0
- package/src/app/chat/features/Migration/Modal.tsx +80 -0
- package/src/app/chat/features/Migration/Start.tsx +106 -0
- package/src/app/chat/features/Migration/UpgradeButton.tsx +74 -0
- package/src/app/chat/features/Migration/const.ts +15 -0
- package/src/app/chat/features/Migration/index.tsx +44 -0
- package/src/app/chat/features/SessionListContent/DefaultMode.tsx +54 -0
- package/src/app/chat/features/SessionListContent/List/Item/Actions.tsx +5 -4
- package/src/app/chat/features/SessionListContent/List/Item/index.tsx +22 -28
- package/src/app/chat/features/SessionListContent/SearchMode.tsx +15 -0
- package/src/app/chat/features/SessionListContent/index.tsx +4 -44
- package/src/app/chat/features/SessionSearchBar/index.tsx +11 -3
- package/src/app/chat/features/TopicListContent/Header.tsx +4 -4
- package/src/app/chat/features/TopicListContent/Topic/TopicContent.tsx +11 -9
- package/src/app/chat/features/TopicListContent/Topic/TopicItem.tsx +15 -13
- package/src/app/chat/features/TopicListContent/Topic/index.tsx +9 -16
- package/src/app/chat/features/TopicListContent/TopicSearchBar/index.tsx +13 -4
- package/src/app/chat/features/TopicListContent/index.tsx +1 -2
- package/src/app/chat/page.tsx +6 -1
- package/src/app/chat/settings/features/HeaderContent.tsx +3 -3
- package/src/app/home/Redirect.tsx +13 -7
- package/src/app/settings/(mobile)/features/ExtraList.tsx +12 -4
- package/src/app/settings/common/Common.tsx +16 -4
- package/src/app/welcome/features/Banner/index.tsx +9 -10
- package/src/chains/summaryTitle.ts +5 -2
- package/src/components/DataStyleModal/index.tsx +64 -0
- package/src/config/client.ts +5 -0
- package/src/const/session.ts +21 -0
- package/src/const/settings.ts +1 -1
- package/src/database/core/__tests__/db.test.ts +8 -5
- package/src/database/core/__tests__/model.test.ts +3 -3
- package/src/database/core/db.ts +38 -10
- package/src/database/core/index.ts +1 -0
- package/src/database/core/model.ts +121 -10
- package/src/database/core/schemas.ts +22 -0
- package/src/database/core/types/db.ts +13 -0
- package/src/database/models/__DEBUG.ts +119 -0
- package/src/database/models/{file.test.ts → __tests__/file.test.ts} +19 -6
- package/src/database/models/__tests__/message.test.ts +314 -0
- package/src/database/models/__tests__/session.test.ts +117 -0
- package/src/database/models/__tests__/topic.test.ts +250 -0
- package/src/database/models/file.ts +9 -5
- package/src/database/models/message.ts +186 -0
- package/src/database/models/session.ts +160 -0
- package/src/database/models/topic.ts +196 -0
- package/src/{types/database → database/schemas}/files.ts +2 -2
- package/src/database/schemas/message.ts +37 -0
- package/src/database/schemas/session.ts +56 -0
- package/src/database/schemas/topic.ts +12 -0
- package/src/features/AgentSetting/AgentConfig/index.tsx +8 -1
- package/src/features/AgentSetting/AgentTTS/SelectWithTTSPreview.tsx +1 -1
- package/src/features/AgentSetting/store/action.ts +1 -1
- package/src/features/AgentSetting/store/initialState.ts +1 -1
- package/src/features/AgentSetting/store/reducers/config.ts +1 -1
- package/src/features/DataImporter/index.tsx +240 -0
- package/src/features/DebugUI/index.tsx +34 -0
- package/src/features/SideBar/BottomActions.tsx +8 -14
- package/src/hooks/useImportConfig.ts +13 -30
- package/src/hooks/useTTS.ts +1 -1
- package/src/layout/GlobalLayout/index.tsx +16 -1
- package/src/locales/default/common.ts +19 -0
- package/src/locales/default/migration.ts +47 -0
- package/src/locales/default/setting.ts +1 -1
- package/src/locales/resources/en_US.ts +2 -0
- package/src/locales/resources/ja_JP.ts +2 -0
- package/src/locales/resources/ko_KR.ts +2 -0
- package/src/locales/resources/ru_RU.ts +2 -0
- package/src/locales/resources/zh_CN.ts +2 -0
- package/src/locales/resources/zh_TW.ts +2 -0
- package/src/migrations/FromV0ToV1.ts +2 -4
- package/src/migrations/FromV1ToV2/fixtures/input-v1-session.json +191 -0
- package/src/migrations/FromV1ToV2/fixtures/output-v2.json +202 -0
- package/src/migrations/FromV1ToV2/index.ts +82 -0
- package/src/migrations/FromV1ToV2/migrations.test.ts +224 -0
- package/src/migrations/FromV1ToV2/types/v1.ts +78 -0
- package/src/migrations/FromV1ToV2/types/v2.ts +52 -0
- package/src/{utils → migrations}/VersionController.test.ts +1 -3
- package/src/{utils → migrations}/VersionController.ts +5 -2
- package/src/migrations/index.ts +17 -3
- package/src/services/__tests__/file.test.ts +3 -3
- package/src/services/chat.ts +66 -5
- package/src/services/config.ts +184 -0
- package/src/services/debug.ts +13 -0
- package/src/services/file.ts +6 -2
- package/src/services/github.ts +25 -0
- package/src/services/message.ts +79 -0
- package/src/services/session.ts +63 -0
- package/src/services/topic.ts +56 -0
- package/src/store/chat/actions/enhance.ts +95 -0
- package/src/store/{session/slices/chat → chat}/actions/message.ts +225 -247
- package/src/store/{session/slices/chat → chat}/actions/plugin.ts +29 -34
- package/src/store/{session/slices/chat → chat}/actions/share.ts +9 -13
- package/src/store/chat/actions/topic.ts +197 -0
- package/src/store/chat/helpers.ts +23 -0
- package/src/store/chat/index.ts +3 -0
- package/src/store/chat/initialState.ts +48 -0
- package/src/store/chat/reducers/message.test.ts +83 -0
- package/src/store/chat/reducers/message.ts +83 -0
- package/src/store/chat/selectors/__tests__/topic.test.ts +43 -0
- package/src/store/chat/selectors/chat.ts +119 -0
- package/src/store/chat/selectors/index.ts +2 -0
- package/src/store/chat/selectors/topic.ts +28 -0
- package/src/store/chat/store.ts +63 -0
- package/src/store/files/slices/images/action.ts +6 -4
- package/src/store/global/selectors/__snapshots__/settings.test.ts.snap +99 -0
- package/src/store/global/selectors/settings.test.ts +51 -0
- package/src/store/global/selectors/settings.ts +36 -5
- package/src/store/global/store.ts +1 -1
- package/src/store/middleware/createHyperStorage/index.ts +40 -24
- package/src/store/middleware/createHyperStorage/keyMapper.ts +8 -2
- package/src/store/middleware/createHyperStorage/type.ts +10 -8
- package/src/store/plugin/slices/plugin/action.ts +1 -1
- package/src/store/plugin/store.ts +2 -4
- package/src/store/session/helpers.ts +1 -1
- package/src/store/session/hooks/useSessionHydrated.ts +6 -10
- package/src/store/session/initialState.ts +26 -7
- package/src/store/session/selectors.ts +1 -2
- package/src/store/session/slices/agent/action.ts +26 -35
- package/src/store/session/slices/agent/selectors.ts +32 -37
- package/src/store/session/slices/session/action.ts +81 -88
- package/src/store/session/slices/session/helpers.ts +19 -0
- package/src/store/session/slices/session/initialState.ts +7 -10
- package/src/store/session/slices/session/selectors/export.ts +12 -9
- package/src/store/session/slices/session/selectors/index.ts +2 -35
- package/src/store/session/slices/session/selectors/list.test.ts +25 -203
- package/src/store/session/slices/session/selectors/list.ts +40 -78
- package/src/store/session/store.ts +11 -17
- package/src/types/agent/index.ts +61 -0
- package/src/types/chatMessage.ts +11 -20
- package/src/types/exportConfig.ts +7 -2
- package/src/types/files.ts +2 -2
- package/src/types/llm.ts +1 -1
- package/src/types/meta.ts +24 -12
- package/src/types/openai/chat.ts +9 -1
- package/src/types/openai/functionCall.ts +4 -0
- package/src/types/session.ts +10 -94
- package/src/types/settings.ts +2 -0
- package/src/types/translate.ts +2 -1
- package/src/utils/config.ts +5 -11
- package/src/utils/fetch.ts +8 -2
- package/src/app/chat/features/Conversation/usePluginsInit.ts +0 -14
- package/src/database/core/schema.ts +0 -9
- package/src/helpers/export.ts +0 -69
- package/src/hooks/useExportConfig.ts +0 -6
- package/src/store/session/slices/chat/actions/index.ts +0 -32
- package/src/store/session/slices/chat/actions/topic.ts +0 -171
- package/src/store/session/slices/chat/actions/translate.ts +0 -113
- package/src/store/session/slices/chat/helpers.ts +0 -9
- package/src/store/session/slices/chat/initialState.ts +0 -13
- package/src/store/session/slices/chat/reducers/message.test.ts +0 -319
- package/src/store/session/slices/chat/reducers/message.ts +0 -156
- package/src/store/session/slices/chat/reducers/topic.test.ts +0 -179
- package/src/store/session/slices/chat/reducers/topic.ts +0 -81
- package/src/store/session/slices/chat/selectors/chat.ts +0 -111
- package/src/store/session/slices/chat/selectors/index.ts +0 -26
- package/src/store/session/slices/chat/selectors/topic.ts +0 -30
- package/src/store/session/slices/chat/selectors/utils.test.ts +0 -385
- package/src/store/session/slices/chat/selectors/utils.ts +0 -91
- package/src/store/session/slices/chat/utils.ts +0 -13
- package/src/store/session/slices/session/reducers/session.test.ts +0 -577
- package/src/store/session/slices/session/reducers/session.ts +0 -182
- package/src/types/database/db.ts +0 -4
- /package/src/store/{session/slices/chat → chat}/reducers/files.test.ts +0 -0
- /package/src/store/{session/slices/chat → chat}/reducers/files.ts +0 -0
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { CreateMessageParams, MessageModel } from '@/database/models/message';
|
|
4
|
+
|
|
5
|
+
import { CreateTopicParams, QueryTopicParams, TopicModel } from '../topic';
|
|
6
|
+
|
|
7
|
+
describe('TopicModel', () => {
|
|
8
|
+
let topicData: CreateTopicParams;
|
|
9
|
+
|
|
10
|
+
beforeEach(() => {
|
|
11
|
+
// Set up topic data with the correct structure
|
|
12
|
+
topicData = {
|
|
13
|
+
sessionId: 'session1',
|
|
14
|
+
title: 'Test Topic',
|
|
15
|
+
favorite: false,
|
|
16
|
+
};
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
afterEach(async () => {
|
|
20
|
+
// Clean up the database after each test
|
|
21
|
+
await TopicModel.clearTable();
|
|
22
|
+
});
|
|
23
|
+
describe('create', () => {
|
|
24
|
+
it('should create a topic record', async () => {
|
|
25
|
+
const result = await TopicModel.create(topicData);
|
|
26
|
+
|
|
27
|
+
expect(result).toHaveProperty('id');
|
|
28
|
+
// Verify that the topic has been added to the database
|
|
29
|
+
const topicInDb = await TopicModel.findById(result.id);
|
|
30
|
+
|
|
31
|
+
expect(topicInDb).toEqual(
|
|
32
|
+
expect.objectContaining({
|
|
33
|
+
title: topicData.title,
|
|
34
|
+
favorite: topicData.favorite ? 1 : 0,
|
|
35
|
+
sessionId: topicData.sessionId,
|
|
36
|
+
}),
|
|
37
|
+
);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('should create a topic with favorite set to true', async () => {
|
|
41
|
+
const favoriteTopicData: CreateTopicParams = {
|
|
42
|
+
...topicData,
|
|
43
|
+
favorite: true,
|
|
44
|
+
};
|
|
45
|
+
const result = await TopicModel.create(favoriteTopicData);
|
|
46
|
+
|
|
47
|
+
expect(result).toHaveProperty('id');
|
|
48
|
+
const topicInDb = await TopicModel.findById(result.id);
|
|
49
|
+
expect(topicInDb).toEqual(
|
|
50
|
+
expect.objectContaining({
|
|
51
|
+
title: favoriteTopicData.title,
|
|
52
|
+
favorite: 1,
|
|
53
|
+
sessionId: favoriteTopicData.sessionId,
|
|
54
|
+
}),
|
|
55
|
+
);
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
describe('batch create', () => {
|
|
59
|
+
it('should batch create topic records', async () => {
|
|
60
|
+
const topicsToCreate = [topicData, topicData];
|
|
61
|
+
const results = await TopicModel.batchCreate(topicsToCreate);
|
|
62
|
+
|
|
63
|
+
expect(results.ids).toHaveLength(topicsToCreate.length);
|
|
64
|
+
// Verify that the topics have been added to the database
|
|
65
|
+
for (const result of results.ids!) {
|
|
66
|
+
const topicInDb = await TopicModel.findById(result);
|
|
67
|
+
expect(topicInDb).toEqual(
|
|
68
|
+
expect.objectContaining({
|
|
69
|
+
title: topicData.title,
|
|
70
|
+
favorite: topicData.favorite ? 1 : 0,
|
|
71
|
+
sessionId: topicData.sessionId,
|
|
72
|
+
}),
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('should batch create topics with mixed favorite values', async () => {
|
|
78
|
+
const mixedTopicsData: CreateTopicParams[] = [
|
|
79
|
+
{ ...topicData, favorite: true },
|
|
80
|
+
{ ...topicData, favorite: false },
|
|
81
|
+
];
|
|
82
|
+
|
|
83
|
+
const results = await TopicModel.batchCreate(mixedTopicsData);
|
|
84
|
+
|
|
85
|
+
expect(results.ids).toHaveLength(mixedTopicsData.length);
|
|
86
|
+
for (const id of results.ids!) {
|
|
87
|
+
const topicInDb = await TopicModel.findById(id);
|
|
88
|
+
expect(topicInDb).toBeDefined();
|
|
89
|
+
expect(topicInDb.favorite).toBeGreaterThanOrEqual(0);
|
|
90
|
+
expect(topicInDb.favorite).toBeLessThanOrEqual(1);
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it('should query topics with pagination', async () => {
|
|
96
|
+
// Create multiple topics to test the query method
|
|
97
|
+
await TopicModel.batchCreate([topicData, topicData]);
|
|
98
|
+
|
|
99
|
+
const queryParams: QueryTopicParams = { pageSize: 1, current: 0, sessionId: 'session1' };
|
|
100
|
+
const queriedTopics = await TopicModel.query(queryParams);
|
|
101
|
+
|
|
102
|
+
expect(queriedTopics).toHaveLength(1);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it('should find topics by session id', async () => {
|
|
106
|
+
// Create multiple topics to test the findBySessionId method
|
|
107
|
+
await TopicModel.batchCreate([topicData, topicData]);
|
|
108
|
+
|
|
109
|
+
const topicsBySessionId = await TopicModel.findBySessionId(topicData.sessionId);
|
|
110
|
+
|
|
111
|
+
expect(topicsBySessionId).toHaveLength(2);
|
|
112
|
+
expect(topicsBySessionId.every((i) => i.sessionId === topicData.sessionId)).toBeTruthy();
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it('should delete a topic and its associated messages', async () => {
|
|
116
|
+
const createdTopic = await TopicModel.create(topicData);
|
|
117
|
+
|
|
118
|
+
await TopicModel.delete(createdTopic.id);
|
|
119
|
+
|
|
120
|
+
// Verify the topic and its related messages are deleted
|
|
121
|
+
const topicInDb = await TopicModel.findById(createdTopic.id);
|
|
122
|
+
expect(topicInDb).toBeUndefined();
|
|
123
|
+
|
|
124
|
+
// You need to verify that messages related to the topic are also deleted
|
|
125
|
+
// This would require additional setup to create messages associated with the topic
|
|
126
|
+
// and then assertions to check that they're deleted after the topic itself is deleted
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it('should batch delete topics by session id', async () => {
|
|
130
|
+
// Create multiple topics to test the batchDeleteBySessionId method
|
|
131
|
+
await TopicModel.batchCreate([topicData, topicData]);
|
|
132
|
+
|
|
133
|
+
await TopicModel.batchDeleteBySessionId(topicData.sessionId);
|
|
134
|
+
|
|
135
|
+
// Verify that all topics with the given session id are deleted
|
|
136
|
+
const topicsInDb = await TopicModel.findBySessionId(topicData.sessionId);
|
|
137
|
+
expect(topicsInDb).toHaveLength(0);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it('should update a topic', async () => {
|
|
141
|
+
const createdTopic = await TopicModel.create(topicData);
|
|
142
|
+
const updateData = { title: 'New Title' };
|
|
143
|
+
|
|
144
|
+
await TopicModel.update(createdTopic.id, updateData);
|
|
145
|
+
const updatedTopic = await TopicModel.findById(createdTopic.id);
|
|
146
|
+
|
|
147
|
+
expect(updatedTopic).toHaveProperty('title', 'New Title');
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
describe('toggleFavorite', () => {
|
|
151
|
+
it('should toggle favorite status of a topic', async () => {
|
|
152
|
+
const createdTopic = await TopicModel.create(topicData);
|
|
153
|
+
|
|
154
|
+
const newState = await TopicModel.toggleFavorite(createdTopic.id);
|
|
155
|
+
|
|
156
|
+
expect(newState).toBe(true);
|
|
157
|
+
const topicInDb = await TopicModel.findById(createdTopic.id);
|
|
158
|
+
expect(topicInDb).toHaveProperty('favorite', 1);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it('should handle toggleFavorite when topic does not exist', async () => {
|
|
162
|
+
const nonExistentTopicId = 'non-existent-id';
|
|
163
|
+
await expect(TopicModel.toggleFavorite(nonExistentTopicId)).rejects.toThrow(
|
|
164
|
+
`Topic with id ${nonExistentTopicId} not found`,
|
|
165
|
+
);
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
it('should set favorite to specific state using toggleFavorite', async () => {
|
|
169
|
+
const createdTopic = await TopicModel.create(topicData);
|
|
170
|
+
|
|
171
|
+
// Set favorite to true regardless of current state
|
|
172
|
+
await TopicModel.toggleFavorite(createdTopic.id, true);
|
|
173
|
+
let topicInDb = await TopicModel.findById(createdTopic.id);
|
|
174
|
+
expect(topicInDb.favorite).toBe(1);
|
|
175
|
+
|
|
176
|
+
// Set favorite to false regardless of current state
|
|
177
|
+
await TopicModel.toggleFavorite(createdTopic.id, false);
|
|
178
|
+
topicInDb = await TopicModel.findById(createdTopic.id);
|
|
179
|
+
expect(topicInDb.favorite).toBe(0);
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
it('should delete a topic and its associated messages', async () => {
|
|
184
|
+
// 创建话题和相关联的消息
|
|
185
|
+
const createdTopic = await TopicModel.create(topicData);
|
|
186
|
+
const messageData: CreateMessageParams = {
|
|
187
|
+
content: 'Test Message',
|
|
188
|
+
topicId: createdTopic.id,
|
|
189
|
+
sessionId: topicData.sessionId,
|
|
190
|
+
role: 'user',
|
|
191
|
+
};
|
|
192
|
+
await MessageModel.create(messageData);
|
|
193
|
+
|
|
194
|
+
// 删除话题
|
|
195
|
+
await TopicModel.delete(createdTopic.id);
|
|
196
|
+
|
|
197
|
+
// 验证话题是否被删除
|
|
198
|
+
const topicInDb = await TopicModel.findById(createdTopic.id);
|
|
199
|
+
expect(topicInDb).toBeUndefined();
|
|
200
|
+
|
|
201
|
+
// 验证与话题关联的消息是否也被删除
|
|
202
|
+
const messagesInDb = await MessageModel.query({
|
|
203
|
+
sessionId: topicData.sessionId,
|
|
204
|
+
topicId: createdTopic.id,
|
|
205
|
+
});
|
|
206
|
+
expect(messagesInDb).toHaveLength(0);
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
it('should batch delete topics and their associated messages', async () => {
|
|
210
|
+
// 创建多个话题和相关联的消息
|
|
211
|
+
const createdTopic1 = await TopicModel.create(topicData);
|
|
212
|
+
const createdTopic2 = await TopicModel.create(topicData);
|
|
213
|
+
|
|
214
|
+
const messageData1: CreateMessageParams = {
|
|
215
|
+
content: 'Test Message 1',
|
|
216
|
+
topicId: createdTopic1.id,
|
|
217
|
+
sessionId: topicData.sessionId,
|
|
218
|
+
role: 'user',
|
|
219
|
+
};
|
|
220
|
+
const messageData2: CreateMessageParams = {
|
|
221
|
+
content: 'Test Message 2',
|
|
222
|
+
topicId: createdTopic2.id,
|
|
223
|
+
sessionId: topicData.sessionId,
|
|
224
|
+
role: 'user',
|
|
225
|
+
};
|
|
226
|
+
await MessageModel.create(messageData1);
|
|
227
|
+
await MessageModel.create(messageData2);
|
|
228
|
+
|
|
229
|
+
// 执行批量删除
|
|
230
|
+
await TopicModel.batchDelete([createdTopic1.id, createdTopic2.id]);
|
|
231
|
+
|
|
232
|
+
// 验证话题是否被删除
|
|
233
|
+
const topicInDb1 = await TopicModel.findById(createdTopic1.id);
|
|
234
|
+
const topicInDb2 = await TopicModel.findById(createdTopic2.id);
|
|
235
|
+
expect(topicInDb1).toBeUndefined();
|
|
236
|
+
expect(topicInDb2).toBeUndefined();
|
|
237
|
+
|
|
238
|
+
// 验证与话题关联的消息是否也被删除
|
|
239
|
+
const messagesInDb1 = await MessageModel.query({
|
|
240
|
+
sessionId: topicData.sessionId,
|
|
241
|
+
topicId: createdTopic1.id,
|
|
242
|
+
});
|
|
243
|
+
const messagesInDb2 = await MessageModel.query({
|
|
244
|
+
sessionId: topicData.sessionId,
|
|
245
|
+
topicId: createdTopic2.id,
|
|
246
|
+
});
|
|
247
|
+
expect(messagesInDb1).toHaveLength(0);
|
|
248
|
+
expect(messagesInDb2).toHaveLength(0);
|
|
249
|
+
});
|
|
250
|
+
});
|
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { DB_File, DB_FileSchema } from '@/database/schemas/files';
|
|
2
2
|
import { nanoid } from '@/utils/uuid';
|
|
3
3
|
|
|
4
4
|
import { BaseModel } from '../core';
|
|
5
5
|
|
|
6
|
-
class _FileModel extends BaseModel {
|
|
6
|
+
class _FileModel extends BaseModel<'files'> {
|
|
7
7
|
constructor() {
|
|
8
|
-
super('files',
|
|
8
|
+
super('files', DB_FileSchema);
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
async create(file:
|
|
11
|
+
async create(file: DB_File) {
|
|
12
12
|
const id = nanoid();
|
|
13
13
|
|
|
14
|
-
return this.
|
|
14
|
+
return this._add(file, `file-${id}`);
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
async findById(id: string) {
|
|
@@ -21,6 +21,10 @@ class _FileModel extends BaseModel {
|
|
|
21
21
|
async delete(id: string) {
|
|
22
22
|
return this.table.delete(id);
|
|
23
23
|
}
|
|
24
|
+
|
|
25
|
+
async clear() {
|
|
26
|
+
return this.table.clear();
|
|
27
|
+
}
|
|
24
28
|
}
|
|
25
29
|
|
|
26
30
|
export const FileModel = new _FileModel();
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import { DeepPartial } from 'utility-types';
|
|
2
|
+
|
|
3
|
+
import { BaseModel } from '@/database/core';
|
|
4
|
+
import { DBModel } from '@/database/core/types/db';
|
|
5
|
+
import { DB_Message, DB_MessageSchema } from '@/database/schemas/message';
|
|
6
|
+
import { ChatMessage } from '@/types/chatMessage';
|
|
7
|
+
import { nanoid } from '@/utils/uuid';
|
|
8
|
+
|
|
9
|
+
export interface CreateMessageParams
|
|
10
|
+
extends Partial<Omit<ChatMessage, 'content' | 'role'>>,
|
|
11
|
+
Pick<ChatMessage, 'content' | 'role'> {
|
|
12
|
+
sessionId: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface QueryMessageParams {
|
|
16
|
+
current?: number;
|
|
17
|
+
pageSize?: number;
|
|
18
|
+
sessionId: string;
|
|
19
|
+
topicId?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
class _MessageModel extends BaseModel {
|
|
23
|
+
constructor() {
|
|
24
|
+
super('messages', DB_MessageSchema);
|
|
25
|
+
}
|
|
26
|
+
async create(data: CreateMessageParams) {
|
|
27
|
+
const id = nanoid();
|
|
28
|
+
|
|
29
|
+
const messageData: DB_Message = this.mapChatMessageToDBMessage(data as ChatMessage);
|
|
30
|
+
|
|
31
|
+
return this._add(messageData, id);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async batchCreate(messages: ChatMessage[]) {
|
|
35
|
+
const data: DB_Message[] = messages.map((m) => this.mapChatMessageToDBMessage(m));
|
|
36
|
+
|
|
37
|
+
return this._batchAdd(data);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async query({
|
|
41
|
+
sessionId,
|
|
42
|
+
topicId,
|
|
43
|
+
pageSize = 9999,
|
|
44
|
+
current = 0,
|
|
45
|
+
}: QueryMessageParams): Promise<ChatMessage[]> {
|
|
46
|
+
const offset = current * pageSize;
|
|
47
|
+
|
|
48
|
+
const query =
|
|
49
|
+
topicId !== undefined
|
|
50
|
+
? // TODO: The query {"sessionId":"xxx","topicId":"xxx"} on messages would benefit of a compound index [sessionId+topicId]
|
|
51
|
+
this.table.where({ sessionId, topicId }) // Use a compound index
|
|
52
|
+
: this.table
|
|
53
|
+
.where('sessionId')
|
|
54
|
+
.equals(sessionId)
|
|
55
|
+
.and((message) => !message.topicId);
|
|
56
|
+
|
|
57
|
+
const dbMessages: DBModel<DB_Message>[] = await query
|
|
58
|
+
.sortBy('createdAt')
|
|
59
|
+
// handle page size
|
|
60
|
+
.then((sortedArray) => sortedArray.slice(offset, offset + pageSize));
|
|
61
|
+
|
|
62
|
+
const messages = dbMessages.map((msg) => this.mapToChatMessage(msg));
|
|
63
|
+
|
|
64
|
+
const finalList: ChatMessage[] = [];
|
|
65
|
+
|
|
66
|
+
const addItem = (item: ChatMessage) => {
|
|
67
|
+
const isExist = finalList.findIndex((i) => item.id === i.id) > -1;
|
|
68
|
+
if (!isExist) {
|
|
69
|
+
finalList.push(item);
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
const messageMap = new Map<string, ChatMessage>();
|
|
73
|
+
for (const item of messages) messageMap.set(item.id, item);
|
|
74
|
+
|
|
75
|
+
for (const item of messages) {
|
|
76
|
+
if (!item.parentId || !messageMap.has(item.parentId)) {
|
|
77
|
+
// 如果消息没有父消息或者父消息不在列表中,直接添加
|
|
78
|
+
addItem(item);
|
|
79
|
+
} else {
|
|
80
|
+
// 如果消息有父消息,确保先添加父消息
|
|
81
|
+
addItem(messageMap.get(item.parentId)!);
|
|
82
|
+
addItem(item);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return finalList;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async findById(id: string): Promise<DBModel<DB_Message>> {
|
|
89
|
+
return this.table.get(id);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async delete(id: string) {
|
|
93
|
+
return this.table.delete(id);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async clearTable() {
|
|
97
|
+
return this.table.clear();
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
async update(id: string, data: DeepPartial<DB_Message>) {
|
|
101
|
+
return super._update(id, data);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Batch updates multiple fields of the specified messages.
|
|
106
|
+
*
|
|
107
|
+
* @param {string[]} messageIds - The identifiers of the messages to be updated.
|
|
108
|
+
* @param {Partial<DB_Message>} updateFields - An object containing the fields to update and their new values.
|
|
109
|
+
* @returns {Promise<number>} - The number of updated messages.
|
|
110
|
+
*/
|
|
111
|
+
async batchUpdate(messageIds: string[], updateFields: Partial<DB_Message>): Promise<number> {
|
|
112
|
+
// Retrieve the messages by their IDs
|
|
113
|
+
const messagesToUpdate = await this.table.where(':id').anyOf(messageIds).toArray();
|
|
114
|
+
|
|
115
|
+
// Update the specified fields of each message
|
|
116
|
+
const updatedMessages = messagesToUpdate.map((message) => ({
|
|
117
|
+
...message,
|
|
118
|
+
...updateFields,
|
|
119
|
+
}));
|
|
120
|
+
|
|
121
|
+
// Use the bulkPut method to update the messages in bulk
|
|
122
|
+
await this.table.bulkPut(updatedMessages);
|
|
123
|
+
|
|
124
|
+
return updatedMessages.length;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Deletes multiple messages based on the assistantId and optionally the topicId.
|
|
129
|
+
* If topicId is not provided, it deletes messages where topicId is undefined.
|
|
130
|
+
* If topicId is provided, it deletes messages with that specific topicId.
|
|
131
|
+
*
|
|
132
|
+
* @param {string} sessionId - The identifier of the assistant associated with the messages.
|
|
133
|
+
* @param {string | undefined} topicId - The identifier of the topic associated with the messages (optional).
|
|
134
|
+
* @returns {Promise<void>}
|
|
135
|
+
*/
|
|
136
|
+
async batchDelete(sessionId: string, topicId: string | undefined): Promise<void> {
|
|
137
|
+
// If topicId is specified, use both assistantId and topicId as the filter criteria in the query.
|
|
138
|
+
// Otherwise, filter by assistantId and require that topicId is undefined.
|
|
139
|
+
const query =
|
|
140
|
+
topicId !== undefined
|
|
141
|
+
? this.table.where({ sessionId, topicId }) // Use a compound index
|
|
142
|
+
: this.table
|
|
143
|
+
.where('sessionId')
|
|
144
|
+
.equals(sessionId)
|
|
145
|
+
.and((message) => message.topicId === undefined);
|
|
146
|
+
|
|
147
|
+
// Retrieve a collection of message IDs that satisfy the criteria
|
|
148
|
+
const messageIds = await query.primaryKeys();
|
|
149
|
+
|
|
150
|
+
// Use the bulkDelete method to delete all selected messages in bulk
|
|
151
|
+
return this.table.bulkDelete(messageIds);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
async queryAll() {
|
|
155
|
+
const data: DBModel<DB_Message>[] = await this.table.orderBy('updatedAt').toArray();
|
|
156
|
+
|
|
157
|
+
return data.map((element) => this.mapToChatMessage(element));
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
async isEmpty() {
|
|
161
|
+
const count = await this.table.count();
|
|
162
|
+
|
|
163
|
+
return count === 0;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
private mapChatMessageToDBMessage(message: ChatMessage): DB_Message {
|
|
167
|
+
const { extra, ...messageData } = message;
|
|
168
|
+
|
|
169
|
+
return { ...messageData, ...extra } as DB_Message;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
private mapToChatMessage = ({
|
|
173
|
+
fromModel,
|
|
174
|
+
translate,
|
|
175
|
+
tts,
|
|
176
|
+
...item
|
|
177
|
+
}: DBModel<DB_Message>): ChatMessage => {
|
|
178
|
+
return {
|
|
179
|
+
...item,
|
|
180
|
+
extra: { fromModel: fromModel, translate: translate, tts: tts },
|
|
181
|
+
meta: {},
|
|
182
|
+
};
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export const MessageModel = new _MessageModel();
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { DeepPartial } from 'utility-types';
|
|
2
|
+
|
|
3
|
+
import { DEFAULT_AGENT_LOBE_SESSION } from '@/const/session';
|
|
4
|
+
import { BaseModel } from '@/database/core';
|
|
5
|
+
import { DB_Session, DB_SessionSchema } from '@/database/schemas/session';
|
|
6
|
+
import { LobeAgentConfig } from '@/types/agent';
|
|
7
|
+
import { LobeAgentSession, LobeSessions, SessionGroupKey } from '@/types/session';
|
|
8
|
+
import { merge } from '@/utils/merge';
|
|
9
|
+
import { uuid } from '@/utils/uuid';
|
|
10
|
+
|
|
11
|
+
class _SessionModel extends BaseModel {
|
|
12
|
+
constructor() {
|
|
13
|
+
super('sessions', DB_SessionSchema);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async create(type: 'agent' | 'group', defaultValue: Partial<LobeAgentSession>, id = uuid()) {
|
|
17
|
+
const data = merge(DEFAULT_AGENT_LOBE_SESSION, { type, ...defaultValue });
|
|
18
|
+
return this._add(data, id);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async batchCreate(sessions: LobeAgentSession[]) {
|
|
22
|
+
return this._batchAdd(sessions, { idGenerator: uuid });
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async query({
|
|
26
|
+
pageSize = 9999,
|
|
27
|
+
current = 0,
|
|
28
|
+
}: { current?: number; pageSize?: number } = {}): Promise<LobeSessions> {
|
|
29
|
+
const offset = current * pageSize;
|
|
30
|
+
|
|
31
|
+
return this.table.orderBy('updatedAt').reverse().offset(offset).limit(pageSize).toArray();
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* get sessions by group
|
|
36
|
+
* @param group
|
|
37
|
+
*/
|
|
38
|
+
async queryByGroup(group: SessionGroupKey) {
|
|
39
|
+
return this.table.where('group').equals(group).toArray();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async update(id: string, data: Partial<DB_Session>) {
|
|
43
|
+
return super._update(id, data);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async updateConfig(id: string, data: DeepPartial<LobeAgentConfig>) {
|
|
47
|
+
const session = await this.findById(id);
|
|
48
|
+
if (!session) return;
|
|
49
|
+
|
|
50
|
+
const config = merge(session.config, data);
|
|
51
|
+
|
|
52
|
+
return this.update(id, { config });
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Delete a session , also delete all messages and topic associated with it.
|
|
56
|
+
*/
|
|
57
|
+
async delete(id: string) {
|
|
58
|
+
return this.db.transaction('rw', [this.table, this.db.topics, this.db.messages], async () => {
|
|
59
|
+
// Delete all topics associated with the session
|
|
60
|
+
const topics = await this.db.topics.where('sessionId').equals(id).toArray();
|
|
61
|
+
const topicIds = topics.map((topic) => topic.id);
|
|
62
|
+
if (topicIds.length > 0) {
|
|
63
|
+
await this.db.topics.bulkDelete(topicIds);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Delete all messages associated with the session
|
|
67
|
+
const messages = await this.db.messages.where('sessionId').equals(id).toArray();
|
|
68
|
+
const messageIds = messages.map((message) => message.id);
|
|
69
|
+
if (messageIds.length > 0) {
|
|
70
|
+
await this.db.messages.bulkDelete(messageIds);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Finally, delete the session itself
|
|
74
|
+
await this.table.delete(id);
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async clearTable() {
|
|
79
|
+
return this.table.clear();
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async findById(id: string) {
|
|
83
|
+
return this.table.get(id);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async isEmpty() {
|
|
87
|
+
return (await this.table.count()) === 0;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Query sessions by keyword in title, description, content, or translated content
|
|
92
|
+
* @param keyword The keyword to search for
|
|
93
|
+
*/
|
|
94
|
+
async queryByKeyword(keyword: string): Promise<LobeSessions> {
|
|
95
|
+
if (!keyword) return [];
|
|
96
|
+
|
|
97
|
+
console.time('queryByKeyword');
|
|
98
|
+
const keywordLowerCase = keyword.toLowerCase();
|
|
99
|
+
|
|
100
|
+
// First, filter sessions by title and description
|
|
101
|
+
const matchingSessionsPromise = this.table
|
|
102
|
+
.filter((session) => {
|
|
103
|
+
return (
|
|
104
|
+
session.meta.title.toLowerCase().includes(keywordLowerCase) ||
|
|
105
|
+
session.meta.description.toLowerCase().includes(keywordLowerCase)
|
|
106
|
+
);
|
|
107
|
+
})
|
|
108
|
+
.toArray();
|
|
109
|
+
|
|
110
|
+
// Next, find message IDs that contain the keyword in content or translated content
|
|
111
|
+
const matchingMessagesPromise = this.db.messages
|
|
112
|
+
.filter((message) => {
|
|
113
|
+
// check content
|
|
114
|
+
if (message.content.toLowerCase().includes(keywordLowerCase)) return true;
|
|
115
|
+
|
|
116
|
+
// check translate content
|
|
117
|
+
if (message.translate && message.translate.content) {
|
|
118
|
+
return message.translate.content.toLowerCase().includes(keywordLowerCase);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return false;
|
|
122
|
+
})
|
|
123
|
+
.toArray();
|
|
124
|
+
|
|
125
|
+
// match topics
|
|
126
|
+
const matchingTopicsPromise = this.db.topics
|
|
127
|
+
.filter((topic) => {
|
|
128
|
+
return topic.title.toLowerCase().includes(keywordLowerCase);
|
|
129
|
+
})
|
|
130
|
+
.toArray();
|
|
131
|
+
|
|
132
|
+
// Resolve both promises
|
|
133
|
+
const [matchingSessions, matchingMessages, matchingTopics] = await Promise.all([
|
|
134
|
+
matchingSessionsPromise,
|
|
135
|
+
matchingMessagesPromise,
|
|
136
|
+
matchingTopicsPromise,
|
|
137
|
+
]);
|
|
138
|
+
|
|
139
|
+
const sessionIdsFromMessages = matchingMessages.map((message) => message.sessionId);
|
|
140
|
+
const sessionIdsFromTopics = matchingTopics.map((topic) => topic.sessionId);
|
|
141
|
+
|
|
142
|
+
// Combine session IDs from both sources
|
|
143
|
+
const combinedSessionIds = new Set([
|
|
144
|
+
...sessionIdsFromMessages,
|
|
145
|
+
...sessionIdsFromTopics,
|
|
146
|
+
...matchingSessions.map((session) => session.id),
|
|
147
|
+
]);
|
|
148
|
+
|
|
149
|
+
// Retrieve unique sessions by IDs
|
|
150
|
+
const data = await this.table
|
|
151
|
+
.where('id')
|
|
152
|
+
.anyOf([...combinedSessionIds])
|
|
153
|
+
.toArray();
|
|
154
|
+
|
|
155
|
+
console.timeEnd('queryByKeyword');
|
|
156
|
+
return data;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export const SessionModel = new _SessionModel();
|