@lobehub/chat 1.33.5 → 1.34.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 +25 -0
- package/changelog/v1.json +9 -0
- package/locales/ar/chat.json +7 -0
- package/locales/ar/common.json +2 -0
- package/locales/ar/models.json +24 -0
- package/locales/ar/setting.json +5 -0
- package/locales/ar/thread.json +5 -0
- package/locales/bg-BG/chat.json +7 -0
- package/locales/bg-BG/common.json +2 -0
- package/locales/bg-BG/models.json +24 -0
- package/locales/bg-BG/setting.json +5 -0
- package/locales/bg-BG/thread.json +5 -0
- package/locales/de-DE/chat.json +7 -0
- package/locales/de-DE/common.json +2 -0
- package/locales/de-DE/models.json +24 -0
- package/locales/de-DE/setting.json +5 -0
- package/locales/de-DE/thread.json +5 -0
- package/locales/en-US/chat.json +7 -0
- package/locales/en-US/common.json +2 -0
- package/locales/en-US/models.json +24 -0
- package/locales/en-US/setting.json +5 -0
- package/locales/en-US/thread.json +5 -0
- package/locales/es-ES/chat.json +7 -0
- package/locales/es-ES/common.json +2 -0
- package/locales/es-ES/models.json +24 -0
- package/locales/es-ES/setting.json +5 -0
- package/locales/es-ES/thread.json +5 -0
- package/locales/fa-IR/chat.json +7 -0
- package/locales/fa-IR/common.json +2 -0
- package/locales/fa-IR/models.json +24 -0
- package/locales/fa-IR/setting.json +5 -0
- package/locales/fa-IR/thread.json +5 -0
- package/locales/fr-FR/chat.json +7 -0
- package/locales/fr-FR/common.json +2 -0
- package/locales/fr-FR/models.json +24 -0
- package/locales/fr-FR/setting.json +5 -0
- package/locales/fr-FR/thread.json +5 -0
- package/locales/it-IT/chat.json +7 -0
- package/locales/it-IT/common.json +2 -0
- package/locales/it-IT/models.json +24 -0
- package/locales/it-IT/setting.json +5 -0
- package/locales/it-IT/thread.json +5 -0
- package/locales/ja-JP/chat.json +7 -0
- package/locales/ja-JP/common.json +2 -0
- package/locales/ja-JP/models.json +24 -0
- package/locales/ja-JP/setting.json +5 -0
- package/locales/ja-JP/thread.json +5 -0
- package/locales/ko-KR/chat.json +7 -0
- package/locales/ko-KR/common.json +2 -0
- package/locales/ko-KR/models.json +24 -0
- package/locales/ko-KR/setting.json +5 -0
- package/locales/ko-KR/thread.json +5 -0
- package/locales/nl-NL/chat.json +7 -0
- package/locales/nl-NL/common.json +2 -0
- package/locales/nl-NL/models.json +24 -0
- package/locales/nl-NL/setting.json +5 -0
- package/locales/nl-NL/thread.json +5 -0
- package/locales/pl-PL/chat.json +7 -0
- package/locales/pl-PL/common.json +2 -0
- package/locales/pl-PL/models.json +24 -0
- package/locales/pl-PL/setting.json +5 -0
- package/locales/pl-PL/thread.json +5 -0
- package/locales/pt-BR/chat.json +7 -0
- package/locales/pt-BR/common.json +2 -0
- package/locales/pt-BR/models.json +24 -0
- package/locales/pt-BR/setting.json +5 -0
- package/locales/pt-BR/thread.json +5 -0
- package/locales/ru-RU/chat.json +7 -0
- package/locales/ru-RU/common.json +2 -0
- package/locales/ru-RU/models.json +24 -0
- package/locales/ru-RU/setting.json +5 -0
- package/locales/ru-RU/thread.json +5 -0
- package/locales/tr-TR/chat.json +7 -0
- package/locales/tr-TR/common.json +2 -0
- package/locales/tr-TR/models.json +24 -0
- package/locales/tr-TR/setting.json +5 -0
- package/locales/tr-TR/thread.json +5 -0
- package/locales/vi-VN/chat.json +7 -0
- package/locales/vi-VN/common.json +2 -0
- package/locales/vi-VN/models.json +24 -0
- package/locales/vi-VN/setting.json +5 -0
- package/locales/vi-VN/thread.json +5 -0
- package/locales/zh-CN/chat.json +7 -0
- package/locales/zh-CN/common.json +2 -0
- package/locales/zh-CN/models.json +24 -0
- package/locales/zh-CN/setting.json +5 -0
- package/locales/zh-CN/thread.json +5 -0
- package/locales/zh-TW/chat.json +7 -0
- package/locales/zh-TW/common.json +2 -0
- package/locales/zh-TW/models.json +24 -0
- package/locales/zh-TW/setting.json +5 -0
- package/locales/zh-TW/thread.json +5 -0
- package/package.json +1 -1
- package/src/app/(main)/chat/(workspace)/@conversation/default.tsx +2 -0
- package/src/app/(main)/chat/(workspace)/@conversation/features/ChatHydration/index.tsx +11 -2
- package/src/{features → app/(main)/chat/(workspace)/@conversation/features}/ChatInput/Desktop/Footer/index.tsx +7 -9
- package/src/app/(main)/chat/(workspace)/@conversation/features/ChatInput/Desktop/index.tsx +7 -2
- package/src/app/(main)/chat/(workspace)/@conversation/features/ChatList/ChatItem/Thread.tsx +62 -0
- package/src/app/(main)/chat/(workspace)/@conversation/features/ChatList/ChatItem/ThreadItem.tsx +68 -0
- package/src/app/(main)/chat/(workspace)/@conversation/features/ChatList/ChatItem/index.tsx +62 -2
- package/src/app/(main)/chat/(workspace)/@conversation/features/ThreadHydration.tsx +47 -0
- package/src/app/(main)/chat/(workspace)/@portal/_layout/Desktop.tsx +3 -2
- package/src/app/(main)/chat/(workspace)/@portal/_layout/Mobile.tsx +47 -6
- package/src/app/(main)/chat/(workspace)/@topic/features/SkeletonList.tsx +3 -2
- package/src/app/(main)/chat/(workspace)/@topic/features/TopicListContent/ByTimeMode/index.tsx +10 -3
- package/src/app/(main)/chat/(workspace)/@topic/features/TopicListContent/FlatMode/index.tsx +1 -1
- package/src/app/(main)/chat/(workspace)/@topic/features/TopicListContent/ThreadItem/Content.tsx +164 -0
- package/src/app/(main)/chat/(workspace)/@topic/features/TopicListContent/ThreadItem/index.tsx +98 -0
- package/src/app/(main)/chat/(workspace)/@topic/features/TopicListContent/{TopicItem.tsx → TopicItem/index.tsx} +33 -22
- package/src/app/(main)/chat/(workspace)/_layout/Desktop/Portal.tsx +12 -5
- package/src/app/(main)/chat/(workspace)/_layout/Mobile/index.tsx +1 -2
- package/src/const/message.ts +2 -0
- package/src/const/settings/systemAgent.ts +1 -0
- package/src/database/server/migrations/0012_add_thread.sql +39 -0
- package/src/database/server/migrations/meta/0012_snapshot.json +3671 -0
- package/src/database/server/migrations/meta/_journal.json +7 -0
- package/src/database/server/models/_template.ts +2 -2
- package/src/database/server/models/message.ts +1 -0
- package/src/database/server/models/thread.ts +79 -0
- package/src/database/server/schemas/lobechat/message.ts +2 -1
- package/src/database/server/schemas/lobechat/relations.ts +13 -1
- package/src/database/server/schemas/lobechat/topic.ts +30 -1
- package/src/database/server/utils/idGenerator.ts +1 -0
- package/src/features/ChatInput/ActionBar/Token/TokenTag.tsx +6 -4
- package/src/features/ChatInput/ActionBar/Token/index.tsx +24 -5
- package/src/features/ChatInput/ActionBar/config.ts +3 -2
- package/src/features/ChatInput/Desktop/index.tsx +15 -7
- package/src/features/ChatInput/Mobile/index.tsx +4 -4
- package/src/features/Conversation/Actions/Assistant.tsx +24 -5
- package/src/features/Conversation/Actions/User.tsx +21 -4
- package/src/features/Conversation/Actions/index.ts +1 -66
- package/src/features/Conversation/Messages/{Tool → Assistant/ToolCallItem}/Inspector/index.tsx +3 -1
- package/src/features/Conversation/Messages/{Tool/index.tsx → Assistant/ToolCallItem/Tool.tsx} +10 -11
- package/src/features/Conversation/Messages/Assistant/ToolCallItem/index.tsx +5 -3
- package/src/features/Conversation/Messages/Assistant/index.tsx +22 -14
- package/src/features/Conversation/Messages/index.ts +0 -2
- package/src/features/Conversation/components/AutoScroll.tsx +1 -1
- package/src/features/Conversation/components/ChatItem/ActionsBar.tsx +79 -5
- package/src/features/Conversation/components/ChatItem/InPortalThreadContext.ts +3 -0
- package/src/features/Conversation/components/ChatItem/index.tsx +16 -5
- package/src/features/Conversation/components/MarkdownElements/LobeArtifact/Render/index.tsx +9 -1
- package/src/features/Conversation/components/ThreadDivider/index.tsx +19 -0
- package/src/features/Conversation/hooks/useChatListActionsBar.tsx +19 -4
- package/src/features/Portal/Thread/Chat/ChatInput/Footer.tsx +90 -0
- package/src/features/Portal/Thread/Chat/ChatInput/TextArea.tsx +30 -0
- package/src/features/Portal/Thread/Chat/ChatInput/index.tsx +66 -0
- package/src/features/Portal/Thread/Chat/ChatInput/useSend.ts +50 -0
- package/src/features/Portal/Thread/Chat/ChatItem.tsx +62 -0
- package/src/features/Portal/Thread/Chat/ChatList.tsx +49 -0
- package/src/features/Portal/Thread/Chat/ThreadDivider/index.tsx +19 -0
- package/src/features/Portal/Thread/Chat/index.tsx +28 -0
- package/src/features/Portal/Thread/Header/Active.tsx +35 -0
- package/src/features/Portal/Thread/Header/New.tsx +37 -0
- package/src/features/Portal/Thread/Header/Title.tsx +18 -0
- package/src/features/Portal/Thread/Header/index.tsx +20 -0
- package/src/features/Portal/Thread/hook.ts +8 -0
- package/src/features/Portal/Thread/index.ts +12 -0
- package/src/features/Portal/router.tsx +2 -1
- package/src/hooks/useFetchTopics.ts +7 -1
- package/src/locales/default/chat.ts +8 -1
- package/src/locales/default/common.ts +3 -0
- package/src/locales/default/index.ts +2 -0
- package/src/locales/default/setting.ts +5 -0
- package/src/locales/default/thread.ts +5 -0
- package/src/server/routers/lambda/index.ts +2 -0
- package/src/server/routers/lambda/thread.ts +83 -0
- package/src/services/thread.ts +54 -0
- package/src/store/chat/initialState.ts +3 -0
- package/src/store/chat/selectors.ts +2 -1
- package/src/store/chat/slices/aiChat/actions/__tests__/generateAIChat.test.ts +1 -1
- package/src/store/chat/slices/aiChat/actions/__tests__/rag.test.ts +1 -1
- package/src/store/chat/slices/aiChat/actions/generateAIChat.ts +31 -8
- package/src/store/chat/slices/aiChat/actions/rag.ts +1 -1
- package/src/store/chat/slices/message/selectors.test.ts +3 -3
- package/src/store/chat/slices/message/selectors.ts +50 -29
- package/src/store/chat/slices/plugin/action.ts +26 -8
- package/src/store/chat/slices/portal/action.ts +1 -0
- package/src/store/chat/slices/portal/initialState.ts +1 -0
- package/src/store/chat/slices/portal/selectors/thread.ts +17 -0
- package/src/store/chat/slices/portal/selectors.ts +2 -0
- package/src/store/chat/slices/thread/action.ts +326 -0
- package/src/store/chat/slices/thread/initialState.ts +34 -0
- package/src/store/chat/slices/thread/reducer.ts +48 -0
- package/src/store/chat/slices/thread/selectors/index.ts +202 -0
- package/src/store/chat/slices/thread/selectors/util.ts +22 -0
- package/src/store/chat/slices/topic/action.ts +5 -1
- package/src/store/chat/store.ts +5 -2
- package/src/store/global/initialState.ts +4 -0
- package/src/store/global/selectors.ts +4 -0
- package/src/store/user/slices/settings/selectors/systemAgent.ts +2 -0
- package/src/types/message/index.ts +17 -1
- package/src/types/topic/index.ts +1 -0
- package/src/types/topic/thread.ts +42 -0
- package/src/types/user/settings/systemAgent.ts +1 -0
- package/src/app/(main)/chat/(workspace)/@portal/features/Header.tsx +0 -11
- package/src/app/(main)/chat/(workspace)/_layout/Mobile/PortalModal.tsx +0 -35
- /package/src/{features → app/(main)/chat/(workspace)/@conversation/features}/ChatInput/Desktop/Footer/SendMore.tsx +0 -0
- /package/src/{features → app/(main)/chat/(workspace)/@conversation/features}/ChatInput/Desktop/Footer/ShortcutHint.tsx +0 -0
- /package/src/app/(main)/chat/(workspace)/@topic/features/TopicListContent/{DefaultContent.tsx → TopicItem/DefaultContent.tsx} +0 -0
- /package/src/app/(main)/chat/(workspace)/@topic/features/TopicListContent/{TopicContent.tsx → TopicItem/TopicContent.tsx} +0 -0
- /package/src/features/Conversation/Messages/{Tool → Assistant/ToolCallItem}/Inspector/PluginResultJSON.tsx +0 -0
- /package/src/features/Conversation/Messages/{Tool → Assistant/ToolCallItem}/Inspector/Settings.tsx +0 -0
- /package/src/features/Conversation/Messages/{Tool → Assistant/ToolCallItem}/Inspector/style.ts +0 -0
@@ -0,0 +1,326 @@
|
|
1
|
+
/* eslint-disable sort-keys-fix/sort-keys-fix, typescript-sort-keys/interface */
|
2
|
+
// Disable the auto sort key eslint rule to make the code more logic and readable
|
3
|
+
import isEqual from 'fast-deep-equal';
|
4
|
+
import { SWRResponse, mutate } from 'swr';
|
5
|
+
import { StateCreator } from 'zustand/vanilla';
|
6
|
+
|
7
|
+
import { chainSummaryTitle } from '@/chains/summaryTitle';
|
8
|
+
import { LOADING_FLAT, THREAD_DRAFT_ID } from '@/const/message';
|
9
|
+
import { isServerMode } from '@/const/version';
|
10
|
+
import { useClientDataSWR } from '@/libs/swr';
|
11
|
+
import { chatService } from '@/services/chat';
|
12
|
+
import { threadService } from '@/services/thread';
|
13
|
+
import { threadSelectors } from '@/store/chat/selectors';
|
14
|
+
import { ChatStore } from '@/store/chat/store';
|
15
|
+
import { useSessionStore } from '@/store/session';
|
16
|
+
import { useUserStore } from '@/store/user';
|
17
|
+
import { systemAgentSelectors } from '@/store/user/selectors';
|
18
|
+
import { ChatMessage, CreateMessageParams, SendThreadMessageParams } from '@/types/message';
|
19
|
+
import { ThreadItem, ThreadType } from '@/types/topic';
|
20
|
+
import { merge } from '@/utils/merge';
|
21
|
+
import { setNamespace } from '@/utils/storeDebug';
|
22
|
+
|
23
|
+
import { ThreadDispatch, threadReducer } from './reducer';
|
24
|
+
|
25
|
+
const n = setNamespace('thd');
|
26
|
+
const SWR_USE_FETCH_THREADS = 'SWR_USE_FETCH_THREADS';
|
27
|
+
|
28
|
+
export interface ChatThreadAction {
|
29
|
+
// update
|
30
|
+
updateThreadInputMessage: (message: string) => void;
|
31
|
+
refreshThreads: () => Promise<void>;
|
32
|
+
/**
|
33
|
+
* Sends a new thread message to the AI chat system
|
34
|
+
*/
|
35
|
+
sendThreadMessage: (params: SendThreadMessageParams) => Promise<void>;
|
36
|
+
resendThreadMessage: (messageId: string) => Promise<void>;
|
37
|
+
delAndResendThreadMessage: (messageId: string) => Promise<void>;
|
38
|
+
createThread: (params: {
|
39
|
+
message: CreateMessageParams;
|
40
|
+
sourceMessageId: string;
|
41
|
+
topicId: string;
|
42
|
+
type: ThreadType;
|
43
|
+
}) => Promise<{ threadId: string; messageId: string }>;
|
44
|
+
openThreadCreator: (messageId: string) => void;
|
45
|
+
openThreadInPortal: (threadId: string, sourceMessageId: string) => void;
|
46
|
+
closeThreadPortal: () => void;
|
47
|
+
useFetchThreads: (topicId?: string) => SWRResponse<ThreadItem[]>;
|
48
|
+
summaryThreadTitle: (threadId: string, messages: ChatMessage[]) => Promise<void>;
|
49
|
+
updateThreadTitle: (id: string, title: string) => Promise<void>;
|
50
|
+
removeThread: (id: string) => Promise<void>;
|
51
|
+
switchThread: (id: string) => void;
|
52
|
+
|
53
|
+
internal_updateThreadTitleInSummary: (id: string, title: string) => void;
|
54
|
+
internal_updateThreadLoading: (id: string, loading: boolean) => void;
|
55
|
+
internal_updateThread: (id: string, data: Partial<ThreadItem>) => Promise<void>;
|
56
|
+
internal_dispatchThread: (payload: ThreadDispatch, action?: any) => void;
|
57
|
+
}
|
58
|
+
|
59
|
+
export const chatThreadMessage: StateCreator<
|
60
|
+
ChatStore,
|
61
|
+
[['zustand/devtools', never]],
|
62
|
+
[],
|
63
|
+
ChatThreadAction
|
64
|
+
> = (set, get) => ({
|
65
|
+
updateThreadInputMessage: (message) => {
|
66
|
+
if (isEqual(message, get().threadInputMessage)) return;
|
67
|
+
|
68
|
+
set({ threadInputMessage: message }, false, n(`updateThreadInputMessage`, message));
|
69
|
+
},
|
70
|
+
|
71
|
+
openThreadCreator: (messageId) => {
|
72
|
+
set(
|
73
|
+
{ threadStartMessageId: messageId, portalThreadId: undefined, startToForkThread: true },
|
74
|
+
false,
|
75
|
+
'openThreadCreator',
|
76
|
+
);
|
77
|
+
get().togglePortal(true);
|
78
|
+
},
|
79
|
+
openThreadInPortal: (threadId, sourceMessageId) => {
|
80
|
+
set(
|
81
|
+
{ portalThreadId: threadId, threadStartMessageId: sourceMessageId, startToForkThread: false },
|
82
|
+
false,
|
83
|
+
'openThreadInPortal',
|
84
|
+
);
|
85
|
+
get().togglePortal(true);
|
86
|
+
},
|
87
|
+
|
88
|
+
closeThreadPortal: () => {
|
89
|
+
set(
|
90
|
+
{ threadStartMessageId: undefined, portalThreadId: undefined, startToForkThread: undefined },
|
91
|
+
false,
|
92
|
+
'closeThreadPortal',
|
93
|
+
);
|
94
|
+
get().togglePortal(false);
|
95
|
+
},
|
96
|
+
sendThreadMessage: async ({ message }) => {
|
97
|
+
const {
|
98
|
+
internal_coreProcessMessage,
|
99
|
+
activeTopicId,
|
100
|
+
activeId,
|
101
|
+
threadStartMessageId,
|
102
|
+
newThreadMode,
|
103
|
+
portalThreadId,
|
104
|
+
} = get();
|
105
|
+
if (!activeId || !activeTopicId) return;
|
106
|
+
|
107
|
+
// if message is empty or no files, then stop
|
108
|
+
if (!message) return;
|
109
|
+
|
110
|
+
set({ isCreatingThreadMessage: true }, false, n('creatingThreadMessage/start'));
|
111
|
+
|
112
|
+
const newMessage: CreateMessageParams = {
|
113
|
+
content: message,
|
114
|
+
// if message has attached with files, then add files to message and the agent
|
115
|
+
// files: fileIdList,
|
116
|
+
role: 'user',
|
117
|
+
sessionId: activeId,
|
118
|
+
// if there is activeTopicId,then add topicId to message
|
119
|
+
topicId: activeTopicId,
|
120
|
+
threadId: portalThreadId,
|
121
|
+
};
|
122
|
+
|
123
|
+
let parentMessageId: string | undefined = undefined;
|
124
|
+
let tempMessageId: string | undefined = undefined;
|
125
|
+
|
126
|
+
// if there is no portalThreadId, then create a thread and then append message
|
127
|
+
if (!portalThreadId) {
|
128
|
+
if (!threadStartMessageId) return;
|
129
|
+
// we need to create a temp message for optimistic update
|
130
|
+
tempMessageId = get().internal_createTmpMessage({
|
131
|
+
...newMessage,
|
132
|
+
threadId: THREAD_DRAFT_ID,
|
133
|
+
});
|
134
|
+
get().internal_toggleMessageLoading(true, tempMessageId);
|
135
|
+
|
136
|
+
const { threadId, messageId } = await get().createThread({
|
137
|
+
message: newMessage,
|
138
|
+
sourceMessageId: threadStartMessageId,
|
139
|
+
topicId: activeTopicId,
|
140
|
+
type: newThreadMode,
|
141
|
+
});
|
142
|
+
|
143
|
+
parentMessageId = messageId;
|
144
|
+
|
145
|
+
// mark the portal in thread mode
|
146
|
+
await get().refreshThreads();
|
147
|
+
await get().refreshMessages();
|
148
|
+
|
149
|
+
get().openThreadInPortal(threadId, threadStartMessageId);
|
150
|
+
} else {
|
151
|
+
// if there is a thread, just append message
|
152
|
+
// we need to create a temp message for optimistic update
|
153
|
+
tempMessageId = get().internal_createTmpMessage(newMessage);
|
154
|
+
get().internal_toggleMessageLoading(true, tempMessageId);
|
155
|
+
|
156
|
+
parentMessageId = await get().internal_createMessage(newMessage, { tempMessageId });
|
157
|
+
}
|
158
|
+
|
159
|
+
get().internal_toggleMessageLoading(false, tempMessageId);
|
160
|
+
|
161
|
+
// update assistant update to make it rerank
|
162
|
+
useSessionStore.getState().triggerSessionUpdate(get().activeId);
|
163
|
+
|
164
|
+
// Get the current messages to generate AI response
|
165
|
+
const messages = threadSelectors.portalAIChats(get());
|
166
|
+
|
167
|
+
await internal_coreProcessMessage(messages, parentMessageId, {
|
168
|
+
ragQuery: get().internal_shouldUseRAG() ? message : undefined,
|
169
|
+
threadId: get().portalThreadId,
|
170
|
+
inPortalThread: true,
|
171
|
+
});
|
172
|
+
|
173
|
+
set({ isCreatingThreadMessage: false }, false, n('creatingThreadMessage/stop'));
|
174
|
+
|
175
|
+
// 说明是在新建 thread,需要自动总结标题
|
176
|
+
if (!portalThreadId) {
|
177
|
+
const portalThread = threadSelectors.currentPortalThread(get());
|
178
|
+
|
179
|
+
if (!portalThread) return;
|
180
|
+
|
181
|
+
const chats = threadSelectors.portalAIChats(get());
|
182
|
+
await get().summaryThreadTitle(portalThread.id, chats);
|
183
|
+
}
|
184
|
+
},
|
185
|
+
resendThreadMessage: async (messageId) => {
|
186
|
+
const chats = threadSelectors.portalAIChats(get());
|
187
|
+
|
188
|
+
await get().internal_resendMessage(messageId, {
|
189
|
+
messages: chats,
|
190
|
+
threadId: get().portalThreadId,
|
191
|
+
inPortalThread: true,
|
192
|
+
});
|
193
|
+
},
|
194
|
+
delAndResendThreadMessage: async (id) => {
|
195
|
+
get().resendThreadMessage(id);
|
196
|
+
get().deleteMessage(id);
|
197
|
+
},
|
198
|
+
createThread: async ({ message, sourceMessageId, topicId, type }) => {
|
199
|
+
set({ isCreatingThread: true }, false, n('creatingThread/start'));
|
200
|
+
|
201
|
+
const data = await threadService.createThreadWithMessage({
|
202
|
+
topicId,
|
203
|
+
sourceMessageId,
|
204
|
+
type,
|
205
|
+
message,
|
206
|
+
});
|
207
|
+
set({ isCreatingThread: false }, false, n('creatingThread/end'));
|
208
|
+
|
209
|
+
return data;
|
210
|
+
},
|
211
|
+
|
212
|
+
useFetchThreads: (topicId) =>
|
213
|
+
useClientDataSWR<ThreadItem[]>(
|
214
|
+
!!topicId && isServerMode ? [SWR_USE_FETCH_THREADS, topicId] : null,
|
215
|
+
async ([, topicId]: [string, string]) => threadService.getThreads(topicId),
|
216
|
+
{
|
217
|
+
suspense: true,
|
218
|
+
fallbackData: [],
|
219
|
+
onSuccess: (threads) => {
|
220
|
+
const nextMap = { ...get().threadMaps, [topicId!]: threads };
|
221
|
+
|
222
|
+
// no need to update map if the topics have been init and the map is the same
|
223
|
+
if (get().topicsInit && isEqual(nextMap, get().topicMaps)) return;
|
224
|
+
|
225
|
+
set(
|
226
|
+
{ threadMaps: nextMap, threadsInit: true },
|
227
|
+
false,
|
228
|
+
n('useFetchThreads(success)', { topicId }),
|
229
|
+
);
|
230
|
+
},
|
231
|
+
},
|
232
|
+
),
|
233
|
+
|
234
|
+
refreshThreads: async () => {
|
235
|
+
const topicId = get().activeTopicId;
|
236
|
+
if (!topicId) return;
|
237
|
+
|
238
|
+
return mutate([SWR_USE_FETCH_THREADS, topicId]);
|
239
|
+
},
|
240
|
+
removeThread: async (id) => {
|
241
|
+
await threadService.removeThread(id);
|
242
|
+
await get().refreshThreads();
|
243
|
+
|
244
|
+
if (get().activeThreadId === id) {
|
245
|
+
set({ activeThreadId: undefined });
|
246
|
+
}
|
247
|
+
},
|
248
|
+
switchThread: async (id) => {
|
249
|
+
set({ activeThreadId: id }, false, n('toggleTopic'));
|
250
|
+
},
|
251
|
+
updateThreadTitle: async (id, title) => {
|
252
|
+
await get().internal_updateThread(id, { title });
|
253
|
+
},
|
254
|
+
|
255
|
+
summaryThreadTitle: async (threadId, messages) => {
|
256
|
+
const { internal_updateThreadTitleInSummary, internal_updateThreadLoading } = get();
|
257
|
+
const portalThread = threadSelectors.currentPortalThread(get());
|
258
|
+
if (!portalThread) return;
|
259
|
+
|
260
|
+
internal_updateThreadTitleInSummary(threadId, LOADING_FLAT);
|
261
|
+
|
262
|
+
let output = '';
|
263
|
+
const threadConfig = systemAgentSelectors.thread(useUserStore.getState());
|
264
|
+
|
265
|
+
await chatService.fetchPresetTaskResult({
|
266
|
+
onError: () => {
|
267
|
+
internal_updateThreadTitleInSummary(threadId, portalThread.title);
|
268
|
+
},
|
269
|
+
onFinish: async (text) => {
|
270
|
+
await get().internal_updateThread(threadId, { title: text });
|
271
|
+
},
|
272
|
+
onLoadingChange: (loading) => {
|
273
|
+
internal_updateThreadLoading(threadId, loading);
|
274
|
+
},
|
275
|
+
onMessageHandle: (chunk) => {
|
276
|
+
switch (chunk.type) {
|
277
|
+
case 'text': {
|
278
|
+
output += chunk.text;
|
279
|
+
}
|
280
|
+
}
|
281
|
+
|
282
|
+
internal_updateThreadTitleInSummary(threadId, output);
|
283
|
+
},
|
284
|
+
params: merge(threadConfig, chainSummaryTitle(messages)),
|
285
|
+
});
|
286
|
+
},
|
287
|
+
|
288
|
+
// Internal process method of the topics
|
289
|
+
internal_updateThreadTitleInSummary: (id, title) => {
|
290
|
+
get().internal_dispatchThread(
|
291
|
+
{ type: 'updateThread', id, value: { title } },
|
292
|
+
'updateThreadTitleInSummary',
|
293
|
+
);
|
294
|
+
},
|
295
|
+
|
296
|
+
internal_updateThreadLoading: (id, loading) => {
|
297
|
+
set(
|
298
|
+
(state) => {
|
299
|
+
if (loading) return { threadLoadingIds: [...state.threadLoadingIds, id] };
|
300
|
+
|
301
|
+
return { threadLoadingIds: state.threadLoadingIds.filter((i) => i !== id) };
|
302
|
+
},
|
303
|
+
false,
|
304
|
+
n('updateThreadLoading'),
|
305
|
+
);
|
306
|
+
},
|
307
|
+
|
308
|
+
internal_updateThread: async (id, data) => {
|
309
|
+
get().internal_dispatchThread({ type: 'updateThread', id, value: data });
|
310
|
+
|
311
|
+
get().internal_updateThreadLoading(id, true);
|
312
|
+
await threadService.updateThread(id, data);
|
313
|
+
await get().refreshThreads();
|
314
|
+
get().internal_updateThreadLoading(id, false);
|
315
|
+
},
|
316
|
+
|
317
|
+
internal_dispatchThread: (payload, action) => {
|
318
|
+
const nextThreads = threadReducer(threadSelectors.currentTopicThreads(get()), payload);
|
319
|
+
const nextMap = { ...get().threadMaps, [get().activeTopicId!]: nextThreads };
|
320
|
+
|
321
|
+
// no need to update map if is the same
|
322
|
+
if (isEqual(nextMap, get().threadMaps)) return;
|
323
|
+
|
324
|
+
set({ threadMaps: nextMap }, false, action ?? n(`dispatchThread/${payload.type}`));
|
325
|
+
},
|
326
|
+
});
|
@@ -0,0 +1,34 @@
|
|
1
|
+
import { ThreadItem, ThreadType } from '@/types/topic';
|
2
|
+
|
3
|
+
export interface ChatThreadState {
|
4
|
+
activeThreadId?: string;
|
5
|
+
/**
|
6
|
+
* is creating thread with service call
|
7
|
+
*/
|
8
|
+
isCreatingThread?: boolean;
|
9
|
+
isCreatingThreadMessage?: boolean;
|
10
|
+
newThreadMode: ThreadType;
|
11
|
+
/**
|
12
|
+
* if true it mean to start to fork a new thread
|
13
|
+
*/
|
14
|
+
startToForkThread?: boolean;
|
15
|
+
|
16
|
+
threadInputMessage: string;
|
17
|
+
threadLoadingIds: string[];
|
18
|
+
threadMaps: Record<string, ThreadItem[]>;
|
19
|
+
threadRenamingId?: string;
|
20
|
+
/**
|
21
|
+
* when open thread creator, set the message id to it
|
22
|
+
*/
|
23
|
+
threadStartMessageId?: string;
|
24
|
+
threadsInit?: boolean;
|
25
|
+
}
|
26
|
+
|
27
|
+
export const initialThreadState: ChatThreadState = {
|
28
|
+
isCreatingThread: false,
|
29
|
+
newThreadMode: ThreadType.Continuation,
|
30
|
+
threadInputMessage: '',
|
31
|
+
threadLoadingIds: [],
|
32
|
+
threadMaps: {},
|
33
|
+
threadsInit: false,
|
34
|
+
};
|
@@ -0,0 +1,48 @@
|
|
1
|
+
import { produce } from 'immer';
|
2
|
+
|
3
|
+
import { ThreadItem } from '@/types/topic';
|
4
|
+
|
5
|
+
interface UpdateThreadAction {
|
6
|
+
id: string;
|
7
|
+
type: 'updateThread';
|
8
|
+
value: Partial<ThreadItem>;
|
9
|
+
}
|
10
|
+
|
11
|
+
interface DeleteThreadAction {
|
12
|
+
id: string;
|
13
|
+
type: 'deleteThread';
|
14
|
+
}
|
15
|
+
|
16
|
+
export type ThreadDispatch = UpdateThreadAction | DeleteThreadAction;
|
17
|
+
|
18
|
+
export const threadReducer = (state: ThreadItem[] = [], payload: ThreadDispatch): ThreadItem[] => {
|
19
|
+
switch (payload.type) {
|
20
|
+
case 'updateThread': {
|
21
|
+
return produce(state, (draftState) => {
|
22
|
+
const { value, id } = payload;
|
23
|
+
const threadIndex = draftState.findIndex((thread) => thread.id === id);
|
24
|
+
|
25
|
+
if (threadIndex !== -1) {
|
26
|
+
draftState[threadIndex] = {
|
27
|
+
...draftState[threadIndex],
|
28
|
+
...value,
|
29
|
+
updatedAt: new Date(),
|
30
|
+
};
|
31
|
+
}
|
32
|
+
});
|
33
|
+
}
|
34
|
+
|
35
|
+
case 'deleteThread': {
|
36
|
+
return produce(state, (draftState) => {
|
37
|
+
const threadIndex = draftState.findIndex((thread) => thread.id === payload.id);
|
38
|
+
if (threadIndex !== -1) {
|
39
|
+
draftState.splice(threadIndex, 1);
|
40
|
+
}
|
41
|
+
});
|
42
|
+
}
|
43
|
+
|
44
|
+
default: {
|
45
|
+
return state;
|
46
|
+
}
|
47
|
+
}
|
48
|
+
};
|
@@ -0,0 +1,202 @@
|
|
1
|
+
import { THREAD_DRAFT_ID } from '@/const/message';
|
2
|
+
import { useAgentStore } from '@/store/agent';
|
3
|
+
import { agentSelectors } from '@/store/agent/selectors';
|
4
|
+
import type { ChatStoreState } from '@/store/chat';
|
5
|
+
import { chatHelpers } from '@/store/chat/helpers';
|
6
|
+
import { ChatMessage } from '@/types/message';
|
7
|
+
import { ThreadItem } from '@/types/topic';
|
8
|
+
|
9
|
+
import { chatSelectors } from '../../message/selectors';
|
10
|
+
import { genMessage } from './util';
|
11
|
+
|
12
|
+
const currentTopicThreads = (s: ChatStoreState) => {
|
13
|
+
if (!s.activeTopicId) return [];
|
14
|
+
|
15
|
+
return s.threadMaps[s.activeTopicId] || [];
|
16
|
+
};
|
17
|
+
|
18
|
+
const currentPortalThread = (s: ChatStoreState): ThreadItem | undefined => {
|
19
|
+
if (!s.portalThreadId) return undefined;
|
20
|
+
|
21
|
+
const threads = currentTopicThreads(s);
|
22
|
+
|
23
|
+
return threads.find((t) => t.id === s.portalThreadId);
|
24
|
+
};
|
25
|
+
|
26
|
+
const threadStartMessageId = (s: ChatStoreState) => s.threadStartMessageId;
|
27
|
+
|
28
|
+
const threadSourceMessageId = (s: ChatStoreState) => {
|
29
|
+
if (s.startToForkThread) return threadStartMessageId(s);
|
30
|
+
|
31
|
+
const portalThread = currentPortalThread(s);
|
32
|
+
return portalThread?.sourceMessageId;
|
33
|
+
};
|
34
|
+
|
35
|
+
const getTheadParentMessages = (s: ChatStoreState, data: ChatMessage[]) => {
|
36
|
+
if (s.startToForkThread) {
|
37
|
+
const startMessageId = threadStartMessageId(s)!;
|
38
|
+
|
39
|
+
// 存在 threadId 的消息是子消息,在创建付消息时需要忽略
|
40
|
+
const messages = data.filter((m) => !m.threadId);
|
41
|
+
return genMessage(messages, startMessageId, s.newThreadMode);
|
42
|
+
}
|
43
|
+
|
44
|
+
const portalThread = currentPortalThread(s);
|
45
|
+
return genMessage(data, portalThread?.sourceMessageId, portalThread?.type);
|
46
|
+
};
|
47
|
+
|
48
|
+
// ======= Portal Thread Display Chats ======= //
|
49
|
+
// =========================================== //
|
50
|
+
|
51
|
+
/**
|
52
|
+
* 获取当前 thread 的父级消息
|
53
|
+
*/
|
54
|
+
const portalDisplayParentMessages = (s: ChatStoreState): ChatMessage[] => {
|
55
|
+
const data = chatSelectors.activeBaseChatsWithoutTool(s);
|
56
|
+
|
57
|
+
return getTheadParentMessages(s, data);
|
58
|
+
};
|
59
|
+
|
60
|
+
/**
|
61
|
+
* these messages are the messages that are in the thread
|
62
|
+
*
|
63
|
+
*/
|
64
|
+
const portalDisplayChildChatsByThreadId =
|
65
|
+
(id?: string) =>
|
66
|
+
(s: ChatStoreState): ChatMessage[] => {
|
67
|
+
// skip tool message
|
68
|
+
const data = chatSelectors.activeBaseChatsWithoutTool(s);
|
69
|
+
|
70
|
+
return data.filter((m) => !!id && m.threadId === id);
|
71
|
+
};
|
72
|
+
|
73
|
+
const portalDisplayChats = (s: ChatStoreState) => {
|
74
|
+
const parentMessages = portalDisplayParentMessages(s);
|
75
|
+
const afterMessages = portalDisplayChildChatsByThreadId(s.portalThreadId)(s);
|
76
|
+
// use for optimistic update
|
77
|
+
const draftMessage = chatSelectors.activeBaseChats(s).find((m) => m.threadId === THREAD_DRAFT_ID);
|
78
|
+
|
79
|
+
return [...parentMessages, draftMessage, ...afterMessages].filter(Boolean) as ChatMessage[];
|
80
|
+
};
|
81
|
+
|
82
|
+
const portalDisplayChatsLength = (s: ChatStoreState) => {
|
83
|
+
// history length include a thread divider
|
84
|
+
return portalDisplayChats(s).length;
|
85
|
+
};
|
86
|
+
const portalDisplayChatsString = (s: ChatStoreState) => {
|
87
|
+
const messages = portalDisplayChats(s);
|
88
|
+
|
89
|
+
return messages.map((m) => m.content).join('');
|
90
|
+
};
|
91
|
+
|
92
|
+
const portalDisplayChatIDs = (s: ChatStoreState): string[] =>
|
93
|
+
portalDisplayChats(s).map((i) => i.id);
|
94
|
+
|
95
|
+
// ========= Portal Thread AI Chats ========= //
|
96
|
+
// ========================================== //
|
97
|
+
|
98
|
+
const portalAIParentMessages = (s: ChatStoreState): ChatMessage[] => {
|
99
|
+
const data = chatSelectors.activeBaseChats(s);
|
100
|
+
|
101
|
+
return getTheadParentMessages(s, data);
|
102
|
+
};
|
103
|
+
|
104
|
+
const portalAIChildChatsByThreadId =
|
105
|
+
(id?: string) =>
|
106
|
+
(s: ChatStoreState): ChatMessage[] => {
|
107
|
+
// skip tool message
|
108
|
+
const data = chatSelectors.activeBaseChats(s);
|
109
|
+
|
110
|
+
return data.filter((m) => !!id && m.threadId === id);
|
111
|
+
};
|
112
|
+
|
113
|
+
const portalAIChats = (s: ChatStoreState) => {
|
114
|
+
const parentMessages = portalAIParentMessages(s);
|
115
|
+
const afterMessages = portalAIChildChatsByThreadId(s.portalThreadId)(s);
|
116
|
+
|
117
|
+
return [...parentMessages, ...afterMessages].filter(Boolean) as ChatMessage[];
|
118
|
+
};
|
119
|
+
|
120
|
+
const portalAIChatsWithHistoryConfig = (s: ChatStoreState) => {
|
121
|
+
const parentMessages = portalAIParentMessages(s);
|
122
|
+
const afterMessages = portalAIChildChatsByThreadId(s.portalThreadId)(s);
|
123
|
+
|
124
|
+
const messages = [...parentMessages, ...afterMessages].filter(Boolean) as ChatMessage[];
|
125
|
+
|
126
|
+
const config = agentSelectors.currentAgentChatConfig(useAgentStore.getState());
|
127
|
+
|
128
|
+
return chatHelpers.getSlicedMessagesWithConfig(messages, config);
|
129
|
+
};
|
130
|
+
|
131
|
+
const threadSourceMessageIndex = (s: ChatStoreState) => {
|
132
|
+
const theadMessageId = threadSourceMessageId(s);
|
133
|
+
const data = portalDisplayChats(s);
|
134
|
+
|
135
|
+
return !theadMessageId ? -1 : data.findIndex((d) => d.id === theadMessageId);
|
136
|
+
};
|
137
|
+
const getThreadsByTopic = (topicId?: string) => (s: ChatStoreState) => {
|
138
|
+
if (!topicId) return;
|
139
|
+
|
140
|
+
return s.threadMaps[topicId];
|
141
|
+
};
|
142
|
+
|
143
|
+
const getFirstThreadBySourceMsgId = (id: string) => (s: ChatStoreState) => {
|
144
|
+
const threads = currentTopicThreads(s);
|
145
|
+
|
146
|
+
return threads.find((t) => t.sourceMessageId === id);
|
147
|
+
};
|
148
|
+
|
149
|
+
const getThreadsBySourceMsgId = (id: string) => (s: ChatStoreState) => {
|
150
|
+
const threads = currentTopicThreads(s);
|
151
|
+
|
152
|
+
return threads.filter((t) => t.sourceMessageId === id);
|
153
|
+
};
|
154
|
+
|
155
|
+
const hasThreadBySourceMsgId = (id: string) => (s: ChatStoreState) => {
|
156
|
+
const threads = currentTopicThreads(s);
|
157
|
+
|
158
|
+
return threads.some((t) => t.sourceMessageId === id);
|
159
|
+
};
|
160
|
+
|
161
|
+
const isThreadAIGenerating = (s: ChatStoreState) =>
|
162
|
+
s.chatLoadingIds.some((id) => portalDisplayChatIDs(s).includes(id));
|
163
|
+
|
164
|
+
const isInRAGFlow = (s: ChatStoreState) =>
|
165
|
+
s.messageRAGLoadingIds.some((id) => portalDisplayChatIDs(s).includes(id));
|
166
|
+
const isCreatingMessage = (s: ChatStoreState) => s.isCreatingThreadMessage;
|
167
|
+
const isHasMessageLoading = (s: ChatStoreState) =>
|
168
|
+
s.messageLoadingIds.some((id) => portalDisplayChatIDs(s).includes(id));
|
169
|
+
|
170
|
+
/**
|
171
|
+
* this function is used to determine whether the send button should be disabled
|
172
|
+
*/
|
173
|
+
const isSendButtonDisabledByMessage = (s: ChatStoreState) =>
|
174
|
+
// 1. when there is message loading
|
175
|
+
isHasMessageLoading(s) ||
|
176
|
+
// 2. when is creating the topic
|
177
|
+
s.isCreatingThread ||
|
178
|
+
// 3. when is creating the message
|
179
|
+
isCreatingMessage(s) ||
|
180
|
+
// 4. when the message is in RAG flow
|
181
|
+
isInRAGFlow(s);
|
182
|
+
|
183
|
+
export const threadSelectors = {
|
184
|
+
currentPortalThread,
|
185
|
+
currentTopicThreads,
|
186
|
+
getFirstThreadBySourceMsgId,
|
187
|
+
getThreadsBySourceMsgId,
|
188
|
+
getThreadsByTopic,
|
189
|
+
hasThreadBySourceMsgId,
|
190
|
+
isSendButtonDisabledByMessage,
|
191
|
+
isThreadAIGenerating,
|
192
|
+
portalAIChats,
|
193
|
+
portalAIChatsWithHistoryConfig,
|
194
|
+
portalDisplayChatIDs,
|
195
|
+
portalDisplayChats,
|
196
|
+
portalDisplayChatsLength,
|
197
|
+
portalDisplayChatsString,
|
198
|
+
portalDisplayChildChatsByThreadId,
|
199
|
+
threadSourceMessageId,
|
200
|
+
threadSourceMessageIndex,
|
201
|
+
threadStartMessageId,
|
202
|
+
};
|
@@ -0,0 +1,22 @@
|
|
1
|
+
import { ChatMessage } from '@/types/message';
|
2
|
+
import { ThreadType } from '@/types/topic';
|
3
|
+
|
4
|
+
export const genMessage = (
|
5
|
+
messages: ChatMessage[],
|
6
|
+
startMessageId: string | undefined,
|
7
|
+
threadMode?: ThreadType,
|
8
|
+
) => {
|
9
|
+
if (!startMessageId) return [];
|
10
|
+
|
11
|
+
// 如果是独立话题模式,则只显示话题开始消息
|
12
|
+
if (threadMode === ThreadType.Standalone) {
|
13
|
+
return messages.filter((m) => m.id === startMessageId);
|
14
|
+
}
|
15
|
+
|
16
|
+
// 如果是连续模式下,那么只显示话题开始消息和话题分割线
|
17
|
+
const targetIndex = messages.findIndex((item) => item.id === startMessageId);
|
18
|
+
|
19
|
+
if (targetIndex < 0) return [];
|
20
|
+
|
21
|
+
return messages.slice(0, targetIndex + 1);
|
22
|
+
};
|
@@ -223,7 +223,11 @@ export const chatTopic: StateCreator<
|
|
223
223
|
},
|
224
224
|
),
|
225
225
|
switchTopic: async (id, skipRefreshMessage) => {
|
226
|
-
set(
|
226
|
+
set(
|
227
|
+
{ activeTopicId: !id ? (null as any) : id, activeThreadId: undefined },
|
228
|
+
false,
|
229
|
+
n('toggleTopic'),
|
230
|
+
);
|
227
231
|
|
228
232
|
if (skipRefreshMessage) return;
|
229
233
|
await get().refreshMessages();
|
package/src/store/chat/store.ts
CHANGED
@@ -13,11 +13,13 @@ import { ChatMessageAction, chatMessage } from './slices/message/action';
|
|
13
13
|
import { ChatPluginAction, chatPlugin } from './slices/plugin/action';
|
14
14
|
import { ShareAction, chatShare } from './slices/share/action';
|
15
15
|
import { ChatTopicAction, chatTopic } from './slices/topic/action';
|
16
|
-
import {
|
17
|
-
import {
|
16
|
+
import { ChatAIChatAction, chatAiChat } from './slices/aiChat/actions';
|
17
|
+
import { ChatTTSAction, chatTTS } from './slices/tts/action';
|
18
|
+
import { ChatThreadAction, chatThreadMessage } from './slices/thread/action';
|
18
19
|
|
19
20
|
export interface ChatStoreAction
|
20
21
|
extends ChatMessageAction,
|
22
|
+
ChatThreadAction,
|
21
23
|
ChatAIChatAction,
|
22
24
|
ChatTopicAction,
|
23
25
|
ShareAction,
|
@@ -35,6 +37,7 @@ const createStore: StateCreator<ChatStore, [['zustand/devtools', never]]> = (...
|
|
35
37
|
...initialState,
|
36
38
|
|
37
39
|
...chatMessage(...params),
|
40
|
+
...chatThreadMessage(...params),
|
38
41
|
...chatAiChat(...params),
|
39
42
|
...chatTopic(...params),
|
40
43
|
...chatShare(...params),
|