@lobehub/chat 0.100.3 → 0.100.5
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 +50 -0
- package/package.json +4 -4
- package/src/app/chat/(desktop)/features/ChatInput/Footer.tsx +34 -6
- package/src/app/chat/(desktop)/features/ChatInput/InputArea.tsx +69 -0
- package/src/app/chat/(desktop)/features/ChatInput/index.tsx +37 -4
- package/src/app/chat/(desktop)/features/ChatInput/useSend.ts +17 -0
- package/src/app/chat/(mobile)/features/ChatInput/Mobile.tsx +79 -0
- package/src/app/chat/(mobile)/features/{ChatInput.tsx → ChatInput/index.tsx} +19 -3
- package/src/app/chat/(mobile)/features/ChatInput/style.mobile.ts +19 -0
- package/src/app/chat/features/ChatInput/ActionBar/Clear.tsx +39 -0
- package/src/app/chat/features/ChatInput/ActionBar/History.tsx +64 -0
- package/src/app/chat/features/ChatInput/ActionBar/ModelSwitch.tsx +42 -0
- package/src/app/chat/features/ChatInput/ActionBar/Temperature.tsx +47 -0
- package/src/app/chat/features/{ChatInputContent/ActionBar/Token.tsx → ChatInput/ActionBar/Token/TokenTag.tsx} +4 -4
- package/src/app/chat/features/ChatInput/ActionBar/Token/index.tsx +21 -0
- package/src/app/chat/features/ChatInput/ActionBar/config.ts +19 -0
- package/src/app/chat/features/ChatInput/ActionBar/index.tsx +35 -0
- package/src/app/chat/features/{ChatInputContent → ChatInput}/Topic/index.tsx +1 -0
- package/src/store/session/slices/chat/actions/message.ts +7 -3
- package/src/store/session/slices/chat/initialState.ts +4 -1
- package/src/store/session/slices/chat/selectors/chat.ts +3 -2
- package/src/app/chat/features/ChatInputContent/ActionBar/ActionLeft.tsx +0 -117
- package/src/app/chat/features/ChatInputContent/ActionBar/ActionRight.tsx +0 -48
- package/src/app/chat/features/ChatInputContent/index.tsx +0 -71
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,56 @@
|
|
|
2
2
|
|
|
3
3
|
# Changelog
|
|
4
4
|
|
|
5
|
+
### [Version 0.100.5](https://github.com/lobehub/lobe-chat/compare/v0.100.4...v0.100.5)
|
|
6
|
+
|
|
7
|
+
<sup>Released on **2023-11-11**</sup>
|
|
8
|
+
|
|
9
|
+
#### ♻ Code Refactoring
|
|
10
|
+
|
|
11
|
+
- **misc**: Refactor the input area to suit the files upload feature.
|
|
12
|
+
|
|
13
|
+
<br/>
|
|
14
|
+
|
|
15
|
+
<details>
|
|
16
|
+
<summary><kbd>Improvements and Fixes</kbd></summary>
|
|
17
|
+
|
|
18
|
+
#### Code refactoring
|
|
19
|
+
|
|
20
|
+
- **misc**: Refactor the input area to suit the files upload feature, closes [#442](https://github.com/lobehub/lobe-chat/issues/442) ([57a61fd](https://github.com/lobehub/lobe-chat/commit/57a61fd))
|
|
21
|
+
|
|
22
|
+
</details>
|
|
23
|
+
|
|
24
|
+
<div align="right">
|
|
25
|
+
|
|
26
|
+
[](#readme-top)
|
|
27
|
+
|
|
28
|
+
</div>
|
|
29
|
+
|
|
30
|
+
### [Version 0.100.4](https://github.com/lobehub/lobe-chat/compare/v0.100.3...v0.100.4)
|
|
31
|
+
|
|
32
|
+
<sup>Released on **2023-11-11**</sup>
|
|
33
|
+
|
|
34
|
+
#### 🐛 Bug Fixes
|
|
35
|
+
|
|
36
|
+
- **misc**: Hotkey disabled in form tags.
|
|
37
|
+
|
|
38
|
+
<br/>
|
|
39
|
+
|
|
40
|
+
<details>
|
|
41
|
+
<summary><kbd>Improvements and Fixes</kbd></summary>
|
|
42
|
+
|
|
43
|
+
#### What's fixed
|
|
44
|
+
|
|
45
|
+
- **misc**: Hotkey disabled in form tags ([165888f](https://github.com/lobehub/lobe-chat/commit/165888f))
|
|
46
|
+
|
|
47
|
+
</details>
|
|
48
|
+
|
|
49
|
+
<div align="right">
|
|
50
|
+
|
|
51
|
+
[](#readme-top)
|
|
52
|
+
|
|
53
|
+
</div>
|
|
54
|
+
|
|
5
55
|
### [Version 0.100.3](https://github.com/lobehub/lobe-chat/compare/v0.100.2...v0.100.3)
|
|
6
56
|
|
|
7
57
|
<sup>Released on **2023-11-09**</sup>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lobehub/chat",
|
|
3
|
-
"version": "0.100.
|
|
3
|
+
"version": "0.100.5",
|
|
4
4
|
"description": "An open-source, extensible (Function Calling), high-performance chatbot framework. It supports one-click free deployment of your private ChatGPT/LLM web application",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"chatbot",
|
|
@@ -92,7 +92,7 @@
|
|
|
92
92
|
"lucide-react": "latest",
|
|
93
93
|
"modern-screenshot": "^4",
|
|
94
94
|
"nanoid": "^5",
|
|
95
|
-
"next": "
|
|
95
|
+
"next": "14.0.1",
|
|
96
96
|
"openai": "~4.15.0",
|
|
97
97
|
"polished": "^4",
|
|
98
98
|
"posthog-js": "^1",
|
|
@@ -124,8 +124,8 @@
|
|
|
124
124
|
"@ducanh2912/next-pwa": "^9.7.2",
|
|
125
125
|
"@lobehub/i18n-cli": "latest",
|
|
126
126
|
"@lobehub/lint": "latest",
|
|
127
|
-
"@next/bundle-analyzer": "
|
|
128
|
-
"@next/eslint-plugin-next": "
|
|
127
|
+
"@next/bundle-analyzer": "14.0.1",
|
|
128
|
+
"@next/eslint-plugin-next": "14.0.1",
|
|
129
129
|
"@testing-library/jest-dom": "^6",
|
|
130
130
|
"@testing-library/react": "^14",
|
|
131
131
|
"@types/chroma-js": "^2",
|
|
@@ -1,18 +1,37 @@
|
|
|
1
1
|
import { Icon } from '@lobehub/ui';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { Button } from 'antd';
|
|
3
|
+
import { createStyles } from 'antd-style';
|
|
4
|
+
import { ArrowBigUp, CornerDownLeft, Loader2 } from 'lucide-react';
|
|
4
5
|
import { memo } from 'react';
|
|
5
6
|
import { useTranslation } from 'react-i18next';
|
|
6
7
|
import { Flexbox } from 'react-layout-kit';
|
|
7
8
|
|
|
8
|
-
import SaveTopic from '
|
|
9
|
+
import SaveTopic from '@/app/chat/features/ChatInput/Topic';
|
|
10
|
+
import { useSessionStore } from '@/store/session';
|
|
11
|
+
|
|
12
|
+
import { useSendMessage } from './useSend';
|
|
13
|
+
|
|
14
|
+
const useStyles = createStyles(({ css }) => ({
|
|
15
|
+
footerBar: css`
|
|
16
|
+
display: flex;
|
|
17
|
+
flex: none;
|
|
18
|
+
gap: 8px;
|
|
19
|
+
align-items: center;
|
|
20
|
+
justify-content: flex-end;
|
|
21
|
+
|
|
22
|
+
padding: 0 24px;
|
|
23
|
+
`,
|
|
24
|
+
}));
|
|
9
25
|
|
|
10
26
|
const Footer = memo(() => {
|
|
11
|
-
const theme = useTheme();
|
|
12
27
|
const { t } = useTranslation('chat');
|
|
28
|
+
const { styles, theme } = useStyles();
|
|
29
|
+
const [loading, onStop] = useSessionStore((s) => [!!s.chatLoadingId, s.stopGenerateMessage]);
|
|
30
|
+
|
|
31
|
+
const onSend = useSendMessage();
|
|
13
32
|
|
|
14
33
|
return (
|
|
15
|
-
|
|
34
|
+
<div className={styles.footerBar}>
|
|
16
35
|
<Flexbox
|
|
17
36
|
gap={4}
|
|
18
37
|
horizontal
|
|
@@ -28,7 +47,16 @@ const Footer = memo(() => {
|
|
|
28
47
|
<span>{t('warp')}</span>
|
|
29
48
|
</Flexbox>
|
|
30
49
|
<SaveTopic />
|
|
31
|
-
|
|
50
|
+
{loading ? (
|
|
51
|
+
<Button icon={loading && <Icon icon={Loader2} spin />} onClick={onStop}>
|
|
52
|
+
{t('stop')}
|
|
53
|
+
</Button>
|
|
54
|
+
) : (
|
|
55
|
+
<Button onClick={() => onSend()} type={'primary'}>
|
|
56
|
+
{t('send')}
|
|
57
|
+
</Button>
|
|
58
|
+
)}
|
|
59
|
+
</div>
|
|
32
60
|
);
|
|
33
61
|
});
|
|
34
62
|
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { TextArea } from '@lobehub/ui';
|
|
2
|
+
import { createStyles } from 'antd-style';
|
|
3
|
+
import { memo, useRef } from 'react';
|
|
4
|
+
import { useTranslation } from 'react-i18next';
|
|
5
|
+
|
|
6
|
+
import { useSessionStore } from '@/store/session';
|
|
7
|
+
|
|
8
|
+
import { useSendMessage } from './useSend';
|
|
9
|
+
|
|
10
|
+
const useStyles = createStyles(({ css }) => {
|
|
11
|
+
return {
|
|
12
|
+
textarea: css`
|
|
13
|
+
height: 100% !important;
|
|
14
|
+
padding: 0 24px;
|
|
15
|
+
line-height: 1.5;
|
|
16
|
+
`,
|
|
17
|
+
textareaContainer: css`
|
|
18
|
+
position: relative;
|
|
19
|
+
flex: 1;
|
|
20
|
+
`,
|
|
21
|
+
};
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
const InputArea = memo(() => {
|
|
25
|
+
const { t } = useTranslation('common');
|
|
26
|
+
const isChineseInput = useRef(false);
|
|
27
|
+
|
|
28
|
+
const { cx, styles } = useStyles();
|
|
29
|
+
|
|
30
|
+
const [loading, message, updateInputMessage] = useSessionStore((s) => [
|
|
31
|
+
!!s.chatLoadingId,
|
|
32
|
+
s.inputMessage,
|
|
33
|
+
s.updateInputMessage,
|
|
34
|
+
]);
|
|
35
|
+
|
|
36
|
+
const handleSend = useSendMessage();
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<div className={cx(styles.textareaContainer)}>
|
|
40
|
+
<TextArea
|
|
41
|
+
className={styles.textarea}
|
|
42
|
+
onBlur={(e) => {
|
|
43
|
+
updateInputMessage(e.target.value);
|
|
44
|
+
}}
|
|
45
|
+
onChange={(e) => {
|
|
46
|
+
updateInputMessage(e.target.value);
|
|
47
|
+
}}
|
|
48
|
+
onCompositionEnd={() => {
|
|
49
|
+
isChineseInput.current = false;
|
|
50
|
+
}}
|
|
51
|
+
onCompositionStart={() => {
|
|
52
|
+
isChineseInput.current = true;
|
|
53
|
+
}}
|
|
54
|
+
onPressEnter={(e) => {
|
|
55
|
+
if (!loading && !e.shiftKey && !isChineseInput.current) {
|
|
56
|
+
e.preventDefault();
|
|
57
|
+
handleSend();
|
|
58
|
+
}
|
|
59
|
+
}}
|
|
60
|
+
placeholder={t('sendPlaceholder', { ns: 'chat' })}
|
|
61
|
+
resize={false}
|
|
62
|
+
type="pure"
|
|
63
|
+
value={message}
|
|
64
|
+
/>
|
|
65
|
+
</div>
|
|
66
|
+
);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
export default InputArea;
|
|
@@ -1,14 +1,34 @@
|
|
|
1
|
-
import { DraggablePanel } from '@lobehub/ui';
|
|
1
|
+
import { ActionIcon, DraggablePanel } from '@lobehub/ui';
|
|
2
|
+
import { createStyles } from 'antd-style';
|
|
3
|
+
import { Maximize2, Minimize2 } from 'lucide-react';
|
|
2
4
|
import { memo, useState } from 'react';
|
|
3
5
|
|
|
6
|
+
import Footer from '@/app/chat/(desktop)/features/ChatInput/Footer';
|
|
7
|
+
import ActionBar from '@/app/chat/features/ChatInput/ActionBar';
|
|
4
8
|
import { CHAT_TEXTAREA_HEIGHT, HEADER_HEIGHT } from '@/const/layoutTokens';
|
|
5
9
|
import { useGlobalStore } from '@/store/global';
|
|
6
10
|
|
|
7
|
-
import
|
|
8
|
-
|
|
11
|
+
import InputArea from './InputArea';
|
|
12
|
+
|
|
13
|
+
const useStyles = createStyles(({ css }) => {
|
|
14
|
+
return {
|
|
15
|
+
container: css`
|
|
16
|
+
position: relative;
|
|
17
|
+
|
|
18
|
+
display: flex;
|
|
19
|
+
flex-direction: column;
|
|
20
|
+
gap: 8px;
|
|
21
|
+
|
|
22
|
+
height: 100%;
|
|
23
|
+
padding: 12px 0 16px;
|
|
24
|
+
`,
|
|
25
|
+
};
|
|
26
|
+
});
|
|
9
27
|
|
|
10
28
|
const ChatInputDesktopLayout = memo(() => {
|
|
29
|
+
const { styles } = useStyles();
|
|
11
30
|
const [expand, setExpand] = useState<boolean>(false);
|
|
31
|
+
|
|
12
32
|
const [inputHeight, updatePreference] = useGlobalStore((s) => [
|
|
13
33
|
s.preference.inputHeight,
|
|
14
34
|
s.updatePreference,
|
|
@@ -30,7 +50,20 @@ const ChatInputDesktopLayout = memo(() => {
|
|
|
30
50
|
size={{ height: inputHeight, width: '100%' }}
|
|
31
51
|
style={{ zIndex: 10 }}
|
|
32
52
|
>
|
|
33
|
-
<
|
|
53
|
+
<section className={styles.container} style={{ minHeight: CHAT_TEXTAREA_HEIGHT }}>
|
|
54
|
+
<ActionBar
|
|
55
|
+
rightAreaEndRender={
|
|
56
|
+
<ActionIcon
|
|
57
|
+
icon={expand ? Minimize2 : Maximize2}
|
|
58
|
+
onClick={() => {
|
|
59
|
+
setExpand(!expand);
|
|
60
|
+
}}
|
|
61
|
+
/>
|
|
62
|
+
}
|
|
63
|
+
/>
|
|
64
|
+
<InputArea />
|
|
65
|
+
<Footer />
|
|
66
|
+
</section>
|
|
34
67
|
</DraggablePanel>
|
|
35
68
|
);
|
|
36
69
|
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { useCallback } from 'react';
|
|
2
|
+
|
|
3
|
+
import { useSessionStore } from '@/store/session';
|
|
4
|
+
|
|
5
|
+
export const useSendMessage = () => {
|
|
6
|
+
const [sendMessage, updateInputMessage] = useSessionStore((s) => [
|
|
7
|
+
s.sendMessage,
|
|
8
|
+
s.updateInputMessage,
|
|
9
|
+
]);
|
|
10
|
+
|
|
11
|
+
return useCallback(() => {
|
|
12
|
+
const store = useSessionStore.getState();
|
|
13
|
+
if (!!store.chatLoadingId) return;
|
|
14
|
+
sendMessage(store.inputMessage);
|
|
15
|
+
updateInputMessage('');
|
|
16
|
+
}, []);
|
|
17
|
+
};
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { Icon, Input } from '@lobehub/ui';
|
|
2
|
+
import { Button, type InputRef } from 'antd';
|
|
3
|
+
import { Loader2, SendHorizonal } from 'lucide-react';
|
|
4
|
+
import { forwardRef, useCallback, useRef } from 'react';
|
|
5
|
+
import { useTranslation } from 'react-i18next';
|
|
6
|
+
import { Flexbox } from 'react-layout-kit';
|
|
7
|
+
import useControlledState from 'use-merge-value';
|
|
8
|
+
|
|
9
|
+
import ActionBar from '@/app/chat/features/ChatInput/ActionBar';
|
|
10
|
+
import SaveTopic from '@/app/chat/features/ChatInput/Topic';
|
|
11
|
+
|
|
12
|
+
import { useStyles } from './style.mobile';
|
|
13
|
+
|
|
14
|
+
export type ChatInputAreaMobile = {
|
|
15
|
+
loading?: boolean;
|
|
16
|
+
onChange?: (value: string) => void;
|
|
17
|
+
onSend?: (value: string) => void;
|
|
18
|
+
onStop?: () => void;
|
|
19
|
+
value?: string;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const ChatInputArea = forwardRef<InputRef, ChatInputAreaMobile>(
|
|
23
|
+
({ onSend, loading, onChange, onStop, value }) => {
|
|
24
|
+
const { t } = useTranslation('chat');
|
|
25
|
+
const [currentValue, setCurrentValue] = useControlledState<string>('', {
|
|
26
|
+
onChange: onChange,
|
|
27
|
+
value,
|
|
28
|
+
});
|
|
29
|
+
const { cx, styles } = useStyles();
|
|
30
|
+
const isChineseInput = useRef(false);
|
|
31
|
+
|
|
32
|
+
const handleSend = useCallback(() => {
|
|
33
|
+
if (loading) return;
|
|
34
|
+
if (onSend) onSend(currentValue);
|
|
35
|
+
setCurrentValue('');
|
|
36
|
+
}, [currentValue]);
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<Flexbox className={cx(styles.container)} gap={12}>
|
|
40
|
+
<ActionBar rightAreaStartRender={<SaveTopic />} />
|
|
41
|
+
<Flexbox className={styles.inner} gap={8} horizontal>
|
|
42
|
+
<Input
|
|
43
|
+
className={cx(styles.input)}
|
|
44
|
+
onBlur={(e) => {
|
|
45
|
+
setCurrentValue(e.target.value);
|
|
46
|
+
}}
|
|
47
|
+
onChange={(e) => {
|
|
48
|
+
setCurrentValue(e.target.value);
|
|
49
|
+
}}
|
|
50
|
+
onCompositionEnd={() => {
|
|
51
|
+
isChineseInput.current = false;
|
|
52
|
+
}}
|
|
53
|
+
onCompositionStart={() => {
|
|
54
|
+
isChineseInput.current = true;
|
|
55
|
+
}}
|
|
56
|
+
onPressEnter={(e) => {
|
|
57
|
+
if (!loading && !e.shiftKey && !isChineseInput.current) {
|
|
58
|
+
e.preventDefault();
|
|
59
|
+
handleSend();
|
|
60
|
+
}
|
|
61
|
+
}}
|
|
62
|
+
placeholder={t('sendPlaceholder')}
|
|
63
|
+
type={'block'}
|
|
64
|
+
value={currentValue}
|
|
65
|
+
/>
|
|
66
|
+
<div>
|
|
67
|
+
{loading ? (
|
|
68
|
+
<Button icon={loading && <Icon icon={Loader2} spin />} onClick={onStop} />
|
|
69
|
+
) : (
|
|
70
|
+
<Button icon={<Icon icon={SendHorizonal} />} onClick={handleSend} type={'primary'} />
|
|
71
|
+
)}
|
|
72
|
+
</div>
|
|
73
|
+
</Flexbox>
|
|
74
|
+
</Flexbox>
|
|
75
|
+
);
|
|
76
|
+
},
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
export default ChatInputArea;
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { createStyles } from 'antd-style';
|
|
2
|
-
import { memo } from 'react';
|
|
2
|
+
import { memo, useState } from 'react';
|
|
3
3
|
|
|
4
4
|
import SafeSpacing from '@/components/SafeSpacing';
|
|
5
5
|
import { CHAT_TEXTAREA_HEIGHT_MOBILE } from '@/const/layoutTokens';
|
|
6
|
+
import { useSessionStore } from '@/store/session';
|
|
6
7
|
|
|
7
|
-
import
|
|
8
|
+
import ChatInputArea from './Mobile';
|
|
8
9
|
|
|
9
10
|
const useStyles = createStyles(
|
|
10
11
|
({ css, token }) => css`
|
|
@@ -21,11 +22,26 @@ const useStyles = createStyles(
|
|
|
21
22
|
|
|
22
23
|
const ChatInputMobileLayout = memo(() => {
|
|
23
24
|
const { styles } = useStyles();
|
|
25
|
+
|
|
26
|
+
const [message, setMessage] = useState('');
|
|
27
|
+
|
|
28
|
+
const [isLoading, sendMessage, stopGenerateMessage] = useSessionStore((s) => [
|
|
29
|
+
!!s.chatLoadingId,
|
|
30
|
+
s.sendMessage,
|
|
31
|
+
s.stopGenerateMessage,
|
|
32
|
+
]);
|
|
33
|
+
|
|
24
34
|
return (
|
|
25
35
|
<>
|
|
26
36
|
<SafeSpacing height={CHAT_TEXTAREA_HEIGHT_MOBILE} mobile position={'bottom'} />
|
|
27
37
|
<div className={styles}>
|
|
28
|
-
<
|
|
38
|
+
<ChatInputArea
|
|
39
|
+
loading={isLoading}
|
|
40
|
+
onChange={setMessage}
|
|
41
|
+
onSend={sendMessage}
|
|
42
|
+
onStop={stopGenerateMessage}
|
|
43
|
+
value={message}
|
|
44
|
+
/>
|
|
29
45
|
</div>
|
|
30
46
|
</>
|
|
31
47
|
);
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { createStyles } from 'antd-style';
|
|
2
|
+
import { rgba } from 'polished';
|
|
3
|
+
|
|
4
|
+
export const useStyles = createStyles(({ css, token }) => {
|
|
5
|
+
return {
|
|
6
|
+
container: css`
|
|
7
|
+
padding: 12px 0;
|
|
8
|
+
background: ${token.colorBgLayout};
|
|
9
|
+
border-top: 1px solid ${rgba(token.colorBorder, 0.25)};
|
|
10
|
+
`,
|
|
11
|
+
inner: css`
|
|
12
|
+
padding: 0 16px;
|
|
13
|
+
`,
|
|
14
|
+
input: css`
|
|
15
|
+
background: ${token.colorFillSecondary} !important;
|
|
16
|
+
border: none !important;
|
|
17
|
+
`,
|
|
18
|
+
};
|
|
19
|
+
});
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { ActionIcon } from '@lobehub/ui';
|
|
2
|
+
import { Popconfirm } from 'antd';
|
|
3
|
+
import { Eraser } from 'lucide-react';
|
|
4
|
+
import { memo } from 'react';
|
|
5
|
+
import { useHotkeys } from 'react-hotkeys-hook';
|
|
6
|
+
import { useTranslation } from 'react-i18next';
|
|
7
|
+
|
|
8
|
+
import HotKeys from '@/components/HotKeys';
|
|
9
|
+
import { CLEAN_MESSAGE_KEY, PREFIX_KEY } from '@/const/hotkeys';
|
|
10
|
+
import { useSessionStore } from '@/store/session';
|
|
11
|
+
|
|
12
|
+
const Clear = memo(() => {
|
|
13
|
+
const { t } = useTranslation('setting');
|
|
14
|
+
const [clearMessage] = useSessionStore((s) => [s.clearMessage, s.updateAgentConfig]);
|
|
15
|
+
const hotkeys = [PREFIX_KEY, CLEAN_MESSAGE_KEY].join('+');
|
|
16
|
+
|
|
17
|
+
useHotkeys(hotkeys, clearMessage, {
|
|
18
|
+
preventDefault: true,
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<Popconfirm
|
|
23
|
+
cancelText={t('cancel', { ns: 'common' })}
|
|
24
|
+
okButtonProps={{ danger: true }}
|
|
25
|
+
okText={t('ok', { ns: 'common' })}
|
|
26
|
+
onConfirm={() => clearMessage()}
|
|
27
|
+
placement={'topRight'}
|
|
28
|
+
title={t('confirmClearCurrentMessages', { ns: 'chat' })}
|
|
29
|
+
>
|
|
30
|
+
<ActionIcon
|
|
31
|
+
icon={Eraser}
|
|
32
|
+
placement={'bottom'}
|
|
33
|
+
title={(<HotKeys desc={t('clearCurrentMessages', { ns: 'chat' })} keys={hotkeys} />) as any}
|
|
34
|
+
/>
|
|
35
|
+
</Popconfirm>
|
|
36
|
+
);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
export default Clear;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { ActionIcon, SliderWithInput } from '@lobehub/ui';
|
|
2
|
+
import { Popover, Switch } from 'antd';
|
|
3
|
+
import { Timer, TimerOff } from 'lucide-react';
|
|
4
|
+
import { memo } from 'react';
|
|
5
|
+
import { useTranslation } from 'react-i18next';
|
|
6
|
+
import { Flexbox } from 'react-layout-kit';
|
|
7
|
+
|
|
8
|
+
import { useSessionStore } from '@/store/session';
|
|
9
|
+
import { agentSelectors } from '@/store/session/selectors';
|
|
10
|
+
|
|
11
|
+
const History = memo(() => {
|
|
12
|
+
const { t } = useTranslation('setting');
|
|
13
|
+
|
|
14
|
+
const [historyCount, unlimited, updateAgentConfig] = useSessionStore((s) => {
|
|
15
|
+
const config = agentSelectors.currentAgentConfig(s);
|
|
16
|
+
return [config.historyCount, !config.enableHistoryCount, s.updateAgentConfig];
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
return (
|
|
20
|
+
<Popover
|
|
21
|
+
arrow={false}
|
|
22
|
+
content={
|
|
23
|
+
<Flexbox align={'center'} gap={16} horizontal>
|
|
24
|
+
<SliderWithInput
|
|
25
|
+
disabled={unlimited}
|
|
26
|
+
max={30}
|
|
27
|
+
min={1}
|
|
28
|
+
onChange={(v) => {
|
|
29
|
+
updateAgentConfig({ historyCount: v });
|
|
30
|
+
}}
|
|
31
|
+
step={1}
|
|
32
|
+
style={{ width: 160 }}
|
|
33
|
+
value={historyCount}
|
|
34
|
+
/>
|
|
35
|
+
<Flexbox align={'center'} gap={4} horizontal>
|
|
36
|
+
<Switch
|
|
37
|
+
checked={unlimited}
|
|
38
|
+
onChange={(checked) => {
|
|
39
|
+
updateAgentConfig({ enableHistoryCount: !checked });
|
|
40
|
+
}}
|
|
41
|
+
size={'small'}
|
|
42
|
+
/>
|
|
43
|
+
{t('settingChat.enableHistoryCount.alias')}
|
|
44
|
+
</Flexbox>
|
|
45
|
+
</Flexbox>
|
|
46
|
+
}
|
|
47
|
+
placement={'top'}
|
|
48
|
+
trigger={'click'}
|
|
49
|
+
>
|
|
50
|
+
<ActionIcon
|
|
51
|
+
icon={unlimited ? TimerOff : Timer}
|
|
52
|
+
placement={'bottom'}
|
|
53
|
+
title={t(
|
|
54
|
+
unlimited
|
|
55
|
+
? 'settingChat.enableHistoryCount.unlimited'
|
|
56
|
+
: 'settingChat.enableHistoryCount.limited',
|
|
57
|
+
{ number: historyCount || 0 },
|
|
58
|
+
)}
|
|
59
|
+
/>
|
|
60
|
+
</Popover>
|
|
61
|
+
);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
export default History;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { ActionIcon } from '@lobehub/ui';
|
|
2
|
+
import { Dropdown } from 'antd';
|
|
3
|
+
import { BrainCog } from 'lucide-react';
|
|
4
|
+
import { memo } from 'react';
|
|
5
|
+
import { useTranslation } from 'react-i18next';
|
|
6
|
+
|
|
7
|
+
import { settingsSelectors, useGlobalStore } from '@/store/global';
|
|
8
|
+
import { useSessionStore } from '@/store/session';
|
|
9
|
+
import { agentSelectors } from '@/store/session/selectors';
|
|
10
|
+
import { LanguageModel } from '@/types/llm';
|
|
11
|
+
|
|
12
|
+
const ModelSwitch = memo(() => {
|
|
13
|
+
const { t } = useTranslation('setting');
|
|
14
|
+
|
|
15
|
+
const [model, updateAgentConfig] = useSessionStore((s) => {
|
|
16
|
+
const config = agentSelectors.currentAgentConfig(s);
|
|
17
|
+
return [config.model, s.updateAgentConfig];
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
const modelList = useGlobalStore(settingsSelectors.modelList);
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<Dropdown
|
|
24
|
+
menu={{
|
|
25
|
+
activeKey: model,
|
|
26
|
+
items: modelList.map((i) => ({ key: i, label: i })),
|
|
27
|
+
onClick: (e) => {
|
|
28
|
+
updateAgentConfig({ model: e.key as LanguageModel });
|
|
29
|
+
},
|
|
30
|
+
style: {
|
|
31
|
+
maxHeight: 400,
|
|
32
|
+
overflow: 'scroll',
|
|
33
|
+
},
|
|
34
|
+
}}
|
|
35
|
+
trigger={['click']}
|
|
36
|
+
>
|
|
37
|
+
<ActionIcon icon={BrainCog} placement={'bottom'} title={t('settingModel.model.title')} />
|
|
38
|
+
</Dropdown>
|
|
39
|
+
);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
export default ModelSwitch;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { ActionIcon, SliderWithInput } from '@lobehub/ui';
|
|
2
|
+
import { Popover } from 'antd';
|
|
3
|
+
import { Thermometer } from 'lucide-react';
|
|
4
|
+
import { memo } from 'react';
|
|
5
|
+
import { useTranslation } from 'react-i18next';
|
|
6
|
+
|
|
7
|
+
import { useSessionStore } from '@/store/session';
|
|
8
|
+
import { agentSelectors } from '@/store/session/selectors';
|
|
9
|
+
|
|
10
|
+
const Temperature = memo(() => {
|
|
11
|
+
const { t } = useTranslation('setting');
|
|
12
|
+
|
|
13
|
+
const [temperature, updateAgentConfig] = useSessionStore((s) => {
|
|
14
|
+
const config = agentSelectors.currentAgentConfig(s);
|
|
15
|
+
return [config.params.temperature, s.updateAgentConfig];
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
return (
|
|
19
|
+
<Popover
|
|
20
|
+
arrow={false}
|
|
21
|
+
content={
|
|
22
|
+
<SliderWithInput
|
|
23
|
+
controls={false}
|
|
24
|
+
max={1}
|
|
25
|
+
min={0}
|
|
26
|
+
onChange={(v) => {
|
|
27
|
+
updateAgentConfig({ params: { temperature: v } });
|
|
28
|
+
}}
|
|
29
|
+
size={'small'}
|
|
30
|
+
step={0.1}
|
|
31
|
+
style={{ width: 160 }}
|
|
32
|
+
value={temperature}
|
|
33
|
+
/>
|
|
34
|
+
}
|
|
35
|
+
placement={'top'}
|
|
36
|
+
trigger={'click'}
|
|
37
|
+
>
|
|
38
|
+
<ActionIcon
|
|
39
|
+
icon={Thermometer}
|
|
40
|
+
placement={'bottom'}
|
|
41
|
+
title={t('settingModel.temperature.titleWithValue', { value: temperature })}
|
|
42
|
+
/>
|
|
43
|
+
</Popover>
|
|
44
|
+
);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
export default Temperature;
|
|
@@ -8,16 +8,16 @@ import { useSessionStore } from '@/store/session';
|
|
|
8
8
|
import { agentSelectors, chatSelectors } from '@/store/session/selectors';
|
|
9
9
|
import { LanguageModel } from '@/types/llm';
|
|
10
10
|
|
|
11
|
-
const Token = memo
|
|
11
|
+
const Token = memo(() => {
|
|
12
12
|
const { t } = useTranslation('chat');
|
|
13
13
|
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
const [messageString, systemRole, model] = useSessionStore((s) => [
|
|
14
|
+
const [input, messageString, systemRole, model] = useSessionStore((s) => [
|
|
15
|
+
s.inputMessage,
|
|
17
16
|
chatSelectors.chatsMessageString(s),
|
|
18
17
|
agentSelectors.currentAgentSystemRole(s),
|
|
19
18
|
agentSelectors.currentAgentModel(s) as LanguageModel,
|
|
20
19
|
]);
|
|
20
|
+
const inputTokenCount = useTokenCount(input);
|
|
21
21
|
|
|
22
22
|
const systemRoleToken = useTokenCount(systemRole);
|
|
23
23
|
const chatsToken = useTokenCount(messageString);
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import dynamic from 'next/dynamic';
|
|
2
|
+
import { Suspense, memo } from 'react';
|
|
3
|
+
|
|
4
|
+
import { useSessionStore } from '@/store/session';
|
|
5
|
+
import { agentSelectors } from '@/store/session/selectors';
|
|
6
|
+
|
|
7
|
+
const LargeTokenContent = dynamic(() => import('./TokenTag'), { ssr: false });
|
|
8
|
+
|
|
9
|
+
const Token = memo(() => {
|
|
10
|
+
const [showTokenTag] = useSessionStore((s) => [agentSelectors.showTokenTag(s)]);
|
|
11
|
+
|
|
12
|
+
return (
|
|
13
|
+
showTokenTag && (
|
|
14
|
+
<Suspense>
|
|
15
|
+
<LargeTokenContent />
|
|
16
|
+
</Suspense>
|
|
17
|
+
)
|
|
18
|
+
);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
export default Token;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { FC } from 'react';
|
|
2
|
+
|
|
3
|
+
import Clear from './Clear';
|
|
4
|
+
import History from './History';
|
|
5
|
+
import ModelSwitch from './ModelSwitch';
|
|
6
|
+
import Temperature from './Temperature';
|
|
7
|
+
import Token from './Token';
|
|
8
|
+
|
|
9
|
+
export const actionMap: Record<string, FC> = {
|
|
10
|
+
clear: Clear,
|
|
11
|
+
history: History,
|
|
12
|
+
model: ModelSwitch,
|
|
13
|
+
temperature: Temperature,
|
|
14
|
+
token: Token,
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
// we can make these action lists configurable in the future
|
|
18
|
+
export const leftActionList = ['model', 'temperature', 'history', 'token'];
|
|
19
|
+
export const rightActionList = ['clear'];
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { ReactNode, memo } from 'react';
|
|
2
|
+
import { Flexbox } from 'react-layout-kit';
|
|
3
|
+
|
|
4
|
+
import { actionMap, leftActionList, rightActionList } from './config';
|
|
5
|
+
|
|
6
|
+
const RenderActionList = ({ dataSource }: { dataSource: string[] }) => (
|
|
7
|
+
<>
|
|
8
|
+
{dataSource.map((key) => {
|
|
9
|
+
const Render = actionMap[key];
|
|
10
|
+
return <Render key={key} />;
|
|
11
|
+
})}
|
|
12
|
+
</>
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
export interface ActionBarProps {
|
|
16
|
+
rightAreaEndRender?: ReactNode;
|
|
17
|
+
rightAreaStartRender?: ReactNode;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const ActionBar = memo<ActionBarProps>(({ rightAreaStartRender, rightAreaEndRender }) => {
|
|
21
|
+
return (
|
|
22
|
+
<Flexbox align={'center'} flex={'none'} horizontal justify={'space-between'} padding={'0 16px'}>
|
|
23
|
+
<Flexbox align={'center'} flex={1} gap={4} horizontal>
|
|
24
|
+
<RenderActionList dataSource={leftActionList} />
|
|
25
|
+
</Flexbox>
|
|
26
|
+
<Flexbox align={'center'} flex={0} gap={4} horizontal justify={'flex-end'}>
|
|
27
|
+
{rightAreaStartRender}
|
|
28
|
+
<RenderActionList dataSource={rightActionList} />
|
|
29
|
+
{rightAreaEndRender}
|
|
30
|
+
</Flexbox>
|
|
31
|
+
</Flexbox>
|
|
32
|
+
);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
export default ActionBar;
|
|
@@ -68,12 +68,13 @@ export interface ChatMessageAction {
|
|
|
68
68
|
*/
|
|
69
69
|
sendMessage: (text: string) => Promise<void>;
|
|
70
70
|
stopGenerateMessage: () => void;
|
|
71
|
-
|
|
72
71
|
toggleChatLoading: (
|
|
73
72
|
loading: boolean,
|
|
74
73
|
id?: string,
|
|
75
74
|
action?: string,
|
|
76
75
|
) => AbortController | undefined;
|
|
76
|
+
|
|
77
|
+
updateInputMessage: (message: string) => void;
|
|
77
78
|
}
|
|
78
79
|
|
|
79
80
|
export const chatMessage: StateCreator<
|
|
@@ -94,7 +95,6 @@ export const chatMessage: StateCreator<
|
|
|
94
95
|
// after remove topic , go back to default topic
|
|
95
96
|
toggleTopic();
|
|
96
97
|
},
|
|
97
|
-
|
|
98
98
|
coreProcessMessage: async (messages, userMessageId) => {
|
|
99
99
|
const { dispatchMessage, fetchAIChatMessage, triggerFunctionCall, activeTopicId } = get();
|
|
100
100
|
|
|
@@ -163,6 +163,7 @@ export const chatMessage: StateCreator<
|
|
|
163
163
|
deleteMessage: (id) => {
|
|
164
164
|
get().dispatchMessage({ id, type: 'deleteMessage' });
|
|
165
165
|
},
|
|
166
|
+
|
|
166
167
|
dispatchMessage: (payload) => {
|
|
167
168
|
const { activeId } = get();
|
|
168
169
|
const session = sessionSelectors.currentSession(get());
|
|
@@ -172,7 +173,6 @@ export const chatMessage: StateCreator<
|
|
|
172
173
|
|
|
173
174
|
get().dispatchSession({ chats, id: activeId, type: 'updateSessionChat' });
|
|
174
175
|
},
|
|
175
|
-
|
|
176
176
|
fetchAIChatMessage: async (messages, assistantId) => {
|
|
177
177
|
const { dispatchMessage, toggleChatLoading } = get();
|
|
178
178
|
|
|
@@ -341,6 +341,7 @@ export const chatMessage: StateCreator<
|
|
|
341
341
|
|
|
342
342
|
toggleChatLoading(false);
|
|
343
343
|
},
|
|
344
|
+
|
|
344
345
|
toggleChatLoading: (loading, id, action) => {
|
|
345
346
|
if (loading) {
|
|
346
347
|
const abortController = new AbortController();
|
|
@@ -350,4 +351,7 @@ export const chatMessage: StateCreator<
|
|
|
350
351
|
set({ abortController: undefined, chatLoadingId: undefined }, false, action);
|
|
351
352
|
}
|
|
352
353
|
},
|
|
354
|
+
updateInputMessage: (message) => {
|
|
355
|
+
set({ inputMessage: message }, false, t('updateInputMessage'));
|
|
356
|
+
},
|
|
353
357
|
});
|
|
@@ -2,9 +2,12 @@ export interface ChatState {
|
|
|
2
2
|
abortController?: AbortController;
|
|
3
3
|
activeTopicId?: string;
|
|
4
4
|
chatLoadingId?: string;
|
|
5
|
+
inputMessage: string;
|
|
5
6
|
renameTopicId?: string;
|
|
6
7
|
shareLoading?: boolean;
|
|
7
8
|
topicLoadingId?: string;
|
|
8
9
|
}
|
|
9
10
|
|
|
10
|
-
export const initialChatState: ChatState = {
|
|
11
|
+
export const initialChatState: ChatState = {
|
|
12
|
+
inputMessage: '',
|
|
13
|
+
};
|
|
@@ -41,6 +41,7 @@ export const currentChats = (s: SessionStore): ChatMessage[] => {
|
|
|
41
41
|
return getChatsById(s.activeId)(s);
|
|
42
42
|
};
|
|
43
43
|
|
|
44
|
+
const initTime = Date.now();
|
|
44
45
|
// 针对新助手添加初始化时的自定义消息
|
|
45
46
|
export const currentChatsWithGuideMessage = (s: SessionStore): ChatMessage[] => {
|
|
46
47
|
const data = currentChats(s);
|
|
@@ -67,14 +68,14 @@ export const currentChatsWithGuideMessage = (s: SessionStore): ChatMessage[] =>
|
|
|
67
68
|
|
|
68
69
|
const emptyInboxGuideMessage = {
|
|
69
70
|
content: isInbox ? inboxMsg : !!meta.description ? agentSystemRoleMsg : agentMsg,
|
|
70
|
-
createAt:
|
|
71
|
+
createAt: initTime,
|
|
71
72
|
extra: {},
|
|
72
73
|
id: 'default',
|
|
73
74
|
meta: meta || {
|
|
74
75
|
avatar: DEFAULT_INBOX_AVATAR,
|
|
75
76
|
},
|
|
76
77
|
role: 'assistant',
|
|
77
|
-
updateAt:
|
|
78
|
+
updateAt: initTime,
|
|
78
79
|
} as ChatMessage;
|
|
79
80
|
|
|
80
81
|
return [emptyInboxGuideMessage];
|
|
@@ -1,117 +0,0 @@
|
|
|
1
|
-
import { ActionIcon, SliderWithInput } from '@lobehub/ui';
|
|
2
|
-
import { Dropdown, Popover, Switch } from 'antd';
|
|
3
|
-
import { BrainCog, Thermometer, Timer, TimerOff } from 'lucide-react';
|
|
4
|
-
import { memo } from 'react';
|
|
5
|
-
import { useTranslation } from 'react-i18next';
|
|
6
|
-
import { Flexbox } from 'react-layout-kit';
|
|
7
|
-
|
|
8
|
-
import { settingsSelectors, useGlobalStore } from '@/store/global';
|
|
9
|
-
import { useSessionStore } from '@/store/session';
|
|
10
|
-
import { agentSelectors } from '@/store/session/selectors';
|
|
11
|
-
import { LanguageModel } from '@/types/llm';
|
|
12
|
-
|
|
13
|
-
const ActionLeft = memo(() => {
|
|
14
|
-
const { t } = useTranslation('setting');
|
|
15
|
-
|
|
16
|
-
const [model, temperature, historyCount, unlimited, updateAgentConfig] = useSessionStore((s) => {
|
|
17
|
-
const config = agentSelectors.currentAgentConfig(s);
|
|
18
|
-
return [
|
|
19
|
-
config.model,
|
|
20
|
-
config.params.temperature,
|
|
21
|
-
config.historyCount,
|
|
22
|
-
!config.enableHistoryCount,
|
|
23
|
-
s.updateAgentConfig,
|
|
24
|
-
];
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
const modelList = useGlobalStore(settingsSelectors.modelList);
|
|
28
|
-
|
|
29
|
-
return (
|
|
30
|
-
<>
|
|
31
|
-
<Dropdown
|
|
32
|
-
menu={{
|
|
33
|
-
activeKey: model,
|
|
34
|
-
items: modelList.map((i) => ({ key: i, label: i })),
|
|
35
|
-
onClick: (e) => {
|
|
36
|
-
updateAgentConfig({ model: e.key as LanguageModel });
|
|
37
|
-
},
|
|
38
|
-
style: {
|
|
39
|
-
maxHeight: 400,
|
|
40
|
-
overflow: 'scroll',
|
|
41
|
-
},
|
|
42
|
-
}}
|
|
43
|
-
trigger={['click']}
|
|
44
|
-
>
|
|
45
|
-
<ActionIcon icon={BrainCog} placement={'bottom'} title={t('settingModel.model.title')} />
|
|
46
|
-
</Dropdown>
|
|
47
|
-
<Popover
|
|
48
|
-
arrow={false}
|
|
49
|
-
content={
|
|
50
|
-
<SliderWithInput
|
|
51
|
-
controls={false}
|
|
52
|
-
max={1}
|
|
53
|
-
min={0}
|
|
54
|
-
onChange={(v) => {
|
|
55
|
-
updateAgentConfig({ params: { temperature: v } });
|
|
56
|
-
}}
|
|
57
|
-
size={'small'}
|
|
58
|
-
step={0.1}
|
|
59
|
-
style={{ width: 160 }}
|
|
60
|
-
value={temperature}
|
|
61
|
-
/>
|
|
62
|
-
}
|
|
63
|
-
placement={'top'}
|
|
64
|
-
trigger={'click'}
|
|
65
|
-
>
|
|
66
|
-
<ActionIcon
|
|
67
|
-
icon={Thermometer}
|
|
68
|
-
placement={'bottom'}
|
|
69
|
-
title={t('settingModel.temperature.titleWithValue', { value: temperature })}
|
|
70
|
-
/>
|
|
71
|
-
</Popover>
|
|
72
|
-
<Popover
|
|
73
|
-
arrow={false}
|
|
74
|
-
content={
|
|
75
|
-
<Flexbox align={'center'} gap={16} horizontal>
|
|
76
|
-
<SliderWithInput
|
|
77
|
-
disabled={unlimited}
|
|
78
|
-
max={30}
|
|
79
|
-
min={1}
|
|
80
|
-
onChange={(v) => {
|
|
81
|
-
updateAgentConfig({ historyCount: v });
|
|
82
|
-
}}
|
|
83
|
-
step={1}
|
|
84
|
-
style={{ width: 160 }}
|
|
85
|
-
value={historyCount}
|
|
86
|
-
/>
|
|
87
|
-
<Flexbox align={'center'} gap={4} horizontal>
|
|
88
|
-
<Switch
|
|
89
|
-
checked={unlimited}
|
|
90
|
-
onChange={(checked) => {
|
|
91
|
-
updateAgentConfig({ enableHistoryCount: !checked });
|
|
92
|
-
}}
|
|
93
|
-
size={'small'}
|
|
94
|
-
/>
|
|
95
|
-
{t('settingChat.enableHistoryCount.alias')}
|
|
96
|
-
</Flexbox>
|
|
97
|
-
</Flexbox>
|
|
98
|
-
}
|
|
99
|
-
placement={'top'}
|
|
100
|
-
trigger={'click'}
|
|
101
|
-
>
|
|
102
|
-
<ActionIcon
|
|
103
|
-
icon={unlimited ? TimerOff : Timer}
|
|
104
|
-
placement={'bottom'}
|
|
105
|
-
title={t(
|
|
106
|
-
unlimited
|
|
107
|
-
? 'settingChat.enableHistoryCount.unlimited'
|
|
108
|
-
: 'settingChat.enableHistoryCount.limited',
|
|
109
|
-
{ number: historyCount || 0 },
|
|
110
|
-
)}
|
|
111
|
-
/>
|
|
112
|
-
</Popover>
|
|
113
|
-
</>
|
|
114
|
-
);
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
export default ActionLeft;
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
import { ActionIcon } from '@lobehub/ui';
|
|
2
|
-
import { Popconfirm } from 'antd';
|
|
3
|
-
import { useResponsive } from 'antd-style';
|
|
4
|
-
import { Eraser } from 'lucide-react';
|
|
5
|
-
import { memo } from 'react';
|
|
6
|
-
import { useHotkeys } from 'react-hotkeys-hook';
|
|
7
|
-
import { useTranslation } from 'react-i18next';
|
|
8
|
-
|
|
9
|
-
import HotKeys from '@/components/HotKeys';
|
|
10
|
-
import { CLEAN_MESSAGE_KEY, PREFIX_KEY } from '@/const/hotkeys';
|
|
11
|
-
import { useSessionStore } from '@/store/session';
|
|
12
|
-
|
|
13
|
-
import SaveTopic from '../Topic';
|
|
14
|
-
|
|
15
|
-
const ActionsRight = memo(() => {
|
|
16
|
-
const { t } = useTranslation('setting');
|
|
17
|
-
const [clearMessage] = useSessionStore((s) => [s.clearMessage, s.updateAgentConfig]);
|
|
18
|
-
const { mobile } = useResponsive();
|
|
19
|
-
const hotkeys = [PREFIX_KEY, CLEAN_MESSAGE_KEY].join('+');
|
|
20
|
-
|
|
21
|
-
useHotkeys(hotkeys, clearMessage, {
|
|
22
|
-
preventDefault: true,
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
return (
|
|
26
|
-
<>
|
|
27
|
-
{mobile && <SaveTopic />}
|
|
28
|
-
<Popconfirm
|
|
29
|
-
cancelText={t('cancel', { ns: 'common' })}
|
|
30
|
-
okButtonProps={{ danger: true }}
|
|
31
|
-
okText={t('ok', { ns: 'common' })}
|
|
32
|
-
onConfirm={() => clearMessage()}
|
|
33
|
-
placement={'topRight'}
|
|
34
|
-
title={t('confirmClearCurrentMessages', { ns: 'chat' })}
|
|
35
|
-
>
|
|
36
|
-
<ActionIcon
|
|
37
|
-
icon={Eraser}
|
|
38
|
-
placement={'bottom'}
|
|
39
|
-
title={
|
|
40
|
-
(<HotKeys desc={t('clearCurrentMessages', { ns: 'chat' })} keys={hotkeys} />) as any
|
|
41
|
-
}
|
|
42
|
-
/>
|
|
43
|
-
</Popconfirm>
|
|
44
|
-
</>
|
|
45
|
-
);
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
export default ActionsRight;
|
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
import { ChatInputArea } from '@lobehub/ui';
|
|
2
|
-
import { useResponsive } from 'antd-style';
|
|
3
|
-
import dynamic from 'next/dynamic';
|
|
4
|
-
import { ReactNode, Suspense, memo, useState } from 'react';
|
|
5
|
-
import { useTranslation } from 'react-i18next';
|
|
6
|
-
|
|
7
|
-
import { CHAT_TEXTAREA_HEIGHT } from '@/const/layoutTokens';
|
|
8
|
-
import { useSessionStore } from '@/store/session';
|
|
9
|
-
import { agentSelectors } from '@/store/session/selectors';
|
|
10
|
-
|
|
11
|
-
import ActionLeft from './ActionBar/ActionLeft';
|
|
12
|
-
import ActionsRight from './ActionBar/ActionRight';
|
|
13
|
-
|
|
14
|
-
const Token = dynamic(() => import('./ActionBar/Token'), { ssr: false });
|
|
15
|
-
|
|
16
|
-
interface ChatContentProps {
|
|
17
|
-
expand?: boolean;
|
|
18
|
-
footer?: ReactNode;
|
|
19
|
-
mobile?: boolean;
|
|
20
|
-
onExpandChange?: (expand: boolean) => void;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
const ChatInputContent = memo<ChatContentProps>(
|
|
24
|
-
({ expand, onExpandChange, mobile: defaultMobile, footer }) => {
|
|
25
|
-
const { t } = useTranslation('common');
|
|
26
|
-
|
|
27
|
-
const [message, setMessage] = useState('');
|
|
28
|
-
const { mobile: runtimeMobile } = useResponsive();
|
|
29
|
-
|
|
30
|
-
const mobile = runtimeMobile || defaultMobile;
|
|
31
|
-
|
|
32
|
-
const [isLoading, sendMessage, stopGenerateMessage, showTokenTag] = useSessionStore((s) => [
|
|
33
|
-
!!s.chatLoadingId,
|
|
34
|
-
s.sendMessage,
|
|
35
|
-
s.stopGenerateMessage,
|
|
36
|
-
agentSelectors.showTokenTag(s),
|
|
37
|
-
]);
|
|
38
|
-
|
|
39
|
-
return (
|
|
40
|
-
<ChatInputArea
|
|
41
|
-
actions={
|
|
42
|
-
<>
|
|
43
|
-
<ActionLeft />
|
|
44
|
-
{showTokenTag && (
|
|
45
|
-
<Suspense>
|
|
46
|
-
<Token input={message} />
|
|
47
|
-
</Suspense>
|
|
48
|
-
)}
|
|
49
|
-
</>
|
|
50
|
-
}
|
|
51
|
-
actionsRight={<ActionsRight />}
|
|
52
|
-
expand={expand}
|
|
53
|
-
footer={footer}
|
|
54
|
-
loading={isLoading}
|
|
55
|
-
minHeight={mobile ? 0 : CHAT_TEXTAREA_HEIGHT}
|
|
56
|
-
onExpandChange={onExpandChange}
|
|
57
|
-
onInputChange={setMessage}
|
|
58
|
-
onSend={sendMessage}
|
|
59
|
-
onStop={stopGenerateMessage}
|
|
60
|
-
placeholder={t('sendPlaceholder', { ns: 'chat' })}
|
|
61
|
-
text={{
|
|
62
|
-
send: t('send'),
|
|
63
|
-
stop: t('stop'),
|
|
64
|
-
}}
|
|
65
|
-
value={message}
|
|
66
|
-
/>
|
|
67
|
-
);
|
|
68
|
-
},
|
|
69
|
-
);
|
|
70
|
-
|
|
71
|
-
export default ChatInputContent;
|