From 1fc9c4e220705e5519ed4b4331ba21453ab43168 Mon Sep 17 00:00:00 2001 From: NamH Date: Tue, 14 Nov 2023 09:37:54 +0700 Subject: [PATCH] fix(#591): prevent duplicate message id issue (#595) Signed-off-by: James Co-authored-by: James --- plugins/inference-plugin/package.json | 6 ++- .../inference-plugin/src/helpers/message.ts | 3 -- plugins/inference-plugin/src/index.ts | 6 +-- plugins/model-plugin/package.json | 3 +- web/containers/Providers/EventHandler.tsx | 1 + web/helpers/atoms/ChatMessage.atom.ts | 2 +- web/helpers/atoms/Conversation.atom.ts | 1 + web/hooks/useCreateConversation.ts | 1 + web/hooks/useGetInputState.ts | 1 + web/hooks/useGetUserConversations.ts | 1 + web/hooks/useSendChatMessage.ts | 45 +++++++++---------- web/package.json | 1 + web/screens/Chat/ChatItem/index.tsx | 29 ++++++------ web/types/chatMessage.d.ts | 2 +- web/utils/message.ts | 4 -- 15 files changed, 53 insertions(+), 53 deletions(-) delete mode 100644 plugins/inference-plugin/src/helpers/message.ts diff --git a/plugins/inference-plugin/package.json b/plugins/inference-plugin/package.json index 78f803795..8a2e84ad7 100644 --- a/plugins/inference-plugin/package.json +++ b/plugins/inference-plugin/package.json @@ -22,7 +22,8 @@ "postinstall": "rimraf *.tgz --glob && npm run build && npm run downloadnitro:linux-cpu && npm run downloadnitro:linux-cuda && npm run downloadnitro:mac-arm64 && npm run downloadnitro:mac-x64 && rimraf dist/nitro/* && cpx \"nitro/**\" \"dist/nitro\"", "postinstall:dev": "rimraf *.tgz --glob && npm run build && npm run downloadnitro:mac-arm64 && npm run downloadnitro:mac-x64 && rimraf dist/nitro/* && cpx \"nitro/**\" \"dist/nitro\"", "postinstall:windows": "rimraf *.tgz --glob && npm run build && npm run downloadnitro:win-cpu && npm run downloadnitro:win-cuda && rimraf dist/nitro/* && cpx \"nitro/**\" \"dist/nitro\"", - "build:publish": "npm pack && cpx *.tgz ../../electron/core/pre-install" + "build:publish": "npm pack && cpx *.tgz ../../electron/core/pre-install", + "build:debug": "rimraf *.tgz --glob && npm run build && npm pack && cpx *.tgz ../../electron/core/pre-install" }, "exports": { ".": "./dist/index.js", @@ -40,7 +41,8 @@ "kill-port": "^2.0.1", "rxjs": "^7.8.1", "tcp-port-used": "^1.0.2", - "ts-loader": "^9.5.0" + "ts-loader": "^9.5.0", + "ulid": "^2.3.0" }, "engines": { "node": ">=18.0.0" diff --git a/plugins/inference-plugin/src/helpers/message.ts b/plugins/inference-plugin/src/helpers/message.ts deleted file mode 100644 index 0913f9403..000000000 --- a/plugins/inference-plugin/src/helpers/message.ts +++ /dev/null @@ -1,3 +0,0 @@ -export const generateMessageId = () => { - return `m-${Date.now()}` -} diff --git a/plugins/inference-plugin/src/index.ts b/plugins/inference-plugin/src/index.ts index f82ceec88..80857ec42 100644 --- a/plugins/inference-plugin/src/index.ts +++ b/plugins/inference-plugin/src/index.ts @@ -16,7 +16,7 @@ import { } from "@janhq/core"; import { InferencePlugin } from "@janhq/core/lib/plugins"; import { requestInference } from "./helpers/sse"; -import { generateMessageId } from "./helpers/message"; +import { ulid } from "ulid"; /** * A class that implements the InferencePlugin interface from the @janhq/core package. @@ -112,13 +112,13 @@ export default class JanInferencePlugin implements InferencePlugin { content: data.message, }, ]; - const recentMessages = await (data.history ?? prompts); + const recentMessages = data.history ?? prompts; const message = { ...data, message: "", user: "assistant", createdAt: new Date().toISOString(), - _id: generateMessageId(), + _id: ulid(), }; events.emit(EventName.OnNewMessageResponse, message); diff --git a/plugins/model-plugin/package.json b/plugins/model-plugin/package.json index 1ee8e5496..67b50016e 100644 --- a/plugins/model-plugin/package.json +++ b/plugins/model-plugin/package.json @@ -15,7 +15,8 @@ "scripts": { "build": "tsc -b . && webpack --config webpack.config.js", "postinstall": "rimraf *.tgz --glob && npm run build", - "build:publish": "npm pack && cpx *.tgz ../../electron/core/pre-install" + "build:publish": "npm pack && cpx *.tgz ../../electron/core/pre-install", + "build:debug": "rimraf *.tgz --glob && npm run build && npm pack && cpx *.tgz ../../electron/core/pre-install" }, "devDependencies": { "cpx": "^1.5.0", diff --git a/web/containers/Providers/EventHandler.tsx b/web/containers/Providers/EventHandler.tsx index f3a603cbf..f1b943427 100644 --- a/web/containers/Providers/EventHandler.tsx +++ b/web/containers/Providers/EventHandler.tsx @@ -24,6 +24,7 @@ import { import { downloadingModelsAtom } from '@/helpers/atoms/Model.atom' import { MessageStatus, toChatMessage } from '@/models/ChatMessage' import { pluginManager } from '@/plugin' +import { ChatMessage, Conversation } from '@/types/chatMessage' let currentConversation: Conversation | undefined = undefined diff --git a/web/helpers/atoms/ChatMessage.atom.ts b/web/helpers/atoms/ChatMessage.atom.ts index 079568a9a..c45808288 100644 --- a/web/helpers/atoms/ChatMessage.atom.ts +++ b/web/helpers/atoms/ChatMessage.atom.ts @@ -2,7 +2,7 @@ import { atom } from 'jotai' import { getActiveConvoIdAtom } from './Conversation.atom' -import { MessageStatus } from '@/models/ChatMessage' +import { ChatMessage, MessageStatus } from '@/models/ChatMessage' /** * Stores all chat messages for all conversations diff --git a/web/helpers/atoms/Conversation.atom.ts b/web/helpers/atoms/Conversation.atom.ts index f15b60581..3a661d385 100644 --- a/web/helpers/atoms/Conversation.atom.ts +++ b/web/helpers/atoms/Conversation.atom.ts @@ -1,3 +1,4 @@ +import { Conversation, ConversationState } from '@/types/chatMessage' import { atom } from 'jotai' // import { MainViewState, setMainViewStateAtom } from './MainView.atom' diff --git a/web/hooks/useCreateConversation.ts b/web/hooks/useCreateConversation.ts index 0bb03582c..755876c0a 100644 --- a/web/hooks/useCreateConversation.ts +++ b/web/hooks/useCreateConversation.ts @@ -12,6 +12,7 @@ import { addNewConversationStateAtom, } from '@/helpers/atoms/Conversation.atom' import { pluginManager } from '@/plugin' +import { Conversation } from '@/types/chatMessage' export const useCreateConversation = () => { const [userConversations, setUserConversations] = useAtom( diff --git a/web/hooks/useGetInputState.ts b/web/hooks/useGetInputState.ts index e2b654e8c..26a1c83d7 100644 --- a/web/hooks/useGetInputState.ts +++ b/web/hooks/useGetInputState.ts @@ -7,6 +7,7 @@ import { useActiveModel } from './useActiveModel' import { useGetDownloadedModels } from './useGetDownloadedModels' import { currentConversationAtom } from '@/helpers/atoms/Conversation.atom' +import { Conversation } from '@/types/chatMessage' export default function useGetInputState() { const [inputState, setInputState] = useState('loading') diff --git a/web/hooks/useGetUserConversations.ts b/web/hooks/useGetUserConversations.ts index 579b9ca5e..35e659ee2 100644 --- a/web/hooks/useGetUserConversations.ts +++ b/web/hooks/useGetUserConversations.ts @@ -10,6 +10,7 @@ import { } from '@/helpers/atoms/Conversation.atom' import { toChatMessage } from '@/models/ChatMessage' import { pluginManager } from '@/plugin/PluginManager' +import { ChatMessage, ConversationState } from '@/types/chatMessage' const useGetUserConversations = () => { const setConversationStates = useSetAtom(conversationStatesAtom) diff --git a/web/hooks/useSendChatMessage.ts b/web/hooks/useSendChatMessage.ts index a06c67b4c..256c701ef 100644 --- a/web/hooks/useSendChatMessage.ts +++ b/web/hooks/useSendChatMessage.ts @@ -12,9 +12,7 @@ import { Message } from '@janhq/core/lib/types' import { useAtom, useAtomValue, useSetAtom } from 'jotai' import { currentPromptAtom } from '@/containers/Providers/Jotai' - -import { generateMessageId } from '@/utils/message' - +import { ulid } from 'ulid' import { addNewMessageAtom, getCurrentChatMessagesAtom, @@ -24,9 +22,10 @@ import { updateConversationAtom, updateConversationWaitingForResponseAtom, } from '@/helpers/atoms/Conversation.atom' -import { toChatMessage } from '@/models/ChatMessage' +import { MessageSenderType, toChatMessage } from '@/models/ChatMessage' import { pluginManager } from '@/plugin/PluginManager' +import { ChatMessage, Conversation } from '@/types/chatMessage' export default function useSendChatMessage() { const currentConvo = useAtomValue(currentConversationAtom) @@ -73,16 +72,13 @@ export default function useSendChatMessage() { ...updatedConv, name: updatedConv.name ?? '', message: updatedConv.lastMessage ?? '', - messages: currentMessages.map((e: ChatMessage) => { - return { - // eslint-disable-next-line @typescript-eslint/naming-convention - _id: e.id, - message: e.text, - user: e.senderUid, - updatedAt: new Date(e.createdAt).toISOString(), - createdAt: new Date(e.createdAt).toISOString(), - } - }), + messages: currentMessages.map((e: ChatMessage) => ({ + _id: e.id, + message: e.text, + user: e.senderUid, + updatedAt: new Date(e.createdAt).toISOString(), + createdAt: new Date(e.createdAt).toISOString(), + })), }) } }, 1000) @@ -98,25 +94,23 @@ export default function useSendChatMessage() { const prompt = currentPrompt.trim() const messageHistory: MessageHistory[] = currentMessages - .map((msg) => { - return { - role: msg.senderUid === 'user' ? 'user' : 'assistant', - content: msg.text ?? '', - } - }) + .map((msg) => ({ + role: msg.senderUid, + content: msg.text ?? '', + })) .reverse() .concat([ { - role: 'user', + role: MessageSenderType.User, content: prompt, } as MessageHistory, ]) const newMessage: NewMessageRequest = { // eslint-disable-next-line @typescript-eslint/naming-convention - _id: generateMessageId(), + _id: ulid(), conversationId: convoId, message: prompt, - user: 'user', + user: MessageSenderType.User, createdAt: new Date().toISOString(), history: messageHistory, } @@ -124,6 +118,11 @@ export default function useSendChatMessage() { const newChatMessage = toChatMessage(newMessage) addNewMessage(newChatMessage) + // delay randomly from 50 - 100ms + // to prevent duplicate message id + const delay = Math.floor(Math.random() * 50) + 50 + await new Promise((resolve) => setTimeout(resolve, delay)) + events.emit(EventName.OnNewMessageRequest, newMessage) if (!currentConvo?.summary && currentConvo) { const updatedConv: Conversation = { diff --git a/web/package.json b/web/package.json index 6d0347646..59d142dbf 100644 --- a/web/package.json +++ b/web/package.json @@ -38,6 +38,7 @@ "tailwind-merge": "^2.0.0", "tailwindcss": "3.3.5", "typescript": "5.2.2", + "ulid": "^2.3.0", "uuid": "^9.0.1", "zod": "^3.22.4" }, diff --git a/web/screens/Chat/ChatItem/index.tsx b/web/screens/Chat/ChatItem/index.tsx index 6e38230a3..6b4f0b3f8 100644 --- a/web/screens/Chat/ChatItem/index.tsx +++ b/web/screens/Chat/ChatItem/index.tsx @@ -1,6 +1,7 @@ import React, { forwardRef } from 'react' import SimpleTextMessage from '../SimpleTextMessage' +import { ChatMessage } from '@/types/chatMessage' type Props = { message: ChatMessage @@ -8,20 +9,18 @@ type Props = { type Ref = HTMLDivElement -const ChatItem = forwardRef(({ message }, ref) => { - return ( -
- -
- ) -}) +const ChatItem = forwardRef(({ message }, ref) => ( +
+ +
+)) export default ChatItem diff --git a/web/types/chatMessage.d.ts b/web/types/chatMessage.d.ts index 70764a211..79357b88b 100644 --- a/web/types/chatMessage.d.ts +++ b/web/types/chatMessage.d.ts @@ -6,7 +6,7 @@ enum MessageType { Error = 'Error', } -enum MessageSenderType { +export enum MessageSenderType { Ai = 'assistant', User = 'user', } diff --git a/web/utils/message.ts b/web/utils/message.ts index d19d52ff3..860a2be31 100644 --- a/web/utils/message.ts +++ b/web/utils/message.ts @@ -25,7 +25,3 @@ export function mergeAndRemoveDuplicates( return result.reverse() } - -export const generateMessageId = () => { - return `m-${Date.now()}` -}