@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.
- package/CHANGELOG.md +44 -0
- package/locales/de-DE/chat.json +10 -0
- package/locales/de-DE/common.json +1 -0
- package/locales/de-DE/error.json +4 -4
- package/locales/en-US/chat.json +10 -0
- package/locales/en-US/common.json +1 -0
- package/locales/en-US/error.json +1 -1
- package/locales/es-ES/chat.json +10 -0
- package/locales/es-ES/common.json +1 -0
- package/locales/fr-FR/chat.json +10 -0
- package/locales/fr-FR/common.json +1 -0
- package/locales/fr-FR/error.json +1 -2
- package/locales/it-IT/chat.json +10 -0
- package/locales/it-IT/common.json +1 -0
- package/locales/it-IT/error.json +5 -5
- package/locales/ja-JP/chat.json +10 -0
- package/locales/ja-JP/common.json +1 -0
- package/locales/ko-KR/chat.json +10 -0
- package/locales/ko-KR/common.json +1 -0
- package/locales/ko-KR/error.json +4 -4
- package/locales/nl-NL/chat.json +10 -0
- package/locales/nl-NL/common.json +1 -0
- package/locales/pl-PL/chat.json +10 -0
- package/locales/pl-PL/common.json +1 -0
- package/locales/pl-PL/error.json +2 -3
- package/locales/pt-BR/chat.json +10 -0
- package/locales/pt-BR/common.json +1 -0
- package/locales/ru-RU/chat.json +10 -0
- package/locales/ru-RU/common.json +1 -0
- package/locales/ru-RU/error.json +3 -3
- package/locales/tr-TR/chat.json +10 -0
- package/locales/tr-TR/common.json +1 -0
- package/locales/vi-VN/chat.json +10 -0
- package/locales/vi-VN/common.json +1 -0
- package/locales/vi-VN/error.json +1 -1
- package/locales/zh-CN/chat.json +10 -0
- package/locales/zh-CN/common.json +1 -0
- package/locales/zh-TW/chat.json +10 -0
- package/locales/zh-TW/common.json +1 -0
- package/package.json +2 -2
- package/src/app/chat/(desktop)/features/Conversation.tsx +17 -0
- package/src/app/chat/(desktop)/features/HotKeys.tsx +44 -0
- package/src/app/chat/(desktop)/index.tsx +2 -3
- package/src/app/chat/features/ChatHeader/ShareButton/Preview.tsx +1 -1
- package/src/app/chat/features/SessionListContent/List/Item/Actions.tsx +24 -3
- package/src/app/chat/features/TopicListContent/Topic/TopicContent.tsx +43 -2
- package/src/database/models/__tests__/message.test.ts +48 -0
- package/src/database/models/__tests__/topic.test.ts +135 -0
- package/src/database/models/message.ts +35 -0
- package/src/database/models/session.ts +11 -1
- package/src/database/models/topic.ts +34 -2
- package/src/features/ChatInput/ActionBar/Clear.tsx +0 -6
- package/src/features/Conversation/Actions/Assistant.tsx +16 -5
- package/src/features/Conversation/Actions/Error.tsx +2 -2
- package/src/features/Conversation/Actions/Fallback.tsx +4 -3
- package/src/features/Conversation/Actions/Function.tsx +3 -3
- package/src/features/Conversation/Actions/User.tsx +3 -3
- package/src/features/Conversation/Actions/customAction.ts +2 -4
- package/src/features/Conversation/Actions/index.ts +50 -45
- package/src/features/Conversation/Error/InvalidAccess.tsx +1 -1
- package/src/features/Conversation/Error/OpenAPIKey.tsx +1 -1
- package/src/features/Conversation/Error/OpenAiBizError.tsx +1 -1
- package/src/features/Conversation/Error/Plugin/PluginError.tsx +1 -1
- package/src/features/Conversation/Error/Plugin/PluginSettings.tsx +1 -1
- package/src/features/Conversation/Error/index.tsx +2 -2
- package/src/features/Conversation/Extras/Assistant.tsx +32 -29
- package/src/features/Conversation/Extras/User.tsx +2 -1
- package/src/features/Conversation/Extras/index.ts +4 -4
- package/src/features/Conversation/Messages/index.ts +3 -3
- package/src/features/Conversation/components/ChatItem/ActionsBar.tsx +58 -0
- package/src/features/Conversation/components/ChatItem/index.tsx +126 -0
- package/src/features/Conversation/components/ChatList/HistoryDivider.tsx +6 -3
- package/src/features/Conversation/components/ChatList/index.tsx +35 -79
- package/src/features/Conversation/hooks/useChatListActionsBar.tsx +43 -31
- package/src/features/Conversation/hooks/useInitConversation.ts +4 -1
- package/src/features/Conversation/index.tsx +4 -3
- package/src/features/Conversation/types/index.tsx +35 -0
- package/src/features/PluginStore/PluginItem/EditCustomPlugin.tsx +7 -2
- package/src/locales/default/chat.ts +10 -1
- package/src/locales/default/common.ts +1 -0
- package/src/services/config.ts +22 -0
- package/src/services/session.ts +6 -0
- package/src/services/topic.ts +4 -0
- package/src/store/chat/slices/message/selectors.ts +10 -0
- package/src/store/chat/slices/topic/action.ts +22 -0
- package/src/store/global/slices/common/action.ts +2 -0
- package/src/store/session/slices/session/action.test.ts +136 -0
- package/src/store/session/slices/session/action.ts +26 -6
- package/src/types/topic.ts +1 -0
- package/src/features/Conversation/components/ChatList/ActionsBar.tsx +0 -27
- package/src/features/Conversation/components/ChatList/Item.tsx +0 -236
- 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
|
|
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} />}>
|
|
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
|
|
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
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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 = (
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
|
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
|
-
|
|
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: '即将删除未收藏话题,删除后将不可恢复,请谨慎操作。',
|
package/src/services/config.ts
CHANGED
|
@@ -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;
|
package/src/services/session.ts
CHANGED
|
@@ -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();
|
package/src/services/topic.ts
CHANGED
|
@@ -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'));
|