@lobehub/chat 1.123.4 → 1.124.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 (126) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/changelog/v1.json +9 -0
  3. package/locales/ar/chat.json +6 -2
  4. package/locales/ar/editor.json +47 -0
  5. package/locales/bg-BG/chat.json +6 -2
  6. package/locales/bg-BG/editor.json +47 -0
  7. package/locales/de-DE/chat.json +6 -2
  8. package/locales/de-DE/editor.json +47 -0
  9. package/locales/en-US/chat.json +6 -2
  10. package/locales/en-US/editor.json +47 -0
  11. package/locales/es-ES/chat.json +6 -2
  12. package/locales/es-ES/editor.json +47 -0
  13. package/locales/es-ES/models.json +3 -1
  14. package/locales/fa-IR/chat.json +6 -2
  15. package/locales/fa-IR/editor.json +47 -0
  16. package/locales/fr-FR/chat.json +6 -2
  17. package/locales/fr-FR/editor.json +47 -0
  18. package/locales/it-IT/chat.json +6 -2
  19. package/locales/it-IT/editor.json +47 -0
  20. package/locales/ja-JP/chat.json +6 -2
  21. package/locales/ja-JP/editor.json +47 -0
  22. package/locales/ko-KR/chat.json +6 -2
  23. package/locales/ko-KR/editor.json +47 -0
  24. package/locales/ko-KR/models.json +3 -1
  25. package/locales/nl-NL/chat.json +6 -2
  26. package/locales/nl-NL/editor.json +47 -0
  27. package/locales/nl-NL/models.json +3 -1
  28. package/locales/pl-PL/chat.json +6 -2
  29. package/locales/pl-PL/editor.json +47 -0
  30. package/locales/pt-BR/chat.json +6 -2
  31. package/locales/pt-BR/editor.json +47 -0
  32. package/locales/ru-RU/chat.json +6 -2
  33. package/locales/ru-RU/editor.json +47 -0
  34. package/locales/tr-TR/chat.json +6 -2
  35. package/locales/tr-TR/editor.json +47 -0
  36. package/locales/vi-VN/chat.json +6 -2
  37. package/locales/vi-VN/editor.json +47 -0
  38. package/locales/zh-CN/chat.json +6 -2
  39. package/locales/zh-CN/editor.json +47 -0
  40. package/locales/zh-TW/chat.json +6 -2
  41. package/locales/zh-TW/editor.json +47 -0
  42. package/locales/zh-TW/models.json +3 -1
  43. package/next.config.ts +4 -0
  44. package/package.json +4 -2
  45. package/packages/const/src/layoutTokens.ts +1 -0
  46. package/packages/types/src/index.ts +1 -0
  47. package/packages/utils/src/index.ts +1 -0
  48. package/src/app/[variants]/(main)/chat/(workspace)/@conversation/features/ChatInput/Desktop/{Footer/MessageFromUrl.tsx → MessageFromUrl.tsx} +3 -2
  49. package/src/app/[variants]/(main)/chat/(workspace)/@conversation/features/ChatInput/Desktop/index.tsx +129 -28
  50. package/src/app/[variants]/(main)/chat/(workspace)/@conversation/features/ChatInput/Mobile/index.tsx +44 -66
  51. package/src/app/[variants]/(main)/chat/(workspace)/@conversation/features/ChatInput/useSend.ts +141 -0
  52. package/src/app/[variants]/(main)/chat/(workspace)/@conversation/features/ChatList/Content.tsx +7 -1
  53. package/src/app/[variants]/(main)/chat/(workspace)/@conversation/features/ChatList/WelcomeChatItem/InboxWelcome/QuestionSuggest.tsx +3 -2
  54. package/src/app/[variants]/(main)/chat/(workspace)/@conversation/features/ChatList/WelcomeChatItem/OpeningQuestions.tsx +3 -2
  55. package/src/app/[variants]/(main)/chat/(workspace)/_layout/Desktop/ChatHeader/HeaderAction.tsx +18 -2
  56. package/src/features/ChatInput/ActionBar/STT/common.tsx +41 -47
  57. package/src/features/ChatInput/{Topic → ActionBar/SaveTopic}/index.tsx +15 -4
  58. package/src/features/ChatInput/ActionBar/Typo/index.tsx +22 -0
  59. package/src/features/ChatInput/ActionBar/components/Action.tsx +4 -0
  60. package/src/features/ChatInput/ActionBar/config.ts +7 -1
  61. package/src/features/ChatInput/ActionBar/index.tsx +40 -51
  62. package/src/features/ChatInput/ChatInputProvider.tsx +54 -0
  63. package/src/features/ChatInput/Desktop/FilePreview/FileItem/index.tsx +20 -11
  64. package/src/features/ChatInput/Desktop/FilePreview/FileList.tsx +16 -15
  65. package/src/features/ChatInput/Desktop/index.tsx +81 -68
  66. package/src/features/ChatInput/InputEditor/index.tsx +134 -0
  67. package/src/{app/[variants]/(main)/chat/(workspace)/@conversation/features/ChatInput/Mobile/Files → features/ChatInput/Mobile/FilePreview}/FileItem/File.tsx +1 -2
  68. package/src/features/ChatInput/Mobile/FilePreview/index.tsx +44 -0
  69. package/src/features/ChatInput/Mobile/index.tsx +72 -0
  70. package/src/features/ChatInput/SendArea/ExpandButton.tsx +30 -0
  71. package/src/features/ChatInput/SendArea/SendButton.tsx +29 -0
  72. package/src/features/ChatInput/SendArea/ShortcutHint.tsx +52 -0
  73. package/src/features/ChatInput/SendArea/index.tsx +36 -0
  74. package/src/features/ChatInput/StoreUpdater.tsx +41 -0
  75. package/src/features/ChatInput/TypoBar/index.tsx +139 -0
  76. package/src/features/ChatInput/hooks/useChatInputEditor.ts +36 -0
  77. package/src/features/ChatInput/index.ts +7 -0
  78. package/src/features/ChatInput/store/action.ts +75 -0
  79. package/src/features/ChatInput/store/index.ts +23 -0
  80. package/src/features/ChatInput/store/initialState.ts +54 -0
  81. package/src/features/ChatInput/store/selectors.ts +5 -0
  82. package/src/features/Conversation/components/BackBottom/style.ts +1 -1
  83. package/src/features/Conversation/components/SkeletonList.tsx +10 -3
  84. package/src/features/Conversation/components/VirtualizedList/index.tsx +53 -44
  85. package/src/features/Conversation/components/WideScreenContainer/index.tsx +43 -0
  86. package/src/features/Portal/Thread/Chat/ChatInput/index.tsx +49 -42
  87. package/src/features/Portal/Thread/Chat/ChatInput/useSend.ts +48 -22
  88. package/src/features/Portal/Thread/Chat/index.tsx +2 -2
  89. package/src/features/Portal/Thread/Header/index.tsx +1 -1
  90. package/src/hooks/useHotkeys/chatScope.ts +5 -3
  91. package/src/layout/GlobalProvider/Editor.tsx +27 -0
  92. package/src/layout/GlobalProvider/Locale.tsx +3 -23
  93. package/src/locales/default/chat.ts +7 -2
  94. package/src/locales/default/editor.ts +47 -0
  95. package/src/locales/default/index.ts +2 -0
  96. package/src/services/aiChat.ts +8 -2
  97. package/src/store/chat/slices/aiChat/actions/__tests__/generateAIChatV2.test.ts +42 -33
  98. package/src/store/chat/slices/aiChat/actions/generateAIChatV2.ts +174 -35
  99. package/src/store/chat/slices/aiChat/initialState.ts +19 -0
  100. package/src/store/chat/slices/aiChat/selectors.ts +18 -0
  101. package/src/store/global/action.test.ts +6 -5
  102. package/src/store/global/actions/__tests__/general.test.ts +6 -6
  103. package/src/store/global/actions/workspacePane.ts +6 -0
  104. package/src/store/global/initialState.ts +2 -4
  105. package/src/store/global/selectors/systemStatus.test.ts +1 -2
  106. package/src/store/global/selectors/systemStatus.ts +2 -5
  107. package/src/app/[variants]/(main)/chat/(workspace)/@conversation/features/ChatInput/Desktop/Footer/SendMore.tsx +0 -104
  108. package/src/app/[variants]/(main)/chat/(workspace)/@conversation/features/ChatInput/Desktop/Footer/ShortcutHint.tsx +0 -40
  109. package/src/app/[variants]/(main)/chat/(workspace)/@conversation/features/ChatInput/Desktop/Footer/index.tsx +0 -125
  110. package/src/app/[variants]/(main)/chat/(workspace)/@conversation/features/ChatInput/Desktop/TextArea.test.tsx +0 -332
  111. package/src/app/[variants]/(main)/chat/(workspace)/@conversation/features/ChatInput/Desktop/TextArea.tsx +0 -29
  112. package/src/app/[variants]/(main)/chat/(workspace)/@conversation/features/ChatInput/Mobile/Files/index.tsx +0 -33
  113. package/src/app/[variants]/(main)/chat/(workspace)/@conversation/features/ChatInput/Mobile/InputArea/Container.tsx +0 -41
  114. package/src/app/[variants]/(main)/chat/(workspace)/@conversation/features/ChatInput/Mobile/InputArea/index.tsx +0 -156
  115. package/src/app/[variants]/(main)/chat/(workspace)/@conversation/features/ChatInput/Mobile/Send.tsx +0 -33
  116. package/src/features/ChatInput/Desktop/Header/index.tsx +0 -30
  117. package/src/features/ChatInput/Desktop/InputArea/index.tsx +0 -143
  118. package/src/features/ChatInput/Desktop/__tests__/useAutoFocus.test.ts +0 -45
  119. package/src/features/ChatInput/Desktop/useAutoFocus.ts +0 -13
  120. package/src/features/ChatInput/useSend.ts +0 -102
  121. package/src/features/Portal/Thread/Chat/ChatInput/Footer.tsx +0 -90
  122. package/src/features/Portal/Thread/Chat/ChatInput/TextArea.tsx +0 -30
  123. package/src/libs/trpc/client/types.ts +0 -18
  124. /package/src/{app/[variants]/(main)/chat/(workspace)/@conversation/features/ChatInput/Mobile/Files → features/ChatInput/Mobile/FilePreview}/FileItem/Image.tsx +0 -0
  125. /package/src/{app/[variants]/(main)/chat/(workspace)/@conversation/features/ChatInput/Mobile/Files → features/ChatInput/Mobile/FilePreview}/FileItem/index.tsx +0 -0
  126. /package/src/{app/[variants]/(main)/chat/(workspace)/@conversation/features/ChatInput/Mobile/Files → features/ChatInput/Mobile/FilePreview}/FileItem/style.ts +0 -0
@@ -72,8 +72,8 @@
72
72
  "addUser": "新增一條使用者訊息",
73
73
  "more": "更多",
74
74
  "send": "發送",
75
- "sendWithCmdEnter": "按 {{meta}} + Enter 鍵發送",
76
- "sendWithEnter": "按 Enter 鍵發送",
75
+ "sendWithCmdEnter": "按 <key/> 鍵發送",
76
+ "sendWithEnter": "按 <key/> 鍵發送",
77
77
  "stop": "停止",
78
78
  "warp": "換行"
79
79
  },
@@ -232,6 +232,10 @@
232
232
  "threadMessageCount": "{{messageCount}} 條消息",
233
233
  "title": "子話題"
234
234
  },
235
+ "toggleWideScreen": {
236
+ "off": "關閉寬螢幕模式",
237
+ "on": "開啟寬螢幕模式"
238
+ },
235
239
  "tokenDetails": {
236
240
  "chats": "聊天訊息",
237
241
  "historySummary": "歷史總結",
@@ -0,0 +1,47 @@
1
+ {
2
+ "actions": {
3
+ "expand": {
4
+ "off": "收合",
5
+ "on": "展開"
6
+ },
7
+ "typobar": {
8
+ "off": "隱藏格式工具列",
9
+ "on": "顯示格式工具列"
10
+ }
11
+ },
12
+ "file": {
13
+ "error": "錯誤:{{message}}",
14
+ "uploading": "正在上傳檔案..."
15
+ },
16
+ "image": {
17
+ "broken": "圖片損毀"
18
+ },
19
+ "link": {
20
+ "edit": "編輯連結",
21
+ "open": "開啟連結",
22
+ "placeholder": "輸入連結 URL",
23
+ "unlink": "取消連結"
24
+ },
25
+ "table": {
26
+ "delete": "刪除表格",
27
+ "deleteColumn": "刪除列",
28
+ "deleteRow": "刪除行",
29
+ "insertColumnLeft": "在左側插入 {{count}} 列",
30
+ "insertColumnRight": "在右側插入 {{count}} 列",
31
+ "insertRowAbove": "在上方插入 {{count}} 行",
32
+ "insertRowBelow": "在下方插入 {{count}} 行"
33
+ },
34
+ "typobar": {
35
+ "blockquote": "引用",
36
+ "bold": "粗體",
37
+ "bulletList": "無序清單",
38
+ "code": "行內程式碼",
39
+ "codeblock": "程式碼區塊",
40
+ "italic": "斜體",
41
+ "link": "連結",
42
+ "numberList": "有序清單",
43
+ "strikethrough": "刪除線",
44
+ "table": "插入表格",
45
+ "underline": "底線"
46
+ }
47
+ }
@@ -1505,7 +1505,9 @@
1505
1505
  "gpt-4.1-nano": {
1506
1506
  "description": "GPT-4.1 mini 提供了智能、速度和成本之間的平衡,使其成為許多用例中具吸引力的模型。"
1507
1507
  },
1508
- "gpt-4.5-preview": "GPT-4.5-preview 是最新的通用模型,具備深厚的世界知識並能更好地理解使用者意圖,擅長創意任務與代理規劃。此模型的知識截至 2023 年 10 月。",
1508
+ "gpt-4.5-preview": {
1509
+ "description": "GPT-4.5-preview 是最新的通用模型,擁有深厚的世界知識和對使用者意圖的更佳理解,擅長創意任務與代理規劃。該模型的知識截止至2023年10月。"
1510
+ },
1509
1511
  "gpt-4o": {
1510
1512
  "description": "ChatGPT-4o是一款動態模型,實時更新以保持當前最新版本。它結合了強大的語言理解與生成能力,適合於大規模應用場景,包括客戶服務、教育和技術支持。"
1511
1513
  },
package/next.config.ts CHANGED
@@ -22,6 +22,9 @@ const standaloneConfig: NextConfig = {
22
22
 
23
23
  const nextConfig: NextConfig = {
24
24
  ...(isStandaloneMode ? standaloneConfig : {}),
25
+ compiler: {
26
+ emotion: true,
27
+ },
25
28
  compress: isProd,
26
29
  experimental: {
27
30
  optimizePackageImports: [
@@ -30,6 +33,7 @@ const nextConfig: NextConfig = {
30
33
  '@emoji-mart/data',
31
34
  '@icons-pack/react-simple-icons',
32
35
  '@lobehub/ui',
36
+ '@lobehub/icons',
33
37
  'gpt-tokenizer',
34
38
  ],
35
39
  // oidc provider depend on constructor.name
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobehub/chat",
3
- "version": "1.123.4",
3
+ "version": "1.124.0",
4
4
  "description": "Lobe Chat - an open-source, high-performance chatbot framework that supports speech synthesis, multimodal, and extensible Function Call plugin system. Supports one-click free deployment of your private ChatGPT/LLM web application.",
5
5
  "keywords": [
6
6
  "framework",
@@ -134,6 +134,7 @@
134
134
  "@codesandbox/sandpack-react": "^2.20.0",
135
135
  "@cyntler/react-doc-viewer": "^1.17.0",
136
136
  "@electric-sql/pglite": "0.2.17",
137
+ "@emotion/react": "^11.14.0",
137
138
  "@fal-ai/client": "^1.6.1",
138
139
  "@formkit/auto-animate": "^0.8.2",
139
140
  "@google/genai": "^1.13.0",
@@ -154,10 +155,11 @@
154
155
  "@lobehub/charts": "^2.0.0",
155
156
  "@lobehub/chat-plugin-sdk": "^1.32.4",
156
157
  "@lobehub/chat-plugins-gateway": "^1.9.0",
158
+ "@lobehub/editor": "^1.4.4",
157
159
  "@lobehub/icons": "^2.31.0",
158
160
  "@lobehub/market-sdk": "^0.22.7",
159
161
  "@lobehub/tts": "^2.0.1",
160
- "@lobehub/ui": "^2.8.3",
162
+ "@lobehub/ui": "^2.11.9",
161
163
  "@modelcontextprotocol/sdk": "^1.17.1",
162
164
  "@neondatabase/serverless": "^1.0.1",
163
165
  "@next/third-parties": "^15.4.6",
@@ -7,6 +7,7 @@ export const CHAT_TEXTAREA_MAX_HEIGHT = 800;
7
7
  export const CHAT_TEXTAREA_HEIGHT = 160;
8
8
  export const CHAT_TEXTAREA_HEIGHT_MOBILE = 108;
9
9
  export const CHAT_SIDEBAR_WIDTH = 280;
10
+ export const CONVERSATION_MIN_WIDTH = 850;
10
11
 
11
12
  export const CHAT_PORTAL_WIDTH = 400;
12
13
  export const CHAT_PORTAL_MAX_WIDTH = 1280;
@@ -8,6 +8,7 @@ export * from './chunk';
8
8
  export * from './clientDB';
9
9
  export * from './eval';
10
10
  export * from './fetch';
11
+ export * from './hotkey';
11
12
  export * from './knowledgeBase';
12
13
  export * from './llm';
13
14
  export * from './message';
@@ -2,6 +2,7 @@ export * from './client/cookie';
2
2
  export * from './detectChinese';
3
3
  export * from './format';
4
4
  export * from './imageToBase64';
5
+ export * from './keyboard';
5
6
  export * from './object';
6
7
  export * from './parseModels';
7
8
  export * from './pricing';
@@ -3,12 +3,13 @@
3
3
  import { useSearchParams } from 'next/navigation';
4
4
  import { useEffect } from 'react';
5
5
 
6
- import { useSendMessage } from '@/features/ChatInput/useSend';
7
6
  import { useChatStore } from '@/store/chat';
8
7
 
8
+ import { useSend } from '../useSend';
9
+
9
10
  const MessageFromUrl = () => {
10
11
  const updateInputMessage = useChatStore((s) => s.updateInputMessage);
11
- const { send: sendMessage } = useSendMessage();
12
+ const { send: sendMessage } = useSend();
12
13
  const searchParams = useSearchParams();
13
14
 
14
15
  useEffect(() => {
@@ -1,51 +1,152 @@
1
1
  'use client';
2
2
 
3
- import { memo } from 'react';
3
+ import { Alert, Hotkey, Icon } from '@lobehub/ui';
4
+ import { BotMessageSquare, LucideCheck, MessageSquarePlus } from 'lucide-react';
5
+ import { Suspense, memo } from 'react';
6
+ import { Trans, useTranslation } from 'react-i18next';
7
+ import { Flexbox } from 'react-layout-kit';
4
8
 
5
- import { ActionKeys } from '@/features/ChatInput/ActionBar/config';
6
- import DesktopChatInput, { FooterRender } from '@/features/ChatInput/Desktop';
7
- import { useGlobalStore } from '@/store/global';
8
- import { systemStatusSelectors } from '@/store/global/selectors';
9
+ import { type ActionKeys, ChatInputProvider, DesktopChatInput } from '@/features/ChatInput';
10
+ import WideScreenContainer from '@/features/Conversation/components/WideScreenContainer';
11
+ import { useChatStore } from '@/store/chat';
12
+ import { aiChatSelectors } from '@/store/chat/selectors';
13
+ import { useUserStore } from '@/store/user';
14
+ import { preferenceSelectors, settingsSelectors } from '@/store/user/selectors';
15
+ import { HotkeyEnum, KeyEnum } from '@/types/hotkey';
9
16
 
10
- import Footer from './Footer';
11
- import TextArea from './TextArea';
17
+ import { useSend } from '../useSend';
18
+ import MessageFromUrl from './MessageFromUrl';
12
19
 
13
- const leftActions = [
20
+ const leftActions: ActionKeys[] = [
14
21
  'model',
15
22
  'search',
23
+ 'typo',
16
24
  'fileUpload',
17
25
  'knowledgeBase',
18
- 'params',
19
- 'history',
20
- 'stt',
21
26
  'tools',
27
+ '---',
28
+ ['params', 'history', 'stt', 'clear'],
22
29
  'mainToken',
23
- ] as ActionKeys[];
30
+ ];
24
31
 
25
- const rightActions = ['clear'] as ActionKeys[];
26
-
27
- const renderTextArea = (onSend: () => void) => <TextArea onSend={onSend} />;
28
- const renderFooter: FooterRender = ({ expand, onExpandChange }) => (
29
- <Footer expand={expand} onExpandChange={onExpandChange} />
30
- );
32
+ const rightActions: ActionKeys[] = ['saveTopic'];
31
33
 
32
34
  const Desktop = memo(() => {
33
- const [inputHeight, updatePreference] = useGlobalStore((s) => [
34
- systemStatusSelectors.inputHeight(s),
35
- s.updateSystemStatus,
35
+ const { t } = useTranslation('chat');
36
+ const { send, generating, disabled, stop } = useSend();
37
+ const [useCmdEnterToSend, updatePreference] = useUserStore((s) => [
38
+ preferenceSelectors.useCmdEnterToSend(s),
39
+ s.updatePreference,
40
+ ]);
41
+
42
+ const [mainInputSendErrorMsg, clearSendMessageError] = useChatStore((s) => [
43
+ aiChatSelectors.isCurrentSendMessageError(s),
44
+ s.clearSendMessageError,
36
45
  ]);
46
+ const hotkey = useUserStore(settingsSelectors.getHotkeyById(HotkeyEnum.AddUserMessage));
37
47
 
38
48
  return (
39
- <DesktopChatInput
40
- inputHeight={inputHeight}
49
+ <ChatInputProvider
50
+ chatInputEditorRef={(instance) => {
51
+ if (!instance) return;
52
+ useChatStore.setState({ mainInputEditor: instance });
53
+ }}
41
54
  leftActions={leftActions}
42
- onInputHeightChange={(height) => {
43
- updatePreference({ inputHeight: height });
55
+ onMarkdownContentChange={(content) => {
56
+ useChatStore.setState({ inputMessage: content });
57
+ }}
58
+ onSend={() => {
59
+ send();
44
60
  }}
45
- renderFooter={renderFooter}
46
- renderTextArea={renderTextArea}
47
61
  rightActions={rightActions}
48
- />
62
+ sendButtonProps={{ disabled, generating, onStop: stop }}
63
+ sendMenu={{
64
+ items: [
65
+ {
66
+ icon: !useCmdEnterToSend ? <Icon icon={LucideCheck} /> : <div />,
67
+ key: 'sendWithEnter',
68
+ label: (
69
+ <Flexbox align={'center'} gap={4} horizontal>
70
+ <Trans
71
+ components={{
72
+ key: <Hotkey keys={KeyEnum.Enter} variant={'borderless'} />,
73
+ }}
74
+ i18nKey={'input.sendWithEnter'}
75
+ ns={'chat'}
76
+ />
77
+ </Flexbox>
78
+ ),
79
+ onClick: () => {
80
+ updatePreference({ useCmdEnterToSend: false });
81
+ },
82
+ },
83
+ {
84
+ icon: useCmdEnterToSend ? <Icon icon={LucideCheck} /> : <div />,
85
+ key: 'sendWithCmdEnter',
86
+ label: (
87
+ <Flexbox align={'center'} gap={4} horizontal>
88
+ <Trans
89
+ components={{
90
+ key: (
91
+ <Hotkey
92
+ keys={[KeyEnum.Mod, KeyEnum.Enter].join('+')}
93
+ variant={'borderless'}
94
+ />
95
+ ),
96
+ }}
97
+ i18nKey={'input.sendWithCmdEnter'}
98
+ ns={'chat'}
99
+ />
100
+ </Flexbox>
101
+ ),
102
+ onClick: () => {
103
+ updatePreference({ useCmdEnterToSend: true });
104
+ },
105
+ },
106
+ { type: 'divider' },
107
+ {
108
+ // disabled,
109
+ icon: <Icon icon={BotMessageSquare} />,
110
+ key: 'addAi',
111
+ label: t('input.addAi'),
112
+ onClick: () => {
113
+ send({ onlyAddAIMessage: true });
114
+ },
115
+ },
116
+ {
117
+ // disabled,
118
+ icon: <Icon icon={MessageSquarePlus} />,
119
+ key: 'addUser',
120
+ label: (
121
+ <Flexbox align={'center'} gap={24} horizontal>
122
+ {t('input.addUser')}
123
+ <Hotkey keys={hotkey} />
124
+ </Flexbox>
125
+ ),
126
+ onClick: () => {
127
+ send({ onlyAddUserMessage: true });
128
+ },
129
+ },
130
+ ],
131
+ }}
132
+ >
133
+ <WideScreenContainer>
134
+ {mainInputSendErrorMsg && (
135
+ <Flexbox paddingBlock={'0 6px'} paddingInline={12}>
136
+ <Alert
137
+ closable
138
+ message={t('input.errorMsg', { errorMsg: mainInputSendErrorMsg })}
139
+ onClose={clearSendMessageError}
140
+ type={'warning'}
141
+ />
142
+ </Flexbox>
143
+ )}
144
+ <DesktopChatInput />
145
+ </WideScreenContainer>
146
+ <Suspense>
147
+ <MessageFromUrl />
148
+ </Suspense>
149
+ </ChatInputProvider>
49
150
  );
50
151
  });
51
152
 
@@ -1,91 +1,69 @@
1
1
  'use client';
2
2
 
3
- import { Skeleton } from 'antd';
4
- import { useTheme } from 'antd-style';
5
- import { TextAreaRef } from 'antd/es/input/TextArea';
6
- import { memo, useRef, useState } from 'react';
3
+ import { Alert } from '@lobehub/ui';
4
+ import { memo } from 'react';
5
+ import { useTranslation } from 'react-i18next';
7
6
  import { Flexbox } from 'react-layout-kit';
8
7
 
9
- import ActionBar from '@/features/ChatInput/ActionBar';
10
- import STT from '@/features/ChatInput/ActionBar/STT';
11
- import { ActionKeys } from '@/features/ChatInput/ActionBar/config';
12
- import SaveTopic from '@/features/ChatInput/Topic';
13
- import { useSendMessage } from '@/features/ChatInput/useSend';
14
- import { useInitAgentConfig } from '@/hooks/useInitAgentConfig';
8
+ import {
9
+ type ActionKey,
10
+ type ActionKeys,
11
+ MobileChatInput as ChatInput,
12
+ ChatInputProvider,
13
+ } from '@/features/ChatInput';
15
14
  import { useChatStore } from '@/store/chat';
16
- import { chatSelectors } from '@/store/chat/selectors';
15
+ import { aiChatSelectors } from '@/store/chat/slices/aiChat/selectors';
17
16
 
18
- import Files from './Files';
19
- import InputArea from './InputArea';
20
- import SendButton from './Send';
17
+ import { useSend } from '../useSend';
21
18
 
22
- const defaultLeftActions: ActionKeys[] = [
19
+ const leftActions: ActionKeys[] = [
23
20
  'model',
24
21
  'search',
25
22
  'fileUpload',
26
23
  'knowledgeBase',
27
- 'history',
28
24
  'tools',
29
- 'params',
25
+ '---',
26
+ ['params', 'history', 'stt', 'clear'],
30
27
  'mainToken',
31
28
  ];
32
29
 
33
- const defaultRightActions: ActionKeys[] = ['clear'];
30
+ const rightActions: ActionKey[] = ['saveTopic'];
34
31
 
35
32
  const MobileChatInput = memo(() => {
36
- const theme = useTheme();
37
- const ref = useRef<TextAreaRef>(null);
38
- const [expand, setExpand] = useState<boolean>(false);
39
- const { send: sendMessage, canSend } = useSendMessage();
40
- const { isLoading } = useInitAgentConfig();
33
+ const { t } = useTranslation('chat');
34
+ const { send, disabled, generating, stop } = useSend();
41
35
 
42
- const [loading, value, onInput, onStop] = useChatStore((s) => [
43
- chatSelectors.isAIGenerating(s),
44
- s.inputMessage,
45
- s.updateInputMessage,
46
- s.stopGenerateMessage,
36
+ const [mainInputSendErrorMsg, clearSendMessageError] = useChatStore((s) => [
37
+ aiChatSelectors.isCurrentSendMessageError(s),
38
+ s.clearSendMessageError,
47
39
  ]);
48
-
49
40
  return (
50
- <InputArea
51
- expand={expand}
52
- onInput={onInput}
53
- onSend={() => {
54
- setExpand(false);
55
-
56
- sendMessage();
41
+ <ChatInputProvider
42
+ chatInputEditorRef={(instance) => {
43
+ if (!instance) return;
44
+ useChatStore.setState({ mainInputEditor: instance });
57
45
  }}
58
- ref={ref}
59
- setExpand={setExpand}
60
- style={{
61
- background: theme.colorBgLayout,
62
- top: expand ? 0 : undefined,
63
- width: '100%',
64
- zIndex: 101,
46
+ leftActions={leftActions}
47
+ mobile
48
+ onMarkdownContentChange={(content) => {
49
+ useChatStore.setState({ inputMessage: content });
65
50
  }}
66
- textAreaLeftAddons={<STT mobile />}
67
- textAreaRightAddons={
68
- <SendButton disabled={!canSend} loading={loading} onSend={sendMessage} onStop={onStop} />
69
- }
70
- topAddons={
71
- isLoading ? (
72
- <Flexbox paddingInline={8}>
73
- <Skeleton.Button active block size={'small'} />
74
- </Flexbox>
75
- ) : (
76
- <>
77
- <Files />
78
- <ActionBar
79
- leftActions={defaultLeftActions}
80
- padding={'0 8px'}
81
- rightActions={defaultRightActions}
82
- rightAreaStartRender={<SaveTopic mobile />}
83
- />
84
- </>
85
- )
86
- }
87
- value={value}
88
- />
51
+ onSend={() => send()}
52
+ rightActions={rightActions}
53
+ sendButtonProps={{ disabled, generating, onStop: stop }}
54
+ >
55
+ {mainInputSendErrorMsg && (
56
+ <Flexbox paddingBlock={'0 6px'} paddingInline={12}>
57
+ <Alert
58
+ closable
59
+ message={t('input.errorMsg', { errorMsg: mainInputSendErrorMsg })}
60
+ onClose={clearSendMessageError}
61
+ type={'warning'}
62
+ />
63
+ </Flexbox>
64
+ )}
65
+ <ChatInput />
66
+ </ChatInputProvider>
89
67
  );
90
68
  });
91
69
 
@@ -0,0 +1,141 @@
1
+ import { useAnalytics } from '@lobehub/analytics/react';
2
+ import { useMemo } from 'react';
3
+
4
+ import { useGeminiChineseWarning } from '@/hooks/useGeminiChineseWarning';
5
+ import { getAgentStoreState } from '@/store/agent';
6
+ import { agentSelectors } from '@/store/agent/selectors';
7
+ import { getChatStoreState, useChatStore } from '@/store/chat';
8
+ import { aiChatSelectors, chatSelectors, topicSelectors } from '@/store/chat/selectors';
9
+ import { fileChatSelectors, useFileStore } from '@/store/file';
10
+ import { getUserStoreState } from '@/store/user';
11
+
12
+ export interface UseSendMessageParams {
13
+ isWelcomeQuestion?: boolean;
14
+ onlyAddAIMessage?: boolean;
15
+ onlyAddUserMessage?: boolean;
16
+ }
17
+
18
+ export const useSend = () => {
19
+ const [
20
+ isContentEmpty,
21
+ sendMessage,
22
+ addAIMessage,
23
+ stopGenerateMessage,
24
+ cancelSendMessageInServer,
25
+ generating,
26
+ isSendButtonDisabledByMessage,
27
+ isSendingMessage,
28
+ ] = useChatStore((s) => [
29
+ !s.inputMessage,
30
+ s.sendMessage,
31
+ s.addAIMessage,
32
+ s.stopGenerateMessage,
33
+ s.cancelSendMessageInServer,
34
+ chatSelectors.isAIGenerating(s),
35
+ chatSelectors.isSendButtonDisabledByMessage(s),
36
+ aiChatSelectors.isCurrentSendMessageLoading(s),
37
+ ]);
38
+ const { analytics } = useAnalytics();
39
+ const checkGeminiChineseWarning = useGeminiChineseWarning();
40
+
41
+ const fileList = fileChatSelectors.chatUploadFileList(useFileStore.getState());
42
+ const [isUploadingFiles, clearChatUploadFileList] = useFileStore((s) => [
43
+ fileChatSelectors.isUploadingFiles(s),
44
+ s.clearChatUploadFileList,
45
+ ]);
46
+
47
+ const isInputEmpty = isContentEmpty && fileList.length === 0;
48
+
49
+ const canNotSend =
50
+ isInputEmpty || isUploadingFiles || isSendButtonDisabledByMessage || isSendingMessage;
51
+
52
+ const handleSend = async (params: UseSendMessageParams = {}) => {
53
+ if (canNotSend) return;
54
+
55
+ const store = useChatStore.getState();
56
+ const mainInputEditor = store.mainInputEditor;
57
+
58
+ if (!mainInputEditor) {
59
+ console.warn('not found mainInputEditor instance');
60
+ return;
61
+ }
62
+
63
+ if (chatSelectors.isAIGenerating(store)) return;
64
+
65
+ const inputMessage = store.inputMessage;
66
+
67
+ // if there is no message and no image, then we should not send the message
68
+ if (!inputMessage && fileList.length === 0) return;
69
+
70
+ // Check for Chinese text warning with Gemini model
71
+ const agentStore = getAgentStoreState();
72
+ const currentModel = agentSelectors.currentAgentModel(agentStore);
73
+ const shouldContinue = await checkGeminiChineseWarning({
74
+ model: currentModel,
75
+ prompt: inputMessage,
76
+ scenario: 'chat',
77
+ });
78
+
79
+ if (!shouldContinue) return;
80
+
81
+ if (params.onlyAddAIMessage) {
82
+ addAIMessage();
83
+ } else {
84
+ sendMessage({ files: fileList, message: inputMessage, ...params });
85
+ }
86
+
87
+ clearChatUploadFileList();
88
+ mainInputEditor.setExpand(false);
89
+ mainInputEditor.clearContent();
90
+ mainInputEditor.focus();
91
+
92
+ // 获取分析数据
93
+ const userStore = getUserStoreState();
94
+
95
+ // 直接使用现有数据结构判断消息类型
96
+ const hasImages = fileList.some((file) => file.file?.type?.startsWith('image'));
97
+ const messageType = fileList.length === 0 ? 'text' : hasImages ? 'image' : 'file';
98
+
99
+ analytics?.track({
100
+ name: 'send_message',
101
+ properties: {
102
+ chat_id: store.activeId || 'unknown',
103
+ current_topic: topicSelectors.currentActiveTopic(store)?.title || null,
104
+ has_attachments: fileList.length > 0,
105
+ history_message_count: chatSelectors.activeBaseChats(store).length,
106
+ message: inputMessage,
107
+ message_length: inputMessage.length,
108
+ message_type: messageType,
109
+ selected_model: agentSelectors.currentAgentModel(agentStore),
110
+ session_id: store.activeId || 'inbox', // 当前活跃的会话ID
111
+ user_id: userStore.user?.id || 'anonymous',
112
+ },
113
+ });
114
+ };
115
+
116
+ const stop = () => {
117
+ const store = getChatStoreState();
118
+ const generating = chatSelectors.isAIGenerating(store);
119
+
120
+ if (generating) {
121
+ stopGenerateMessage();
122
+ return;
123
+ }
124
+
125
+ const isCreatingMessage = aiChatSelectors.isCurrentSendMessageLoading(store);
126
+
127
+ if (isCreatingMessage) {
128
+ cancelSendMessageInServer();
129
+ }
130
+ };
131
+
132
+ return useMemo(
133
+ () => ({
134
+ disabled: canNotSend,
135
+ generating: generating || isSendingMessage,
136
+ send: handleSend,
137
+ stop,
138
+ }),
139
+ [canNotSend, generating, isSendingMessage, stop, handleSend],
140
+ );
141
+ };
@@ -3,6 +3,7 @@
3
3
  import React, { memo, useCallback } from 'react';
4
4
 
5
5
  import { SkeletonList, VirtualizedList } from '@/features/Conversation';
6
+ import WideScreenContainer from '@/features/Conversation/components/WideScreenContainer';
6
7
  import { useFetchMessages } from '@/hooks/useFetchMessages';
7
8
  import { useChatStore } from '@/store/chat';
8
9
  import { chatSelectors } from '@/store/chat/selectors';
@@ -27,7 +28,12 @@ const Content = memo<ListProps>(({ mobile }) => {
27
28
 
28
29
  if (!isCurrentChatLoaded) return <SkeletonList mobile={mobile} />;
29
30
 
30
- if (data.length === 0) return <Welcome />;
31
+ if (data.length === 0)
32
+ return (
33
+ <WideScreenContainer flex={1} height={'100%'}>
34
+ <Welcome />
35
+ </WideScreenContainer>
36
+ );
31
37
 
32
38
  return <VirtualizedList dataSource={data} itemContent={itemContent} mobile={mobile} />;
33
39
  });