@lobehub/chat 0.96.8 → 0.96.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/release.yml +3 -0
- package/CHANGELOG.md +25 -0
- package/CODE_OF_CONDUCT.md +21 -21
- package/CONTRIBUTING.md +1 -1
- package/README.md +26 -9
- package/README.zh-CN.md +29 -12
- package/docs/Deploy-with-Azure-OpenAI.md +6 -6
- package/docs/Home.md +14 -2
- package/docs/Lighthouse.md +8 -8
- package/docs/Plugin-Development.md +3 -3
- package/docs/Plugin-Development.zh-CN.md +3 -3
- package/docs/Usage-Agents.md +195 -0
- package/docs/Usage-Agents.zh-CN.md +196 -0
- package/docs/Usage-Topics.md +31 -0
- package/docs/Usage-Topics.zh-CN.md +31 -0
- package/docs/_Sidebar.md +10 -2
- package/locales/en_US/chat.json +8 -2
- package/locales/ja_JP/chat.json +7 -1
- package/locales/ko_KR/chat.json +7 -1
- package/locales/ru_RU/chat.json +7 -1
- package/locales/zh_CN/chat.json +5 -1
- package/locales/zh_TW/chat.json +7 -1
- package/next.config.mjs +9 -1
- package/package.json +6 -7
- package/scripts/readmeWorkflow/const.ts +19 -0
- package/scripts/readmeWorkflow/index.ts +12 -0
- package/scripts/readmeWorkflow/syncAgentIndex.ts +47 -0
- package/scripts/readmeWorkflow/syncPluginIndex.ts +43 -0
- package/scripts/readmeWorkflow/utlis.ts +52 -0
- package/src/app/chat/(desktop)/features/ChatHeader.tsx +11 -1
- package/src/app/chat/(desktop)/features/SessionList.tsx +1 -1
- package/src/app/chat/(desktop)/features/SideBar.tsx +1 -1
- package/src/app/chat/(mobile)/features/ChatHeader.tsx +2 -6
- package/src/app/chat/(mobile)/features/TopicList.tsx +1 -1
- package/src/app/chat/features/Conversation/ChatList/Actions/Error.tsx +1 -7
- package/src/app/chat/features/Conversation/ChatList/Actions/User.tsx +0 -1
- package/src/app/chat/features/Conversation/ChatList/Actions/index.ts +48 -0
- package/src/app/chat/features/Conversation/ChatList/Messages/index.ts +27 -0
- package/src/app/chat/features/Conversation/ChatList/index.tsx +7 -29
- package/src/app/chat/features/{TopicListContent/SystemRole/Header.tsx → SidebarHeader/index.tsx} +9 -8
- package/src/app/chat/features/{TopicListContent/SystemRole → SystemRole}/index.tsx +45 -16
- package/src/app/chat/features/TopicListContent/Header.tsx +86 -0
- package/src/app/chat/features/TopicListContent/Topic/SkeletonList.tsx +1 -1
- package/src/app/chat/features/TopicListContent/TopicSearchBar/index.tsx +8 -3
- package/src/app/chat/features/TopicListContent/index.tsx +18 -7
- package/src/app/settings/features/SideBar/index.tsx +1 -1
- package/src/features/AgentInfo/index.tsx +3 -1
- package/src/features/AgentSetting/AgentMeta/index.tsx +5 -1
- package/src/features/PluginDevModal/MetaForm.tsx +4 -3
- package/src/layout/GlobalLayout/Locale.tsx +12 -2
- package/src/locales/default/chat.ts +5 -1
- package/src/store/global/initialState.ts +2 -0
- package/src/store/global/slices/common.ts +7 -0
- package/src/store/session/slices/chat/actions/topic.ts +30 -0
- package/src/store/session/slices/chat/selectors/index.ts +2 -1
- package/src/store/session/slices/chat/selectors/topic.ts +4 -0
- package/src/components/EmojiPicker/index.tsx +0 -56
- package/src/components/EmojiPicker/style.ts +0 -30
- /package/src/app/chat/features/{TopicListContent/SystemRole → SystemRole}/style.ts +0 -0
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { ActionIcon, Icon } from '@lobehub/ui';
|
|
2
|
+
import { App, Dropdown, MenuProps } from 'antd';
|
|
3
|
+
import { MoreHorizontal, Search, Trash } from 'lucide-react';
|
|
4
|
+
import { memo, useMemo, useState } from 'react';
|
|
5
|
+
import { useTranslation } from 'react-i18next';
|
|
6
|
+
import { Flexbox } from 'react-layout-kit';
|
|
7
|
+
|
|
8
|
+
import { useSessionStore } from '@/store/session';
|
|
9
|
+
import { topicSelectors } from '@/store/session/selectors';
|
|
10
|
+
|
|
11
|
+
import SidebarHeader from '../SidebarHeader';
|
|
12
|
+
import TopicSearchBar from './TopicSearchBar';
|
|
13
|
+
|
|
14
|
+
const Header = memo(() => {
|
|
15
|
+
const { t } = useTranslation('chat');
|
|
16
|
+
const [topicLength, removeUnstarredTopic, removeAllTopic] = useSessionStore((s) => [
|
|
17
|
+
topicSelectors.currentTopicLength(s),
|
|
18
|
+
s.removeUnstarredTopic,
|
|
19
|
+
s.removeAllTopic,
|
|
20
|
+
]);
|
|
21
|
+
|
|
22
|
+
const [showSearch, setShowSearch] = useState(false);
|
|
23
|
+
const { modal } = App.useApp();
|
|
24
|
+
|
|
25
|
+
const items = useMemo<MenuProps['items']>(
|
|
26
|
+
() => [
|
|
27
|
+
{
|
|
28
|
+
icon: <Icon icon={Trash} />,
|
|
29
|
+
key: 'deleteUnstarred',
|
|
30
|
+
label: t('topic.removeUnstarred'),
|
|
31
|
+
onClick: () => {
|
|
32
|
+
modal.confirm({
|
|
33
|
+
centered: true,
|
|
34
|
+
okButtonProps: { danger: true },
|
|
35
|
+
onOk: removeUnstarredTopic,
|
|
36
|
+
title: t('topic.confirmRemoveUnstarred', { ns: 'chat' }),
|
|
37
|
+
});
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
danger: true,
|
|
42
|
+
icon: <Icon icon={Trash} />,
|
|
43
|
+
key: 'deleteAll',
|
|
44
|
+
label: t('topic.removeAll'),
|
|
45
|
+
onClick: () => {
|
|
46
|
+
modal.confirm({
|
|
47
|
+
centered: true,
|
|
48
|
+
okButtonProps: { danger: true },
|
|
49
|
+
onOk: removeAllTopic,
|
|
50
|
+
title: t('topic.confirmRemoveAll', { ns: 'chat' }),
|
|
51
|
+
});
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
],
|
|
55
|
+
[],
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
return showSearch ? (
|
|
59
|
+
<Flexbox padding={'12px 16px 4px'}>
|
|
60
|
+
<TopicSearchBar onClear={() => setShowSearch(false)} />
|
|
61
|
+
</Flexbox>
|
|
62
|
+
) : (
|
|
63
|
+
<SidebarHeader
|
|
64
|
+
actions={
|
|
65
|
+
<>
|
|
66
|
+
<ActionIcon icon={Search} onClick={() => setShowSearch(true)} size={'small'} />
|
|
67
|
+
<Dropdown
|
|
68
|
+
arrow={false}
|
|
69
|
+
menu={{
|
|
70
|
+
items: items,
|
|
71
|
+
onClick: ({ domEvent }) => {
|
|
72
|
+
domEvent.stopPropagation();
|
|
73
|
+
},
|
|
74
|
+
}}
|
|
75
|
+
trigger={['click']}
|
|
76
|
+
>
|
|
77
|
+
<ActionIcon icon={MoreHorizontal} size={'small'} />
|
|
78
|
+
</Dropdown>
|
|
79
|
+
</>
|
|
80
|
+
}
|
|
81
|
+
title={`${t('topic.title')} ${topicLength > 1 ? topicLength + 1 : ''}`}
|
|
82
|
+
/>
|
|
83
|
+
);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
export default Header;
|
|
@@ -4,7 +4,7 @@ import { Flexbox } from 'react-layout-kit';
|
|
|
4
4
|
|
|
5
5
|
const SkeletonList = memo(() => {
|
|
6
6
|
return (
|
|
7
|
-
<Flexbox gap={8} style={{ marginTop: 8 }}>
|
|
7
|
+
<Flexbox gap={8} paddingInline={10} style={{ marginTop: 8 }}>
|
|
8
8
|
{Array.from({ length: 8 }).map((_, i) => (
|
|
9
9
|
<Skeleton
|
|
10
10
|
active
|
|
@@ -5,14 +5,19 @@ import { useTranslation } from 'react-i18next';
|
|
|
5
5
|
|
|
6
6
|
import { useSessionStore } from '@/store/session';
|
|
7
7
|
|
|
8
|
-
const TopicSearchBar = memo(() => {
|
|
8
|
+
const TopicSearchBar = memo<{ onClear?: () => void }>(({ onClear }) => {
|
|
9
9
|
const { t } = useTranslation('chat');
|
|
10
10
|
const [keywords] = useSessionStore((s) => [s.topicSearchKeywords]);
|
|
11
11
|
const { mobile } = useResponsive();
|
|
12
12
|
return (
|
|
13
13
|
<SearchBar
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
autoFocus
|
|
15
|
+
onBlur={() => {
|
|
16
|
+
if (keywords === '') onClear?.();
|
|
17
|
+
}}
|
|
18
|
+
onChange={(e) => {
|
|
19
|
+
useSessionStore.setState({ topicSearchKeywords: e.target.value });
|
|
20
|
+
}}
|
|
16
21
|
placeholder={t('topic.searchPlaceholder')}
|
|
17
22
|
spotlight={!mobile}
|
|
18
23
|
type={mobile ? 'block' : 'ghost'}
|
|
@@ -1,19 +1,30 @@
|
|
|
1
|
+
import { memo } from 'react';
|
|
1
2
|
import { Flexbox } from 'react-layout-kit';
|
|
2
3
|
|
|
4
|
+
import TopicSearchBar from '@/app/chat/features/TopicListContent/TopicSearchBar';
|
|
5
|
+
|
|
6
|
+
import Header from './Header';
|
|
3
7
|
import { Topic } from './Topic';
|
|
4
|
-
import TopicSearchBar from './TopicSearchBar';
|
|
5
8
|
|
|
6
|
-
const TopicListContent = () => {
|
|
9
|
+
const TopicListContent = memo<{ mobile?: boolean }>(({ mobile }) => {
|
|
7
10
|
return (
|
|
8
11
|
<Flexbox height={'100%'} style={{ overflow: 'hidden' }}>
|
|
9
|
-
|
|
10
|
-
<
|
|
11
|
-
|
|
12
|
-
|
|
12
|
+
{mobile ? (
|
|
13
|
+
<Flexbox padding={'12px 16px'}>
|
|
14
|
+
<TopicSearchBar />
|
|
15
|
+
</Flexbox>
|
|
16
|
+
) : (
|
|
17
|
+
<Header />
|
|
18
|
+
)}
|
|
19
|
+
<Flexbox
|
|
20
|
+
gap={16}
|
|
21
|
+
paddingInline={mobile ? 16 : 8}
|
|
22
|
+
style={{ overflowY: 'auto', paddingTop: 6, position: 'relative' }}
|
|
23
|
+
>
|
|
13
24
|
<Topic />
|
|
14
25
|
</Flexbox>
|
|
15
26
|
</Flexbox>
|
|
16
27
|
);
|
|
17
|
-
};
|
|
28
|
+
});
|
|
18
29
|
|
|
19
30
|
export default TopicListContent;
|
|
@@ -26,11 +26,12 @@ const useStyles = createStyles(({ css, token, stylish }) => ({
|
|
|
26
26
|
|
|
27
27
|
export interface AgentInfoProps {
|
|
28
28
|
meta?: MetaData;
|
|
29
|
+
onAvatarClick?: () => void;
|
|
29
30
|
style?: CSSProperties;
|
|
30
31
|
systemRole?: string;
|
|
31
32
|
}
|
|
32
33
|
|
|
33
|
-
const AgentInfo = memo<AgentInfoProps>(({ systemRole, style, meta }) => {
|
|
34
|
+
const AgentInfo = memo<AgentInfoProps>(({ systemRole, style, meta, onAvatarClick }) => {
|
|
34
35
|
const { styles, theme } = useStyles();
|
|
35
36
|
|
|
36
37
|
if (!meta) return;
|
|
@@ -43,6 +44,7 @@ const AgentInfo = memo<AgentInfoProps>(({ systemRole, style, meta }) => {
|
|
|
43
44
|
avatar={meta.avatar}
|
|
44
45
|
background={meta.backgroundColor || theme.colorFillTertiary}
|
|
45
46
|
className={styles.avatar}
|
|
47
|
+
onClick={onAvatarClick}
|
|
46
48
|
size={100}
|
|
47
49
|
/>
|
|
48
50
|
)}
|
|
@@ -8,6 +8,7 @@ import { memo, useMemo } from 'react';
|
|
|
8
8
|
import { useTranslation } from 'react-i18next';
|
|
9
9
|
|
|
10
10
|
import { FORM_STYLE } from '@/const/layoutTokens';
|
|
11
|
+
import { settingsSelectors, useGlobalStore } from '@/store/global';
|
|
11
12
|
|
|
12
13
|
import { useStore } from '../store';
|
|
13
14
|
import { SessionLoadingState } from '../store/initialState';
|
|
@@ -15,16 +16,18 @@ import AutoGenerateInput from './AutoGenerateInput';
|
|
|
15
16
|
import AutoGenerateSelect from './AutoGenerateSelect';
|
|
16
17
|
import BackgroundSwatches from './BackgroundSwatches';
|
|
17
18
|
|
|
18
|
-
const EmojiPicker = dynamic(() => import('
|
|
19
|
+
const EmojiPicker = dynamic(() => import('@lobehub/ui/es/EmojiPicker'), { ssr: false });
|
|
19
20
|
|
|
20
21
|
const AgentMeta = memo(() => {
|
|
21
22
|
const { t } = useTranslation('setting');
|
|
23
|
+
|
|
22
24
|
const [hasSystemRole, updateMeta, autocompleteMeta, autocompleteAllMeta] = useStore((s) => [
|
|
23
25
|
!!s.config.systemRole,
|
|
24
26
|
s.setAgentMeta,
|
|
25
27
|
s.autocompleteMeta,
|
|
26
28
|
s.autocompleteAllMeta,
|
|
27
29
|
]);
|
|
30
|
+
const locale = useGlobalStore(settingsSelectors.currentLanguage);
|
|
28
31
|
const loading = useStore((s) => s.autocompleteLoading);
|
|
29
32
|
const meta = useStore((s) => s.meta, isEqual);
|
|
30
33
|
|
|
@@ -77,6 +80,7 @@ const AgentMeta = memo(() => {
|
|
|
77
80
|
children: (
|
|
78
81
|
<EmojiPicker
|
|
79
82
|
backgroundColor={meta.backgroundColor}
|
|
83
|
+
locale={locale}
|
|
80
84
|
onChange={(avatar) => updateMeta({ avatar })}
|
|
81
85
|
value={meta.avatar}
|
|
82
86
|
/>
|
|
@@ -4,13 +4,14 @@ import dynamic from 'next/dynamic';
|
|
|
4
4
|
import { memo } from 'react';
|
|
5
5
|
import { useTranslation } from 'react-i18next';
|
|
6
6
|
|
|
7
|
+
import { settingsSelectors, useGlobalStore } from '@/store/global';
|
|
7
8
|
import { pluginSelectors, usePluginStore } from '@/store/plugin';
|
|
8
9
|
|
|
9
|
-
const EmojiPicker = dynamic(() => import('
|
|
10
|
+
const EmojiPicker = dynamic(() => import('@lobehub/ui/es/EmojiPicker'), { ssr: false });
|
|
10
11
|
|
|
11
12
|
const MetaForm = memo<{ form: FormInstance; mode?: 'edit' | 'create' }>(({ form, mode }) => {
|
|
12
13
|
const isEditMode = mode === 'edit';
|
|
13
|
-
|
|
14
|
+
const locale = useGlobalStore(settingsSelectors.currentLanguage);
|
|
14
15
|
const { t } = useTranslation('plugin');
|
|
15
16
|
const [plugins] = usePluginStore((s) => [pluginSelectors.pluginList(s).map((i) => i.identifier)]);
|
|
16
17
|
|
|
@@ -65,7 +66,7 @@ const MetaForm = memo<{ form: FormInstance; mode?: 'edit' | 'create' }>(({ form,
|
|
|
65
66
|
name: 'homepage',
|
|
66
67
|
},
|
|
67
68
|
{
|
|
68
|
-
children: <EmojiPicker defaultAvatar={'🧩'} />,
|
|
69
|
+
children: <EmojiPicker defaultAvatar={'🧩'} locale={locale} />,
|
|
69
70
|
desc: t('dev.meta.avatar.desc'),
|
|
70
71
|
label: t('dev.meta.avatar.label'),
|
|
71
72
|
name: ['meta', 'avatar'],
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ConfigProvider } from 'antd';
|
|
2
|
-
import
|
|
2
|
+
import defaultLocale from 'antd/locale/en_US';
|
|
3
3
|
import { PropsWithChildren, memo, useState } from 'react';
|
|
4
4
|
|
|
5
5
|
import { createI18nNext } from '@/locales/create';
|
|
@@ -12,6 +12,7 @@ interface LocaleLayoutProps extends PropsWithChildren {
|
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
const InnerLocale = memo<LocaleLayoutProps>(({ children, lang }) => {
|
|
15
|
+
const [locale, setLocale] = useState(defaultLocale);
|
|
15
16
|
const [i18n] = useState(createI18nNext(lang));
|
|
16
17
|
|
|
17
18
|
// if run on server side, init i18n instance everytime
|
|
@@ -26,13 +27,22 @@ const InnerLocale = memo<LocaleLayoutProps>(({ children, lang }) => {
|
|
|
26
27
|
});
|
|
27
28
|
}
|
|
28
29
|
|
|
30
|
+
const getLocale = async (localeName: string) => {
|
|
31
|
+
setLocale((await import(`antd/locale/${localeName}.js`)) as any);
|
|
32
|
+
};
|
|
33
|
+
|
|
29
34
|
useOnFinishHydrationGlobal((s) => {
|
|
30
35
|
if (s.settings.language === 'auto') {
|
|
31
36
|
switchLang('auto');
|
|
32
37
|
}
|
|
38
|
+
|
|
39
|
+
if (lang?.includes('-') && lang !== 'en-US') {
|
|
40
|
+
const localeName = lang?.replace('-', '_');
|
|
41
|
+
getLocale(localeName);
|
|
42
|
+
}
|
|
33
43
|
}, []);
|
|
34
44
|
|
|
35
|
-
return <ConfigProvider locale={
|
|
45
|
+
return <ConfigProvider locale={locale}>{children}</ConfigProvider>;
|
|
36
46
|
});
|
|
37
47
|
|
|
38
48
|
// const Locale = memo<LocaleLayoutProps>((props) => (
|
|
@@ -46,11 +46,15 @@ export default {
|
|
|
46
46
|
used: '使用',
|
|
47
47
|
},
|
|
48
48
|
topic: {
|
|
49
|
+
confirmRemoveAll: '即将删除全部话题,删除后将不可恢复,请谨慎操作。',
|
|
49
50
|
confirmRemoveTopic: '即将删除该话题,删除后将不可恢复,请谨慎操作。',
|
|
51
|
+
confirmRemoveUnstarred: '即将删除未收藏话题,删除后将不可恢复,请谨慎操作。',
|
|
50
52
|
defaultTitle: '默认话题',
|
|
53
|
+
removeAll: '删除全部话题',
|
|
54
|
+
removeUnstarred: '删除未收藏话题',
|
|
51
55
|
saveCurrentMessages: '将当前会话保存为话题',
|
|
52
56
|
searchPlaceholder: '搜索话题...',
|
|
53
|
-
title: '
|
|
57
|
+
title: '话题列表',
|
|
54
58
|
},
|
|
55
59
|
translate: {
|
|
56
60
|
clear: '删除翻译',
|
|
@@ -26,6 +26,7 @@ export interface GlobalPreference {
|
|
|
26
26
|
sessionsWidth: number;
|
|
27
27
|
showChatSideBar?: boolean;
|
|
28
28
|
showSessionPanel?: boolean;
|
|
29
|
+
showSystemRole?: boolean;
|
|
29
30
|
}
|
|
30
31
|
|
|
31
32
|
export interface GlobalState {
|
|
@@ -54,6 +55,7 @@ export const initialState: GlobalState = {
|
|
|
54
55
|
sessionsWidth: 320,
|
|
55
56
|
showChatSideBar: true,
|
|
56
57
|
showSessionPanel: true,
|
|
58
|
+
showSystemRole: false,
|
|
57
59
|
},
|
|
58
60
|
settings: DEFAULT_SETTINGS,
|
|
59
61
|
settingsTab: SettingsTabs.Common,
|
|
@@ -24,6 +24,7 @@ export interface CommonAction {
|
|
|
24
24
|
switchSideBar: (key: SidebarTabKey) => void;
|
|
25
25
|
toggleChatSideBar: (visible?: boolean) => void;
|
|
26
26
|
toggleMobileTopic: (visible?: boolean) => void;
|
|
27
|
+
toggleSystemRole: (visible?: boolean) => void;
|
|
27
28
|
updateGuideState: (guide: Partial<Guide>) => void;
|
|
28
29
|
updatePreference: (preference: Partial<GlobalPreference>, action?: string) => void;
|
|
29
30
|
useCheckLatestVersion: () => SWRResponse<string>;
|
|
@@ -50,6 +51,12 @@ export const createCommonSlice: StateCreator<
|
|
|
50
51
|
|
|
51
52
|
get().updatePreference({ mobileShowTopic }, t('toggleMobileTopic', newValue) as string);
|
|
52
53
|
},
|
|
54
|
+
toggleSystemRole: (newValue) => {
|
|
55
|
+
const showSystemRole =
|
|
56
|
+
typeof newValue === 'boolean' ? newValue : !get().preference.mobileShowTopic;
|
|
57
|
+
|
|
58
|
+
get().updatePreference({ showSystemRole }, t('toggleMobileTopic', newValue) as string);
|
|
59
|
+
},
|
|
53
60
|
updateGuideState: (guide) => {
|
|
54
61
|
const { updatePreference } = get();
|
|
55
62
|
const nextGuide = merge(get().preference.guide, guide);
|
|
@@ -18,11 +18,19 @@ export interface ChatTopicAction {
|
|
|
18
18
|
* @param payload - 要分发的主题
|
|
19
19
|
*/
|
|
20
20
|
dispatchTopic: (payload: ChatTopicDispatch) => void;
|
|
21
|
+
/**
|
|
22
|
+
* 移出所有话题
|
|
23
|
+
*/
|
|
24
|
+
removeAllTopic: () => void;
|
|
21
25
|
/**
|
|
22
26
|
* 移除话题
|
|
23
27
|
* @param id
|
|
24
28
|
*/
|
|
25
29
|
removeTopic: (id: string) => void;
|
|
30
|
+
/**
|
|
31
|
+
* 移出所有未标记的话题
|
|
32
|
+
*/
|
|
33
|
+
removeUnstarredTopic: () => void;
|
|
26
34
|
/**
|
|
27
35
|
* 将当前消息保存为主题
|
|
28
36
|
*/
|
|
@@ -54,6 +62,17 @@ export const chatTopic: StateCreator<
|
|
|
54
62
|
|
|
55
63
|
get().dispatchSession({ id: activeId, topics, type: 'updateSessionTopic' });
|
|
56
64
|
},
|
|
65
|
+
removeAllTopic: () => {
|
|
66
|
+
const { removeTopic, toggleTopic } = get();
|
|
67
|
+
const topics = topicSelectors.currentTopics(get());
|
|
68
|
+
|
|
69
|
+
for (const { id } of topics) {
|
|
70
|
+
removeTopic(id);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// 切换到默认 topic
|
|
74
|
+
toggleTopic();
|
|
75
|
+
},
|
|
57
76
|
removeTopic: (id) => {
|
|
58
77
|
const { dispatchTopic, dispatchMessage, toggleTopic } = get();
|
|
59
78
|
|
|
@@ -72,6 +91,17 @@ export const chatTopic: StateCreator<
|
|
|
72
91
|
// 切换到默认 topic
|
|
73
92
|
toggleTopic();
|
|
74
93
|
},
|
|
94
|
+
removeUnstarredTopic: () => {
|
|
95
|
+
const { removeTopic, toggleTopic } = get();
|
|
96
|
+
const topics = topicSelectors.currentTopics(get());
|
|
97
|
+
|
|
98
|
+
for (const { id, favorite } of topics) {
|
|
99
|
+
if (!favorite) removeTopic(id);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// 切换到默认 topic
|
|
103
|
+
toggleTopic();
|
|
104
|
+
},
|
|
75
105
|
saveToTopic: async () => {
|
|
76
106
|
const session = sessionSelectors.currentSession(get());
|
|
77
107
|
if (!session) return;
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
getFunctionMessageProps,
|
|
8
8
|
getMessageById,
|
|
9
9
|
} from './chat';
|
|
10
|
-
import { currentTopics, getTopicMessages } from './topic';
|
|
10
|
+
import { currentTopicLength, currentTopics, getTopicMessages } from './topic';
|
|
11
11
|
|
|
12
12
|
export const chatSelectors = {
|
|
13
13
|
chatsMessageString,
|
|
@@ -20,6 +20,7 @@ export const chatSelectors = {
|
|
|
20
20
|
};
|
|
21
21
|
|
|
22
22
|
export const topicSelectors = {
|
|
23
|
+
currentTopicLength,
|
|
23
24
|
currentTopics,
|
|
24
25
|
getTopicMessages,
|
|
25
26
|
};
|
|
@@ -18,6 +18,10 @@ export const currentTopics = (s: SessionStore): ChatTopic[] => {
|
|
|
18
18
|
return [...favTopics, ...defaultTopics];
|
|
19
19
|
};
|
|
20
20
|
|
|
21
|
+
export const currentTopicLength = (s: SessionStore): number => {
|
|
22
|
+
return currentTopics(s).length;
|
|
23
|
+
};
|
|
24
|
+
|
|
21
25
|
export const getTopicMessages = (topicId: string) => (s: SessionStore) => {
|
|
22
26
|
const session = sessionSelectors.currentSession(s);
|
|
23
27
|
if (!session) return [];
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
import data from '@emoji-mart/data';
|
|
2
|
-
import i18n from '@emoji-mart/data/i18n/zh.json';
|
|
3
|
-
import Picker from '@emoji-mart/react';
|
|
4
|
-
import { Avatar } from '@lobehub/ui';
|
|
5
|
-
import { Popover } from 'antd';
|
|
6
|
-
import { memo } from 'react';
|
|
7
|
-
import useMergeState from 'use-merge-value';
|
|
8
|
-
|
|
9
|
-
import { DEFAULT_AVATAR, DEFAULT_BACKGROUND_COLOR } from '@/const/meta';
|
|
10
|
-
|
|
11
|
-
import { useStyles } from './style';
|
|
12
|
-
|
|
13
|
-
export interface EmojiPickerProps {
|
|
14
|
-
backgroundColor?: string;
|
|
15
|
-
defaultAvatar?: string;
|
|
16
|
-
onChange?: (emoji: string) => void;
|
|
17
|
-
value?: string;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
const EmojiPicker = memo<EmojiPickerProps>(
|
|
21
|
-
({ value, defaultAvatar, backgroundColor = DEFAULT_BACKGROUND_COLOR, onChange }) => {
|
|
22
|
-
const { styles } = useStyles();
|
|
23
|
-
|
|
24
|
-
const [ava, setAva] = useMergeState(DEFAULT_AVATAR, {
|
|
25
|
-
defaultValue: defaultAvatar,
|
|
26
|
-
onChange,
|
|
27
|
-
value,
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
return (
|
|
31
|
-
<Popover
|
|
32
|
-
content={
|
|
33
|
-
<div className={styles.picker}>
|
|
34
|
-
<Picker
|
|
35
|
-
data={data}
|
|
36
|
-
i18n={i18n}
|
|
37
|
-
locale={'zh'}
|
|
38
|
-
onEmojiSelect={(e: any) => setAva(e.native)}
|
|
39
|
-
skinTonePosition={'none'}
|
|
40
|
-
theme={'auto'}
|
|
41
|
-
/>
|
|
42
|
-
</div>
|
|
43
|
-
}
|
|
44
|
-
placement={'left'}
|
|
45
|
-
rootClassName={styles.popover}
|
|
46
|
-
trigger={'click'}
|
|
47
|
-
>
|
|
48
|
-
<div className={styles.avatar} style={{ width: 'fit-content' }}>
|
|
49
|
-
<Avatar avatar={ava} background={backgroundColor} size={44} />
|
|
50
|
-
</div>
|
|
51
|
-
</Popover>
|
|
52
|
-
);
|
|
53
|
-
},
|
|
54
|
-
);
|
|
55
|
-
|
|
56
|
-
export default EmojiPicker;
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import { createStyles } from 'antd-style';
|
|
2
|
-
import chroma from 'chroma-js';
|
|
3
|
-
|
|
4
|
-
export const useStyles = createStyles(({ css, token, prefixCls }) => ({
|
|
5
|
-
avatar: css`
|
|
6
|
-
border-radius: 50%;
|
|
7
|
-
transition:
|
|
8
|
-
scale 400ms ${token.motionEaseOut},
|
|
9
|
-
box-shadow 100ms ${token.motionEaseOut};
|
|
10
|
-
|
|
11
|
-
&:hover {
|
|
12
|
-
box-shadow: 0 0 0 3px ${token.colorText};
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
&:active {
|
|
16
|
-
scale: 0.8;
|
|
17
|
-
}
|
|
18
|
-
`,
|
|
19
|
-
picker: css`
|
|
20
|
-
em-emoji-picker {
|
|
21
|
-
--rgb-accent: ${chroma(token.colorPrimary) .rgb() .join(',')};
|
|
22
|
-
--shadow: none;
|
|
23
|
-
}
|
|
24
|
-
`,
|
|
25
|
-
popover: css`
|
|
26
|
-
.${prefixCls}-popover-inner {
|
|
27
|
-
padding: 0;
|
|
28
|
-
}
|
|
29
|
-
`,
|
|
30
|
-
}));
|
|
File without changes
|