@lobehub/chat 1.123.3 → 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 (137) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/changelog/v1.json +18 -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/(backend)/webapi/chat/[provider]/route.ts +1 -1
  49. package/src/app/(backend)/webapi/chat/vertexai/route.ts +1 -0
  50. package/src/app/[variants]/(main)/chat/(workspace)/@conversation/features/ChatInput/Desktop/{Footer/MessageFromUrl.tsx → MessageFromUrl.tsx} +3 -2
  51. package/src/app/[variants]/(main)/chat/(workspace)/@conversation/features/ChatInput/Desktop/index.tsx +129 -28
  52. package/src/app/[variants]/(main)/chat/(workspace)/@conversation/features/ChatInput/Mobile/index.tsx +44 -66
  53. package/src/app/[variants]/(main)/chat/(workspace)/@conversation/features/ChatInput/useSend.ts +141 -0
  54. package/src/app/[variants]/(main)/chat/(workspace)/@conversation/features/ChatList/Content.tsx +7 -1
  55. package/src/app/[variants]/(main)/chat/(workspace)/@conversation/features/ChatList/WelcomeChatItem/InboxWelcome/QuestionSuggest.tsx +3 -2
  56. package/src/app/[variants]/(main)/chat/(workspace)/@conversation/features/ChatList/WelcomeChatItem/OpeningQuestions.tsx +3 -2
  57. package/src/app/[variants]/(main)/chat/(workspace)/_layout/Desktop/ChatHeader/HeaderAction.tsx +18 -2
  58. package/src/features/ChatInput/ActionBar/STT/common.tsx +41 -47
  59. package/src/features/ChatInput/{Topic → ActionBar/SaveTopic}/index.tsx +15 -4
  60. package/src/features/ChatInput/ActionBar/Typo/index.tsx +22 -0
  61. package/src/features/ChatInput/ActionBar/components/Action.tsx +4 -0
  62. package/src/features/ChatInput/ActionBar/config.ts +7 -1
  63. package/src/features/ChatInput/ActionBar/index.tsx +40 -51
  64. package/src/features/ChatInput/ChatInputProvider.tsx +54 -0
  65. package/src/features/ChatInput/Desktop/FilePreview/FileItem/index.tsx +20 -11
  66. package/src/features/ChatInput/Desktop/FilePreview/FileList.tsx +16 -15
  67. package/src/features/ChatInput/Desktop/index.tsx +81 -68
  68. package/src/features/ChatInput/InputEditor/index.tsx +134 -0
  69. package/src/{app/[variants]/(main)/chat/(workspace)/@conversation/features/ChatInput/Mobile/Files → features/ChatInput/Mobile/FilePreview}/FileItem/File.tsx +1 -2
  70. package/src/features/ChatInput/Mobile/FilePreview/index.tsx +44 -0
  71. package/src/features/ChatInput/Mobile/index.tsx +72 -0
  72. package/src/features/ChatInput/SendArea/ExpandButton.tsx +30 -0
  73. package/src/features/ChatInput/SendArea/SendButton.tsx +29 -0
  74. package/src/features/ChatInput/SendArea/ShortcutHint.tsx +52 -0
  75. package/src/features/ChatInput/SendArea/index.tsx +36 -0
  76. package/src/features/ChatInput/StoreUpdater.tsx +41 -0
  77. package/src/features/ChatInput/TypoBar/index.tsx +139 -0
  78. package/src/features/ChatInput/hooks/useChatInputEditor.ts +36 -0
  79. package/src/features/ChatInput/index.ts +7 -0
  80. package/src/features/ChatInput/store/action.ts +75 -0
  81. package/src/features/ChatInput/store/index.ts +23 -0
  82. package/src/features/ChatInput/store/initialState.ts +54 -0
  83. package/src/features/ChatInput/store/selectors.ts +5 -0
  84. package/src/features/Conversation/components/BackBottom/style.ts +1 -1
  85. package/src/features/Conversation/components/SkeletonList.tsx +10 -3
  86. package/src/features/Conversation/components/VirtualizedList/index.tsx +53 -44
  87. package/src/features/Conversation/components/WideScreenContainer/index.tsx +43 -0
  88. package/src/features/Portal/Thread/Chat/ChatInput/index.tsx +49 -42
  89. package/src/features/Portal/Thread/Chat/ChatInput/useSend.ts +48 -22
  90. package/src/features/Portal/Thread/Chat/index.tsx +2 -2
  91. package/src/features/Portal/Thread/Header/index.tsx +1 -1
  92. package/src/hooks/useHotkeys/chatScope.ts +5 -3
  93. package/src/layout/GlobalProvider/Editor.tsx +27 -0
  94. package/src/layout/GlobalProvider/Locale.tsx +3 -23
  95. package/src/libs/trpc/client/lambda.ts +76 -63
  96. package/src/locales/default/chat.ts +7 -2
  97. package/src/locales/default/editor.ts +47 -0
  98. package/src/locales/default/index.ts +2 -0
  99. package/src/services/aiChat.ts +8 -2
  100. package/src/store/chat/slices/aiChat/actions/__tests__/generateAIChatV2.test.ts +42 -33
  101. package/src/store/chat/slices/aiChat/actions/generateAIChatV2.ts +174 -35
  102. package/src/store/chat/slices/aiChat/initialState.ts +19 -0
  103. package/src/store/chat/slices/aiChat/selectors.ts +18 -0
  104. package/src/store/global/action.test.ts +6 -5
  105. package/src/store/global/actions/__tests__/general.test.ts +6 -6
  106. package/src/store/global/actions/workspacePane.ts +6 -0
  107. package/src/store/global/initialState.ts +2 -4
  108. package/src/store/global/selectors/systemStatus.test.ts +1 -2
  109. package/src/store/global/selectors/systemStatus.ts +2 -5
  110. package/src/app/(backend)/webapi/chat/anthropic/route.test.ts +0 -30
  111. package/src/app/(backend)/webapi/chat/anthropic/route.ts +0 -21
  112. package/src/app/(backend)/webapi/chat/google/route.test.ts +0 -35
  113. package/src/app/(backend)/webapi/chat/google/route.ts +0 -25
  114. package/src/app/(backend)/webapi/chat/groq/route.test.ts +0 -29
  115. package/src/app/(backend)/webapi/chat/groq/route.ts +0 -21
  116. package/src/app/(backend)/webapi/chat/openai/route.test.ts +0 -30
  117. package/src/app/(backend)/webapi/chat/openai/route.ts +0 -26
  118. package/src/app/[variants]/(main)/chat/(workspace)/@conversation/features/ChatInput/Desktop/Footer/SendMore.tsx +0 -104
  119. package/src/app/[variants]/(main)/chat/(workspace)/@conversation/features/ChatInput/Desktop/Footer/ShortcutHint.tsx +0 -40
  120. package/src/app/[variants]/(main)/chat/(workspace)/@conversation/features/ChatInput/Desktop/Footer/index.tsx +0 -125
  121. package/src/app/[variants]/(main)/chat/(workspace)/@conversation/features/ChatInput/Desktop/TextArea.test.tsx +0 -332
  122. package/src/app/[variants]/(main)/chat/(workspace)/@conversation/features/ChatInput/Desktop/TextArea.tsx +0 -29
  123. package/src/app/[variants]/(main)/chat/(workspace)/@conversation/features/ChatInput/Mobile/Files/index.tsx +0 -33
  124. package/src/app/[variants]/(main)/chat/(workspace)/@conversation/features/ChatInput/Mobile/InputArea/Container.tsx +0 -41
  125. package/src/app/[variants]/(main)/chat/(workspace)/@conversation/features/ChatInput/Mobile/InputArea/index.tsx +0 -156
  126. package/src/app/[variants]/(main)/chat/(workspace)/@conversation/features/ChatInput/Mobile/Send.tsx +0 -33
  127. package/src/features/ChatInput/Desktop/Header/index.tsx +0 -30
  128. package/src/features/ChatInput/Desktop/InputArea/index.tsx +0 -143
  129. package/src/features/ChatInput/Desktop/__tests__/useAutoFocus.test.ts +0 -45
  130. package/src/features/ChatInput/Desktop/useAutoFocus.ts +0 -13
  131. package/src/features/ChatInput/useSend.ts +0 -102
  132. package/src/features/Portal/Thread/Chat/ChatInput/Footer.tsx +0 -90
  133. package/src/features/Portal/Thread/Chat/ChatInput/TextArea.tsx +0 -30
  134. package/src/libs/trpc/client/types.ts +0 -18
  135. /package/src/{app/[variants]/(main)/chat/(workspace)/@conversation/features/ChatInput/Mobile/Files → features/ChatInput/Mobile/FilePreview}/FileItem/Image.tsx +0 -0
  136. /package/src/{app/[variants]/(main)/chat/(workspace)/@conversation/features/ChatInput/Mobile/Files → features/ChatInput/Mobile/FilePreview}/FileItem/index.tsx +0 -0
  137. /package/src/{app/[variants]/(main)/chat/(workspace)/@conversation/features/ChatInput/Mobile/Files → features/ChatInput/Mobile/FilePreview}/FileItem/style.ts +0 -0
@@ -4,63 +4,70 @@ import { Alert } from '@lobehub/ui';
4
4
  import Link from 'next/link';
5
5
  import { memo } from 'react';
6
6
  import { Trans } from 'react-i18next';
7
+ import { Flexbox } from 'react-layout-kit';
7
8
 
8
- import { ActionKeys } from '@/features/ChatInput/ActionBar/config';
9
- import DesktopChatInput, { FooterRender } from '@/features/ChatInput/Desktop';
9
+ import { type ActionKeys, ChatInputProvider, DesktopChatInput } from '@/features/ChatInput';
10
+ import WideScreenContainer from '@/features/Conversation/components/WideScreenContainer';
11
+ import { useChatStore } from '@/store/chat';
10
12
  import { useGlobalStore } from '@/store/global';
11
13
  import { systemStatusSelectors } from '@/store/global/selectors';
12
14
 
13
- import Footer from './Footer';
14
- import TextArea from './TextArea';
15
+ import { useSendThreadMessage } from './useSend';
15
16
 
16
- const leftActions = ['stt', 'portalToken'] as ActionKeys[];
17
-
18
- const rightActions = [] as ActionKeys[];
19
-
20
- const renderTextArea = (onSend: () => void) => <TextArea onSend={onSend} />;
21
- const renderFooter: FooterRender = (props) => <Footer {...props} />;
17
+ const threadActions: ActionKeys[] = ['typo', 'stt', 'portalToken'];
22
18
 
23
19
  const Desktop = memo(() => {
24
- const [inputHeight, hideThreadLimitAlert, updateSystemStatus] = useGlobalStore((s) => [
25
- systemStatusSelectors.threadInputHeight(s),
20
+ const [hideThreadLimitAlert, updateSystemStatus] = useGlobalStore((s) => [
26
21
  systemStatusSelectors.systemStatus(s).hideThreadLimitAlert,
27
22
  s.updateSystemStatus,
28
23
  ]);
29
24
 
25
+ const { send, disabled, generating, stop } = useSendThreadMessage();
26
+
30
27
  return (
31
- <>
28
+ <WideScreenContainer>
32
29
  {!hideThreadLimitAlert && (
33
- <Alert
34
- banner
35
- closable
36
- message={
37
- <Trans i18nKey={'notSupportMultiModals'} ns={'thread'}>
38
- 子话题暂不支持文件/图片上传,如有需求,欢迎留言:
39
- <Link
40
- href={'https://github.com/lobehub/lobe-chat/discussions/4717'}
41
- style={{ textDecoration: 'underline' }}
42
- >
43
- 💬 讨论
44
- </Link>
45
- </Trans>
46
- }
47
- onClose={() => {
48
- updateSystemStatus({ hideThreadLimitAlert: true });
49
- }}
50
- type={'info'}
51
- />
30
+ <Flexbox paddingBlock={'0 6px'} paddingInline={12}>
31
+ <Alert
32
+ closable
33
+ message={
34
+ <Trans i18nKey={'notSupportMultiModals'} ns={'thread'}>
35
+ 子话题暂不支持文件/图片上传,如有需求,欢迎留言:
36
+ <Link
37
+ href={'https://github.com/lobehub/lobe-chat/discussions/4717'}
38
+ style={{ textDecoration: 'underline' }}
39
+ >
40
+ 💬 讨论
41
+ </Link>
42
+ </Trans>
43
+ }
44
+ onClose={() => {
45
+ updateSystemStatus({ hideThreadLimitAlert: true });
46
+ }}
47
+ type={'info'}
48
+ />
49
+ </Flexbox>
52
50
  )}
53
- <DesktopChatInput
54
- inputHeight={inputHeight}
55
- leftActions={leftActions}
56
- onInputHeightChange={(height) => {
57
- updateSystemStatus({ threadInputHeight: height });
51
+
52
+ <ChatInputProvider
53
+ chatInputEditorRef={(instance) => {
54
+ if (!instance) return;
55
+ useChatStore.setState({ threadInputEditor: instance });
56
+ }}
57
+ leftActions={threadActions}
58
+ onSend={() => {
59
+ send();
60
+ }}
61
+ sendButtonProps={{
62
+ disabled,
63
+ generating,
64
+ onStop: stop,
65
+ shape: 'round',
58
66
  }}
59
- renderFooter={renderFooter}
60
- renderTextArea={renderTextArea}
61
- rightActions={rightActions}
62
- />
63
- </>
67
+ >
68
+ <DesktopChatInput />
69
+ </ChatInputProvider>
70
+ </WideScreenContainer>
64
71
  );
65
72
  });
66
73
 
@@ -1,5 +1,8 @@
1
- import { useCallback, useMemo } from 'react';
1
+ import { useMemo, useState } from 'react';
2
2
 
3
+ import { useGeminiChineseWarning } from '@/hooks/useGeminiChineseWarning';
4
+ import { getAgentStoreState } from '@/store/agent';
5
+ import { agentSelectors } from '@/store/agent/slices/chat';
3
6
  import { useChatStore } from '@/store/chat';
4
7
  import { threadSelectors } from '@/store/chat/selectors';
5
8
  import { SendMessageParams } from '@/types/message';
@@ -10,41 +13,64 @@ export type UseSendMessageParams = Pick<
10
13
  >;
11
14
 
12
15
  export const useSendThreadMessage = () => {
16
+ const [loading, setLoading] = useState(false);
17
+ const canNotSend = useChatStore(threadSelectors.isSendButtonDisabledByMessage);
18
+ const generating = useChatStore((s) => threadSelectors.isThreadAIGenerating(s));
19
+ const stop = useChatStore((s) => s.stopGenerateMessage);
13
20
  const [sendMessage, updateInputMessage] = useChatStore((s) => [
14
21
  s.sendThreadMessage,
15
22
  s.updateThreadInputMessage,
16
23
  ]);
24
+ const checkGeminiChineseWarning = useGeminiChineseWarning();
17
25
 
18
- const isSendButtonDisabledByMessage = useChatStore(threadSelectors.isSendButtonDisabledByMessage);
19
-
20
- const canSend = !isSendButtonDisabledByMessage;
21
-
22
- const send = useCallback((params: UseSendMessageParams = {}) => {
26
+ const handleSend = async (params: UseSendMessageParams = {}) => {
23
27
  const store = useChatStore.getState();
28
+
24
29
  if (threadSelectors.isThreadAIGenerating(store)) return;
30
+ const canNotSend = threadSelectors.isSendButtonDisabledByMessage(store);
31
+
32
+ if (canNotSend) return;
33
+
34
+ const threadInputEditor = store.threadInputEditor;
25
35
 
26
- const isSendButtonDisabledByMessage = threadSelectors.isSendButtonDisabledByMessage(
27
- useChatStore.getState(),
28
- );
36
+ if (!threadInputEditor) {
37
+ console.warn('not found threadInputEditor instance');
38
+ return;
39
+ }
29
40
 
30
- const canSend = !isSendButtonDisabledByMessage;
31
- if (!canSend) return;
41
+ const inputMessage = threadInputEditor.getMarkdownContent();
32
42
 
33
43
  // if there is no message and no image, then we should not send the message
34
- if (!store.threadInputMessage) return;
44
+ if (!inputMessage) return;
35
45
 
36
- sendMessage({ message: store.threadInputMessage, ...params });
46
+ // Check for Chinese text warning with Gemini model
47
+ const agentStore = getAgentStoreState();
48
+ const currentModel = agentSelectors.currentAgentModel(agentStore);
49
+ const shouldContinue = await checkGeminiChineseWarning({
50
+ model: currentModel,
51
+ prompt: inputMessage,
52
+ scenario: 'chat',
53
+ });
37
54
 
38
- updateInputMessage('');
55
+ if (!shouldContinue) return;
56
+
57
+ updateInputMessage(inputMessage);
39
58
 
40
- // const hasSystemRole = agentSelectors.hasSystemRole(useAgentStore.getState());
41
- // const agentSetting = useAgentStore.getState().agentSettingInstance;
59
+ sendMessage({ message: inputMessage, ...params });
60
+
61
+ updateInputMessage('');
62
+ threadInputEditor.clearContent();
63
+ threadInputEditor.focus();
64
+ };
42
65
 
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
- }, []);
66
+ const send = async (params: UseSendMessageParams = {}) => {
67
+ setLoading(true);
68
+ await handleSend(params);
69
+ setLoading(false);
70
+ };
48
71
 
49
- return useMemo(() => ({ canSend, send }), [canSend]);
72
+ return useMemo(
73
+ () => ({ disabled: canNotSend, generating, loading, send, stop }),
74
+ [canNotSend, send, generating, stop, loading],
75
+ );
50
76
  };
@@ -11,7 +11,7 @@ interface ConversationProps {
11
11
  }
12
12
 
13
13
  const Conversation = memo<ConversationProps>(({ mobile }) => (
14
- <Flexbox height={'100%'}>
14
+ <>
15
15
  <Suspense
16
16
  fallback={
17
17
  <Flexbox flex={1} height={'100%'}>
@@ -22,7 +22,7 @@ const Conversation = memo<ConversationProps>(({ mobile }) => (
22
22
  <ChatList mobile={mobile} />
23
23
  </Suspense>
24
24
  <ChatInput />
25
- </Flexbox>
25
+ </>
26
26
  ));
27
27
 
28
28
  export default Conversation;
@@ -40,7 +40,7 @@ const Header = memo(() => {
40
40
  paddingBlock={6}
41
41
  paddingInline={8}
42
42
  style={{
43
- background: `linear-gradient(to bottom, ${theme.colorBgContainerSecondary}, ${theme.colorFillQuaternary})`,
43
+ borderBottom: `1px solid ${theme.colorBorderSecondary}`,
44
44
  }}
45
45
  title={<Title />}
46
46
  />
@@ -2,8 +2,8 @@ import isEqual from 'fast-deep-equal';
2
2
  import { useEffect } from 'react';
3
3
  import { useHotkeysContext } from 'react-hotkeys-hook';
4
4
 
5
+ import { useSend } from '@/app/[variants]/(main)/chat/(workspace)/@conversation/features/ChatInput/useSend';
5
6
  import { useClearCurrentMessages } from '@/features/ChatInput/ActionBar/Clear';
6
- import { useSendMessage } from '@/features/ChatInput/useSend';
7
7
  import { useOpenChatSettings } from '@/hooks/useInterceptingRoutes';
8
8
  import { useActionSWR } from '@/libs/swr';
9
9
  import { useChatStore } from '@/store/chat';
@@ -75,8 +75,10 @@ export const useToggleRightPanelHotkey = () => {
75
75
  };
76
76
 
77
77
  export const useAddUserMessageHotkey = () => {
78
- const { send } = useSendMessage();
79
- return useHotkeyById(HotkeyEnum.AddUserMessage, () => send({ onlyAddUserMessage: true }));
78
+ const { send } = useSend();
79
+ return useHotkeyById(HotkeyEnum.AddUserMessage, () => {
80
+ send({ onlyAddUserMessage: true });
81
+ });
80
82
  };
81
83
 
82
84
  export const useClearCurrentMessagesHotkey = () => {
@@ -0,0 +1,27 @@
1
+ 'use client';
2
+
3
+ import { EditorProvider } from '@lobehub/editor/react';
4
+ import { PropsWithChildren, memo, useMemo } from 'react';
5
+ import { useTranslation } from 'react-i18next';
6
+
7
+ const Editor = memo<PropsWithChildren>(({ children }) => {
8
+ const {
9
+ i18n: { language, getResourceBundle },
10
+ } = useTranslation('editor');
11
+
12
+ const localization = useMemo(() => getResourceBundle(language, 'editor'), [language]);
13
+
14
+ return (
15
+ <EditorProvider
16
+ config={{
17
+ locale: localization,
18
+ }}
19
+ >
20
+ {children}
21
+ </EditorProvider>
22
+ );
23
+ });
24
+
25
+ Editor.displayName = 'Editor';
26
+
27
+ export default Editor;
@@ -1,7 +1,6 @@
1
1
  'use client';
2
2
 
3
3
  import { ConfigProvider } from 'antd';
4
- import { useTheme } from 'antd-style';
5
4
  import dayjs from 'dayjs';
6
5
  import { PropsWithChildren, memo, useEffect, useState } from 'react';
7
6
  import { isRtlLang } from 'rtl-detect';
@@ -10,6 +9,8 @@ import { createI18nNext } from '@/locales/create';
10
9
  import { isOnServerSide } from '@/utils/env';
11
10
  import { getAntdLocale } from '@/utils/locale';
12
11
 
12
+ import Editor from './Editor';
13
+
13
14
  const updateDayjs = async (lang: string) => {
14
15
  // load default lang
15
16
  let dayJSLocale;
@@ -36,7 +37,6 @@ const Locale = memo<LocaleLayoutProps>(({ children, defaultLang, antdLocale }) =
36
37
  const [i18n] = useState(createI18nNext(defaultLang));
37
38
  const [lang, setLang] = useState(defaultLang);
38
39
  const [locale, setLocale] = useState(antdLocale);
39
- const theme = useTheme();
40
40
 
41
41
  // if run on server side, init i18n instance everytime
42
42
  if (isOnServerSide) {
@@ -91,30 +91,10 @@ const Locale = memo<LocaleLayoutProps>(({ children, defaultLang, antdLocale }) =
91
91
  Button: {
92
92
  contentFontSizeSM: 12,
93
93
  },
94
- DatePicker: {
95
- activeBorderColor: theme.colorBorder,
96
- hoverBorderColor: theme.colorBorder,
97
- },
98
- Input: {
99
- activeBorderColor: theme.colorBorder,
100
- hoverBorderColor: theme.colorBorder,
101
- },
102
- InputNumber: {
103
- activeBorderColor: theme.colorBorder,
104
- hoverBorderColor: theme.colorBorder,
105
- },
106
- Mentions: {
107
- activeBorderColor: theme.colorBorder,
108
- hoverBorderColor: theme.colorBorder,
109
- },
110
- Select: {
111
- activeBorderColor: theme.colorBorder,
112
- hoverBorderColor: theme.colorBorder,
113
- },
114
94
  },
115
95
  }}
116
96
  >
117
- {children}
97
+ <Editor>{children}</Editor>
118
98
  </ConfigProvider>
119
99
  );
120
100
  });
@@ -1,81 +1,94 @@
1
1
  import { ModelProvider } from '@lobechat/model-runtime';
2
- import { createTRPCClient, httpBatchLink } from '@trpc/client';
2
+ import { TRPCLink, createTRPCClient, httpBatchLink } from '@trpc/client';
3
3
  import { createTRPCReact } from '@trpc/react-query';
4
+ import { observable } from '@trpc/server/observable';
4
5
  import debug from 'debug';
5
6
  import superjson from 'superjson';
6
7
 
7
8
  import { isDesktop } from '@/const/version';
8
9
  import type { LambdaRouter } from '@/server/routers/lambda';
9
10
 
10
- import { ErrorResponse } from './types';
11
-
12
11
  const log = debug('lobe-image:lambda-client');
13
12
 
14
- const links = [
15
- httpBatchLink({
16
- fetch: async (input, init) => {
17
- if (isDesktop) {
18
- const { desktopRemoteRPCFetch } = await import('@/utils/electron/desktopRemoteRPCFetch');
13
+ // handle error
14
+ const errorHandlingLink: TRPCLink<LambdaRouter> = () => {
15
+ return ({ op, next }) =>
16
+ observable((observer) =>
17
+ next(op).subscribe({
18
+ complete: () => observer.complete(),
19
+ error: async (err) => {
20
+ const showError = (op.context?.showNotification as boolean) ?? true;
21
+
22
+ if (showError) {
23
+ const status = err.data?.httpStatus as number;
24
+
25
+ const { loginRequired } = await import('@/components/Error/loginRequiredNotification');
26
+ const { fetchErrorNotification } = await import(
27
+ '@/components/Error/fetchErrorNotification'
28
+ );
29
+
30
+ switch (status) {
31
+ case 401: {
32
+ loginRequired.redirect();
33
+ break;
34
+ }
35
+
36
+ default: {
37
+ fetchErrorNotification.error({ errorMessage: err.message, status });
38
+ }
39
+ }
40
+ }
19
41
 
20
- // eslint-disable-next-line no-undef
21
- const res = await desktopRemoteRPCFetch(input as string, init as RequestInit);
42
+ observer.error(err);
43
+ },
44
+ next: (value) => observer.next(value),
45
+ }),
46
+ );
47
+ };
22
48
 
23
- if (res) return res;
24
- }
49
+ // 2. httpBatchLink
50
+ const customHttpBatchLink = httpBatchLink({
51
+ fetch: async (input, init) => {
52
+ if (isDesktop) {
53
+ const { desktopRemoteRPCFetch } = await import('@/utils/electron/desktopRemoteRPCFetch');
25
54
 
26
55
  // eslint-disable-next-line no-undef
27
- const response = await fetch(input, init as RequestInit);
28
-
29
- if (response.ok) return response;
30
-
31
- const errorRes: ErrorResponse = await response.clone().json();
32
-
33
- const { loginRequired } = await import('@/components/Error/loginRequiredNotification');
34
- const { fetchErrorNotification } = await import('@/components/Error/fetchErrorNotification');
35
-
36
- errorRes.forEach((item) => {
37
- const errorData = item.error.json;
38
- const status = errorData.data.httpStatus;
56
+ const res = await desktopRemoteRPCFetch(input as string, init as RequestInit);
57
+
58
+ if (res) return res;
59
+ }
60
+
61
+ // eslint-disable-next-line no-undef
62
+ return await fetch(input, init as RequestInit);
63
+ },
64
+ headers: async () => {
65
+ // dynamic import to avoid circular dependency
66
+ const { createHeaderWithAuth } = await import('@/services/_auth');
67
+
68
+ let provider: ModelProvider = ModelProvider.OpenAI;
69
+ // for image page, we need to get the provider from the store
70
+ log('Getting provider from store for image page: %s', location.pathname);
71
+ if (location.pathname === '/image') {
72
+ const { getImageStoreState } = await import('@/store/image');
73
+ const { imageGenerationConfigSelectors } = await import(
74
+ '@/store/image/slices/generationConfig/selectors'
75
+ );
76
+ provider = imageGenerationConfigSelectors.provider(getImageStoreState()) as ModelProvider;
77
+ log('Getting provider from store for image page: %s', provider);
78
+ }
79
+
80
+ // TODO: we need to support provider select for chat page
81
+ const headers = await createHeaderWithAuth({ provider });
82
+ log('Headers: %O', headers);
83
+ return headers;
84
+ },
85
+ maxURLLength: 2083,
86
+ transformer: superjson,
87
+ url: '/trpc/lambda',
88
+ });
39
89
 
40
- switch (status) {
41
- case 401: {
42
- loginRequired.redirect();
43
- break;
44
- }
45
- default: {
46
- fetchErrorNotification.error({ errorMessage: errorData.message, status });
47
- }
48
- }
49
- });
50
-
51
- return response;
52
- },
53
- headers: async () => {
54
- // dynamic import to avoid circular dependency
55
- const { createHeaderWithAuth } = await import('@/services/_auth');
56
-
57
- let provider: ModelProvider = ModelProvider.OpenAI;
58
- // for image page, we need to get the provider from the store
59
- log('Getting provider from store for image page: %s', location.pathname);
60
- if (location.pathname === '/image') {
61
- const { getImageStoreState } = await import('@/store/image');
62
- const { imageGenerationConfigSelectors } = await import(
63
- '@/store/image/slices/generationConfig/selectors'
64
- );
65
- provider = imageGenerationConfigSelectors.provider(getImageStoreState()) as ModelProvider;
66
- log('Getting provider from store for image page: %s', provider);
67
- }
68
-
69
- // TODO: we need to support provider select for chat page
70
- const headers = await createHeaderWithAuth({ provider });
71
- log('Headers: %O', headers);
72
- return headers;
73
- },
74
- maxURLLength: 2083,
75
- transformer: superjson,
76
- url: '/trpc/lambda',
77
- }),
78
- ];
90
+ // 3. assembly links
91
+ const links = [errorHandlingLink, customHttpBatchLink];
79
92
 
80
93
  export const lambdaClient = createTRPCClient<LambdaRouter>({
81
94
  links,
@@ -71,10 +71,11 @@ export default {
71
71
  input: {
72
72
  addAi: '添加一条 AI 消息',
73
73
  addUser: '添加一条用户消息',
74
+ errorMsg: '消息发送失败,请检查网络后重试: {{errorMsg}}',
74
75
  more: '更多',
75
76
  send: '发送',
76
- sendWithCmdEnter: '按 {{meta}} + Enter 键发送',
77
- sendWithEnter: '按 Enter 键发送',
77
+ sendWithCmdEnter: '按 <key/> 键发送',
78
+ sendWithEnter: '按 <key/> 键发送',
78
79
  stop: '停止',
79
80
  warp: '换行',
80
81
  },
@@ -236,6 +237,10 @@ export default {
236
237
  threadMessageCount: '{{messageCount}} 条消息',
237
238
  title: '子话题',
238
239
  },
240
+ toggleWideScreen: {
241
+ off: '关闭宽屏模式',
242
+ on: '开启宽屏模式',
243
+ },
239
244
  tokenDetails: {
240
245
  chats: '会话消息',
241
246
  historySummary: '历史总结',
@@ -0,0 +1,47 @@
1
+ export default {
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
+ };
@@ -6,6 +6,7 @@ import color from './color';
6
6
  import common from './common';
7
7
  import components from './components';
8
8
  import discover from './discover';
9
+ import editor from './editor';
9
10
  import electron from './electron';
10
11
  import error from './error';
11
12
  import file from './file';
@@ -37,6 +38,7 @@ const resources = {
37
38
  common,
38
39
  components,
39
40
  discover,
41
+ editor,
40
42
  electron,
41
43
  error,
42
44
  file,
@@ -4,8 +4,14 @@ import { cleanObject } from '@lobechat/utils';
4
4
  import { lambdaClient } from '@/libs/trpc/client';
5
5
 
6
6
  class AiChatService {
7
- sendMessageInServer = async (params: SendMessageServerParams) => {
8
- return lambdaClient.aiChat.sendMessageInServer.mutate(cleanObject(params));
7
+ sendMessageInServer = async (
8
+ params: SendMessageServerParams,
9
+ abortController: AbortController,
10
+ ) => {
11
+ return lambdaClient.aiChat.sendMessageInServer.mutate(cleanObject(params), {
12
+ context: { showNotification: false },
13
+ signal: abortController?.signal,
14
+ });
9
15
  };
10
16
  }
11
17