@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,136 @@
|
|
|
1
|
+
import { act, renderHook } from '@testing-library/react';
|
|
2
|
+
import { AppRouterInstance } from 'next/dist/shared/lib/app-router-context.shared-runtime';
|
|
3
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
4
|
+
|
|
5
|
+
import { INBOX_SESSION_ID } from '@/const/session';
|
|
6
|
+
import { SESSION_CHAT_URL } from '@/const/url';
|
|
7
|
+
import { sessionService } from '@/services/session';
|
|
8
|
+
import { useSessionStore } from '@/store/session';
|
|
9
|
+
import { LobeSessionType } from '@/types/session';
|
|
10
|
+
|
|
11
|
+
// Mock sessionService 和其他依赖项
|
|
12
|
+
vi.mock('@/services/session', () => ({
|
|
13
|
+
sessionService: {
|
|
14
|
+
removeAllSessions: vi.fn(),
|
|
15
|
+
createNewSession: vi.fn(),
|
|
16
|
+
duplicateSession: vi.fn(),
|
|
17
|
+
updateSessionGroup: vi.fn(),
|
|
18
|
+
removeSession: vi.fn(),
|
|
19
|
+
getSessions: vi.fn(),
|
|
20
|
+
searchSessions: vi.fn(),
|
|
21
|
+
},
|
|
22
|
+
}));
|
|
23
|
+
|
|
24
|
+
// Mock router
|
|
25
|
+
const mockRouterPush = vi.fn();
|
|
26
|
+
|
|
27
|
+
const mockRefresh = vi.fn();
|
|
28
|
+
beforeEach(() => {
|
|
29
|
+
vi.clearAllMocks();
|
|
30
|
+
useSessionStore.setState({
|
|
31
|
+
refreshSessions: mockRefresh,
|
|
32
|
+
router: {
|
|
33
|
+
push: mockRouterPush,
|
|
34
|
+
} as unknown as AppRouterInstance,
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
afterEach(() => {
|
|
39
|
+
vi.restoreAllMocks();
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
describe('SessionAction', () => {
|
|
43
|
+
describe('clearSessions', () => {
|
|
44
|
+
it('should clear all sessions and refresh the list', async () => {
|
|
45
|
+
const { result } = renderHook(() => useSessionStore());
|
|
46
|
+
|
|
47
|
+
await act(async () => {
|
|
48
|
+
await result.current.clearSessions();
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
expect(sessionService.removeAllSessions).toHaveBeenCalled();
|
|
52
|
+
expect(mockRefresh).toHaveBeenCalled(); // 假设 refreshSessions 调用了 getSessions
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
describe('createSession', () => {
|
|
57
|
+
it('should create a new session and switch to it', async () => {
|
|
58
|
+
const { result } = renderHook(() => useSessionStore());
|
|
59
|
+
const newSessionId = 'new-session-id';
|
|
60
|
+
vi.mocked(sessionService.createNewSession).mockResolvedValue(newSessionId);
|
|
61
|
+
|
|
62
|
+
let createdSessionId;
|
|
63
|
+
|
|
64
|
+
await act(async () => {
|
|
65
|
+
createdSessionId = await result.current.createSession({ config: { displayMode: 'docs' } });
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
const call = vi.mocked(sessionService.createNewSession).mock.calls[0];
|
|
69
|
+
expect(call[0]).toEqual(LobeSessionType.Agent);
|
|
70
|
+
expect(call[1]).toMatchObject({ config: { displayMode: 'docs' } });
|
|
71
|
+
|
|
72
|
+
expect(createdSessionId).toBe(newSessionId);
|
|
73
|
+
expect(mockRouterPush).toHaveBeenCalledWith(
|
|
74
|
+
SESSION_CHAT_URL(newSessionId, result.current.isMobile),
|
|
75
|
+
);
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
describe('duplicateSession', () => {
|
|
80
|
+
it('should duplicate a session and switch to the new one', async () => {
|
|
81
|
+
const { result } = renderHook(() => useSessionStore());
|
|
82
|
+
const sessionId = 'session-id';
|
|
83
|
+
const duplicatedSessionId = 'duplicated-session-id';
|
|
84
|
+
vi.mocked(sessionService.duplicateSession).mockResolvedValue(duplicatedSessionId);
|
|
85
|
+
|
|
86
|
+
await act(async () => {
|
|
87
|
+
await result.current.duplicateSession(sessionId);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
expect(sessionService.duplicateSession).toHaveBeenCalledWith(sessionId, undefined);
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
describe('removeSession', () => {
|
|
95
|
+
it('should remove a session and refresh the list', async () => {
|
|
96
|
+
const { result } = renderHook(() => useSessionStore());
|
|
97
|
+
const sessionId = 'session-id';
|
|
98
|
+
|
|
99
|
+
await act(async () => {
|
|
100
|
+
await result.current.removeSession(sessionId);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
expect(sessionService.removeSession).toHaveBeenCalledWith(sessionId);
|
|
104
|
+
expect(mockRefresh).toHaveBeenCalled();
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
describe('switchSession', () => {
|
|
109
|
+
it('should switch to the provided session id', async () => {
|
|
110
|
+
const { result } = renderHook(() => useSessionStore());
|
|
111
|
+
const sessionId = 'session-id';
|
|
112
|
+
|
|
113
|
+
act(() => {
|
|
114
|
+
result.current.switchSession(sessionId);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
expect(result.current.activeId).toBe(sessionId);
|
|
118
|
+
expect(mockRouterPush).toHaveBeenCalledWith(
|
|
119
|
+
SESSION_CHAT_URL(sessionId, result.current.isMobile),
|
|
120
|
+
);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it('should switch to the inbox session id if none is provided', async () => {
|
|
124
|
+
const { result } = renderHook(() => useSessionStore());
|
|
125
|
+
|
|
126
|
+
act(() => {
|
|
127
|
+
result.current.switchSession();
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
expect(result.current.activeId).toBe(INBOX_SESSION_ID);
|
|
131
|
+
expect(mockRouterPush).toHaveBeenCalledWith(
|
|
132
|
+
SESSION_CHAT_URL(INBOX_SESSION_ID, result.current.isMobile),
|
|
133
|
+
);
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
});
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { message } from 'antd';
|
|
2
|
+
import { t } from 'i18next';
|
|
1
3
|
import useSWR, { SWRResponse, mutate } from 'swr';
|
|
2
4
|
import { DeepPartial } from 'utility-types';
|
|
3
5
|
import { StateCreator } from 'zustand/vanilla';
|
|
@@ -17,7 +19,9 @@ import {
|
|
|
17
19
|
import { merge } from '@/utils/merge';
|
|
18
20
|
import { setNamespace } from '@/utils/storeDebug';
|
|
19
21
|
|
|
22
|
+
import { agentSelectors } from '../agent/selectors';
|
|
20
23
|
import { initLobeSession } from './initialState';
|
|
24
|
+
import { sessionSelectors } from './selectors';
|
|
21
25
|
|
|
22
26
|
const n = setNamespace('session');
|
|
23
27
|
|
|
@@ -39,7 +43,7 @@ export interface SessionAction {
|
|
|
39
43
|
* @returns sessionId
|
|
40
44
|
*/
|
|
41
45
|
createSession: (agent?: DeepPartial<LobeAgentSettings>) => Promise<string>;
|
|
42
|
-
|
|
46
|
+
duplicateSession: (id: string) => Promise<void>;
|
|
43
47
|
/**
|
|
44
48
|
* Pins or unpins a session.
|
|
45
49
|
*
|
|
@@ -48,20 +52,16 @@ export interface SessionAction {
|
|
|
48
52
|
* @returns {Promise<void>} A promise that resolves when the session is successfully pinned or unpinned.
|
|
49
53
|
*/
|
|
50
54
|
pinSession: (id: string, pinned?: boolean) => Promise<void>;
|
|
51
|
-
|
|
52
55
|
/**
|
|
53
56
|
* re-fetch the data
|
|
54
57
|
*/
|
|
55
58
|
refreshSessions: () => Promise<void>;
|
|
56
|
-
|
|
57
59
|
/**
|
|
58
60
|
* remove session
|
|
59
61
|
* @param id - sessionId
|
|
60
62
|
*/
|
|
61
63
|
removeSession: (id: string) => void;
|
|
62
|
-
|
|
63
64
|
switchBackToChat: () => void;
|
|
64
|
-
|
|
65
65
|
/**
|
|
66
66
|
* switch session url
|
|
67
67
|
*/
|
|
@@ -70,7 +70,6 @@ export interface SessionAction {
|
|
|
70
70
|
* A custom hook that uses SWR to fetch sessions data.
|
|
71
71
|
*/
|
|
72
72
|
useFetchSessions: () => SWRResponse<any>;
|
|
73
|
-
|
|
74
73
|
useSearchSessions: (keyword?: string) => SWRResponse<any>;
|
|
75
74
|
}
|
|
76
75
|
|
|
@@ -109,6 +108,27 @@ export const createSessionSlice: StateCreator<
|
|
|
109
108
|
return id;
|
|
110
109
|
},
|
|
111
110
|
|
|
111
|
+
duplicateSession: async (id) => {
|
|
112
|
+
const { switchSession, refreshSessions } = get();
|
|
113
|
+
const session = sessionSelectors.getSessionById(id)(get());
|
|
114
|
+
|
|
115
|
+
if (!session) return;
|
|
116
|
+
const title = agentSelectors.getTitle(session.meta);
|
|
117
|
+
|
|
118
|
+
const newTitle = t('duplicateTitle', { ns: 'chat', title: title });
|
|
119
|
+
|
|
120
|
+
const newId = await sessionService.duplicateSession(id, newTitle);
|
|
121
|
+
|
|
122
|
+
// duplicate Session Error
|
|
123
|
+
if (!newId) {
|
|
124
|
+
message.error('复制失败');
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
await refreshSessions();
|
|
129
|
+
switchSession(newId);
|
|
130
|
+
},
|
|
131
|
+
|
|
112
132
|
pinSession: async (sessionId, pinned) => {
|
|
113
133
|
await sessionService.updateSessionGroup(sessionId, pinned ? 'pinned' : 'default');
|
|
114
134
|
|
package/src/types/topic.ts
CHANGED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import { ActionIconGroup, type ActionIconGroupProps } from '@lobehub/ui';
|
|
2
|
-
import { memo } from 'react';
|
|
3
|
-
|
|
4
|
-
import { useChatListActionsBar } from '../../hooks/useChatListActionsBar';
|
|
5
|
-
|
|
6
|
-
export interface ActionsBarProps extends ActionIconGroupProps {
|
|
7
|
-
text?: {
|
|
8
|
-
copy?: string;
|
|
9
|
-
delete?: string;
|
|
10
|
-
edit?: string;
|
|
11
|
-
regenerate?: string;
|
|
12
|
-
};
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
const ActionsBar = memo<ActionsBarProps>(({ text, ...rest }) => {
|
|
16
|
-
const { regenerate, edit, copy, divider, del } = useChatListActionsBar(text);
|
|
17
|
-
return (
|
|
18
|
-
<ActionIconGroup
|
|
19
|
-
dropdownMenu={[edit, copy, regenerate, divider, del]}
|
|
20
|
-
items={[regenerate, edit]}
|
|
21
|
-
type="ghost"
|
|
22
|
-
{...rest}
|
|
23
|
-
/>
|
|
24
|
-
);
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
export default ActionsBar;
|
|
@@ -1,236 +0,0 @@
|
|
|
1
|
-
import { type AlertProps, ChatItem, type ChatItemProps } from '@lobehub/ui';
|
|
2
|
-
import { ActionEvent, copyToClipboard } from '@lobehub/ui';
|
|
3
|
-
import { App } from 'antd';
|
|
4
|
-
import { FC, ReactNode, memo, useCallback, useMemo, useState } from 'react';
|
|
5
|
-
|
|
6
|
-
import { LLMRoleType } from '@/types/llm';
|
|
7
|
-
import { ChatMessage } from '@/types/message';
|
|
8
|
-
|
|
9
|
-
import ActionsBar, { type ActionsBarProps } from './ActionsBar';
|
|
10
|
-
|
|
11
|
-
export type OnMessageChange = (id: string, content: string) => void;
|
|
12
|
-
export type OnActionsClick = (action: ActionEvent, message: ChatMessage) => void;
|
|
13
|
-
export type OnAvatatsClick = (role: RenderRole) => ChatItemProps['onAvatarClick'];
|
|
14
|
-
export type RenderRole = LLMRoleType | 'default' | string;
|
|
15
|
-
export type RenderItem = FC<{ key: string } & ChatMessage & ListItemProps>;
|
|
16
|
-
export type RenderMessage = FC<ChatMessage & { editableContent: ReactNode }>;
|
|
17
|
-
export type RenderMessageExtra = FC<ChatMessage>;
|
|
18
|
-
export interface RenderErrorMessage {
|
|
19
|
-
Render?: FC<ChatMessage>;
|
|
20
|
-
config?: AlertProps;
|
|
21
|
-
}
|
|
22
|
-
export type RenderAction = FC<ActionsBarProps & ChatMessage>;
|
|
23
|
-
|
|
24
|
-
export interface ListItemProps {
|
|
25
|
-
groupNav?: ChatItemProps['avatarAddon'];
|
|
26
|
-
loading?: boolean;
|
|
27
|
-
/**
|
|
28
|
-
* @description 点击操作按钮的回调函数
|
|
29
|
-
*/
|
|
30
|
-
onActionsClick?: OnActionsClick;
|
|
31
|
-
onAvatarsClick?: OnAvatatsClick;
|
|
32
|
-
/**
|
|
33
|
-
* @description 消息变化的回调函数
|
|
34
|
-
*/
|
|
35
|
-
onMessageChange?: OnMessageChange;
|
|
36
|
-
renderActions?: {
|
|
37
|
-
[actionKey: string]: RenderAction;
|
|
38
|
-
};
|
|
39
|
-
/**
|
|
40
|
-
* @description 渲染错误消息的函数
|
|
41
|
-
*/
|
|
42
|
-
renderErrorMessages?: {
|
|
43
|
-
[errorType: 'default' | string]: RenderErrorMessage;
|
|
44
|
-
};
|
|
45
|
-
renderItems?: {
|
|
46
|
-
[role: RenderRole]: RenderItem;
|
|
47
|
-
};
|
|
48
|
-
/**
|
|
49
|
-
* @description 渲染消息的函数
|
|
50
|
-
*/
|
|
51
|
-
renderMessages?: {
|
|
52
|
-
[role: RenderRole]: RenderMessage;
|
|
53
|
-
};
|
|
54
|
-
/**
|
|
55
|
-
* @description 渲染消息额外内容的函数
|
|
56
|
-
*/
|
|
57
|
-
renderMessagesExtra?: {
|
|
58
|
-
[role: RenderRole]: RenderMessageExtra;
|
|
59
|
-
};
|
|
60
|
-
/**
|
|
61
|
-
* @description 是否显示聊天项的名称
|
|
62
|
-
* @default false
|
|
63
|
-
*/
|
|
64
|
-
showTitle?: boolean;
|
|
65
|
-
/**
|
|
66
|
-
* @description 文本内容
|
|
67
|
-
*/
|
|
68
|
-
text?: ChatItemProps['text'] &
|
|
69
|
-
ActionsBarProps['text'] & {
|
|
70
|
-
copySuccess?: string;
|
|
71
|
-
history?: string;
|
|
72
|
-
} & {
|
|
73
|
-
[key: string]: string;
|
|
74
|
-
};
|
|
75
|
-
/**
|
|
76
|
-
* @description 聊天列表的类型
|
|
77
|
-
* @default 'chat'
|
|
78
|
-
*/
|
|
79
|
-
type?: 'docs' | 'chat';
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
export type ChatListItemProps = ChatMessage & ListItemProps;
|
|
83
|
-
|
|
84
|
-
const Item = memo<ChatListItemProps>((props) => {
|
|
85
|
-
const {
|
|
86
|
-
renderMessagesExtra,
|
|
87
|
-
showTitle,
|
|
88
|
-
onActionsClick,
|
|
89
|
-
onAvatarsClick,
|
|
90
|
-
onMessageChange,
|
|
91
|
-
type,
|
|
92
|
-
text,
|
|
93
|
-
renderMessages,
|
|
94
|
-
renderErrorMessages,
|
|
95
|
-
renderActions,
|
|
96
|
-
loading,
|
|
97
|
-
groupNav,
|
|
98
|
-
renderItems,
|
|
99
|
-
...item
|
|
100
|
-
} = props;
|
|
101
|
-
|
|
102
|
-
const [editing, setEditing] = useState(false);
|
|
103
|
-
|
|
104
|
-
const { message } = App.useApp();
|
|
105
|
-
|
|
106
|
-
const RenderItem = useMemo(() => {
|
|
107
|
-
if (!renderItems || !item?.role) return;
|
|
108
|
-
let renderFunction;
|
|
109
|
-
if (renderItems?.[item.role]) renderFunction = renderItems[item.role];
|
|
110
|
-
if (!renderFunction && renderItems?.['default']) renderFunction = renderItems['default'];
|
|
111
|
-
if (!renderFunction) return;
|
|
112
|
-
return renderFunction;
|
|
113
|
-
}, [renderItems?.[item.role]]);
|
|
114
|
-
|
|
115
|
-
const RenderMessage = useCallback(
|
|
116
|
-
({ editableContent, data }: { data: ChatMessage; editableContent: ReactNode }) => {
|
|
117
|
-
if (!renderMessages || !item?.role) return;
|
|
118
|
-
let RenderFunction;
|
|
119
|
-
if (renderMessages?.[item.role]) RenderFunction = renderMessages[item.role];
|
|
120
|
-
if (!RenderFunction && renderMessages?.['default'])
|
|
121
|
-
RenderFunction = renderMessages['default'];
|
|
122
|
-
if (!RenderFunction) return;
|
|
123
|
-
return <RenderFunction {...data} editableContent={editableContent} />;
|
|
124
|
-
},
|
|
125
|
-
[renderMessages?.[item.role]],
|
|
126
|
-
);
|
|
127
|
-
|
|
128
|
-
const MessageExtra = useCallback(
|
|
129
|
-
({ data }: { data: ChatMessage }) => {
|
|
130
|
-
if (!renderMessagesExtra || !item?.role) return;
|
|
131
|
-
let RenderFunction;
|
|
132
|
-
if (renderMessagesExtra?.[item.role]) RenderFunction = renderMessagesExtra[item.role];
|
|
133
|
-
if (renderMessagesExtra?.['default']) RenderFunction = renderMessagesExtra['default'];
|
|
134
|
-
if (!RenderFunction) return;
|
|
135
|
-
return <RenderFunction {...data} />;
|
|
136
|
-
},
|
|
137
|
-
[renderMessagesExtra?.[item.role]],
|
|
138
|
-
);
|
|
139
|
-
|
|
140
|
-
const ErrorMessage = useCallback(
|
|
141
|
-
({ data }: { data: ChatMessage }) => {
|
|
142
|
-
if (!renderErrorMessages || !item?.error?.type) return;
|
|
143
|
-
let RenderFunction;
|
|
144
|
-
if (renderErrorMessages?.[item.error.type])
|
|
145
|
-
RenderFunction = renderErrorMessages[item.error.type].Render;
|
|
146
|
-
if (!RenderFunction && renderErrorMessages?.['default'])
|
|
147
|
-
RenderFunction = renderErrorMessages['default'].Render;
|
|
148
|
-
if (!RenderFunction) return;
|
|
149
|
-
return <RenderFunction {...data} />;
|
|
150
|
-
},
|
|
151
|
-
[renderErrorMessages?.[item?.error?.type]],
|
|
152
|
-
);
|
|
153
|
-
|
|
154
|
-
const Actions = useCallback(
|
|
155
|
-
({ data }: { data: ChatMessage }) => {
|
|
156
|
-
if (!renderActions || !item?.role) return;
|
|
157
|
-
let RenderFunction;
|
|
158
|
-
if (renderActions?.[item.role]) RenderFunction = renderActions[item.role];
|
|
159
|
-
if (renderActions?.['default']) RenderFunction = renderActions['default'];
|
|
160
|
-
if (!RenderFunction) RenderFunction = ActionsBar;
|
|
161
|
-
|
|
162
|
-
const handleActionClick: ListItemProps['onActionsClick'] = async (action, data) => {
|
|
163
|
-
switch (action.key) {
|
|
164
|
-
case 'copy': {
|
|
165
|
-
await copyToClipboard(data.content);
|
|
166
|
-
message.success(text?.copySuccess || 'Copy Success');
|
|
167
|
-
break;
|
|
168
|
-
}
|
|
169
|
-
case 'edit': {
|
|
170
|
-
setEditing(true);
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
onActionsClick?.(action, data);
|
|
175
|
-
};
|
|
176
|
-
|
|
177
|
-
return (
|
|
178
|
-
<RenderFunction
|
|
179
|
-
{...data}
|
|
180
|
-
onActionClick={(actionKey) => handleActionClick?.(actionKey, data)}
|
|
181
|
-
text={text}
|
|
182
|
-
/>
|
|
183
|
-
);
|
|
184
|
-
},
|
|
185
|
-
[renderActions?.[item.role], text, onActionsClick],
|
|
186
|
-
);
|
|
187
|
-
|
|
188
|
-
const error = useMemo(() => {
|
|
189
|
-
if (!item.error) return;
|
|
190
|
-
const message = item.error?.message;
|
|
191
|
-
let alertConfig = {};
|
|
192
|
-
if (item.error.type && renderErrorMessages?.[item.error.type]) {
|
|
193
|
-
alertConfig = renderErrorMessages[item.error.type]?.config as AlertProps;
|
|
194
|
-
}
|
|
195
|
-
return {
|
|
196
|
-
message,
|
|
197
|
-
...alertConfig,
|
|
198
|
-
};
|
|
199
|
-
}, [renderErrorMessages, item.error]);
|
|
200
|
-
|
|
201
|
-
if (RenderItem) return <RenderItem key={item.id} {...props} />;
|
|
202
|
-
|
|
203
|
-
return (
|
|
204
|
-
<ChatItem
|
|
205
|
-
actions={<Actions data={item} />}
|
|
206
|
-
avatar={item.meta}
|
|
207
|
-
avatarAddon={groupNav}
|
|
208
|
-
editing={editing}
|
|
209
|
-
error={error}
|
|
210
|
-
errorMessage={<ErrorMessage data={item} />}
|
|
211
|
-
loading={loading}
|
|
212
|
-
message={item.content}
|
|
213
|
-
messageExtra={<MessageExtra data={item} />}
|
|
214
|
-
onAvatarClick={onAvatarsClick?.(item.role)}
|
|
215
|
-
onChange={(value) => onMessageChange?.(item.id, value)}
|
|
216
|
-
onDoubleClick={(e) => {
|
|
217
|
-
if (item.id === 'default' || item.error) return;
|
|
218
|
-
if (item.role && ['assistant', 'user'].includes(item.role) && e.altKey) {
|
|
219
|
-
setEditing(true);
|
|
220
|
-
}
|
|
221
|
-
}}
|
|
222
|
-
onEditingChange={setEditing}
|
|
223
|
-
placement={type === 'chat' ? (item.role === 'user' ? 'right' : 'left') : 'left'}
|
|
224
|
-
primary={item.role === 'user'}
|
|
225
|
-
renderMessage={(editableContent) => (
|
|
226
|
-
<RenderMessage data={item} editableContent={editableContent} />
|
|
227
|
-
)}
|
|
228
|
-
showTitle={showTitle}
|
|
229
|
-
text={text}
|
|
230
|
-
time={item.updateAt || item.createAt}
|
|
231
|
-
type={type === 'chat' ? 'block' : 'pure'}
|
|
232
|
-
/>
|
|
233
|
-
);
|
|
234
|
-
});
|
|
235
|
-
|
|
236
|
-
export default Item;
|
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
import isEqual from 'fast-deep-equal';
|
|
2
|
-
import { memo } from 'react';
|
|
3
|
-
import { useHotkeys } from 'react-hotkeys-hook';
|
|
4
|
-
import { useTranslation } from 'react-i18next';
|
|
5
|
-
|
|
6
|
-
import { PREFIX_KEY, REGENERATE_KEY } from '@/const/hotkeys';
|
|
7
|
-
import { useChatStore } from '@/store/chat';
|
|
8
|
-
import { chatSelectors } from '@/store/chat/selectors';
|
|
9
|
-
import { useSessionStore } from '@/store/session';
|
|
10
|
-
import { agentSelectors } from '@/store/session/selectors';
|
|
11
|
-
|
|
12
|
-
import { renderActions, useActionsClick } from '../Actions';
|
|
13
|
-
import { renderErrorMessages } from '../Error';
|
|
14
|
-
import { renderMessagesExtra } from '../Extras';
|
|
15
|
-
import { renderMessages, useAvatarsClick } from '../Messages';
|
|
16
|
-
import ChatList from '../components/ChatList';
|
|
17
|
-
import SkeletonList from '../components/SkeletonList';
|
|
18
|
-
|
|
19
|
-
const List = memo(() => {
|
|
20
|
-
const { t } = useTranslation('common');
|
|
21
|
-
const meta = useSessionStore(agentSelectors.currentAgentMeta, isEqual);
|
|
22
|
-
const data = useChatStore(chatSelectors.currentChatsWithGuideMessage(meta), isEqual);
|
|
23
|
-
|
|
24
|
-
const [init, chatLoadingId, resendMessage, updateMessageContent] = useChatStore((s) => [
|
|
25
|
-
s.messagesInit,
|
|
26
|
-
s.chatLoadingId,
|
|
27
|
-
s.resendMessage,
|
|
28
|
-
s.updateMessageContent,
|
|
29
|
-
]);
|
|
30
|
-
|
|
31
|
-
const [displayMode, enableHistoryCount, historyCount] = useSessionStore((s) => {
|
|
32
|
-
const config = agentSelectors.currentAgentConfig(s);
|
|
33
|
-
return [config.displayMode, config.enableHistoryCount, config.historyCount];
|
|
34
|
-
});
|
|
35
|
-
const onActionsClick = useActionsClick();
|
|
36
|
-
const onAvatarsClick = useAvatarsClick();
|
|
37
|
-
|
|
38
|
-
const hotkeys = [PREFIX_KEY, REGENERATE_KEY].join('+');
|
|
39
|
-
|
|
40
|
-
useHotkeys(
|
|
41
|
-
hotkeys,
|
|
42
|
-
() => {
|
|
43
|
-
const lastMessage = data.at(-1);
|
|
44
|
-
if (!lastMessage || lastMessage.id === 'default' || lastMessage.role === 'system') return;
|
|
45
|
-
resendMessage(lastMessage.id);
|
|
46
|
-
},
|
|
47
|
-
{
|
|
48
|
-
enableOnFormTags: true,
|
|
49
|
-
preventDefault: true,
|
|
50
|
-
},
|
|
51
|
-
);
|
|
52
|
-
|
|
53
|
-
if (!init) return <SkeletonList />;
|
|
54
|
-
|
|
55
|
-
return (
|
|
56
|
-
<ChatList
|
|
57
|
-
data={data as any}
|
|
58
|
-
enableHistoryCount={enableHistoryCount}
|
|
59
|
-
historyCount={historyCount}
|
|
60
|
-
loadingId={chatLoadingId}
|
|
61
|
-
onActionsClick={onActionsClick}
|
|
62
|
-
onAvatarsClick={onAvatarsClick}
|
|
63
|
-
onMessageChange={(id, content) => updateMessageContent(id, content)}
|
|
64
|
-
renderActions={renderActions}
|
|
65
|
-
renderErrorMessages={renderErrorMessages}
|
|
66
|
-
renderMessages={renderMessages}
|
|
67
|
-
renderMessagesExtra={renderMessagesExtra}
|
|
68
|
-
text={{
|
|
69
|
-
cancel: t('cancel'),
|
|
70
|
-
confirm: t('ok'),
|
|
71
|
-
copy: t('copy'),
|
|
72
|
-
copySuccess: t('copySuccess'),
|
|
73
|
-
delete: t('delete'),
|
|
74
|
-
edit: t('edit'),
|
|
75
|
-
history: t('historyRange'),
|
|
76
|
-
regenerate: t('regenerate'),
|
|
77
|
-
}}
|
|
78
|
-
type={displayMode || 'chat'}
|
|
79
|
-
/>
|
|
80
|
-
);
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
export default List;
|