@lobehub/chat 1.124.3 → 1.124.4
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.
- package/CHANGELOG.md +25 -0
- package/changelog/v1.json +9 -0
- package/package.json +1 -1
- package/packages/model-bank/src/aiModels/qwen.ts +4 -0
- package/src/app/[variants]/(main)/chat/(workspace)/@conversation/features/ChatInput/V1Mobile/ActionBar.tsx +30 -0
- package/src/app/[variants]/(main)/chat/(workspace)/@conversation/features/ChatInput/V1Mobile/Files/index.tsx +32 -0
- package/src/app/[variants]/(main)/chat/(workspace)/@conversation/features/ChatInput/V1Mobile/InputArea/Container.tsx +41 -0
- package/src/app/[variants]/(main)/chat/(workspace)/@conversation/features/ChatInput/V1Mobile/InputArea/index.tsx +156 -0
- package/src/app/[variants]/(main)/chat/(workspace)/@conversation/features/ChatInput/V1Mobile/Send.tsx +33 -0
- package/src/app/[variants]/(main)/chat/(workspace)/@conversation/features/ChatInput/V1Mobile/index.tsx +89 -0
- package/src/app/[variants]/(main)/chat/(workspace)/@conversation/features/ChatInput/V1Mobile/useSend.ts +102 -0
- package/src/app/[variants]/(main)/chat/(workspace)/@conversation/features/ChatInput/index.tsx +1 -1
- package/src/app/[variants]/(main)/settings/_layout/Mobile/Header.tsx +4 -0
- package/src/features/ChatInput/ActionBar/SaveTopic/index.tsx +4 -1
package/CHANGELOG.md
CHANGED
@@ -2,6 +2,31 @@
|
|
2
2
|
|
3
3
|
# Changelog
|
4
4
|
|
5
|
+
### [Version 1.124.4](https://github.com/lobehub/lobe-chat/compare/v1.124.3...v1.124.4)
|
6
|
+
|
7
|
+
<sup>Released on **2025-09-06**</sup>
|
8
|
+
|
9
|
+
#### 🐛 Bug Fixes
|
10
|
+
|
11
|
+
- **misc**: Revert V1 Mobile.
|
12
|
+
|
13
|
+
<br/>
|
14
|
+
|
15
|
+
<details>
|
16
|
+
<summary><kbd>Improvements and Fixes</kbd></summary>
|
17
|
+
|
18
|
+
#### What's fixed
|
19
|
+
|
20
|
+
- **misc**: Revert V1 Mobile, closes [#9143](https://github.com/lobehub/lobe-chat/issues/9143) ([b385602](https://github.com/lobehub/lobe-chat/commit/b385602))
|
21
|
+
|
22
|
+
</details>
|
23
|
+
|
24
|
+
<div align="right">
|
25
|
+
|
26
|
+
[](#readme-top)
|
27
|
+
|
28
|
+
</div>
|
29
|
+
|
5
30
|
### [Version 1.124.3](https://github.com/lobehub/lobe-chat/compare/v1.124.2...v1.124.3)
|
6
31
|
|
7
32
|
<sup>Released on **2025-09-06**</sup>
|
package/changelog/v1.json
CHANGED
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@lobehub/chat",
|
3
|
-
"version": "1.124.
|
3
|
+
"version": "1.124.4",
|
4
4
|
"description": "Lobe Chat - an open-source, high-performance chatbot framework that supports speech synthesis, multimodal, and extensible Function Call plugin system. Supports one-click free deployment of your private ChatGPT/LLM web application.",
|
5
5
|
"keywords": [
|
6
6
|
"framework",
|
@@ -604,6 +604,7 @@ const qwenChatModels: AIChatModelCard[] = [
|
|
604
604
|
{
|
605
605
|
abilities: {
|
606
606
|
functionCall: true,
|
607
|
+
search: true,
|
607
608
|
},
|
608
609
|
config: {
|
609
610
|
deploymentName: 'qwen3-max-preview',
|
@@ -661,6 +662,9 @@ const qwenChatModels: AIChatModelCard[] = [
|
|
661
662
|
],
|
662
663
|
},
|
663
664
|
releasedAt: '2025-09-05',
|
665
|
+
settings: {
|
666
|
+
searchImpl: 'params',
|
667
|
+
},
|
664
668
|
type: 'chat',
|
665
669
|
},
|
666
670
|
{
|
@@ -0,0 +1,30 @@
|
|
1
|
+
import { ChatInputActionBar } from '@lobehub/ui/chat';
|
2
|
+
import { memo } from 'react';
|
3
|
+
|
4
|
+
import { ActionKeys, actionMap } from '@/features/ChatInput/ActionBar/config';
|
5
|
+
|
6
|
+
const RenderActionList = ({ dataSource }: { dataSource: ActionKeys[] }) => (
|
7
|
+
<>
|
8
|
+
{dataSource.map((key) => {
|
9
|
+
// @ts-ignore
|
10
|
+
const Render = actionMap[key];
|
11
|
+
return <Render key={key} />;
|
12
|
+
})}
|
13
|
+
</>
|
14
|
+
);
|
15
|
+
|
16
|
+
export interface ActionBarProps {
|
17
|
+
leftActions: ActionKeys[];
|
18
|
+
padding?: number | string;
|
19
|
+
rightActions: ActionKeys[];
|
20
|
+
}
|
21
|
+
|
22
|
+
const ActionBar = memo<ActionBarProps>(({ padding = '0 8px', leftActions, rightActions }) => (
|
23
|
+
<ChatInputActionBar
|
24
|
+
leftAddons={<RenderActionList dataSource={leftActions} />}
|
25
|
+
padding={padding}
|
26
|
+
rightAddons={<RenderActionList dataSource={rightActions} />}
|
27
|
+
/>
|
28
|
+
));
|
29
|
+
|
30
|
+
export default ActionBar;
|
@@ -0,0 +1,32 @@
|
|
1
|
+
import { PreviewGroup } from '@lobehub/ui';
|
2
|
+
import isEqual from 'fast-deep-equal';
|
3
|
+
import { memo } from 'react';
|
4
|
+
import { Flexbox } from 'react-layout-kit';
|
5
|
+
|
6
|
+
import FileItem from '@/features/ChatInput/Mobile/FilePreview/FileItem';
|
7
|
+
import { filesSelectors, useFileStore } from '@/store/file';
|
8
|
+
|
9
|
+
const Files = memo(() => {
|
10
|
+
const list = useFileStore(filesSelectors.chatUploadFileList, isEqual);
|
11
|
+
|
12
|
+
if (!list || list?.length === 0) return null;
|
13
|
+
|
14
|
+
return (
|
15
|
+
<Flexbox paddingBlock={4} style={{ position: 'relative' }}>
|
16
|
+
<Flexbox
|
17
|
+
gap={4}
|
18
|
+
horizontal
|
19
|
+
padding={'4px 8px 8px'}
|
20
|
+
style={{ overflow: 'scroll', width: '100%' }}
|
21
|
+
>
|
22
|
+
<PreviewGroup>
|
23
|
+
{list.map((i) => (
|
24
|
+
<FileItem {...i} key={i.id} loading={i.status === 'pending'} />
|
25
|
+
))}
|
26
|
+
</PreviewGroup>
|
27
|
+
</Flexbox>
|
28
|
+
</Flexbox>
|
29
|
+
);
|
30
|
+
});
|
31
|
+
|
32
|
+
export default Files;
|
@@ -0,0 +1,41 @@
|
|
1
|
+
import { css, cx } from 'antd-style';
|
2
|
+
import { FC, ReactNode, memo } from 'react';
|
3
|
+
import { Flexbox } from 'react-layout-kit';
|
4
|
+
|
5
|
+
const container = css`
|
6
|
+
height: inherit;
|
7
|
+
padding-block: 0;
|
8
|
+
padding-inline: 8px;
|
9
|
+
`;
|
10
|
+
|
11
|
+
interface InnerContainerProps {
|
12
|
+
bottomAddons?: ReactNode;
|
13
|
+
children: ReactNode;
|
14
|
+
expand?: boolean;
|
15
|
+
textAreaLeftAddons?: ReactNode;
|
16
|
+
textAreaRightAddons?: ReactNode;
|
17
|
+
topAddons?: ReactNode;
|
18
|
+
}
|
19
|
+
|
20
|
+
const InnerContainer: FC<InnerContainerProps> = memo(
|
21
|
+
({ children, expand, textAreaRightAddons, textAreaLeftAddons, bottomAddons, topAddons }) =>
|
22
|
+
expand ? (
|
23
|
+
<Flexbox className={cx(container)} gap={8}>
|
24
|
+
<Flexbox gap={8} horizontal justify={'flex-end'}>
|
25
|
+
{textAreaLeftAddons}
|
26
|
+
{textAreaRightAddons}
|
27
|
+
</Flexbox>
|
28
|
+
{children}
|
29
|
+
{topAddons}
|
30
|
+
{bottomAddons}
|
31
|
+
</Flexbox>
|
32
|
+
) : (
|
33
|
+
<Flexbox align={'flex-end'} className={cx(container)} gap={8} horizontal>
|
34
|
+
{textAreaLeftAddons}
|
35
|
+
{children}
|
36
|
+
{textAreaRightAddons}
|
37
|
+
</Flexbox>
|
38
|
+
),
|
39
|
+
);
|
40
|
+
|
41
|
+
export default InnerContainer;
|
@@ -0,0 +1,156 @@
|
|
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;
|
@@ -0,0 +1,33 @@
|
|
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;
|
@@ -0,0 +1,89 @@
|
|
1
|
+
'use client';
|
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';
|
7
|
+
import { Flexbox } from 'react-layout-kit';
|
8
|
+
|
9
|
+
import { ActionKeys } from '@/features/ChatInput/ActionBar/config';
|
10
|
+
import { useInitAgentConfig } from '@/hooks/useInitAgentConfig';
|
11
|
+
import { useChatStore } from '@/store/chat';
|
12
|
+
import { chatSelectors } from '@/store/chat/selectors';
|
13
|
+
|
14
|
+
import ActionBar from './ActionBar';
|
15
|
+
import Files from './Files';
|
16
|
+
import InputArea from './InputArea';
|
17
|
+
import SendButton from './Send';
|
18
|
+
import { useSendMessage } from './useSend';
|
19
|
+
|
20
|
+
const defaultLeftActions: ActionKeys[] = [
|
21
|
+
'model',
|
22
|
+
'search',
|
23
|
+
'fileUpload',
|
24
|
+
'knowledgeBase',
|
25
|
+
'tools',
|
26
|
+
'params',
|
27
|
+
'mainToken',
|
28
|
+
];
|
29
|
+
|
30
|
+
const defaultRightActions: ActionKeys[] = ['saveTopic', 'clear'];
|
31
|
+
|
32
|
+
const MobileChatInput = memo(() => {
|
33
|
+
const theme = useTheme();
|
34
|
+
const ref = useRef<TextAreaRef>(null);
|
35
|
+
const [expand, setExpand] = useState<boolean>(false);
|
36
|
+
const { send: sendMessage, canSend } = useSendMessage();
|
37
|
+
const { isLoading } = useInitAgentConfig();
|
38
|
+
|
39
|
+
const [loading, value, onInput, onStop] = useChatStore((s) => [
|
40
|
+
chatSelectors.isAIGenerating(s),
|
41
|
+
s.inputMessage,
|
42
|
+
s.updateInputMessage,
|
43
|
+
s.stopGenerateMessage,
|
44
|
+
]);
|
45
|
+
|
46
|
+
return (
|
47
|
+
<InputArea
|
48
|
+
expand={expand}
|
49
|
+
onInput={onInput}
|
50
|
+
onSend={() => {
|
51
|
+
setExpand(false);
|
52
|
+
|
53
|
+
sendMessage();
|
54
|
+
}}
|
55
|
+
ref={ref}
|
56
|
+
setExpand={setExpand}
|
57
|
+
style={{
|
58
|
+
background: theme.colorBgLayout,
|
59
|
+
top: expand ? 0 : undefined,
|
60
|
+
width: '100%',
|
61
|
+
zIndex: 101,
|
62
|
+
}}
|
63
|
+
textAreaRightAddons={
|
64
|
+
<SendButton disabled={!canSend} loading={loading} onSend={sendMessage} onStop={onStop} />
|
65
|
+
}
|
66
|
+
topAddons={
|
67
|
+
isLoading ? (
|
68
|
+
<Flexbox paddingInline={8}>
|
69
|
+
<Skeleton.Button active block size={'small'} />
|
70
|
+
</Flexbox>
|
71
|
+
) : (
|
72
|
+
<>
|
73
|
+
<Files />
|
74
|
+
<ActionBar
|
75
|
+
leftActions={defaultLeftActions}
|
76
|
+
padding={'0 8px'}
|
77
|
+
rightActions={defaultRightActions}
|
78
|
+
/>
|
79
|
+
</>
|
80
|
+
)
|
81
|
+
}
|
82
|
+
value={value}
|
83
|
+
/>
|
84
|
+
);
|
85
|
+
});
|
86
|
+
|
87
|
+
MobileChatInput.displayName = 'MobileChatInput';
|
88
|
+
|
89
|
+
export default MobileChatInput;
|
@@ -0,0 +1,102 @@
|
|
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
|
+
};
|
@@ -26,10 +26,14 @@ const Header = memo(() => {
|
|
26
26
|
const pathname = usePathname();
|
27
27
|
const isProvider = pathname.includes('/settings/provider/');
|
28
28
|
const providerName = useProviderName(activeSettingsKey);
|
29
|
+
const isProviderList = pathname === '/settings/provider';
|
30
|
+
const isProviderDetail = isProvider && !isProviderList;
|
29
31
|
|
30
32
|
const handleBackClick = () => {
|
31
33
|
if (isSessionActive && showMobileWorkspace) {
|
32
34
|
router.push('/chat');
|
35
|
+
} else if (isProviderDetail) {
|
36
|
+
router.push('/settings/provider');
|
33
37
|
} else {
|
34
38
|
router.push(enableAuth ? '/me/settings' : '/me');
|
35
39
|
}
|
@@ -5,13 +5,14 @@ import { memo, useState } from 'react';
|
|
5
5
|
import { useTranslation } from 'react-i18next';
|
6
6
|
import { Flexbox } from 'react-layout-kit';
|
7
7
|
|
8
|
+
import { useIsMobile } from '@/hooks/useIsMobile';
|
8
9
|
import { useActionSWR } from '@/libs/swr';
|
9
10
|
import { useChatStore } from '@/store/chat';
|
10
11
|
import { useUserStore } from '@/store/user';
|
11
12
|
import { settingsSelectors } from '@/store/user/selectors';
|
12
13
|
import { HotkeyEnum } from '@/types/hotkey';
|
13
14
|
|
14
|
-
const SaveTopic = memo
|
15
|
+
const SaveTopic = memo(() => {
|
15
16
|
const { t } = useTranslation('chat');
|
16
17
|
const hotkey = useUserStore(settingsSelectors.getHotkeyById(HotkeyEnum.SaveTopic));
|
17
18
|
const [hasTopic, openNewTopicOrSaveTopic] = useChatStore((s) => [
|
@@ -19,6 +20,8 @@ const SaveTopic = memo<{ mobile?: boolean }>(({ mobile }) => {
|
|
19
20
|
s.openNewTopicOrSaveTopic,
|
20
21
|
]);
|
21
22
|
|
23
|
+
const mobile = useIsMobile();
|
24
|
+
|
22
25
|
const { mutate, isValidating } = useActionSWR('openNewTopicOrSaveTopic', openNewTopicOrSaveTopic);
|
23
26
|
|
24
27
|
const [confirmOpened, setConfirmOpened] = useState(false);
|