@lobehub/chat 0.155.4 → 0.155.6

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 (149) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/README.md +2 -2
  3. package/README.zh-CN.md +8 -8
  4. package/package.json +1 -1
  5. package/src/app/(main)/(mobile)/me/features/Header.tsx +2 -9
  6. package/src/app/(main)/(mobile)/me/loading.tsx +1 -12
  7. package/src/app/(main)/(mobile)/me/page.tsx +2 -5
  8. package/src/app/(main)/@nav/_layout/Mobile.tsx +1 -1
  9. package/src/app/(main)/_layout/Mobile.tsx +5 -1
  10. package/src/app/(main)/chat/(workspace)/@conversation/default.tsx +23 -0
  11. package/src/app/(main)/chat/{(desktop)/features/ChatInput → (workspace)/@conversation/features/ChatInput/Desktop}/Footer/index.tsx +2 -1
  12. package/src/app/(main)/chat/{(desktop)/features/ChatInput → (workspace)/@conversation/features/ChatInput/Desktop}/TextArea.test.tsx +6 -6
  13. package/src/app/(main)/chat/{(desktop)/features/ChatInput → (workspace)/@conversation/features/ChatInput/Desktop}/TextArea.tsx +2 -1
  14. package/src/app/(main)/chat/{(desktop)/features/ChatInput → (workspace)/@conversation/features/ChatInput/Desktop}/index.tsx +6 -2
  15. package/src/app/(main)/chat/{(mobile)/features/ChatInput → (workspace)/@conversation/features/ChatInput/Mobile}/index.tsx +9 -4
  16. package/src/app/(main)/chat/(workspace)/@topic/default.tsx +19 -0
  17. package/src/app/(main)/chat/{(desktop)/features/SideBar/SystemRole/index.tsx → (workspace)/@topic/features/SystemRole/SystemRoleContent.tsx} +2 -0
  18. package/src/app/(main)/chat/(workspace)/@topic/features/SystemRole/index.tsx +18 -0
  19. package/src/app/(main)/chat/{features → (workspace)/@topic/features}/TopicListContent/Header.tsx +2 -0
  20. package/src/app/(main)/chat/{features → (workspace)/@topic/features}/TopicListContent/Topic/index.tsx +26 -28
  21. package/src/app/(main)/chat/{features → (workspace)/@topic/features}/TopicListContent/TopicSearchBar/index.tsx +4 -2
  22. package/src/app/(main)/chat/{features → (workspace)/@topic/features}/TopicListContent/index.tsx +3 -4
  23. package/src/app/(main)/chat/{(desktop)/features → (workspace)/_layout/Desktop}/ChatHeader/HeaderAction.tsx +2 -0
  24. package/src/app/(main)/chat/{(desktop)/features → (workspace)/_layout/Desktop}/ChatHeader/Main.tsx +3 -1
  25. package/src/app/(main)/chat/{(desktop)/features → (workspace)/_layout/Desktop}/ChatHeader/Tags.tsx +1 -1
  26. package/src/app/(main)/chat/{(desktop)/features → (workspace)/_layout/Desktop}/ChatHeader/index.tsx +1 -2
  27. package/src/app/(main)/chat/{(desktop)/features → (workspace)/_layout/Desktop}/HotKeys.tsx +2 -0
  28. package/src/app/(main)/chat/{(desktop)/features/SideBar/index.tsx → (workspace)/_layout/Desktop/TopicPanel.tsx} +23 -20
  29. package/src/app/(main)/chat/(workspace)/_layout/Desktop/index.tsx +35 -0
  30. package/src/app/(main)/chat/(workspace)/_layout/Mobile/ChatHeader/index.tsx +35 -0
  31. package/src/app/(main)/chat/(workspace)/_layout/Mobile/TopicModal.tsx +26 -0
  32. package/src/app/(main)/chat/(workspace)/_layout/Mobile/index.tsx +21 -0
  33. package/src/app/(main)/chat/(workspace)/_layout/type.ts +7 -0
  34. package/src/app/(main)/chat/{features → (workspace)/features}/PluginTag/index.tsx +2 -0
  35. package/src/app/(main)/chat/{features → (workspace)/features}/SettingButton.tsx +3 -1
  36. package/src/app/(main)/chat/{features → (workspace)/features}/ShareButton/index.tsx +5 -6
  37. package/src/app/(main)/chat/{features/TelemetryNotification/index.tsx → (workspace)/features/TelemetryNotification.tsx} +2 -0
  38. package/src/app/(main)/chat/(workspace)/features/useWorkspaceModal.tsx +27 -0
  39. package/src/app/(main)/chat/(workspace)/layout.ts +11 -0
  40. package/src/app/(main)/chat/(workspace)/page.tsx +19 -0
  41. package/src/app/(main)/chat/@session/_layout/Desktop/PanelBody.tsx +22 -0
  42. package/src/app/(main)/chat/{_layout → @session/_layout}/Desktop/SessionHeader.tsx +2 -0
  43. package/src/app/(main)/chat/@session/_layout/Desktop/index.tsx +15 -0
  44. package/src/app/(main)/chat/{(mobile)/features → @session/_layout/Mobile}/SessionHeader.tsx +7 -20
  45. package/src/app/(main)/chat/@session/_layout/Mobile/index.tsx +19 -0
  46. package/src/app/(main)/chat/@session/default.tsx +23 -0
  47. package/src/app/(main)/chat/{components/SessionHydration/index.tsx → @session/features/SessionHydration.tsx} +1 -0
  48. package/src/app/(main)/chat/{features → @session/features}/SessionListContent/DefaultMode.tsx +3 -3
  49. package/src/app/(main)/chat/{features → @session/features}/SessionListContent/Inbox/index.tsx +2 -2
  50. package/src/app/(main)/chat/{features → @session/features}/SessionListContent/List/Item/index.tsx +2 -1
  51. package/src/app/(main)/chat/{features → @session/features}/SessionListContent/List/index.tsx +3 -4
  52. package/src/app/(main)/chat/{features → @session/features}/SessionListContent/ListItem/index.tsx +12 -10
  53. package/src/app/(main)/chat/{features → @session/features}/SessionListContent/SearchMode.tsx +4 -2
  54. package/src/app/(main)/chat/{features → @session/features}/SessionListContent/index.tsx +2 -0
  55. package/src/app/(main)/chat/{features/SessionSearchBar/index.tsx → @session/features/SessionSearchBar.tsx} +3 -5
  56. package/src/app/(main)/chat/_layout/Desktop/SessionPanel.tsx +79 -0
  57. package/src/app/(main)/chat/_layout/Desktop/index.tsx +11 -7
  58. package/src/app/(main)/chat/_layout/Mobile.tsx +52 -0
  59. package/src/app/(main)/chat/_layout/type.ts +1 -0
  60. package/src/app/(main)/chat/error.tsx +5 -0
  61. package/src/app/(main)/chat/features/Migration/index.tsx +3 -8
  62. package/src/app/(main)/chat/not-found.tsx +3 -0
  63. package/src/app/(main)/chat/settings/_layout/Mobile/Header.tsx +3 -4
  64. package/src/app/(main)/chat/settings/features/HeaderContent.tsx +2 -2
  65. package/src/app/(main)/chat/settings/features/SubmitAgentButton/index.tsx +2 -2
  66. package/src/app/(main)/market/@detail/features/Header.tsx +2 -2
  67. package/src/components/Cell/Divider.tsx +2 -2
  68. package/src/components/Cell/index.tsx +2 -2
  69. package/src/components/ModelTag/ModelIcon.tsx +2 -2
  70. package/src/components/StoreHydration/ChatHydration/index.tsx +2 -2
  71. package/src/const/session.ts +2 -0
  72. package/src/const/url.ts +5 -1
  73. package/src/features/ChatInput/ActionBar/Tools/index.tsx +3 -2
  74. package/src/features/ChatInput/STT/browser.tsx +2 -1
  75. package/src/features/ChatInput/STT/openai.tsx +2 -1
  76. package/src/features/ChatInput/useChatInput.ts +2 -1
  77. package/src/features/ChatInput/useSend.ts +2 -1
  78. package/src/features/Conversation/Actions/customAction.ts +9 -0
  79. package/src/features/Conversation/Extras/Assistant.tsx +2 -1
  80. package/src/features/Conversation/Extras/User.tsx +2 -1
  81. package/src/features/Conversation/components/AutoScroll.tsx +1 -1
  82. package/src/features/Conversation/components/ChatItem/index.tsx +22 -12
  83. package/src/features/Conversation/components/InboxWelcome/AgentsSuggest.tsx +1 -1
  84. package/src/features/Conversation/components/InboxWelcome/index.tsx +2 -2
  85. package/src/features/Conversation/components/SkeletonList.tsx +21 -8
  86. package/src/features/Conversation/components/VirtualizedList/index.tsx +19 -17
  87. package/src/features/Conversation/index.tsx +12 -31
  88. package/src/features/PluginStore/InstalledPluginList.tsx +28 -21
  89. package/src/features/PluginStore/OnlineList.tsx +4 -10
  90. package/src/features/PluginStore/PluginItem/Action.tsx +3 -2
  91. package/src/features/PluginStore/PluginItem/index.tsx +2 -0
  92. package/src/features/PluginStore/index.tsx +4 -2
  93. package/src/features/User/UserAvatar.tsx +2 -1
  94. package/src/features/User/UserPanel/useMenu.tsx +1 -1
  95. package/src/layout/GlobalProvider/AppTheme.tsx +7 -17
  96. package/src/libs/agent-runtime/openrouter/index.ts +4 -1
  97. package/src/store/chat/slices/message/action.test.ts +52 -2
  98. package/src/store/chat/slices/message/action.ts +40 -13
  99. package/src/store/chat/slices/message/initialState.ts +13 -1
  100. package/src/store/chat/slices/message/selectors.test.ts +1 -1
  101. package/src/store/chat/slices/message/selectors.ts +10 -1
  102. package/src/store/chat/slices/plugin/action.test.ts +10 -2
  103. package/src/store/chat/slices/plugin/action.ts +6 -2
  104. package/src/store/global/action.ts +2 -0
  105. package/src/store/global/initialState.ts +2 -0
  106. package/src/styles/global.ts +12 -9
  107. package/src/styles/mobileHeader.ts +1 -1
  108. package/src/app/(main)/(mobile)/me/features/AvatarBanner.tsx +0 -27
  109. package/src/app/(main)/(mobile)/me/features/style.ts +0 -29
  110. package/src/app/(main)/chat/(desktop)/features/Conversation.tsx +0 -19
  111. package/src/app/(main)/chat/(desktop)/index.tsx +0 -22
  112. package/src/app/(main)/chat/(mobile)/features/SessionList.tsx +0 -17
  113. package/src/app/(main)/chat/(mobile)/features/TopicList.tsx +0 -29
  114. package/src/app/(main)/chat/(mobile)/index.tsx +0 -26
  115. package/src/app/(main)/chat/(mobile)/mobile/ChatHeader/index.tsx +0 -56
  116. package/src/app/(main)/chat/(mobile)/mobile/page.tsx +0 -26
  117. package/src/app/(main)/chat/_layout/Desktop/SessionList.tsx +0 -39
  118. package/src/app/(main)/chat/_layout/Mobile/index.tsx +0 -9
  119. package/src/app/(main)/chat/page.tsx +0 -25
  120. package/src/features/FolderPanel/index.tsx +0 -60
  121. package/src/utils/screen.ts +0 -14
  122. /package/src/app/(main)/chat/{(desktop)/features/ChatInput → (workspace)/@conversation/features/ChatInput/Desktop}/Footer/DragUpload.tsx +0 -0
  123. /package/src/app/(main)/chat/{(desktop)/features/ChatInput → (workspace)/@conversation/features/ChatInput/Desktop}/Footer/LocalFiles.tsx +0 -0
  124. /package/src/app/(main)/chat/{(desktop)/features/ChatInput → (workspace)/@conversation/features/ChatInput/Desktop}/Footer/SendMore.tsx +0 -0
  125. /package/src/app/(main)/chat/{(desktop)/features/ChatInput → (workspace)/@conversation/features/ChatInput/Desktop}/Header/index.tsx +0 -0
  126. /package/src/app/(main)/chat/{(desktop)/features/ChatInput → (workspace)/@conversation/features/ChatInput/Desktop}/__tests__/useAutoFocus.test.ts +0 -0
  127. /package/src/app/(main)/chat/{(desktop)/features/ChatInput → (workspace)/@conversation/features/ChatInput/Desktop}/useAutoFocus.ts +0 -0
  128. /package/src/app/(main)/chat/{(mobile)/features/ChatInput → (workspace)/@conversation/features/ChatInput/Mobile}/Files.tsx +0 -0
  129. /package/src/app/(main)/chat/{(desktop)/features/SideBar → (workspace)/@topic/features}/SystemRole/style.ts +0 -0
  130. /package/src/app/(main)/chat/{features → (workspace)/@topic/features}/TopicListContent/Topic/DefaultContent.tsx +0 -0
  131. /package/src/app/(main)/chat/{features → (workspace)/@topic/features}/TopicListContent/Topic/SkeletonList.tsx +0 -0
  132. /package/src/app/(main)/chat/{features → (workspace)/@topic/features}/TopicListContent/Topic/TopicContent.tsx +0 -0
  133. /package/src/app/(main)/chat/{features → (workspace)/@topic/features}/TopicListContent/Topic/TopicItem.tsx +0 -0
  134. /package/src/app/(main)/chat/{(mobile)/mobile → (workspace)/_layout/Mobile}/ChatHeader/ChatHeaderTitle.tsx +0 -0
  135. /package/src/app/(main)/chat/{features → (workspace)/features}/PluginTag/PluginStatus.tsx +0 -0
  136. /package/src/app/(main)/chat/{features → (workspace)/features}/ShareButton/Preview.tsx +0 -0
  137. /package/src/app/(main)/chat/{features → (workspace)/features}/ShareButton/ShareModal.tsx +0 -0
  138. /package/src/app/(main)/chat/{features → (workspace)/features}/ShareButton/style.ts +0 -0
  139. /package/src/app/(main)/chat/{features → (workspace)/features}/ShareButton/type.ts +0 -0
  140. /package/src/app/(main)/chat/{features → (workspace)/features}/ShareButton/useScreenshot.ts +0 -0
  141. /package/src/app/(main)/chat/{features → @session/features}/SessionListContent/CollapseGroup/Actions.tsx +0 -0
  142. /package/src/app/(main)/chat/{features → @session/features}/SessionListContent/CollapseGroup/index.tsx +0 -0
  143. /package/src/app/(main)/chat/{features → @session/features}/SessionListContent/List/AddButton.tsx +0 -0
  144. /package/src/app/(main)/chat/{features → @session/features}/SessionListContent/List/Item/Actions.tsx +0 -0
  145. /package/src/app/(main)/chat/{features → @session/features}/SessionListContent/Modals/ConfigGroupModal/GroupItem.tsx +0 -0
  146. /package/src/app/(main)/chat/{features → @session/features}/SessionListContent/Modals/ConfigGroupModal/index.tsx +0 -0
  147. /package/src/app/(main)/chat/{features → @session/features}/SessionListContent/Modals/CreateGroupModal.tsx +0 -0
  148. /package/src/app/(main)/chat/{features → @session/features}/SessionListContent/Modals/RenameGroupModal.tsx +0 -0
  149. /package/src/app/(main)/chat/{features → @session/features}/SessionListContent/SkeletonList.tsx +0 -0
@@ -365,6 +365,31 @@ describe('chatMessage actions', () => {
365
365
  });
366
366
  });
367
367
 
368
+ describe('toggleMessageEditing action', () => {
369
+ it('should add message id to messageEditingIds when editing is true', () => {
370
+ const { result } = renderHook(() => useChatStore());
371
+ const messageId = 'message-id';
372
+
373
+ act(() => {
374
+ result.current.toggleMessageEditing(messageId, true);
375
+ });
376
+
377
+ expect(result.current.messageEditingIds).toContain(messageId);
378
+ });
379
+
380
+ it('should remove message id from messageEditingIds when editing is false', () => {
381
+ const { result } = renderHook(() => useChatStore());
382
+ const messageId = 'abc';
383
+
384
+ act(() => {
385
+ result.current.toggleMessageEditing(messageId, true);
386
+ result.current.toggleMessageEditing(messageId, false);
387
+ });
388
+
389
+ expect(result.current.messageEditingIds).not.toContain(messageId);
390
+ });
391
+ });
392
+
368
393
  describe('internal_resendMessage action', () => {
369
394
  it('should resend a message by id and refresh messages', async () => {
370
395
  const { result } = renderHook(() => useChatStore());
@@ -701,7 +726,7 @@ describe('chatMessage actions', () => {
701
726
 
702
727
  const state = useChatStore.getState();
703
728
  expect(state.abortController).toBeInstanceOf(AbortController);
704
- expect(state.chatLoadingId).toEqual('message-id');
729
+ expect(state.chatLoadingIds).toEqual(['message-id']);
705
730
  });
706
731
 
707
732
  it('should clear loading state and abort controller when loading is false', () => {
@@ -720,7 +745,7 @@ describe('chatMessage actions', () => {
720
745
 
721
746
  const state = useChatStore.getState();
722
747
  expect(state.abortController).toBeUndefined();
723
- expect(state.chatLoadingId).toBeUndefined();
748
+ expect(state.chatLoadingIds).toEqual([]);
724
749
  });
725
750
 
726
751
  it('should attach beforeunload event listener when loading starts', () => {
@@ -760,4 +785,29 @@ describe('chatMessage actions', () => {
760
785
  expect(state.abortController).toEqual(abortController);
761
786
  });
762
787
  });
788
+
789
+ describe('internal_toggleMessageLoading action', () => {
790
+ it('should add message id to messageLoadingIds when loading is true', () => {
791
+ const { result } = renderHook(() => useChatStore());
792
+ const messageId = 'message-id';
793
+
794
+ act(() => {
795
+ result.current.internal_toggleMessageLoading(true, messageId);
796
+ });
797
+
798
+ expect(result.current.messageLoadingIds).toContain(messageId);
799
+ });
800
+
801
+ it('should remove message id from messageLoadingIds when loading is false', () => {
802
+ const { result } = renderHook(() => useChatStore());
803
+ const messageId = 'ddd-id';
804
+
805
+ act(() => {
806
+ result.current.internal_toggleMessageLoading(true, messageId);
807
+ result.current.internal_toggleMessageLoading(false, messageId);
808
+ });
809
+
810
+ expect(result.current.messageLoadingIds).not.toContain(messageId);
811
+ });
812
+ });
763
813
  });
@@ -72,7 +72,7 @@ export interface ChatMessageAction {
72
72
  stopGenerateMessage: () => void;
73
73
  copyMessage: (id: string, content: string) => Promise<void>;
74
74
  refreshMessages: () => Promise<void>;
75
-
75
+ toggleMessageEditing: (id: string, editing: boolean) => void;
76
76
  // ========= ↓ Internal Method ↓ ========== //
77
77
  // ========================================== //
78
78
  // ========================================== //
@@ -137,6 +137,18 @@ const preventLeavingFn = (e: BeforeUnloadEvent) => {
137
137
  e.returnValue = '你有正在生成中的请求,确定要离开吗?';
138
138
  };
139
139
 
140
+ const toggleBooleanList = (ids: string[], id: string, loading: boolean) => {
141
+ return produce(ids, (draft) => {
142
+ if (loading) {
143
+ draft.push(id);
144
+ } else {
145
+ const index = draft.indexOf(id);
146
+
147
+ if (index >= 0) draft.splice(index, 1);
148
+ }
149
+ });
150
+ };
151
+
140
152
  export const chatMessage: StateCreator<
141
153
  ChatStore,
142
154
  [['zustand/devtools', never]],
@@ -244,7 +256,13 @@ export const chatMessage: StateCreator<
244
256
 
245
257
  get().internal_traceMessage(id, { eventType: TraceEventType.CopyMessage });
246
258
  },
247
-
259
+ toggleMessageEditing: (id, editing) => {
260
+ set(
261
+ { messageEditingIds: toggleBooleanList(get().messageEditingIds, id, editing) },
262
+ false,
263
+ 'toggleMessageEditing',
264
+ );
265
+ },
248
266
  stopGenerateMessage: () => {
249
267
  const { abortController, internal_toggleChatLoading } = get();
250
268
  if (!abortController) return;
@@ -518,11 +536,28 @@ export const chatMessage: StateCreator<
518
536
  window.addEventListener('beforeunload', preventLeavingFn);
519
537
 
520
538
  const abortController = new AbortController();
521
- set({ abortController, chatLoadingId: id }, false, action);
539
+ set(
540
+ {
541
+ abortController,
542
+ chatLoadingIds: toggleBooleanList(get().messageLoadingIds, id!, loading),
543
+ },
544
+ false,
545
+ action,
546
+ );
522
547
 
523
548
  return abortController;
524
549
  } else {
525
- set({ abortController: undefined, chatLoadingId: undefined }, false, action);
550
+ if (!id) {
551
+ set({ abortController: undefined, chatLoadingIds: [] }, false, action);
552
+ } else
553
+ set(
554
+ {
555
+ abortController: undefined,
556
+ chatLoadingIds: toggleBooleanList(get().messageLoadingIds, id, loading),
557
+ },
558
+ false,
559
+ action,
560
+ );
526
561
 
527
562
  window.removeEventListener('beforeunload', preventLeavingFn);
528
563
  }
@@ -530,15 +565,7 @@ export const chatMessage: StateCreator<
530
565
  internal_toggleMessageLoading: (loading, id) => {
531
566
  set(
532
567
  {
533
- messageLoadingIds: produce(get().messageLoadingIds, (draft) => {
534
- if (loading) {
535
- draft.push(id);
536
- } else {
537
- const index = draft.indexOf(id);
538
-
539
- if (index >= 0) draft.splice(index, 1);
540
- }
541
- }),
568
+ messageLoadingIds: toggleBooleanList(get().messageLoadingIds, id, loading),
542
569
  },
543
570
  false,
544
571
  'internal_toggleMessageLoading',
@@ -7,8 +7,18 @@ export interface ChatMessageState {
7
7
  * @description 当前正在编辑或查看的会话
8
8
  */
9
9
  activeId: string;
10
- chatLoadingId?: string;
10
+ /**
11
+ * is the AI message is generating
12
+ */
13
+ chatLoadingIds: string[];
11
14
  inputMessage: string;
15
+ /**
16
+ * is the message is editing
17
+ */
18
+ messageEditingIds: string[];
19
+ /**
20
+ * is the message is creating or updating in the service
21
+ */
12
22
  messageLoadingIds: string[];
13
23
  messages: ChatMessage[];
14
24
  /**
@@ -19,7 +29,9 @@ export interface ChatMessageState {
19
29
 
20
30
  export const initialMessageState: ChatMessageState = {
21
31
  activeId: 'inbox',
32
+ chatLoadingIds: [],
22
33
  inputMessage: '',
34
+ messageEditingIds: [],
23
35
  messageLoadingIds: [],
24
36
  messages: [],
25
37
  messagesInit: false,
@@ -107,7 +107,7 @@ describe('chatSelectors', () => {
107
107
  it('should return the properties of a function message', () => {
108
108
  const state = merge(initialStore, {
109
109
  messages: mockMessages,
110
- chatLoadingId: 'msg3', // Assuming this id represents a loading state
110
+ chatLoadingIds: ['msg3'], // Assuming this id represents a loading state
111
111
  });
112
112
  const props = chatSelectors.getFunctionMessageProps(mockMessages[2])(state);
113
113
  expect(props).toEqual({
@@ -123,7 +123,7 @@ const getFunctionMessageProps =
123
123
  command: plugin,
124
124
  content,
125
125
  id: plugin?.identifier,
126
- loading: id === s.chatLoadingId,
126
+ loading: s.chatLoadingIds.includes(id),
127
127
  type: plugin?.type as LobePluginType,
128
128
  });
129
129
 
@@ -134,6 +134,11 @@ const latestMessage = (s: ChatStore) => currentChats(s).at(-1);
134
134
 
135
135
  const currentChatLoadingState = (s: ChatStore) => !s.messagesInit;
136
136
 
137
+ const isMessageEditing = (id: string) => (s: ChatStore) => s.messageEditingIds.includes(id);
138
+ const isMessageLoading = (id: string) => (s: ChatStore) => s.messageLoadingIds.includes(id);
139
+ const isMessageGenerating = (id: string) => (s: ChatStore) => s.chatLoadingIds.includes(id);
140
+ const isAIGenerating = (s: ChatStore) => s.chatLoadingIds.length > 0;
141
+
137
142
  export const chatSelectors = {
138
143
  chatsMessageString,
139
144
  currentChatIDsWithGuideMessage,
@@ -145,6 +150,10 @@ export const chatSelectors = {
145
150
  getFunctionMessageProps,
146
151
  getMessageById,
147
152
  getTraceIdByMessageId,
153
+ isAIGenerating,
154
+ isMessageEditing,
155
+ isMessageGenerating,
156
+ isMessageLoading,
148
157
  latestMessage,
149
158
  showInboxWelcome,
150
159
  };
@@ -131,7 +131,11 @@ describe('ChatPluginAction', () => {
131
131
  });
132
132
  expect(storeState.refreshMessages).toHaveBeenCalled();
133
133
  expect(storeState.triggerAIMessage).toHaveBeenCalled();
134
- expect(storeState.internal_toggleChatLoading).toHaveBeenCalledWith(false);
134
+ expect(storeState.internal_toggleChatLoading).toHaveBeenCalledWith(
135
+ false,
136
+ 'message-id',
137
+ 'plugin/fetchPlugin/end',
138
+ );
135
139
  });
136
140
 
137
141
  it('should handle errors when the plugin API call fails', async () => {
@@ -159,7 +163,11 @@ describe('ChatPluginAction', () => {
159
163
  expect(chatService.runPluginApi).toHaveBeenCalledWith(pluginPayload, { trace: {} });
160
164
  expect(messageService.updateMessageError).toHaveBeenCalledWith(messageId, error);
161
165
  expect(storeState.refreshMessages).toHaveBeenCalled();
162
- expect(storeState.internal_toggleChatLoading).toHaveBeenCalledWith(false);
166
+ expect(storeState.internal_toggleChatLoading).toHaveBeenCalledWith(
167
+ false,
168
+ 'message-id',
169
+ 'plugin/fetchPlugin/end',
170
+ );
163
171
  expect(storeState.triggerAIMessage).not.toHaveBeenCalled(); // 确保在错误情况下不调用此方法
164
172
  });
165
173
  });
@@ -135,7 +135,11 @@ export const chatPlugin: StateCreator<
135
135
  let data: string;
136
136
 
137
137
  try {
138
- const abortController = internal_toggleChatLoading(true, id, n('fetchPlugin') as string);
138
+ const abortController = internal_toggleChatLoading(
139
+ true,
140
+ id,
141
+ n('fetchPlugin/start') as string,
142
+ );
139
143
 
140
144
  const message = chatSelectors.getMessageById(id)(get());
141
145
 
@@ -162,7 +166,7 @@ export const chatPlugin: StateCreator<
162
166
  data = '';
163
167
  }
164
168
 
165
- internal_toggleChatLoading(false);
169
+ internal_toggleChatLoading(false, id, n('fetchPlugin/end') as string);
166
170
  // 如果报错则结束了
167
171
  if (!data) return;
168
172
 
@@ -97,6 +97,8 @@ export const globalActionSlice: StateCreator<
97
97
  onSuccess: (preference) => {
98
98
  const nextPreference = merge(get().preference, preference);
99
99
 
100
+ set({ isPreferenceInit: true });
101
+
100
102
  if (isEqual(get().preference, nextPreference)) return;
101
103
 
102
104
  set({ preference: nextPreference }, false, n('initPreference'));
@@ -50,6 +50,7 @@ export interface GlobalPreferenceState {
50
50
  export interface GlobalCommonState {
51
51
  hasNewVersion?: boolean;
52
52
  isMobile?: boolean;
53
+ isPreferenceInit?: boolean;
53
54
  latestVersion?: string;
54
55
  router?: AppRouterInstance;
55
56
  sidebarKey: SidebarTabKey;
@@ -59,6 +60,7 @@ export type GlobalState = GlobalCommonState & GlobalPreferenceState;
59
60
 
60
61
  export const initialState: GlobalState = {
61
62
  isMobile: false,
63
+ isPreferenceInit: false,
62
64
  preference: {
63
65
  expandSessionGroupKeys: [SessionDefaultGroup.Pinned, SessionDefaultGroup.Default],
64
66
  inputHeight: 200,
@@ -1,22 +1,25 @@
1
1
  import { Theme, css } from 'antd-style';
2
2
 
3
- export default ({ prefixCls, token }: { prefixCls: string; token: Theme }) => css`
3
+ // fix ios input keyboard
4
+ // overflow: hidden;
5
+ // ref: https://zhuanlan.zhihu.com/p/113855026
6
+ export default ({ token }: { prefixCls: string; token: Theme }) => css`
4
7
  html,
5
8
  body,
6
- #__next,
7
- .${prefixCls}-app {
9
+ #__next {
8
10
  position: relative;
11
+
9
12
  overscroll-behavior: none;
10
- height: 100% !important;
11
- max-height: 100dvh !important;
13
+
14
+ height: 100%;
15
+ min-height: 100dvh;
16
+ max-height: 100dvh;
17
+
18
+ background: ${token.colorBgLayout};
12
19
  }
13
20
 
14
21
  * {
15
22
  scrollbar-color: ${token.colorFill} transparent;
16
23
  scrollbar-width: thin;
17
24
  }
18
-
19
- p {
20
- margin-bottom: 0;
21
- }
22
25
  `;
@@ -8,7 +8,7 @@ export const mobileHeaderSticky: CSSProperties = {
8
8
  };
9
9
 
10
10
  export const mobileHeaderFixed: CSSProperties = {
11
- position: 'fixed',
11
+ position: 'absolute',
12
12
  top: 0,
13
13
  width: '100%',
14
14
  zIndex: 100,
@@ -1,27 +0,0 @@
1
- 'use client';
2
-
3
- import { PropsWithChildren, memo } from 'react';
4
- import { Flexbox } from 'react-layout-kit';
5
-
6
- import UserAvatar from '@/features/User/UserAvatar';
7
-
8
- import { useStyles } from './style';
9
-
10
- export const AVATAR_SIZE = 80;
11
-
12
- const AvatarBanner = memo<PropsWithChildren>(({ children }) => {
13
- const { styles } = useStyles();
14
-
15
- return (
16
- <>
17
- <Flexbox align={'center'} className={styles.bannerBox} justify={'center'}>
18
- <div className={styles.bannerImg}>
19
- <UserAvatar shape={'square'} size={AVATAR_SIZE} />
20
- </div>
21
- </Flexbox>
22
- <Flexbox className={styles.info}>{children}</Flexbox>
23
- </>
24
- );
25
- });
26
-
27
- export default AvatarBanner;
@@ -1,29 +0,0 @@
1
- import { createStyles } from 'antd-style';
2
-
3
- export const useStyles = createStyles(({ css, token, isDarkMode }) => ({
4
- bannerBox: css`
5
- position: relative;
6
-
7
- overflow: hidden;
8
- flex: none;
9
-
10
- width: 100%;
11
- height: 100px;
12
-
13
- background: ${token.colorFill};
14
- `,
15
- bannerImg: css`
16
- position: absolute;
17
- scale: 8;
18
- filter: blur(6px) saturate(2);
19
- `,
20
- info: css`
21
- position: relative;
22
-
23
- margin-top: ${-token.borderRadiusLG}px;
24
-
25
- background: ${isDarkMode ? token.colorBgLayout : token.colorBgContainer};
26
- border-top-left-radius: ${token.borderRadiusLG}px;
27
- border-top-right-radius: ${token.borderRadiusLG}px;
28
- `,
29
- }));
@@ -1,19 +0,0 @@
1
- import { memo } from 'react';
2
-
3
- import RawConversation from '@/features/Conversation';
4
-
5
- import TelemetryNotification from '../../features/TelemetryNotification';
6
- import ChatInput from './ChatInput';
7
- import HotKeys from './HotKeys';
8
-
9
- const Conversation = memo(() => {
10
- return (
11
- <>
12
- <RawConversation chatInput={<ChatInput />} />
13
- <HotKeys />
14
- <TelemetryNotification />
15
- </>
16
- );
17
- });
18
-
19
- export default Conversation;
@@ -1,22 +0,0 @@
1
- 'use client';
2
-
3
- import { memo } from 'react';
4
- import { Flexbox } from 'react-layout-kit';
5
-
6
- import ClientResponsiveContent from '@/components/client/ClientResponsiveContent';
7
-
8
- import ChatHeader from './features/ChatHeader';
9
- import Conversation from './features/Conversation';
10
- import SideBar from './features/SideBar';
11
-
12
- const Desktop = memo(() => (
13
- <>
14
- <ChatHeader />
15
- <Flexbox flex={1} height={'calc(100% - 64px)'} horizontal>
16
- <Conversation />
17
- <SideBar />
18
- </Flexbox>
19
- </>
20
- ));
21
-
22
- export default ClientResponsiveContent({ Desktop, Mobile: () => import('../(mobile)') });
@@ -1,17 +0,0 @@
1
- import { memo } from 'react';
2
-
3
- import SessionListContent from '../../features/SessionListContent';
4
- import SessionSearchBar from '../../features/SessionSearchBar';
5
-
6
- const Sessions = memo(() => {
7
- return (
8
- <>
9
- <div style={{ padding: '8px 16px' }}>
10
- <SessionSearchBar mobile />
11
- </div>
12
- <SessionListContent />
13
- </>
14
- );
15
- });
16
-
17
- export default Sessions;
@@ -1,29 +0,0 @@
1
- import { Modal } from '@lobehub/ui';
2
- import { memo } from 'react';
3
- import { useTranslation } from 'react-i18next';
4
-
5
- import { useGlobalStore } from '@/store/global';
6
-
7
- import TopicListContent from '../../features/TopicListContent';
8
-
9
- const Topics = memo(() => {
10
- const [showAgentSettings, toggleConfig] = useGlobalStore((s) => [
11
- s.preference.mobileShowTopic,
12
- s.toggleMobileTopic,
13
- ]);
14
-
15
- const { t } = useTranslation('chat');
16
-
17
- return (
18
- <Modal
19
- allowFullscreen
20
- onCancel={() => toggleConfig(false)}
21
- open={showAgentSettings}
22
- title={t('topic.title')}
23
- >
24
- <TopicListContent mobile />
25
- </Modal>
26
- );
27
- });
28
-
29
- export default Topics;
@@ -1,26 +0,0 @@
1
- 'use client';
2
-
3
- import { useRouter } from 'next/navigation';
4
- import { memo, useEffect } from 'react';
5
-
6
- import MobileContentLayout from '@/components/server/MobileNavLayout';
7
-
8
- import SessionHeader from './features/SessionHeader';
9
- import SessionList from './features/SessionList';
10
-
11
- const ChatMobilePage = memo(() => {
12
- const router = useRouter();
13
-
14
- useEffect(() => {
15
- router.prefetch('/chat/mobile');
16
- router.prefetch('/settings');
17
- }, []);
18
-
19
- return (
20
- <MobileContentLayout header={<SessionHeader />} withNav>
21
- <SessionList />
22
- </MobileContentLayout>
23
- );
24
- });
25
-
26
- export default ChatMobilePage;
@@ -1,56 +0,0 @@
1
- import { MobileNavBar } from '@lobehub/ui';
2
- import { useRouter } from 'next/navigation';
3
- import { memo, useState } from 'react';
4
-
5
- import { featureFlagsSelectors, useServerConfigStore } from '@/store/serverConfig';
6
-
7
- import SettingButton from '../../../features/SettingButton';
8
- import ShareButton from '../../../features/ShareButton';
9
- import ChatHeaderTitle from './ChatHeaderTitle';
10
-
11
- const MobileHeader = memo(() => {
12
- const router = useRouter();
13
- const [open, setOpen] = useState(false);
14
-
15
- const { isAgentEditable } = useServerConfigStore(featureFlagsSelectors);
16
-
17
- // const items: MenuProps['items'] = [
18
- // {
19
- // icon: <Icon icon={Share2} />,
20
- // key: 'share',
21
- // label: t('share', { ns: 'common' }),
22
- // onClick: () => setOpen(true),
23
- // },
24
- // !isInbox && {
25
- // icon: <Icon icon={Settings} />,
26
- // key: 'settings',
27
- // label: t('header.session', { ns: 'setting' }),
28
- // onClick: () => router.push(pathString('/chat/settings', { hash: location.hash })),
29
- // },
30
- // ].filter(Boolean) as MenuProps['items'];
31
-
32
- return (
33
- <MobileNavBar
34
- center={<ChatHeaderTitle />}
35
- onBackClick={() => router.push('/chat')}
36
- right={
37
- <>
38
- <ShareButton mobile open={open} setOpen={setOpen} />
39
- {isAgentEditable && <SettingButton mobile />}
40
- {/*<Dropdown*/}
41
- {/* menu={{*/}
42
- {/* items,*/}
43
- {/* }}*/}
44
- {/* trigger={['click']}*/}
45
- {/*>*/}
46
- {/* <ActionIcon icon={MoreHorizontal} />*/}
47
- {/*</Dropdown>*/}
48
- </>
49
- }
50
- showBackButton
51
- style={{ width: '100%' }}
52
- />
53
- );
54
- });
55
-
56
- export default MobileHeader;
@@ -1,26 +0,0 @@
1
- 'use client';
2
-
3
- import dynamic from 'next/dynamic';
4
- import { memo } from 'react';
5
-
6
- import MobileContentLayout from '@/components/server/MobileNavLayout';
7
- import Conversation from '@/features/Conversation';
8
-
9
- import SessionHydration from '../../components/SessionHydration';
10
- import TelemetryNotification from '../../features/TelemetryNotification';
11
- import ChatInput from '../features/ChatInput';
12
- import ChatHeader from './ChatHeader';
13
-
14
- const TopicList = dynamic(() => import('../features/TopicList'));
15
-
16
- const Chat = memo(() => {
17
- return (
18
- <MobileContentLayout header={<ChatHeader />}>
19
- <Conversation chatInput={<ChatInput />} mobile />
20
- <TopicList />
21
- <TelemetryNotification mobile />
22
- <SessionHydration />
23
- </MobileContentLayout>
24
- );
25
- });
26
- export default Chat;
@@ -1,39 +0,0 @@
1
- 'use client';
2
-
3
- import { DraggablePanelBody } from '@lobehub/ui';
4
- import { createStyles } from 'antd-style';
5
- import { memo } from 'react';
6
-
7
- import FolderPanel from '@/features/FolderPanel';
8
-
9
- import SessionListContent from '../../features/SessionListContent';
10
- import Header from './SessionHeader';
11
-
12
- const useStyles = createStyles(({ stylish, css, cx }) =>
13
- cx(
14
- stylish.noScrollbar,
15
- css`
16
- display: flex;
17
- flex-direction: column;
18
- gap: 2px;
19
- padding: 8px 8px 0;
20
- `,
21
- ),
22
- );
23
-
24
- const Sessions = memo(() => {
25
- const { styles } = useStyles();
26
-
27
- return (
28
- <FolderPanel>
29
- <Header />
30
- <DraggablePanelBody className={styles}>
31
- <SessionListContent />
32
- </DraggablePanelBody>
33
- </FolderPanel>
34
- );
35
- });
36
-
37
- Sessions.displayName = 'SessionsList';
38
-
39
- export default Sessions;
@@ -1,9 +0,0 @@
1
- import { LayoutProps } from '../type';
2
-
3
- const Layout = ({ children }: LayoutProps) => {
4
- return children;
5
- };
6
-
7
- Layout.displayName = 'MobileChatLayout';
8
-
9
- export default Layout;