@lobehub/chat 0.4.3 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +66 -0
- package/package.json +3 -1
- package/src/locales/default/common.ts +4 -0
- package/src/pages/chat/[id]/Conversation/Input.tsx +24 -15
- package/src/pages/chat/[id]/edit/AgentConfig/index.tsx +12 -4
- package/src/pages/chat/[id]/edit/AgentMeta/EmojiPicker.tsx +74 -0
- package/src/pages/chat/[id]/edit/AgentMeta/index.tsx +3 -2
- package/src/prompts/agent.ts +20 -9
- package/src/store/session/slices/chat/{selectors.ts → selectors/chat.ts} +4 -29
- package/src/store/session/slices/chat/selectors/index.ts +9 -0
- package/src/store/session/slices/chat/selectors/token.ts +21 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,72 @@
|
|
|
2
2
|
|
|
3
3
|
# Changelog
|
|
4
4
|
|
|
5
|
+
## [Version 0.6.0](https://github.com/lobehub/lobe-chat/compare/v0.5.0...v0.6.0)
|
|
6
|
+
|
|
7
|
+
<sup>Released on **2023-07-22**</sup>
|
|
8
|
+
|
|
9
|
+
#### ♻ Code Refactoring
|
|
10
|
+
|
|
11
|
+
- **misc**: 重构 selector 文件组织.
|
|
12
|
+
|
|
13
|
+
#### ✨ Features
|
|
14
|
+
|
|
15
|
+
- **misc**: 补充 token 详情.
|
|
16
|
+
|
|
17
|
+
<br/>
|
|
18
|
+
|
|
19
|
+
<details>
|
|
20
|
+
<summary><kbd>Improvements and Fixes</kbd></summary>
|
|
21
|
+
|
|
22
|
+
#### Code refactoring
|
|
23
|
+
|
|
24
|
+
- **misc**: 重构 selector 文件组织 ([2ad0ef9](https://github.com/lobehub/lobe-chat/commit/2ad0ef9))
|
|
25
|
+
|
|
26
|
+
#### What's improved
|
|
27
|
+
|
|
28
|
+
- **misc**: 补充 token 详情 ([098f7ff](https://github.com/lobehub/lobe-chat/commit/098f7ff))
|
|
29
|
+
|
|
30
|
+
</details>
|
|
31
|
+
|
|
32
|
+
<div align="right">
|
|
33
|
+
|
|
34
|
+
[](#readme-top)
|
|
35
|
+
|
|
36
|
+
</div>
|
|
37
|
+
|
|
38
|
+
## [Version 0.5.0](https://github.com/lobehub/lobe-chat/compare/v0.4.3...v0.5.0)
|
|
39
|
+
|
|
40
|
+
<sup>Released on **2023-07-22**</sup>
|
|
41
|
+
|
|
42
|
+
#### ✨ Features
|
|
43
|
+
|
|
44
|
+
- **misc**: 支持选择 Emoji.
|
|
45
|
+
|
|
46
|
+
#### 🐛 Bug Fixes
|
|
47
|
+
|
|
48
|
+
- **misc**: 修正 total token 计算不正确的问题.
|
|
49
|
+
|
|
50
|
+
<br/>
|
|
51
|
+
|
|
52
|
+
<details>
|
|
53
|
+
<summary><kbd>Improvements and Fixes</kbd></summary>
|
|
54
|
+
|
|
55
|
+
#### What's improved
|
|
56
|
+
|
|
57
|
+
- **misc**: 支持选择 Emoji ([6cb4828](https://github.com/lobehub/lobe-chat/commit/6cb4828))
|
|
58
|
+
|
|
59
|
+
#### What's fixed
|
|
60
|
+
|
|
61
|
+
- **misc**: 修正 total token 计算不正确的问题 ([17815c6](https://github.com/lobehub/lobe-chat/commit/17815c6))
|
|
62
|
+
|
|
63
|
+
</details>
|
|
64
|
+
|
|
65
|
+
<div align="right">
|
|
66
|
+
|
|
67
|
+
[](#readme-top)
|
|
68
|
+
|
|
69
|
+
</div>
|
|
70
|
+
|
|
5
71
|
### [Version 0.4.3](https://github.com/lobehub/lobe-chat/compare/v0.4.2...v0.4.3)
|
|
6
72
|
|
|
7
73
|
<sup>Released on **2023-07-22**</sup>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lobehub/chat",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Lobe Chat is an open-source chatbot client using LangChain, Typescript and Next.js",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"chatbot",
|
|
@@ -61,6 +61,8 @@
|
|
|
61
61
|
"@ant-design/colors": "^7",
|
|
62
62
|
"@ant-design/icons": "^5",
|
|
63
63
|
"@commitlint/cli": "^17",
|
|
64
|
+
"@emoji-mart/data": "^1",
|
|
65
|
+
"@emoji-mart/react": "^1",
|
|
64
66
|
"@lobehub/ui": "^1",
|
|
65
67
|
"@vercel/analytics": "^1",
|
|
66
68
|
"ahooks": "^3",
|
|
@@ -12,10 +12,13 @@ export default {
|
|
|
12
12
|
'agentTag': '标签',
|
|
13
13
|
'agentTagPlaceholder': '请输入标签',
|
|
14
14
|
'archive': '归档',
|
|
15
|
+
'archiveCurrentMessages': '归档当前会话',
|
|
15
16
|
'autoGenerate': '自动补全',
|
|
16
17
|
'autoGenerateTooltip': '基于提示词自动补全助手描述',
|
|
17
18
|
'cancel': '取消',
|
|
19
|
+
'clearCurrentMessages': '清空当前会话消息',
|
|
18
20
|
'close': '关闭',
|
|
21
|
+
'confirmClearCurrentMessages': '即将清空当前会话消息,清空后将无法找回,请确认你的操作',
|
|
19
22
|
'confirmRemoveSessionItemAlert': '即将删除该助手,删除后该将无法找回,请确认你的操作',
|
|
20
23
|
'defaultAgent': '默认助手',
|
|
21
24
|
'edit': '编辑',
|
|
@@ -36,6 +39,7 @@ export default {
|
|
|
36
39
|
'sessionSetting': '会话设置',
|
|
37
40
|
'setting': '设置',
|
|
38
41
|
'share': '分享',
|
|
42
|
+
'tokenDetail': '系统设定: {{systemRoleToken}} 历史消息: {{chatsToken}}',
|
|
39
43
|
'updateAgent': '更新助理信息',
|
|
40
44
|
'updatePrompt': '更新提示词',
|
|
41
45
|
};
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { ActionIcon, ChatInputArea, DraggablePanel, Icon, TokenTag } from '@lobehub/ui';
|
|
2
|
-
import { Button } from 'antd';
|
|
1
|
+
import { ActionIcon, ChatInputArea, DraggablePanel, Icon, TokenTag, Tooltip } from '@lobehub/ui';
|
|
2
|
+
import { Button, Popconfirm } from 'antd';
|
|
3
3
|
import { encode } from 'gpt-tokenizer';
|
|
4
4
|
import { Archive, Eraser, Languages } from 'lucide-react';
|
|
5
5
|
import { memo, useMemo, useState } from 'react';
|
|
6
|
+
import { useTranslation } from 'react-i18next';
|
|
6
7
|
import { shallow } from 'zustand/shallow';
|
|
7
8
|
|
|
8
9
|
import { CHAT_TEXTAREA_HEIGHT, HEADER_HEIGHT } from '@/const/layoutTokens';
|
|
@@ -11,20 +12,24 @@ import { agentSelectors, chatSelectors, useSessionStore } from '@/store/session'
|
|
|
11
12
|
import { useSettings } from '@/store/settings';
|
|
12
13
|
|
|
13
14
|
const ChatInput = () => {
|
|
15
|
+
const { t } = useTranslation();
|
|
14
16
|
const [expand, setExpand] = useState<boolean>(false);
|
|
15
17
|
const [text, setText] = useState('');
|
|
16
|
-
const
|
|
18
|
+
const inputTokenCount = useMemo(() => encode(text).length, [text]);
|
|
17
19
|
|
|
18
20
|
const [inputHeight] = useSettings((s) => [s.inputHeight], shallow);
|
|
19
|
-
const [totalToken, model, sendMessage, clearMessage] =
|
|
20
|
-
(
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
21
|
+
const [totalToken, systemRoleToken, chatsToken, model, sendMessage, clearMessage] =
|
|
22
|
+
useSessionStore(
|
|
23
|
+
(s) => [
|
|
24
|
+
chatSelectors.totalTokenCount(s),
|
|
25
|
+
chatSelectors.systemRoleTokenCount(s),
|
|
26
|
+
chatSelectors.chatsTokenCount(s),
|
|
27
|
+
agentSelectors.currentAgentModel(s),
|
|
28
|
+
s.createOrSendMsg,
|
|
29
|
+
s.clearMessage,
|
|
30
|
+
],
|
|
31
|
+
shallow,
|
|
32
|
+
);
|
|
28
33
|
|
|
29
34
|
return (
|
|
30
35
|
<DraggablePanel
|
|
@@ -46,12 +51,16 @@ const ChatInput = () => {
|
|
|
46
51
|
actions={
|
|
47
52
|
<>
|
|
48
53
|
<ActionIcon icon={Languages} />
|
|
49
|
-
<
|
|
50
|
-
|
|
54
|
+
<Popconfirm onConfirm={() => clearMessage()} title={t('confirmClearCurrentMessages')}>
|
|
55
|
+
<ActionIcon icon={Eraser} title={t('clearCurrentMessages')} />
|
|
56
|
+
</Popconfirm>
|
|
57
|
+
<Tooltip title={t('tokenDetail', { chatsToken, systemRoleToken })}>
|
|
58
|
+
<TokenTag maxValue={ModelTokens[model]} value={totalToken + inputTokenCount} />
|
|
59
|
+
</Tooltip>
|
|
51
60
|
</>
|
|
52
61
|
}
|
|
53
62
|
expand={expand}
|
|
54
|
-
footer={<Button icon={<Icon icon={Archive} />} />}
|
|
63
|
+
footer={<Button icon={<Icon icon={Archive} title={t('archiveCurrentMessages')} />} />}
|
|
55
64
|
minHeight={CHAT_TEXTAREA_HEIGHT}
|
|
56
65
|
onExpandChange={setExpand}
|
|
57
66
|
onInputChange={setText}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Collapse, InputNumber, Segmented, Slider } from 'antd';
|
|
1
|
+
import { Collapse, ConfigProvider, InputNumber, Segmented, Slider } from 'antd';
|
|
2
2
|
import isEqual from 'fast-deep-equal';
|
|
3
3
|
import { useTranslation } from 'react-i18next';
|
|
4
4
|
import { Flexbox } from 'react-layout-kit';
|
|
@@ -21,7 +21,15 @@ const AgentConfig = () => {
|
|
|
21
21
|
const [updateAgentConfig] = useSessionStore((s) => [s.updateAgentConfig], shallow);
|
|
22
22
|
|
|
23
23
|
return (
|
|
24
|
-
|
|
24
|
+
<ConfigProvider
|
|
25
|
+
theme={{
|
|
26
|
+
components: {
|
|
27
|
+
Segmented: {
|
|
28
|
+
colorBgLayout: theme.isDarkMode ? '#111' : '#f1f1f1',
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
}}
|
|
32
|
+
>
|
|
25
33
|
<Flexbox
|
|
26
34
|
align={'center'}
|
|
27
35
|
distribution={'space-between'}
|
|
@@ -87,8 +95,8 @@ const AgentConfig = () => {
|
|
|
87
95
|
},
|
|
88
96
|
]}
|
|
89
97
|
/>
|
|
90
|
-
</Flexbox>
|
|
91
|
-
|
|
98
|
+
</Flexbox>{' '}
|
|
99
|
+
</ConfigProvider>
|
|
92
100
|
);
|
|
93
101
|
};
|
|
94
102
|
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import data from '@emoji-mart/data';
|
|
2
|
+
import i18n from '@emoji-mart/data/i18n/zh.json';
|
|
3
|
+
import Picker from '@emoji-mart/react';
|
|
4
|
+
import { Avatar } from '@lobehub/ui';
|
|
5
|
+
import { Popover } from 'antd';
|
|
6
|
+
import { createStyles } from 'antd-style';
|
|
7
|
+
import chroma from 'chroma-js';
|
|
8
|
+
import { memo } from 'react';
|
|
9
|
+
import { shallow } from 'zustand/shallow';
|
|
10
|
+
|
|
11
|
+
import { useSessionStore } from '@/store/session';
|
|
12
|
+
import { agentSelectors } from '@/store/session/selectors';
|
|
13
|
+
|
|
14
|
+
const useStyles = createStyles(({ css, token, prefixCls }) => ({
|
|
15
|
+
avatar: css`
|
|
16
|
+
border-radius: 50%;
|
|
17
|
+
transition: scale 400ms ${token.motionEaseOut}, box-shadow 100ms ${token.motionEaseOut};
|
|
18
|
+
|
|
19
|
+
&:hover {
|
|
20
|
+
box-shadow: 0 0 0 3px ${token.colorText};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
&:active {
|
|
24
|
+
scale: 0.8;
|
|
25
|
+
}
|
|
26
|
+
`,
|
|
27
|
+
picker: css`
|
|
28
|
+
em-emoji-picker {
|
|
29
|
+
--rgb-accent: ${chroma(token.colorPrimary) .rgb() .join(',')};
|
|
30
|
+
--shadow: none;
|
|
31
|
+
}
|
|
32
|
+
`,
|
|
33
|
+
popover: css`
|
|
34
|
+
.${prefixCls}-popover-inner {
|
|
35
|
+
padding: 0;
|
|
36
|
+
}
|
|
37
|
+
`,
|
|
38
|
+
}));
|
|
39
|
+
|
|
40
|
+
const EmojiPicker = () => {
|
|
41
|
+
const { styles } = useStyles();
|
|
42
|
+
const [avatar, updateAgentMeta] = useSessionStore(
|
|
43
|
+
(s) => [agentSelectors.currentAgentAvatar(s), s.updateAgentMeta],
|
|
44
|
+
shallow,
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
return (
|
|
48
|
+
<Popover
|
|
49
|
+
content={
|
|
50
|
+
<div className={styles.picker}>
|
|
51
|
+
<Picker
|
|
52
|
+
data={data}
|
|
53
|
+
i18n={i18n}
|
|
54
|
+
locale={'zh'}
|
|
55
|
+
onEmojiSelect={(e: any) => {
|
|
56
|
+
updateAgentMeta({ avatar: e.native });
|
|
57
|
+
}}
|
|
58
|
+
skinTonePosition={'none'}
|
|
59
|
+
theme={'auto'}
|
|
60
|
+
/>
|
|
61
|
+
</div>
|
|
62
|
+
}
|
|
63
|
+
placement={'left'}
|
|
64
|
+
rootClassName={styles.popover}
|
|
65
|
+
trigger={'click'}
|
|
66
|
+
>
|
|
67
|
+
<div className={styles.avatar} style={{ width: 'fit-content' }}>
|
|
68
|
+
<Avatar avatar={avatar} size={200} />
|
|
69
|
+
</div>
|
|
70
|
+
</Popover>
|
|
71
|
+
);
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
export default memo(EmojiPicker);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ActionIcon,
|
|
1
|
+
import { ActionIcon, Input, Tooltip } from '@lobehub/ui';
|
|
2
2
|
import { Button, Collapse } from 'antd';
|
|
3
3
|
import isEqual from 'fast-deep-equal';
|
|
4
4
|
import { LucideSparkles } from 'lucide-react';
|
|
@@ -10,6 +10,7 @@ import { agentSelectors, useSessionStore } from '@/store/session';
|
|
|
10
10
|
|
|
11
11
|
import { FormItem } from '../FormItem';
|
|
12
12
|
import { useStyles } from '../style';
|
|
13
|
+
import EmojiPicker from './EmojiPicker';
|
|
13
14
|
|
|
14
15
|
const AgentMeta = () => {
|
|
15
16
|
const { t } = useTranslation('common');
|
|
@@ -83,7 +84,7 @@ const AgentMeta = () => {
|
|
|
83
84
|
))}
|
|
84
85
|
</Flexbox>
|
|
85
86
|
<FormItem label={t('agentAvatar')}>
|
|
86
|
-
<
|
|
87
|
+
<EmojiPicker />
|
|
87
88
|
</FormItem>
|
|
88
89
|
</Flexbox>
|
|
89
90
|
),
|
package/src/prompts/agent.ts
CHANGED
|
@@ -35,15 +35,25 @@ export const promptSummaryAgentName = (content: string): Partial<OpenAIStreamPay
|
|
|
35
35
|
export const promptPickEmoji = (content: string): Partial<OpenAIStreamPayload> => ({
|
|
36
36
|
messages: [
|
|
37
37
|
{
|
|
38
|
-
content:
|
|
39
|
-
输入:你是一名精通体验设计的设计系统设计师,设计系统存在诸多类别的 token,比如品牌色、成功色等,你需要为各个类别的 token 提供说明文案。
|
|
40
|
-
输出: 💅
|
|
41
|
-
|
|
42
|
-
输入:用户会输入一串 ts 代码,为了确保所有功能和分支的 100% 的覆盖率,你需要给出需要考虑哪些数据场景。
|
|
43
|
-
输出: 🧪
|
|
44
|
-
`,
|
|
38
|
+
content: '你是一名非常懂设计与时尚的设计师,你需要从用户的描述中匹配一个合适的 emoji。',
|
|
45
39
|
role: 'system',
|
|
46
40
|
},
|
|
41
|
+
{
|
|
42
|
+
content: `输入:你是一名精通体验设计的设计系统设计师,设计系统存在诸多类别的 token,比如品牌色、成功色等,你需要为各个类别的 token 提供说明文案。`,
|
|
43
|
+
role: 'user',
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
content: `💅`,
|
|
47
|
+
role: 'assistant',
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
content: `输入:用户会输入一串 ts 代码,为了确保所有功能和分支的 100% 的覆盖率,你需要给出需要考虑哪些数据场景。`,
|
|
51
|
+
role: 'user',
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
content: `🧪`,
|
|
55
|
+
role: 'assistant',
|
|
56
|
+
},
|
|
47
57
|
{
|
|
48
58
|
content: `输入:${content}`,
|
|
49
59
|
role: 'user',
|
|
@@ -54,11 +64,12 @@ export const promptPickEmoji = (content: string): Partial<OpenAIStreamPayload> =
|
|
|
54
64
|
export const promptSummaryDescription = (content: string): Partial<OpenAIStreamPayload> => ({
|
|
55
65
|
messages: [
|
|
56
66
|
{
|
|
57
|
-
content:
|
|
67
|
+
content:
|
|
68
|
+
'你是一名擅长会话的助理,你需要将用户的输入的内容总结为一个专家的简介,不超过 20 个字',
|
|
58
69
|
role: 'system',
|
|
59
70
|
},
|
|
60
71
|
{
|
|
61
|
-
content:
|
|
72
|
+
content: `${content}`,
|
|
62
73
|
role: 'user',
|
|
63
74
|
},
|
|
64
75
|
],
|
|
@@ -1,13 +1,11 @@
|
|
|
1
|
-
import { encode } from 'gpt-tokenizer';
|
|
2
|
-
|
|
3
1
|
import { agentSelectors } from '@/store/session';
|
|
4
2
|
import { ChatMessage } from '@/types/chatMessage';
|
|
5
3
|
|
|
6
|
-
import type { SessionStore } from '
|
|
7
|
-
import { sessionSelectors } from '
|
|
4
|
+
import type { SessionStore } from '../../../store';
|
|
5
|
+
import { sessionSelectors } from '../../session';
|
|
8
6
|
|
|
9
7
|
// 展示在聊天框中的消息
|
|
10
|
-
const currentChats = (s: SessionStore): ChatMessage[] => {
|
|
8
|
+
export const currentChats = (s: SessionStore): ChatMessage[] => {
|
|
11
9
|
const session = sessionSelectors.currentSession(s);
|
|
12
10
|
if (!session) return [];
|
|
13
11
|
|
|
@@ -53,31 +51,8 @@ const currentChats = (s: SessionStore): ChatMessage[] => {
|
|
|
53
51
|
return finalList;
|
|
54
52
|
};
|
|
55
53
|
|
|
56
|
-
const systemRoleSel = (s: SessionStore): string => {
|
|
54
|
+
export const systemRoleSel = (s: SessionStore): string => {
|
|
57
55
|
const config = agentSelectors.currentAgentConfigSafe(s);
|
|
58
56
|
|
|
59
57
|
return config.systemRole;
|
|
60
58
|
};
|
|
61
|
-
|
|
62
|
-
const totalTokens = (s: SessionStore): number[] => {
|
|
63
|
-
const chats = currentChats(s);
|
|
64
|
-
return encode(chats.map((m) => m.content).join(''));
|
|
65
|
-
};
|
|
66
|
-
|
|
67
|
-
const systemRoleTokens = (s: SessionStore): number[] => {
|
|
68
|
-
const systemRole = systemRoleSel(s);
|
|
69
|
-
|
|
70
|
-
return encode(systemRole || '');
|
|
71
|
-
};
|
|
72
|
-
|
|
73
|
-
const totalTokenCount = (s: SessionStore) => totalTokens(s).length;
|
|
74
|
-
|
|
75
|
-
const systemRoleTokenCount = (s: SessionStore) => systemRoleTokens(s).length;
|
|
76
|
-
|
|
77
|
-
export const chatSelectors = {
|
|
78
|
-
currentChats,
|
|
79
|
-
systemRoleTokenCount,
|
|
80
|
-
systemRoleTokens,
|
|
81
|
-
totalTokenCount,
|
|
82
|
-
totalTokens,
|
|
83
|
-
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { encode } from 'gpt-tokenizer';
|
|
2
|
+
|
|
3
|
+
import type { SessionStore } from '../../../store';
|
|
4
|
+
import { currentChats, systemRoleSel } from './chat';
|
|
5
|
+
|
|
6
|
+
const systemRoleTokens = (s: SessionStore): number[] => {
|
|
7
|
+
const systemRole = systemRoleSel(s);
|
|
8
|
+
|
|
9
|
+
return encode(systemRole || '');
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const chatsTokens = (s: SessionStore): number[] => {
|
|
13
|
+
const chats = currentChats(s);
|
|
14
|
+
return encode(chats.map((m) => m.content).join(''));
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export const chatsTokenCount = (s: SessionStore) => chatsTokens(s).length;
|
|
18
|
+
|
|
19
|
+
export const systemRoleTokenCount = (s: SessionStore) => systemRoleTokens(s).length;
|
|
20
|
+
|
|
21
|
+
export const totalTokenCount = (s: SessionStore) => chatsTokens(s).length + systemRoleTokenCount(s);
|