mirror of
https://github.com/lobehub/lobehub
synced 2026-04-21 17:47:27 +00:00
✨ 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:
parent
b58ce6d4a7
commit
4b61bf4f2f
9 changed files with 259 additions and 88 deletions
|
|
@ -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"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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={
|
||||
<>
|
||||
|
|
|
|||
|
|
@ -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: '确定',
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
/>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
/>
|
||||
|
|
|
|||
Loading…
Reference in a new issue