feat: Improve organization and functionality of settings and configuration features

The code changes involve renaming and reorganizing files and components related to settings and chat configuration. It also includes updates to the state, functions, and selectors related to managing settings. Additionally, new imports and types are introduced, and existing interfaces are modified to include new properties. The changes aim to improve the organization and functionality of the settings and configuration features in the codebase.
This commit is contained in:
canisminor1990
2023-07-31 18:36:55 +08:00
parent 1232d95a92
commit badde35947
41 changed files with 377 additions and 278 deletions

View File

@@ -1,3 +1,6 @@
import { MetaData } from '@/types/meta';
export const DEFAULT_AVATAR = '🤖';
export const DEFAULT_USER_AVATAR = '😀';
export const DEFAULT_BACKGROUND_COLOR = 'rgba(0,0,0,0)';
export const DEFAULT_AGENT_META: MetaData = {};

50
src/const/settings.ts Normal file
View File

@@ -0,0 +1,50 @@
import { DEFAULT_AGENT_META } from '@/const/meta';
import { LanguageModel } from '@/types/llm';
import { LobeAgentConfig } from '@/types/session';
import { GlobalBaseSettings, GlobalDefaultAgent, GlobalSettings } from '@/types/settings';
export const DEFAULT_BASE_SETTINGS: GlobalBaseSettings = {
OPENAI_API_KEY: '',
avatar: '',
compressThreshold: 24,
enableCompressThreshold: false,
enableHistoryCount: false,
enableMaxTokens: true,
endpoint: '',
fontSize: 14,
frequencyPenalty: 0,
historyCount: 24,
language: 'zh-CN',
maxTokens: 2000,
model: LanguageModel.GPT3_5,
neutralColor: '',
password: '',
presencePenalty: 0,
primaryColor: '',
temperature: 0.5,
themeMode: 'auto',
topP: 1,
};
export const DEFAULT_AGENT_CONFIG: LobeAgentConfig = {
displayMode: 'chat',
model: LanguageModel.GPT3_5,
params: {
frequency_penalty: 0,
presence_penalty: 0,
temperature: 0.6,
top_p: 1,
},
plugins: [],
systemRole: '',
};
export const DEFAULT_AGENT: GlobalDefaultAgent = {
config: DEFAULT_AGENT_CONFIG,
meta: DEFAULT_AGENT_META,
};
export const DEFAULT_SETTINGS: GlobalSettings = {
defaultAgent: DEFAULT_AGENT,
...DEFAULT_BASE_SETTINGS,
};

View File

@@ -13,13 +13,15 @@ import { LobeAgentConfig } from '@/types/session';
import AutoGenerateInput from './AutoGenerateInput';
import BackgroundSwatches from './BackgroundSwatches';
export interface Autocomplete {
autocompleteMeta: AgentAction['autocompleteMeta'];
autocompleteSessionAgentMeta: AgentAction['autocompleteSessionAgentMeta'];
id: string | null;
loading: SessionLoadingState;
}
export interface AgentMetaProps {
autocomplete?: {
autocompleteMeta: AgentAction['autocompleteMeta'];
autocompleteSessionAgentMeta: AgentAction['autocompleteSessionAgentMeta'];
id: string | null;
loading: SessionLoadingState;
};
autocomplete?: Autocomplete;
config: LobeAgentConfig;
meta: MetaData;
updateMeta: AgentAction['updateAgentMeta'];
@@ -46,22 +48,23 @@ const AgentMeta = memo<AgentMetaProps>(({ config, meta, updateMeta, autocomplete
// { key: 'tag', label: t('agentTag'), placeholder: t('agentTagPlaceholder') },
];
const autocompleteItems: FormItemProps[] = basic.map((item) => ({
children: (
<AutoGenerateInput
loading={autocomplete?.loading[item.key as keyof SessionLoadingState]}
onChange={(e) => {
updateMeta({ [item.key]: e.target.value });
}}
onGenerate={() => {
autocomplete?.autocompleteMeta(item.key as keyof typeof meta);
}}
placeholder={item.placeholder}
value={meta[item.key as keyof typeof meta]}
/>
),
label: item.label,
}));
const autocompleteItems: FormItemProps[] = basic.map((item) => {
const handleGenerate = () => autocomplete?.autocompleteMeta(item.key as keyof typeof meta);
return {
children: (
<AutoGenerateInput
loading={autocomplete?.loading[item.key as keyof SessionLoadingState]}
onChange={(e) => {
updateMeta({ [item.key]: e.target.value });
}}
onGenerate={autocomplete ? handleGenerate : undefined}
placeholder={item.placeholder}
value={meta[item.key as keyof typeof meta]}
/>
),
label: item.label,
};
});
if (autocomplete) {
const { autocompleteSessionAgentMeta, loading, id } = autocomplete;

View File

@@ -8,17 +8,24 @@ import {
Github,
Heart,
Settings,
Settings2,
} from 'lucide-react';
import Router from 'next/router';
import { ReactNode, memo, useMemo } from 'react';
import { memo, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { useExportConfig } from '@/hooks/useExportConfig';
import { useImportConfig } from '@/hooks/useImportConfig';
import { SettingsStore } from '@/store/settings';
import pkg from '../../../package.json';
const BottomAction = memo<{ children: ReactNode }>(({ children }) => {
export interface BottomActionProps {
setTab: SettingsStore['switchSideBar'];
tab: SettingsStore['sidebarKey'];
}
const BottomActions = memo<BottomActionProps>(({ tab, setTab }) => {
const { t } = useTranslation('common');
const { exportSessions, exportSettings, exportAll, exportAgents } = useExportConfig();
@@ -93,7 +100,10 @@ const BottomAction = memo<{ children: ReactNode }>(({ children }) => {
icon: <Icon icon={Settings} />,
key: 'setting',
label: t('setting'),
onClick: () => Router.push('/setting'),
onClick: () => {
setTab('settings');
Router.push('/settings');
},
},
],
[],
@@ -103,10 +113,10 @@ const BottomAction = memo<{ children: ReactNode }>(({ children }) => {
<>
<ActionIcon icon={Github} onClick={() => window.open(pkg.homepage, '__blank')} />
<Dropdown arrow={false} menu={{ items }} trigger={['click']}>
{children}
<ActionIcon active={tab === 'settings'} icon={Settings2} />
</Dropdown>
</>
);
});
export default BottomAction;
export default BottomActions;

View File

@@ -0,0 +1,42 @@
import { ActionIcon } from '@lobehub/ui';
import { MessageSquare, Sticker } from 'lucide-react';
import Router from 'next/router';
import { memo } from 'react';
import { useTranslation } from 'react-i18next';
import { SettingsStore } from '@/store/settings';
export interface TopActionProps {
setTab: SettingsStore['switchSideBar'];
tab: SettingsStore['sidebarKey'];
}
const TopActions = memo<TopActionProps>(({ tab, setTab }) => {
const { t } = useTranslation('common');
return (
<>
<ActionIcon
active={tab === 'chat'}
icon={MessageSquare}
onClick={() => {
// 如果已经在 chat 路径下了,那么就不用再跳转了
if (Router.asPath.startsWith('/chat')) return;
Router.push('/');
}}
size="large"
title={t('tab.chat')}
/>
<ActionIcon
active={tab === 'market'}
icon={Sticker}
onClick={() => setTab('market')}
size="large"
title={t('tab.market')}
/>
</>
);
});
export default TopActions;

View File

@@ -1,13 +1,12 @@
import { ActionIcon, SideNav } from '@lobehub/ui';
import { MessageSquare, Settings2, Sticker } from 'lucide-react';
import Router from 'next/router';
import { SideNav } from '@lobehub/ui';
import { memo } from 'react';
import { shallow } from 'zustand/shallow';
import AvatarWithUpload from '@/features/AvatarWithUpload';
import { useSettings } from '@/store/settings';
import BottomAction from './BottomAction';
import BottomActions from './BottomActions';
import TopActions from './TopActions';
export default memo(() => {
const [tab, setTab] = useSettings((s) => [s.sidebarKey, s.switchSideBar], shallow);
@@ -15,33 +14,9 @@ export default memo(() => {
return (
<SideNav
avatar={<AvatarWithUpload />}
bottomActions={
<BottomAction>
<ActionIcon active={tab === 'setting'} icon={Settings2} />
</BottomAction>
}
bottomActions={<BottomActions setTab={setTab} tab={tab} />}
style={{ height: '100vh' }}
topActions={
<>
<ActionIcon
active={tab === 'chat'}
icon={MessageSquare}
onClick={() => {
// 如果已经在 chat 路径下了,那么就不用再跳转了
if (Router.asPath.startsWith('/chat')) return;
Router.push('/');
}}
size="large"
/>
<ActionIcon
active={tab === 'market'}
icon={Sticker}
onClick={() => setTab('market')}
size="large"
/>
</>
}
topActions={<TopActions setTab={setTab} tab={tab} />}
/>
);
});

View File

@@ -6,13 +6,13 @@ import { importConfigFile } from '@/utils/config';
export const useImportConfig = () => {
const importSessions = useSessionStore((s) => s.importSessions);
const importSettings = useSettings((s) => s.importSettings);
const importAppSettings = useSettings((s) => s.importAppSettings);
const importConfig = (info: any) => {
importConfigFile(info, (config) => {
switch (config.exportType) {
case 'settings': {
importSettings(config.state.settings);
importAppSettings(config.state.settings);
break;
}
@@ -24,7 +24,7 @@ export const useImportConfig = () => {
case 'all': {
importSessions(config.state.sessions);
importSettings(config.state.settings);
importAppSettings(config.state.settings);
break;
}

View File

@@ -49,6 +49,10 @@ export default {
sendPlaceholder: '输入聊天内容...',
setting: '设置',
share: '分享',
tab: {
chat: '会话',
market: '助手市场',
},
temp: '临时',
tokenDetail: '系统设定: {{systemRoleToken}} 历史消息: {{chatsToken}}',
topic: {

View File

@@ -162,4 +162,8 @@ export default {
},
title: '主题设置',
},
tab: {
agent: '默认角色',
common: '通用设置',
},
};

View File

@@ -33,7 +33,7 @@ const List = () => {
const [displayMode, chatLoadingId, deleteMessage, resendMessage, dispatchMessage] =
useSessionStore(
(s) => [
agentSelectors.currentAgentConfigSafe(s).displayMode,
agentSelectors.currentAgentConfig(s).displayMode,
s.chatLoadingId,
s.deleteMessage,
s.resendMessage,

View File

@@ -16,7 +16,7 @@ const InputActions = memo(() => {
shallow,
);
const [model, temperature] = useSessionStore((s) => {
const config = agentSelectors.currentAgentConfigSafe(s);
const config = agentSelectors.currentAgentConfig(s);
return [
config.model,
config.params.temperature,

View File

@@ -67,7 +67,7 @@ const Header = memo(() => {
<ActionIcon
icon={Settings}
onClick={() => {
Router.push(`/chat/${id}/edit`);
Router.push(`/chat/${id}/setting`);
}}
size={{ fontSize: 24 }}
title={t('header.session', { ns: 'setting' })}

View File

@@ -40,7 +40,7 @@ const useStyles = createStyles(({ css, token }) => ({
`,
}));
const SideBar = memo(() => {
const Inner = memo(() => {
const [openModal, setOpenModal] = useState(false);
const { styles } = useStyles();
const [systemRole, updateAgentConfig] = useSessionStore(
@@ -87,4 +87,4 @@ const SideBar = memo(() => {
);
});
export default SideBar;
export default Inner;

View File

@@ -7,7 +7,7 @@ import HeaderSpacing from '@/components/HeaderSpacing';
import { CHAT_SIDEBAR_WIDTH } from '@/const/layoutTokens';
import { useSettings } from '@/store/settings';
import SideBar from './SideBar';
import Inner from './Inner';
const useStyles = createStyles(({ cx, css, token, stylish }) => ({
drawer: cx(
@@ -38,7 +38,7 @@ const Config = () => {
>
<HeaderSpacing />
<DraggablePanelContainer style={{ flex: 'none', minWidth: CHAT_SIDEBAR_WIDTH }}>
<SideBar />
<Inner />
</DraggablePanelContainer>
</DraggablePanel>
);

View File

@@ -7,9 +7,9 @@ import { sessionSelectors, useSessionStore } from '@/store/session';
import { genSiteHeadTitle } from '@/utils/genSiteHeadTitle';
import Layout from '../layout';
import Config from './Config';
import Conversation from './Conversation';
import Header from './Header';
import Config from './Sidebar';
const Chat = memo(() => {
const [title] = useSessionStore((s) => {

View File

@@ -16,26 +16,14 @@ import Header from './Header';
const EditPage = memo(() => {
const { t } = useTranslation('setting');
const config = useSessionStore(agentSelectors.currentAgentConfigSafe, isEqual);
const config = useSessionStore(agentSelectors.currentAgentConfig, isEqual);
const meta = useSessionStore(agentSelectors.currentAgentMeta, isEqual);
const [
updateAgentConfig,
toggleAgentPlugin,
autocompleteMeta,
autocompleteSessionAgentMeta,
loading,
updateAgentMeta,
id,
title,
] = useSessionStore(
const autocomplete = useSessionStore(agentSelectors.currentAutocomplete, shallow);
const [updateAgentConfig, toggleAgentPlugin, updateAgentMeta, title] = useSessionStore(
(s) => [
s.updateAgentConfig,
s.toggleAgentPlugin,
s.autocompleteMeta,
s.autocompleteSessionAgentMeta,
s.autocompleteLoading,
s.updateAgentMeta,
s.activeId,
agentSelectors.currentAgentTitle(s),
],
shallow,
@@ -54,7 +42,7 @@ const EditPage = memo(() => {
<HeaderSpacing height={HEADER_HEIGHT - 16} />
<AgentPrompt config={config} updateConfig={updateAgentConfig} />
<AgentMeta
autocomplete={{ autocompleteMeta, autocompleteSessionAgentMeta, id, loading }}
autocomplete={autocomplete}
config={config}
meta={meta}
updateMeta={updateAgentMeta}

View File

@@ -0,0 +1,25 @@
import isEqual from 'fast-deep-equal';
import { memo } from 'react';
import { shallow } from 'zustand/shallow';
import { AgentConfig, AgentMeta, AgentPlugin, AgentPrompt } from '@/features/AgentSetting';
import { settingsSelectors, useSettings } from '@/store/settings';
const Agent = memo(() => {
const config = useSettings(settingsSelectors.currentAgentConfig, isEqual);
const meta = useSettings(settingsSelectors.currentAgentMeta, isEqual);
const [setAgentConfig, setAgentMeta, toggleAgentPlugin] = useSettings(
(s) => [s.setAgentConfig, s.setAgentMeta, s.toggleAgentPlugin],
shallow,
);
return (
<>
<AgentPrompt config={config} updateConfig={setAgentConfig} />
<AgentMeta config={config} meta={meta} updateMeta={setAgentMeta} />
<AgentConfig config={config} updateConfig={setAgentConfig} />
<AgentPlugin config={config} updateConfig={toggleAgentPlugin} />
</>
);
});
export default Agent;

View File

@@ -1,23 +1,22 @@
import { Form, type ItemGroup, ThemeSwitch } from '@lobehub/ui';
import { Form as AntForm, App, Button, Input, Select, Switch } from 'antd';
import { Form as AntForm, App, Button, Input, Select } from 'antd';
import isEqual from 'fast-deep-equal';
import { debounce } from 'lodash-es';
import { AppWindow, BrainCog, MessagesSquare, Palette, Webhook } from 'lucide-react';
import { AppWindow, Palette, Webhook } from 'lucide-react';
import { memo, useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import SliderWithInput from 'src/components/SliderWithInput';
import { shallow } from 'zustand/shallow';
import { FORM_STYLE } from '@/const/layoutTokens';
import { DEFAULT_SETTINGS } from '@/const/settings';
import AvatarWithUpload from '@/features/AvatarWithUpload';
import { options } from '@/locales/options';
import { useSessionStore } from '@/store/session';
import { settingsSelectors, useSettings } from '@/store/settings';
import { DEFAULT_SETTINGS } from '@/store/settings/initialState';
import { LanguageModel } from '@/types/llm';
import { ConfigKeys } from '@/types/settings';
import { ThemeSwatchesNeutral, ThemeSwatchesPrimary } from './ThemeSwatches';
import { ThemeSwatchesNeutral, ThemeSwatchesPrimary } from '../ThemeSwatches';
type SettingItemGroup = ItemGroup & {
children: {
@@ -25,7 +24,7 @@ type SettingItemGroup = ItemGroup & {
}[];
};
const SettingForm = memo(() => {
const Common = memo(() => {
const { t } = useTranslation('setting');
const [form] = AntForm.useForm();
const clearSessions = useSessionStore((s) => s.clearSessions);
@@ -147,111 +146,6 @@ const SettingForm = memo(() => {
[settings],
);
const chat: SettingItemGroup = useMemo(
() => ({
children: [
{
children: <Switch />,
label: t('settingChat.enableHistoryCount.title'),
minWidth: undefined,
name: 'enableHistoryCount',
valuePropName: 'checked',
},
{
children: <SliderWithInput max={32} min={0} />,
desc: t('settingChat.historyCount.desc'),
hidden: !settings.enableHistoryCount,
label: t('settingChat.historyCount.title'),
name: 'historyCount',
},
{
children: <Switch />,
label: t('settingChat.enableCompressThreshold.title'),
minWidth: undefined,
name: 'enableCompressThreshold',
valuePropName: 'checked',
},
{
children: <SliderWithInput max={32} min={0} />,
desc: t('settingChat.compressThreshold.desc'),
hidden: !settings.enableCompressThreshold,
label: t('settingChat.compressThreshold.title'),
name: 'compressThreshold',
},
],
icon: MessagesSquare,
title: t('settingChat.title'),
}),
[settings],
);
const model: SettingItemGroup = useMemo(
() => ({
children: [
{
children: (
<Select
options={Object.values(LanguageModel).map((value) => ({
label: value,
value,
}))}
/>
),
desc: t('settingModel.model.desc'),
label: t('settingModel.model.title'),
name: 'model',
tag: 'model',
},
{
children: <SliderWithInput max={1} min={0} step={0.1} />,
desc: t('settingModel.temperature.desc'),
label: t('settingModel.temperature.title'),
name: 'temperature',
tag: 'temperature',
},
{
children: <SliderWithInput max={1} min={0} step={0.1} />,
desc: t('settingModel.topP.desc'),
label: t('settingModel.topP.title'),
name: 'topP',
tag: 'top_p',
},
{
children: <SliderWithInput max={2} min={-2} step={0.1} />,
desc: t('settingModel.presencePenalty.desc'),
label: t('settingModel.presencePenalty.title'),
name: 'presencePenalty',
tag: 'presence_penalty',
},
{
children: <SliderWithInput max={2} min={-2} step={0.1} />,
desc: t('settingModel.frequencyPenalty.desc'),
label: t('settingModel.frequencyPenalty.title'),
name: 'frequencyPenalty',
tag: 'frequency_penalty',
},
{
children: <Switch />,
label: t('settingModel.enableMaxTokens.title'),
minWidth: undefined,
name: 'enableMaxTokens',
valuePropName: 'checked',
},
{
children: <SliderWithInput max={32_000} min={0} step={100} />,
desc: t('settingModel.maxTokens.desc'),
hidden: !settings.enableMaxTokens,
label: t('settingModel.maxTokens.title'),
name: 'maxTokens',
tag: 'max_tokens',
},
],
icon: BrainCog,
title: t('settingModel.title'),
}),
[settings],
);
const system: SettingItemGroup = useMemo(
() => ({
children: [
@@ -288,7 +182,7 @@ const SettingForm = memo(() => {
[settings],
);
const items = useMemo(() => [theme, openAI, chat, model, system], [settings]);
const items = useMemo(() => [theme, openAI, system], [settings]);
return (
<Form
@@ -301,4 +195,4 @@ const SettingForm = memo(() => {
);
});
export default SettingForm;
export default Common;

View File

@@ -0,0 +1,50 @@
import { Segmented } from 'antd';
import { createStyles } from 'antd-style';
import { memo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { Center } from 'react-layout-kit';
import Agent from './Agent';
import Common from './Common';
const useStyles = createStyles(({ css, token }) => ({
tabs: css`
padding: 4px;
border: 1px solid ${token.colorBorderSecondary};
border-radius: ${token.borderRadiusLG}px;
&:hover {
border-color: ${token.colorBorder};
}
`,
}));
enum Tabs {
common,
agent,
}
const Settings = memo(() => {
const [tab, setTab] = useState<Tabs>(Tabs.common);
const { styles } = useStyles();
const { t } = useTranslation('setting');
return (
<Center gap={16} width={'100%'}>
<div className={styles.tabs}>
<Segmented
onChange={(e) => setTab(e as Tabs)}
options={[
{ label: t('tab.common'), value: Tabs.common },
{ label: t('tab.agent'), value: Tabs.agent },
]}
value={tab}
/>
</div>
{tab === Tabs.common && <Common />}
{tab === Tabs.agent && <Agent />}
</Center>
);
});
export default Settings;

View File

@@ -4,7 +4,7 @@ import { Flexbox } from 'react-layout-kit';
import HeaderSpacing from '@/components/HeaderSpacing';
import Header from './Header';
import SettingForm from './SettingForm';
import Settings from './Settings';
import SettingLayout from './layout';
const Setting = memo(() => {
@@ -13,7 +13,7 @@ const Setting = memo(() => {
<Header />
<Flexbox align={'center'} flex={1} padding={24} style={{ overflow: 'auto' }}>
<HeaderSpacing />
<SettingForm />
<Settings />
</Flexbox>
</SettingLayout>
);

View File

@@ -21,7 +21,7 @@ const SettingLayout = memo<{ children: ReactNode }>(({ children }) => {
useEffect(() => {
const hasRehydrated = useSettings.persist.hasHydrated();
if (hasRehydrated) {
useSettings.setState({ sidebarKey: 'setting' });
useSettings.setState({ sidebarKey: 'settings' });
}
}, []);

View File

@@ -1,4 +1,4 @@
import { LanguageModel } from '@/types/llm';
import { DEFAULT_AGENT_CONFIG } from '@/const/settings';
import { MetaData } from '@/types/meta';
import { LobeAgentConfig } from '@/types/session';
@@ -8,18 +8,7 @@ export interface AgentConfigState {
autocompleteLoading: SessionLoadingState;
}
export const initialLobeAgentConfig: LobeAgentConfig = {
displayMode: 'chat',
model: LanguageModel.GPT3_5,
params: {
frequency_penalty: 0,
presence_penalty: 0,
temperature: 0.6,
top_p: 1,
},
plugins: [],
systemRole: '',
};
export const initialLobeAgentConfig: LobeAgentConfig = DEFAULT_AGENT_CONFIG;
export const initialAgentConfigState: AgentConfigState = {
// // loading 中间态

View File

@@ -1,10 +1,11 @@
import { t } from 'i18next';
import { defaults } from 'lodash-es';
import { DEFAULT_AVATAR, DEFAULT_BACKGROUND_COLOR } from '@/const/meta';
import { Autocomplete } from '@/features/AgentSetting/AgentMeta';
import { SessionStore } from '@/store/session';
import { LanguageModel } from '@/types/llm';
import { MetaData } from '@/types/meta';
import { LobeAgentConfig } from '@/types/session';
import { sessionSelectors } from '../session';
import { initialLobeAgentConfig } from './initialState';
@@ -15,6 +16,13 @@ const currentAgentMeta = (s: SessionStore): MetaData => {
return { avatar: DEFAULT_AVATAR, backgroundColor: DEFAULT_BACKGROUND_COLOR, ...session?.meta };
};
const currentAutocomplete = (s: SessionStore): Autocomplete => ({
autocompleteMeta: s.autocompleteMeta,
autocompleteSessionAgentMeta: s.autocompleteSessionAgentMeta,
id: s.activeId,
loading: s.autocompleteLoading,
});
const currentAgentTitle = (s: SessionStore) => currentAgentMeta(s)?.title || t('defaultSession');
const currentAgentDescription = (s: SessionStore) =>
@@ -34,15 +42,11 @@ const currentAgentAvatar = (s: SessionStore) => {
const currentAgentConfig = (s: SessionStore) => {
const session = sessionSelectors.currentSession(s);
return session?.config;
};
const currentAgentConfigSafe = (s: SessionStore): LobeAgentConfig => {
return currentAgentConfig(s) || initialLobeAgentConfig;
return defaults(session?.config, initialLobeAgentConfig);
};
const currentAgentSystemRole = (s: SessionStore) => {
return currentAgentConfigSafe(s).systemRole;
return currentAgentConfig(s).systemRole;
};
const currentAgentModel = (s: SessionStore): LanguageModel => {
@@ -52,7 +56,7 @@ const currentAgentModel = (s: SessionStore): LanguageModel => {
};
const hasSystemRole = (s: SessionStore) => {
const config = currentAgentConfigSafe(s);
const config = currentAgentConfig(s);
return !!config.systemRole;
};
@@ -64,12 +68,12 @@ export const agentSelectors = {
currentAgentAvatar,
currentAgentBackgroundColor,
currentAgentConfig,
currentAgentConfigSafe,
currentAgentDescription,
currentAgentMeta,
currentAgentModel,
currentAgentSystemRole,
currentAgentTitle,
currentAutocomplete,
getAvatar,
getTitle,
hasSystemRole,

View File

@@ -112,7 +112,7 @@ export const chatMessage: StateCreator<
generateMessage: async (messages, assistantId) => {
const { dispatchMessage } = get();
set({ chatLoadingId: assistantId });
const config = agentSelectors.currentAgentConfigSafe(get());
const config = agentSelectors.currentAgentConfig(get());
const compiler = template(config.inputTemplate, { interpolate: /{{([\S\s]+?)}}/g });
@@ -137,7 +137,7 @@ export const chatMessage: StateCreator<
// 2. TODO 按参数设定截断长度
// 3. 添加 systemRole
const { systemRole } = agentSelectors.currentAgentConfigSafe(get());
const { systemRole } = agentSelectors.currentAgentConfig(get());
if (systemRole) {
postMessages.unshift({ content: systemRole, role: 'system' } as ChatMessage);
}
@@ -188,7 +188,7 @@ export const chatMessage: StateCreator<
realFetchAIResponse: async (messages, userMessageId) => {
const { dispatchMessage, generateMessage, triggerFunctionCall, activeTopicId } = get();
const { model } = agentSelectors.currentAgentConfigSafe(get());
const { model } = agentSelectors.currentAgentConfig(get());
// 添加一个空的信息用于放置 ai 响应,注意顺序不能反
// 因为如果顺序反了messages 中将包含新增的 ai message

View File

@@ -23,7 +23,7 @@ export const currentChats = (s: SessionStore): ChatMessage[] => {
};
export const systemRoleSel = (s: SessionStore): string => {
const config = agentSelectors.currentAgentConfigSafe(s);
const config = agentSelectors.currentAgentConfig(s);
return config.systemRole;
};

View File

@@ -1,3 +1,4 @@
import { DEFAULT_AGENT_META } from '@/const/meta';
import { LobeAgentSession, LobeSessionType } from '@/types/session';
import { initialLobeAgentConfig } from '../agentConfig';
@@ -18,7 +19,7 @@ export const initLobeSession: LobeAgentSession = {
config: initialLobeAgentConfig,
createAt: Date.now(),
id: '',
meta: {},
meta: DEFAULT_AGENT_META,
type: LobeSessionType.Agent,
updateAt: Date.now(),
};

View File

@@ -1,26 +1,37 @@
import { ThemeMode } from 'antd-style';
import { produce } from 'immer';
import { merge } from 'lodash-es';
import type { StateCreator } from 'zustand/vanilla';
import { DEFAULT_AGENT, DEFAULT_BASE_SETTINGS, DEFAULT_SETTINGS } from '@/const/settings';
import type { LobeAgentSession } from '@/types/session';
import { LobeAgentConfig } from '@/types/session';
import type { GlobalSettings } from '@/types/settings';
import type { SidebarTabKey } from './initialState';
import { DEFAULT_SETTINGS, SettingsState } from './initialState';
import { AppSettingsState } from './initialState';
import type { SettingsStore } from './store';
/**
* 设置操作
*/
export interface SettingsAction {
importSettings: (settings: GlobalSettings) => void;
importAppSettings: (settings: GlobalSettings) => void;
resetAgentConfig: () => void;
resetAgentMeta: () => void;
resetBaseSettings: () => void;
resetDefaultAgent: () => void;
/**
* 重置设置
*/
resetSettings: () => void;
setAgentConfig: (config: Partial<LobeAgentSession['config']>) => void;
setAgentMeta: (meta: Partial<LobeAgentSession['meta']>) => void;
/**
* 设置全局设置
* @param settings - 全局设置
*/
setGlobalSettings: (settings: SettingsState) => void;
setAppSettings: (settings: AppSettingsState) => void;
/**
* 设置部分配置设置
* @param settings - 部分配置设置
@@ -37,6 +48,7 @@ export interface SettingsAction {
*/
switchSideBar: (key: SidebarTabKey) => void;
toggleAgentPanel: (visible?: boolean) => void;
toggleAgentPlugin: (pluginId: string) => void;
}
export const createSettings: StateCreator<
@@ -45,10 +57,10 @@ export const createSettings: StateCreator<
[],
SettingsAction
> = (set, get) => ({
importSettings: (importSettings) => {
importAppSettings: (importAppSettings) => {
const { setSettings } = get();
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { OPENAI_API_KEY: _, password: __, ...settings } = importSettings;
const { OPENAI_API_KEY: _, password: __, ...settings } = importAppSettings;
setSettings({
...settings,
@@ -56,15 +68,49 @@ export const createSettings: StateCreator<
avatar: !get().settings.avatar ? settings.avatar : get().settings.avatar,
});
},
resetAgentConfig: () => {
const settings = get().settings;
settings.defaultAgent.config = DEFAULT_AGENT.config;
set({ settings });
},
resetAgentMeta: () => {
const settings = get().settings;
settings.defaultAgent.meta = DEFAULT_AGENT.meta;
set({ settings });
},
resetBaseSettings: () => {
const settings = get().settings;
set({ settings: { ...settings, ...DEFAULT_BASE_SETTINGS } });
},
resetDefaultAgent: () => {
const settings = get().settings;
settings.defaultAgent = DEFAULT_AGENT;
set({ settings });
},
resetSettings: () => {
set({ settings: DEFAULT_SETTINGS });
},
setGlobalSettings: (settings) => {
setAgentConfig: (config) => {
const settings = get().settings;
const oldConfig = settings.defaultAgent.config;
settings.defaultAgent.config = merge(config, oldConfig);
set({ settings });
},
setAgentMeta: (meta) => {
const settings = get().settings;
const oldMeta = settings.defaultAgent.meta;
settings.defaultAgent.meta = merge(meta, oldMeta);
set({ settings });
},
setAppSettings: (settings) => {
set({ ...settings });
},
setSettings: (settings) => {
const oldSetting = get().settings;
set({ settings: { ...oldSetting, ...settings } });
set({ settings: merge(settings, oldSetting) });
},
setThemeMode: (themeMode) => {
get().setSettings({ themeMode });
@@ -77,4 +123,25 @@ export const createSettings: StateCreator<
set({ showAgentConfig });
},
toggleAgentPlugin: (id: string) => {
const settings = get().settings;
settings.defaultAgent.config = produce(
settings.defaultAgent.config,
(draft: LobeAgentConfig) => {
if (draft.plugins === undefined) {
draft.plugins = [id];
} else {
const plugins = draft.plugins;
if (plugins.includes(id)) {
plugins.splice(plugins.indexOf(id), 1);
} else {
plugins.push(id);
}
}
},
);
set({ settings });
},
});

View File

@@ -1,32 +1,9 @@
import { LanguageModel } from '@/types/llm';
import { DEFAULT_SETTINGS } from '@/const/settings';
import type { GlobalSettings } from '@/types/settings';
export type SidebarTabKey = 'chat' | 'market' | 'setting';
export type SidebarTabKey = 'chat' | 'market' | 'settings';
export const DEFAULT_SETTINGS: GlobalSettings = {
OPENAI_API_KEY: '',
avatar: '',
compressThreshold: 24,
enableCompressThreshold: false,
enableHistoryCount: false,
enableMaxTokens: true,
endpoint: '',
fontSize: 14,
frequencyPenalty: 0,
historyCount: 24,
language: 'zh-CN',
maxTokens: 2000,
model: LanguageModel.GPT3_5,
neutralColor: '',
password: '',
presencePenalty: 0,
primaryColor: '',
temperature: 0.5,
themeMode: 'auto',
topP: 1,
};
export interface SettingsState {
export interface AppSettingsState {
inputHeight: number;
sessionExpandable?: boolean;
sessionsWidth: number;
@@ -35,7 +12,7 @@ export interface SettingsState {
sidebarKey: SidebarTabKey;
}
export const initialState: SettingsState = {
export const initialState: AppSettingsState = {
inputHeight: 200,
sessionExpandable: true,
sessionsWidth: 320,

View File

@@ -1,16 +1,20 @@
import { defaults } from 'lodash-es';
import { DEFAULT_SETTINGS } from '@/store/settings/initialState';
import { DEFAULT_AGENT_META } from '@/const/meta';
import { DEFAULT_AGENT, DEFAULT_AGENT_CONFIG, DEFAULT_SETTINGS } from '@/const/settings';
import { GlobalSettings } from '@/types/settings';
import { SettingsStore } from './store';
const currentSettings = (s: SettingsStore) => defaults(s.settings, DEFAULT_SETTINGS);
const selecThemeMode = (s: SettingsStore) => ({
setThemeMode: s.setThemeMode,
themeMode: s.settings.themeMode,
});
const currentDefaultAgent = (s: SettingsStore) => defaults(s.settings.defaultAgent, DEFAULT_AGENT);
const currentAgentConfig = (s: SettingsStore) =>
defaults(s.settings.defaultAgent.config, DEFAULT_AGENT_CONFIG);
const currentAgentMeta = (s: SettingsStore) =>
defaults(s.settings.defaultAgent.meta, DEFAULT_AGENT_META);
export const exportSettings = (s: SettingsStore) => {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
@@ -20,8 +24,9 @@ export const exportSettings = (s: SettingsStore) => {
};
export const settingsSelectors = {
currentAgentConfig,
currentAgentMeta,
currentDefaultAgent,
currentSettings,
exportSettings,
selecThemeMode,
};

View File

@@ -1,9 +1,9 @@
import { StateCreator } from 'zustand/vanilla';
import { type SettingsAction, createSettings } from './action';
import { type SettingsState, initialState } from './initialState';
import { type AppSettingsState, initialState } from './initialState';
export type SettingsStore = SettingsAction & SettingsState;
export type SettingsStore = SettingsAction & AppSettingsState;
export const createStore: StateCreator<SettingsStore, [['zustand/devtools', never]]> = (
...parameters

View File

@@ -1,13 +1,11 @@
import { NeutralColors, PrimaryColors } from '@lobehub/ui';
import { ThemeMode } from 'antd-style';
import type { NeutralColors, PrimaryColors } from '@lobehub/ui';
import type { ThemeMode } from 'antd-style';
import { Locales } from '@/locales/resources';
import { LanguageModel } from '@/types/llm';
import type { Locales } from '@/locales/resources';
import type { LanguageModel } from '@/types/llm';
import type { LobeAgentSession } from '@/types/session';
/**
* 配置设置
*/
export interface GlobalSettings {
export interface GlobalBaseSettings {
OPENAI_API_KEY: string;
avatar: string;
compressThreshold: number;
@@ -29,4 +27,14 @@ export interface GlobalSettings {
themeMode: ThemeMode;
topP: number;
}
export type GlobalDefaultAgent = Partial<LobeAgentSession>;
/**
* 配置设置
*/
export interface GlobalSettings extends GlobalBaseSettings {
defaultAgent: GlobalDefaultAgent;
}
export type ConfigKeys = keyof GlobalSettings;