@lobehub/chat 1.123.4 → 1.124.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 (149) hide show
  1. package/.env.example +5 -0
  2. package/CHANGELOG.md +58 -0
  3. package/Dockerfile +2 -0
  4. package/Dockerfile.database +2 -0
  5. package/Dockerfile.pglite +2 -0
  6. package/changelog/v1.json +21 -0
  7. package/docs/self-hosting/environment-variables/model-provider.mdx +18 -0
  8. package/docs/self-hosting/environment-variables/model-provider.zh-CN.mdx +20 -0
  9. package/locales/ar/chat.json +8 -2
  10. package/locales/ar/editor.json +47 -0
  11. package/locales/bg-BG/chat.json +8 -2
  12. package/locales/bg-BG/editor.json +47 -0
  13. package/locales/de-DE/chat.json +8 -2
  14. package/locales/de-DE/editor.json +47 -0
  15. package/locales/en-US/chat.json +8 -2
  16. package/locales/en-US/editor.json +47 -0
  17. package/locales/es-ES/chat.json +8 -2
  18. package/locales/es-ES/editor.json +47 -0
  19. package/locales/es-ES/models.json +3 -1
  20. package/locales/fa-IR/chat.json +8 -2
  21. package/locales/fa-IR/editor.json +47 -0
  22. package/locales/fr-FR/chat.json +8 -2
  23. package/locales/fr-FR/editor.json +47 -0
  24. package/locales/it-IT/chat.json +8 -2
  25. package/locales/it-IT/editor.json +47 -0
  26. package/locales/ja-JP/chat.json +8 -2
  27. package/locales/ja-JP/editor.json +47 -0
  28. package/locales/ko-KR/chat.json +8 -2
  29. package/locales/ko-KR/editor.json +47 -0
  30. package/locales/ko-KR/models.json +3 -1
  31. package/locales/nl-NL/chat.json +8 -2
  32. package/locales/nl-NL/editor.json +47 -0
  33. package/locales/nl-NL/models.json +3 -1
  34. package/locales/pl-PL/chat.json +8 -2
  35. package/locales/pl-PL/editor.json +47 -0
  36. package/locales/pt-BR/chat.json +8 -2
  37. package/locales/pt-BR/editor.json +47 -0
  38. package/locales/ru-RU/chat.json +8 -2
  39. package/locales/ru-RU/editor.json +47 -0
  40. package/locales/tr-TR/chat.json +8 -2
  41. package/locales/tr-TR/editor.json +47 -0
  42. package/locales/vi-VN/chat.json +8 -2
  43. package/locales/vi-VN/editor.json +47 -0
  44. package/locales/zh-CN/chat.json +8 -2
  45. package/locales/zh-CN/editor.json +47 -0
  46. package/locales/zh-CN/modelProvider.json +1 -1
  47. package/locales/zh-TW/chat.json +8 -2
  48. package/locales/zh-TW/editor.json +47 -0
  49. package/locales/zh-TW/models.json +3 -1
  50. package/next.config.ts +4 -0
  51. package/package.json +4 -2
  52. package/packages/const/src/layoutTokens.ts +1 -0
  53. package/packages/model-bank/src/aiModels/aihubmix.ts +38 -4
  54. package/packages/model-bank/src/aiModels/groq.ts +26 -8
  55. package/packages/model-bank/src/aiModels/hunyuan.ts +3 -3
  56. package/packages/model-bank/src/aiModels/modelscope.ts +13 -2
  57. package/packages/model-bank/src/aiModels/moonshot.ts +25 -5
  58. package/packages/model-bank/src/aiModels/novita.ts +40 -9
  59. package/packages/model-bank/src/aiModels/openrouter.ts +0 -13
  60. package/packages/model-bank/src/aiModels/qwen.ts +62 -1
  61. package/packages/model-bank/src/aiModels/siliconcloud.ts +20 -0
  62. package/packages/model-bank/src/aiModels/volcengine.ts +141 -15
  63. package/packages/model-runtime/src/newapi/index.test.ts +49 -42
  64. package/packages/model-runtime/src/newapi/index.ts +124 -143
  65. package/packages/types/src/index.ts +1 -0
  66. package/packages/utils/src/index.ts +1 -0
  67. package/src/app/[variants]/(main)/chat/(workspace)/@conversation/features/ChatInput/Desktop/{Footer/MessageFromUrl.tsx → MessageFromUrl.tsx} +3 -2
  68. package/src/app/[variants]/(main)/chat/(workspace)/@conversation/features/ChatInput/Desktop/index.tsx +129 -28
  69. package/src/app/[variants]/(main)/chat/(workspace)/@conversation/features/ChatInput/Mobile/index.tsx +44 -66
  70. package/src/app/[variants]/(main)/chat/(workspace)/@conversation/features/ChatInput/useSend.ts +141 -0
  71. package/src/app/[variants]/(main)/chat/(workspace)/@conversation/features/ChatList/Content.tsx +7 -1
  72. package/src/app/[variants]/(main)/chat/(workspace)/@conversation/features/ChatList/WelcomeChatItem/InboxWelcome/QuestionSuggest.tsx +3 -2
  73. package/src/app/[variants]/(main)/chat/(workspace)/@conversation/features/ChatList/WelcomeChatItem/OpeningQuestions.tsx +3 -2
  74. package/src/app/[variants]/(main)/chat/(workspace)/_layout/Desktop/ChatHeader/HeaderAction.tsx +18 -2
  75. package/src/app/[variants]/(main)/settings/provider/(detail)/newapi/page.tsx +1 -1
  76. package/src/config/llm.ts +8 -0
  77. package/src/features/ChatInput/ActionBar/STT/common.tsx +41 -47
  78. package/src/features/ChatInput/{Topic → ActionBar/SaveTopic}/index.tsx +15 -4
  79. package/src/features/ChatInput/ActionBar/Typo/index.tsx +22 -0
  80. package/src/features/ChatInput/ActionBar/components/Action.tsx +4 -0
  81. package/src/features/ChatInput/ActionBar/config.ts +7 -1
  82. package/src/features/ChatInput/ActionBar/index.tsx +40 -51
  83. package/src/features/ChatInput/ChatInputProvider.tsx +54 -0
  84. package/src/features/ChatInput/Desktop/FilePreview/FileItem/index.tsx +20 -11
  85. package/src/features/ChatInput/Desktop/FilePreview/FileList.tsx +16 -15
  86. package/src/features/ChatInput/Desktop/index.tsx +94 -69
  87. package/src/features/ChatInput/InputEditor/index.tsx +134 -0
  88. package/src/{app/[variants]/(main)/chat/(workspace)/@conversation/features/ChatInput/Mobile/Files → features/ChatInput/Mobile/FilePreview}/FileItem/File.tsx +1 -2
  89. package/src/features/ChatInput/Mobile/FilePreview/index.tsx +44 -0
  90. package/src/features/ChatInput/Mobile/index.tsx +72 -0
  91. package/src/features/ChatInput/SendArea/ExpandButton.tsx +30 -0
  92. package/src/features/ChatInput/SendArea/SendButton.tsx +29 -0
  93. package/src/features/ChatInput/SendArea/ShortcutHint.tsx +52 -0
  94. package/src/features/ChatInput/SendArea/index.tsx +36 -0
  95. package/src/features/ChatInput/StoreUpdater.tsx +41 -0
  96. package/src/features/ChatInput/TypoBar/index.tsx +139 -0
  97. package/src/features/ChatInput/hooks/useChatInputEditor.ts +36 -0
  98. package/src/features/ChatInput/index.ts +7 -0
  99. package/src/features/ChatInput/store/action.ts +75 -0
  100. package/src/features/ChatInput/store/index.ts +23 -0
  101. package/src/features/ChatInput/store/initialState.ts +54 -0
  102. package/src/features/ChatInput/store/selectors.ts +5 -0
  103. package/src/features/Conversation/components/BackBottom/style.ts +1 -1
  104. package/src/features/Conversation/components/SkeletonList.tsx +10 -3
  105. package/src/features/Conversation/components/VirtualizedList/index.tsx +53 -44
  106. package/src/features/Conversation/components/WideScreenContainer/index.tsx +43 -0
  107. package/src/features/Portal/Thread/Chat/ChatInput/index.tsx +49 -42
  108. package/src/features/Portal/Thread/Chat/ChatInput/useSend.ts +48 -22
  109. package/src/features/Portal/Thread/Chat/index.tsx +2 -2
  110. package/src/features/Portal/Thread/Header/index.tsx +1 -1
  111. package/src/hooks/useHotkeys/chatScope.ts +5 -3
  112. package/src/layout/GlobalProvider/Editor.tsx +27 -0
  113. package/src/layout/GlobalProvider/Locale.tsx +3 -23
  114. package/src/locales/default/chat.ts +8 -2
  115. package/src/locales/default/editor.ts +47 -0
  116. package/src/locales/default/index.ts +2 -0
  117. package/src/locales/default/modelProvider.ts +1 -1
  118. package/src/services/aiChat.ts +8 -2
  119. package/src/store/chat/slices/aiChat/actions/__tests__/cancel-functionality.test.ts +107 -0
  120. package/src/store/chat/slices/aiChat/actions/__tests__/generateAIChatV2.test.ts +394 -40
  121. package/src/store/chat/slices/aiChat/actions/generateAIChatV2.ts +175 -35
  122. package/src/store/chat/slices/aiChat/initialState.ts +19 -0
  123. package/src/store/chat/slices/aiChat/selectors.ts +18 -0
  124. package/src/store/global/action.test.ts +6 -5
  125. package/src/store/global/actions/__tests__/general.test.ts +6 -6
  126. package/src/store/global/actions/workspacePane.ts +6 -0
  127. package/src/store/global/initialState.ts +2 -4
  128. package/src/store/global/selectors/systemStatus.test.ts +1 -2
  129. package/src/store/global/selectors/systemStatus.ts +2 -5
  130. package/src/app/[variants]/(main)/chat/(workspace)/@conversation/features/ChatInput/Desktop/Footer/SendMore.tsx +0 -104
  131. package/src/app/[variants]/(main)/chat/(workspace)/@conversation/features/ChatInput/Desktop/Footer/ShortcutHint.tsx +0 -40
  132. package/src/app/[variants]/(main)/chat/(workspace)/@conversation/features/ChatInput/Desktop/Footer/index.tsx +0 -125
  133. package/src/app/[variants]/(main)/chat/(workspace)/@conversation/features/ChatInput/Desktop/TextArea.test.tsx +0 -332
  134. package/src/app/[variants]/(main)/chat/(workspace)/@conversation/features/ChatInput/Desktop/TextArea.tsx +0 -29
  135. package/src/app/[variants]/(main)/chat/(workspace)/@conversation/features/ChatInput/Mobile/Files/index.tsx +0 -33
  136. package/src/app/[variants]/(main)/chat/(workspace)/@conversation/features/ChatInput/Mobile/InputArea/Container.tsx +0 -41
  137. package/src/app/[variants]/(main)/chat/(workspace)/@conversation/features/ChatInput/Mobile/InputArea/index.tsx +0 -156
  138. package/src/app/[variants]/(main)/chat/(workspace)/@conversation/features/ChatInput/Mobile/Send.tsx +0 -33
  139. package/src/features/ChatInput/Desktop/Header/index.tsx +0 -30
  140. package/src/features/ChatInput/Desktop/InputArea/index.tsx +0 -143
  141. package/src/features/ChatInput/Desktop/__tests__/useAutoFocus.test.ts +0 -45
  142. package/src/features/ChatInput/Desktop/useAutoFocus.ts +0 -13
  143. package/src/features/ChatInput/useSend.ts +0 -102
  144. package/src/features/Portal/Thread/Chat/ChatInput/Footer.tsx +0 -90
  145. package/src/features/Portal/Thread/Chat/ChatInput/TextArea.tsx +0 -30
  146. package/src/libs/trpc/client/types.ts +0 -18
  147. /package/src/{app/[variants]/(main)/chat/(workspace)/@conversation/features/ChatInput/Mobile/Files → features/ChatInput/Mobile/FilePreview}/FileItem/Image.tsx +0 -0
  148. /package/src/{app/[variants]/(main)/chat/(workspace)/@conversation/features/ChatInput/Mobile/Files → features/ChatInput/Mobile/FilePreview}/FileItem/index.tsx +0 -0
  149. /package/src/{app/[variants]/(main)/chat/(workspace)/@conversation/features/ChatInput/Mobile/Files → features/ChatInput/Mobile/FilePreview}/FileItem/style.ts +0 -0
@@ -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
  });
@@ -11,9 +11,10 @@ import { Flexbox } from 'react-layout-kit';
11
11
 
12
12
  import { BRANDING_NAME } from '@/const/branding';
13
13
  import { USAGE_DOCUMENTS } from '@/const/url';
14
- import { useSendMessage } from '@/features/ChatInput/useSend';
15
14
  import { useChatStore } from '@/store/chat';
16
15
 
16
+ import { useSend } from '../../../ChatInput/useSend';
17
+
17
18
  const useStyles = createStyles(({ css, token, responsive }) => ({
18
19
  card: css`
19
20
  padding-block: 12px;
@@ -60,7 +61,7 @@ const QuestionSuggest = memo<{ mobile?: boolean }>(({ mobile }) => {
60
61
 
61
62
  const { t } = useTranslation('welcome');
62
63
  const { styles } = useStyles();
63
- const { send: sendMessage } = useSendMessage();
64
+ const { send: sendMessage } = useSend();
64
65
 
65
66
  return (
66
67
  <Flexbox gap={8} width={'100%'}>
@@ -6,9 +6,10 @@ import { memo } from 'react';
6
6
  import { useTranslation } from 'react-i18next';
7
7
  import { Flexbox } from 'react-layout-kit';
8
8
 
9
- import { useSendMessage } from '@/features/ChatInput/useSend';
10
9
  import { useChatStore } from '@/store/chat';
11
10
 
11
+ import { useSend } from '../../ChatInput/useSend';
12
+
12
13
  const useStyles = createStyles(({ css, token, responsive }) => ({
13
14
  card: css`
14
15
  padding-block: 8px;
@@ -42,7 +43,7 @@ const OpeningQuestions = memo<OpeningQuestionsProps>(({ mobile, questions }) =>
42
43
  const [updateInputMessage] = useChatStore((s) => [s.updateInputMessage]);
43
44
 
44
45
  const { styles } = useStyles();
45
- const { send: sendMessage } = useSendMessage();
46
+ const { send: sendMessage } = useSend();
46
47
 
47
48
  return (
48
49
  <div className={styles.container}>
@@ -1,7 +1,12 @@
1
1
  'use client';
2
2
 
3
3
  import { ActionIcon } from '@lobehub/ui';
4
- import { PanelRightClose, PanelRightOpen } from 'lucide-react';
4
+ import {
5
+ PanelLeftRightDashedIcon,
6
+ PanelRightClose,
7
+ PanelRightOpen,
8
+ SquareChartGanttIcon,
9
+ } from 'lucide-react';
5
10
  import { memo } from 'react';
6
11
  import { useTranslation } from 'react-i18next';
7
12
  import { Flexbox } from 'react-layout-kit';
@@ -20,15 +25,26 @@ import ShareButton from '../../../features/ShareButton';
20
25
  const HeaderAction = memo<{ className?: string }>(({ className }) => {
21
26
  const { t } = useTranslation('chat');
22
27
  const hotkey = useUserStore(settingsSelectors.getHotkeyById(HotkeyEnum.ToggleRightPanel));
23
- const [showAgentSettings, toggleConfig] = useGlobalStore((s) => [
28
+ const [showAgentSettings, wideScreen, toggleConfig, toggleWideScreen] = useGlobalStore((s) => [
24
29
  systemStatusSelectors.showChatSideBar(s),
30
+ systemStatusSelectors.wideScreen(s),
25
31
  s.toggleChatSideBar,
32
+ s.toggleWideScreen,
26
33
  ]);
27
34
 
28
35
  const { isAgentEditable } = useServerConfigStore(featureFlagsSelectors);
29
36
 
30
37
  return (
31
38
  <Flexbox className={className} gap={4} horizontal>
39
+ <ActionIcon
40
+ icon={wideScreen ? SquareChartGanttIcon : PanelLeftRightDashedIcon}
41
+ onClick={() => toggleWideScreen()}
42
+ size={DESKTOP_HEADER_ICON_SIZE}
43
+ title={t(wideScreen ? 'toggleWideScreen.off' : 'toggleWideScreen.on')}
44
+ tooltipProps={{
45
+ placement: 'bottom',
46
+ }}
47
+ />
32
48
  <ShareButton />
33
49
  <ActionIcon
34
50
  icon={showAgentSettings ? PanelRightClose : PanelRightOpen}
@@ -16,7 +16,7 @@ const Page = () => {
16
16
  ...NewAPIProviderCard.settings,
17
17
  proxyUrl: {
18
18
  desc: t('newapi.apiUrl.desc'),
19
- placeholder: 'https://any-newapi-provider.com/v1',
19
+ placeholder: 'https://any-newapi-provider.com/',
20
20
  title: t('newapi.apiUrl.title'),
21
21
  },
22
22
  }}
package/src/config/llm.ts CHANGED
@@ -186,6 +186,10 @@ export const getLLMConfig = () => {
186
186
 
187
187
  ENABLED_AIHUBMIX: z.boolean(),
188
188
  AIHUBMIX_API_KEY: z.string().optional(),
189
+
190
+ ENABLED_NEWAPI: z.boolean(),
191
+ NEWAPI_API_KEY: z.string().optional(),
192
+ NEWAPI_PROXY_URL: z.string().optional(),
189
193
  },
190
194
  runtimeEnv: {
191
195
  API_KEY_SELECT_MODE: process.env.API_KEY_SELECT_MODE,
@@ -368,6 +372,10 @@ export const getLLMConfig = () => {
368
372
  ENABLED_AIHUBMIX: !!process.env.AIHUBMIX_API_KEY,
369
373
  AIHUBMIX_API_KEY: process.env.AIHUBMIX_API_KEY,
370
374
 
375
+ ENABLED_NEWAPI: !!process.env.NEWAPI_API_KEY,
376
+ NEWAPI_API_KEY: process.env.NEWAPI_API_KEY,
377
+ NEWAPI_PROXY_URL: process.env.NEWAPI_PROXY_URL,
378
+
371
379
  ENABLED_NEBIUS: !!process.env.NEBIUS_API_KEY,
372
380
  NEBIUS_API_KEY: process.env.NEBIUS_API_KEY,
373
381
  },