feat: Add new features, update URLs, customize appearance, and implement components

Add new features, update URLs, customize appearance, and implement various components and hooks. Changes include modifying input components, adding menus and tags, and customizing styles. The code snippet includes changes made to multiple files, including package.json and index.tsx files.
This commit is contained in:
canisminor1990 2023-07-26 22:23:55 +08:00
parent b58ce6d4a7
commit 4b61bf4f2f
9 changed files with 259 additions and 88 deletions

View file

@ -11,7 +11,7 @@
],
"homepage": "https://github.com/lobehub/lobe-chat",
"bugs": {
"url": "https://github.com/lobehub/lobe-chat/issues/new"
"url": "https://github.com/lobehub/lobe-chat/issues/new/choose"
},
"repository": {
"type": "git",
@ -30,6 +30,7 @@
"lint:style": "stylelint \"{src,tests}/**/*.{js,jsx,ts,tsx}\" --fix",
"prepare": "husky install",
"prettier": "prettier -c --write \"**/**\"",
"pull": "git pull",
"release": "semantic-release",
"start": "next start",
"stylelint": "stylelint \"src/**/*.{js,jsx,ts,tsx}\" --fix",
@ -141,5 +142,6 @@
"publishConfig": {
"access": "public",
"registry": "https://registry.npmjs.org"
}
},
"changelog": "https://github.com/lobehub/lobe-chat"
}

View file

@ -36,7 +36,12 @@ const HeaderTitle = memo<HeaderTitleProps>(({ title, desc, tag }) => {
</Flexbox>
</Flexbox>
);
return <div className={styles.title}>{title}</div>;
return (
<Flexbox align={'center'} className={styles.title} gap={8} horizontal>
{title}
{tag}
</Flexbox>
);
});
export default HeaderTitle;

View file

@ -1,36 +1,61 @@
import { InputNumber, Slider } from 'antd';
import { InputNumber, type InputNumberProps, Slider } from 'antd';
import { SliderSingleProps } from 'antd/es/slider';
import { isNull } from 'lodash-es';
import { memo, useCallback } from 'react';
import { Flexbox } from 'react-layout-kit';
const SliderWithInput = memo<SliderSingleProps>(
({ step, value, onChange, max, min, defaultValue, ...props }) => {
export interface SliderWithInputProps extends SliderSingleProps {
controls?: InputNumberProps['controls'];
size?: InputNumberProps['size'];
}
const SliderWithInput = memo<SliderWithInputProps>(
({
step,
value,
onChange,
max,
min,
defaultValue,
size,
controls,
style,
className,
...props
}) => {
const handleOnchange = useCallback((value: number | null) => {
if (Number.isNaN(value) || isNull(value)) return;
onChange?.(value);
}, []);
return (
<Flexbox direction={'horizontal'} gap={8}>
<Flexbox
align={'center'}
className={className}
direction={'horizontal'}
gap={8}
style={style}
>
<Slider
defaultValue={defaultValue}
max={max}
min={min}
onChange={handleOnchange}
step={step}
style={{ flex: 1 }}
tooltip={{ getPopupContainer: (triggerNode) => triggerNode }}
style={size === 'small' ? { flex: 1, margin: 0 } : { flex: 1 }}
tooltip={{ open: false }}
value={typeof value === 'number' ? value : 0}
{...props}
/>
<InputNumber
controls={size !== 'small' || controls}
defaultValue={defaultValue}
max={max}
min={min}
onChange={handleOnchange}
size={size}
step={Number.isNaN(step) || isNull(step) ? undefined : step}
style={{ flex: 1, maxWidth: 64 }}
style={{ flex: 1, maxWidth: size === 'small' ? 40 : 64 }}
value={typeof value === 'number' ? value : 0}
/>
</Flexbox>

View file

@ -1,18 +1,117 @@
import { ActionIcon, SideNav } from '@lobehub/ui';
import { MessageSquare, Settings2, Sticker } from 'lucide-react';
import { ActionIcon, Icon, SideNav } from '@lobehub/ui';
import { Dropdown, type MenuProps } from 'antd';
import {
Feather,
FileClock,
FolderInput,
FolderOutput,
Github,
Heart,
MessageSquare,
Settings,
Settings2,
Sticker,
} from 'lucide-react';
import Router from 'next/router';
import { memo } from 'react';
import { memo, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { shallow } from 'zustand/shallow';
import { bugs, homepage } from '@/../package.json';
import AvatarWithUpload from '@/features/AvatarWithUpload';
import { useSettings } from '@/store/settings';
export default memo(() => {
const [tab, setTab] = useSettings((s) => [s.sidebarKey, s.switchSideBar], shallow);
const { t } = useTranslation('common');
const items: MenuProps['items'] = useMemo(
() => [
{
icon: <Icon icon={FolderInput} />,
key: 'import',
label: <div>{t('import')}</div>,
},
{
children: [
{
key: 'allAgent',
label: <div>{t('exportType.allAgent')}</div>,
},
{
key: 'allAgentWithMessage',
label: <div>{t('exportType.allAgentWithMessage')}</div>,
},
{
key: 'globalSetting',
label: <div>{t('exportType.globalSetting')}</div>,
},
{
type: 'divider',
},
{
key: 'all',
label: <div>{t('exportType.all')}</div>,
},
],
icon: <Icon icon={FolderOutput} />,
key: 'export',
label: t('export'),
},
{
type: 'divider',
},
{
icon: <Icon icon={Feather} />,
key: 'feedback',
label: (
<a href={bugs.url} rel="noreferrer" target={'_blank'}>
{t('feedback')}
</a>
),
},
{
icon: <Icon icon={FileClock} />,
key: 'changelog',
label: (
<a href={`${homepage}/blob/master/CHANGELOG.md`} rel="noreferrer" target={'_blank'}>
{t('changelog')}
</a>
),
},
{
icon: <Icon icon={Heart} />,
key: 'about',
label: (
<a href={homepage} rel="noreferrer" target={'_blank'}>
{t('about')}
</a>
),
},
{
type: 'divider',
},
{
icon: <Icon icon={Settings} />,
key: 'setting',
label: <div onClick={() => Router.push('/setting')}>{t('setting')}</div>,
},
],
[],
);
return (
<SideNav
avatar={<AvatarWithUpload />}
bottomActions={<ActionIcon icon={Settings2} onClick={() => Router.push('/setting')} />}
bottomActions={
<>
<a href={homepage} rel="noreferrer" target={'_blank'}>
<ActionIcon icon={Github} />
</a>
<Dropdown arrow={false} menu={{ items }} trigger={['click']}>
<ActionIcon icon={Settings2} />
</Dropdown>
</>
}
style={{ height: '100vh' }}
topActions={
<>

View file

@ -1,4 +1,5 @@
export default {
about: '关于',
advanceSettings: '高级设置',
agentMaxToken: '会话最大长度',
agentModel: '模型',
@ -7,6 +8,7 @@ export default {
autoGenerate: '自动补全',
autoGenerateTooltip: '基于提示词自动补全助手描述',
cancel: '取消',
changelog: '更新日志',
clearCurrentMessages: '清空当前会话消息',
close: '关闭',
confirmClearCurrentMessages: '即将清空当前会话消息,清空后将无法找回,请确认你的操作',
@ -14,7 +16,17 @@ export default {
defaultAgent: '默认助手',
defaultSession: '默认对话',
edit: '编辑',
export: '导出',
export: '导出配置',
exportType: {
agent: '导出助手设定',
agentWithMessage: '导出助手和消息',
all: '导出全局设置和所有助手数据',
allAgent: '导出所有助手设定',
allAgentWithMessage: '导出所有助手和消息',
globalSetting: '导出全局设置',
},
feedback: '反馈与建议',
import: '导入配置',
newAgent: '新建助手',
noDescription: '暂无描述',
ok: '确定',

View file

@ -1,7 +1,8 @@
import { ActionIcon, Avatar, List } from '@lobehub/ui';
import { useHover } from 'ahooks';
import { Popconfirm, Tag } from 'antd';
import { X } from 'lucide-react';
import { FC, memo } from 'react';
import { FC, memo, useRef } from 'react';
import { useTranslation } from 'react-i18next';
import { Flexbox } from 'react-layout-kit';
import { shallow } from 'zustand/shallow';
@ -20,6 +21,8 @@ interface SessionItemProps {
}
const SessionItem: FC<SessionItemProps> = memo(({ id, active = true, loading }) => {
const ref = useRef(null);
const isHovering = useHover(ref);
const { t } = useTranslation('common');
const { styles, theme, cx } = useStyles();
const [defaultModel] = useSettings((s) => [s.settings.model], shallow);
@ -55,71 +58,74 @@ const SessionItem: FC<SessionItemProps> = memo(({ id, active = true, loading })
const showChatLength = chatLength > 0;
return (
<Flexbox className={styles.container} style={{ position: 'relative' }}>
<Item
active={active}
avatar={
<Avatar
avatar={avatar}
background={avatarBackground}
shape="circle"
size={46}
title={title}
/>
}
classNames={{ time: cx('session-time', styles.time) }}
date={updateAt}
description={
<Flexbox gap={4}>
<Flexbox>{description || systemRole}</Flexbox>
<div ref={ref}>
<Flexbox className={styles.container} style={{ position: 'relative' }}>
<Item
active={active}
avatar={
<Avatar
animation={isHovering}
avatar={avatar}
background={avatarBackground}
shape="circle"
size={46}
title={title}
/>
}
classNames={{ time: cx('session-time', styles.time) }}
date={updateAt}
description={
<Flexbox gap={4}>
<Flexbox>{description || systemRole}</Flexbox>
{!(showModel || showChatLength) ? undefined : (
<Flexbox horizontal>
{showModel && (
<Tag bordered={false} style={{ color: theme.colorTextSecondary }}>
{model}
</Tag>
)}
{/*{showChatLength && (*/}
{/* <Tag*/}
{/* bordered={false}*/}
{/* style={{ color: theme.colorTextSecondary, display: 'flex', gap: 4 }}*/}
{/* >*/}
{/* <Icon icon={LucideMessageCircle} />*/}
{/* {chatLength}*/}
{/* </Tag>*/}
{/*)}*/}
</Flexbox>
)}
</Flexbox>
}
loading={loading}
style={{ color: theme.colorText }}
title={title}
/>
<Popconfirm
arrow={false}
cancelText={t('cancel')}
okButtonProps={{ danger: true }}
okText={t('ok')}
onConfirm={(e) => {
e?.stopPropagation();
removeSession(id);
}}
overlayStyle={{ width: 280 }}
title={t('confirmRemoveSessionItemAlert')}
>
<ActionIcon
className="session-remove"
icon={X}
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
}}
size={'small'}
{!(showModel || showChatLength) ? undefined : (
<Flexbox horizontal>
{showModel && (
<Tag bordered={false} style={{ color: theme.colorTextSecondary }}>
{model}
</Tag>
)}
{/*{showChatLength && (*/}
{/* <Tag*/}
{/* bordered={false}*/}
{/* style={{ color: theme.colorTextSecondary, display: 'flex', gap: 4 }}*/}
{/* >*/}
{/* <Icon icon={LucideMessageCircle} />*/}
{/* {chatLength}*/}
{/* </Tag>*/}
{/*)}*/}
</Flexbox>
)}
</Flexbox>
}
loading={loading}
style={{ color: theme.colorText }}
title={title}
/>
</Popconfirm>
</Flexbox>
<Popconfirm
arrow={false}
cancelText={t('cancel')}
okButtonProps={{ danger: true }}
okText={t('ok')}
onConfirm={(e) => {
e?.stopPropagation();
removeSession(id);
}}
overlayStyle={{ width: 280 }}
title={t('confirmRemoveSessionItemAlert')}
>
<ActionIcon
className="session-remove"
icon={X}
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
}}
size={'small'}
/>
</Popconfirm>
</Flexbox>
</div>
);
}, shallow);

View file

@ -39,15 +39,18 @@ const InputActions = memo(() => {
<ActionIcon icon={BrainCog} placement={'bottom'} title={t('settingModel.model.title')} />
</Dropdown>
<Popover
arrow={false}
content={
<SliderWithInput
controls={false}
max={1}
min={0}
onChange={(v) => {
updateAgentConfig({ params: { temperature: v } });
}}
size={'small'}
step={0.1}
style={{ width: 120 }}
style={{ width: 160 }}
value={temperature}
/>
}

View file

@ -1,7 +1,8 @@
import { ActionIcon, ChatHeader } from '@lobehub/ui';
import { Download, Share2 } from 'lucide-react';
import { Dropdown, MenuProps } from 'antd';
import { FolderOutput, Share2 } from 'lucide-react';
import Router from 'next/router';
import { memo } from 'react';
import { memo, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import HeaderTitle from '@/components/HeaderTitle';
@ -9,6 +10,20 @@ import HeaderTitle from '@/components/HeaderTitle';
const Header = memo(() => {
const { t } = useTranslation('setting');
const items: MenuProps['items'] = useMemo(
() => [
{
key: 'agent',
label: <div>{t('exportType.agent', { ns: 'common' })}</div>,
},
{
key: 'agentWithMessage',
label: <div>{t('exportType.agentWithMessage', { ns: 'common' })}</div>,
},
],
[],
);
return (
<ChatHeader
left={<HeaderTitle title={t('header.session')} />}
@ -16,11 +31,13 @@ const Header = memo(() => {
right={
<>
<ActionIcon icon={Share2} size={{ fontSize: 24 }} title={t('share', { ns: 'common' })} />
<ActionIcon
icon={Download}
size={{ fontSize: 24 }}
title={t('export', { ns: 'common' })}
/>
<Dropdown arrow={false} menu={{ items }} trigger={['click']}>
<ActionIcon
icon={FolderOutput}
size={{ fontSize: 24 }}
title={t('export', { ns: 'common' })}
/>
</Dropdown>
</>
}
showBackButton

View file

@ -1,8 +1,10 @@
import { ChatHeader } from '@lobehub/ui';
import { Tag } from 'antd';
import Router from 'next/router';
import { memo } from 'react';
import { useTranslation } from 'react-i18next';
import { version } from '@/../package.json';
import HeaderTitle from '@/components/HeaderTitle';
const Header = memo(() => {
@ -10,7 +12,7 @@ const Header = memo(() => {
return (
<ChatHeader
left={<HeaderTitle title={t('header.global')} />}
left={<HeaderTitle tag={<Tag>{`v${version}`}</Tag>} title={t('header.global')} />}
onBackClick={() => Router.back()}
showBackButton
/>