@lobehub/chat 0.120.6 → 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.
Files changed (121) hide show
  1. package/.github/ISSUE_TEMPLATE/2_feature_request_cn.yml +1 -1
  2. package/CHANGELOG.md +25 -0
  3. package/README.md +9 -9
  4. package/README.zh-CN.md +9 -9
  5. package/docs/Development/Feature-Development-Frontend.zh-CN.md +126 -0
  6. package/docs/Development/Feature-Development.zh-CN.md +664 -83
  7. package/docs/Development/{State-Management/State-Management-Intro.zh-CN.md → State-Management-Intro.zh-CN.md} +9 -9
  8. package/docs/Home.md +1 -0
  9. package/docs/_Sidebar.md +1 -0
  10. package/locales/ar/chat.json +13 -1
  11. package/locales/ar/common.json +1 -0
  12. package/locales/de-DE/chat.json +13 -1
  13. package/locales/de-DE/common.json +1 -0
  14. package/locales/en-US/chat.json +13 -1
  15. package/locales/en-US/common.json +1 -0
  16. package/locales/es-ES/chat.json +13 -1
  17. package/locales/es-ES/common.json +1 -0
  18. package/locales/fr-FR/chat.json +13 -1
  19. package/locales/fr-FR/common.json +1 -0
  20. package/locales/it-IT/chat.json +13 -1
  21. package/locales/it-IT/common.json +1 -0
  22. package/locales/ja-JP/chat.json +13 -1
  23. package/locales/ja-JP/common.json +1 -0
  24. package/locales/ko-KR/chat.json +13 -1
  25. package/locales/ko-KR/common.json +1 -0
  26. package/locales/nl-NL/chat.json +13 -1
  27. package/locales/nl-NL/common.json +1 -0
  28. package/locales/pl-PL/chat.json +13 -1
  29. package/locales/pl-PL/common.json +1 -0
  30. package/locales/pt-BR/chat.json +13 -1
  31. package/locales/pt-BR/common.json +1 -0
  32. package/locales/ru-RU/chat.json +13 -1
  33. package/locales/ru-RU/common.json +1 -0
  34. package/locales/tr-TR/chat.json +13 -1
  35. package/locales/tr-TR/common.json +1 -0
  36. package/locales/vi-VN/chat.json +13 -1
  37. package/locales/vi-VN/common.json +1 -0
  38. package/locales/zh-CN/chat.json +13 -1
  39. package/locales/zh-CN/common.json +1 -0
  40. package/locales/zh-TW/chat.json +13 -1
  41. package/locales/zh-TW/common.json +1 -0
  42. package/package.json +4 -6
  43. package/src/app/chat/(desktop)/features/ChatInput/Footer/index.tsx +18 -5
  44. package/src/app/chat/(desktop)/features/ChatInput/index.tsx +1 -1
  45. package/src/app/chat/features/ChatHeader/ShareButton/index.tsx +0 -1
  46. package/src/app/chat/features/Migration/Failed.tsx +2 -0
  47. package/src/app/chat/features/SessionListContent/CollapseGroup/Actions.tsx +115 -0
  48. package/src/app/chat/features/SessionListContent/CollapseGroup/index.tsx +6 -0
  49. package/src/app/chat/features/SessionListContent/DefaultMode.tsx +63 -23
  50. package/src/app/chat/features/SessionListContent/List/AddButton.tsx +3 -3
  51. package/src/app/chat/features/SessionListContent/List/Item/Actions.tsx +89 -35
  52. package/src/app/chat/features/SessionListContent/List/Item/index.tsx +35 -15
  53. package/src/app/chat/features/SessionListContent/List/index.tsx +4 -2
  54. package/src/app/chat/features/SessionListContent/Modals/ConfigGroupModal/GroupItem.tsx +84 -0
  55. package/src/app/chat/features/SessionListContent/Modals/ConfigGroupModal/index.tsx +79 -0
  56. package/src/app/chat/features/SessionListContent/Modals/CreateGroupModal.tsx +63 -0
  57. package/src/app/chat/features/SessionListContent/Modals/RenameGroupModal.tsx +50 -0
  58. package/src/app/chat/features/SessionListContent/SearchMode.tsx +1 -1
  59. package/src/app/settings/common/Common.tsx +5 -1
  60. package/src/components/FullscreenLoading/index.tsx +1 -1
  61. package/src/database/core/__tests__/db-upgrade.test.ts +42 -0
  62. package/src/database/core/db.ts +26 -4
  63. package/src/database/core/schemas.ts +14 -1
  64. package/src/database/core/types/db.ts +2 -0
  65. package/src/database/models/__tests__/message.test.ts +42 -0
  66. package/src/database/models/__tests__/plugin.test.ts +77 -0
  67. package/src/database/models/__tests__/session.test.ts +202 -63
  68. package/src/database/models/__tests__/sessionGroup.test.ts +195 -0
  69. package/src/database/models/__tests__/topic.test.ts +76 -1
  70. package/src/database/models/message.ts +7 -8
  71. package/src/database/models/session.ts +103 -7
  72. package/src/database/models/sessionGroup.ts +76 -0
  73. package/src/database/schemas/session.ts +1 -0
  74. package/src/database/schemas/sessionGroup.ts +8 -0
  75. package/src/features/ChatInput/ActionBar/Clear.tsx +2 -1
  76. package/src/features/Conversation/Actions/Assistant.tsx +1 -1
  77. package/src/features/Conversation/Actions/Function.tsx +2 -2
  78. package/src/features/Conversation/Actions/User.tsx +1 -1
  79. package/src/features/Conversation/hooks/useChatListActionsBar.tsx +3 -3
  80. package/src/features/PluginDevModal/index.tsx +3 -0
  81. package/src/features/PluginStore/PluginItem/Action.tsx +2 -0
  82. package/src/locales/default/chat.ts +13 -1
  83. package/src/locales/default/common.ts +1 -0
  84. package/src/migrations/FromV2ToV3/fixtures/input-v2-session.json +72 -0
  85. package/src/migrations/FromV2ToV3/fixtures/output-v3-from-v1.json +203 -0
  86. package/src/migrations/FromV2ToV3/fixtures/output-v3.json +74 -0
  87. package/src/migrations/FromV2ToV3/index.ts +30 -0
  88. package/src/migrations/FromV2ToV3/migrations.test.ts +42 -0
  89. package/src/migrations/FromV2ToV3/types/v3.ts +27 -0
  90. package/src/migrations/index.ts +7 -1
  91. package/src/services/__tests__/session.test.ts +198 -7
  92. package/src/services/config.ts +23 -7
  93. package/src/services/session.ts +67 -14
  94. package/src/store/global/slices/common/action.test.ts +130 -0
  95. package/src/store/global/slices/common/action.ts +14 -0
  96. package/src/store/global/slices/common/initialState.ts +6 -2
  97. package/src/store/global/slices/common/selectors.ts +1 -4
  98. package/src/store/global/slices/settings/__snapshots__/selectors.test.ts.snap +83 -0
  99. package/src/store/global/slices/settings/action.ts +2 -2
  100. package/src/store/global/slices/settings/selectors.test.ts +132 -182
  101. package/src/store/global/store.ts +7 -0
  102. package/src/store/session/initialState.ts +5 -30
  103. package/src/store/session/selectors.ts +2 -1
  104. package/src/store/session/slices/session/action.test.ts +56 -0
  105. package/src/store/session/slices/session/action.ts +13 -9
  106. package/src/store/session/slices/session/helpers.ts +2 -3
  107. package/src/store/session/slices/session/initialState.ts +31 -23
  108. package/src/store/session/slices/session/selectors/index.ts +0 -1
  109. package/src/store/session/slices/session/selectors/list.ts +11 -18
  110. package/src/store/session/slices/sessionGroup/action.ts +54 -0
  111. package/src/store/session/slices/sessionGroup/initialState.ts +5 -0
  112. package/src/store/session/slices/sessionGroup/selectors.ts +15 -0
  113. package/src/store/session/store.ts +9 -2
  114. package/src/styles/antdOverride.ts +0 -7
  115. package/src/types/exportConfig.ts +3 -1
  116. package/src/types/session.ts +27 -3
  117. package/src/store/session/slices/session/selectors/export.ts +0 -31
  118. /package/docs/Development/{Feature-Development.md → Feature-Development-Frontend.md} +0 -0
  119. /package/docs/Development/{State-Management/State-Management-Intro.md → State-Management-Intro.md} +0 -0
  120. /package/docs/Development/{State-Management/Selectors.md → State-Management-Selectors.md} +0 -0
  121. /package/docs/Development/{State-Management/Selectors.zh-CN.md → State-Management-Selectors.zh-CN.md} +0 -0
@@ -4,10 +4,10 @@ import { topicService } from '@/services/topic';
4
4
  import { useGlobalStore } from '@/store/global';
5
5
  import { settingsSelectors } from '@/store/global/selectors';
6
6
  import { useSessionStore } from '@/store/session';
7
- import { sessionExportSelectors, sessionSelectors } from '@/store/session/selectors';
7
+ import { sessionSelectors } from '@/store/session/selectors';
8
8
  import { ConfigFile } from '@/types/exportConfig';
9
9
  import { ChatMessage } from '@/types/message';
10
- import { LobeSessions } from '@/types/session';
10
+ import { LobeSessions, SessionGroupItem } from '@/types/session';
11
11
  import { GlobalSettings } from '@/types/settings';
12
12
  import { ChatTopic } from '@/types/topic';
13
13
  import { createConfigFile, exportConfigFile } from '@/utils/config';
@@ -19,6 +19,7 @@ export interface ImportResult {
19
19
  }
20
20
  export interface ImportResults {
21
21
  messages?: ImportResult;
22
+ sessionGroups?: ImportResult;
22
23
  sessions: ImportResult;
23
24
  topics?: ImportResult;
24
25
  }
@@ -40,6 +41,9 @@ class ConfigService {
40
41
  importTopics = async (topics: ChatTopic[]) => {
41
42
  return topicService.batchCreateTopics(topics);
42
43
  };
44
+ importSessionGroups = async (sessionGroups: SessionGroupItem[]) => {
45
+ return sessionService.batchCreateSessionGroups(sessionGroups);
46
+ };
43
47
 
44
48
  importConfigState = async (config: ConfigFile): Promise<ImportResults | undefined> => {
45
49
  switch (config.exportType) {
@@ -50,8 +54,11 @@ class ConfigService {
50
54
  }
51
55
 
52
56
  case 'agents': {
57
+ const sessionGroups = await this.importSessionGroups(config.state.sessionGroups);
58
+
53
59
  const data = await this.importSessions(config.state.sessions);
54
60
  return {
61
+ sessionGroups: this.mapImportResult(sessionGroups),
55
62
  sessions: this.mapImportResult(data),
56
63
  };
57
64
  }
@@ -59,6 +66,8 @@ class ConfigService {
59
66
  case 'all': {
60
67
  await this.importSettings(config.state.settings);
61
68
 
69
+ const sessionGroups = await this.importSessionGroups(config.state.sessionGroups);
70
+
62
71
  const [sessions, messages, topics] = await Promise.all([
63
72
  this.importSessions(config.state.sessions),
64
73
  this.importMessages(config.state.messages),
@@ -67,12 +76,15 @@ class ConfigService {
67
76
 
68
77
  return {
69
78
  messages: this.mapImportResult(messages),
79
+ sessionGroups: this.mapImportResult(sessionGroups),
70
80
  sessions: this.mapImportResult(sessions),
71
81
  topics: this.mapImportResult(topics),
72
82
  };
73
83
  }
74
84
 
75
85
  case 'sessions': {
86
+ const sessionGroups = await this.importSessionGroups(config.state.sessionGroups);
87
+
76
88
  const [sessions, messages, topics] = await Promise.all([
77
89
  this.importSessions(config.state.sessions),
78
90
  this.importMessages(config.state.messages),
@@ -81,6 +93,7 @@ class ConfigService {
81
93
 
82
94
  return {
83
95
  messages: this.mapImportResult(messages),
96
+ sessionGroups: this.mapImportResult(sessionGroups),
84
97
  sessions: this.mapImportResult(sessions),
85
98
  topics: this.mapImportResult(topics),
86
99
  };
@@ -93,8 +106,9 @@ class ConfigService {
93
106
  */
94
107
  exportAgents = async () => {
95
108
  const agents = await sessionService.getAllAgents();
109
+ const sessionGroups = await sessionService.getSessionGroups();
96
110
 
97
- const config = createConfigFile('agents', { sessions: agents });
111
+ const config = createConfigFile('agents', { sessionGroups, sessions: agents });
98
112
 
99
113
  exportConfigFile(config, 'agents');
100
114
  };
@@ -104,10 +118,11 @@ class ConfigService {
104
118
  */
105
119
  exportSessions = async () => {
106
120
  const sessions = await sessionService.getSessions();
121
+ const sessionGroups = await sessionService.getSessionGroups();
107
122
  const messages = await messageService.getAllMessages();
108
123
  const topics = await topicService.getAllTopics();
109
124
 
110
- const config = createConfigFile('sessions', { messages, sessions, topics });
125
+ const config = createConfigFile('sessions', { messages, sessionGroups, sessions, topics });
111
126
 
112
127
  exportConfigFile(config, 'sessions');
113
128
  };
@@ -153,7 +168,7 @@ class ConfigService {
153
168
  const agent = this.getAgent(id);
154
169
  if (!agent) return;
155
170
 
156
- const config = createConfigFile('agents', { sessions: [agent] });
171
+ const config = createConfigFile('agents', { sessionGroups: [], sessions: [agent] });
157
172
 
158
173
  exportConfigFile(config, agent.meta?.title || 'agent');
159
174
  };
@@ -174,11 +189,12 @@ class ConfigService {
174
189
  */
175
190
  exportAll = async () => {
176
191
  const sessions = await sessionService.getSessions();
192
+ const sessionGroups = await sessionService.getSessionGroups();
177
193
  const messages = await messageService.getAllMessages();
178
194
  const topics = await topicService.getAllTopics();
179
195
  const settings = this.getSettings();
180
196
 
181
- const config = createConfigFile('all', { messages, sessions, settings, topics });
197
+ const config = createConfigFile('all', { messages, sessionGroups, sessions, settings, topics });
182
198
 
183
199
  exportConfigFile(config, 'config');
184
200
  };
@@ -189,7 +205,7 @@ class ConfigService {
189
205
  sessionSelectors.getSessionById(id)(useSessionStore.getState());
190
206
 
191
207
  private getAgent = (id: string) =>
192
- sessionExportSelectors.getExportAgent(id)(useSessionStore.getState());
208
+ sessionSelectors.getSessionById(id)(useSessionStore.getState());
193
209
 
194
210
  private mapImportResult = (input: {
195
211
  added: number;
@@ -1,9 +1,18 @@
1
1
  import { DeepPartial } from 'utility-types';
2
2
 
3
3
  import { SessionModel } from '@/database/models/session';
4
+ import { SessionGroupModel } from '@/database/models/sessionGroup';
4
5
  import { LobeAgentConfig } from '@/types/agent';
5
6
  import { MetaData } from '@/types/meta';
6
- import { LobeAgentSession, LobeSessionType, LobeSessions, SessionGroupKey } from '@/types/session';
7
+ import {
8
+ ChatSessionList,
9
+ LobeAgentSession,
10
+ LobeSessionType,
11
+ LobeSessions,
12
+ SessionGroupId,
13
+ SessionGroupItem,
14
+ SessionGroups,
15
+ } from '@/types/session';
7
16
 
8
17
  class SessionService {
9
18
  async createNewSession(
@@ -21,26 +30,40 @@ class SessionService {
21
30
  return SessionModel.batchCreate(importSessions);
22
31
  }
23
32
 
33
+ async duplicateSession(id: string, newTitle: string): Promise<string | undefined> {
34
+ const res = await SessionModel.duplicate(id, newTitle);
35
+
36
+ if (res) return res?.id;
37
+ }
38
+
24
39
  async getSessions(): Promise<LobeSessions> {
25
40
  return SessionModel.query();
26
41
  }
27
42
 
43
+ async getSessionsWithGroup(): Promise<ChatSessionList> {
44
+ return SessionModel.queryWithGroups();
45
+ }
46
+
28
47
  async getAllAgents(): Promise<LobeSessions> {
29
48
  // TODO: add a filter to get only agents
30
49
  return await SessionModel.query();
31
50
  }
32
51
 
33
- async removeSession(id: string) {
34
- return SessionModel.delete(id);
52
+ async hasSessions() {
53
+ const isEmpty = await SessionModel.isEmpty();
54
+ return !isEmpty;
35
55
  }
36
56
 
37
- async removeAllSessions() {
38
- return SessionModel.clearTable();
57
+ async searchSessions(keyword: string) {
58
+ return SessionModel.queryByKeyword(keyword);
39
59
  }
40
60
 
41
- async updateSessionGroup(id: string, group: SessionGroupKey) {
61
+ async updateSessionGroupId(id: string, group: SessionGroupId) {
42
62
  return SessionModel.update(id, { group });
43
63
  }
64
+ async updateSessionPinned(id: string, pinned: boolean) {
65
+ return SessionModel.updatePinned(id, pinned);
66
+ }
44
67
 
45
68
  async updateSessionMeta(activeId: string, meta: Partial<MetaData>) {
46
69
  return SessionModel.update(activeId, { meta });
@@ -50,19 +73,49 @@ class SessionService {
50
73
  return SessionModel.updateConfig(activeId, config);
51
74
  }
52
75
 
53
- async hasSessions() {
54
- const isEmpty = await SessionModel.isEmpty();
55
- return !isEmpty;
76
+ async removeSession(id: string) {
77
+ return SessionModel.delete(id);
56
78
  }
57
79
 
58
- async searchSessions(keyword: string) {
59
- return SessionModel.queryByKeyword(keyword);
80
+ async removeAllSessions() {
81
+ return SessionModel.clearTable();
60
82
  }
61
83
 
62
- async duplicateSession(id: string, newTitle: string): Promise<string | undefined> {
63
- const res = await SessionModel.duplicate(id, newTitle);
84
+ // ************************************** //
85
+ // *********** SessionGroup *********** //
86
+ // ************************************** //
64
87
 
65
- if (res) return res?.id;
88
+ async createSessionGroup(name: string, sort?: number) {
89
+ const item = await SessionGroupModel.create(name, sort);
90
+ if (!item) {
91
+ throw new Error('session group create Error');
92
+ }
93
+
94
+ return item.id;
95
+ }
96
+
97
+ async batchCreateSessionGroups(groups: SessionGroups) {
98
+ return SessionGroupModel.batchCreate(groups);
99
+ }
100
+
101
+ async removeSessionGroup(id: string, removeChildren?: boolean) {
102
+ return await SessionGroupModel.delete(id, removeChildren);
103
+ }
104
+
105
+ async updateSessionGroup(id: string, data: Partial<SessionGroupItem>) {
106
+ return SessionGroupModel.update(id, data);
107
+ }
108
+
109
+ async updateSessionGroupOrder(sortMap: { id: string; sort: number }[]) {
110
+ return SessionGroupModel.updateOrder(sortMap);
111
+ }
112
+
113
+ async getSessionGroups(): Promise<SessionGroupItem[]> {
114
+ return SessionGroupModel.query();
115
+ }
116
+
117
+ async clearSessionGroups() {
118
+ return SessionGroupModel.clear();
66
119
  }
67
120
  }
68
121
 
@@ -0,0 +1,130 @@
1
+ import { act, renderHook } from '@testing-library/react';
2
+ import { router } from 'next/client';
3
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
4
+
5
+ import { globalService } from '@/services/global';
6
+ import { useGlobalStore } from '@/store/global';
7
+ import { type Guide, SidebarTabKey } from '@/store/global/slices/common/initialState';
8
+
9
+ // Mock globalService
10
+ vi.mock('@/services/global', () => ({
11
+ globalService: {
12
+ getLatestVersion: vi.fn(),
13
+ getGlobalConfig: vi.fn(),
14
+ },
15
+ }));
16
+
17
+ beforeEach(() => {
18
+ vi.clearAllMocks();
19
+ });
20
+
21
+ afterEach(() => {
22
+ vi.restoreAllMocks();
23
+ });
24
+
25
+ describe('createCommonSlice', () => {
26
+ describe('switchBackToChat', () => {
27
+ it('should switch back to chat', () => {
28
+ const { result } = renderHook(() => useGlobalStore());
29
+ const sessionId = 'session-id';
30
+ const router = { push: vi.fn() } as any;
31
+
32
+ act(() => {
33
+ useGlobalStore.setState({ router });
34
+ result.current.switchBackToChat(sessionId);
35
+ });
36
+
37
+ expect(router.push).toHaveBeenCalledWith('/chat?session=session-id');
38
+ });
39
+ });
40
+
41
+ describe('switchSideBar', () => {
42
+ it('should switch sidebar', () => {
43
+ const { result } = renderHook(() => useGlobalStore());
44
+
45
+ act(() => {
46
+ result.current.switchSideBar(SidebarTabKey.Market);
47
+ });
48
+
49
+ expect(result.current.sidebarKey).toEqual(SidebarTabKey.Market);
50
+ });
51
+ });
52
+
53
+ describe('toggleChatSideBar', () => {
54
+ it('should toggle chat sidebar', () => {
55
+ const { result } = renderHook(() => useGlobalStore());
56
+
57
+ act(() => {
58
+ useGlobalStore.getState().updatePreference({ showChatSideBar: false });
59
+ result.current.toggleChatSideBar();
60
+ });
61
+
62
+ expect(result.current.preference.showChatSideBar).toBe(true);
63
+ });
64
+
65
+ // Add more tests for other actions in createCommonSlice
66
+ });
67
+
68
+ describe('toggleExpandSessionGroup', () => {
69
+ it('should toggle expand session group', () => {
70
+ const { result } = renderHook(() => useGlobalStore());
71
+ const groupId = 'group-id';
72
+
73
+ act(() => {
74
+ result.current.toggleExpandSessionGroup(groupId, true);
75
+ });
76
+
77
+ expect(result.current.preference.expandSessionGroupKeys).toContain(groupId);
78
+ });
79
+ });
80
+
81
+ describe('toggleMobileTopic', () => {
82
+ it('should toggle mobile topic', () => {
83
+ const { result } = renderHook(() => useGlobalStore());
84
+
85
+ act(() => {
86
+ result.current.toggleMobileTopic();
87
+ });
88
+
89
+ expect(result.current.preference.mobileShowTopic).toBe(true);
90
+ });
91
+ });
92
+
93
+ describe('toggleSystemRole', () => {
94
+ it('should toggle system role', () => {
95
+ const { result } = renderHook(() => useGlobalStore());
96
+
97
+ act(() => {
98
+ result.current.toggleSystemRole(true);
99
+ });
100
+
101
+ expect(result.current.preference.showSystemRole).toBe(true);
102
+ });
103
+ });
104
+
105
+ describe('updateGuideState', () => {
106
+ it('should update guide state', () => {
107
+ const { result } = renderHook(() => useGlobalStore());
108
+ const guide: Guide = { topic: true };
109
+
110
+ act(() => {
111
+ result.current.updateGuideState(guide);
112
+ });
113
+
114
+ expect(result.current.preference.guide).toEqual(guide);
115
+ });
116
+ });
117
+
118
+ describe('updatePreference', () => {
119
+ it('should update preference', () => {
120
+ const { result } = renderHook(() => useGlobalStore());
121
+ const preference = { inputHeight: 200 };
122
+
123
+ act(() => {
124
+ result.current.updatePreference(preference);
125
+ });
126
+
127
+ expect(result.current.preference.inputHeight).toEqual(200);
128
+ });
129
+ });
130
+ });
@@ -27,6 +27,7 @@ export interface CommonAction {
27
27
  */
28
28
  switchSideBar: (key: SidebarTabKey) => void;
29
29
  toggleChatSideBar: (visible?: boolean) => void;
30
+ toggleExpandSessionGroup: (id: string, expand: boolean) => void;
30
31
  toggleMobileTopic: (visible?: boolean) => void;
31
32
  toggleSystemRole: (visible?: boolean) => void;
32
33
  updateGuideState: (guide: Partial<Guide>) => void;
@@ -53,6 +54,19 @@ export const createCommonSlice: StateCreator<
53
54
 
54
55
  get().updatePreference({ showChatSideBar }, n('toggleAgentPanel', newValue) as string);
55
56
  },
57
+ toggleExpandSessionGroup: (id, expand) => {
58
+ const { preference } = get();
59
+ const nextExpandSessionGroup = produce(preference.expandSessionGroupKeys, (draft) => {
60
+ if (expand) {
61
+ if (draft.includes(id)) return;
62
+ draft.push(id);
63
+ } else {
64
+ const index = draft.indexOf(id);
65
+ if (index !== -1) draft.splice(index, 1);
66
+ }
67
+ });
68
+ get().updatePreference({ expandSessionGroupKeys: nextExpandSessionGroup });
69
+ },
56
70
  toggleMobileTopic: (newValue) => {
57
71
  const mobileShowTopic =
58
72
  typeof newValue === 'boolean' ? newValue : !get().preference.mobileShowTopic;
@@ -1,5 +1,7 @@
1
1
  import { AppRouterInstance } from 'next/dist/shared/lib/app-router-context.shared-runtime';
2
2
 
3
+ import { SessionDefaultGroup, SessionGroupId } from '@/types/session';
4
+
3
5
  export enum SidebarTabKey {
4
6
  Chat = 'chat',
5
7
  Market = 'market',
@@ -19,11 +21,13 @@ export interface Guide {
19
21
  }
20
22
 
21
23
  export interface GlobalPreference {
24
+ // which sessionGroup should expand
25
+ expandSessionGroupKeys: SessionGroupId[];
22
26
  guide?: Guide;
23
27
  inputHeight: number;
24
28
  mobileShowTopic?: boolean;
25
- sessionGroupKeys: string[];
26
29
  sessionsWidth: number;
30
+
27
31
  showChatSideBar?: boolean;
28
32
  showSessionPanel?: boolean;
29
33
  showSystemRole?: boolean;
@@ -50,10 +54,10 @@ export interface GlobalCommonState {
50
54
  export const initialCommonState: GlobalCommonState = {
51
55
  isMobile: false,
52
56
  preference: {
57
+ expandSessionGroupKeys: [SessionDefaultGroup.Pinned, SessionDefaultGroup.Default],
53
58
  guide: {},
54
59
  inputHeight: 200,
55
60
  mobileShowTopic: false,
56
- sessionGroupKeys: ['pinned', 'sessionList'],
57
61
  sessionsWidth: 320,
58
62
  showChatSideBar: true,
59
63
  showSessionPanel: true,
@@ -1,9 +1,6 @@
1
1
  import { GlobalStore } from '@/store/global';
2
2
 
3
- import { initialState } from '../../initialState';
4
-
5
- const sessionGroupKeys = (s: GlobalStore): string[] =>
6
- s.preference.sessionGroupKeys || initialState.preference.sessionGroupKeys;
3
+ const sessionGroupKeys = (s: GlobalStore): string[] => s.preference.expandSessionGroupKeys || [];
7
4
 
8
5
  const useCmdEnterToSend = (s: GlobalStore): boolean => s.preference.useCmdEnterToSend || false;
9
6
 
@@ -161,3 +161,86 @@ exports[`settingsSelectors > currentSettings > should merge DEFAULT_SETTINGS and
161
161
  },
162
162
  }
163
163
  `;
164
+
165
+ exports[`settingsSelectors > currentTTS > should merge DEFAULT_TTS_CONFIG and s.settings.tts correctly 1`] = `
166
+ {
167
+ "openAI": {
168
+ "sttModel": "whisper-2",
169
+ "ttsModel": "tts-1",
170
+ },
171
+ "sttAutoStop": false,
172
+ "sttServer": "openai",
173
+ }
174
+ `;
175
+
176
+ exports[`settingsSelectors > dalleConfig > should return the dalle configuration 1`] = `
177
+ {
178
+ "apiKey": "dalle-api-key",
179
+ "autoGenerate": true,
180
+ }
181
+ `;
182
+
183
+ exports[`settingsSelectors > defaultAgent > should merge DEFAULT_AGENT and s.settings.defaultAgent correctly 1`] = `
184
+ {
185
+ "config": {
186
+ "autoCreateTopicThreshold": 2,
187
+ "displayMode": "chat",
188
+ "enableAutoCreateTopic": true,
189
+ "historyCount": 1,
190
+ "model": "gpt-3.5-turbo",
191
+ "params": {
192
+ "frequency_penalty": 0,
193
+ "presence_penalty": 0,
194
+ "temperature": 0.6,
195
+ "top_p": 1,
196
+ },
197
+ "plugins": [],
198
+ "systemRole": "user",
199
+ "tts": {
200
+ "showAllLocaleVoice": false,
201
+ "sttLocale": "auto",
202
+ "ttsService": "openai",
203
+ "voice": {
204
+ "openai": "alloy",
205
+ },
206
+ },
207
+ },
208
+ "meta": {
209
+ "avatar": "agent-avatar.jpg",
210
+ "description": "Test agent",
211
+ },
212
+ }
213
+ `;
214
+
215
+ exports[`settingsSelectors > defaultAgentConfig > should merge DEFAULT_AGENT_CONFIG and defaultAgent(s).config correctly 1`] = `
216
+ {
217
+ "autoCreateTopicThreshold": 2,
218
+ "displayMode": "chat",
219
+ "enableAutoCreateTopic": true,
220
+ "historyCount": 1,
221
+ "model": "gpt-3.5-turbo",
222
+ "params": {
223
+ "frequency_penalty": 0,
224
+ "presence_penalty": 0,
225
+ "temperature": 0.7,
226
+ "top_p": 1,
227
+ },
228
+ "plugins": [],
229
+ "systemRole": "user",
230
+ "tts": {
231
+ "showAllLocaleVoice": false,
232
+ "sttLocale": "auto",
233
+ "ttsService": "openai",
234
+ "voice": {
235
+ "openai": "alloy",
236
+ },
237
+ },
238
+ }
239
+ `;
240
+
241
+ exports[`settingsSelectors > defaultAgentMeta > should merge DEFAULT_AGENT_META and defaultAgent(s).meta correctly 1`] = `
242
+ {
243
+ "avatar": "agent-avatar.jpg",
244
+ "description": "Test agent",
245
+ }
246
+ `;
@@ -24,12 +24,12 @@ export interface SettingsAction {
24
24
  */
25
25
  resetSettings: () => void;
26
26
  setOpenAIConfig: (config: Partial<OpenAIConfig>) => void;
27
-
28
27
  /**
29
28
  * 设置部分配置设置
30
29
  * @param settings - 部分配置设置
31
30
  */
32
31
  setSettings: (settings: DeepPartial<GlobalSettings>) => void;
32
+
33
33
  switchSettingTabs: (tab: SettingsTabs) => void;
34
34
  /**
35
35
  * 设置主题模式
@@ -68,7 +68,6 @@ export const createSettingsSlice: StateCreator<
68
68
  setOpenAIConfig: (config) => {
69
69
  get().setSettings({ languageModel: { openAI: config } });
70
70
  },
71
-
72
71
  setSettings: (settings) => {
73
72
  const prevSetting = get().settings;
74
73
  const nextSettings = merge(prevSetting, settings);
@@ -77,6 +76,7 @@ export const createSettingsSlice: StateCreator<
77
76
 
78
77
  set({ settings: merge(prevSetting, settings) }, false, n('setSettings', settings));
79
78
  },
79
+
80
80
  switchSettingTabs: (tab) => {
81
81
  set({ settingsTab: tab });
82
82
  },