mirror of
https://github.com/lobehub/lobehub.git
synced 2026-03-27 13:29:15 +07:00
✨ feat: Add and modify settings page, update Header styles, and improve useTranslation hook
This commit introduces changes to the setting.json, index.page.tsx, and Header.tsx files. The settings page is enhanced by adding, removing, and modifying settings and their descriptions. The Header component's styles are updated, and improvements are made to the useTranslation hook. Additionally, a new file called SettingForm.tsx is added, which defines a component for retrieving current settings and translations. The index.page.tsx file is updated to display translated text and import/render the SettingForm component. The settings.ts and action.ts files are deleted, and a new index.ts file is added to define the settings store using Zustand. The store includes initial state, selectors, and actions related to the settings. Changes: - Modifications to setting.json, index.page.tsx, and Header.tsx files - Addition, removal, and modification of settings and descriptions on the settings page - Updates to the styles of the Header component - Improvements to the useTranslation hook - Addition of SettingForm.tsx file for retrieving settings and translations - Update of index.page.tsx to display translated text and import/render SettingForm component - Deletion of settings.ts and action.ts files - Addition of index.ts file to define settings store using Zustand, including initial state, selectors, and actions related to the settings.
This commit is contained in:
@@ -1,87 +1,98 @@
|
||||
{
|
||||
"accessCode": {
|
||||
"title": "访问密码",
|
||||
"subTitle": "管理员已开启加密访问",
|
||||
"placeholder": "请输入访问密码"
|
||||
},
|
||||
"avatar": "头像",
|
||||
"compressThreshold": {
|
||||
"title": "历史消息长度压缩阈值",
|
||||
"subTitle": "当未压缩的历史消息超过该值时,将进行压缩"
|
||||
},
|
||||
"customModel": {
|
||||
"title": "自定义模型名",
|
||||
"subTitle": "增加自定义模型可选项,使用英文逗号隔开"
|
||||
},
|
||||
"danger": {
|
||||
"reset": {
|
||||
"title": "重置所有设置",
|
||||
"subTitle": "重置所有设置项回默认值",
|
||||
"desc": "重置所有设置项回默认值",
|
||||
"action": "立即重置",
|
||||
"confirm": "确认重置所有设置?",
|
||||
"currentVersion": "当前版本"
|
||||
},
|
||||
"clear": {
|
||||
"title": "清除所有数据",
|
||||
"subTitle": "清除所有聊天、设置数据",
|
||||
"desc": "清除所有聊天、设置数据",
|
||||
"action": "立即清除",
|
||||
"confirm": "确认清除所有聊天、设置数据?"
|
||||
}
|
||||
},
|
||||
"endpoint": {
|
||||
"title": "接口地址",
|
||||
"subTitle": "除默认地址外,必须包含 http(s)://"
|
||||
|
||||
"header": "设置",
|
||||
"settingChat": {
|
||||
"title": "聊天设置",
|
||||
"inputTemplate": {
|
||||
"title": "用户输入预处理",
|
||||
"desc": "用户最新的一条消息会填充到此模板"
|
||||
},
|
||||
"compressThreshold": {
|
||||
"title": "历史消息长度压缩阈值",
|
||||
"desc": "当未压缩的历史消息超过该值时,将进行压缩"
|
||||
},
|
||||
"historyCount": {
|
||||
"title": "附带历史消息数",
|
||||
"desc": "每次请求携带的历史消息数"
|
||||
},
|
||||
"maxTokens": {
|
||||
"title": "单次回复限制 (max_tokens)",
|
||||
"desc": "单次交互所用的最大 Token 数"
|
||||
},
|
||||
"sendKey": {
|
||||
"title": "发送键"
|
||||
}
|
||||
},
|
||||
"fontSize": {
|
||||
"title": "字体大小",
|
||||
"subTitle": "聊天内容的字体大小"
|
||||
"settingModel": {
|
||||
"title": "模型设置",
|
||||
"model": {
|
||||
"title": "模型"
|
||||
},
|
||||
"temperature": {
|
||||
"title": "随机性 (temperature)",
|
||||
"desc": "值越大,回复越随机"
|
||||
},
|
||||
"topP": {
|
||||
"title": "核采样 (top_p)",
|
||||
"desc": "与随机性类似,但不要和随机性一起更改"
|
||||
},
|
||||
"presencePenalty": {
|
||||
"title": "话题新鲜度 (presence_penalty)",
|
||||
"desc": "值越大,越有可能扩展到新话题"
|
||||
},
|
||||
"frequencyPenalty": {
|
||||
"title": "频率惩罚度 (frequency_penalty)",
|
||||
"desc": "值越大,越有可能降低重复字词"
|
||||
}
|
||||
},
|
||||
"frequencyPenalty": {
|
||||
"title": "频率惩罚度 (frequency_penalty)",
|
||||
"subTitle": "值越大,越有可能降低重复字词"
|
||||
"settingOpenAI": {
|
||||
"title": "OpenAI 设置",
|
||||
"token": {
|
||||
"title": "API Key",
|
||||
"desc": "使用自己的 Key 可绕过密码访问限制",
|
||||
"placeholder": "OpenAI API Key"
|
||||
},
|
||||
"endpoint": {
|
||||
"title": "接口地址",
|
||||
"desc": "除默认地址外,必须包含 http(s)://"
|
||||
}
|
||||
},
|
||||
"historyCount": {
|
||||
"title": "附带历史消息数",
|
||||
"subTitle": "每次请求携带的历史消息数"
|
||||
"settingSystem": {
|
||||
"title": "系统设置",
|
||||
"accessCode": {
|
||||
"title": "访问密码",
|
||||
"desc": "管理员已开启加密访问",
|
||||
"placeholder": "请输入访问密码"
|
||||
}
|
||||
},
|
||||
"inputTemplate": {
|
||||
"title": "用户输入预处理",
|
||||
"subTitle": "用户最新的一条消息会填充到此模板"
|
||||
},
|
||||
"lang": {
|
||||
"name": "语言设置",
|
||||
"all": "所有语言"
|
||||
},
|
||||
"maxTokens": {
|
||||
"title": "单次回复限制 (max_tokens)",
|
||||
"subTitle": "单次交互所用的最大 Token 数"
|
||||
},
|
||||
"model": "模型 (model)",
|
||||
"presencePenalty": {
|
||||
"title": "话题新鲜度 (presence_penalty)",
|
||||
"subTitle": "值越大,越有可能扩展到新话题"
|
||||
},
|
||||
"sendKey": "发送键",
|
||||
"setting": "设置",
|
||||
"temperature": {
|
||||
"title": "随机性 (temperature)",
|
||||
"subTitle": "值越大,回复越随机"
|
||||
},
|
||||
"theme": "主题",
|
||||
"token": {
|
||||
"title": "API Key",
|
||||
"subTitle": "使用自己的 Key 可绕过密码访问限制",
|
||||
"placeholder": "OpenAI API Key"
|
||||
},
|
||||
"topP": {
|
||||
"title": "核采样 (top_p)",
|
||||
"subTitle": "与随机性类似,但不要和随机性一起更改"
|
||||
},
|
||||
"update": {
|
||||
"isLatest": "已是最新版本",
|
||||
"checkUpdate": "检查更新",
|
||||
"isChecking": "正在检查更新...",
|
||||
"foundUpdate": "发现新版本",
|
||||
"goToUpdate": "前往更新"
|
||||
"settingTheme": {
|
||||
"title": "主题设置",
|
||||
"avatar": {
|
||||
"title": "头像",
|
||||
"desc": "支持 URL / Base64 / Emoji 表情符号"
|
||||
},
|
||||
"fontSize": {
|
||||
"title": "字体大小",
|
||||
"desc": "聊天内容的字体大小"
|
||||
},
|
||||
"lang": {
|
||||
"name": "语言设置",
|
||||
"all": "所有语言"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1 +1 @@
|
||||
export { default, getServerSideProps } from './[id]/index.page';
|
||||
export { default, getStaticProps } from './[id]/index.page';
|
||||
|
||||
@@ -1 +1,6 @@
|
||||
export { default, getServerSideProps } from './chat/index.page';
|
||||
import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
|
||||
|
||||
export { default } from './chat/index.page';
|
||||
export const getStaticProps = async (context: any) => ({
|
||||
props: await serverSideTranslations(context.locale, ['common', 'setting']),
|
||||
});
|
||||
|
||||
@@ -12,12 +12,12 @@ const useStyles = createStyles(({ css, token }) => ({
|
||||
`,
|
||||
}));
|
||||
const Header = memo(() => {
|
||||
const { t } = useTranslation('common');
|
||||
const { t } = useTranslation('setting');
|
||||
|
||||
const { styles } = useStyles();
|
||||
return (
|
||||
<ChatHeader
|
||||
left={<div className={styles.title}>{t('setting')}</div>}
|
||||
left={<div className={styles.title}>{t('header')}</div>}
|
||||
onBackClick={() => Router.back()}
|
||||
showBackButton
|
||||
/>
|
||||
|
||||
35
src/pages/setting/SettingForm.tsx
Normal file
35
src/pages/setting/SettingForm.tsx
Normal file
@@ -0,0 +1,35 @@
|
||||
import { Form, Input, type ItemGroup } from '@lobehub/ui';
|
||||
import isEqual from 'fast-deep-equal';
|
||||
import { Palette } from 'lucide-react';
|
||||
import { memo, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { settingsSelectors, useSettings } from '@/store/settings';
|
||||
|
||||
const SettingForm = memo(() => {
|
||||
const settings = useSettings(settingsSelectors.currentSettings, isEqual);
|
||||
|
||||
const { t } = useTranslation('setting');
|
||||
|
||||
const theme: ItemGroup = useMemo(
|
||||
() => ({
|
||||
children: [
|
||||
{
|
||||
children: <Input />,
|
||||
desc: t('settingTheme.avatar.desc'),
|
||||
label: t('settingTheme.avatar.title'),
|
||||
name: 'avatar',
|
||||
},
|
||||
],
|
||||
icon: Palette,
|
||||
title: t('settingTheme.title'),
|
||||
}),
|
||||
[settings],
|
||||
);
|
||||
|
||||
return (
|
||||
<Form initialValues={settings} items={[theme]} style={{ maxWidth: 1024, width: '100%' }} />
|
||||
);
|
||||
});
|
||||
|
||||
export default SettingForm;
|
||||
@@ -8,19 +8,23 @@ import { Sessions } from '@/pages/chat/SessionList';
|
||||
|
||||
import Sidebar from '../Sidebar';
|
||||
import Header from './Header';
|
||||
import SettingForm from './SettingForm';
|
||||
|
||||
const SettingLayout = memo(() => {
|
||||
const { t } = useTranslation();
|
||||
const { t } = useTranslation('setting');
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>{t('setting')} - LobeChat</title>
|
||||
<title>{t('header')} - LobeChat</title>
|
||||
</Head>
|
||||
<Flexbox horizontal width={'100%'}>
|
||||
<Sidebar />
|
||||
<Sessions />
|
||||
<Flexbox flex={1}>
|
||||
<Header />
|
||||
<Flexbox align={'center'} padding={24}>
|
||||
<SettingForm />
|
||||
</Flexbox>
|
||||
</Flexbox>
|
||||
</Flexbox>
|
||||
</>
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
import { ThemeMode } from 'antd-style';
|
||||
import { create } from 'zustand';
|
||||
import { persist } from 'zustand/middleware';
|
||||
|
||||
import { ConfigSettings } from '@/types/exportConfig';
|
||||
|
||||
export type SidebarTabKey = 'chat' | 'market';
|
||||
|
||||
interface SettingsStore {
|
||||
avatar?: string;
|
||||
importSettings: (settings: ConfigSettings) => void;
|
||||
inputHeight: number;
|
||||
sessionExpandable?: boolean;
|
||||
sessionsWidth: number;
|
||||
sidebarKey: SidebarTabKey;
|
||||
switchSideBar: (key: SidebarTabKey) => void;
|
||||
themeMode?: ThemeMode;
|
||||
}
|
||||
|
||||
export const useSettings = create<SettingsStore>()(
|
||||
persist<SettingsStore>(
|
||||
(set) => ({
|
||||
importSettings: (settings) => {
|
||||
set({ ...settings });
|
||||
},
|
||||
inputHeight: 200,
|
||||
sessionExpandable: true,
|
||||
sessionsWidth: 320,
|
||||
sidebarKey: 'chat',
|
||||
switchSideBar: (key) => {
|
||||
set({ sidebarKey: key });
|
||||
},
|
||||
}),
|
||||
{ name: 'LOBE_SETTINGS', skipHydration: true },
|
||||
),
|
||||
);
|
||||
31
src/store/settings/action.ts
Normal file
31
src/store/settings/action.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import { merge } from 'lodash-es';
|
||||
import type { StateCreator } from 'zustand/vanilla';
|
||||
|
||||
import type { ConfigSettings } from '@/types/exportConfig';
|
||||
|
||||
import type { SidebarTabKey } from './initialState';
|
||||
import { SettingsState } from './initialState';
|
||||
import type { SettingsStore } from './store';
|
||||
|
||||
export interface SettingsAction {
|
||||
importSettings: (settings: SettingsState) => void;
|
||||
saveSettings: (settings: ConfigSettings) => void;
|
||||
switchSideBar: (key: SidebarTabKey) => void;
|
||||
}
|
||||
|
||||
export const createSettings: StateCreator<
|
||||
SettingsStore,
|
||||
[['zustand/devtools', never]],
|
||||
[],
|
||||
SettingsAction
|
||||
> = (set, get) => ({
|
||||
importSettings: (settings) => {
|
||||
set({ ...settings });
|
||||
},
|
||||
saveSettings: (settings) => {
|
||||
set({ settings: merge(get().settings, settings) });
|
||||
},
|
||||
switchSideBar: (key) => {
|
||||
set({ sidebarKey: key });
|
||||
},
|
||||
});
|
||||
21
src/store/settings/index.ts
Normal file
21
src/store/settings/index.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { create } from 'zustand';
|
||||
import { type PersistOptions, devtools, persist } from 'zustand/middleware';
|
||||
|
||||
import { type SettingsStore, createStore } from './store';
|
||||
|
||||
const persistOptions: PersistOptions<SettingsStore> = {
|
||||
name: 'LOBE_SETTINGS',
|
||||
skipHydration: true,
|
||||
};
|
||||
|
||||
export const useSettings = create<SettingsStore>()(
|
||||
persist(
|
||||
devtools(createStore, {
|
||||
name: 'LOBE_SETTINGS',
|
||||
}),
|
||||
persistOptions,
|
||||
),
|
||||
);
|
||||
|
||||
export * from './selectors';
|
||||
export type { SettingsStore } from './store';
|
||||
25
src/store/settings/initialState.ts
Normal file
25
src/store/settings/initialState.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import type { ThemeMode } from 'antd-style';
|
||||
|
||||
import type { ConfigSettings } from '@/types/exportConfig';
|
||||
|
||||
export type SidebarTabKey = 'chat' | 'market';
|
||||
export const DEFAULT_SETTINGS: ConfigSettings = {
|
||||
avatar: '',
|
||||
};
|
||||
|
||||
export interface SettingsState {
|
||||
inputHeight: number;
|
||||
sessionExpandable?: boolean;
|
||||
sessionsWidth: number;
|
||||
settings: ConfigSettings;
|
||||
sidebarKey: SidebarTabKey;
|
||||
themeMode?: ThemeMode;
|
||||
}
|
||||
|
||||
export const initialState: SettingsState = {
|
||||
inputHeight: 200,
|
||||
sessionExpandable: true,
|
||||
sessionsWidth: 320,
|
||||
settings: DEFAULT_SETTINGS,
|
||||
sidebarKey: 'chat',
|
||||
};
|
||||
9
src/store/settings/selectors.ts
Normal file
9
src/store/settings/selectors.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { DEFAULT_SETTINGS } from '@/store/settings/initialState';
|
||||
|
||||
import { SettingsStore } from './store';
|
||||
|
||||
const currentSettings = (s: SettingsStore) => s.settings || DEFAULT_SETTINGS;
|
||||
|
||||
export const settingsSelectors = {
|
||||
currentSettings,
|
||||
};
|
||||
13
src/store/settings/store.ts
Normal file
13
src/store/settings/store.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { StateCreator } from 'zustand/vanilla';
|
||||
|
||||
import { type SettingsAction, createSettings } from './action';
|
||||
import { type SettingsState, initialState } from './initialState';
|
||||
|
||||
export type SettingsStore = SettingsAction & SettingsState;
|
||||
|
||||
export const createStore: StateCreator<SettingsStore, [['zustand/devtools', never]]> = (
|
||||
...parameters
|
||||
) => ({
|
||||
...initialState,
|
||||
...createSettings(...parameters),
|
||||
});
|
||||
Reference in New Issue
Block a user