@lobehub/chat 0.53.0 → 0.54.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,31 @@
2
2
 
3
3
  # Changelog
4
4
 
5
+ ## [Version 0.54.0](https://github.com/lobehub/lobe-chat/compare/v0.53.0...v0.54.0)
6
+
7
+ <sup>Released on **2023-08-15**</sup>
8
+
9
+ #### ✨ Features
10
+
11
+ - **misc**: Add new features and improve user interface and functionality.
12
+
13
+ <br/>
14
+
15
+ <details>
16
+ <summary><kbd>Improvements and Fixes</kbd></summary>
17
+
18
+ #### What's improved
19
+
20
+ - **misc**: Add new features and improve user interface and functionality ([1543bd1](https://github.com/lobehub/lobe-chat/commit/1543bd1))
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
+
5
30
  ## [Version 0.53.0](https://github.com/lobehub/lobe-chat/compare/v0.52.1...v0.53.0)
6
31
 
7
32
  <sup>Released on **2023-08-15**</sup>
@@ -72,7 +72,7 @@
72
72
  },
73
73
  "inputTemplate": {
74
74
  "desc": "The latest user message will be filled into this template",
75
- "placeholder": "Enter the input template, {text} will be replaced with real-time input information",
75
+ "placeholder": "{{text}} will be replaced with real-time input information",
76
76
  "title": "User Input Preprocessing"
77
77
  },
78
78
  "title": "Chat Settings"
@@ -72,7 +72,7 @@
72
72
  },
73
73
  "inputTemplate": {
74
74
  "desc": "用户最新的一条消息会填充到此模板",
75
- "placeholder": "输入预处理模版,{text} 将替换为实时输入信息",
75
+ "placeholder": "预处理模版 {{text}} 将替换为实时输入信息",
76
76
  "title": "用户输入预处理"
77
77
  },
78
78
  "title": "聊天设置"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobehub/chat",
3
- "version": "0.53.0",
3
+ "version": "0.54.0",
4
4
  "description": "Lobe Chat is an open-source chatbot client using LangChain, Typescript and Next.js",
5
5
  "keywords": [
6
6
  "chatbot",
Binary file
@@ -0,0 +1,77 @@
1
+ import { Icon, IconProps } from '@lobehub/ui';
2
+ import { SelectProps } from 'antd';
3
+ import { type CSSProperties, ReactNode, memo, useEffect, useState } from 'react';
4
+ import { Flexbox } from 'react-layout-kit';
5
+
6
+ import { useStyles } from './styles';
7
+
8
+ export interface SelectWithImgOptionItem {
9
+ icon?: IconProps['icon'];
10
+ img: string;
11
+ label: ReactNode;
12
+ value: string;
13
+ }
14
+
15
+ export interface SelectWithImgProps {
16
+ className?: string;
17
+ defaultValue?: SelectProps['defaultValue'];
18
+ imgClassName?: string;
19
+ imgStyle?: CSSProperties;
20
+ onChange?: (value: this['value']) => void;
21
+ options?: SelectWithImgOptionItem[];
22
+ size?: number;
23
+ style?: CSSProperties;
24
+ value?: SelectProps['value'];
25
+ }
26
+
27
+ const SelectWithImg = memo<SelectWithImgProps>(
28
+ ({
29
+ className,
30
+ style,
31
+ value,
32
+ defaultValue,
33
+ onChange,
34
+ options,
35
+ size = 100,
36
+ imgStyle,
37
+ imgClassName,
38
+ }) => {
39
+ const [currentValue, setCurrentValue] = useState(value || defaultValue);
40
+ const { styles, cx } = useStyles();
41
+
42
+ useEffect(() => {
43
+ onChange?.(currentValue);
44
+ }, [currentValue]);
45
+
46
+ return (
47
+ <Flexbox className={className} gap={8} horizontal style={style}>
48
+ {options?.map((item) => {
49
+ const isActive = value ? item.value === value : item.value === currentValue;
50
+ return (
51
+ <Flexbox
52
+ align={'center'}
53
+ className={cx(styles.container, isActive && styles.active)}
54
+ gap={4}
55
+ key={item.value}
56
+ onClick={() => setCurrentValue(item.value)}
57
+ >
58
+ <img
59
+ alt={item.value}
60
+ className={imgClassName}
61
+ src={item.img}
62
+ style={imgStyle}
63
+ width={size}
64
+ />
65
+ <Flexbox align={'center'} gap={4} horizontal>
66
+ {item.icon && <Icon icon={item.icon} />}
67
+ {item.label}
68
+ </Flexbox>
69
+ </Flexbox>
70
+ );
71
+ })}
72
+ </Flexbox>
73
+ );
74
+ },
75
+ );
76
+
77
+ export default SelectWithImg;
@@ -0,0 +1,27 @@
1
+ import { createStyles } from 'antd-style';
2
+
3
+ export const useStyles = createStyles(({ css, token }) => ({
4
+ active: css`
5
+ color: ${token.colorText};
6
+
7
+ > img {
8
+ box-shadow: 0 0 0 1px ${token.colorTextDescription};
9
+ }
10
+ `,
11
+ container: css`
12
+ cursor: pointer;
13
+ color: ${token.colorTextDescription};
14
+
15
+ > img {
16
+ overflow: hidden;
17
+ border-radius: ${token.borderRadius}px;
18
+ box-shadow: 0 0 0 1px ${token.colorBorder};
19
+ transition: all 100ms ${token.motionEaseOut};
20
+
21
+ &:hover {
22
+ border-color: ${token.colorText};
23
+ box-shadow: 0 0 0 2px ${token.colorText};
24
+ }
25
+ }
26
+ `,
27
+ }));
@@ -1,11 +1,12 @@
1
1
  import { Form, ItemGroup, SliderWithInput } from '@lobehub/ui';
2
- import { ConfigProvider, Input, Segmented, Select, Switch } from 'antd';
3
- import { useTheme } from 'antd-style';
2
+ import { ConfigProvider, Input, Select, Switch } from 'antd';
3
+ import { useTheme, useThemeMode } from 'antd-style';
4
4
  import { debounce } from 'lodash-es';
5
- import { BrainCog, MessagesSquare } from 'lucide-react';
5
+ import { BrainCog, LayoutList, MessageSquare, MessagesSquare } from 'lucide-react';
6
6
  import { memo, useMemo } from 'react';
7
7
  import { useTranslation } from 'react-i18next';
8
8
 
9
+ import SelectWithImg from '@/components/SelectWithImg';
9
10
  import { FORM_STYLE } from '@/const/layoutTokens';
10
11
  import { AgentAction } from '@/store/session/slices/agentConfig';
11
12
  import { LanguageModel } from '@/types/llm';
@@ -26,17 +27,36 @@ export interface AgentConfigProps {
26
27
  const AgentConfig = memo<AgentConfigProps>(({ config, updateConfig }) => {
27
28
  const { t } = useTranslation('setting');
28
29
  const theme = useTheme();
30
+ const { isDarkMode } = useThemeMode();
29
31
 
30
32
  const chat: SettingItemGroup = useMemo(
31
33
  () => ({
32
34
  children: [
33
35
  {
34
36
  children: (
35
- <Segmented
37
+ <SelectWithImg
38
+ imgStyle={{
39
+ background: `linear-gradient(
40
+ to bottom,
41
+ ${isDarkMode ? theme.colorBgElevated : theme.colorBgLayout},
42
+ ${theme.colorBgContainer}
43
+ )`,
44
+ }}
36
45
  options={[
37
- { label: t('settingChat.chatStyleType.type.chat'), value: 'chat' },
38
- { label: t('settingChat.chatStyleType.type.docs'), value: 'docs' },
46
+ {
47
+ icon: MessagesSquare,
48
+ img: `/images/chatmode_chat_${isDarkMode ? 'dark' : 'light'}.webp`,
49
+ label: t('settingChat.chatStyleType.type.chat'),
50
+ value: 'chat',
51
+ },
52
+ {
53
+ icon: LayoutList,
54
+ img: `/images/chatmode_docs_${isDarkMode ? 'dark' : 'light'}.webp`,
55
+ label: t('settingChat.chatStyleType.type.docs'),
56
+ value: 'docs',
57
+ },
39
58
  ]}
59
+ size={144}
40
60
  />
41
61
  ),
42
62
  label: t('settingChat.chatStyleType.title'),
@@ -80,7 +100,7 @@ const AgentConfig = memo<AgentConfigProps>(({ config, updateConfig }) => {
80
100
  name: 'compressThreshold',
81
101
  },
82
102
  ],
83
- icon: MessagesSquare,
103
+ icon: MessageSquare,
84
104
  title: t('settingChat.title'),
85
105
  }),
86
106
  [config],
@@ -72,7 +72,7 @@ export default {
72
72
  },
73
73
  inputTemplate: {
74
74
  desc: '用户最新的一条消息会填充到此模板',
75
- placeholder: '输入预处理模版,{text} 将替换为实时输入信息',
75
+ placeholder: '预处理模版 {{text}} 将替换为实时输入信息',
76
76
  title: '用户输入预处理',
77
77
  },
78
78
  title: '聊天设置',
@@ -16,6 +16,9 @@ const useStyles = createStyles(({ css, prefixCls, token }) => ({
16
16
  background: ${token.colorFillTertiary};
17
17
  }
18
18
  }
19
+ .${prefixCls}-collapse-content {
20
+ border-radius: 0 !important;
21
+ }
19
22
  .${prefixCls}-collapse-content-box {
20
23
  padding: 0 !important;
21
24
  }
@@ -35,7 +35,7 @@ export const FormAction = memo<{
35
35
 
36
36
  return (
37
37
  <Center gap={16} style={{ maxWidth: 300 }}>
38
- <Avatar avatar={avatar} background={theme.colorText} gap={12} size={80} />
38
+ <Avatar avatar={avatar} background={theme.colorFillContent} gap={12} size={80} />
39
39
  <Flexbox style={{ fontSize: 20 }}>{title}</Flexbox>
40
40
  <Flexbox className={styles.desc}>{description}</Flexbox>
41
41
  {children}
@@ -1,16 +1,14 @@
1
- import { ChatHeader, ChatHeaderTitle, Tag } from '@lobehub/ui';
1
+ import { ChatHeader, ChatHeaderTitle } from '@lobehub/ui';
2
2
  import Router from 'next/router';
3
3
  import { memo } from 'react';
4
4
  import { useTranslation } from 'react-i18next';
5
5
 
6
- import pkg from '@/../package.json';
7
-
8
6
  const Header = memo(() => {
9
7
  const { t } = useTranslation('setting');
10
8
 
11
9
  return (
12
10
  <ChatHeader
13
- left={<ChatHeaderTitle tag={<Tag>{`v${pkg.version}`}</Tag>} title={t('header.global')} />}
11
+ left={<ChatHeaderTitle title={t('header.global')} />}
14
12
  onBackClick={() => Router.back()}
15
13
  showBackButton
16
14
  />
@@ -1,12 +1,13 @@
1
- import { Form, type ItemGroup, SliderWithInput, ThemeSwitch } from '@lobehub/ui';
1
+ import { Form, type ItemGroup, SliderWithInput } from '@lobehub/ui';
2
2
  import { Form as AntForm, App, Button, Input, Select } from 'antd';
3
3
  import isEqual from 'fast-deep-equal';
4
4
  import { changeLanguage } from 'i18next';
5
5
  import { debounce } from 'lodash-es';
6
- import { AppWindow, Palette, Webhook } from 'lucide-react';
6
+ import { AppWindow, Monitor, Moon, Palette, Sun, Webhook } from 'lucide-react';
7
7
  import { memo, useCallback, useMemo } from 'react';
8
8
  import { useTranslation } from 'react-i18next';
9
9
 
10
+ import SelectWithImg from '@/components/SelectWithImg';
10
11
  import { FORM_STYLE } from '@/const/layoutTokens';
11
12
  import { DEFAULT_SETTINGS } from '@/const/settings';
12
13
  import AvatarWithUpload from '@/features/AvatarWithUpload';
@@ -76,18 +77,33 @@ const Common = memo(() => {
76
77
  },
77
78
  {
78
79
  children: (
79
- <ThemeSwitch
80
- labels={{
81
- auto: t('settingTheme.themeMode.auto'),
82
- dark: t('settingTheme.themeMode.dark'),
83
- light: t('settingTheme.themeMode.light'),
84
- }}
85
- onThemeSwitch={setThemeMode}
86
- themeMode={settings.themeMode}
87
- type={'select'}
80
+ <SelectWithImg
81
+ defaultValue={settings.themeMode}
82
+ onChange={setThemeMode}
83
+ options={[
84
+ {
85
+ icon: Sun,
86
+ img: '/images/theme_light.webp',
87
+ label: t('settingTheme.themeMode.light'),
88
+ value: 'light',
89
+ },
90
+ {
91
+ icon: Moon,
92
+ img: '/images/theme_dark.webp',
93
+ label: t('settingTheme.themeMode.dark'),
94
+ value: 'dark',
95
+ },
96
+ {
97
+ icon: Monitor,
98
+ img: '/images/theme_auto.webp',
99
+ label: t('settingTheme.themeMode.auto'),
100
+ value: 'auto',
101
+ },
102
+ ]}
88
103
  />
89
104
  ),
90
105
  label: t('settingTheme.themeMode.title'),
106
+ minWidth: undefined,
91
107
  },
92
108
  {
93
109
  children: (
@@ -1,46 +1,97 @@
1
- import { Segmented } from 'antd';
1
+ import { GridBackground, Icon, Logo, TabsNav } from '@lobehub/ui';
2
2
  import { createStyles } from 'antd-style';
3
+ import { PackageCheck } from 'lucide-react';
4
+ import { rgba } from 'polished';
3
5
  import { memo, useState } from 'react';
4
6
  import { useTranslation } from 'react-i18next';
5
- import { Center } from 'react-layout-kit';
7
+ import { Center, Flexbox } from 'react-layout-kit';
8
+
9
+ import pkg from '@/../package.json';
6
10
 
7
11
  import Agent from './Agent';
8
12
  import Common from './Common';
9
13
 
10
- const useStyles = createStyles(({ css, token }) => ({
14
+ const useStyles = createStyles(({ css, token, isDarkMode }) => ({
15
+ background: css`
16
+ position: absolute;
17
+ bottom: -10%;
18
+ left: 0;
19
+ width: 100%;
20
+ `,
21
+ logo: css`
22
+ position: absolute;
23
+ top: 50%;
24
+ left: 32px;
25
+ transform: translateY(-50%);
26
+ `,
11
27
  tabs: css`
28
+ position: relative;
29
+
30
+ overflow: hidden;
31
+
32
+ width: 100%;
33
+ max-width: 1024px;
34
+ height: 160px;
12
35
  padding: 4px;
36
+
37
+ background: radial-gradient(
38
+ 120% 120% at 20% 100%,
39
+ ${isDarkMode ? rgba(token.colorBgContainer, 0.5) : token.colorBgContainer} 32%,
40
+ ${isDarkMode ? token.colorPrimaryBgHover : rgba(token.colorPrimaryBgHover, 0.3)} 50%,
41
+ ${isDarkMode ? token.colorPrimaryHover : rgba(token.colorPrimaryHover, 0.3)} 100%
42
+ );
13
43
  border: 1px solid ${token.colorBorderSecondary};
14
44
  border-radius: ${token.borderRadiusLG}px;
45
+ `,
46
+ tag: css`
47
+ position: absolute;
48
+ top: 6px;
49
+ right: 12px;
50
+
51
+ font-family: ${token.fontFamilyCode};
52
+ font-size: 12px;
53
+ color: ${isDarkMode ? token.colorPrimaryBg : token.colorPrimaryActive};
15
54
 
16
- &:hover {
17
- border-color: ${token.colorBorder};
18
- }
55
+ opacity: 0.5;
19
56
  `,
20
57
  }));
21
58
 
22
59
  enum Tabs {
23
- common,
24
- agent,
60
+ agent = 'agent',
61
+ common = 'common',
25
62
  }
26
63
 
27
64
  const Settings = memo(() => {
28
65
  const [tab, setTab] = useState<Tabs>(Tabs.common);
29
- const { styles } = useStyles();
66
+ const { styles, theme } = useStyles();
30
67
  const { t } = useTranslation('setting');
31
68
 
32
69
  return (
33
70
  <Center gap={16} width={'100%'}>
34
- <div className={styles.tabs}>
35
- <Segmented
36
- onChange={(e) => setTab(e as Tabs)}
37
- options={[
38
- { label: t('tab.common'), value: Tabs.common },
39
- { label: t('tab.agent'), value: Tabs.agent },
71
+ <Flexbox align={'flex-end'} className={styles.tabs} horizontal justify={'center'}>
72
+ <GridBackground
73
+ animation
74
+ className={styles.background}
75
+ colorBack={theme.colorFillSecondary}
76
+ colorFront={theme.colorPrimary}
77
+ random
78
+ />
79
+ <TabsNav
80
+ activeKey={tab}
81
+ items={[
82
+ { key: Tabs.common, label: t('tab.common') },
83
+ { key: Tabs.agent, label: t('tab.agent') },
40
84
  ]}
41
- value={tab}
85
+ onChange={(e) => setTab(e as Tabs)}
42
86
  />
43
- </div>
87
+ <div className={styles.logo}>
88
+ <Logo extra={'Chat'} type={'text'} />
89
+ </div>
90
+ <Flexbox align={'center'} className={styles.tag} gap={4} horizontal>
91
+ <Icon icon={PackageCheck} />
92
+ <div>{`${pkg.version}`}</div>
93
+ </Flexbox>
94
+ </Flexbox>
44
95
  {tab === Tabs.common && <Common />}
45
96
  {tab === Tabs.agent && <Agent />}
46
97
  </Center>