Skip to content

Commit

Permalink
✨ feat: 支持持久化隐藏 Topic 功能
Browse files Browse the repository at this point in the history
- 在 settings 中添加 guide 字段
- 为 Empty 组件添加受控模式
- setting store 添加 guide 状态控制方法
  • Loading branch information
arvinxx committed Aug 10, 2023
1 parent fefae61 commit 9ea2778
Show file tree
Hide file tree
Showing 8 changed files with 177 additions and 90 deletions.
1 change: 1 addition & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,6 @@ config.rules['unicorn/prefer-type-error'] = 0;
config.rules['unicorn/prefer-logical-operator-over-ternary'] = 0;
config.rules['unicorn/no-null'] = 0;
config.rules['unicorn/no-typeof-undefined'] = 0;
config.rules['unicorn/explicit-length-check'] = 0;

module.exports = config;
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@
"serpapi": "^2",
"swr": "^2",
"ts-md5": "^1",
"use-merge-value": "^1",
"uuid": "^9",
"zustand": "^4.4",
"zustand-utils": "^1"
Expand Down
66 changes: 46 additions & 20 deletions src/components/Empty/index.tsx
Original file line number Diff line number Diff line change
@@ -1,34 +1,60 @@
import { ActionIcon, DivProps } from '@lobehub/ui';
import { X } from 'lucide-react';
import { memo, useState } from 'react';
import Image from 'next/image';
import { memo } from 'react';
import useMergeState from 'use-merge-value';

import { useStyles } from './style';

interface EmptyProps extends DivProps {
alt: string;
cover: string;
defaultVisible?: boolean;
desc: string;
height?: number;
onVisibleChange?: (visible: boolean) => void;
title: string;
visible?: boolean;
width?: number;
}

const Empty = memo<EmptyProps>(({ cover, title, desc, ...props }) => {
const [visiable, setVisiable] = useState(true);
const { styles } = useStyles();
if (!visiable) return null;
return (
<div className={styles.container} {...props}>
<ActionIcon
className={styles.close}
icon={X}
onClick={() => setVisiable(false)}
size={{ blockSize: 24, fontSize: 16 }}
/>
{cover && <img alt="empty" src={cover} width="100%" />}
<div className={styles.content}>
{title && <h3>{title}</h3>}
{desc && <p className={styles.desc}>{desc}</p>}
const Empty = memo<EmptyProps>(
({
cover,
visible,
defaultVisible,
onVisibleChange,
alt,
title,
desc,
width,
height,
...props
}) => {
const [value, setValue] = useMergeState(true, {
defaultValue: defaultVisible,
onChange: onVisibleChange,
value: visible,
});

const { styles } = useStyles();
if (!value) return null;
return (
<div className={styles.container} {...props}>
<ActionIcon
className={styles.close}
icon={X}
onClick={() => setValue(false)}
size={{ blockSize: 24, fontSize: 16 }}
/>
{cover && <Image alt={alt} height={height} src={cover} width={width} />}
<div className={styles.content}>
{title && <h3>{title}</h3>}
{desc && <p className={styles.desc}>{desc}</p>}
</div>
</div>
</div>
);
});
);
},
);

export default Empty;
17 changes: 16 additions & 1 deletion src/pages/chat/[id]/Sidebar/Topic/index.tsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,41 @@
import { useThemeMode } from 'antd-style';
import isEqual from 'fast-deep-equal';
import { useTranslation } from 'react-i18next';
import { Flexbox } from 'react-layout-kit';
import { shallow } from 'zustand/shallow';

import Empty from '@/components/Empty';
import { topicSelectors, useSessionStore } from '@/store/session';
import { useSettings } from '@/store/settings';

import TopicItem from './TopicItem';

export const Topic = () => {
const topics = useSessionStore(topicSelectors.currentTopics);
const topics = useSessionStore(topicSelectors.currentTopics, isEqual);
const { isDarkMode } = useThemeMode();
const [activeTopicId] = useSessionStore((s) => [s.activeTopicId], shallow);
const { t } = useTranslation('empty');

const [visible, updateGuideState] = useSettings(
(s) => [s.guide?.topic, s.updateGuideState],
shallow,
);

return (
<Flexbox gap={2}>
{topics?.length === 0 && (
<Empty
alt={t('topic.desc')}
cover={`/images/empty_topic_${isDarkMode ? 'dark' : 'light'}.webp`}
desc={t('topic.desc')}
height={120}
onVisibleChange={(visible) => {
updateGuideState({ topic: visible });
}}
style={{ marginBottom: 6 }}
title={t('topic.title')}
visible={visible}
width={200}
/>
)}

Expand Down
7 changes: 7 additions & 0 deletions src/store/settings/initialState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,13 @@ import type { GlobalSettings } from '@/types/settings';

export type SidebarTabKey = 'chat' | 'market' | 'settings';

export interface Guide {
// Topic 引导
topic?: boolean;
}

export interface AppSettingsState {
guide?: Guide;
inputHeight: number;
sessionExpandable?: boolean;
sessionsWidth: number;
Expand All @@ -13,6 +19,7 @@ export interface AppSettingsState {
}

export const initialState: AppSettingsState = {
guide: {},
inputHeight: 200,
sessionExpandable: true,
sessionsWidth: 320,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,77 +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 { DEFAULT_AGENT } from '@/const/settings';
import { MetaData } from '@/types/meta';
import type { LobeAgentSession } from '@/types/session';
import { LobeAgentConfig } from '@/types/session';
import type { GlobalSettings } from '@/types/settings';
import { setNamespace } from '@/utils/storeDebug';

import type { SidebarTabKey } from './initialState';
import { AppSettingsState } from './initialState';
import type { SettingsStore } from './store';
import type { SettingsStore } from '../store';

const t = setNamespace('settings');

/**
* 设置操作
*/
export interface SettingsAction {
importAppSettings: (settings: GlobalSettings) => void;
export interface AgentAction {
resetAgentConfig: () => void;
resetAgentMeta: () => void;
resetBaseSettings: () => void;
resetDefaultAgent: () => void;
/**
* 重置设置
*/
resetSettings: () => void;
setAgentConfig: (config: Partial<LobeAgentSession['config']>) => void;
setAgentMeta: (meta: Partial<LobeAgentSession['meta']>) => void;
/**
* 设置全局设置
* @param settings - 全局设置
*/
setAppSettings: (settings: AppSettingsState) => void;
/**
* 设置部分配置设置
* @param settings - 部分配置设置
*/
setSettings: (settings: Partial<GlobalSettings>) => void;
/**
* 设置主题模式
* @param themeMode - 主题模式
*/
setThemeMode: (themeMode: ThemeMode) => void;
/**
* 切换侧边栏选项
* @param key - 选中的侧边栏选项
*/
switchSideBar: (key: SidebarTabKey) => void;
toggleAgentPanel: (visible?: boolean) => void;
toggleAgentPlugin: (pluginId: string) => void;
}

export const createSettings: StateCreator<
export const createAgentSlice: StateCreator<
SettingsStore,
[['zustand/devtools', never]],
[],
SettingsAction
AgentAction
> = (set, get) => ({
importAppSettings: (importAppSettings) => {
const { setSettings } = get();
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { OPENAI_API_KEY: _, password: __, ...settings } = importAppSettings;

setSettings({
...settings,
// 如果用户存在用户头像,那么不做导入
avatar: !get().settings.avatar ? settings.avatar : get().settings.avatar,
});
},
resetAgentConfig: () => {
const settings = produce(get().settings, (draft: GlobalSettings) => {
draft.defaultAgent.config = DEFAULT_AGENT.config;
Expand All @@ -84,20 +44,12 @@ export const createSettings: StateCreator<
});
set({ settings }, false, t('resetAgentMeta'));
},
resetBaseSettings: () => {
const settings = get().settings;

set({ settings: { ...settings, ...DEFAULT_BASE_SETTINGS } }, false, t('resetBaseSettings'));
},
resetDefaultAgent: () => {
const settings = produce(get().settings, (draft: GlobalSettings) => {
draft.defaultAgent = DEFAULT_AGENT;
});
set({ settings }, false, t('resetDefaultAgent'));
},
resetSettings: () => {
set({ settings: DEFAULT_SETTINGS }, false, t('resetSettings'));
},
setAgentConfig: (config) => {
const settings = produce(get().settings, (draft: GlobalSettings) => {
const oldConfig = draft.defaultAgent.config as LobeAgentConfig;
Expand All @@ -114,19 +66,6 @@ export const createSettings: StateCreator<

set({ settings }, false, t('setAgentMeta', meta));
},
setAppSettings: (settings) => {
set({ ...settings }, false, t('setAppSettings', settings));
},
setSettings: (settings) => {
const oldSetting = get().settings;
set({ settings: merge(oldSetting, settings) }, false, t('setSettings', settings));
},
setThemeMode: (themeMode) => {
get().setSettings({ themeMode });
},
switchSideBar: (key) => {
set({ sidebarKey: key }, false, t('switchSideBar', key));
},
toggleAgentPanel: (newValue) => {
const showAgentConfig = typeof newValue === 'boolean' ? newValue : !get().showAgentConfig;

Expand Down
96 changes: 96 additions & 0 deletions src/store/settings/slices/common.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
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 { GlobalSettings } from '@/types/settings';
import { setNamespace } from '@/utils/storeDebug';

import type { Guide, SidebarTabKey } from '../initialState';
import { AppSettingsState } from '../initialState';
import type { SettingsStore } from '../store';

const t = setNamespace('settings');

/**
* 设置操作
*/
export interface CommonAction {
importAppSettings: (settings: GlobalSettings) => void;
resetBaseSettings: () => void;
/**
* 重置设置
*/
resetSettings: () => void;
/**
* 设置全局设置
* @param settings - 全局设置
*/
setAppSettings: (settings: AppSettingsState) => void;
/**
* 设置部分配置设置
* @param settings - 部分配置设置
*/
setSettings: (settings: Partial<GlobalSettings>) => void;
/**
* 设置主题模式
* @param themeMode - 主题模式
*/
setThemeMode: (themeMode: ThemeMode) => void;
/**
* 切换侧边栏选项
* @param key - 选中的侧边栏选项
*/
switchSideBar: (key: SidebarTabKey) => void;
updateGuideState: (guide: Partial<Guide>) => void;
}

export const createCommonSlice: StateCreator<
SettingsStore,
[['zustand/devtools', never]],
[],
CommonAction
> = (set, get) => ({
importAppSettings: (importAppSettings) => {
const { setSettings } = get();
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { OPENAI_API_KEY: _, password: __, ...settings } = importAppSettings;

setSettings({
...settings,
// 如果用户存在用户头像,那么不做导入
avatar: !get().settings.avatar ? settings.avatar : get().settings.avatar,
});
},
resetBaseSettings: () => {
const settings = get().settings;

set({ settings: { ...settings, ...DEFAULT_BASE_SETTINGS } }, false, t('resetBaseSettings'));
},
resetDefaultAgent: () => {
const settings = produce(get().settings, (draft: GlobalSettings) => {
draft.defaultAgent = DEFAULT_AGENT;
});
set({ settings }, false, t('resetDefaultAgent'));
},
resetSettings: () => {
set({ settings: DEFAULT_SETTINGS }, false, t('resetSettings'));
},
setAppSettings: (settings) => {
set({ ...settings }, false, t('setAppSettings', settings));
},
setSettings: (settings) => {
const oldSetting = get().settings;
set({ settings: merge(oldSetting, settings) }, false, t('setSettings', settings));
},
setThemeMode: (themeMode) => {
get().setSettings({ themeMode });
},
switchSideBar: (key) => {
set({ sidebarKey: key }, false, t('switchSideBar', key));
},
updateGuideState: (guide) => {
set({ guide: { ...get().guide, ...guide } });
},
});
Loading

0 comments on commit 9ea2778

Please sign in to comment.