@lobehub/chat 0.118.10 → 0.119.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 (92) hide show
  1. package/CHANGELOG.md +44 -0
  2. package/locales/de-DE/chat.json +10 -0
  3. package/locales/de-DE/common.json +1 -0
  4. package/locales/de-DE/error.json +4 -4
  5. package/locales/en-US/chat.json +10 -0
  6. package/locales/en-US/common.json +1 -0
  7. package/locales/en-US/error.json +1 -1
  8. package/locales/es-ES/chat.json +10 -0
  9. package/locales/es-ES/common.json +1 -0
  10. package/locales/fr-FR/chat.json +10 -0
  11. package/locales/fr-FR/common.json +1 -0
  12. package/locales/fr-FR/error.json +1 -2
  13. package/locales/it-IT/chat.json +10 -0
  14. package/locales/it-IT/common.json +1 -0
  15. package/locales/it-IT/error.json +5 -5
  16. package/locales/ja-JP/chat.json +10 -0
  17. package/locales/ja-JP/common.json +1 -0
  18. package/locales/ko-KR/chat.json +10 -0
  19. package/locales/ko-KR/common.json +1 -0
  20. package/locales/ko-KR/error.json +4 -4
  21. package/locales/nl-NL/chat.json +10 -0
  22. package/locales/nl-NL/common.json +1 -0
  23. package/locales/pl-PL/chat.json +10 -0
  24. package/locales/pl-PL/common.json +1 -0
  25. package/locales/pl-PL/error.json +2 -3
  26. package/locales/pt-BR/chat.json +10 -0
  27. package/locales/pt-BR/common.json +1 -0
  28. package/locales/ru-RU/chat.json +10 -0
  29. package/locales/ru-RU/common.json +1 -0
  30. package/locales/ru-RU/error.json +3 -3
  31. package/locales/tr-TR/chat.json +10 -0
  32. package/locales/tr-TR/common.json +1 -0
  33. package/locales/vi-VN/chat.json +10 -0
  34. package/locales/vi-VN/common.json +1 -0
  35. package/locales/vi-VN/error.json +1 -1
  36. package/locales/zh-CN/chat.json +10 -0
  37. package/locales/zh-CN/common.json +1 -0
  38. package/locales/zh-TW/chat.json +10 -0
  39. package/locales/zh-TW/common.json +1 -0
  40. package/package.json +2 -2
  41. package/src/app/chat/(desktop)/features/Conversation.tsx +17 -0
  42. package/src/app/chat/(desktop)/features/HotKeys.tsx +44 -0
  43. package/src/app/chat/(desktop)/index.tsx +2 -3
  44. package/src/app/chat/features/ChatHeader/ShareButton/Preview.tsx +1 -1
  45. package/src/app/chat/features/SessionListContent/List/Item/Actions.tsx +24 -3
  46. package/src/app/chat/features/TopicListContent/Topic/TopicContent.tsx +43 -2
  47. package/src/database/models/__tests__/message.test.ts +48 -0
  48. package/src/database/models/__tests__/topic.test.ts +135 -0
  49. package/src/database/models/message.ts +35 -0
  50. package/src/database/models/session.ts +11 -1
  51. package/src/database/models/topic.ts +34 -2
  52. package/src/features/ChatInput/ActionBar/Clear.tsx +0 -6
  53. package/src/features/Conversation/Actions/Assistant.tsx +16 -5
  54. package/src/features/Conversation/Actions/Error.tsx +2 -2
  55. package/src/features/Conversation/Actions/Fallback.tsx +4 -3
  56. package/src/features/Conversation/Actions/Function.tsx +3 -3
  57. package/src/features/Conversation/Actions/User.tsx +3 -3
  58. package/src/features/Conversation/Actions/customAction.ts +2 -4
  59. package/src/features/Conversation/Actions/index.ts +50 -45
  60. package/src/features/Conversation/Error/InvalidAccess.tsx +1 -1
  61. package/src/features/Conversation/Error/OpenAPIKey.tsx +1 -1
  62. package/src/features/Conversation/Error/OpenAiBizError.tsx +1 -1
  63. package/src/features/Conversation/Error/Plugin/PluginError.tsx +1 -1
  64. package/src/features/Conversation/Error/Plugin/PluginSettings.tsx +1 -1
  65. package/src/features/Conversation/Error/index.tsx +2 -2
  66. package/src/features/Conversation/Extras/Assistant.tsx +32 -29
  67. package/src/features/Conversation/Extras/User.tsx +2 -1
  68. package/src/features/Conversation/Extras/index.ts +4 -4
  69. package/src/features/Conversation/Messages/index.ts +3 -3
  70. package/src/features/Conversation/components/ChatItem/ActionsBar.tsx +58 -0
  71. package/src/features/Conversation/components/ChatItem/index.tsx +126 -0
  72. package/src/features/Conversation/components/ChatList/HistoryDivider.tsx +6 -3
  73. package/src/features/Conversation/components/ChatList/index.tsx +35 -79
  74. package/src/features/Conversation/hooks/useChatListActionsBar.tsx +43 -31
  75. package/src/features/Conversation/hooks/useInitConversation.ts +4 -1
  76. package/src/features/Conversation/index.tsx +4 -3
  77. package/src/features/Conversation/types/index.tsx +35 -0
  78. package/src/features/PluginStore/PluginItem/EditCustomPlugin.tsx +7 -2
  79. package/src/locales/default/chat.ts +10 -1
  80. package/src/locales/default/common.ts +1 -0
  81. package/src/services/config.ts +22 -0
  82. package/src/services/session.ts +6 -0
  83. package/src/services/topic.ts +4 -0
  84. package/src/store/chat/slices/message/selectors.ts +10 -0
  85. package/src/store/chat/slices/topic/action.ts +22 -0
  86. package/src/store/global/slices/common/action.ts +2 -0
  87. package/src/store/session/slices/session/action.test.ts +136 -0
  88. package/src/store/session/slices/session/action.ts +26 -6
  89. package/src/types/topic.ts +1 -0
  90. package/src/features/Conversation/components/ChatList/ActionsBar.tsx +0 -27
  91. package/src/features/Conversation/components/ChatList/Item.tsx +0 -236
  92. package/src/features/Conversation/container/index.tsx +0 -83
@@ -0,0 +1,126 @@
1
+ import { type AlertProps, ChatItem } from '@lobehub/ui';
2
+ import isEqual from 'fast-deep-equal';
3
+ import { ReactNode, memo, useCallback, useMemo, useState } from 'react';
4
+ import { useTranslation } from 'react-i18next';
5
+
6
+ import { useChatStore } from '@/store/chat';
7
+ import { chatSelectors } from '@/store/chat/selectors';
8
+ import { useSessionStore } from '@/store/session';
9
+ import { agentSelectors } from '@/store/session/selectors';
10
+ import { ChatMessage } from '@/types/message';
11
+
12
+ import { renderErrorMessages } from '../../Error';
13
+ import { renderMessagesExtra } from '../../Extras';
14
+ import { renderMessages, useAvatarsClick } from '../../Messages';
15
+ import ActionsBar from './ActionsBar';
16
+
17
+ export interface ChatListItemProps {
18
+ index: number;
19
+ }
20
+
21
+ const Item = memo<ChatListItemProps>(({ index }) => {
22
+ const { t } = useTranslation('common');
23
+
24
+ const [editing, setEditing] = useState(false);
25
+ const [type = 'chat'] = useSessionStore((s) => {
26
+ const config = agentSelectors.currentAgentConfig(s);
27
+ return [config.displayMode];
28
+ });
29
+
30
+ const meta = useSessionStore(agentSelectors.currentAgentMeta, isEqual);
31
+ const item = useChatStore(
32
+ (s) => chatSelectors.currentChatsWithGuideMessage(meta)(s)[index],
33
+ isEqual,
34
+ );
35
+
36
+ const [loading, onMessageChange] = useChatStore((s) => [
37
+ s.chatLoadingId === item.id,
38
+ s.updateMessageContent,
39
+ ]);
40
+
41
+ const onAvatarsClick = useAvatarsClick();
42
+
43
+ const RenderMessage = useCallback(
44
+ ({ editableContent, data }: { data: ChatMessage; editableContent: ReactNode }) => {
45
+ if (!item?.role) return;
46
+ const RenderFunction = renderMessages[item.role] ?? renderMessages['default'];
47
+
48
+ if (!RenderFunction) return;
49
+
50
+ return <RenderFunction {...data} editableContent={editableContent} />;
51
+ },
52
+ [item.role],
53
+ );
54
+
55
+ const MessageExtra = useCallback(
56
+ ({ data }: { data: ChatMessage }) => {
57
+ if (!renderMessagesExtra || !item?.role) return;
58
+ let RenderFunction;
59
+ if (renderMessagesExtra?.[item.role]) RenderFunction = renderMessagesExtra[item.role];
60
+
61
+ if (!RenderFunction) return;
62
+ return <RenderFunction {...data} />;
63
+ },
64
+ [item.role],
65
+ );
66
+
67
+ const ErrorMessage = useCallback(
68
+ ({ data }: { data: ChatMessage }) => {
69
+ if (!renderErrorMessages || !item?.error?.type) return;
70
+ let RenderFunction;
71
+ if (renderErrorMessages?.[item.error.type])
72
+ RenderFunction = renderErrorMessages[item.error.type].Render;
73
+ if (!RenderFunction && renderErrorMessages?.['default'])
74
+ RenderFunction = renderErrorMessages['default'].Render;
75
+ if (!RenderFunction) return;
76
+ return <RenderFunction {...data} />;
77
+ },
78
+ [item.error],
79
+ );
80
+
81
+ const error = useMemo(() => {
82
+ if (!item.error) return;
83
+ const message = item.error?.message;
84
+ let alertConfig = {};
85
+ if (item.error.type && renderErrorMessages?.[item.error.type]) {
86
+ alertConfig = renderErrorMessages[item.error.type]?.config as AlertProps;
87
+ }
88
+ return { message, ...alertConfig };
89
+ }, [item.error]);
90
+
91
+ return (
92
+ <ChatItem
93
+ actions={<ActionsBar index={index} setEditing={setEditing} />}
94
+ avatar={item.meta}
95
+ editing={editing}
96
+ error={error}
97
+ errorMessage={<ErrorMessage data={item} />}
98
+ loading={loading}
99
+ message={item.content}
100
+ messageExtra={<MessageExtra data={item} />}
101
+ onAvatarClick={onAvatarsClick?.(item.role)}
102
+ onChange={(value) => onMessageChange(item.id, value)}
103
+ onDoubleClick={(e) => {
104
+ if (item.id === 'default' || item.error) return;
105
+ if (item.role && ['assistant', 'user'].includes(item.role) && e.altKey) {
106
+ setEditing(true);
107
+ }
108
+ }}
109
+ onEditingChange={setEditing}
110
+ placement={type === 'chat' ? (item.role === 'user' ? 'right' : 'left') : 'left'}
111
+ primary={item.role === 'user'}
112
+ renderMessage={(editableContent) => (
113
+ <RenderMessage data={item} editableContent={editableContent} />
114
+ )}
115
+ text={{
116
+ cancel: t('cancel'),
117
+ confirm: t('ok'),
118
+ edit: t('edit'),
119
+ }}
120
+ time={item.updatedAt || item.createdAt}
121
+ type={type === 'chat' ? 'block' : 'pure'}
122
+ />
123
+ );
124
+ });
125
+
126
+ export default Item;
@@ -2,19 +2,22 @@ import { Icon, Tag } from '@lobehub/ui';
2
2
  import { Divider } from 'antd';
3
3
  import { Timer } from 'lucide-react';
4
4
  import { memo } from 'react';
5
+ import { useTranslation } from 'react-i18next';
5
6
 
6
7
  interface HistoryDividerProps {
7
8
  enable?: boolean;
8
- text?: string;
9
9
  }
10
10
 
11
- const HistoryDivider = memo<HistoryDividerProps>(({ enable, text }) => {
11
+ const HistoryDivider = memo<HistoryDividerProps>(({ enable }) => {
12
+ const { t } = useTranslation('common');
12
13
  if (!enable) return null;
13
14
 
14
15
  return (
15
16
  <div style={{ padding: '0 20px' }}>
16
17
  <Divider>
17
- <Tag icon={<Icon icon={Timer} />}>{text || 'History Message'}</Tag>
18
+ <Tag icon={<Icon icon={Timer} />}>
19
+ {t('historyRange', { defaultValue: 'History Message' })}
20
+ </Tag>
18
21
  </Divider>
19
22
  </div>
20
23
  );
@@ -1,88 +1,44 @@
1
+ import isEqual from 'fast-deep-equal';
1
2
  import { Fragment, memo } from 'react';
2
3
  import { Flexbox } from 'react-layout-kit';
3
4
 
4
- import type { ChatMessage } from '@/types/message';
5
+ import { useChatStore } from '@/store/chat';
6
+ import { chatSelectors } from '@/store/chat/selectors';
7
+ import { useSessionStore } from '@/store/session';
8
+ import { agentSelectors } from '@/store/session/selectors';
5
9
 
10
+ import Item from '../ChatItem';
6
11
  import HistoryDivider from './HistoryDivider';
7
- import Item, { ListItemProps } from './Item';
8
12
  import { useStyles } from './style';
9
13
 
10
- export interface ChatListProps extends ListItemProps {
11
- className?: string;
12
- /**
13
- * @description Data of chat messages to be displayed
14
- */
15
- data: ChatMessage[];
16
- enableHistoryCount?: boolean;
17
- historyCount?: number;
18
- loadingId?: string;
19
- }
20
- export type {
21
- OnActionsClick,
22
- OnAvatatsClick,
23
- OnMessageChange,
24
- RenderAction,
25
- RenderErrorMessage,
26
- RenderItem,
27
- RenderMessage,
28
- RenderMessageExtra,
29
- } from './Item';
30
-
31
- const ChatList = memo<ChatListProps>(
32
- ({
33
- onActionsClick,
34
- onAvatarsClick,
35
- renderMessagesExtra,
36
- className,
37
- data,
38
- type = 'chat',
39
- text,
40
- showTitle,
41
- onMessageChange,
42
- renderMessages,
43
- renderErrorMessages,
44
- loadingId,
45
- renderItems,
46
- enableHistoryCount,
47
- renderActions,
48
- historyCount = 0,
49
- }) => {
50
- const { cx, styles } = useStyles();
51
-
52
- return (
53
- <Flexbox className={cx(styles.container, className)}>
54
- {data.map((item, index) => {
55
- const itemProps = {
56
- loading: loadingId === item.id,
57
- onActionsClick,
58
- onAvatarsClick,
59
- onMessageChange,
60
- renderActions,
61
- renderErrorMessages,
62
- renderItems,
63
- renderMessages,
64
- renderMessagesExtra,
65
- showTitle,
66
- text,
67
- type,
68
- };
69
-
70
- const historyLength = data.length;
71
- const enableHistoryDivider =
72
- enableHistoryCount &&
73
- historyLength > historyCount &&
74
- historyCount === historyLength - index + 1;
75
-
76
- return (
77
- <Fragment key={item.id}>
78
- <HistoryDivider enable={enableHistoryDivider} text={text?.history} />
79
- <Item {...itemProps} {...item} />
80
- </Fragment>
81
- );
82
- })}
83
- </Flexbox>
84
- );
85
- },
86
- );
14
+ const ChatList = memo(() => {
15
+ const { styles } = useStyles();
16
+
17
+ const data = useChatStore(chatSelectors.currentChatIDsWithGuideMessage, isEqual);
18
+
19
+ const [enableHistoryCount, historyCount = 0] = useSessionStore((s) => {
20
+ const config = agentSelectors.currentAgentConfig(s);
21
+ return [config.enableHistoryCount, config.historyCount];
22
+ });
23
+
24
+ return (
25
+ <Flexbox className={styles.container}>
26
+ {data.map((item, index) => {
27
+ const historyLength = data.length;
28
+ const enableHistoryDivider =
29
+ enableHistoryCount &&
30
+ historyLength > historyCount &&
31
+ historyCount === historyLength - index + 1;
32
+
33
+ return (
34
+ <Fragment key={item}>
35
+ <HistoryDivider enable={enableHistoryDivider} />
36
+ <Item index={index} />
37
+ </Fragment>
38
+ );
39
+ })}
40
+ </Flexbox>
41
+ );
42
+ });
87
43
 
88
44
  export default ChatList;
@@ -1,43 +1,55 @@
1
1
  import { ActionIconGroupItems } from '@lobehub/ui/es/ActionIconGroup';
2
2
  import { Copy, Edit, RotateCw, Trash } from 'lucide-react';
3
+ import { useMemo } from 'react';
4
+ import { useTranslation } from 'react-i18next';
3
5
 
4
6
  interface ChatListActionsBar {
5
7
  copy: ActionIconGroupItems;
6
8
  del: ActionIconGroupItems;
9
+ delAndRegenerate: ActionIconGroupItems;
7
10
  divider: { type: 'divider' };
8
11
  edit: ActionIconGroupItems;
9
12
  regenerate: ActionIconGroupItems;
10
13
  }
11
14
 
12
- export const useChatListActionsBar = (text?: {
13
- copy?: string;
14
- delete?: string;
15
- edit?: string;
16
- regenerate?: string;
17
- }): ChatListActionsBar => {
18
- return {
19
- copy: {
20
- icon: Copy,
21
- key: 'copy',
22
- label: text?.copy || 'Copy',
23
- },
24
- del: {
25
- icon: Trash,
26
- key: 'del',
27
- label: text?.delete || 'Delete',
28
- },
29
- divider: {
30
- type: 'divider',
31
- },
32
- edit: {
33
- icon: Edit,
34
- key: 'edit',
35
- label: text?.edit || 'Edit',
36
- },
37
- regenerate: {
38
- icon: RotateCw,
39
- key: 'regenerate',
40
- label: text?.regenerate || 'Regenerate',
41
- },
42
- };
15
+ export const useChatListActionsBar = (): ChatListActionsBar => {
16
+ const { t } = useTranslation('common');
17
+
18
+ return useMemo(
19
+ () => ({
20
+ copy: {
21
+ icon: Copy,
22
+ key: 'copy',
23
+ label: t('copy', { defaultValue: 'Copy' }),
24
+ },
25
+ del: {
26
+ danger: true,
27
+ icon: Trash,
28
+ key: 'del',
29
+ label: t('delete', { defaultValue: 'Delete' }),
30
+ },
31
+ delAndRegenerate: {
32
+ icon: RotateCw,
33
+ key: 'delAndRegenerate',
34
+ label: t('messageAction.delAndRegenerate', {
35
+ defaultValue: 'Delete and regenerate',
36
+ ns: 'chat',
37
+ }),
38
+ },
39
+ divider: {
40
+ type: 'divider',
41
+ },
42
+ edit: {
43
+ icon: Edit,
44
+ key: 'edit',
45
+ label: t('edit', { defaultValue: 'Edit' }),
46
+ },
47
+ regenerate: {
48
+ icon: RotateCw,
49
+ key: 'regenerate',
50
+ label: t('regenerate', { defaultValue: 'Regenerate' }),
51
+ },
52
+ }),
53
+ [],
54
+ );
43
55
  };
@@ -8,7 +8,8 @@ import { useToolStore } from '@/store/tool';
8
8
  export const useInitConversation = () => {
9
9
  const [sessionId] = useSessionStore((s) => [s.activeId]);
10
10
  const plugins = useSessionStore((s) => agentSelectors.currentAgentPlugins(s));
11
- const [activeTopicId, switchTopic, useFetchMessages, useFetchTopics] = useChatStore((s) => [
11
+ const [init, activeTopicId, switchTopic, useFetchMessages, useFetchTopics] = useChatStore((s) => [
12
+ s.messagesInit,
12
13
  s.activeTopicId,
13
14
  s.switchTopic,
14
15
  s.useFetchMessages,
@@ -40,4 +41,6 @@ export const useInitConversation = () => {
40
41
  unsubscribe();
41
42
  };
42
43
  }, []);
44
+
45
+ return init;
43
46
  };
@@ -8,8 +8,9 @@ import SafeSpacing from '@/components/SafeSpacing';
8
8
  import { useGlobalStore } from '@/store/global';
9
9
  import { settingsSelectors } from '@/store/global/selectors';
10
10
 
11
+ import ChatList from './components/ChatList';
11
12
  import ChatScrollAnchor from './components/ScrollAnchor';
12
- import ChatList from './container';
13
+ import SkeletonList from './components/SkeletonList';
13
14
  import { useInitConversation } from './hooks/useInitConversation';
14
15
 
15
16
  const useStyles = createStyles(({ css, responsive, stylish, cx }, fontSize: number = 14) =>
@@ -52,14 +53,14 @@ const Conversation = memo<ConversationProps>(({ mobile, chatInput }) => {
52
53
  const { styles } = useStyles(fontSize);
53
54
 
54
55
  // init conversation
55
- useInitConversation();
56
+ const init = useInitConversation();
56
57
 
57
58
  return (
58
59
  <Flexbox flex={1} style={{ position: 'relative' }}>
59
60
  <div style={{ flex: 1, overflow: 'hidden', position: 'relative' }}>
60
61
  <div className={styles} ref={ref}>
61
62
  {!mobile && <SafeSpacing />}
62
- <ChatList />
63
+ {init ? <ChatList /> : <SkeletonList />}
63
64
  <ChatScrollAnchor />
64
65
  </div>
65
66
  <BackBottom target={ref} text={t('backToBottom')} />
@@ -0,0 +1,35 @@
1
+ import { type AlertProps, type ChatItemProps } from '@lobehub/ui';
2
+ import { ActionEvent } from '@lobehub/ui';
3
+ import { FC, ReactNode } from 'react';
4
+
5
+ import { LLMRoleType } from '@/types/llm';
6
+ import { ChatMessage } from '@/types/message';
7
+
8
+ import { type ActionsBarProps } from '../components/ChatItem/ActionsBar';
9
+
10
+ export type OnActionsClick = (action: ActionEvent, message: ChatMessage) => void;
11
+ export type OnAvatarsClick = (role: RenderRole) => ChatItemProps['onAvatarClick'];
12
+ export type RenderRole = LLMRoleType | 'default' | string;
13
+ export type RenderMessage = FC<ChatMessage & { editableContent: ReactNode }>;
14
+ export type RenderMessageExtra = FC<ChatMessage>;
15
+ export interface RenderErrorMessage {
16
+ Render?: FC<ChatMessage>;
17
+ config?: AlertProps;
18
+ }
19
+ export type RenderAction = FC<ActionsBarProps & ChatMessage>;
20
+
21
+ export type RenderItem = FC<{ key: string } & ChatMessage & ListItemProps>;
22
+
23
+ export interface ListItemProps {
24
+ groupNav?: ChatItemProps['avatarAddon'];
25
+
26
+ renderItems?: {
27
+ [role: RenderRole]: RenderItem;
28
+ };
29
+
30
+ /**
31
+ * @description 是否显示聊天项的名称
32
+ * @default false
33
+ */
34
+ showTitle?: boolean;
35
+ }
@@ -12,9 +12,10 @@ const EditCustomPlugin = memo<{ identifier: string }>(({ identifier }) => {
12
12
  const { t } = useTranslation('plugin');
13
13
  const [showModal, setModal] = useState(false);
14
14
 
15
- const [installCustomPlugin, updateNewDevPlugin] = useToolStore((s) => [
15
+ const [installCustomPlugin, updateNewDevPlugin, uninstallCustomPlugin] = useToolStore((s) => [
16
16
  s.installCustomPlugin,
17
17
  s.updateNewCustomPlugin,
18
+ s.uninstallCustomPlugin,
18
19
  ]);
19
20
 
20
21
  const customPlugin = useToolStore(pluginSelectors.getCustomPluginById(identifier), isEqual);
@@ -27,10 +28,14 @@ const EditCustomPlugin = memo<{ identifier: string }>(({ identifier }) => {
27
28
  >
28
29
  <DevModal
29
30
  mode={'edit'}
31
+ onDelete={() => {
32
+ uninstallCustomPlugin(identifier);
33
+ setModal(false);
34
+ }}
30
35
  onOpenChange={setModal}
31
36
  onSave={async (devPlugin) => {
32
37
  await installCustomPlugin(devPlugin);
33
- // toggleAgentPlugin(devPlugin.identifier);
38
+ setModal(false);
34
39
  }}
35
40
  onValueChange={updateNewDevPlugin}
36
41
  open={showModal}
@@ -8,6 +8,7 @@ export default {
8
8
  confirmRemoveSessionItemAlert: '即将删除该助手,删除后该将无法找回,请确认你的操作',
9
9
  defaultAgent: '自定义助手',
10
10
  defaultSession: '自定义助手',
11
+ duplicateTitle: '{{title}} 副本',
11
12
  historyRange: '历史范围',
12
13
  inbox: {
13
14
  defaultMessage:
@@ -15,9 +16,12 @@ export default {
15
16
  desc: '开启大脑集群,激发思维火花。你的智能助理,在这里与你交流一切',
16
17
  title: '随便聊聊',
17
18
  },
19
+ messageAction: {
20
+ delAndRegenerate: '删除并重新生成',
21
+ regenerate: '重新生成',
22
+ },
18
23
  newAgent: '新建助手',
19
24
  noDescription: '暂无描述',
20
-
21
25
  pin: '置顶',
22
26
  pinOff: '取消置顶',
23
27
  regenerate: '重新生成',
@@ -52,6 +56,11 @@ export default {
52
56
  used: '使用',
53
57
  },
54
58
  topic: {
59
+ actions: {
60
+ autoRename: '智能重命名',
61
+ duplicate: '创建副本',
62
+ export: '导出话题',
63
+ },
55
64
  confirmRemoveAll: '即将删除全部话题,删除后将不可恢复,请谨慎操作。',
56
65
  confirmRemoveTopic: '即将删除该话题,删除后将不可恢复,请谨慎操作。',
57
66
  confirmRemoveUnstarred: '即将删除未收藏话题,删除后将不可恢复,请谨慎操作。',
@@ -17,6 +17,7 @@ export default {
17
17
  defaultAgent: '自定义助手',
18
18
  defaultSession: '自定义助手',
19
19
  delete: '删除',
20
+ duplicate: '创建副本',
20
21
  edit: '编辑',
21
22
  export: '导出配置',
22
23
  exportType: {
@@ -127,6 +127,28 @@ class ConfigService {
127
127
  exportConfigFile(config, `${session.meta?.title}-session`);
128
128
  };
129
129
 
130
+ /**
131
+ * export a topic
132
+ */
133
+ exportSingleTopic = async (sessionId: string, topicId: string) => {
134
+ const session = this.getSession(sessionId);
135
+ if (!session) return;
136
+
137
+ const messages = await messageService.getMessages(sessionId, topicId);
138
+ const topics = await topicService.getTopics({ sessionId });
139
+
140
+ const topic = topics.find((item) => item.id === topicId);
141
+ if (!topic) return;
142
+
143
+ const config = createConfigFile('singleSession', {
144
+ messages,
145
+ sessions: [session],
146
+ topics: [topic],
147
+ });
148
+
149
+ exportConfigFile(config, `${topic.title}-topic`);
150
+ };
151
+
130
152
  exportSingleAgent = async (id: string) => {
131
153
  const agent = this.getAgent(id);
132
154
  if (!agent) return;
@@ -58,6 +58,12 @@ class SessionService {
58
58
  async searchSessions(keyword: string) {
59
59
  return SessionModel.queryByKeyword(keyword);
60
60
  }
61
+
62
+ async duplicateSession(id: string, newTitle: string): Promise<string | undefined> {
63
+ const res = await SessionModel.duplicate(id, newTitle);
64
+
65
+ if (res) return res?.id;
66
+ }
61
67
  }
62
68
 
63
69
  export const sessionService = new SessionService();
@@ -51,6 +51,10 @@ class TopicService {
51
51
  async searchTopics(keyword: string) {
52
52
  return TopicModel.queryByKeyword(keyword);
53
53
  }
54
+
55
+ async duplicateTopic(id: string, newTitle?: string) {
56
+ return TopicModel.duplicateTopic(id, newTitle);
57
+ }
54
58
  }
55
59
 
56
60
  export const topicService = new TopicService();
@@ -84,6 +84,12 @@ const currentChatsWithGuideMessage =
84
84
  return [emptyInboxGuideMessage];
85
85
  };
86
86
 
87
+ const currentChatIDsWithGuideMessage = (s: ChatStore) => {
88
+ const meta = agentSelectors.currentAgentMeta(useSessionStore.getState());
89
+
90
+ return currentChatsWithGuideMessage(meta)(s).map((s) => s.id);
91
+ };
92
+
87
93
  const currentChatsWithHistoryConfig = (s: ChatStore): ChatMessage[] => {
88
94
  const chats = currentChats(s);
89
95
  const config = agentSelectors.currentAgentConfig(useSessionStore.getState());
@@ -109,11 +115,15 @@ const getFunctionMessageProps =
109
115
 
110
116
  const getMessageById = (id: string) => (s: ChatStore) => chatHelpers.getMessageById(s.messages, id);
111
117
 
118
+ const latestMessage = (s: ChatStore) => currentChats(s).at(-1);
119
+
112
120
  export const chatSelectors = {
113
121
  chatsMessageString,
122
+ currentChatIDsWithGuideMessage,
114
123
  currentChats,
115
124
  currentChatsWithGuideMessage,
116
125
  currentChatsWithHistoryConfig,
117
126
  getFunctionMessageProps,
118
127
  getMessageById,
128
+ latestMessage,
119
129
  };
@@ -30,6 +30,8 @@ export interface ChatTopicAction {
30
30
  removeTopic: (id: string) => Promise<void>;
31
31
  removeUnstarredTopic: () => void;
32
32
  saveToTopic: () => Promise<string | undefined>;
33
+ autoRenameTopicTitle: (id: string) => Promise<void>;
34
+ duplicateTopic: (id: string) => Promise<void>;
33
35
  summaryTopicTitle: (topicId: string, messages: ChatMessage[]) => Promise<void>;
34
36
  switchTopic: (id?: string) => Promise<void>;
35
37
  updateTopicTitleInSummary: (id: string, title: string) => void;
@@ -78,6 +80,19 @@ export const chatTopic: StateCreator<
78
80
 
79
81
  return topicId;
80
82
  },
83
+ duplicateTopic: async (id) => {
84
+ const { refreshTopic, switchTopic } = get();
85
+
86
+ const topic = topicSelectors.getTopicById(id)(get());
87
+ if (!topic) return;
88
+
89
+ const newTitle = t('duplicateTitle', { ns: 'chat', title: topic?.title });
90
+
91
+ const newTopicId = await topicService.duplicateTopic(id, newTitle);
92
+ await refreshTopic();
93
+
94
+ switchTopic(newTopicId);
95
+ },
81
96
  // update
82
97
  summaryTopicTitle: async (topicId, messages) => {
83
98
  const { updateTopicTitleInSummary, updateTopicLoading, refreshTopic } = get();
@@ -115,6 +130,13 @@ export const chatTopic: StateCreator<
115
130
  await topicService.updateTitle(id, title);
116
131
  await get().refreshTopic();
117
132
  },
133
+
134
+ autoRenameTopicTitle: async (id) => {
135
+ const { activeId: sessionId, summaryTopicTitle } = get();
136
+ const messages = await messageService.getMessages(sessionId, id);
137
+
138
+ await summaryTopicTitle(id, messages);
139
+ },
118
140
  // query
119
141
  useFetchTopics: (sessionId) =>
120
142
  useSWR<ChatTopic[]>(sessionId, async (sessionId) => topicService.getTopics({ sessionId }), {
@@ -75,6 +75,8 @@ export const createCommonSlice: StateCreator<
75
75
  },
76
76
  useCheckLatestVersion: () =>
77
77
  useSWR('checkLatestVersion', globalService.getLatestVersion, {
78
+ // check latest version every 30 minutes
79
+ focusThrottleInterval: 1000 * 60 * 30,
78
80
  onSuccess: (data: string) => {
79
81
  if (gt(data, CURRENT_VERSION))
80
82
  set({ hasNewVersion: true, latestVersion: data }, false, n('checkLatestVersion'));