@lobehub/chat 1.124.2 → 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.
Files changed (46) hide show
  1. package/.github/scripts/pr-comment.js +2 -11
  2. package/.github/workflows/desktop-pr-build.yml +12 -86
  3. package/.github/workflows/release-desktop-beta.yml +20 -91
  4. package/CHANGELOG.md +50 -0
  5. package/apps/desktop/electron-builder.js +4 -8
  6. package/changelog/v1.json +18 -0
  7. package/package.json +1 -1
  8. package/packages/const/package.json +3 -1
  9. package/packages/const/src/analytics.ts +1 -1
  10. package/packages/const/src/desktop.ts +3 -2
  11. package/packages/const/src/discover.ts +3 -2
  12. package/packages/const/src/guide.ts +2 -2
  13. package/packages/const/src/index.ts +1 -0
  14. package/packages/const/src/settings/common.ts +1 -1
  15. package/packages/const/src/settings/genUserLLMConfig.test.ts +1 -2
  16. package/packages/const/src/settings/genUserLLMConfig.ts +1 -2
  17. package/packages/const/src/settings/index.ts +1 -3
  18. package/packages/const/src/settings/knowledge.ts +1 -1
  19. package/packages/const/src/settings/llm.ts +2 -4
  20. package/packages/const/src/settings/systemAgent.ts +1 -5
  21. package/packages/const/src/settings/tts.ts +1 -1
  22. package/packages/const/src/url.ts +2 -2
  23. package/packages/model-bank/src/aiModels/qwen.ts +4 -0
  24. package/packages/model-runtime/src/higress/index.ts +2 -3
  25. package/packages/types/src/discover/index.ts +0 -8
  26. package/packages/types/src/index.ts +2 -0
  27. package/packages/types/src/tool/index.ts +1 -0
  28. package/packages/types/src/tool/tool.ts +1 -1
  29. package/packages/types/src/user/settings/index.ts +1 -2
  30. package/packages/utils/vitest.config.mts +0 -1
  31. package/src/app/(backend)/webapi/models/[provider]/pull/route.ts +0 -2
  32. package/src/app/(backend)/webapi/models/[provider]/route.ts +0 -2
  33. package/src/app/(backend)/webapi/text-to-image/[provider]/route.ts +0 -2
  34. package/src/app/(backend)/webapi/trace/route.ts +0 -2
  35. package/src/app/[variants]/(main)/chat/(workspace)/@conversation/features/ChatInput/V1Mobile/ActionBar.tsx +30 -0
  36. package/src/app/[variants]/(main)/chat/(workspace)/@conversation/features/ChatInput/V1Mobile/Files/index.tsx +32 -0
  37. package/src/app/[variants]/(main)/chat/(workspace)/@conversation/features/ChatInput/V1Mobile/InputArea/Container.tsx +41 -0
  38. package/src/app/[variants]/(main)/chat/(workspace)/@conversation/features/ChatInput/V1Mobile/InputArea/index.tsx +156 -0
  39. package/src/app/[variants]/(main)/chat/(workspace)/@conversation/features/ChatInput/V1Mobile/Send.tsx +33 -0
  40. package/src/app/[variants]/(main)/chat/(workspace)/@conversation/features/ChatInput/V1Mobile/index.tsx +89 -0
  41. package/src/app/[variants]/(main)/chat/(workspace)/@conversation/features/ChatInput/V1Mobile/useSend.ts +102 -0
  42. package/src/app/[variants]/(main)/chat/(workspace)/@conversation/features/ChatInput/index.tsx +1 -1
  43. package/src/app/[variants]/(main)/settings/_layout/Mobile/Header.tsx +4 -0
  44. package/src/features/ChatInput/ActionBar/SaveTopic/index.tsx +4 -1
  45. package/packages/const/src/settings/sync.ts +0 -5
  46. package/scripts/electronWorkflow/mergeMacReleaseFiles.ts +0 -207
@@ -1,6 +1,3 @@
1
- import { Locales } from '@/locales/resources';
2
- import { PageProps } from '@/types/next';
3
-
4
1
  export * from './assistants';
5
2
  export * from './mcp';
6
3
  export * from './models';
@@ -16,11 +13,6 @@ export enum DiscoverTab {
16
13
  Providers = 'provider',
17
14
  }
18
15
 
19
- export type DiscoverPageProps<T = string> = PageProps<
20
- { slug: T; variants: string },
21
- { hl?: Locales }
22
- >;
23
-
24
16
  export type IdentifiersResponse = {
25
17
  identifier: string;
26
18
  lastModified: string;
@@ -6,6 +6,7 @@ export * from './asyncTask';
6
6
  export * from './auth';
7
7
  export * from './chunk';
8
8
  export * from './clientDB';
9
+ export * from './discover';
9
10
  export * from './eval';
10
11
  export * from './fetch';
11
12
  export * from './hotkey';
@@ -17,6 +18,7 @@ export * from './rag';
17
18
  export * from './search';
18
19
  export * from './serverConfig';
19
20
  export * from './session';
21
+ export * from './tool';
20
22
  export * from './topic';
21
23
  export * from './user';
22
24
  export * from './user/settings';
@@ -24,3 +24,4 @@ export interface LobeTool {
24
24
  export type LobeToolRenderType = LobePluginType | 'builtin';
25
25
 
26
26
  export * from './builtin';
27
+ export * from './plugin';
@@ -1,4 +1,4 @@
1
- import { MetaData } from '@/types/meta';
1
+ import { MetaData } from '../meta';
2
2
 
3
3
  export type LobeToolType = 'builtin' | 'customPlugin' | 'plugin';
4
4
 
@@ -4,13 +4,13 @@ import { UserGeneralConfig } from './general';
4
4
  import { UserHotkeyConfig } from './hotkey';
5
5
  import { UserKeyVaults } from './keyVaults';
6
6
  import { UserModelProviderConfig } from './modelProvider';
7
- import { UserSyncSettings } from './sync';
8
7
  import { UserSystemAgentConfig } from './systemAgent';
9
8
  import { UserToolConfig } from './tool';
10
9
  import { UserTTSConfig } from './tts';
11
10
 
12
11
  export type UserDefaultAgent = LobeAgentSettings;
13
12
 
13
+ export * from './filesConfig';
14
14
  export * from './general';
15
15
  export * from './hotkey';
16
16
  export * from './keyVaults';
@@ -28,7 +28,6 @@ export interface UserSettings {
28
28
  hotkey: UserHotkeyConfig;
29
29
  keyVaults: UserKeyVaults;
30
30
  languageModel: UserModelProviderConfig;
31
- sync?: UserSyncSettings;
32
31
  systemAgent: UserSystemAgentConfig;
33
32
  tool: UserToolConfig;
34
33
  tts: UserTTSConfig;
@@ -5,7 +5,6 @@ export default defineConfig({
5
5
  test: {
6
6
  alias: {
7
7
  /* eslint-disable sort-keys-fix/sort-keys-fix */
8
- '@/types': resolve(__dirname, '../types/src'),
9
8
  '@/const': resolve(__dirname, '../const/src'),
10
9
  '@': resolve(__dirname, '../../src'),
11
10
  /* eslint-enable */
@@ -5,8 +5,6 @@ import { checkAuth } from '@/app/(backend)/middleware/auth';
5
5
  import { initModelRuntimeWithUserPayload } from '@/server/modules/ModelRuntime';
6
6
  import { createErrorResponse } from '@/utils/errorResponse';
7
7
 
8
- export const runtime = 'edge';
9
-
10
8
  export const POST = checkAuth(async (req, { params, jwtPayload }) => {
11
9
  const { provider } = await params;
12
10
 
@@ -6,8 +6,6 @@ import { checkAuth } from '@/app/(backend)/middleware/auth';
6
6
  import { initModelRuntimeWithUserPayload } from '@/server/modules/ModelRuntime';
7
7
  import { createErrorResponse } from '@/utils/errorResponse';
8
8
 
9
- export const runtime = 'edge';
10
-
11
9
  const noNeedAPIKey = (provider: string) => [ModelProvider.OpenRouter].includes(provider as any);
12
10
 
13
11
  export const GET = checkAuth(async (req, { params, jwtPayload }) => {
@@ -6,8 +6,6 @@ import { checkAuth } from '@/app/(backend)/middleware/auth';
6
6
  import { initModelRuntimeWithUserPayload } from '@/server/modules/ModelRuntime';
7
7
  import { createErrorResponse } from '@/utils/errorResponse';
8
8
 
9
- export const runtime = 'edge';
10
-
11
9
  export const preferredRegion = [
12
10
  'arn1',
13
11
  'bom1',
@@ -4,8 +4,6 @@ import { after } from 'next/server';
4
4
  import { TraceClient } from '@/libs/traces';
5
5
  import { TraceEventBasePayload, TraceEventPayloads } from '@/types/trace';
6
6
 
7
- export const runtime = 'edge';
8
-
9
7
  export const POST = async (req: Request) => {
10
8
  type RequestData = TraceEventPayloads & TraceEventBasePayload;
11
9
  const data = (await req.json()) as RequestData;
@@ -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
+ };
@@ -1,5 +1,5 @@
1
1
  import DesktopChatInput from './Desktop';
2
- import MobileChatInput from './Mobile';
2
+ import MobileChatInput from './V1Mobile';
3
3
 
4
4
  const ChatInput = ({ mobile }: { mobile: boolean }) => {
5
5
  const Input = mobile ? MobileChatInput : DesktopChatInput;
@@ -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<{ mobile?: boolean }>(({ mobile }) => {
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);
@@ -1,5 +0,0 @@
1
- import { UserSyncSettings } from '@/types/user/settings';
2
-
3
- export const DEFAULT_SYNC_CONFIG: UserSyncSettings = {
4
- webrtc: { enabled: false },
5
- };