@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
@@ -1,156 +0,0 @@
1
- import { ActionIcon, TextArea } from '@lobehub/ui';
2
- import { SafeArea } from '@lobehub/ui/mobile';
3
- import { useSize } from 'ahooks';
4
- import { createStyles } from 'antd-style';
5
- import { TextAreaRef } from 'antd/es/input/TextArea';
6
- import { ChevronDown, ChevronUp } from 'lucide-react';
7
- import { rgba } from 'polished';
8
- import { CSSProperties, ReactNode, forwardRef, useEffect, useRef, useState } from 'react';
9
- import { useTranslation } from 'react-i18next';
10
- import { Flexbox } from 'react-layout-kit';
11
-
12
- import InnerContainer from './Container';
13
-
14
- const useStyles = createStyles(({ css, token }) => {
15
- return {
16
- container: css`
17
- flex: none;
18
- padding-block: 12px 12px;
19
- border-block-start: 1px solid ${rgba(token.colorBorder, 0.25)};
20
- background: ${token.colorFillQuaternary};
21
- `,
22
- expand: css`
23
- position: absolute;
24
- height: 100%;
25
- `,
26
- expandButton: css`
27
- position: absolute;
28
- inset-inline-start: 14px;
29
- `,
30
- textarea: css`
31
- flex: 1;
32
- transition: none !important;
33
- `,
34
- };
35
- });
36
-
37
- export interface MobileChatInputAreaProps {
38
- bottomAddons?: ReactNode;
39
- className?: string;
40
- expand?: boolean;
41
- loading?: boolean;
42
- onInput?: (value: string) => void;
43
- onSend?: () => void;
44
- safeArea?: boolean;
45
- setExpand?: (expand: boolean) => void;
46
- style?: CSSProperties;
47
- textAreaLeftAddons?: ReactNode;
48
- textAreaRightAddons?: ReactNode;
49
- topAddons?: ReactNode;
50
- value: string;
51
- }
52
-
53
- const MobileChatInputArea = forwardRef<TextAreaRef, MobileChatInputAreaProps>(
54
- (
55
- {
56
- className,
57
- style,
58
- topAddons,
59
- textAreaLeftAddons,
60
- textAreaRightAddons,
61
- bottomAddons,
62
- expand = false,
63
- setExpand,
64
- onSend,
65
- onInput,
66
- loading,
67
- value,
68
- safeArea,
69
- },
70
- ref,
71
- ) => {
72
- const { t } = useTranslation('chat');
73
- const isChineseInput = useRef(false);
74
- const containerRef = useRef<HTMLDivElement>(null);
75
- const { cx, styles } = useStyles();
76
- const size = useSize(containerRef);
77
- const [showFullscreen, setShowFullscreen] = useState<boolean>(false);
78
- const [isFocused, setIsFocused] = useState<boolean>(false);
79
-
80
- useEffect(() => {
81
- if (!size?.height) return;
82
- setShowFullscreen(size.height > 72);
83
- }, [size]);
84
-
85
- const showAddons = !expand && !isFocused;
86
-
87
- return (
88
- <Flexbox
89
- className={cx(styles.container, expand && styles.expand, className)}
90
- gap={12}
91
- style={style}
92
- >
93
- {topAddons && <Flexbox style={showAddons ? {} : { display: 'none' }}>{topAddons}</Flexbox>}
94
- <Flexbox
95
- className={cx(expand && styles.expand)}
96
- ref={containerRef}
97
- style={{ position: 'relative' }}
98
- >
99
- {showFullscreen && (
100
- <ActionIcon
101
- active
102
- className={styles.expandButton}
103
- icon={expand ? ChevronDown : ChevronUp}
104
- onClick={() => setExpand?.(!expand)}
105
- size={{ blockSize: 24, borderRadius: '50%', size: 14 }}
106
- style={expand ? { top: 6 } : {}}
107
- />
108
- )}
109
- <InnerContainer
110
- bottomAddons={bottomAddons}
111
- expand={expand}
112
- textAreaLeftAddons={textAreaLeftAddons}
113
- textAreaRightAddons={textAreaRightAddons}
114
- topAddons={topAddons}
115
- >
116
- <TextArea
117
- autoSize={expand ? false : { maxRows: 6, minRows: 0 }}
118
- className={styles.textarea}
119
- onBlur={(e) => {
120
- onInput?.(e.target.value);
121
- setIsFocused(false);
122
- }}
123
- onChange={(e) => {
124
- onInput?.(e.target.value);
125
- }}
126
- onCompositionEnd={() => {
127
- isChineseInput.current = false;
128
- }}
129
- onCompositionStart={() => {
130
- isChineseInput.current = true;
131
- }}
132
- onFocus={() => setIsFocused(true)}
133
- onPressEnter={(e) => {
134
- if (!loading && !isChineseInput.current && e.shiftKey) {
135
- e.preventDefault();
136
- onSend?.();
137
- }
138
- }}
139
- placeholder={t('sendPlaceholder')}
140
- ref={ref}
141
- style={{ height: 36, paddingBlock: 6 }}
142
- value={value}
143
- variant={expand ? 'borderless' : 'filled'}
144
- />
145
- </InnerContainer>
146
- </Flexbox>
147
- {bottomAddons && (
148
- <Flexbox style={showAddons ? {} : { display: 'none' }}>{bottomAddons}</Flexbox>
149
- )}
150
- {safeArea && !isFocused && <SafeArea position={'bottom'} />}
151
- </Flexbox>
152
- );
153
- },
154
- );
155
-
156
- export default MobileChatInputArea;
@@ -1,33 +0,0 @@
1
- import { ActionIcon, type ActionIconSize, Button } from '@lobehub/ui';
2
- import { Loader2, SendHorizontal } from 'lucide-react';
3
- import { memo } from 'react';
4
-
5
- export interface MobileChatSendButtonProps {
6
- disabled?: boolean;
7
- loading?: boolean;
8
- onSend?: () => void;
9
- onStop?: () => void;
10
- }
11
-
12
- const MobileChatSendButton = memo<MobileChatSendButtonProps>(
13
- ({ loading, onStop, onSend, disabled }) => {
14
- const size: ActionIconSize = {
15
- blockSize: 36,
16
- size: 16,
17
- };
18
-
19
- return loading ? (
20
- <ActionIcon active icon={Loader2} onClick={onStop} size={size} spin />
21
- ) : (
22
- <Button
23
- disabled={disabled}
24
- icon={SendHorizontal}
25
- onClick={onSend}
26
- style={{ flex: 'none' }}
27
- type={'primary'}
28
- />
29
- );
30
- },
31
- );
32
-
33
- export default MobileChatSendButton;
@@ -1,30 +0,0 @@
1
- import { ActionIcon } from '@lobehub/ui';
2
- import { Maximize2, Minimize2 } from 'lucide-react';
3
- import { memo } from 'react';
4
-
5
- import ActionBar from '@/features/ChatInput/ActionBar';
6
- import { ActionKeys } from '@/features/ChatInput/types';
7
-
8
- interface HeaderProps {
9
- expand: boolean;
10
- leftActions: ActionKeys[];
11
- rightActions: ActionKeys[];
12
- setExpand: (expand: boolean) => void;
13
- }
14
-
15
- const Header = memo<HeaderProps>(({ expand, setExpand, leftActions, rightActions }) => (
16
- <ActionBar
17
- leftActions={leftActions}
18
- rightActions={rightActions}
19
- rightAreaEndRender={
20
- <ActionIcon
21
- icon={expand ? Minimize2 : Maximize2}
22
- onClick={() => {
23
- setExpand(!expand);
24
- }}
25
- />
26
- }
27
- />
28
- ));
29
-
30
- export default Header;
@@ -1,143 +0,0 @@
1
- import { TextArea } from '@lobehub/ui';
2
- import { createStyles } from 'antd-style';
3
- import { TextAreaRef } from 'antd/es/input/TextArea';
4
- import { RefObject, memo, useEffect, useRef } from 'react';
5
- import { useHotkeysContext } from 'react-hotkeys-hook';
6
- import { useTranslation } from 'react-i18next';
7
-
8
- import { isDesktop } from '@/const/version';
9
- import { useUserStore } from '@/store/user';
10
- import { preferenceSelectors } from '@/store/user/selectors';
11
- import { HotkeyEnum } from '@/types/hotkey';
12
- import { isCommandPressed } from '@/utils/keyboard';
13
-
14
- import { useAutoFocus } from '../useAutoFocus';
15
-
16
- const useStyles = createStyles(({ css }) => {
17
- return {
18
- textarea: css`
19
- resize: none !important;
20
-
21
- height: 100% !important;
22
- padding-block: 0;
23
- padding-inline: 16px;
24
-
25
- line-height: 1.5;
26
-
27
- box-shadow: none !important;
28
- `,
29
- textareaContainer: css`
30
- position: relative;
31
- flex: 1;
32
- `,
33
- };
34
- });
35
-
36
- interface InputAreaProps {
37
- loading?: boolean;
38
- onChange: (string: string) => void;
39
- onSend: () => void;
40
- value: string;
41
- }
42
-
43
- const InputArea = memo<InputAreaProps>(({ onSend, value, loading, onChange }) => {
44
- const { enableScope, disableScope } = useHotkeysContext();
45
- const { t } = useTranslation('chat');
46
- const { styles } = useStyles();
47
-
48
- const ref = useRef<TextAreaRef>(null);
49
- const isChineseInput = useRef(false);
50
-
51
- const useCmdEnterToSend = useUserStore(preferenceSelectors.useCmdEnterToSend);
52
-
53
- useAutoFocus(ref as RefObject<TextAreaRef>);
54
-
55
- const hasValue = !!value;
56
-
57
- useEffect(() => {
58
- const fn = (e: BeforeUnloadEvent) => {
59
- if (hasValue) {
60
- // set returnValue to trigger alert modal
61
- // Note: No matter what value is set, the browser will display the standard text
62
- e.returnValue = '你有正在输入中的内容,确定要离开吗?';
63
- }
64
- };
65
-
66
- window.addEventListener('beforeunload', fn);
67
- return () => {
68
- window.removeEventListener('beforeunload', fn);
69
- };
70
- }, [hasValue]);
71
-
72
- return (
73
- <div className={styles.textareaContainer}>
74
- <TextArea
75
- autoFocus
76
- className={styles.textarea}
77
- onBlur={(e) => {
78
- onChange?.(e.target.value);
79
- disableScope(HotkeyEnum.AddUserMessage);
80
- }}
81
- onChange={(e) => {
82
- onChange?.(e.target.value);
83
- }}
84
- onCompositionEnd={() => {
85
- isChineseInput.current = false;
86
- }}
87
- onCompositionStart={() => {
88
- isChineseInput.current = true;
89
- }}
90
- onContextMenu={async (e) => {
91
- if (isDesktop) {
92
- e.preventDefault();
93
- const textArea = ref.current?.resizableTextArea?.textArea;
94
- const hasSelection = textArea && textArea.selectionStart !== textArea.selectionEnd;
95
- const { electronSystemService } = await import('@/services/electron/system');
96
-
97
- electronSystemService.showContextMenu('editor', {
98
- hasSelection: !!hasSelection,
99
- value: value,
100
- });
101
- }
102
- }}
103
- onFocus={() => {
104
- enableScope(HotkeyEnum.AddUserMessage);
105
- }}
106
- onPressEnter={(e) => {
107
- if (loading || e.altKey || e.shiftKey || isChineseInput.current) return;
108
-
109
- // eslint-disable-next-line unicorn/consistent-function-scoping
110
- const send = () => {
111
- // avoid inserting newline when sending message.
112
- // refs: https://github.com/lobehub/lobe-chat/pull/989
113
- e.preventDefault();
114
-
115
- onSend();
116
- };
117
- const commandKey = isCommandPressed(e);
118
-
119
- // when user like cmd + enter to send message
120
- if (useCmdEnterToSend) {
121
- if (commandKey) send();
122
- } else {
123
- // cmd + enter to wrap
124
- if (commandKey) {
125
- onChange?.((e.target as any).value + '\n');
126
- return;
127
- }
128
-
129
- send();
130
- }
131
- }}
132
- placeholder={t('sendPlaceholder')}
133
- ref={ref}
134
- value={value}
135
- variant={'borderless'}
136
- />
137
- </div>
138
- );
139
- });
140
-
141
- InputArea.displayName = 'DesktopInputArea';
142
-
143
- export default InputArea;
@@ -1,45 +0,0 @@
1
- import { act, renderHook } from '@testing-library/react';
2
- import { describe, expect, it, vi } from 'vitest';
3
-
4
- import { useChatStore } from '@/store/chat';
5
- import { chatSelectors } from '@/store/chat/selectors';
6
-
7
- import { useAutoFocus } from '../useAutoFocus';
8
-
9
- vi.mock('zustand/traditional');
10
-
11
- describe('useAutoFocus', () => {
12
- it('should focus the input when chatKey changes', () => {
13
- const focusMock = vi.fn();
14
- const inputRef = { current: { focus: focusMock } };
15
-
16
- act(() => {
17
- useChatStore.setState({ activeId: '1', activeTopicId: '2' });
18
- });
19
-
20
- renderHook(() => useAutoFocus(inputRef as any));
21
-
22
- expect(focusMock).toHaveBeenCalledTimes(1);
23
-
24
- act(() => {
25
- useChatStore.setState({ activeId: '1', activeTopicId: '3' });
26
- });
27
-
28
- renderHook(() => useAutoFocus(inputRef as any));
29
-
30
- // I don't know why its 3, but is large than 2 is fine
31
- expect(focusMock).toHaveBeenCalledTimes(3);
32
- });
33
-
34
- it('should not focus the input if inputRef is not available', () => {
35
- const inputRef = { current: null };
36
-
37
- act(() => {
38
- useChatStore.setState({ activeId: '1', activeTopicId: '2' });
39
- });
40
-
41
- renderHook(() => useAutoFocus(inputRef as any));
42
-
43
- expect(inputRef.current).toBeNull();
44
- });
45
- });
@@ -1,13 +0,0 @@
1
- import { TextAreaRef } from 'antd/es/input/TextArea';
2
- import { RefObject, useEffect } from 'react';
3
-
4
- import { useChatStore } from '@/store/chat';
5
- import { chatSelectors } from '@/store/chat/selectors';
6
-
7
- export const useAutoFocus = (inputRef: RefObject<TextAreaRef>) => {
8
- const chatKey = useChatStore(chatSelectors.currentChatKey);
9
-
10
- useEffect(() => {
11
- inputRef.current?.focus();
12
- }, [chatKey]);
13
- };
@@ -1,102 +0,0 @@
1
- import { useAnalytics } from '@lobehub/analytics/react';
2
- import { useCallback, 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 { useChatStore } from '@/store/chat';
8
- import { chatSelectors, topicSelectors } from '@/store/chat/selectors';
9
- import { fileChatSelectors, useFileStore } from '@/store/file';
10
- import { getUserStoreState } from '@/store/user';
11
- import { SendMessageParams } from '@/types/message';
12
-
13
- export type UseSendMessageParams = Pick<
14
- SendMessageParams,
15
- 'onlyAddUserMessage' | 'isWelcomeQuestion'
16
- >;
17
-
18
- export const useSendMessage = () => {
19
- const [sendMessage, updateInputMessage] = useChatStore((s) => [
20
- s.sendMessage,
21
- s.updateInputMessage,
22
- ]);
23
- const { analytics } = useAnalytics();
24
- const checkGeminiChineseWarning = useGeminiChineseWarning();
25
-
26
- const clearChatUploadFileList = useFileStore((s) => s.clearChatUploadFileList);
27
-
28
- const isUploadingFiles = useFileStore(fileChatSelectors.isUploadingFiles);
29
- const isSendButtonDisabledByMessage = useChatStore(chatSelectors.isSendButtonDisabledByMessage);
30
-
31
- const canSend = !isUploadingFiles && !isSendButtonDisabledByMessage;
32
-
33
- const send = useCallback(async (params: UseSendMessageParams = {}) => {
34
- const store = useChatStore.getState();
35
- if (chatSelectors.isAIGenerating(store)) return;
36
-
37
- // if uploading file or send button is disabled by message, then we should not send the message
38
- const isUploadingFiles = fileChatSelectors.isUploadingFiles(useFileStore.getState());
39
- const isSendButtonDisabledByMessage = chatSelectors.isSendButtonDisabledByMessage(
40
- useChatStore.getState(),
41
- );
42
-
43
- const canSend = !isUploadingFiles && !isSendButtonDisabledByMessage;
44
- if (!canSend) return;
45
-
46
- const fileList = fileChatSelectors.chatUploadFileList(useFileStore.getState());
47
- // if there is no message and no image, then we should not send the message
48
- if (!store.inputMessage && fileList.length === 0) return;
49
-
50
- // Check for Chinese text warning with Gemini model
51
- const agentStore = getAgentStoreState();
52
- const currentModel = agentSelectors.currentAgentModel(agentStore);
53
- const shouldContinue = await checkGeminiChineseWarning({
54
- model: currentModel,
55
- prompt: store.inputMessage,
56
- scenario: 'chat',
57
- });
58
-
59
- if (!shouldContinue) return;
60
-
61
- sendMessage({
62
- files: fileList,
63
- message: store.inputMessage,
64
- ...params,
65
- });
66
-
67
- updateInputMessage('');
68
- clearChatUploadFileList();
69
-
70
- // 获取分析数据
71
- const userStore = getUserStoreState();
72
-
73
- // 直接使用现有数据结构判断消息类型
74
- const hasImages = fileList.some((file) => file.file?.type?.startsWith('image'));
75
- const messageType = fileList.length === 0 ? 'text' : hasImages ? 'image' : 'file';
76
-
77
- analytics?.track({
78
- name: 'send_message',
79
- properties: {
80
- chat_id: store.activeId || 'unknown',
81
- current_topic: topicSelectors.currentActiveTopic(store)?.title || null,
82
- has_attachments: fileList.length > 0,
83
- history_message_count: chatSelectors.activeBaseChats(store).length,
84
- message: store.inputMessage,
85
- message_length: store.inputMessage.length,
86
- message_type: messageType,
87
- selected_model: agentSelectors.currentAgentModel(agentStore),
88
- session_id: store.activeId || 'inbox', // 当前活跃的会话ID
89
- user_id: userStore.user?.id || 'anonymous',
90
- },
91
- });
92
- // const hasSystemRole = agentSelectors.hasSystemRole(useAgentStore.getState());
93
- // const agentSetting = useAgentStore.getState().agentSettingInstance;
94
-
95
- // // if there is a system role, then we need to use agent setting instance to autocomplete agent meta
96
- // if (hasSystemRole && !!agentSetting) {
97
- // agentSetting.autocompleteAllMeta();
98
- // }
99
- }, []);
100
-
101
- return useMemo(() => ({ canSend, send }), [canSend]);
102
- };
@@ -1,90 +0,0 @@
1
- import { Button } from '@lobehub/ui';
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
- paddingInline={16}
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;
@@ -1,30 +0,0 @@
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;
@@ -1,18 +0,0 @@
1
- export type ErrorResponse = ErrorItem[];
2
-
3
- export interface ErrorItem {
4
- error: {
5
- json: {
6
- code: number;
7
- data: Data;
8
- message: string;
9
- };
10
- };
11
- }
12
-
13
- export interface Data {
14
- code: string;
15
- httpStatus: number;
16
- path: string;
17
- stack: string;
18
- }