@lobehub/chat 0.4.2 → 0.5.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 CHANGED
@@ -2,6 +2,64 @@
2
2
 
3
3
  # Changelog
4
4
 
5
+ ## [Version 0.5.0](https://github.com/lobehub/lobe-chat/compare/v0.4.3...v0.5.0)
6
+
7
+ <sup>Released on **2023-07-22**</sup>
8
+
9
+ #### ✨ Features
10
+
11
+ - **misc**: 支持选择 Emoji.
12
+
13
+ #### 🐛 Bug Fixes
14
+
15
+ - **misc**: 修正 total token 计算不正确的问题.
16
+
17
+ <br/>
18
+
19
+ <details>
20
+ <summary><kbd>Improvements and Fixes</kbd></summary>
21
+
22
+ #### What's improved
23
+
24
+ - **misc**: 支持选择 Emoji ([6cb4828](https://github.com/lobehub/lobe-chat/commit/6cb4828))
25
+
26
+ #### What's fixed
27
+
28
+ - **misc**: 修正 total token 计算不正确的问题 ([17815c6](https://github.com/lobehub/lobe-chat/commit/17815c6))
29
+
30
+ </details>
31
+
32
+ <div align="right">
33
+
34
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
35
+
36
+ </div>
37
+
38
+ ### [Version 0.4.3](https://github.com/lobehub/lobe-chat/compare/v0.4.2...v0.4.3)
39
+
40
+ <sup>Released on **2023-07-22**</sup>
41
+
42
+ #### ♻ Code Refactoring
43
+
44
+ - **misc**: 优化 edit 代码结构.
45
+
46
+ <br/>
47
+
48
+ <details>
49
+ <summary><kbd>Improvements and Fixes</kbd></summary>
50
+
51
+ #### Code refactoring
52
+
53
+ - **misc**: 优化 edit 代码结构 ([fdb3a3f](https://github.com/lobehub/lobe-chat/commit/fdb3a3f))
54
+
55
+ </details>
56
+
57
+ <div align="right">
58
+
59
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
60
+
61
+ </div>
62
+
5
63
  ### [Version 0.4.2](https://github.com/lobehub/lobe-chat/compare/v0.4.1...v0.4.2)
6
64
 
7
65
  <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.4.2",
3
+ "version": "0.5.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,8 +12,8 @@ import { useTranslation } from 'react-i18next';
12
12
  import { Flexbox } from 'react-layout-kit';
13
13
  import { shallow } from 'zustand/shallow';
14
14
 
15
+ import HeaderSpacing from '@/components/HeaderSpacing';
15
16
  import { CHAT_SIDEBAR_WIDTH } from '@/const/layoutTokens';
16
- import HeaderSpacing from '@/features/HeaderSpacing';
17
17
  import { useSessionStore } from '@/store/session';
18
18
 
19
19
  import ReadMode from './ReadMode';
@@ -1,7 +1,7 @@
1
1
  import { memo } from 'react';
2
2
  import { Flexbox } from 'react-layout-kit';
3
3
 
4
- import HeaderSpacing from '@/features/HeaderSpacing';
4
+ import HeaderSpacing from '@/components/HeaderSpacing';
5
5
  import ChatInput from '@/pages/chat/[id]/Conversation/Input';
6
6
 
7
7
  import ChatList from './ChatList';
@@ -8,7 +8,7 @@ import { shallow } from 'zustand/shallow';
8
8
 
9
9
  import { agentSelectors, useSessionStore } from '@/store/session';
10
10
 
11
- import { FormItem } from './FormItem';
11
+ import { FormItem } from '../FormItem';
12
12
 
13
13
  export const useStyles = createStyles(({ css, token }) => ({
14
14
  input: css`
@@ -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';
@@ -7,9 +7,9 @@ import { shallow } from 'zustand/shallow';
7
7
  import { agentSelectors, useSessionStore } from '@/store/session';
8
8
  import { LanguageModel } from '@/types/llm';
9
9
 
10
- import { FormItem } from './FormItem';
10
+ import { FormItem } from '../FormItem';
11
+ import { useStyles } from '../style';
11
12
  import Prompt from './Prompt';
12
- import { useStyles } from './style';
13
13
 
14
14
  const AgentConfig = () => {
15
15
  const { t } = useTranslation('common');
@@ -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, Avatar, Input, Tooltip } from '@lobehub/ui';
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';
@@ -8,8 +8,9 @@ import { shallow } from 'zustand/shallow';
8
8
 
9
9
  import { agentSelectors, useSessionStore } from '@/store/session';
10
10
 
11
- import { FormItem } from './FormItem';
12
- import { useStyles } from './style';
11
+ import { FormItem } from '../FormItem';
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
- <Avatar avatar={metaData.avatar} size={200} />
87
+ <EmojiPicker />
87
88
  </FormItem>
88
89
  </Flexbox>
89
90
  ),
@@ -7,7 +7,7 @@ import { memo } from 'react';
7
7
  import { useTranslation } from 'react-i18next';
8
8
  import { Flexbox } from 'react-layout-kit';
9
9
 
10
- import HeaderSpacing from '@/features/HeaderSpacing';
10
+ import HeaderSpacing from '@/components/HeaderSpacing';
11
11
 
12
12
  import ChatLayout from '../../layout';
13
13
  import AgentConfig from './AgentConfig';
@@ -3,7 +3,7 @@ import { memo, useEffect } from 'react';
3
3
  import { useTranslation } from 'react-i18next';
4
4
  import { Flexbox } from 'react-layout-kit';
5
5
 
6
- import HeaderSpacing from '@/features/HeaderSpacing';
6
+ import HeaderSpacing from '@/components/HeaderSpacing';
7
7
  import { createI18nNext } from '@/locales/create';
8
8
  import ChatLayout from '@/pages/chat/layout';
9
9
 
@@ -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: `你是一名非常懂设计与时尚的设计师,你需要从用户的描述中匹配一个合适的 emoji。
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: '你是一名擅长会话的助理,你需要将用户的提示词做一个3句话以内的总结。',
67
+ content:
68
+ '你是一名擅长会话的助理,你需要将用户的输入的内容总结为一个专家的简介,不超过 20 个字',
58
69
  role: 'system',
59
70
  },
60
71
  {
61
- content: `输入:${content}`,
72
+ content: `${content}`,
62
73
  role: 'user',
63
74
  },
64
75
  ],
@@ -59,25 +59,24 @@ const systemRoleSel = (s: SessionStore): string => {
59
59
  return config.systemRole;
60
60
  };
61
61
 
62
- const totalTokens = (s: SessionStore): number[] => {
63
- const chats = currentChats(s);
64
- return encode(chats.map((m) => m.content).join(''));
65
- };
66
-
67
62
  const systemRoleTokens = (s: SessionStore): number[] => {
68
63
  const systemRole = systemRoleSel(s);
69
64
 
70
65
  return encode(systemRole || '');
71
66
  };
72
67
 
73
- const totalTokenCount = (s: SessionStore) => totalTokens(s).length;
68
+ const chatsTokens = (s: SessionStore): number[] => {
69
+ const chats = currentChats(s);
70
+ return encode(chats.map((m) => m.content).join(''));
71
+ };
74
72
 
75
73
  const systemRoleTokenCount = (s: SessionStore) => systemRoleTokens(s).length;
76
74
 
75
+ const totalTokenCount = (s: SessionStore) => chatsTokens(s).length + systemRoleTokenCount(s);
76
+
77
77
  export const chatSelectors = {
78
78
  currentChats,
79
79
  systemRoleTokenCount,
80
80
  systemRoleTokens,
81
81
  totalTokenCount,
82
- totalTokens,
83
82
  };
@@ -7,11 +7,33 @@ import type { SidebarTabKey } from './initialState';
7
7
  import { DEFAULT_SETTINGS, GlobalSettingsState } from './initialState';
8
8
  import type { SettingsStore } from './store';
9
9
 
10
+ /**
11
+ * 设置操作
12
+ */
10
13
  export interface SettingsAction {
14
+ /**
15
+ * 重置设置
16
+ */
11
17
  resetSettings: () => void;
18
+ /**
19
+ * 设置全局设置
20
+ * @param settings - 全局设置
21
+ */
12
22
  setGlobalSettings: (settings: GlobalSettingsState) => void;
23
+ /**
24
+ * 设置部分配置设置
25
+ * @param settings - 部分配置设置
26
+ */
13
27
  setSettings: (settings: Partial<ConfigSettings>) => void;
28
+ /**
29
+ * 设置主题模式
30
+ * @param themeMode - 主题模式
31
+ */
14
32
  setThemeMode: (themeMode: ThemeMode) => void;
33
+ /**
34
+ * 切换侧边栏选项
35
+ * @param key - 选中的侧边栏选项
36
+ */
15
37
  switchSideBar: (key: SidebarTabKey) => void;
16
38
  }
17
39
 
@@ -1,6 +1,8 @@
1
1
  import type { ConfigSettings } from '@/types/exportConfig';
2
+ import { LanguageModel } from '@/types/llm';
2
3
 
3
4
  export type SidebarTabKey = 'chat' | 'market';
5
+
4
6
  export const DEFAULT_SETTINGS: ConfigSettings = {
5
7
  accessCode: '',
6
8
  avatar: '',
@@ -14,7 +16,7 @@ export const DEFAULT_SETTINGS: ConfigSettings = {
14
16
  historyCount: 24,
15
17
  language: 'zh-CN',
16
18
  maxTokens: 2000,
17
- model: 'gpt-3.5-turbo',
19
+ model: LanguageModel.GPT3_5,
18
20
  neutralColor: '',
19
21
  presencePenalty: 0,
20
22
  primaryColor: '',
@@ -1,6 +1,8 @@
1
1
  import type { NeutralColors, PrimaryColors } from '@lobehub/ui';
2
2
  import { ThemeMode } from 'antd-style';
3
3
 
4
+ import { LanguageModel } from '@/types/llm';
5
+
4
6
  import { Locales } from './locale';
5
7
 
6
8
  /**
@@ -19,7 +21,7 @@ export interface ConfigSettings {
19
21
  historyCount: number;
20
22
  language: Locales;
21
23
  maxTokens: number;
22
- model: string;
24
+ model: LanguageModel;
23
25
  neutralColor: NeutralColors | '';
24
26
  presencePenalty: number;
25
27
  primaryColor: PrimaryColors | '';
package/tsconfig.json CHANGED
@@ -18,7 +18,7 @@
18
18
  "baseUrl": ".",
19
19
  "types": ["vitest/globals"],
20
20
  "paths": {
21
- "@/*": ["src/*"]
21
+ "@/*": ["./src/*"]
22
22
  }
23
23
  },
24
24
  "exclude": ["node_modules"],
@@ -1,36 +0,0 @@
1
- import { ChatMessage } from '@lobehub/ui';
2
-
3
- import { getInputVariablesFromMessages } from '@/helpers/prompt';
4
-
5
- describe('getInputVariablesFromMessages 方法', () => {
6
- it('应当在输入为空数组时返回空数组', () => {
7
- const result = getInputVariablesFromMessages([]);
8
- expect(result).toEqual([]);
9
- });
10
-
11
- it('应当在输入消息不包含变量时返回空数组', () => {
12
- const input: ChatMessage[] = [{ role: 'user', content: 'Hello, how are you?' }];
13
- const result = getInputVariablesFromMessages(input);
14
- expect(result).toEqual([]);
15
- });
16
-
17
- it('应当在输入消息包含变量时返回包含变量、模板和消息索引的对象数组', () => {
18
- const input: ChatMessage[] = [
19
- { role: 'user', content: 'Hello {name}, how are you?' },
20
- { role: 'user', content: 'My name is {name}' },
21
- ];
22
- const expectedOutput = ['name'];
23
- const result = getInputVariablesFromMessages(input);
24
- expect(result).toEqual(expectedOutput);
25
- });
26
-
27
- it('多个变量的情况', () => {
28
- const input: ChatMessage[] = [
29
- { role: 'user', content: 'Hello {name}, how are {you}?' },
30
- { role: 'user', content: 'My name is {name}' },
31
- ];
32
- const expectedOutput = ['name', 'you'];
33
- const result = getInputVariablesFromMessages(input);
34
- expect(result).toEqual(expectedOutput);
35
- });
36
- });
@@ -1,36 +0,0 @@
1
- import { ChatMessage } from '@lobehub/ui';
2
- import {
3
- AIMessagePromptTemplate,
4
- ChatPromptTemplate,
5
- HumanMessagePromptTemplate,
6
- SystemMessagePromptTemplate,
7
- } from 'langchain/prompts';
8
-
9
- export const getChatPromptTemplate = (chatMessages: ChatMessage[]) =>
10
- ChatPromptTemplate.fromPromptMessages(
11
- chatMessages.map((m) => {
12
- switch (m.role) {
13
- default:
14
- case 'user': {
15
- return HumanMessagePromptTemplate.fromTemplate(m.content);
16
- }
17
-
18
- case 'system': {
19
- return SystemMessagePromptTemplate.fromTemplate(m.content);
20
- }
21
-
22
- case 'assistant': {
23
- return AIMessagePromptTemplate.fromTemplate(m.content);
24
- }
25
- }
26
- }),
27
- );
28
- export const getInputVariablesFromMessages = (chatMessages: ChatMessage[]) => {
29
- let inputVariables: string[] = [];
30
- try {
31
- const chatPrompt = getChatPromptTemplate(chatMessages);
32
- inputVariables = chatPrompt.inputVariables;
33
- } catch {}
34
-
35
- return inputVariables;
36
- };