@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.
251 lines (250 loc) • 46.1 kB
JSON
[
{
"pageContent": "import { copyToClipboard } from '@lobehub/ui';\nimport isEqual from 'fast-deep-equal';\nimport { produce } from 'immer';\nimport { template } from 'lodash-es';\nimport { SWRResponse, mutate } from 'swr';\nimport { StateCreator } from 'zustand/vanilla';",
"metadata": { "loc": { "lines": { "from": 1, "to": 6 } } }
},
{
"pageContent": "import { chainAnswerWithContext } from '@/chains/answerWithContext';\nimport { LOADING_FLAT } from '@/const/message';\nimport { TraceEventType, TraceNameMap } from '@/const/trace';\nimport { isServerMode } from '@/const/version';\nimport { useClientDataSWR } from '@/libs/swr';\nimport { chatService } from '@/services/chat';\nimport { messageService } from '@/services/message';\nimport { topicService } from '@/services/topic';\nimport { traceService } from '@/services/trace';\nimport { useAgentStore } from '@/store/agent';\nimport { agentSelectors } from '@/store/agent/selectors';\nimport { chatHelpers } from '@/store/chat/helpers';\nimport { messageMapKey } from '@/store/chat/slices/message/utils';\nimport { ChatStore } from '@/store/chat/store';\nimport { useSessionStore } from '@/store/session';",
"metadata": { "loc": { "lines": { "from": 8, "to": 22 } } }
},
{
"pageContent": "import { traceService } from '@/services/trace';\nimport { useAgentStore } from '@/store/agent';\nimport { agentSelectors } from '@/store/agent/selectors';\nimport { chatHelpers } from '@/store/chat/helpers';\nimport { messageMapKey } from '@/store/chat/slices/message/utils';\nimport { ChatStore } from '@/store/chat/store';\nimport { useSessionStore } from '@/store/session';\nimport { UploadFileItem } from '@/types/files/upload';\nimport {\n ChatMessage,\n ChatMessageError,\n CreateMessageParams,\n MessageToolCall,\n} from '@/types/message';\nimport { TraceEventPayloads } from '@/types/trace';\nimport { setNamespace } from '@/utils/storeDebug';\nimport { nanoid } from '@/utils/uuid';",
"metadata": { "loc": { "lines": { "from": 16, "to": 32 } } }
},
{
"pageContent": "import type { ChatStoreState } from '../../initialState';\nimport { chatSelectors, topicSelectors } from '../../selectors';\nimport { preventLeavingFn, toggleBooleanList } from '../../utils';\nimport { ChatRAGAction, chatRag } from './actions/rag';\nimport { MessageDispatch, messagesReducer } from './reducer';",
"metadata": { "loc": { "lines": { "from": 34, "to": 38 } } }
},
{
"pageContent": "const n = setNamespace('m');",
"metadata": { "loc": { "lines": { "from": 40, "to": 40 } } }
},
{
"pageContent": "const SWR_USE_FETCH_MESSAGES = 'SWR_USE_FETCH_MESSAGES';\n\nexport interface SendMessageParams {\n message: string;\n files?: UploadFileItem[];\n onlyAddUserMessage?: boolean;\n /**\n *\n * https://github.com/lobehub/lobe-chat/pull/2086\n */\n isWelcomeQuestion?: boolean;\n}\n\ninterface ProcessMessageParams {\n traceId?: string;\n isWelcomeQuestion?: boolean;\n /**\n * the RAG query content, should be embedding and used in the semantic search\n */\n ragQuery?: string;\n}\n\nexport interface ChatMessageAction extends ChatRAGAction {\n // create\n sendMessage: (params: SendMessageParams) => Promise<void>;\n addAIMessage: () => Promise<void>;\n /**\n * regenerate message\n * trace enabled\n * @param id\n */\n regenerateMessage: (id: string) => Promise<void>;",
"metadata": { "loc": { "lines": { "from": 42, "to": 73 } } }
},
{
"pageContent": "// delete\n /**\n * clear message on the active session\n */\n clearMessage: () => Promise<void>;\n deleteMessage: (id: string) => Promise<void>;\n deleteToolMessage: (id: string) => Promise<void>;\n delAndRegenerateMessage: (id: string) => Promise<void>;\n clearAllMessages: () => Promise<void>;\n // update\n updateInputMessage: (message: string) => void;\n modifyMessageContent: (id: string, content: string) => Promise<void>;\n // query\n useFetchMessages: (sessionId: string, topicId?: string) => SWRResponse<ChatMessage[]>;\n stopGenerateMessage: () => void;\n copyMessage: (id: string, content: string) => Promise<void>;\n refreshMessages: () => Promise<void>;\n toggleMessageEditing: (id: string, editing: boolean) => void;",
"metadata": { "loc": { "lines": { "from": 75, "to": 92 } } }
},
{
"pageContent": "// ========= ↓ Internal Method ↓ ========== //\n // ========================================== //\n // ========================================== //",
"metadata": { "loc": { "lines": { "from": 94, "to": 96 } } }
},
{
"pageContent": "/**\n * update message at the frontend point\n * this method will not update messages to database\n */\n internal_dispatchMessage: (payload: MessageDispatch) => void;\n /**\n * core process of the AI message (include preprocess and postprocess)\n */\n internal_coreProcessMessage: (\n messages: ChatMessage[],\n parentId: string,\n params?: ProcessMessageParams,\n ) => Promise<void>;\n /**\n * the method to fetch the AI message\n */\n internal_fetchAIChatMessage: (\n messages: ChatMessage[],\n assistantMessageId: string,\n params?: ProcessMessageParams,\n ) => Promise<{\n isFunctionCall: boolean;\n traceId?: string;\n }>;",
"metadata": { "loc": { "lines": { "from": 98, "to": 121 } } }
},
{
"pageContent": "/**\n * update the message content with optimistic update\n * a method used by other action\n */\n internal_updateMessageContent: (\n id: string,\n content: string,\n toolCalls?: MessageToolCall[],\n ) => Promise<void>;\n /**\n * update the message error with optimistic update\n */\n internal_updateMessageError: (id: string, error: ChatMessageError | null) => Promise<void>;\n /**\n * create a message with optimistic update\n */\n internal_createMessage: (\n params: CreateMessageParams,\n context?: { tempMessageId?: string; skipRefresh?: boolean },\n ) => Promise<string>;\n /**\n * create a temp message for optimistic update\n * otherwise the message will be too slow to show\n */\n internal_createTmpMessage: (params: CreateMessageParams) => string;\n /**",
"metadata": { "loc": { "lines": { "from": 123, "to": 148 } } }
},
{
"pageContent": "/**\n * create a message with optimistic update\n */\n internal_createMessage: (\n params: CreateMessageParams,\n context?: { tempMessageId?: string; skipRefresh?: boolean },\n ) => Promise<string>;\n /**\n * create a temp message for optimistic update\n * otherwise the message will be too slow to show\n */\n internal_createTmpMessage: (params: CreateMessageParams) => string;\n /**\n * delete the message content with optimistic update\n */\n internal_deleteMessage: (id: string) => Promise<void>;\n internal_resendMessage: (id: string, traceId?: string) => Promise<void>;",
"metadata": { "loc": { "lines": { "from": 136, "to": 152 } } }
},
{
"pageContent": "internal_fetchMessages: () => Promise<void>;\n internal_traceMessage: (id: string, payload: TraceEventPayloads) => Promise<void>;",
"metadata": { "loc": { "lines": { "from": 154, "to": 155 } } }
},
{
"pageContent": "/**\n * method to toggle message create loading state\n * the AI message status is creating -> generating\n * other message role like user and tool , only this method need to be called\n */\n internal_toggleMessageLoading: (loading: boolean, id: string) => void;\n /**\n * method to toggle ai message generating loading\n */\n internal_toggleChatLoading: (\n loading: boolean,\n id?: string,\n action?: string,\n ) => AbortController | undefined;\n /**\n * method to toggle the tool calling loading state\n */\n internal_toggleToolCallingStreaming: (id: string, streaming: boolean[] | undefined) => void;\n /**\n * helper to toggle the loading state of the array,used by these three toggleXXXLoading\n */\n internal_toggleLoadingArrays: (\n key: keyof ChatStoreState,",
"metadata": { "loc": { "lines": { "from": 157, "to": 179 } } }
},
{
"pageContent": "action?: string,\n ) => AbortController | undefined;\n /**\n * method to toggle the tool calling loading state\n */\n internal_toggleToolCallingStreaming: (id: string, streaming: boolean[] | undefined) => void;\n /**\n * helper to toggle the loading state of the array,used by these three toggleXXXLoading\n */\n internal_toggleLoadingArrays: (\n key: keyof ChatStoreState,\n loading: boolean,\n id?: string,\n action?: string,\n ) => AbortController | undefined;\n}",
"metadata": { "loc": { "lines": { "from": 169, "to": 184 } } }
},
{
"pageContent": "const getAgentConfig = () => agentSelectors.currentAgentConfig(useAgentStore.getState());\nconst getAgentChatConfig = () => agentSelectors.currentAgentChatConfig(useAgentStore.getState());",
"metadata": { "loc": { "lines": { "from": 186, "to": 187 } } }
},
{
"pageContent": "const hasEnabledKnowledge = () => agentSelectors.hasEnabledKnowledge(useAgentStore.getState());\n\nexport const chatMessage: StateCreator<\n ChatStore,\n [['zustand/devtools', never]],\n [],\n ChatMessageAction\n> = (set, get, ...rest) => ({\n ...chatRag(set, get, ...rest),\n\n deleteMessage: async (id) => {\n const message = chatSelectors.getMessageById(id)(get());\n if (!message) return;\n\n let ids = [message.id];\n\n // if the message is a tool calls, then delete all the related messages\n if (message.tools) {\n const toolMessageIds = message.tools.flatMap((tool) => {\n const messages = chatSelectors\n .currentChats(get())\n .filter((m) => m.tool_call_id === tool.id);",
"metadata": { "loc": { "lines": { "from": 188, "to": 209 } } }
},
{
"pageContent": "let ids = [message.id];\n\n // if the message is a tool calls, then delete all the related messages\n if (message.tools) {\n const toolMessageIds = message.tools.flatMap((tool) => {\n const messages = chatSelectors\n .currentChats(get())\n .filter((m) => m.tool_call_id === tool.id);\n\n return messages.map((m) => m.id);\n });\n ids = ids.concat(toolMessageIds);\n }\n\n get().internal_dispatchMessage({ type: 'deleteMessages', ids });\n await messageService.removeMessages(ids);\n await get().refreshMessages();\n },\n\n deleteToolMessage: async (id) => {\n const message = chatSelectors.getMessageById(id)(get());\n if (!message || message.role !== 'tool') return;",
"metadata": { "loc": { "lines": { "from": 202, "to": 223 } } }
},
{
"pageContent": "get().internal_dispatchMessage({ type: 'deleteMessages', ids });\n await messageService.removeMessages(ids);\n await get().refreshMessages();\n },\n\n deleteToolMessage: async (id) => {\n const message = chatSelectors.getMessageById(id)(get());\n if (!message || message.role !== 'tool') return;\n\n const removeToolInAssistantMessage = async () => {\n if (!message.parentId) return;\n await get().internal_removeToolToAssistantMessage(message.parentId, message.tool_call_id);\n };\n\n await Promise.all([\n // 1. remove tool message\n get().internal_deleteMessage(id),\n // 2. remove the tool item in the assistant tools\n removeToolInAssistantMessage(),\n ]);\n },",
"metadata": { "loc": { "lines": { "from": 216, "to": 236 } } }
},
{
"pageContent": "await Promise.all([\n // 1. remove tool message\n get().internal_deleteMessage(id),\n // 2. remove the tool item in the assistant tools\n removeToolInAssistantMessage(),\n ]);\n },\n\n delAndRegenerateMessage: async (id) => {\n const traceId = chatSelectors.getTraceIdByMessageId(id)(get());\n get().internal_resendMessage(id, traceId);\n get().deleteMessage(id);\n\n // trace the delete and regenerate message\n get().internal_traceMessage(id, { eventType: TraceEventType.DeleteAndRegenerateMessage });\n },\n regenerateMessage: async (id: string) => {\n const traceId = chatSelectors.getTraceIdByMessageId(id)(get());\n await get().internal_resendMessage(id, traceId);",
"metadata": { "loc": { "lines": { "from": 230, "to": 248 } } }
},
{
"pageContent": "// trace the delete and regenerate message\n get().internal_traceMessage(id, { eventType: TraceEventType.DeleteAndRegenerateMessage });\n },\n regenerateMessage: async (id: string) => {\n const traceId = chatSelectors.getTraceIdByMessageId(id)(get());\n await get().internal_resendMessage(id, traceId);\n\n // trace the delete and regenerate message\n get().internal_traceMessage(id, { eventType: TraceEventType.RegenerateMessage });\n },\n clearMessage: async () => {\n const { activeId, activeTopicId, refreshMessages, refreshTopic, switchTopic } = get();\n\n await messageService.removeMessagesByAssistant(activeId, activeTopicId);\n\n if (activeTopicId) {\n await topicService.removeTopic(activeTopicId);\n }\n await refreshTopic();\n await refreshMessages();",
"metadata": { "loc": { "lines": { "from": 243, "to": 262 } } }
},
{
"pageContent": "await messageService.removeMessagesByAssistant(activeId, activeTopicId);\n\n if (activeTopicId) {\n await topicService.removeTopic(activeTopicId);\n }\n await refreshTopic();\n await refreshMessages();\n\n // after remove topic , go back to default topic\n switchTopic();\n },\n clearAllMessages: async () => {\n const { refreshMessages } = get();\n await messageService.removeAllMessages();\n await refreshMessages();\n },\n sendMessage: async ({ message, files, onlyAddUserMessage, isWelcomeQuestion }) => {\n const { internal_coreProcessMessage, activeTopicId, activeId } = get();\n if (!activeId) return;\n\n const fileIdList = files?.map((f) => f.id);\n\n const hasFile = !!fileIdList && fileIdList.length > 0;",
"metadata": { "loc": { "lines": { "from": 256, "to": 278 } } }
},
{
"pageContent": "const fileIdList = files?.map((f) => f.id);\n\n const hasFile = !!fileIdList && fileIdList.length > 0;\n\n // if message is empty or no files, then stop\n if (!message && !hasFile) return;\n\n set({ isCreatingMessage: true }, false, 'creatingMessage/start');\n\n const newMessage: CreateMessageParams = {\n content: message,\n // if message has attached with files, then add files to message and the agent\n files: fileIdList,\n role: 'user',\n sessionId: activeId,\n // if there is activeTopicId,then add topicId to message\n topicId: activeTopicId,\n };\n\n const agentConfig = getAgentChatConfig();\n\n let tempMessageId: string | undefined = undefined;\n let newTopicId: string | undefined = undefined;",
"metadata": { "loc": { "lines": { "from": 276, "to": 298 } } }
},
{
"pageContent": "const agentConfig = getAgentChatConfig();\n\n let tempMessageId: string | undefined = undefined;\n let newTopicId: string | undefined = undefined;\n\n // it should be the default topic, then\n // if autoCreateTopic is enabled, check to whether we need to create a topic\n if (!onlyAddUserMessage && !activeTopicId && agentConfig.enableAutoCreateTopic) {\n // check activeTopic and then auto create topic\n const chats = chatSelectors.currentChats(get());\n\n // we will add two messages (user and assistant), so the finial length should +2\n const featureLength = chats.length + 2;",
"metadata": { "loc": { "lines": { "from": 295, "to": 307 } } }
},
{
"pageContent": "// we will add two messages (user and assistant), so the finial length should +2\n const featureLength = chats.length + 2;\n\n // if there is no activeTopicId and the feature length is greater than the threshold\n // then create a new topic and active it\n if (!get().activeTopicId && featureLength >= agentConfig.autoCreateTopicThreshold) {\n // we need to create a temp message for optimistic update\n tempMessageId = get().internal_createTmpMessage(newMessage);\n get().internal_toggleMessageLoading(true, tempMessageId);\n\n const topicId = await get().createTopic();\n\n if (topicId) {\n newTopicId = topicId;\n newMessage.topicId = topicId;",
"metadata": { "loc": { "lines": { "from": 306, "to": 320 } } }
},
{
"pageContent": "const topicId = await get().createTopic();\n\n if (topicId) {\n newTopicId = topicId;\n newMessage.topicId = topicId;\n\n // we need to copy the messages to the new topic or the message will disappear\n const mapKey = chatSelectors.currentChatKey(get());\n const newMaps = {\n ...get().messagesMap,\n [messageMapKey(activeId, topicId)]: get().messagesMap[mapKey],\n };\n set({ messagesMap: newMaps }, false, 'internal_copyMessages');\n\n // get().internal_dispatchMessage({ type: 'deleteMessage', id: tempMessageId });\n get().internal_toggleMessageLoading(false, tempMessageId);",
"metadata": { "loc": { "lines": { "from": 316, "to": 331 } } }
},
{
"pageContent": "// get().internal_dispatchMessage({ type: 'deleteMessage', id: tempMessageId });\n get().internal_toggleMessageLoading(false, tempMessageId);\n\n // make the topic loading\n get().internal_updateTopicLoading(topicId, true);\n }\n }\n }\n // update assistant update to make it rerank\n useSessionStore.getState().triggerSessionUpdate(get().activeId);\n\n const id = await get().internal_createMessage(newMessage, {\n tempMessageId,\n skipRefresh: !onlyAddUserMessage,\n });\n\n // switch to the new topic if create the new topic\n if (!!newTopicId) {\n await get().switchTopic(newTopicId, true);\n await get().internal_fetchMessages();",
"metadata": { "loc": { "lines": { "from": 330, "to": 349 } } }
},
{
"pageContent": "const id = await get().internal_createMessage(newMessage, {\n tempMessageId,\n skipRefresh: !onlyAddUserMessage,\n });\n\n // switch to the new topic if create the new topic\n if (!!newTopicId) {\n await get().switchTopic(newTopicId, true);\n await get().internal_fetchMessages();\n\n // delete previous messages\n // remove the temp message map\n const newMaps = { ...get().messagesMap, [messageMapKey(activeId, null)]: [] };\n set({ messagesMap: newMaps }, false, 'internal_copyMessages');\n }\n\n // if only add user message, then stop\n if (onlyAddUserMessage) {\n set({ isCreatingMessage: false }, false, 'creatingMessage/start');\n return;\n }",
"metadata": { "loc": { "lines": { "from": 341, "to": 361 } } }
},
{
"pageContent": "// delete previous messages\n // remove the temp message map\n const newMaps = { ...get().messagesMap, [messageMapKey(activeId, null)]: [] };\n set({ messagesMap: newMaps }, false, 'internal_copyMessages');\n }\n\n // if only add user message, then stop\n if (onlyAddUserMessage) {\n set({ isCreatingMessage: false }, false, 'creatingMessage/start');\n return;\n }\n\n // Get the current messages to generate AI response\n const messages = chatSelectors.currentChats(get());\n const userFiles = chatSelectors.currentUserFiles(get()).map((f) => f.id);",
"metadata": { "loc": { "lines": { "from": 351, "to": 365 } } }
},
{
"pageContent": "// if only add user message, then stop\n if (onlyAddUserMessage) {\n set({ isCreatingMessage: false }, false, 'creatingMessage/start');\n return;\n }\n\n // Get the current messages to generate AI response\n const messages = chatSelectors.currentChats(get());\n const userFiles = chatSelectors.currentUserFiles(get()).map((f) => f.id);\n\n await internal_coreProcessMessage(messages, id, {\n isWelcomeQuestion,\n // if there is relative files or enabled knowledge, try with ragQuery\n ragQuery: hasEnabledKnowledge() || userFiles.length > 0 ? message : undefined,\n });\n\n set({ isCreatingMessage: false }, false, 'creatingMessage/stop');",
"metadata": { "loc": { "lines": { "from": 357, "to": 373 } } }
},
{
"pageContent": "await internal_coreProcessMessage(messages, id, {\n isWelcomeQuestion,\n // if there is relative files or enabled knowledge, try with ragQuery\n ragQuery: hasEnabledKnowledge() || userFiles.length > 0 ? message : undefined,\n });\n\n set({ isCreatingMessage: false }, false, 'creatingMessage/stop');\n\n const summaryTitle = async () => {\n // if autoCreateTopic is false, then stop\n if (!agentConfig.enableAutoCreateTopic) return;\n\n // check activeTopic and then auto update topic title\n if (newTopicId) {\n const chats = chatSelectors.currentChats(get());\n await get().summaryTopicTitle(newTopicId, chats);\n return;\n }\n\n const topic = topicSelectors.currentActiveTopic(get());",
"metadata": { "loc": { "lines": { "from": 367, "to": 386 } } }
},
{
"pageContent": "// check activeTopic and then auto update topic title\n if (newTopicId) {\n const chats = chatSelectors.currentChats(get());\n await get().summaryTopicTitle(newTopicId, chats);\n return;\n }\n\n const topic = topicSelectors.currentActiveTopic(get());\n\n if (topic && !topic.title) {\n const chats = chatSelectors.currentChats(get());\n await get().summaryTopicTitle(topic.id, chats);\n }\n };\n\n // if there is relative files, then add files to agent\n // only available in server mode\n const addFilesToAgent = async () => {\n if (userFiles.length === 0 || !isServerMode) return;\n\n await useAgentStore.getState().addFilesToAgent(userFiles, false);\n };",
"metadata": { "loc": { "lines": { "from": 379, "to": 400 } } }
},
{
"pageContent": "// if there is relative files, then add files to agent\n // only available in server mode\n const addFilesToAgent = async () => {\n if (userFiles.length === 0 || !isServerMode) return;\n\n await useAgentStore.getState().addFilesToAgent(userFiles, false);\n };\n\n await Promise.all([summaryTitle(), addFilesToAgent()]);\n },\n addAIMessage: async () => {\n const { internal_createMessage, updateInputMessage, activeTopicId, activeId, inputMessage } =\n get();\n if (!activeId) return;\n\n await internal_createMessage({\n content: inputMessage,\n role: 'assistant',\n sessionId: activeId,\n // if there is activeTopicId,then add topicId to message\n topicId: activeTopicId,\n });",
"metadata": { "loc": { "lines": { "from": 394, "to": 415 } } }
},
{
"pageContent": "await internal_createMessage({\n content: inputMessage,\n role: 'assistant',\n sessionId: activeId,\n // if there is activeTopicId,then add topicId to message\n topicId: activeTopicId,\n });\n\n updateInputMessage('');\n },\n copyMessage: async (id, content) => {\n await copyToClipboard(content);\n\n get().internal_traceMessage(id, { eventType: TraceEventType.CopyMessage });\n },\n toggleMessageEditing: (id, editing) => {\n set(\n { messageEditingIds: toggleBooleanList(get().messageEditingIds, id, editing) },\n false,\n 'toggleMessageEditing',\n );\n },\n stopGenerateMessage: () => {\n const { abortController, internal_toggleChatLoading } = get();\n if (!abortController) return;\n\n abortController.abort('canceled');",
"metadata": { "loc": { "lines": { "from": 409, "to": 435 } } }
},
{
"pageContent": "abortController.abort('canceled');\n\n internal_toggleChatLoading(false, undefined, n('stopGenerateMessage') as string);\n },\n\n updateInputMessage: (message) => {\n if (isEqual(message, get().inputMessage)) return;\n\n set({ inputMessage: message }, false, n('updateInputMessage', message));\n },\n modifyMessageContent: async (id, content) => {\n // tracing the diff of update\n // due to message content will change, so we need send trace before update,or will get wrong data\n get().internal_traceMessage(id, {\n eventType: TraceEventType.ModifyMessage,\n nextContent: content,\n });",
"metadata": { "loc": { "lines": { "from": 435, "to": 451 } } }
},
{
"pageContent": "await get().internal_updateMessageContent(id, content);\n },\n useFetchMessages: (sessionId, activeTopicId) =>\n useClientDataSWR<ChatMessage[]>(\n [SWR_USE_FETCH_MESSAGES, sessionId, activeTopicId],\n async ([, sessionId, topicId]: [string, string, string | undefined]) =>\n messageService.getMessages(sessionId, topicId),\n {\n onSuccess: (messages, key) => {\n const nextMap = {\n ...get().messagesMap,\n [messageMapKey(sessionId, activeTopicId)]: messages,\n };\n // no need to update map if the messages have been init and the map is the same\n if (get().messagesInit && isEqual(nextMap, get().messagesMap)) return;",
"metadata": { "loc": { "lines": { "from": 453, "to": 467 } } }
},
{
"pageContent": "set(\n { messagesInit: true, messagesMap: nextMap },\n false,\n n('useFetchMessages', { messages, queryKey: key }),\n );\n },\n },\n ),\n refreshMessages: async () => {\n await mutate([SWR_USE_FETCH_MESSAGES, get().activeId, get().activeTopicId]);\n },\n\n // the internal process method of the AI message\n internal_coreProcessMessage: async (originalMessages, userMessageId, params) => {\n const { internal_fetchAIChatMessage, triggerToolCalls, refreshMessages, activeTopicId } = get();\n\n // create a new array to avoid the original messages array change\n const messages = [...originalMessages];\n\n const { model, provider } = getAgentConfig();\n\n let fileChunkIds: string[] | undefined;",
"metadata": { "loc": { "lines": { "from": 469, "to": 490 } } }
},
{
"pageContent": "// create a new array to avoid the original messages array change\n const messages = [...originalMessages];\n\n const { model, provider } = getAgentConfig();\n\n let fileChunkIds: string[] | undefined;\n\n // go into RAG flow if there is ragQuery flag\n if (params?.ragQuery) {\n // 1. get the relative chunks from semantic search\n const chunks = await get().internal_retrieveChunks(\n userMessageId,\n params?.ragQuery,\n // should skip the last content\n messages.map((m) => m.content).slice(0, messages.length - 1),\n );\n console.log('召回 chunks', chunks);\n\n // 2. build the retrieve context messages\n const retrieveContext = chainAnswerWithContext(\n params?.ragQuery,\n chunks.map((c) => c.text as string),\n );",
"metadata": { "loc": { "lines": { "from": 485, "to": 507 } } }
},
{
"pageContent": "// 2. build the retrieve context messages\n const retrieveContext = chainAnswerWithContext(\n params?.ragQuery,\n chunks.map((c) => c.text as string),\n );\n\n // 3. add the retrieve context messages to the messages history\n if (retrieveContext.messages && retrieveContext.messages?.length > 0) {\n // remove the last message due to the query is in the retrieveContext\n messages.pop();\n retrieveContext.messages?.forEach((m) => messages.push(m as ChatMessage));\n }\n\n fileChunkIds = chunks.map((c) => c.id);\n }\n\n // 2. Add an empty message to place the AI response\n const assistantMessage: CreateMessageParams = {\n role: 'assistant',\n content: LOADING_FLAT,\n fromModel: model,\n fromProvider: provider,",
"metadata": { "loc": { "lines": { "from": 503, "to": 524 } } }
},
{
"pageContent": "fileChunkIds = chunks.map((c) => c.id);\n }\n\n // 2. Add an empty message to place the AI response\n const assistantMessage: CreateMessageParams = {\n role: 'assistant',\n content: LOADING_FLAT,\n fromModel: model,\n fromProvider: provider,\n\n parentId: userMessageId,\n sessionId: get().activeId,\n topicId: activeTopicId, // if there is activeTopicId,then add it to topicId\n fileChunkIds,\n ragQueryId: userMessageId,\n };\n\n const assistantId = await get().internal_createMessage(assistantMessage);\n\n // 3. fetch the AI response\n const { isFunctionCall } = await internal_fetchAIChatMessage(messages, assistantId, params);",
"metadata": { "loc": { "lines": { "from": 516, "to": 536 } } }
},
{
"pageContent": "const assistantId = await get().internal_createMessage(assistantMessage);\n\n // 3. fetch the AI response\n const { isFunctionCall } = await internal_fetchAIChatMessage(messages, assistantId, params);\n\n // 4. if it's the function call message, trigger the function method\n if (isFunctionCall) {\n await refreshMessages();\n await triggerToolCalls(assistantId);\n }\n },\n internal_dispatchMessage: (payload) => {\n const { activeId } = get();\n\n if (!activeId) return;\n\n const messages = messagesReducer(chatSelectors.currentChats(get()), payload);\n\n const nextMap = { ...get().messagesMap, [chatSelectors.currentChatKey(get())]: messages };\n\n if (isEqual(nextMap, get().messagesMap)) return;",
"metadata": { "loc": { "lines": { "from": 533, "to": 553 } } }
},
{
"pageContent": "if (!activeId) return;\n\n const messages = messagesReducer(chatSelectors.currentChats(get()), payload);\n\n const nextMap = { ...get().messagesMap, [chatSelectors.currentChatKey(get())]: messages };\n\n if (isEqual(nextMap, get().messagesMap)) return;\n\n set({ messagesMap: nextMap }, false, { type: `dispatchMessage/${payload.type}`, payload });\n },\n internal_fetchAIChatMessage: async (messages, assistantId, params) => {\n const {\n internal_toggleChatLoading,\n refreshMessages,\n internal_updateMessageContent,\n internal_dispatchMessage,\n internal_toggleToolCallingStreaming,\n } = get();\n\n const abortController = internal_toggleChatLoading(\n true,\n assistantId,\n n('generateMessage(start)', { assistantId, messages }) as string,\n );",
"metadata": { "loc": { "lines": { "from": 547, "to": 570 } } }
},
{
"pageContent": "const abortController = internal_toggleChatLoading(\n true,\n assistantId,\n n('generateMessage(start)', { assistantId, messages }) as string,\n );\n\n const agentConfig = getAgentConfig();\n const chatConfig = agentConfig.chatConfig;\n\n const compiler = template(chatConfig.inputTemplate, { interpolate: /{{([\\S\\s]+?)}}/g });\n\n // ================================== //\n // messages uniformly preprocess //\n // ================================== //\n\n // 1. slice messages with config\n let preprocessMsgs = chatHelpers.getSlicedMessages(messages, chatConfig);",
"metadata": { "loc": { "lines": { "from": 566, "to": 582 } } }
},
{
"pageContent": "const compiler = template(chatConfig.inputTemplate, { interpolate: /{{([\\S\\s]+?)}}/g });\n\n // ================================== //\n // messages uniformly preprocess //\n // ================================== //\n\n // 1. slice messages with config\n let preprocessMsgs = chatHelpers.getSlicedMessages(messages, chatConfig);\n\n // 2. replace inputMessage template\n preprocessMsgs = !chatConfig.inputTemplate\n ? preprocessMsgs\n : preprocessMsgs.map((m) => {\n if (m.role === 'user') {\n try {\n return { ...m, content: compiler({ text: m.content }) };\n } catch (error) {\n console.error(error);\n\n return m;\n }\n }\n\n return m;\n });",
"metadata": { "loc": { "lines": { "from": 575, "to": 599 } } }
},
{
"pageContent": "return m;\n }\n }\n\n return m;\n });\n\n // 3. add systemRole\n if (agentConfig.systemRole) {\n preprocessMsgs.unshift({ content: agentConfig.systemRole, role: 'system' } as ChatMessage);\n }\n\n // 4. handle max_tokens\n agentConfig.params.max_tokens = chatConfig.enableMaxTokens\n ? agentConfig.params.max_tokens\n : undefined;",
"metadata": { "loc": { "lines": { "from": 594, "to": 609 } } }
},
{
"pageContent": "return m;\n });\n\n // 3. add systemRole\n if (agentConfig.systemRole) {\n preprocessMsgs.unshift({ content: agentConfig.systemRole, role: 'system' } as ChatMessage);\n }\n\n // 4. handle max_tokens\n agentConfig.params.max_tokens = chatConfig.enableMaxTokens\n ? agentConfig.params.max_tokens\n : undefined;\n\n // 5. handle config for the vision model\n // Due to the gpt-4-vision-preview model's default max_tokens is very small\n // we need to set the max_tokens a larger one.\n if (agentConfig.model === 'gpt-4-vision-preview') {\n /* eslint-disable unicorn/no-lonely-if */\n if (!agentConfig.params.max_tokens)\n // refs: https://github.com/lobehub/lobe-chat/issues/837\n agentConfig.params.max_tokens = 2048;\n }",
"metadata": { "loc": { "lines": { "from": 598, "to": 619 } } }
},
{
"pageContent": "let isFunctionCall = false;\n let msgTraceId: string | undefined;\n let output = '';",
"metadata": { "loc": { "lines": { "from": 621, "to": 623 } } }
},
{
"pageContent": "await chatService.createAssistantMessageStream({\n abortController,\n params: {\n messages: preprocessMsgs,\n model: agentConfig.model,\n provider: agentConfig.provider,\n ...agentConfig.params,\n plugins: agentConfig.plugins,\n },\n trace: {\n traceId: params?.traceId,\n sessionId: get().activeId,\n topicId: get().activeTopicId,\n traceName: TraceNameMap.Conversation,\n },\n isWelcomeQuestion: params?.isWelcomeQuestion,\n onErrorHandle: async (error) => {\n await messageService.updateMessageError(assistantId, error);\n await refreshMessages();\n },\n onFinish: async (content, { traceId, observationId, toolCalls }) => {\n // if there is traceId, update it\n if (traceId) {",
"metadata": { "loc": { "lines": { "from": 625, "to": 647 } } }
},
{
"pageContent": "traceName: TraceNameMap.Conversation,\n },\n isWelcomeQuestion: params?.isWelcomeQuestion,\n onErrorHandle: async (error) => {\n await messageService.updateMessageError(assistantId, error);\n await refreshMessages();\n },\n onFinish: async (content, { traceId, observationId, toolCalls }) => {\n // if there is traceId, update it\n if (traceId) {\n msgTraceId = traceId;\n await messageService.updateMessage(assistantId, {\n traceId,\n observationId: observationId ?? undefined,\n });\n }",
"metadata": { "loc": { "lines": { "from": 638, "to": 653 } } }
},
{
"pageContent": "if (toolCalls && toolCalls.length > 0) {\n internal_toggleToolCallingStreaming(assistantId, undefined);\n }\n\n // update the content after fetch result\n await internal_updateMessageContent(assistantId, content, toolCalls);\n },\n onMessageHandle: async (chunk) => {\n switch (chunk.type) {\n case 'text': {\n output += chunk.text;\n internal_dispatchMessage({\n id: assistantId,\n type: 'updateMessage',\n value: { content: output },\n });\n break;\n }",
"metadata": { "loc": { "lines": { "from": 655, "to": 672 } } }
},
{
"pageContent": "// is this message is just a tool call\n case 'tool_calls': {\n internal_toggleToolCallingStreaming(assistantId, chunk.isAnimationActives);\n internal_dispatchMessage({\n id: assistantId,\n type: 'updateMessage',\n value: { tools: get().internal_transformToolCalls(chunk.tool_calls) },\n });\n isFunctionCall = true;\n }\n }\n },\n });\n\n internal_toggleChatLoading(false, assistantId, n('generateMessage(end)') as string);\n\n return {\n isFunctionCall,\n traceId: msgTraceId,\n };\n },\n\n internal_resendMessage: async (messageId, traceId) => {\n // 1. 构造所有相关的历史记录\n const chats = chatSelectors.currentChats(get());",
"metadata": { "loc": { "lines": { "from": 674, "to": 698 } } }
},
{
"pageContent": "internal_toggleChatLoading(false, assistantId, n('generateMessage(end)') as string);\n\n return {\n isFunctionCall,\n traceId: msgTraceId,\n };\n },\n\n internal_resendMessage: async (messageId, traceId) => {\n // 1. 构造所有相关的历史记录\n const chats = chatSelectors.currentChats(get());\n\n const currentIndex = chats.findIndex((c) => c.id === messageId);\n if (currentIndex < 0) return;\n\n const currentMessage = chats[currentIndex];\n\n let contextMessages: ChatMessage[] = [];",
"metadata": { "loc": { "lines": { "from": 688, "to": 705 } } }
},
{
"pageContent": "const currentIndex = chats.findIndex((c) => c.id === messageId);\n if (currentIndex < 0) return;\n\n const currentMessage = chats[currentIndex];\n\n let contextMessages: ChatMessage[] = [];\n\n switch (currentMessage.role) {\n case 'tool':\n case 'user': {\n contextMessages = chats.slice(0, currentIndex + 1);\n break;\n }\n case 'assistant': {\n // 消息是 AI 发出的因此需要找到它的 user 消息\n const userId = currentMessage.parentId;\n const userIndex = chats.findIndex((c) => c.id === userId);\n // 如果消息没有 parentId,那么同 user/function 模式\n contextMessages = chats.slice(0, userIndex < 0 ? currentIndex + 1 : userIndex + 1);\n break;\n }\n }\n\n if (contextMessages.length <= 0) return;",
"metadata": { "loc": { "lines": { "from": 700, "to": 723 } } }
},
{
"pageContent": "if (contextMessages.length <= 0) return;\n\n const { internal_coreProcessMessage } = get();\n\n const latestMsg = contextMessages.findLast((s) => s.role === 'user');\n\n if (!latestMsg) return;\n\n await internal_coreProcessMessage(contextMessages, latestMsg.id, {\n traceId,\n ragQuery: currentMessage.content,\n });\n },\n\n internal_updateMessageError: async (id, error) => {\n get().internal_dispatchMessage({ id, type: 'updateMessage', value: { error } });\n await messageService.updateMessage(id, { error });\n await get().refreshMessages();\n },\n internal_updateMessageContent: async (id, content, toolCalls) => {\n const { internal_dispatchMessage, refreshMessages, internal_transformToolCalls } = get();",
"metadata": { "loc": { "lines": { "from": 723, "to": 743 } } }
},
{
"pageContent": "// Due to the async update method and refresh need about 100ms\n // we need to update the message content at the frontend to avoid the update flick\n // refs: https://medium.com/@kyledeguzmanx/what-are-optimistic-updates-483662c3e171\n if (toolCalls) {\n internal_dispatchMessage({\n id,\n type: 'updateMessage',\n value: { tools: internal_transformToolCalls(toolCalls) },\n });\n } else {\n internal_dispatchMessage({ id, type: 'updateMessage', value: { content } });\n }\n\n await messageService.updateMessage(id, {\n content,\n tools: toolCalls ? internal_transformToolCalls(toolCalls) : undefined,\n });\n await refreshMessages();\n },",
"metadata": { "loc": { "lines": { "from": 745, "to": 763 } } }
},
{
"pageContent": "await messageService.updateMessage(id, {\n content,\n tools: toolCalls ? internal_transformToolCalls(toolCalls) : undefined,\n });\n await refreshMessages();\n },\n\n internal_createMessage: async (message, context) => {\n const { internal_createTmpMessage, refreshMessages, internal_toggleMessageLoading } = get();\n let tempId = context?.tempMessageId;\n if (!tempId) {\n // use optimistic update to avoid the slow waiting\n tempId = internal_createTmpMessage(message);\n\n internal_toggleMessageLoading(true, tempId);\n }\n\n const id = await messageService.createMessage(message);\n if (!context?.skipRefresh) {\n await refreshMessages();\n }\n\n internal_toggleMessageLoading(false, tempId);\n return id;\n },",
"metadata": { "loc": { "lines": { "from": 758, "to": 782 } } }
},
{
"pageContent": "internal_toggleMessageLoading(true, tempId);\n }\n\n const id = await messageService.createMessage(message);\n if (!context?.skipRefresh) {\n await refreshMessages();\n }\n\n internal_toggleMessageLoading(false, tempId);\n return id;\n },\n\n internal_fetchMessages: async () => {\n const messages = await messageService.getMessages(get().activeId, get().activeTopicId);\n const nextMap = { ...get().messagesMap, [chatSelectors.currentChatKey(get())]: messages };\n // no need to update map if the messages have been init and the map is the same\n if (get().messagesInit && isEqual(nextMap, get().messagesMap)) return;",
"metadata": { "loc": { "lines": { "from": 772, "to": 788 } } }
},
{
"pageContent": "internal_fetchMessages: async () => {\n const messages = await messageService.getMessages(get().activeId, get().activeTopicId);\n const nextMap = { ...get().messagesMap, [chatSelectors.currentChatKey(get())]: messages };\n // no need to update map if the messages have been init and the map is the same\n if (get().messagesInit && isEqual(nextMap, get().messagesMap)) return;\n\n set(\n { messagesInit: true, messagesMap: nextMap },\n false,\n n('internal_fetchMessages', { messages }),\n );\n },\n internal_createTmpMessage: (message) => {\n const { internal_dispatchMessage } = get();\n\n // use optimistic update to avoid the slow waiting\n const tempId = 'tmp_' + nanoid();\n internal_dispatchMessage({ type: 'createMessage', id: tempId, value: message });",
"metadata": { "loc": { "lines": { "from": 784, "to": 801 } } }
},
{
"pageContent": "// use optimistic update to avoid the slow waiting\n const tempId = 'tmp_' + nanoid();\n internal_dispatchMessage({ type: 'createMessage', id: tempId, value: message });\n\n return tempId;\n },\n internal_deleteMessage: async (id: string) => {\n get().internal_dispatchMessage({ type: 'deleteMessage', id });\n await messageService.removeMessage(id);\n await get().refreshMessages();\n },\n internal_traceMessage: async (id, payload) => {\n // tracing the diff of update\n const message = chatSelectors.getMessageById(id)(get());\n if (!message) return;\n\n const traceId = message?.traceId;\n const observationId = message?.observationId;",
"metadata": { "loc": { "lines": { "from": 799, "to": 816 } } }
},
{
"pageContent": "const traceId = message?.traceId;\n const observationId = message?.observationId;\n\n if (traceId && message?.role === 'assistant') {\n traceService\n .traceEvent({ traceId, observationId, content: message.content, ...payload })\n .catch();\n }\n },",
"metadata": { "loc": { "lines": { "from": 815, "to": 823 } } }
},
{
"pageContent": "// ----- Loading ------- //\n internal_toggleMessageLoading: (loading, id) => {\n set(\n {\n messageLoadingIds: toggleBooleanList(get().messageLoadingIds, id, loading),\n },\n false,\n 'internal_toggleMessageLoading',\n );\n },\n internal_toggleChatLoading: (loading, id, action) => {\n return get().internal_toggleLoadingArrays('chatLoadingIds', loading, id, action);\n },\n internal_toggleToolCallingStreaming: (id, streaming) => {\n set(\n {\n toolCallingStreamIds: produce(get().toolCallingStreamIds, (draft) => {\n if (!!streaming) {\n draft[id] = streaming;\n } else {\n delete draft[id];\n }\n }),\n },",
"metadata": { "loc": { "lines": { "from": 825, "to": 848 } } }
},
{
"pageContent": "false,\n 'toggleToolCallingStreaming',\n );\n },\n internal_toggleLoadingArrays: (key, loading, id, action) => {\n if (loading) {\n window.addEventListener('beforeunload', preventLeavingFn);\n\n const abortController = new AbortController();\n set(\n {\n abortController,\n [key]: toggleBooleanList(get()[key] as string[], id!, loading),\n },\n false,\n action,\n );\n\n return abortController;\n } else {\n if (!id) {\n set({ abortController: undefined, [key]: [] }, false, action);\n } else\n set(\n {\n abortController: undefined,\n [key]: toggleBooleanList(get()[key] as string[], id, loading),\n },\n false,\n action,\n );",
"metadata": { "loc": { "lines": { "from": 850, "to": 880 } } }
},
{
"pageContent": "return abortController;\n } else {\n if (!id) {\n set({ abortController: undefined, [key]: [] }, false, action);\n } else\n set(\n {\n abortController: undefined,\n [key]: toggleBooleanList(get()[key] as string[], id, loading),\n },\n false,\n action,\n );\n\n window.removeEventListener('beforeunload', preventLeavingFn);\n }\n },\n});",
"metadata": { "loc": { "lines": { "from": 868, "to": 885 } } }
}
]