@lobehub/chat 0.39.4 → 0.40.1

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,56 @@
2
2
 
3
3
  # Changelog
4
4
 
5
+ ### [Version 0.40.1](https://github.com/lobehub/lobe-chat/compare/v0.40.0...v0.40.1)
6
+
7
+ <sup>Released on **2023-08-06**</sup>
8
+
9
+ #### 🐛 Bug Fixes
10
+
11
+ - **misc**: 优化 openai 接口的错误处理逻辑.
12
+
13
+ <br/>
14
+
15
+ <details>
16
+ <summary><kbd>Improvements and Fixes</kbd></summary>
17
+
18
+ #### What's fixed
19
+
20
+ - **misc**: 优化 openai 接口的错误处理逻辑 ([eae78fe](https://github.com/lobehub/lobe-chat/commit/eae78fe))
21
+
22
+ </details>
23
+
24
+ <div align="right">
25
+
26
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
27
+
28
+ </div>
29
+
30
+ ## [Version 0.40.0](https://github.com/lobehub/lobe-chat/compare/v0.39.4...v0.40.0)
31
+
32
+ <sup>Released on **2023-08-05**</sup>
33
+
34
+ #### ✨ Features
35
+
36
+ - **misc**: Add new dependency, add Tag and PluginTag components, update HeaderTitle.
37
+
38
+ <br/>
39
+
40
+ <details>
41
+ <summary><kbd>Improvements and Fixes</kbd></summary>
42
+
43
+ #### What's improved
44
+
45
+ - **misc**: Add new dependency, add Tag and PluginTag components, update HeaderTitle, closes [#56](https://github.com/lobehub/lobe-chat/issues/56) [#55](https://github.com/lobehub/lobe-chat/issues/55) [#54](https://github.com/lobehub/lobe-chat/issues/54) ([2812ea2](https://github.com/lobehub/lobe-chat/commit/2812ea2))
46
+
47
+ </details>
48
+
49
+ <div align="right">
50
+
51
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
52
+
53
+ </div>
54
+
5
55
  ### [Version 0.39.4](https://github.com/lobehub/lobe-chat/compare/v0.39.3...v0.39.4)
6
56
 
7
57
  <sup>Released on **2023-08-05**</sup>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobehub/chat",
3
- "version": "0.39.4",
3
+ "version": "0.40.1",
4
4
  "description": "Lobe Chat is an open-source chatbot client using LangChain, Typescript and Next.js",
5
5
  "keywords": [
6
6
  "chatbot",
@@ -64,6 +64,7 @@
64
64
  "@commitlint/cli": "^17",
65
65
  "@emoji-mart/data": "^1",
66
66
  "@emoji-mart/react": "^1",
67
+ "@icons-pack/react-simple-icons": "^8",
67
68
  "@lobehub/ui": "latest",
68
69
  "@vercel/analytics": "^1",
69
70
  "ahooks": "^3",
@@ -1,21 +1,21 @@
1
- import { ActionIcon } from '@lobehub/ui';
1
+ import { ActionIcon, DivProps } from '@lobehub/ui';
2
2
  import { X } from 'lucide-react';
3
3
  import { memo, useState } from 'react';
4
4
 
5
5
  import { useStyles } from './style';
6
6
 
7
- interface EmptyProps {
7
+ interface EmptyProps extends DivProps {
8
8
  cover: string;
9
9
  desc: string;
10
10
  title: string;
11
11
  }
12
12
 
13
- const Empty = memo<EmptyProps>(({ cover, title, desc }) => {
13
+ const Empty = memo<EmptyProps>(({ cover, title, desc, ...props }) => {
14
14
  const [visiable, setVisiable] = useState(true);
15
15
  const { styles } = useStyles();
16
16
  if (!visiable) return null;
17
17
  return (
18
- <div className={styles.container}>
18
+ <div className={styles.container} {...props}>
19
19
  <ActionIcon
20
20
  className={styles.close}
21
21
  icon={X}
@@ -3,16 +3,41 @@ import { ReactNode, memo } from 'react';
3
3
  import { Flexbox } from 'react-layout-kit';
4
4
 
5
5
  const useStyles = createStyles(({ css, token }) => ({
6
+ container: css`
7
+ position: relative;
8
+ overflow: hidden;
9
+ flex: 1;
10
+ max-width: 100%;
11
+ `,
6
12
  desc: css`
13
+ overflow: hidden;
14
+
15
+ width: 100%;
16
+
7
17
  font-size: 12px;
8
18
  color: ${token.colorTextTertiary};
19
+ text-overflow: ellipsis;
20
+ white-space: nowrap;
21
+ `,
22
+ tag: css`
23
+ flex: none;
9
24
  `,
10
25
  title: css`
26
+ overflow: hidden;
27
+
11
28
  font-size: 16px;
12
29
  font-weight: bold;
30
+ text-overflow: ellipsis;
31
+ white-space: nowrap;
32
+ `,
33
+ titleContainer: css`
34
+ flex: 1;
13
35
  `,
14
36
  titleWithDesc: css`
37
+ overflow: hidden;
15
38
  font-weight: bold;
39
+ text-overflow: ellipsis;
40
+ white-space: nowrap;
16
41
  `,
17
42
  }));
18
43
 
@@ -26,20 +51,24 @@ const HeaderTitle = memo<HeaderTitleProps>(({ title, desc, tag }) => {
26
51
  const { styles } = useStyles();
27
52
  if (desc)
28
53
  return (
29
- <Flexbox>
30
- <Flexbox align={'center'} className={styles.titleWithDesc} gap={8} horizontal>
31
- {title}
32
- {tag}
54
+ <Flexbox className={styles.container}>
55
+ <Flexbox align={'center'} className={styles.titleContainer} gap={8} horizontal>
56
+ <div className={styles.titleWithDesc}>{title}</div>
57
+ <Flexbox className={styles.tag} horizontal>
58
+ {tag}
59
+ </Flexbox>
33
60
  </Flexbox>
34
- <Flexbox align={'center'} className={styles.desc} horizontal>
35
- {desc}
61
+ <Flexbox align={'center'} horizontal>
62
+ <div className={styles.desc}>{desc}</div>
36
63
  </Flexbox>
37
64
  </Flexbox>
38
65
  );
39
66
  return (
40
- <Flexbox align={'center'} className={styles.title} gap={8} horizontal>
41
- {title}
42
- {tag}
67
+ <Flexbox align={'center'} className={styles.container} gap={8} horizontal>
68
+ <div className={styles.title}>{title}</div>
69
+ <Flexbox className={styles.tag} horizontal>
70
+ {tag}
71
+ </Flexbox>
43
72
  </Flexbox>
44
73
  );
45
74
  });
@@ -0,0 +1,38 @@
1
+ import { Avatar } from '@lobehub/ui';
2
+ import type { MenuProps } from 'antd';
3
+ import { Dropdown } from 'antd';
4
+ import { memo } from 'react';
5
+ import { useTranslation } from 'react-i18next';
6
+
7
+ import { PluginsMap } from '@/plugins';
8
+
9
+ import Tag from './index';
10
+
11
+ export interface PluginTagProps {
12
+ plugins: string[];
13
+ }
14
+
15
+ const PluginTag = memo<PluginTagProps>(({ plugins }) => {
16
+ const { t } = useTranslation('plugin');
17
+
18
+ if (plugins.length === 0) return <Tag type={'plugin'}>{t(`plugins.${plugins[0]}` as any)}</Tag>;
19
+
20
+ const items: MenuProps['items'] = plugins.map((id) => ({
21
+ icon: (
22
+ <Avatar avatar={PluginsMap[id].avatar} size={24} style={{ marginLeft: -6, marginRight: 2 }} />
23
+ ),
24
+ key: id,
25
+ label: t(`plugins.${id}` as any),
26
+ }));
27
+ return (
28
+ <Dropdown menu={{ items }}>
29
+ <div style={{ height: 20 }}>
30
+ <Tag count={plugins.length} type={'plugin'}>
31
+ {t(`plugins.${plugins[0]}` as any)}
32
+ </Tag>
33
+ </div>
34
+ </Dropdown>
35
+ );
36
+ });
37
+
38
+ export default PluginTag;
@@ -0,0 +1,79 @@
1
+ import { SiOpenai } from '@icons-pack/react-simple-icons';
2
+ import { Tag as AntTag, type TagProps as AntTagProps } from 'antd';
3
+ import { createStyles } from 'antd-style';
4
+ import { capitalize as cap } from 'lodash-es';
5
+ import { type LucideIcon, Milestone, ToyBrick } from 'lucide-react';
6
+ import { ReactNode, memo } from 'react';
7
+ import { Flexbox } from 'react-layout-kit';
8
+
9
+ const useStyles = createStyles(({ cx, css, token }, hasCount?: boolean) => ({
10
+ count: css`
11
+ display: flex;
12
+ align-items: center;
13
+
14
+ height: 20px;
15
+ padding: 0 8px;
16
+
17
+ font-size: 12px;
18
+ line-height: 1;
19
+
20
+ background: ${token.colorFillTertiary};
21
+ border: ${token.borderRadius}px;
22
+ `,
23
+ tag: cx(
24
+ css`
25
+ color: ${token.colorTextSecondary} !important;
26
+ background: ${token.colorFillSecondary};
27
+ border: ${token.borderRadius}px;
28
+
29
+ &:hover {
30
+ color: ${token.colorText};
31
+ background: ${token.colorFill};
32
+ }
33
+ `,
34
+ hasCount &&
35
+ css`
36
+ padding-right: 0 !important;
37
+ `,
38
+ ),
39
+ }));
40
+
41
+ export interface TagProps extends AntTagProps {
42
+ capitalize?: boolean;
43
+ children: string;
44
+ count?: number;
45
+ icon?: ReactNode;
46
+ type?: 'openai' | 'plugin' | 'version';
47
+ }
48
+
49
+ const Tag = memo<TagProps>(({ type = 'openai', icon, capitalize, count, children, ...props }) => {
50
+ const { styles } = useStyles(Boolean(count && count > 1));
51
+ let IconRender: LucideIcon | any;
52
+
53
+ switch (type) {
54
+ case 'openai': {
55
+ IconRender = SiOpenai;
56
+ break;
57
+ }
58
+ case 'version': {
59
+ IconRender = Milestone;
60
+ break;
61
+ }
62
+ case 'plugin': {
63
+ IconRender = ToyBrick;
64
+ break;
65
+ }
66
+ }
67
+
68
+ return (
69
+ <AntTag bordered={false} className={styles.tag} {...props}>
70
+ <Flexbox align={'center'} gap={4} horizontal>
71
+ {icon ? icon : <IconRender size={'1em'} />}
72
+ {capitalize ? cap(children) : children}
73
+ {count && <div className={styles.count}>{`${count}+`}</div>}
74
+ </Flexbox>
75
+ </AntTag>
76
+ );
77
+ });
78
+
79
+ export default Tag;
@@ -0,0 +1,2 @@
1
+ export { default as PluginTag } from './PluginTag';
2
+ export { default } from './Tag';
@@ -10,6 +10,7 @@ const getStatus = (errorType: ErrorType) => {
10
10
  return 577;
11
11
  }
12
12
  }
13
+ return errorType;
13
14
  };
14
15
 
15
16
  export const createErrorResponse = (errorType: ErrorType, body?: any) => {
@@ -65,20 +65,35 @@ export const createChatCompletion = async ({
65
65
 
66
66
  const requestParams = { functions, messages: formatMessages, stream: true, ...params };
67
67
 
68
+ let response: Response;
69
+
68
70
  try {
69
- const response = await openai.createChatCompletion(requestParams);
71
+ response = await openai.createChatCompletion(requestParams);
72
+ } catch (error) {
73
+ // 如果 await 超时报错,说明是 OpenAI 服务端的问题
74
+ return createErrorResponse(ErrorType.GatewayTimeout, { message: error });
75
+ }
70
76
 
71
- if (!response.ok) {
72
- const error = await response.json();
77
+ // ============ 4. 处理异常响应 ============ //
78
+ if (!response.ok) {
79
+ let error;
73
80
 
74
- return createErrorResponse(ErrorType.OpenAIBizError, error);
75
- }
81
+ try {
82
+ // 正常情况下应该是 OpenAI 的 JSON
83
+ error = await response.clone().json();
84
+ } catch {
85
+ // 如果不是 JSON,那么可能是其他接口端的响应,读 text 为结果
86
+ const result = await response.text();
76
87
 
77
- const stream = OpenAIStream(response, callbacks?.(requestParams));
88
+ error = { message: result };
89
+ }
78
90
 
79
- return new StreamingTextResponse(stream);
80
- } catch (error) {
81
- // 如果 await 超时报错,说明是 OpenAI 服务端的问题
82
- return createErrorResponse(ErrorType.GatewayTimeout, { message: error });
91
+ return createErrorResponse(ErrorType.OpenAIBizError, { ...error, endpoint });
83
92
  }
93
+
94
+ // ============ 5. 发送正常相应 ============ //
95
+
96
+ const stream = OpenAIStream(response, callbacks?.(requestParams));
97
+
98
+ return new StreamingTextResponse(stream);
84
99
  };
@@ -1,12 +1,13 @@
1
1
  import { ActionIcon, Avatar, Icon, List } from '@lobehub/ui';
2
2
  import { useHover } from 'ahooks';
3
- import { App, Dropdown, type MenuProps, Tag } from 'antd';
3
+ import { App, Dropdown, type MenuProps } from 'antd';
4
4
  import { FolderOutput, MoreVertical, Pin, PinOff, Trash } from 'lucide-react';
5
5
  import { FC, memo, useMemo, useRef, useState } from 'react';
6
6
  import { useTranslation } from 'react-i18next';
7
7
  import { Flexbox } from 'react-layout-kit';
8
8
  import { shallow } from 'zustand/shallow';
9
9
 
10
+ import Tag from '@/components/Tag';
10
11
  import { exportSingleAgent, exportSingleSession } from '@/helpers/export';
11
12
  import { agentSelectors, chatSelectors, sessionSelectors, useSessionStore } from '@/store/session';
12
13
  import { useSettings } from '@/store/settings';
@@ -28,7 +29,7 @@ const SessionItem: FC<SessionItemProps> = memo(({ id, active = true, loading })
28
29
 
29
30
  const { t } = useTranslation('common');
30
31
 
31
- const { styles, theme } = useStyles();
32
+ const { styles } = useStyles();
32
33
  const [defaultModel] = useSettings((s) => [s.settings.defaultAgent.config?.model], shallow);
33
34
 
34
35
  const [
@@ -146,6 +147,17 @@ const SessionItem: FC<SessionItemProps> = memo(({ id, active = true, loading })
146
147
  </Dropdown>
147
148
  }
148
149
  active={active}
150
+ addon={
151
+ !(showModel || showChatLength) ? undefined : (
152
+ <Flexbox gap={4} horizontal style={{ flexWrap: 'wrap' }}>
153
+ {showModel && (
154
+ <Tag capitalize type={'openai'}>
155
+ {model}
156
+ </Tag>
157
+ )}
158
+ </Flexbox>
159
+ )
160
+ }
149
161
  avatar={
150
162
  <Avatar
151
163
  animation={isHovering}
@@ -158,21 +170,7 @@ const SessionItem: FC<SessionItemProps> = memo(({ id, active = true, loading })
158
170
  }
159
171
  className={styles.container}
160
172
  date={updateAt}
161
- description={
162
- <Flexbox gap={4}>
163
- <Flexbox>{description || systemRole}</Flexbox>
164
-
165
- {!(showModel || showChatLength) ? undefined : (
166
- <Flexbox horizontal>
167
- {showModel && (
168
- <Tag bordered={false} style={{ color: theme.colorTextSecondary }}>
169
- {model}
170
- </Tag>
171
- )}
172
- </Flexbox>
173
- )}
174
- </Flexbox>
175
- }
173
+ description={description || systemRole}
176
174
  loading={loading}
177
175
  pin={pin}
178
176
  ref={ref}
@@ -1,9 +1,9 @@
1
- import { Tag } from 'antd';
2
1
  import { createStyles } from 'antd-style';
3
2
  import { ReactNode } from 'react';
4
3
  import { Flexbox } from 'react-layout-kit';
5
4
  import { shallow } from 'zustand/shallow';
6
5
 
6
+ import Tag from '@/components/Tag';
7
7
  import { agentSelectors, useSessionStore } from '@/store/session';
8
8
  import { ChatMessage } from '@/types/chatMessage';
9
9
 
@@ -45,8 +45,8 @@ const MessageExtra = ({
45
45
 
46
46
  const modelTag = (
47
47
  <div>
48
- <Tag bordered={false} style={{ borderRadius: 6 }}>
49
- {extra?.fromModel}
48
+ <Tag capitalize type={'openai'}>
49
+ {extra?.fromModel as string}
50
50
  </Tag>
51
51
  </div>
52
52
  );
@@ -1,5 +1,4 @@
1
1
  import { ActionIcon, Avatar, ChatHeader } from '@lobehub/ui';
2
- import { Tag } from 'antd';
3
2
  import { PanelRightClose, PanelRightOpen, Settings, Share2 } from 'lucide-react';
4
3
  import Router from 'next/router';
5
4
  import { memo } from 'react';
@@ -7,13 +6,14 @@ import { useTranslation } from 'react-i18next';
7
6
  import { shallow } from 'zustand/shallow';
8
7
 
9
8
  import HeaderTitle from '@/components/HeaderTitle';
9
+ import Tag, { PluginTag } from '@/components/Tag';
10
10
  import { agentSelectors, useSessionStore } from '@/store/session';
11
11
  import { useSettings } from '@/store/settings';
12
12
 
13
13
  const Header = memo(() => {
14
14
  const { t } = useTranslation('common');
15
15
 
16
- const [title, description, avatar, backgroundColor, id, model] = useSessionStore(
16
+ const [title, description, avatar, backgroundColor, id, model, plugins] = useSessionStore(
17
17
  (s) => [
18
18
  agentSelectors.currentAgentTitle(s),
19
19
  agentSelectors.currentAgentDescription(s),
@@ -21,6 +21,7 @@ const Header = memo(() => {
21
21
  agentSelectors.currentAgentBackgroundColor(s),
22
22
  s.activeId,
23
23
  agentSelectors.currentAgentModel(s),
24
+ agentSelectors.currentAgentPlugins(s),
24
25
  ],
25
26
  shallow,
26
27
  );
@@ -38,13 +39,24 @@ const Header = memo(() => {
38
39
  avatar={avatar}
39
40
  background={backgroundColor}
40
41
  onClick={() => {
41
- Router.push(`/chat/${id}/edit`);
42
+ Router.push(`/chat/${id}/setting`);
42
43
  }}
43
44
  size={40}
44
- style={{ cursor: 'pointer' }}
45
+ style={{ cursor: 'pointer', flex: 'none' }}
46
+ title={title}
47
+ />
48
+ <HeaderTitle
49
+ desc={description}
50
+ tag={
51
+ <>
52
+ <Tag capitalize type={'openai'}>
53
+ {model}
54
+ </Tag>
55
+ {plugins?.length > 1 && <PluginTag plugins={plugins} />}
56
+ </>
57
+ }
45
58
  title={title}
46
59
  />
47
- <HeaderTitle desc={description} tag={<Tag>{model}</Tag>} title={title} />
48
60
  </>
49
61
  }
50
62
  right={
@@ -19,6 +19,7 @@ export const Topic = () => {
19
19
  <Empty
20
20
  cover={`/images/empty_topic_${isDarkMode ? 'dark' : 'light'}.webp`}
21
21
  desc={t('topic.desc')}
22
+ style={{ marginBottom: 6 }}
22
23
  title={t('topic.title')}
23
24
  />
24
25
  )}
@@ -1,18 +1,23 @@
1
1
  import { ChatHeader } from '@lobehub/ui';
2
- import { Tag } from 'antd';
3
2
  import Router from 'next/router';
4
3
  import { memo } from 'react';
5
4
  import { useTranslation } from 'react-i18next';
6
5
 
7
6
  import pkg from '@/../package.json';
8
7
  import HeaderTitle from '@/components/HeaderTitle';
8
+ import Tag from '@/components/Tag';
9
9
 
10
10
  const Header = memo(() => {
11
11
  const { t } = useTranslation('setting');
12
12
 
13
13
  return (
14
14
  <ChatHeader
15
- left={<HeaderTitle tag={<Tag>{`v${pkg.version}`}</Tag>} title={t('header.global')} />}
15
+ left={
16
+ <HeaderTitle
17
+ tag={<Tag type={'version'}>{`v${pkg.version}`}</Tag>}
18
+ title={t('header.global')}
19
+ />
20
+ }
16
21
  onBackClick={() => Router.back()}
17
22
  showBackButton
18
23
  />
@@ -55,6 +55,12 @@ const currentAgentModel = (s: SessionStore): LanguageModel => {
55
55
  return config?.model || LanguageModel.GPT3_5;
56
56
  };
57
57
 
58
+ const currentAgentPlugins = (s: SessionStore) => {
59
+ const config = currentAgentConfig(s);
60
+
61
+ return config?.plugins || [];
62
+ };
63
+
58
64
  const hasSystemRole = (s: SessionStore) => {
59
65
  const config = currentAgentConfig(s);
60
66
 
@@ -74,6 +80,7 @@ export const agentSelectors = {
74
80
  currentAgentDescription,
75
81
  currentAgentMeta,
76
82
  currentAgentModel,
83
+ currentAgentPlugins,
77
84
  currentAgentSystemRole,
78
85
  currentAgentTitle,
79
86
  currentAutocomplete,