@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.
- package/.github/scripts/pr-comment.js +2 -11
- package/.github/workflows/desktop-pr-build.yml +12 -86
- package/.github/workflows/release-desktop-beta.yml +20 -91
- package/CHANGELOG.md +50 -0
- package/apps/desktop/electron-builder.js +4 -8
- package/changelog/v1.json +18 -0
- package/package.json +1 -1
- package/packages/const/package.json +3 -1
- package/packages/const/src/analytics.ts +1 -1
- package/packages/const/src/desktop.ts +3 -2
- package/packages/const/src/discover.ts +3 -2
- package/packages/const/src/guide.ts +2 -2
- package/packages/const/src/index.ts +1 -0
- package/packages/const/src/settings/common.ts +1 -1
- package/packages/const/src/settings/genUserLLMConfig.test.ts +1 -2
- package/packages/const/src/settings/genUserLLMConfig.ts +1 -2
- package/packages/const/src/settings/index.ts +1 -3
- package/packages/const/src/settings/knowledge.ts +1 -1
- package/packages/const/src/settings/llm.ts +2 -4
- package/packages/const/src/settings/systemAgent.ts +1 -5
- package/packages/const/src/settings/tts.ts +1 -1
- package/packages/const/src/url.ts +2 -2
- package/packages/model-bank/src/aiModels/qwen.ts +4 -0
- package/packages/model-runtime/src/higress/index.ts +2 -3
- package/packages/types/src/discover/index.ts +0 -8
- package/packages/types/src/index.ts +2 -0
- package/packages/types/src/tool/index.ts +1 -0
- package/packages/types/src/tool/tool.ts +1 -1
- package/packages/types/src/user/settings/index.ts +1 -2
- package/packages/utils/vitest.config.mts +0 -1
- package/src/app/(backend)/webapi/models/[provider]/pull/route.ts +0 -2
- package/src/app/(backend)/webapi/models/[provider]/route.ts +0 -2
- package/src/app/(backend)/webapi/text-to-image/[provider]/route.ts +0 -2
- package/src/app/(backend)/webapi/trace/route.ts +0 -2
- 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/packages/const/src/settings/sync.ts +0 -5
- 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';
|
@@ -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,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
|
+
};
|
@@ -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);
|