@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
@@ -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}
@@ -1,4 +1,4 @@
1
- import { ActionIcon, Alert, Button, Dropdown, Highlighter } from '@lobehub/ui';
1
+ import { Alert, Button, Highlighter } from '@lobehub/ui';
2
2
  import { createStyles } from 'antd-style';
3
3
  import { Mic, MicOff } from 'lucide-react';
4
4
  import { memo, useState } from 'react';
@@ -7,6 +7,8 @@ import { Flexbox } from 'react-layout-kit';
7
7
 
8
8
  import { ChatMessageError } from '@/types/message';
9
9
 
10
+ import Action from '../components/Action';
11
+
10
12
  const useStyles = createStyles(({ css, token }) => ({
11
13
  recording: css`
12
14
  width: 8px;
@@ -49,35 +51,36 @@ const CommonSTT = memo<{
49
51
  };
50
52
 
51
53
  return (
52
- <Dropdown
53
- menu={{
54
- // @ts-expect-error 等待 antd 修复
55
- activeKey: 'time',
56
- items: [
57
- {
58
- key: 'title',
59
- label: (
60
- <Flexbox>
61
- <div style={{ fontWeight: 'bolder' }}>{t('stt.action')}</div>
62
- </Flexbox>
63
- ),
64
- },
65
- {
66
- key: 'time',
67
- label: (
68
- <Flexbox align={'center'} gap={8} horizontal>
69
- <div className={styles.recording} />
70
- {time > 0 ? formattedTime : t(isRecording ? 'stt.loading' : 'stt.prettifying')}
71
- </Flexbox>
72
- ),
73
- },
74
- ],
75
- }}
76
- onOpenChange={handleDropdownVisibleChange}
77
- open={dropdownOpen || !!error || isRecording || isLoading}
78
- placement={mobile ? 'topRight' : 'top'}
79
- popupRender={
80
- error
54
+ <Action
55
+ active={isRecording}
56
+ dropdown={{
57
+ menu: {
58
+ // @ts-expect-error 等待 antd 修复
59
+ activeKey: 'time',
60
+ items: [
61
+ {
62
+ key: 'title',
63
+ label: (
64
+ <Flexbox>
65
+ <div style={{ fontWeight: 'bolder' }}>{t('stt.action')}</div>
66
+ </Flexbox>
67
+ ),
68
+ },
69
+ {
70
+ key: 'time',
71
+ label: (
72
+ <Flexbox align={'center'} gap={8} horizontal>
73
+ <div className={styles.recording} />
74
+ {time > 0 ? formattedTime : t(isRecording ? 'stt.loading' : 'stt.prettifying')}
75
+ </Flexbox>
76
+ ),
77
+ },
78
+ ],
79
+ },
80
+ onOpenChange: handleDropdownVisibleChange,
81
+ open: dropdownOpen || !!error || isRecording || isLoading,
82
+ placement: mobile ? 'topRight' : 'top',
83
+ popupRender: error
81
84
  ? () => (
82
85
  <Alert
83
86
  action={
@@ -103,23 +106,14 @@ const CommonSTT = memo<{
103
106
  type="error"
104
107
  />
105
108
  )
106
- : undefined
107
- }
108
- trigger={['click']}
109
- >
110
- <ActionIcon
111
- active={isRecording}
112
- icon={isLoading ? MicOff : Mic}
113
- onClick={handleTriggerStartStop}
114
- size={mobile ? { blockSize: 36, size: 16 } : 22}
115
- style={{ flex: 'none' }}
116
- title={dropdownOpen ? '' : desc}
117
- tooltipProps={{
118
- placement: 'bottom',
119
- }}
120
- variant={mobile ? 'outlined' : 'borderless'}
121
- />
122
- </Dropdown>
109
+ : undefined,
110
+ trigger: ['click'],
111
+ }}
112
+ icon={isLoading ? MicOff : Mic}
113
+ onClick={handleTriggerStartStop}
114
+ title={dropdownOpen ? undefined : desc}
115
+ variant={mobile ? 'outlined' : 'borderless'}
116
+ />
123
117
  );
124
118
  },
125
119
  );
@@ -1,4 +1,4 @@
1
- import { ActionIcon, Button, Hotkey, Tooltip } from '@lobehub/ui';
1
+ import { ActionIcon, Hotkey } from '@lobehub/ui';
2
2
  import { Popconfirm } from 'antd';
3
3
  import { LucideGalleryVerticalEnd, LucideMessageSquarePlus } from 'lucide-react';
4
4
  import { memo, useState } from 'react';
@@ -54,11 +54,22 @@ const SaveTopic = memo<{ mobile?: boolean }>(({ mobile }) => {
54
54
  );
55
55
  } else {
56
56
  return (
57
- <Tooltip hotkey={hotkey} title={desc}>
58
- <Button aria-label={desc} icon={icon} loading={isValidating} onClick={() => mutate()} />
59
- </Tooltip>
57
+ <ActionIcon
58
+ aria-label={desc}
59
+ icon={icon}
60
+ loading={isValidating}
61
+ onClick={() => mutate()}
62
+ size={{ blockSize: 32, size: 16, strokeWidth: 2.3 }}
63
+ title={desc}
64
+ tooltipProps={{
65
+ hotkey,
66
+ }}
67
+ variant={'outlined'}
68
+ />
60
69
  );
61
70
  }
62
71
  });
63
72
 
73
+ SaveTopic.displayName = 'SaveTopic';
74
+
64
75
  export default SaveTopic;
@@ -0,0 +1,22 @@
1
+ import { TypeIcon } from 'lucide-react';
2
+ import { memo } from 'react';
3
+ import { useTranslation } from 'react-i18next';
4
+
5
+ import { useChatInputStore } from '../../store';
6
+ import Action from '../components/Action';
7
+
8
+ const Typo = memo(() => {
9
+ const { t } = useTranslation('editor');
10
+ const [showTypoBar, setShowTypoBar] = useChatInputStore((s) => [s.showTypoBar, s.setShowTypoBar]);
11
+
12
+ return (
13
+ <Action
14
+ active={showTypoBar}
15
+ icon={TypeIcon}
16
+ onClick={() => setShowTypoBar(!showTypoBar)}
17
+ title={t(showTypoBar ? 'actions.typobar.off' : 'actions.typobar.on')}
18
+ />
19
+ );
20
+ });
21
+
22
+ export default Typo;
@@ -51,6 +51,10 @@ const Action = memo<ActionProps>(
51
51
  placement: 'bottom',
52
52
  }}
53
53
  {...rest}
54
+ size={{
55
+ blockSize: 36,
56
+ size: 20,
57
+ }}
54
58
  />
55
59
  );
56
60
 
@@ -4,9 +4,11 @@ import Knowledge from './Knowledge';
4
4
  import Model from './Model';
5
5
  import Params from './Params';
6
6
  import STT from './STT';
7
+ import SaveTopic from './SaveTopic';
7
8
  import Search from './Search';
8
9
  import { MainToken, PortalToken } from './Token';
9
10
  import Tools from './Tools';
11
+ import Typo from './Typo';
10
12
  import Upload from './Upload';
11
13
 
12
14
  export const actionMap = {
@@ -18,10 +20,14 @@ export const actionMap = {
18
20
  model: Model,
19
21
  params: Params,
20
22
  portalToken: PortalToken,
23
+ saveTopic: SaveTopic,
21
24
  search: Search,
22
25
  stt: STT,
23
26
  temperature: Params,
24
27
  tools: Tools,
28
+ typo: Typo,
25
29
  } as const;
26
30
 
27
- export type ActionKeys = keyof typeof actionMap;
31
+ export type ActionKey = keyof typeof actionMap;
32
+
33
+ export type ActionKeys = ActionKey | ActionKey[] | '---';
@@ -1,55 +1,44 @@
1
- import { ChatInputActionBar } from '@lobehub/ui/chat';
2
- import { ReactNode, memo } from 'react';
1
+ import { ChatInputActions, type ChatInputActionsProps } from '@lobehub/editor/react';
2
+ import { memo, useMemo } from 'react';
3
3
 
4
- import { ActionKeys, actionMap } from './config';
4
+ import { ActionKeys, actionMap } from '../ActionBar/config';
5
+ import { useChatInputStore } from '../store';
5
6
 
6
- const RenderActionList = ({ dataSource }: { dataSource: ActionKeys[] }) => (
7
- <>
8
- {dataSource.map((key) => {
9
- const Render = actionMap[key];
10
- return <Render key={key} />;
11
- })}
12
- </>
13
- );
14
-
15
- export interface ActionBarProps {
16
- leftActions: ActionKeys[];
17
- leftAreaEndRender?: ReactNode;
18
- leftAreaStartRender?: ReactNode;
19
- padding?: number | string;
20
- rightActions: ActionKeys[];
21
- rightAreaEndRender?: ReactNode;
22
- rightAreaStartRender?: ReactNode;
23
- }
24
-
25
- const ActionBar = memo<ActionBarProps>(
26
- ({
27
- padding = '0 8px',
28
- rightAreaStartRender,
29
- rightAreaEndRender,
30
- leftAreaStartRender,
31
- leftAreaEndRender,
32
- leftActions,
33
- rightActions,
34
- }) => (
35
- <ChatInputActionBar
36
- leftAddons={
37
- <>
38
- {leftAreaStartRender}
39
- <RenderActionList dataSource={leftActions} />
40
- {leftAreaEndRender}
41
- </>
7
+ const mapActionsToItems = (keys: ActionKeys[]): ChatInputActionsProps['items'] =>
8
+ keys.map((actionKey, index) => {
9
+ if (typeof actionKey === 'string') {
10
+ if (actionKey === '---') {
11
+ return {
12
+ key: `divider-${index}`,
13
+ type: 'divider',
14
+ };
42
15
  }
43
- padding={padding}
44
- rightAddons={
45
- <>
46
- {rightAreaStartRender}
47
- <RenderActionList dataSource={rightActions} />
48
- {rightAreaEndRender}
49
- </>
50
- }
51
- />
52
- ),
53
- );
16
+ const Render = actionMap[actionKey];
17
+ return {
18
+ alwaysDisplay: actionKey === 'mainToken',
19
+ children: <Render key={actionKey} />,
20
+ key: actionKey,
21
+ };
22
+ } else {
23
+ return {
24
+ children: actionKey.map((groupActionKey) => {
25
+ const Render = actionMap[groupActionKey];
26
+ return {
27
+ children: <Render key={groupActionKey} />,
28
+ key: groupActionKey,
29
+ };
30
+ }),
31
+ key: `group-${index}`,
32
+ type: 'collapse',
33
+ };
34
+ }
35
+ });
36
+
37
+ const ActionToolbar = memo(() => {
38
+ const leftActions = useChatInputStore((s) => s.leftActions);
39
+ const mobile = useChatInputStore((s) => s.mobile);
40
+ const items = useMemo(() => mapActionsToItems(leftActions), [leftActions]);
41
+ return <ChatInputActions collapseOffset={mobile ? 48 : 80} items={items} />;
42
+ });
54
43
 
55
- export default ActionBar;
44
+ export default ActionToolbar;
@@ -0,0 +1,54 @@
1
+ import { useEditor } from '@lobehub/editor/react';
2
+ import { ReactNode, memo, useRef } from 'react';
3
+
4
+ import StoreUpdater, { StoreUpdaterProps } from './StoreUpdater';
5
+ import { Provider, createStore } from './store';
6
+
7
+ interface ChatInputProviderProps extends StoreUpdaterProps {
8
+ children: ReactNode;
9
+ }
10
+
11
+ export const ChatInputProvider = memo<ChatInputProviderProps>(
12
+ ({
13
+ children,
14
+ leftActions,
15
+ rightActions,
16
+ mobile,
17
+ sendButtonProps,
18
+ onSend,
19
+ sendMenu,
20
+ chatInputEditorRef,
21
+ onMarkdownContentChange,
22
+ }) => {
23
+ const editor = useEditor();
24
+ const slashMenuRef = useRef<HTMLDivElement>(null);
25
+
26
+ return (
27
+ <Provider
28
+ createStore={() =>
29
+ createStore({
30
+ editor,
31
+ leftActions,
32
+ mobile,
33
+ rightActions,
34
+ sendButtonProps,
35
+ sendMenu,
36
+ slashMenuRef,
37
+ })
38
+ }
39
+ >
40
+ <StoreUpdater
41
+ chatInputEditorRef={chatInputEditorRef}
42
+ leftActions={leftActions}
43
+ mobile={mobile}
44
+ onMarkdownContentChange={onMarkdownContentChange}
45
+ onSend={onSend}
46
+ rightActions={rightActions}
47
+ sendButtonProps={sendButtonProps}
48
+ sendMenu={sendMenu}
49
+ />
50
+ {children}
51
+ </Provider>
52
+ );
53
+ },
54
+ );
@@ -1,4 +1,4 @@
1
- import { ActionIcon, Text } from '@lobehub/ui';
1
+ import { ActionIcon, Block, Text } from '@lobehub/ui';
2
2
  import { createStyles } from 'antd-style';
3
3
  import { Trash2Icon } from 'lucide-react';
4
4
  import { memo } from 'react';
@@ -26,17 +26,13 @@ const useStyles = createStyles(({ css, token }) => ({
26
26
  ${token.boxShadowTertiary};
27
27
  `,
28
28
  container: css`
29
+ user-select: none;
30
+
29
31
  position: relative;
30
32
 
31
33
  width: 180px;
32
34
  height: 64px;
33
35
  border-radius: 8px;
34
-
35
- background: ${token.colorBgContainer};
36
-
37
- :hover {
38
- background: ${token.colorBgElevated};
39
- }
40
36
  `,
41
37
  image: css`
42
38
  margin-block: 0 !important;
@@ -58,15 +54,28 @@ const FileItem = memo<FileItemProps>((props) => {
58
54
  const [removeChatUploadFile] = useFileStore((s) => [s.removeChatUploadFile]);
59
55
 
60
56
  return (
61
- <Flexbox align={'center'} className={styles.container} horizontal>
57
+ <Block align={'center'} className={styles.container} horizontal variant={'outlined'}>
62
58
  <Center flex={1} height={64} padding={4} style={{ maxWidth: 64 }}>
63
59
  <Content {...props} />
64
60
  </Center>
65
61
  <Flexbox flex={1} gap={4} style={{ paddingBottom: 4, paddingInline: 4 }}>
66
- <Text ellipsis={{ tooltip: true }} style={{ fontSize: 12, maxWidth: 100 }}>
62
+ <Text
63
+ ellipsis={{
64
+ tooltip: {
65
+ styles: {
66
+ body: {
67
+ fontSize: 12,
68
+ whiteSpace: 'balance',
69
+ wordBreak: 'break-all',
70
+ },
71
+ },
72
+ title: file.name,
73
+ },
74
+ }}
75
+ style={{ fontSize: 12, maxWidth: 88 }}
76
+ >
67
77
  {file.name}
68
78
  </Text>
69
-
70
79
  <UploadDetail size={file.size} status={status} tasks={tasks} uploadState={uploadState} />
71
80
  </Flexbox>
72
81
  <Flexbox className={styles.actions}>
@@ -80,7 +89,7 @@ const FileItem = memo<FileItemProps>((props) => {
80
89
  title={t('delete', { ns: 'common' })}
81
90
  />
82
91
  </Flexbox>
83
- </Flexbox>
92
+ </Block>
84
93
  );
85
94
  });
86
95