@lobehub/chat 1.33.5 → 1.34.1

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 (205) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/changelog/v1.json +18 -0
  3. package/locales/ar/chat.json +7 -0
  4. package/locales/ar/common.json +2 -0
  5. package/locales/ar/models.json +24 -0
  6. package/locales/ar/setting.json +5 -0
  7. package/locales/ar/thread.json +5 -0
  8. package/locales/bg-BG/chat.json +7 -0
  9. package/locales/bg-BG/common.json +2 -0
  10. package/locales/bg-BG/models.json +24 -0
  11. package/locales/bg-BG/setting.json +5 -0
  12. package/locales/bg-BG/thread.json +5 -0
  13. package/locales/de-DE/chat.json +7 -0
  14. package/locales/de-DE/common.json +2 -0
  15. package/locales/de-DE/models.json +24 -0
  16. package/locales/de-DE/setting.json +5 -0
  17. package/locales/de-DE/thread.json +5 -0
  18. package/locales/en-US/chat.json +7 -0
  19. package/locales/en-US/common.json +2 -0
  20. package/locales/en-US/models.json +24 -0
  21. package/locales/en-US/setting.json +5 -0
  22. package/locales/en-US/thread.json +5 -0
  23. package/locales/es-ES/chat.json +7 -0
  24. package/locales/es-ES/common.json +2 -0
  25. package/locales/es-ES/models.json +24 -0
  26. package/locales/es-ES/setting.json +5 -0
  27. package/locales/es-ES/thread.json +5 -0
  28. package/locales/fa-IR/chat.json +7 -0
  29. package/locales/fa-IR/common.json +2 -0
  30. package/locales/fa-IR/models.json +24 -0
  31. package/locales/fa-IR/setting.json +5 -0
  32. package/locales/fa-IR/thread.json +5 -0
  33. package/locales/fr-FR/chat.json +7 -0
  34. package/locales/fr-FR/common.json +2 -0
  35. package/locales/fr-FR/models.json +24 -0
  36. package/locales/fr-FR/setting.json +5 -0
  37. package/locales/fr-FR/thread.json +5 -0
  38. package/locales/it-IT/chat.json +7 -0
  39. package/locales/it-IT/common.json +2 -0
  40. package/locales/it-IT/models.json +24 -0
  41. package/locales/it-IT/setting.json +5 -0
  42. package/locales/it-IT/thread.json +5 -0
  43. package/locales/ja-JP/chat.json +7 -0
  44. package/locales/ja-JP/common.json +2 -0
  45. package/locales/ja-JP/models.json +24 -0
  46. package/locales/ja-JP/setting.json +5 -0
  47. package/locales/ja-JP/thread.json +5 -0
  48. package/locales/ko-KR/chat.json +7 -0
  49. package/locales/ko-KR/common.json +2 -0
  50. package/locales/ko-KR/models.json +24 -0
  51. package/locales/ko-KR/setting.json +5 -0
  52. package/locales/ko-KR/thread.json +5 -0
  53. package/locales/nl-NL/chat.json +7 -0
  54. package/locales/nl-NL/common.json +2 -0
  55. package/locales/nl-NL/models.json +24 -0
  56. package/locales/nl-NL/setting.json +5 -0
  57. package/locales/nl-NL/thread.json +5 -0
  58. package/locales/pl-PL/chat.json +7 -0
  59. package/locales/pl-PL/common.json +2 -0
  60. package/locales/pl-PL/models.json +24 -0
  61. package/locales/pl-PL/setting.json +5 -0
  62. package/locales/pl-PL/thread.json +5 -0
  63. package/locales/pt-BR/chat.json +7 -0
  64. package/locales/pt-BR/common.json +2 -0
  65. package/locales/pt-BR/models.json +24 -0
  66. package/locales/pt-BR/setting.json +5 -0
  67. package/locales/pt-BR/thread.json +5 -0
  68. package/locales/ru-RU/chat.json +7 -0
  69. package/locales/ru-RU/common.json +2 -0
  70. package/locales/ru-RU/models.json +24 -0
  71. package/locales/ru-RU/setting.json +5 -0
  72. package/locales/ru-RU/thread.json +5 -0
  73. package/locales/tr-TR/chat.json +7 -0
  74. package/locales/tr-TR/common.json +2 -0
  75. package/locales/tr-TR/models.json +24 -0
  76. package/locales/tr-TR/setting.json +5 -0
  77. package/locales/tr-TR/thread.json +5 -0
  78. package/locales/vi-VN/chat.json +7 -0
  79. package/locales/vi-VN/common.json +2 -0
  80. package/locales/vi-VN/models.json +24 -0
  81. package/locales/vi-VN/setting.json +5 -0
  82. package/locales/vi-VN/thread.json +5 -0
  83. package/locales/zh-CN/chat.json +7 -0
  84. package/locales/zh-CN/common.json +2 -0
  85. package/locales/zh-CN/models.json +24 -0
  86. package/locales/zh-CN/setting.json +5 -0
  87. package/locales/zh-CN/thread.json +5 -0
  88. package/locales/zh-TW/chat.json +7 -0
  89. package/locales/zh-TW/common.json +2 -0
  90. package/locales/zh-TW/models.json +24 -0
  91. package/locales/zh-TW/setting.json +5 -0
  92. package/locales/zh-TW/thread.json +5 -0
  93. package/package.json +1 -1
  94. package/src/app/(main)/chat/(workspace)/@conversation/default.tsx +2 -0
  95. package/src/app/(main)/chat/(workspace)/@conversation/features/ChatHydration/index.tsx +11 -2
  96. package/src/{features → app/(main)/chat/(workspace)/@conversation/features}/ChatInput/Desktop/Footer/index.tsx +7 -9
  97. package/src/app/(main)/chat/(workspace)/@conversation/features/ChatInput/Desktop/index.tsx +7 -2
  98. package/src/app/(main)/chat/(workspace)/@conversation/features/ChatList/ChatItem/Thread.tsx +62 -0
  99. package/src/app/(main)/chat/(workspace)/@conversation/features/ChatList/ChatItem/ThreadItem.tsx +68 -0
  100. package/src/app/(main)/chat/(workspace)/@conversation/features/ChatList/ChatItem/index.tsx +62 -2
  101. package/src/app/(main)/chat/(workspace)/@conversation/features/ThreadHydration.tsx +47 -0
  102. package/src/app/(main)/chat/(workspace)/@portal/_layout/Desktop.tsx +3 -2
  103. package/src/app/(main)/chat/(workspace)/@portal/_layout/Mobile.tsx +47 -6
  104. package/src/app/(main)/chat/(workspace)/@topic/features/SkeletonList.tsx +3 -2
  105. package/src/app/(main)/chat/(workspace)/@topic/features/TopicListContent/ByTimeMode/index.tsx +10 -3
  106. package/src/app/(main)/chat/(workspace)/@topic/features/TopicListContent/FlatMode/index.tsx +1 -1
  107. package/src/app/(main)/chat/(workspace)/@topic/features/TopicListContent/ThreadItem/Content.tsx +164 -0
  108. package/src/app/(main)/chat/(workspace)/@topic/features/TopicListContent/ThreadItem/index.tsx +98 -0
  109. package/src/app/(main)/chat/(workspace)/@topic/features/TopicListContent/{TopicItem.tsx → TopicItem/index.tsx} +33 -22
  110. package/src/app/(main)/chat/(workspace)/_layout/Desktop/Portal.tsx +12 -5
  111. package/src/app/(main)/chat/(workspace)/_layout/Mobile/index.tsx +1 -2
  112. package/src/const/message.ts +2 -0
  113. package/src/const/settings/systemAgent.ts +1 -0
  114. package/src/database/server/migrations/0012_add_thread.sql +39 -0
  115. package/src/database/server/migrations/meta/0012_snapshot.json +3671 -0
  116. package/src/database/server/migrations/meta/_journal.json +7 -0
  117. package/src/database/server/models/_template.ts +2 -2
  118. package/src/database/server/models/message.ts +1 -0
  119. package/src/database/server/models/thread.ts +79 -0
  120. package/src/database/server/schemas/lobechat/message.ts +2 -1
  121. package/src/database/server/schemas/lobechat/relations.ts +13 -1
  122. package/src/database/server/schemas/lobechat/topic.ts +30 -1
  123. package/src/database/server/utils/idGenerator.ts +1 -0
  124. package/src/features/ChatInput/ActionBar/Token/TokenTag.tsx +6 -4
  125. package/src/features/ChatInput/ActionBar/Token/index.tsx +24 -5
  126. package/src/features/ChatInput/ActionBar/config.ts +3 -2
  127. package/src/features/ChatInput/Desktop/index.tsx +15 -7
  128. package/src/features/ChatInput/Mobile/index.tsx +4 -4
  129. package/src/features/Conversation/Actions/Assistant.tsx +24 -5
  130. package/src/features/Conversation/Actions/User.tsx +21 -4
  131. package/src/features/Conversation/Actions/index.ts +1 -66
  132. package/src/features/Conversation/Messages/{Tool → Assistant/ToolCallItem}/Inspector/index.tsx +3 -1
  133. package/src/features/Conversation/Messages/{Tool/index.tsx → Assistant/ToolCallItem/Tool.tsx} +10 -11
  134. package/src/features/Conversation/Messages/Assistant/ToolCallItem/index.tsx +5 -3
  135. package/src/features/Conversation/Messages/Assistant/index.tsx +22 -14
  136. package/src/features/Conversation/Messages/index.ts +0 -2
  137. package/src/features/Conversation/components/AutoScroll.tsx +1 -1
  138. package/src/features/Conversation/components/ChatItem/ActionsBar.tsx +79 -5
  139. package/src/features/Conversation/components/ChatItem/InPortalThreadContext.ts +3 -0
  140. package/src/features/Conversation/components/ChatItem/index.tsx +16 -5
  141. package/src/features/Conversation/components/MarkdownElements/LobeArtifact/Render/index.tsx +9 -1
  142. package/src/features/Conversation/components/ThreadDivider/index.tsx +19 -0
  143. package/src/features/Conversation/hooks/useChatListActionsBar.tsx +19 -4
  144. package/src/features/Portal/Thread/Chat/ChatInput/Footer.tsx +90 -0
  145. package/src/features/Portal/Thread/Chat/ChatInput/TextArea.tsx +30 -0
  146. package/src/features/Portal/Thread/Chat/ChatInput/index.tsx +66 -0
  147. package/src/features/Portal/Thread/Chat/ChatInput/useSend.ts +50 -0
  148. package/src/features/Portal/Thread/Chat/ChatItem.tsx +62 -0
  149. package/src/features/Portal/Thread/Chat/ChatList.tsx +49 -0
  150. package/src/features/Portal/Thread/Chat/ThreadDivider/index.tsx +19 -0
  151. package/src/features/Portal/Thread/Chat/index.tsx +28 -0
  152. package/src/features/Portal/Thread/Header/Active.tsx +35 -0
  153. package/src/features/Portal/Thread/Header/New.tsx +37 -0
  154. package/src/features/Portal/Thread/Header/Title.tsx +18 -0
  155. package/src/features/Portal/Thread/Header/index.tsx +20 -0
  156. package/src/features/Portal/Thread/hook.ts +8 -0
  157. package/src/features/Portal/Thread/index.ts +12 -0
  158. package/src/features/Portal/router.tsx +2 -1
  159. package/src/hooks/useFetchTopics.ts +7 -1
  160. package/src/locales/default/chat.ts +8 -1
  161. package/src/locales/default/common.ts +3 -0
  162. package/src/locales/default/index.ts +2 -0
  163. package/src/locales/default/setting.ts +5 -0
  164. package/src/locales/default/thread.ts +5 -0
  165. package/src/server/modules/AgentRuntime/index.test.ts +10 -0
  166. package/src/server/modules/AgentRuntime/index.ts +1 -1
  167. package/src/server/routers/lambda/index.ts +2 -0
  168. package/src/server/routers/lambda/thread.ts +83 -0
  169. package/src/services/thread.ts +54 -0
  170. package/src/store/chat/initialState.ts +3 -0
  171. package/src/store/chat/selectors.ts +2 -1
  172. package/src/store/chat/slices/aiChat/actions/__tests__/generateAIChat.test.ts +1 -1
  173. package/src/store/chat/slices/aiChat/actions/__tests__/rag.test.ts +1 -1
  174. package/src/store/chat/slices/aiChat/actions/generateAIChat.ts +31 -8
  175. package/src/store/chat/slices/aiChat/actions/rag.ts +1 -1
  176. package/src/store/chat/slices/message/selectors.test.ts +3 -3
  177. package/src/store/chat/slices/message/selectors.ts +50 -29
  178. package/src/store/chat/slices/plugin/action.ts +26 -8
  179. package/src/store/chat/slices/portal/action.ts +1 -0
  180. package/src/store/chat/slices/portal/initialState.ts +1 -0
  181. package/src/store/chat/slices/portal/selectors/thread.ts +17 -0
  182. package/src/store/chat/slices/portal/selectors.ts +2 -0
  183. package/src/store/chat/slices/thread/action.ts +326 -0
  184. package/src/store/chat/slices/thread/initialState.ts +34 -0
  185. package/src/store/chat/slices/thread/reducer.ts +48 -0
  186. package/src/store/chat/slices/thread/selectors/index.ts +202 -0
  187. package/src/store/chat/slices/thread/selectors/util.ts +22 -0
  188. package/src/store/chat/slices/topic/action.ts +5 -1
  189. package/src/store/chat/store.ts +5 -2
  190. package/src/store/global/initialState.ts +4 -0
  191. package/src/store/global/selectors.ts +4 -0
  192. package/src/store/user/slices/settings/selectors/systemAgent.ts +2 -0
  193. package/src/types/message/index.ts +17 -1
  194. package/src/types/topic/index.ts +1 -0
  195. package/src/types/topic/thread.ts +42 -0
  196. package/src/types/user/settings/systemAgent.ts +1 -0
  197. package/src/app/(main)/chat/(workspace)/@portal/features/Header.tsx +0 -11
  198. package/src/app/(main)/chat/(workspace)/_layout/Mobile/PortalModal.tsx +0 -35
  199. /package/src/{features → app/(main)/chat/(workspace)/@conversation/features}/ChatInput/Desktop/Footer/SendMore.tsx +0 -0
  200. /package/src/{features → app/(main)/chat/(workspace)/@conversation/features}/ChatInput/Desktop/Footer/ShortcutHint.tsx +0 -0
  201. /package/src/app/(main)/chat/(workspace)/@topic/features/TopicListContent/{DefaultContent.tsx → TopicItem/DefaultContent.tsx} +0 -0
  202. /package/src/app/(main)/chat/(workspace)/@topic/features/TopicListContent/{TopicContent.tsx → TopicItem/TopicContent.tsx} +0 -0
  203. /package/src/features/Conversation/Messages/{Tool → Assistant/ToolCallItem}/Inspector/PluginResultJSON.tsx +0 -0
  204. /package/src/features/Conversation/Messages/{Tool → Assistant/ToolCallItem}/Inspector/Settings.tsx +0 -0
  205. /package/src/features/Conversation/Messages/{Tool → Assistant/ToolCallItem}/Inspector/style.ts +0 -0
@@ -1,7 +1,9 @@
1
- import { ReactNode, memo } from 'react';
1
+ import { Skeleton } from 'antd';
2
+ import { ReactNode, Suspense, memo, useContext } from 'react';
2
3
  import { Flexbox } from 'react-layout-kit';
3
4
 
4
5
  import { LOADING_FLAT } from '@/const/message';
6
+ import { InPortalThreadContext } from '@/features/Conversation/components/ChatItem/InPortalThreadContext';
5
7
  import { useChatStore } from '@/store/chat';
6
8
  import { chatSelectors } from '@/store/chat/selectors';
7
9
  import { ChatMessage } from '@/types/message';
@@ -18,6 +20,7 @@ export const AssistantMessage = memo<
18
20
  const editing = useChatStore(chatSelectors.isMessageEditing(id));
19
21
  const generating = useChatStore(chatSelectors.isMessageGenerating(id));
20
22
 
23
+ const inThread = useContext(InPortalThreadContext);
21
24
  const isToolCallGenerating = generating && (content === LOADING_FLAT || !content) && !!tools;
22
25
 
23
26
  return editing ? (
@@ -40,19 +43,24 @@ export const AssistantMessage = memo<
40
43
  />
41
44
  )}
42
45
  {tools && (
43
- <Flexbox gap={8}>
44
- {tools.map((toolCall, index) => (
45
- <ToolCall
46
- apiName={toolCall.apiName}
47
- arguments={toolCall.arguments}
48
- id={toolCall.id}
49
- identifier={toolCall.identifier}
50
- index={index}
51
- key={toolCall.id}
52
- messageId={id}
53
- />
54
- ))}
55
- </Flexbox>
46
+ <Suspense
47
+ fallback={<Skeleton.Button active style={{ height: 46, minWidth: 200, width: '100%' }} />}
48
+ >
49
+ <Flexbox gap={8}>
50
+ {tools.map((toolCall, index) => (
51
+ <ToolCall
52
+ apiName={toolCall.apiName}
53
+ arguments={toolCall.arguments}
54
+ id={toolCall.id}
55
+ identifier={toolCall.identifier}
56
+ index={index}
57
+ key={toolCall.id}
58
+ messageId={id}
59
+ showPortal={!inThread}
60
+ />
61
+ ))}
62
+ </Flexbox>
63
+ </Suspense>
56
64
  )}
57
65
  </Flexbox>
58
66
  );
@@ -8,14 +8,12 @@ import { sessionSelectors } from '@/store/session/selectors';
8
8
  import { MarkdownCustomRender, RenderBelowMessage, RenderMessage } from '../types';
9
9
  import { AssistantMessage } from './Assistant';
10
10
  import { DefaultBelowMessage, DefaultMessage } from './Default';
11
- import { ToolMessage } from './Tool';
12
11
  import { UserBelowMessage, UserMarkdownRender, UserMessage } from './User';
13
12
 
14
13
  export const renderMessages: Record<string, RenderMessage> = {
15
14
  assistant: AssistantMessage,
16
15
  default: DefaultMessage,
17
16
  function: DefaultMessage,
18
- tool: ToolMessage,
19
17
  user: UserMessage,
20
18
  };
21
19
 
@@ -12,7 +12,7 @@ interface AutoScrollProps {
12
12
  }
13
13
  const AutoScroll = memo<AutoScrollProps>(({ atBottom, isScrolling, onScrollToBottom }) => {
14
14
  const trackVisibility = useChatStore(chatSelectors.isAIGenerating);
15
- const str = useChatStore(chatSelectors.chatsMessageString);
15
+ const str = useChatStore(chatSelectors.mainAIChatsMessageString);
16
16
 
17
17
  useEffect(() => {
18
18
  if (atBottom && trackVisibility && !isScrolling) {
@@ -1,18 +1,21 @@
1
1
  import { ActionEvent, ActionIconGroup, type ActionIconGroupProps } from '@lobehub/ui';
2
+ import { App } from 'antd';
2
3
  import isEqual from 'fast-deep-equal';
3
4
  import { memo, useCallback } from 'react';
5
+ import { useTranslation } from 'react-i18next';
4
6
 
5
7
  import { useChatStore } from '@/store/chat';
6
8
  import { chatSelectors } from '@/store/chat/selectors';
7
9
  import { MessageRoleType } from '@/types/message';
8
10
 
9
- import { renderActions, useActionsClick } from '../../Actions';
11
+ import { renderActions } from '../../Actions';
10
12
  import { useChatListActionsBar } from '../../hooks/useChatListActionsBar';
11
13
 
12
14
  export type ActionsBarProps = ActionIconGroupProps;
13
15
 
14
16
  const ActionsBar = memo<ActionsBarProps>((props) => {
15
17
  const { regenerate, edit, copy, divider, del } = useChatListActionsBar();
18
+
16
19
  return (
17
20
  <ActionIconGroup
18
21
  dropdownMenu={[edit, copy, regenerate, divider, del]}
@@ -25,12 +28,36 @@ const ActionsBar = memo<ActionsBarProps>((props) => {
25
28
 
26
29
  interface ActionsProps {
27
30
  id: string;
31
+ inPortalThread?: boolean;
28
32
  }
29
33
 
30
- const Actions = memo<ActionsProps>(({ id }) => {
34
+ const Actions = memo<ActionsProps>(({ id, inPortalThread }) => {
31
35
  const item = useChatStore(chatSelectors.getMessageById(id), isEqual);
32
- const [toggleMessageEditing] = useChatStore((s) => [s.toggleMessageEditing]);
33
- const onActionsClick = useActionsClick();
36
+ const { t } = useTranslation('common');
37
+ const [
38
+ deleteMessage,
39
+ regenerateMessage,
40
+ translateMessage,
41
+ ttsMessage,
42
+ delAndRegenerateMessage,
43
+ copyMessage,
44
+ openThreadCreator,
45
+ resendThreadMessage,
46
+ delAndResendThreadMessage,
47
+ toggleMessageEditing,
48
+ ] = useChatStore((s) => [
49
+ s.deleteMessage,
50
+ s.regenerateMessage,
51
+ s.translateMessage,
52
+ s.ttsMessage,
53
+ s.delAndRegenerateMessage,
54
+ s.copyMessage,
55
+ s.openThreadCreator,
56
+ s.resendThreadMessage,
57
+ s.delAndResendThreadMessage,
58
+ s.toggleMessageEditing,
59
+ ]);
60
+ const { message } = App.useApp();
34
61
 
35
62
  const handleActionClick = useCallback(
36
63
  async (action: ActionEvent) => {
@@ -41,7 +68,54 @@ const Actions = memo<ActionsProps>(({ id }) => {
41
68
  }
42
69
  if (!item) return;
43
70
 
44
- onActionsClick(action, item);
71
+ switch (action.key) {
72
+ case 'copy': {
73
+ await copyMessage(id, item.content);
74
+ message.success(t('copySuccess', { defaultValue: 'Copy Success' }));
75
+ break;
76
+ }
77
+ case 'branching': {
78
+ openThreadCreator(id);
79
+ break;
80
+ }
81
+
82
+ case 'del': {
83
+ deleteMessage(id);
84
+ break;
85
+ }
86
+
87
+ case 'regenerate': {
88
+ if (inPortalThread) {
89
+ resendThreadMessage(id);
90
+ } else regenerateMessage(id);
91
+
92
+ // if this message is an error message, we need to delete it
93
+ if (item.error) deleteMessage(id);
94
+ break;
95
+ }
96
+
97
+ case 'delAndRegenerate': {
98
+ if (inPortalThread) {
99
+ delAndResendThreadMessage(id);
100
+ } else {
101
+ delAndRegenerateMessage(id);
102
+ }
103
+ break;
104
+ }
105
+
106
+ case 'tts': {
107
+ ttsMessage(id);
108
+ break;
109
+ }
110
+ }
111
+
112
+ if (action.keyPath.at(-1) === 'translate') {
113
+ // click the menu item with translate item, the result is:
114
+ // key: 'en-US'
115
+ // keyPath: ['en-US','translate']
116
+ const lang = action.keyPath[0];
117
+ translateMessage(id, lang);
118
+ }
45
119
  },
46
120
  [item],
47
121
  );
@@ -0,0 +1,3 @@
1
+ import { createContext } from 'react';
2
+
3
+ export const InPortalThreadContext = createContext(false);
@@ -25,6 +25,7 @@ import {
25
25
  } from '../../Messages';
26
26
  import History from '../History';
27
27
  import { markdownElements } from '../MarkdownElements';
28
+ import { InPortalThreadContext } from './InPortalThreadContext';
28
29
  import { processWithArtifact } from './utils';
29
30
 
30
31
  const rehypePlugins = markdownElements.map((element) => element.rehypePlugin);
@@ -45,14 +46,24 @@ const useStyles = createStyles(({ css, prefixCls }) => ({
45
46
  export interface ChatListItemProps {
46
47
  actionBar?: ReactNode;
47
48
  className?: string;
49
+ disableEditing?: boolean;
48
50
  enableHistoryDivider?: boolean;
49
51
  endRender?: ReactNode;
50
52
  id: string;
53
+ inPortalThread?: boolean;
51
54
  index: number;
52
55
  }
53
56
 
54
57
  const Item = memo<ChatListItemProps>(
55
- ({ className, enableHistoryDivider, id, actionBar, endRender }) => {
58
+ ({
59
+ className,
60
+ enableHistoryDivider,
61
+ id,
62
+ actionBar,
63
+ endRender,
64
+ disableEditing,
65
+ inPortalThread = false,
66
+ }) => {
56
67
  const fontSize = useUserStore(userGeneralSettingsSelectors.fontSize);
57
68
  const { t } = useTranslation('common');
58
69
  const { styles, cx } = useStyles();
@@ -169,13 +180,13 @@ const Item = memo<ChatListItemProps>(
169
180
 
170
181
  const onDoubleClick = useCallback<MouseEventHandler<HTMLDivElement>>(
171
182
  (e) => {
172
- if (!item) return;
183
+ if (!item || disableEditing) return;
173
184
  if (item.id === 'default' || item.error) return;
174
185
  if (item.role && ['assistant', 'user'].includes(item.role) && e.altKey) {
175
186
  toggleMessageEditing(id, true);
176
187
  }
177
188
  },
178
- [item],
189
+ [item, disableEditing],
179
190
  );
180
191
 
181
192
  const text = useMemo(
@@ -197,7 +208,7 @@ const Item = memo<ChatListItemProps>(
197
208
 
198
209
  return (
199
210
  item && (
200
- <>
211
+ <InPortalThreadContext.Provider value={inPortalThread}>
201
212
  {enableHistoryDivider && <History />}
202
213
  <Flexbox className={cx(styles.message, className, isMessageLoading && styles.loading)}>
203
214
  <ChatItem
@@ -225,7 +236,7 @@ const Item = memo<ChatListItemProps>(
225
236
  />
226
237
  {endRender}
227
238
  </Flexbox>
228
- </>
239
+ </InPortalThreadContext.Provider>
229
240
  )
230
241
  );
231
242
  },
@@ -1,7 +1,8 @@
1
1
  import { Icon } from '@lobehub/ui';
2
+ import { App } from 'antd';
2
3
  import { createStyles } from 'antd-style';
3
4
  import { Loader2 } from 'lucide-react';
4
- import { memo, useEffect } from 'react';
5
+ import { memo, useContext, useEffect } from 'react';
5
6
  import { useTranslation } from 'react-i18next';
6
7
  import { Center, Flexbox } from 'react-layout-kit';
7
8
 
@@ -9,6 +10,7 @@ import { useChatStore } from '@/store/chat';
9
10
  import { chatPortalSelectors, chatSelectors } from '@/store/chat/selectors';
10
11
  import { dotLoading } from '@/styles/loading';
11
12
 
13
+ import { InPortalThreadContext } from '../../../ChatItem/InPortalThreadContext';
12
14
  import { MarkdownElementProps } from '../../type';
13
15
  import ArtifactIcon from './Icon';
14
16
 
@@ -60,6 +62,8 @@ const Render = memo<ArtifactProps>(({ identifier, title, type, language, childre
60
62
  const hasChildren = !!children;
61
63
  const str = ((children as string) || '').toString?.();
62
64
 
65
+ const inThread = useContext(InPortalThreadContext);
66
+ const { message } = App.useApp();
63
67
  const [isGenerating, isArtifactTagClosed, currentArtifactMessageId, openArtifact, closeArtifact] =
64
68
  useChatStore((s) => {
65
69
  return [
@@ -90,6 +94,10 @@ const Render = memo<ArtifactProps>(({ identifier, title, type, language, childre
90
94
  if (currentArtifactMessageId === id) {
91
95
  closeArtifact();
92
96
  } else {
97
+ if (inThread) {
98
+ message.info(t('artifact.inThread'));
99
+ return;
100
+ }
93
101
  openArtifactUI();
94
102
  }
95
103
  }}
@@ -0,0 +1,19 @@
1
+ import { Icon, Tag } from '@lobehub/ui';
2
+ import { Divider } from 'antd';
3
+ import { GitBranch } from 'lucide-react';
4
+ import { memo } from 'react';
5
+ import { useTranslation } from 'react-i18next';
6
+
7
+ const ThreadDivider = memo(() => {
8
+ const { t } = useTranslation('chat');
9
+
10
+ return (
11
+ <div style={{ padding: '0 20px' }}>
12
+ <Divider style={{ margin: 0, padding: '20px 0' }}>
13
+ <Tag icon={<Icon icon={GitBranch} />}>{t('thread.divider')}</Tag>
14
+ </Divider>
15
+ </div>
16
+ );
17
+ });
18
+
19
+ export default ThreadDivider;
@@ -1,9 +1,12 @@
1
1
  import { ActionIconGroupItems } from '@lobehub/ui/es/ActionIconGroup';
2
- import { Copy, Edit, ListRestart, RotateCcw, Trash } from 'lucide-react';
2
+ import { Copy, Edit, ListRestart, RotateCcw, Split, Trash } from 'lucide-react';
3
3
  import { useMemo } from 'react';
4
4
  import { useTranslation } from 'react-i18next';
5
5
 
6
+ import { isServerMode } from '@/const/version';
7
+
6
8
  interface ChatListActionsBar {
9
+ branching: ActionIconGroupItems;
7
10
  copy: ActionIconGroupItems;
8
11
  del: ActionIconGroupItems;
9
12
  delAndRegenerate: ActionIconGroupItems;
@@ -12,11 +15,21 @@ interface ChatListActionsBar {
12
15
  regenerate: ActionIconGroupItems;
13
16
  }
14
17
 
15
- export const useChatListActionsBar = (): ChatListActionsBar => {
18
+ export const useChatListActionsBar = ({
19
+ hasThread,
20
+ }: { hasThread?: boolean } = {}): ChatListActionsBar => {
16
21
  const { t } = useTranslation('common');
17
22
 
18
23
  return useMemo(
19
24
  () => ({
25
+ branching: {
26
+ disable: !isServerMode,
27
+ icon: Split,
28
+ key: 'branching',
29
+ label: isServerMode
30
+ ? t('branching', { defaultValue: 'Create Sub Topic' })
31
+ : t('branchingDisable'),
32
+ },
20
33
  copy: {
21
34
  icon: Copy,
22
35
  key: 'copy',
@@ -24,11 +37,13 @@ export const useChatListActionsBar = (): ChatListActionsBar => {
24
37
  },
25
38
  del: {
26
39
  danger: true,
40
+ disable: hasThread,
27
41
  icon: Trash,
28
42
  key: 'del',
29
- label: t('delete', { defaultValue: 'Delete' }),
43
+ label: hasThread ? t('messageAction.deleteDisabledByThreads', { ns: 'chat' }) : t('delete'),
30
44
  },
31
45
  delAndRegenerate: {
46
+ disable: hasThread,
32
47
  icon: ListRestart,
33
48
  key: 'delAndRegenerate',
34
49
  label: t('messageAction.delAndRegenerate', {
@@ -50,6 +65,6 @@ export const useChatListActionsBar = (): ChatListActionsBar => {
50
65
  label: t('regenerate', { defaultValue: 'Regenerate' }),
51
66
  },
52
67
  }),
53
- [],
68
+ [hasThread],
54
69
  );
55
70
  };
@@ -0,0 +1,90 @@
1
+ import { Button } from 'antd';
2
+ import { createStyles } from 'antd-style';
3
+ import { rgba } from 'polished';
4
+ import { memo } from 'react';
5
+ import { useTranslation } from 'react-i18next';
6
+ import { Flexbox } from 'react-layout-kit';
7
+
8
+ import StopLoadingIcon from '@/components/StopLoading';
9
+ import { useChatStore } from '@/store/chat';
10
+ import { threadSelectors } from '@/store/chat/selectors';
11
+
12
+ import { useSendThreadMessage } from './useSend';
13
+
14
+ const useStyles = createStyles(({ css, prefixCls, token }) => {
15
+ return {
16
+ loadingButton: css`
17
+ display: flex;
18
+ align-items: center;
19
+ `,
20
+ overrideAntdIcon: css`
21
+ .${prefixCls}-btn.${prefixCls}-btn-icon-only {
22
+ display: flex;
23
+ align-items: center;
24
+ justify-content: center;
25
+ }
26
+
27
+ .${prefixCls}-btn.${prefixCls}-dropdown-trigger {
28
+ &::before {
29
+ background-color: ${rgba(token.colorBgLayout, 0.1)} !important;
30
+ }
31
+ }
32
+ `,
33
+ };
34
+ });
35
+
36
+ interface FooterProps {
37
+ onExpandChange: (expand: boolean) => void;
38
+ }
39
+
40
+ const Footer = memo<FooterProps>(({ onExpandChange }) => {
41
+ const { t } = useTranslation('chat');
42
+
43
+ const { styles } = useStyles();
44
+
45
+ const [isAIGenerating, stopGenerateMessage] = useChatStore((s) => [
46
+ threadSelectors.isThreadAIGenerating(s),
47
+ s.stopGenerateMessage,
48
+ ]);
49
+
50
+ const { send: sendMessage, canSend } = useSendThreadMessage();
51
+
52
+ return (
53
+ <Flexbox
54
+ align={'end'}
55
+ className={styles.overrideAntdIcon}
56
+ distribution={'space-between'}
57
+ flex={'none'}
58
+ gap={8}
59
+ horizontal
60
+ padding={'0 24px'}
61
+ >
62
+ <div />
63
+ {isAIGenerating ? (
64
+ <Button
65
+ className={styles.loadingButton}
66
+ icon={<StopLoadingIcon />}
67
+ onClick={stopGenerateMessage}
68
+ >
69
+ {t('input.stop')}
70
+ </Button>
71
+ ) : (
72
+ <Button
73
+ disabled={!canSend}
74
+ loading={!canSend}
75
+ onClick={() => {
76
+ sendMessage();
77
+ onExpandChange?.(false);
78
+ }}
79
+ type={'primary'}
80
+ >
81
+ {t('input.send')}
82
+ </Button>
83
+ )}
84
+ </Flexbox>
85
+ );
86
+ });
87
+
88
+ Footer.displayName = 'Footer';
89
+
90
+ export default Footer;
@@ -0,0 +1,30 @@
1
+ import { memo } from 'react';
2
+
3
+ import InputArea from '@/features/ChatInput/Desktop/InputArea';
4
+ import { useChatStore } from '@/store/chat';
5
+ import { chatSelectors } from '@/store/chat/selectors';
6
+
7
+ import { useSendThreadMessage } from './useSend';
8
+
9
+ const TextArea = memo<{ onSend?: () => void }>(({ onSend }) => {
10
+ const [loading, value, updateInputMessage] = useChatStore((s) => [
11
+ chatSelectors.isAIGenerating(s),
12
+ s.threadInputMessage,
13
+ s.updateThreadInputMessage,
14
+ ]);
15
+ const { send: sendMessage } = useSendThreadMessage();
16
+
17
+ return (
18
+ <InputArea
19
+ loading={loading}
20
+ onChange={updateInputMessage}
21
+ onSend={() => {
22
+ sendMessage();
23
+ onSend?.();
24
+ }}
25
+ value={value}
26
+ />
27
+ );
28
+ });
29
+
30
+ export default TextArea;
@@ -0,0 +1,66 @@
1
+ 'use client';
2
+
3
+ import { Alert } from '@lobehub/ui';
4
+ import Link from 'next/link';
5
+ import { memo } from 'react';
6
+
7
+ import { ActionKeys } from '@/features/ChatInput/ActionBar/config';
8
+ import DesktopChatInput, { FooterRender } from '@/features/ChatInput/Desktop';
9
+ import { useGlobalStore } from '@/store/global';
10
+ import { systemStatusSelectors } from '@/store/global/selectors';
11
+
12
+ import Footer from './Footer';
13
+ import TextArea from './TextArea';
14
+
15
+ const leftActions = ['stt', 'portalToken'] as ActionKeys[];
16
+
17
+ const rightActions = [] as ActionKeys[];
18
+
19
+ const renderTextArea = (onSend: () => void) => <TextArea onSend={onSend} />;
20
+ const renderFooter: FooterRender = (props) => <Footer {...props} />;
21
+
22
+ const Desktop = memo(() => {
23
+ const [inputHeight, hideThreadLimitAlert, updateSystemStatus] = useGlobalStore((s) => [
24
+ systemStatusSelectors.threadInputHeight(s),
25
+ systemStatusSelectors.systemStatus(s).hideThreadLimitAlert,
26
+ s.updateSystemStatus,
27
+ ]);
28
+
29
+ return (
30
+ <>
31
+ {!hideThreadLimitAlert && (
32
+ <Alert
33
+ banner
34
+ closable
35
+ message={
36
+ <div>
37
+ 子话题暂不支持文件/图片上传,如有需求,欢迎留言:
38
+ <Link
39
+ href={'https://github.com/lobehub/lobe-chat/discussions/4717'}
40
+ style={{ textDecoration: 'underline' }}
41
+ >
42
+ 💬 讨论
43
+ </Link>
44
+ </div>
45
+ }
46
+ onClose={() => {
47
+ updateSystemStatus({ hideThreadLimitAlert: true });
48
+ }}
49
+ type={'info'}
50
+ />
51
+ )}
52
+ <DesktopChatInput
53
+ inputHeight={inputHeight}
54
+ leftActions={leftActions}
55
+ onInputHeightChange={(height) => {
56
+ updateSystemStatus({ threadInputHeight: height });
57
+ }}
58
+ renderFooter={renderFooter}
59
+ renderTextArea={renderTextArea}
60
+ rightActions={rightActions}
61
+ />
62
+ </>
63
+ );
64
+ });
65
+
66
+ export default Desktop;
@@ -0,0 +1,50 @@
1
+ import { useCallback, useMemo } from 'react';
2
+
3
+ import { useChatStore } from '@/store/chat';
4
+ import { threadSelectors } from '@/store/chat/selectors';
5
+ import { SendMessageParams } from '@/types/message';
6
+
7
+ export type UseSendMessageParams = Pick<
8
+ SendMessageParams,
9
+ 'onlyAddUserMessage' | 'isWelcomeQuestion'
10
+ >;
11
+
12
+ export const useSendThreadMessage = () => {
13
+ const [sendMessage, updateInputMessage] = useChatStore((s) => [
14
+ s.sendThreadMessage,
15
+ s.updateThreadInputMessage,
16
+ ]);
17
+
18
+ const isSendButtonDisabledByMessage = useChatStore(threadSelectors.isSendButtonDisabledByMessage);
19
+
20
+ const canSend = !isSendButtonDisabledByMessage;
21
+
22
+ const send = useCallback((params: UseSendMessageParams = {}) => {
23
+ const store = useChatStore.getState();
24
+ if (threadSelectors.isThreadAIGenerating(store)) return;
25
+
26
+ const isSendButtonDisabledByMessage = threadSelectors.isSendButtonDisabledByMessage(
27
+ useChatStore.getState(),
28
+ );
29
+
30
+ const canSend = !isSendButtonDisabledByMessage;
31
+ if (!canSend) return;
32
+
33
+ // if there is no message and no image, then we should not send the message
34
+ if (!store.threadInputMessage) return;
35
+
36
+ sendMessage({ message: store.threadInputMessage, ...params });
37
+
38
+ updateInputMessage('');
39
+
40
+ // const hasSystemRole = agentSelectors.hasSystemRole(useAgentStore.getState());
41
+ // const agentSetting = useAgentStore.getState().agentSettingInstance;
42
+
43
+ // // if there is a system role, then we need to use agent setting instance to autocomplete agent meta
44
+ // if (hasSystemRole && !!agentSetting) {
45
+ // agentSetting.autocompleteAllMeta();
46
+ // }
47
+ }, []);
48
+
49
+ return useMemo(() => ({ canSend, send }), [canSend]);
50
+ };