UNPKG

@lobehub/chat

Version:

Lobe Chat - an open-source, high-performance chatbot framework that supports speech synthesis, multimodal, and extensible Function Call plugin system. Supports one-click free deployment of your private ChatGPT/LLM web application.

207 lines (160 loc) 6.67 kB
import { THREAD_DRAFT_ID } from '@/const/message'; import { useAgentStore } from '@/store/agent'; import { agentChatConfigSelectors } from '@/store/agent/selectors'; import type { ChatStoreState } from '@/store/chat'; import { chatHelpers } from '@/store/chat/helpers'; import { ChatMessage } from '@/types/message'; import { ThreadItem } from '@/types/topic'; import { chatSelectors } from '../../message/selectors'; import { genMessage } from './util'; const currentTopicThreads = (s: ChatStoreState) => { if (!s.activeTopicId) return []; return s.threadMaps[s.activeTopicId] || []; }; const currentPortalThread = (s: ChatStoreState): ThreadItem | undefined => { if (!s.portalThreadId) return undefined; const threads = currentTopicThreads(s); return threads.find((t) => t.id === s.portalThreadId); }; const threadStartMessageId = (s: ChatStoreState) => s.threadStartMessageId; const threadSourceMessageId = (s: ChatStoreState) => { if (s.startToForkThread) return threadStartMessageId(s); const portalThread = currentPortalThread(s); return portalThread?.sourceMessageId; }; const getTheadParentMessages = (s: ChatStoreState, data: ChatMessage[]) => { if (s.startToForkThread) { const startMessageId = threadStartMessageId(s)!; // 存在 threadId 的消息是子消息,在创建付消息时需要忽略 const messages = data.filter((m) => !m.threadId); return genMessage(messages, startMessageId, s.newThreadMode); } const portalThread = currentPortalThread(s); return genMessage(data, portalThread?.sourceMessageId, portalThread?.type); }; // ======= Portal Thread Display Chats ======= // // =========================================== // /** * 获取当前 thread 的父级消息 */ const portalDisplayParentMessages = (s: ChatStoreState): ChatMessage[] => { const data = chatSelectors.activeBaseChatsWithoutTool(s); return getTheadParentMessages(s, data); }; /** * these messages are the messages that are in the thread * */ const portalDisplayChildChatsByThreadId = (id?: string) => (s: ChatStoreState): ChatMessage[] => { // skip tool message const data = chatSelectors.activeBaseChatsWithoutTool(s); return data.filter((m) => !!id && m.threadId === id); }; const portalDisplayChats = (s: ChatStoreState) => { const parentMessages = portalDisplayParentMessages(s); const afterMessages = portalDisplayChildChatsByThreadId(s.portalThreadId)(s); // use for optimistic update const draftMessage = chatSelectors.activeBaseChats(s).find((m) => m.threadId === THREAD_DRAFT_ID); return [...parentMessages, draftMessage, ...afterMessages].filter(Boolean) as ChatMessage[]; }; const portalDisplayChatsLength = (s: ChatStoreState) => { // history length include a thread divider return portalDisplayChats(s).length; }; const portalDisplayChatsString = (s: ChatStoreState) => { const messages = portalDisplayChats(s); return messages.map((m) => m.content).join(''); }; const portalDisplayChatIDs = (s: ChatStoreState): string[] => portalDisplayChats(s).map((i) => i.id); // ========= Portal Thread AI Chats ========= // // ========================================== // const portalAIParentMessages = (s: ChatStoreState): ChatMessage[] => { const data = chatSelectors.activeBaseChats(s); return getTheadParentMessages(s, data); }; const portalAIChildChatsByThreadId = (id?: string) => (s: ChatStoreState): ChatMessage[] => { // skip tool message const data = chatSelectors.activeBaseChats(s); return data.filter((m) => !!id && m.threadId === id); }; const portalAIChats = (s: ChatStoreState) => { const parentMessages = portalAIParentMessages(s); const afterMessages = portalAIChildChatsByThreadId(s.portalThreadId)(s); return [...parentMessages, ...afterMessages].filter(Boolean) as ChatMessage[]; }; const portalAIChatsWithHistoryConfig = (s: ChatStoreState) => { const parentMessages = portalAIParentMessages(s); const afterMessages = portalAIChildChatsByThreadId(s.portalThreadId)(s); const messages = [...parentMessages, ...afterMessages].filter(Boolean) as ChatMessage[]; const enableHistoryCount = agentChatConfigSelectors.enableHistoryCount(useAgentStore.getState()); const historyCount = agentChatConfigSelectors.historyCount(useAgentStore.getState()); return chatHelpers.getSlicedMessages(messages, { enableHistoryCount, historyCount, }); }; const threadSourceMessageIndex = (s: ChatStoreState) => { const theadMessageId = threadSourceMessageId(s); const data = portalDisplayChats(s); return !theadMessageId ? -1 : data.findIndex((d) => d.id === theadMessageId); }; const getThreadsByTopic = (topicId?: string) => (s: ChatStoreState) => { if (!topicId) return; return s.threadMaps[topicId]; }; const getFirstThreadBySourceMsgId = (id: string) => (s: ChatStoreState) => { const threads = currentTopicThreads(s); return threads.find((t) => t.sourceMessageId === id); }; const getThreadsBySourceMsgId = (id: string) => (s: ChatStoreState) => { const threads = currentTopicThreads(s); return threads.filter((t) => t.sourceMessageId === id); }; const hasThreadBySourceMsgId = (id: string) => (s: ChatStoreState) => { const threads = currentTopicThreads(s); return threads.some((t) => t.sourceMessageId === id); }; const isThreadAIGenerating = (s: ChatStoreState) => s.chatLoadingIds.some((id) => portalDisplayChatIDs(s).includes(id)); const isInRAGFlow = (s: ChatStoreState) => s.messageRAGLoadingIds.some((id) => portalDisplayChatIDs(s).includes(id)); const isCreatingMessage = (s: ChatStoreState) => s.isCreatingThreadMessage; const isHasMessageLoading = (s: ChatStoreState) => s.messageLoadingIds.some((id) => portalDisplayChatIDs(s).includes(id)); /** * this function is used to determine whether the send button should be disabled */ const isSendButtonDisabledByMessage = (s: ChatStoreState) => // 1. when there is message loading isHasMessageLoading(s) || // 2. when is creating the topic s.isCreatingThread || // 3. when is creating the message isCreatingMessage(s) || // 4. when the message is in RAG flow isInRAGFlow(s); export const threadSelectors = { currentPortalThread, currentTopicThreads, getFirstThreadBySourceMsgId, getThreadsBySourceMsgId, getThreadsByTopic, hasThreadBySourceMsgId, isSendButtonDisabledByMessage, isThreadAIGenerating, portalAIChats, portalAIChatsWithHistoryConfig, portalDisplayChatIDs, portalDisplayChats, portalDisplayChatsLength, portalDisplayChatsString, portalDisplayChildChatsByThreadId, threadSourceMessageId, threadSourceMessageIndex, threadStartMessageId, };