@lobehub/chat 0.28.0 → 0.30.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 (40) hide show
  1. package/.eslintrc.js +3 -0
  2. package/CHANGELOG.md +54 -0
  3. package/package.json +1 -1
  4. package/src/const/message.ts +2 -0
  5. package/src/features/SideBar/BottomAction.tsx +1 -1
  6. package/src/helpers/export.ts +62 -0
  7. package/src/hooks/useExportConfig.ts +3 -66
  8. package/src/locales/default/common.ts +2 -0
  9. package/src/locales/default/setting.ts +4 -3
  10. package/src/migrations/index.ts +2 -2
  11. package/src/pages/chat/SessionList/List/SessionItem.tsx +44 -52
  12. package/src/pages/chat/SessionList/List/style.ts +3 -0
  13. package/src/pages/chat/[id]/Config/SideBar.tsx +1 -1
  14. package/src/pages/chat/[id]/Config/Topic/DefaultContent.tsx +29 -0
  15. package/src/pages/chat/[id]/Config/Topic/TopicContent.tsx +151 -0
  16. package/src/pages/chat/[id]/Config/Topic/TopicItem.tsx +70 -0
  17. package/src/pages/chat/[id]/Config/{Topic.tsx → Topic/index.tsx} +1 -8
  18. package/src/pages/chat/[id]/Config/index.tsx +3 -5
  19. package/src/pages/chat/[id]/Conversation/Input/index.tsx +7 -10
  20. package/src/pages/chat/[id]/Header.tsx +3 -2
  21. package/src/pages/chat/[id]/edit/Header.tsx +14 -1
  22. package/src/pages/setting/SettingForm.tsx +34 -29
  23. package/src/store/session/slices/agentConfig/action.ts +0 -11
  24. package/src/store/session/slices/agentConfig/initialState.ts +0 -4
  25. package/src/store/session/slices/chat/actions/message.ts +2 -2
  26. package/src/store/session/slices/chat/actions/topic.ts +28 -2
  27. package/src/store/session/slices/chat/initialState.ts +1 -0
  28. package/src/store/session/slices/chat/reducers/topic.ts +23 -1
  29. package/src/store/session/slices/chat/selectors/index.ts +2 -1
  30. package/src/store/session/slices/chat/selectors/topic.ts +8 -0
  31. package/src/store/session/slices/session/action.ts +8 -5
  32. package/src/store/session/slices/session/selectors/export.ts +23 -0
  33. package/src/store/session/slices/session/selectors/index.ts +6 -2
  34. package/src/store/session/slices/session/selectors/list.ts +3 -2
  35. package/src/store/settings/action.ts +11 -1
  36. package/src/store/settings/initialState.ts +2 -0
  37. package/src/store/settings/selectors.ts +10 -0
  38. package/src/types/exportConfig.ts +34 -14
  39. package/src/utils/config.ts +54 -1
  40. package/src/pages/chat/[id]/Config/TopicItem.tsx +0 -136
package/.eslintrc.js CHANGED
@@ -4,5 +4,8 @@ config.extends.push('plugin:@next/next/recommended');
4
4
 
5
5
  config.rules['unicorn/no-negated-condition'] = 0;
6
6
  config.rules['unicorn/prefer-type-error'] = 0;
7
+ config.rules['unicorn/prefer-logical-operator-over-ternary'] = 0;
8
+ config.rules['unicorn/no-null'] = 0;
9
+ config.rules['unicorn/no-typeof-undefined'] = 0;
7
10
 
8
11
  module.exports = config;
package/CHANGELOG.md CHANGED
@@ -2,6 +2,60 @@
2
2
 
3
3
  # Changelog
4
4
 
5
+ ## [Version 0.30.0](https://github.com/lobehub/lobe-chat/compare/v0.29.0...v0.30.0)
6
+
7
+ <sup>Released on **2023-07-30**</sup>
8
+
9
+ #### ✨ Features
10
+
11
+ - **misc**: 优化保存为话题功能, 实现 Topic 重命名功能, 实现话题删除功能, 支持缓存角色面板的展开折叠状态.
12
+
13
+ <br/>
14
+
15
+ <details>
16
+ <summary><kbd>Improvements and Fixes</kbd></summary>
17
+
18
+ #### What's improved
19
+
20
+ - **misc**: 优化保存为话题功能 ([fdbe084](https://github.com/lobehub/lobe-chat/commit/fdbe084))
21
+ - **misc**: 实现 Topic 重命名功能 ([5ef1685](https://github.com/lobehub/lobe-chat/commit/5ef1685))
22
+ - **misc**: 实现话题删除功能 ([970889d](https://github.com/lobehub/lobe-chat/commit/970889d))
23
+ - **misc**: 支持缓存角色面板的展开折叠状态 ([c241c4b](https://github.com/lobehub/lobe-chat/commit/c241c4b))
24
+
25
+ </details>
26
+
27
+ <div align="right">
28
+
29
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
30
+
31
+ </div>
32
+
33
+ ## [Version 0.29.0](https://github.com/lobehub/lobe-chat/compare/v0.28.0...v0.29.0)
34
+
35
+ <sup>Released on **2023-07-30**</sup>
36
+
37
+ #### ✨ Features
38
+
39
+ - **misc**: 实现单个会话和角色的导出功能, 实现清空所有会话消息.
40
+
41
+ <br/>
42
+
43
+ <details>
44
+ <summary><kbd>Improvements and Fixes</kbd></summary>
45
+
46
+ #### What's improved
47
+
48
+ - **misc**: 实现单个会话和角色的导出功能 ([d15a481](https://github.com/lobehub/lobe-chat/commit/d15a481))
49
+ - **misc**: 实现清空所有会话消息 ([64c5125](https://github.com/lobehub/lobe-chat/commit/64c5125))
50
+
51
+ </details>
52
+
53
+ <div align="right">
54
+
55
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
56
+
57
+ </div>
58
+
5
59
  ## [Version 0.28.0](https://github.com/lobehub/lobe-chat/compare/v0.27.4...v0.28.0)
6
60
 
7
61
  <sup>Released on **2023-07-30**</sup>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobehub/chat",
3
- "version": "0.28.0",
3
+ "version": "0.30.0",
4
4
  "description": "Lobe Chat is an open-source chatbot client using LangChain, Typescript and Next.js",
5
5
  "keywords": [
6
6
  "chatbot",
@@ -1,3 +1,5 @@
1
+ export const LOADING_FLAT = '...';
2
+
1
3
  // 只要 start with 这个,就可以判断为 function message
2
4
  export const FUNCTION_MESSAGE_FLAG = '{"function';
3
5
 
@@ -22,8 +22,8 @@ const BottomAction = memo<{ children: ReactNode }>(({ children }) => {
22
22
  const { t } = useTranslation('common');
23
23
 
24
24
  const { exportSessions, exportSettings, exportAll, exportAgents } = useExportConfig();
25
-
26
25
  const { importConfig } = useImportConfig();
26
+
27
27
  const items: MenuProps['items'] = useMemo(
28
28
  () => [
29
29
  {
@@ -0,0 +1,62 @@
1
+ import { sessionSelectors, useSessionStore } from '@/store/session';
2
+ import { settingsSelectors, useSettings } from '@/store/settings';
3
+ import { createConfigFile, exportConfigFile } from '@/utils/config';
4
+
5
+ const getAgents = () => sessionSelectors.exportAgents(useSessionStore.getState());
6
+ const getAgent = (id: string) => sessionSelectors.getExportAgent(id)(useSessionStore.getState());
7
+
8
+ const getSessions = () => sessionSelectors.exportSessions(useSessionStore.getState());
9
+ const getSession = (id: string) => sessionSelectors.getSessionById(id)(useSessionStore.getState());
10
+
11
+ const getSettings = () => settingsSelectors.exportSettings(useSettings.getState());
12
+
13
+ export const exportAgents = () => {
14
+ const sessions = getAgents();
15
+
16
+ const config = createConfigFile('agents', { sessions });
17
+
18
+ exportConfigFile(config, 'agents');
19
+ };
20
+
21
+ export const exportSingleAgent = (id: string) => {
22
+ const agent = getAgent(id);
23
+ if (!agent) return;
24
+
25
+ const config = createConfigFile('agents', { sessions: { [id]: agent } });
26
+
27
+ exportConfigFile(config, agent.meta?.title || 'agent');
28
+ };
29
+
30
+ export const exportSessions = () => {
31
+ const sessions = getSessions();
32
+ const config = createConfigFile('sessions', { sessions });
33
+
34
+ exportConfigFile(config, 'sessions');
35
+ };
36
+
37
+ export const exportSingleSession = (id: string) => {
38
+ const session = getSession(id);
39
+ if (!session) return;
40
+ const sessions = { [id]: session };
41
+
42
+ const config = createConfigFile('sessions', { sessions });
43
+
44
+ exportConfigFile(config, `${session.meta?.title}-session`);
45
+ };
46
+
47
+ export const exportSettings = () => {
48
+ const settings = getSettings();
49
+
50
+ const config = createConfigFile('settings', { settings });
51
+
52
+ exportConfigFile(config, 'settings');
53
+ };
54
+
55
+ export const exportAll = () => {
56
+ const sessions = getSessions();
57
+ const settings = getSettings();
58
+
59
+ const config = createConfigFile('all', { sessions, settings });
60
+
61
+ exportConfigFile(config, 'config');
62
+ };
@@ -1,69 +1,6 @@
1
- import { transform } from 'lodash-es';
2
1
  import { useMemo } from 'react';
3
- import { shallow } from 'zustand/shallow';
4
2
 
5
- import { Migration } from '@/migrations';
6
- import { useSessionStore } from '@/store/session';
7
- import { useSettings } from '@/store/settings';
8
- import {
9
- ConfigFileAgents,
10
- ConfigFileAll,
11
- ConfigFileSessions,
12
- ConfigFileSettings,
13
- } from '@/types/exportConfig';
14
- import { exportConfigFile } from '@/utils/config';
3
+ import { exportAgents, exportAll, exportSessions, exportSettings } from '@/helpers/export';
15
4
 
16
- export const useExportConfig = () => {
17
- const [sessions] = useSessionStore((s) => [s.sessions], shallow);
18
-
19
- const [settings] = useSettings((s) => [s.settings, s.importSettings], shallow);
20
-
21
- const exportAgents = () => {
22
- const config: ConfigFileAgents = {
23
- exportType: 'agents',
24
- state: {
25
- sessions: transform(sessions, (result, value, key) => {
26
- result[key] = { ...value, chats: {}, topics: {} };
27
- }),
28
- },
29
- version: Migration.targetVersion,
30
- };
31
-
32
- exportConfigFile(config, 'agents');
33
- };
34
-
35
- const exportSessions = () => {
36
- const config: ConfigFileSessions = {
37
- exportType: 'sessions',
38
- state: { sessions },
39
- version: Migration.targetVersion,
40
- };
41
-
42
- exportConfigFile(config, 'sessions');
43
- };
44
-
45
- const exportSettings = () => {
46
- const config: ConfigFileSettings = {
47
- exportType: 'settings',
48
- state: { settings },
49
- version: Migration.targetVersion,
50
- };
51
-
52
- exportConfigFile(config, 'settings');
53
- };
54
-
55
- const exportAll = () => {
56
- // 将 入参转换为 配置文件格式
57
- const config: ConfigFileAll = {
58
- exportType: 'all',
59
- state: { sessions, settings },
60
- version: Migration.targetVersion,
61
- };
62
- exportConfigFile(config, 'config');
63
- };
64
-
65
- return useMemo(
66
- () => ({ exportAgents, exportAll, exportSessions, exportSettings }),
67
- [sessions, settings],
68
- );
69
- };
5
+ export const useExportConfig = () =>
6
+ useMemo(() => ({ exportAgents, exportAll, exportSessions, exportSettings }), []);
@@ -52,6 +52,8 @@ export default {
52
52
  temp: '临时',
53
53
  tokenDetail: '系统设定: {{systemRoleToken}} 历史消息: {{chatsToken}}',
54
54
  topic: {
55
+ confirmRemoveTopic: '即将删除该话题,删除后将不可恢复,请谨慎操作。',
56
+ defaultTitle: '默认话题',
55
57
  saveCurrentMessages: '将当前会话保存为话题',
56
58
  searchPlaceholder: '搜索话题...',
57
59
  },
@@ -2,9 +2,10 @@ export default {
2
2
  danger: {
3
3
  clear: {
4
4
  action: '立即清除',
5
- confirm: '确认清除所有聊天、设置数据?',
6
- desc: '清除所有聊天、设置数据',
7
- title: '清除所有数据',
5
+ confirm: '确认清除所有聊天数据?',
6
+ desc: '清除所有会话数据',
7
+ success: '已清除所有会话消息',
8
+ title: '清除所有会话消息',
8
9
  },
9
10
  reset: {
10
11
  action: '立即重置',
@@ -1,4 +1,4 @@
1
- import { ConfigState } from '@/types/exportConfig';
1
+ import { ConfigStateAll } from '@/types/exportConfig';
2
2
  import { VersionController } from '@/utils/VersionController';
3
3
 
4
4
  // 当前最新的版本号
@@ -7,7 +7,7 @@ export const CURRENT_CONFIG_VERSION = 1;
7
7
  // 历史记录版本升级模块
8
8
  export const ConfigMigrations = [];
9
9
 
10
- export const Migration = new VersionController<ConfigState>(
10
+ export const Migration = new VersionController<ConfigStateAll>(
11
11
  ConfigMigrations,
12
12
  CURRENT_CONFIG_VERSION,
13
13
  );
@@ -1,12 +1,13 @@
1
1
  import { ActionIcon, Avatar, Icon, List } from '@lobehub/ui';
2
2
  import { useHover } from 'ahooks';
3
- import { Dropdown, type MenuProps, Popconfirm, Tag } from 'antd';
3
+ import { App, Dropdown, type MenuProps, Tag } from 'antd';
4
4
  import { FolderOutput, MoreVertical, Pin, PinOff, Trash } from 'lucide-react';
5
- import { FC, memo, useMemo, useRef, useState } from 'react';
5
+ import { FC, memo, useMemo, useRef } from 'react';
6
6
  import { useTranslation } from 'react-i18next';
7
7
  import { Flexbox } from 'react-layout-kit';
8
8
  import { shallow } from 'zustand/shallow';
9
9
 
10
+ import { exportSingleAgent, exportSingleSession } from '@/helpers/export';
10
11
  import { agentSelectors, chatSelectors, sessionSelectors, useSessionStore } from '@/store/session';
11
12
  import { useSettings } from '@/store/settings';
12
13
 
@@ -24,10 +25,10 @@ interface SessionItemProps {
24
25
  const SessionItem: FC<SessionItemProps> = memo(({ id, active = true, loading, pin }) => {
25
26
  const ref = useRef(null);
26
27
  const isHovering = useHover(ref);
27
- const [popOpen, setPopOpen] = useState(false);
28
- const [dropdownOpen, setDropdownOpen] = useState(false);
28
+
29
29
  const { t } = useTranslation('common');
30
- const isHighlight = isHovering || dropdownOpen;
30
+ const isHighlight = isHovering;
31
+
31
32
  const { styles, theme, cx } = useStyles(isHighlight);
32
33
  const [defaultModel] = useSettings((s) => [s.settings.model], shallow);
33
34
 
@@ -58,23 +59,31 @@ const SessionItem: FC<SessionItemProps> = memo(({ id, active = true, loading, pi
58
59
  ];
59
60
  }, shallow);
60
61
 
61
- // TODO: 动作绑定
62
+ const { modal } = App.useApp();
62
63
  const items: MenuProps['items'] = useMemo(
63
64
  () => [
64
65
  {
65
66
  icon: <Icon icon={pin ? PinOff : Pin} />,
66
67
  key: 'pin',
67
68
  label: t(pin ? 'pinOff' : 'pin'),
69
+ // TODO: 动作绑定
70
+ onClick: () => {},
68
71
  },
69
72
  {
70
73
  children: [
71
74
  {
72
75
  key: 'agent',
73
76
  label: <div>{t('exportType.agent')}</div>,
77
+ onClick: () => {
78
+ exportSingleAgent(id);
79
+ },
74
80
  },
75
81
  {
76
82
  key: 'agentWithMessage',
77
83
  label: <div>{t('exportType.agentWithMessage')}</div>,
84
+ onClick: () => {
85
+ exportSingleSession(id);
86
+ },
78
87
  },
79
88
  ],
80
89
  icon: <Icon icon={FolderOutput} />,
@@ -82,10 +91,24 @@ const SessionItem: FC<SessionItemProps> = memo(({ id, active = true, loading, pi
82
91
  label: t('export'),
83
92
  },
84
93
  {
94
+ danger: true,
85
95
  icon: <Icon icon={Trash} />,
86
96
  key: 'delete',
87
97
  label: t('delete'),
88
- onClick: () => setPopOpen(true),
98
+ onClick: ({ domEvent }) => {
99
+ domEvent.stopPropagation();
100
+
101
+ modal.confirm({
102
+ centered: true,
103
+ okButtonProps: { danger: true },
104
+ okText: t('ok'),
105
+ onOk: () => {
106
+ removeSession(id);
107
+ },
108
+ rootClassName: styles.modalRoot,
109
+ title: t('confirmRemoveSessionItemAlert'),
110
+ });
111
+ },
89
112
  },
90
113
  ],
91
114
  [id],
@@ -123,15 +146,6 @@ const SessionItem: FC<SessionItemProps> = memo(({ id, active = true, loading, pi
123
146
  {model}
124
147
  </Tag>
125
148
  )}
126
- {/*{showChatLength && (*/}
127
- {/* <Tag*/}
128
- {/* bordered={false}*/}
129
- {/* style={{ color: theme.colorTextSecondary, display: 'flex', gap: 4 }}*/}
130
- {/* >*/}
131
- {/* <Icon icon={LucideMessageCircle} />*/}
132
- {/* {chatLength}*/}
133
- {/* </Tag>*/}
134
- {/*)}*/}
135
149
  </Flexbox>
136
150
  )}
137
151
  </Flexbox>
@@ -140,42 +154,20 @@ const SessionItem: FC<SessionItemProps> = memo(({ id, active = true, loading, pi
140
154
  style={{ color: theme.colorText }}
141
155
  title={title}
142
156
  />
143
- <Popconfirm
144
- arrow={false}
145
- cancelText={t('cancel')}
146
- okButtonProps={{ danger: true }}
147
- okText={t('ok')}
148
- onCancel={() => setPopOpen(false)}
149
- onConfirm={(e) => {
150
- e?.stopPropagation();
151
- removeSession(id);
152
- setPopOpen(false);
153
- }}
154
- open={popOpen}
155
- overlayStyle={{ width: 280 }}
156
- title={t('confirmRemoveSessionItemAlert')}
157
- >
158
- <Dropdown
159
- arrow={false}
160
- menu={{ items }}
161
- onOpenChange={setDropdownOpen}
162
- open={dropdownOpen}
163
- trigger={['click']}
164
- >
165
- <ActionIcon
166
- className="session-remove"
167
- icon={MoreVertical}
168
- onClick={(e) => {
169
- e.preventDefault();
170
- e.stopPropagation();
171
- }}
172
- size={{
173
- blockSize: 28,
174
- fontSize: 16,
175
- }}
176
- />
177
- </Dropdown>
178
- </Popconfirm>
157
+ <Dropdown arrow={false} menu={{ items }} trigger={['click']}>
158
+ <ActionIcon
159
+ className="session-remove"
160
+ icon={MoreVertical}
161
+ onClick={(e) => {
162
+ e.preventDefault();
163
+ e.stopPropagation();
164
+ }}
165
+ size={{
166
+ blockSize: 28,
167
+ fontSize: 16,
168
+ }}
169
+ />
170
+ </Dropdown>
179
171
  </Flexbox>
180
172
  </div>
181
173
  );
@@ -52,6 +52,9 @@ export const useStyles = createStyles(({ css, token }, isHighlight: boolean) =>
52
52
  hover: css`
53
53
  background-color: ${token.colorFillSecondary};
54
54
  `,
55
+ modalRoot: css`
56
+ z-index: 2000;
57
+ `,
55
58
  pin: css`
56
59
  background-color: ${token.colorFillTertiary};
57
60
  `,
@@ -6,10 +6,10 @@ import { useTranslation } from 'react-i18next';
6
6
  import { Flexbox } from 'react-layout-kit';
7
7
  import { shallow } from 'zustand/shallow';
8
8
 
9
- import { Topic } from '@/pages/chat/[id]/Config/Topic';
10
9
  import { agentSelectors, useSessionStore } from '@/store/session';
11
10
 
12
11
  import Header from './Header';
12
+ import { Topic } from './Topic';
13
13
 
14
14
  const useStyles = createStyles(({ css, token }) => ({
15
15
  desc: css`
@@ -0,0 +1,29 @@
1
+ import { Icon } from '@lobehub/ui';
2
+ import { Tag, Typography } from 'antd';
3
+ import { useTheme } from 'antd-style';
4
+ import { MessageSquareDashed } from 'lucide-react';
5
+ import { memo } from 'react';
6
+ import { useTranslation } from 'react-i18next';
7
+ import { Flexbox } from 'react-layout-kit';
8
+
9
+ const { Paragraph } = Typography;
10
+
11
+ const DefaultContent = memo(() => {
12
+ const { t } = useTranslation('common');
13
+
14
+ const theme = useTheme();
15
+
16
+ return (
17
+ <Flexbox align={'center'} gap={8} horizontal>
18
+ <Flexbox align={'center'} height={24} justify={'center'} width={24}>
19
+ <Icon color={theme.colorTextDescription} icon={MessageSquareDashed} />
20
+ </Flexbox>
21
+ <Paragraph ellipsis={{ rows: 1 }} style={{ margin: 0 }}>
22
+ {t('topic.defaultTitle')}
23
+ </Paragraph>
24
+ <Tag>{t('temp')}</Tag>
25
+ </Flexbox>
26
+ );
27
+ });
28
+
29
+ export default DefaultContent;
@@ -0,0 +1,151 @@
1
+ import { ActionIcon, EditableText, Icon } from '@lobehub/ui';
2
+ import { App, Dropdown, type MenuProps, Typography } from 'antd';
3
+ import { createStyles } from 'antd-style';
4
+ import { MoreVertical, PencilLine, Star, Trash } from 'lucide-react';
5
+ import { memo, useMemo } from 'react';
6
+ import { useTranslation } from 'react-i18next';
7
+ import { shallow } from 'zustand/shallow';
8
+
9
+ import { useSessionStore } from '@/store/session';
10
+
11
+ const useStyles = createStyles(({ css }) => ({
12
+ content: css`
13
+ position: relative;
14
+ overflow: hidden;
15
+ flex: 1;
16
+ `,
17
+ title: css`
18
+ flex: 1;
19
+ height: 28px;
20
+ line-height: 28px;
21
+ text-align: start;
22
+ `,
23
+ }));
24
+ const { Paragraph } = Typography;
25
+
26
+ interface TopicContentProps {
27
+ fav?: boolean;
28
+ id: string;
29
+ title: string;
30
+ }
31
+
32
+ const TopicContent = memo<TopicContentProps>(({ id, title, fav }) => {
33
+ const { t } = useTranslation('common');
34
+
35
+ const [editing, dispatchTopic, removeTopic] = useSessionStore(
36
+ (s) => [s.renameTopicId === id, s.dispatchTopic, s.removeTopic],
37
+ shallow,
38
+ );
39
+ const { styles, theme } = useStyles();
40
+
41
+ const toggleEditing = (visible?: boolean) => {
42
+ useSessionStore.setState({ renameTopicId: visible ? id : '' });
43
+ };
44
+
45
+ const { modal } = App.useApp();
46
+
47
+ const items = useMemo<MenuProps['items']>(
48
+ () => [
49
+ {
50
+ icon: <Icon icon={PencilLine} />,
51
+ key: 'rename',
52
+ label: t('rename'),
53
+ onClick: () => {
54
+ toggleEditing(true);
55
+ },
56
+ },
57
+ // {
58
+ // icon: <Icon icon={Share2} />,
59
+ // key: 'share',
60
+ // label: t('share'),
61
+ // },
62
+ {
63
+ danger: true,
64
+ icon: <Icon icon={Trash} />,
65
+ key: 'delete',
66
+ label: t('delete'),
67
+ onClick: () => {
68
+ if (!id) return;
69
+
70
+ modal.confirm({
71
+ centered: true,
72
+ okButtonProps: { danger: true },
73
+ onOk: () => {
74
+ removeTopic(id);
75
+ },
76
+ title: t('topic.confirmRemoveTopic'),
77
+ });
78
+ },
79
+ },
80
+ ],
81
+ [],
82
+ );
83
+
84
+ return (
85
+ <>
86
+ <ActionIcon
87
+ color={fav ? theme.colorWarning : undefined}
88
+ fill={fav ? theme.colorWarning : 'transparent'}
89
+ icon={Star}
90
+ onClick={() => {
91
+ if (!id) return;
92
+ dispatchTopic({ favorite: !fav, id, type: 'favorChatTopic' });
93
+ }}
94
+ size={'small'}
95
+ />
96
+
97
+ {!editing ? (
98
+ <Paragraph
99
+ className={styles.title}
100
+ ellipsis={{ rows: 1, tooltip: title }}
101
+ style={{ margin: 0 }}
102
+ >
103
+ {title}
104
+ </Paragraph>
105
+ ) : (
106
+ <EditableText
107
+ editing={editing}
108
+ onChangeEnd={(v) => {
109
+ if (title !== v) {
110
+ dispatchTopic({ id, key: 'title', type: 'updateChatTopic', value: v });
111
+ }
112
+ toggleEditing(false);
113
+ }}
114
+ onEditingChange={toggleEditing}
115
+ showEditIcon={false}
116
+ size={'small'}
117
+ style={{
118
+ background: theme.colorBgContainer,
119
+ borderRadius: 6,
120
+ height: 28,
121
+ padding: '0 2px',
122
+ }}
123
+ value={title}
124
+ />
125
+ )}
126
+ {!editing && (
127
+ <Dropdown
128
+ arrow={false}
129
+ menu={{
130
+ items: items,
131
+ onClick: ({ domEvent }) => {
132
+ domEvent.stopPropagation();
133
+ },
134
+ }}
135
+ trigger={['click']}
136
+ >
137
+ <ActionIcon
138
+ className="topic-more"
139
+ icon={MoreVertical}
140
+ onClick={(e) => {
141
+ e.stopPropagation();
142
+ }}
143
+ size={'small'}
144
+ />
145
+ </Dropdown>
146
+ )}
147
+ </>
148
+ );
149
+ });
150
+
151
+ export default TopicContent;