@lobehub/chat 0.96.8 → 0.97.0

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.
Files changed (65) hide show
  1. package/.github/workflows/release.yml +3 -0
  2. package/CHANGELOG.md +58 -0
  3. package/CODE_OF_CONDUCT.md +21 -21
  4. package/CONTRIBUTING.md +1 -1
  5. package/README.md +26 -9
  6. package/README.zh-CN.md +29 -12
  7. package/docs/Deploy-with-Azure-OpenAI.md +6 -6
  8. package/docs/Home.md +14 -2
  9. package/docs/Lighthouse.md +8 -8
  10. package/docs/Plugin-Development.md +3 -3
  11. package/docs/Plugin-Development.zh-CN.md +3 -3
  12. package/docs/Usage-Agents.md +195 -0
  13. package/docs/Usage-Agents.zh-CN.md +196 -0
  14. package/docs/Usage-Topics.md +31 -0
  15. package/docs/Usage-Topics.zh-CN.md +31 -0
  16. package/docs/_Sidebar.md +10 -2
  17. package/locales/en_US/chat.json +9 -2
  18. package/locales/ja_JP/chat.json +8 -1
  19. package/locales/ko_KR/chat.json +8 -1
  20. package/locales/ru_RU/chat.json +8 -1
  21. package/locales/zh_CN/chat.json +6 -1
  22. package/locales/zh_TW/chat.json +8 -1
  23. package/next.config.mjs +9 -1
  24. package/package.json +6 -7
  25. package/scripts/readmeWorkflow/const.ts +19 -0
  26. package/scripts/readmeWorkflow/index.ts +12 -0
  27. package/scripts/readmeWorkflow/syncAgentIndex.ts +47 -0
  28. package/scripts/readmeWorkflow/syncPluginIndex.ts +43 -0
  29. package/scripts/readmeWorkflow/utlis.ts +52 -0
  30. package/src/app/chat/(desktop)/features/ChatHeader.tsx +11 -1
  31. package/src/app/chat/{features/ChatInputContent/Footer/index.tsx → (desktop)/features/ChatInput/Footer.tsx} +1 -1
  32. package/src/app/chat/(desktop)/features/{ChatInput.tsx → ChatInput/index.tsx} +3 -2
  33. package/src/app/chat/(desktop)/features/SessionList.tsx +1 -1
  34. package/src/app/chat/(desktop)/features/SideBar.tsx +1 -1
  35. package/src/app/chat/(mobile)/features/ChatHeader.tsx +2 -6
  36. package/src/app/chat/(mobile)/features/TopicList.tsx +1 -1
  37. package/src/app/chat/features/ChatInputContent/ActionBar/ActionRight.tsx +1 -1
  38. package/src/app/chat/features/ChatInputContent/{Footer/SaveTopic.tsx → Topic/index.tsx} +13 -11
  39. package/src/app/chat/features/ChatInputContent/index.tsx +4 -4
  40. package/src/app/chat/features/Conversation/ChatList/Actions/Error.tsx +1 -7
  41. package/src/app/chat/features/Conversation/ChatList/Actions/User.tsx +0 -1
  42. package/src/app/chat/features/Conversation/ChatList/Actions/index.ts +48 -0
  43. package/src/app/chat/features/Conversation/ChatList/Messages/index.ts +27 -0
  44. package/src/app/chat/features/Conversation/ChatList/index.tsx +7 -29
  45. package/src/app/chat/features/{TopicListContent/SystemRole/Header.tsx → SidebarHeader/index.tsx} +9 -8
  46. package/src/app/chat/features/{TopicListContent/SystemRole → SystemRole}/index.tsx +45 -16
  47. package/src/app/chat/features/TopicListContent/Header.tsx +86 -0
  48. package/src/app/chat/features/TopicListContent/Topic/SkeletonList.tsx +1 -1
  49. package/src/app/chat/features/TopicListContent/TopicSearchBar/index.tsx +8 -3
  50. package/src/app/chat/features/TopicListContent/index.tsx +18 -7
  51. package/src/app/settings/features/SideBar/index.tsx +1 -1
  52. package/src/features/AgentInfo/index.tsx +3 -1
  53. package/src/features/AgentSetting/AgentMeta/index.tsx +5 -1
  54. package/src/features/PluginDevModal/MetaForm.tsx +4 -3
  55. package/src/layout/GlobalLayout/Locale.tsx +12 -2
  56. package/src/locales/default/chat.ts +6 -1
  57. package/src/store/global/initialState.ts +2 -0
  58. package/src/store/global/slices/common.ts +7 -0
  59. package/src/store/session/slices/chat/actions/message.ts +4 -1
  60. package/src/store/session/slices/chat/actions/topic.ts +46 -5
  61. package/src/store/session/slices/chat/selectors/index.ts +2 -1
  62. package/src/store/session/slices/chat/selectors/topic.ts +4 -0
  63. package/src/components/EmojiPicker/index.tsx +0 -56
  64. package/src/components/EmojiPicker/style.ts +0 -30
  65. /package/src/app/chat/features/{TopicListContent/SystemRole → SystemRole}/style.ts +0 -0
@@ -1,19 +1,23 @@
1
1
  import { ActionIcon, EditableMessage } from '@lobehub/ui';
2
2
  import { Skeleton } from 'antd';
3
3
  import { Edit } from 'lucide-react';
4
+ import { useRouter } from 'next/navigation';
4
5
  import { memo, useState } from 'react';
5
6
  import { useTranslation } from 'react-i18next';
6
7
  import { Flexbox } from 'react-layout-kit';
8
+ import useMergeState from 'use-merge-value';
7
9
 
8
10
  import AgentInfo from '@/features/AgentInfo';
11
+ import { useGlobalStore } from '@/store/global';
9
12
  import { useSessionChatInit, useSessionStore } from '@/store/session';
10
13
  import { agentSelectors } from '@/store/session/selectors';
14
+ import { pathString } from '@/utils/url';
11
15
 
12
- import Header from './Header';
16
+ import SidebarHeader from '../SidebarHeader';
13
17
  import { useStyles } from './style';
14
18
 
15
19
  const SystemRole = memo(() => {
16
- const [openModal, setOpenModal] = useState(false);
20
+ const router = useRouter();
17
21
  const [editing, setEditing] = useState(false);
18
22
  const { styles } = useStyles();
19
23
  const [systemRole, meta, updateAgentConfig] = useSessionStore((s) => [
@@ -21,30 +25,45 @@ const SystemRole = memo(() => {
21
25
  agentSelectors.currentAgentMeta(s),
22
26
  s.updateAgentConfig,
23
27
  ]);
28
+ const [showSystemRole, toggleSystemRole] = useGlobalStore((s) => [
29
+ s.preference.showSystemRole,
30
+ s.toggleSystemRole,
31
+ ]);
32
+ const [open, setOpen] = useMergeState(false, {
33
+ defaultValue: showSystemRole,
34
+ onChange: toggleSystemRole,
35
+ value: showSystemRole,
36
+ });
24
37
 
25
38
  const init = useSessionChatInit();
26
39
  const { t } = useTranslation('common');
40
+
41
+ const handleOpenWithEdit = () => {
42
+ if (!init) return;
43
+ setEditing(true);
44
+ setOpen(true);
45
+ };
46
+
47
+ const handleOpen = () => {
48
+ if (!init) return;
49
+ setEditing(false);
50
+ setOpen(true);
51
+ };
52
+
27
53
  return (
28
54
  <Flexbox height={'fit-content'}>
29
- <Header
55
+ <SidebarHeader
30
56
  actions={
31
- <ActionIcon
32
- icon={Edit}
33
- onClick={() => setOpenModal(true)}
34
- size={'small'}
35
- title={t('edit')}
36
- />
57
+ <ActionIcon icon={Edit} onClick={handleOpenWithEdit} size={'small'} title={t('edit')} />
37
58
  }
38
59
  title={t('settingAgent.prompt.title', { ns: 'setting' })}
39
60
  />
40
61
  <Flexbox
41
62
  className={styles.promptBox}
42
63
  height={200}
64
+ onClick={handleOpen}
43
65
  onDoubleClick={(e) => {
44
- if (e.altKey) {
45
- setOpenModal(true);
46
- setEditing(true);
47
- }
66
+ if (e.altKey) handleOpenWithEdit();
48
67
  }}
49
68
  >
50
69
  {!init ? (
@@ -59,13 +78,23 @@ const SystemRole = memo(() => {
59
78
  <EditableMessage
60
79
  classNames={{ markdown: styles.prompt }}
61
80
  editing={editing}
62
- model={{ extra: <AgentInfo meta={meta} style={{ marginBottom: 16 }} /> }}
81
+ model={{
82
+ extra: (
83
+ <AgentInfo
84
+ meta={meta}
85
+ onAvatarClick={() =>
86
+ router.push(pathString('/chat/settings', { hash: location.hash }))
87
+ }
88
+ style={{ marginBottom: 16 }}
89
+ />
90
+ ),
91
+ }}
63
92
  onChange={(e) => {
64
93
  updateAgentConfig({ systemRole: e });
65
94
  }}
66
95
  onEditingChange={setEditing}
67
- onOpenChange={setOpenModal}
68
- openModal={openModal}
96
+ onOpenChange={setOpen}
97
+ openModal={open}
69
98
  placeholder={`${t('settingAgent.prompt.placeholder', { ns: 'setting' })}...`}
70
99
  styles={{ markdown: systemRole ? {} : { opacity: 0.5 } }}
71
100
  text={{
@@ -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
- allowClear
15
- onChange={(e) => useSessionStore.setState({ topicSearchKeywords: e.target.value })}
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
- <Flexbox padding={16}>
10
- <TopicSearchBar />
11
- </Flexbox>
12
- <Flexbox gap={16} paddingInline={16} style={{ overflowY: 'auto', position: 'relative' }}>
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;
@@ -31,7 +31,7 @@ const SideBar = memo(() => {
31
31
  </div>
32
32
  </Flexbox>
33
33
  <UpgradeAlert />
34
- <Flexbox gap={2} style={{ paddingInline: 6 }}>
34
+ <Flexbox gap={2} style={{ paddingInline: 8 }}>
35
35
  <List />
36
36
  </Flexbox>
37
37
  </DraggablePanelBody>
@@ -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('@/components/EmojiPicker'), { ssr: false });
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('@/components/EmojiPicker'), { ssr: false });
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 Zh_CN from 'antd/locale/zh_CN';
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={Zh_CN}>{children}</ConfigProvider>;
45
+ return <ConfigProvider locale={locale}>{children}</ConfigProvider>;
36
46
  });
37
47
 
38
48
  // const Locale = memo<LocaleLayoutProps>((props) => (
@@ -46,11 +46,16 @@ export default {
46
46
  used: '使用',
47
47
  },
48
48
  topic: {
49
+ confirmRemoveAll: '即将删除全部话题,删除后将不可恢复,请谨慎操作。',
49
50
  confirmRemoveTopic: '即将删除该话题,删除后将不可恢复,请谨慎操作。',
51
+ confirmRemoveUnstarred: '即将删除未收藏话题,删除后将不可恢复,请谨慎操作。',
50
52
  defaultTitle: '默认话题',
53
+ openNewTopic: '开启新话题',
54
+ removeAll: '删除全部话题',
55
+ removeUnstarred: '删除未收藏话题',
51
56
  saveCurrentMessages: '将当前会话保存为话题',
52
57
  searchPlaceholder: '搜索话题...',
53
- title: '话题',
58
+ title: '话题列表',
54
59
  },
55
60
  translate: {
56
61
  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);
@@ -83,13 +83,16 @@ export const chatMessage: StateCreator<
83
83
  ChatMessageAction
84
84
  > = (set, get) => ({
85
85
  clearMessage: () => {
86
- const { dispatchMessage, activeTopicId, dispatchTopic } = get();
86
+ const { dispatchMessage, activeTopicId, dispatchTopic, toggleTopic } = get();
87
87
 
88
88
  dispatchMessage({ topicId: activeTopicId, type: 'resetMessages' });
89
89
 
90
90
  if (activeTopicId) {
91
91
  dispatchTopic({ id: activeTopicId, type: 'deleteChatTopic' });
92
92
  }
93
+
94
+ // after remove topic , go back to default topic
95
+ toggleTopic();
93
96
  },
94
97
 
95
98
  coreProcessMessage: async (messages, userMessageId) => {
@@ -18,11 +18,20 @@ export interface ChatTopicAction {
18
18
  * @param payload - 要分发的主题
19
19
  */
20
20
  dispatchTopic: (payload: ChatTopicDispatch) => void;
21
+ openNewTopicOrSaveTopic: () => void;
22
+ /**
23
+ * 移出所有话题
24
+ */
25
+ removeAllTopic: () => void;
21
26
  /**
22
27
  * 移除话题
23
28
  * @param id
24
29
  */
25
30
  removeTopic: (id: string) => void;
31
+ /**
32
+ * 移出所有未标记的话题
33
+ */
34
+ removeUnstarredTopic: () => void;
26
35
  /**
27
36
  * 将当前消息保存为主题
28
37
  */
@@ -54,16 +63,33 @@ export const chatTopic: StateCreator<
54
63
 
55
64
  get().dispatchSession({ id: activeId, topics, type: 'updateSessionTopic' });
56
65
  },
66
+ openNewTopicOrSaveTopic: () => {
67
+ const { toggleTopic, saveToTopic, activeTopicId } = get();
68
+ const hasTopic = !!activeTopicId;
69
+
70
+ if (hasTopic) toggleTopic();
71
+ else {
72
+ saveToTopic();
73
+ }
74
+ },
75
+ removeAllTopic: () => {
76
+ const { removeTopic, toggleTopic } = get();
77
+ const topics = topicSelectors.currentTopics(get());
78
+
79
+ for (const { id } of topics) {
80
+ removeTopic(id);
81
+ }
82
+
83
+ // 切换到默认 topic
84
+ toggleTopic();
85
+ },
57
86
  removeTopic: (id) => {
58
87
  const { dispatchTopic, dispatchMessage, toggleTopic } = get();
59
88
 
60
89
  // 移除关联的 message
61
90
  const messages = topicSelectors.getTopicMessages(id)(get());
62
91
  for (const m of messages) {
63
- dispatchMessage({
64
- id: m.id,
65
- type: 'deleteMessage',
66
- });
92
+ dispatchMessage({ id: m.id, type: 'deleteMessage' });
67
93
  }
68
94
 
69
95
  // 最后移除 topic
@@ -72,14 +98,28 @@ export const chatTopic: StateCreator<
72
98
  // 切换到默认 topic
73
99
  toggleTopic();
74
100
  },
101
+ removeUnstarredTopic: () => {
102
+ const { removeTopic, toggleTopic } = get();
103
+ const topics = topicSelectors.currentTopics(get());
104
+
105
+ for (const { id, favorite } of topics) {
106
+ if (!favorite) removeTopic(id);
107
+ }
108
+
109
+ // 切换到默认 topic
110
+ toggleTopic();
111
+ },
75
112
  saveToTopic: async () => {
76
113
  const session = sessionSelectors.currentSession(get());
77
114
  if (!session) return;
78
115
 
79
116
  const { dispatchTopic, dispatchMessage, updateTopicLoading } = get();
80
- // 获取当前的 messages
117
+ // get current messages
81
118
  const messages = chatSelectors.currentChats(get());
82
119
 
120
+ // if there is no message, stop saving
121
+ if (messages.length === 0) return;
122
+
83
123
  const topicId = nanoid();
84
124
 
85
125
  const defaultTitle = '默认话题';
@@ -124,6 +164,7 @@ export const chatTopic: StateCreator<
124
164
  toggleTopic: (id) => {
125
165
  set({ activeTopicId: id }, false, t('toggleTopic'));
126
166
  },
167
+
127
168
  updateTopicLoading: (id) => {
128
169
  set({ topicLoadingId: id }, false, t('updateTopicLoading'));
129
170
  },
@@ -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;