fix(#591): prevent duplicate message id issue (#595)

Signed-off-by: James <james@jan.ai>
Co-authored-by: James <james@jan.ai>
This commit is contained in:
NamH 2023-11-14 09:37:54 +07:00 committed by GitHub
parent 52b96e69a5
commit 1fc9c4e220
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 53 additions and 53 deletions

View File

@ -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": "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: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\"", "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": { "exports": {
".": "./dist/index.js", ".": "./dist/index.js",
@ -40,7 +41,8 @@
"kill-port": "^2.0.1", "kill-port": "^2.0.1",
"rxjs": "^7.8.1", "rxjs": "^7.8.1",
"tcp-port-used": "^1.0.2", "tcp-port-used": "^1.0.2",
"ts-loader": "^9.5.0" "ts-loader": "^9.5.0",
"ulid": "^2.3.0"
}, },
"engines": { "engines": {
"node": ">=18.0.0" "node": ">=18.0.0"

View File

@ -1,3 +0,0 @@
export const generateMessageId = () => {
return `m-${Date.now()}`
}

View File

@ -16,7 +16,7 @@ import {
} from "@janhq/core"; } from "@janhq/core";
import { InferencePlugin } from "@janhq/core/lib/plugins"; import { InferencePlugin } from "@janhq/core/lib/plugins";
import { requestInference } from "./helpers/sse"; 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. * 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, content: data.message,
}, },
]; ];
const recentMessages = await (data.history ?? prompts); const recentMessages = data.history ?? prompts;
const message = { const message = {
...data, ...data,
message: "", message: "",
user: "assistant", user: "assistant",
createdAt: new Date().toISOString(), createdAt: new Date().toISOString(),
_id: generateMessageId(), _id: ulid(),
}; };
events.emit(EventName.OnNewMessageResponse, message); events.emit(EventName.OnNewMessageResponse, message);

View File

@ -15,7 +15,8 @@
"scripts": { "scripts": {
"build": "tsc -b . && webpack --config webpack.config.js", "build": "tsc -b . && webpack --config webpack.config.js",
"postinstall": "rimraf *.tgz --glob && npm run build", "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": { "devDependencies": {
"cpx": "^1.5.0", "cpx": "^1.5.0",

View File

@ -24,6 +24,7 @@ import {
import { downloadingModelsAtom } from '@/helpers/atoms/Model.atom' import { downloadingModelsAtom } from '@/helpers/atoms/Model.atom'
import { MessageStatus, toChatMessage } from '@/models/ChatMessage' import { MessageStatus, toChatMessage } from '@/models/ChatMessage'
import { pluginManager } from '@/plugin' import { pluginManager } from '@/plugin'
import { ChatMessage, Conversation } from '@/types/chatMessage'
let currentConversation: Conversation | undefined = undefined let currentConversation: Conversation | undefined = undefined

View File

@ -2,7 +2,7 @@ import { atom } from 'jotai'
import { getActiveConvoIdAtom } from './Conversation.atom' import { getActiveConvoIdAtom } from './Conversation.atom'
import { MessageStatus } from '@/models/ChatMessage' import { ChatMessage, MessageStatus } from '@/models/ChatMessage'
/** /**
* Stores all chat messages for all conversations * Stores all chat messages for all conversations

View File

@ -1,3 +1,4 @@
import { Conversation, ConversationState } from '@/types/chatMessage'
import { atom } from 'jotai' import { atom } from 'jotai'
// import { MainViewState, setMainViewStateAtom } from './MainView.atom' // import { MainViewState, setMainViewStateAtom } from './MainView.atom'

View File

@ -12,6 +12,7 @@ import {
addNewConversationStateAtom, addNewConversationStateAtom,
} from '@/helpers/atoms/Conversation.atom' } from '@/helpers/atoms/Conversation.atom'
import { pluginManager } from '@/plugin' import { pluginManager } from '@/plugin'
import { Conversation } from '@/types/chatMessage'
export const useCreateConversation = () => { export const useCreateConversation = () => {
const [userConversations, setUserConversations] = useAtom( const [userConversations, setUserConversations] = useAtom(

View File

@ -7,6 +7,7 @@ import { useActiveModel } from './useActiveModel'
import { useGetDownloadedModels } from './useGetDownloadedModels' import { useGetDownloadedModels } from './useGetDownloadedModels'
import { currentConversationAtom } from '@/helpers/atoms/Conversation.atom' import { currentConversationAtom } from '@/helpers/atoms/Conversation.atom'
import { Conversation } from '@/types/chatMessage'
export default function useGetInputState() { export default function useGetInputState() {
const [inputState, setInputState] = useState<InputType>('loading') const [inputState, setInputState] = useState<InputType>('loading')

View File

@ -10,6 +10,7 @@ import {
} from '@/helpers/atoms/Conversation.atom' } from '@/helpers/atoms/Conversation.atom'
import { toChatMessage } from '@/models/ChatMessage' import { toChatMessage } from '@/models/ChatMessage'
import { pluginManager } from '@/plugin/PluginManager' import { pluginManager } from '@/plugin/PluginManager'
import { ChatMessage, ConversationState } from '@/types/chatMessage'
const useGetUserConversations = () => { const useGetUserConversations = () => {
const setConversationStates = useSetAtom(conversationStatesAtom) const setConversationStates = useSetAtom(conversationStatesAtom)

View File

@ -12,9 +12,7 @@ import { Message } from '@janhq/core/lib/types'
import { useAtom, useAtomValue, useSetAtom } from 'jotai' import { useAtom, useAtomValue, useSetAtom } from 'jotai'
import { currentPromptAtom } from '@/containers/Providers/Jotai' import { currentPromptAtom } from '@/containers/Providers/Jotai'
import { ulid } from 'ulid'
import { generateMessageId } from '@/utils/message'
import { import {
addNewMessageAtom, addNewMessageAtom,
getCurrentChatMessagesAtom, getCurrentChatMessagesAtom,
@ -24,9 +22,10 @@ import {
updateConversationAtom, updateConversationAtom,
updateConversationWaitingForResponseAtom, updateConversationWaitingForResponseAtom,
} from '@/helpers/atoms/Conversation.atom' } from '@/helpers/atoms/Conversation.atom'
import { toChatMessage } from '@/models/ChatMessage' import { MessageSenderType, toChatMessage } from '@/models/ChatMessage'
import { pluginManager } from '@/plugin/PluginManager' import { pluginManager } from '@/plugin/PluginManager'
import { ChatMessage, Conversation } from '@/types/chatMessage'
export default function useSendChatMessage() { export default function useSendChatMessage() {
const currentConvo = useAtomValue(currentConversationAtom) const currentConvo = useAtomValue(currentConversationAtom)
@ -73,16 +72,13 @@ export default function useSendChatMessage() {
...updatedConv, ...updatedConv,
name: updatedConv.name ?? '', name: updatedConv.name ?? '',
message: updatedConv.lastMessage ?? '', message: updatedConv.lastMessage ?? '',
messages: currentMessages.map<Message>((e: ChatMessage) => { messages: currentMessages.map<Message>((e: ChatMessage) => ({
return { _id: e.id,
// eslint-disable-next-line @typescript-eslint/naming-convention message: e.text,
_id: e.id, user: e.senderUid,
message: e.text, updatedAt: new Date(e.createdAt).toISOString(),
user: e.senderUid, createdAt: new Date(e.createdAt).toISOString(),
updatedAt: new Date(e.createdAt).toISOString(), })),
createdAt: new Date(e.createdAt).toISOString(),
}
}),
}) })
} }
}, 1000) }, 1000)
@ -98,25 +94,23 @@ export default function useSendChatMessage() {
const prompt = currentPrompt.trim() const prompt = currentPrompt.trim()
const messageHistory: MessageHistory[] = currentMessages const messageHistory: MessageHistory[] = currentMessages
.map((msg) => { .map((msg) => ({
return { role: msg.senderUid,
role: msg.senderUid === 'user' ? 'user' : 'assistant', content: msg.text ?? '',
content: msg.text ?? '', }))
}
})
.reverse() .reverse()
.concat([ .concat([
{ {
role: 'user', role: MessageSenderType.User,
content: prompt, content: prompt,
} as MessageHistory, } as MessageHistory,
]) ])
const newMessage: NewMessageRequest = { const newMessage: NewMessageRequest = {
// eslint-disable-next-line @typescript-eslint/naming-convention // eslint-disable-next-line @typescript-eslint/naming-convention
_id: generateMessageId(), _id: ulid(),
conversationId: convoId, conversationId: convoId,
message: prompt, message: prompt,
user: 'user', user: MessageSenderType.User,
createdAt: new Date().toISOString(), createdAt: new Date().toISOString(),
history: messageHistory, history: messageHistory,
} }
@ -124,6 +118,11 @@ export default function useSendChatMessage() {
const newChatMessage = toChatMessage(newMessage) const newChatMessage = toChatMessage(newMessage)
addNewMessage(newChatMessage) 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) events.emit(EventName.OnNewMessageRequest, newMessage)
if (!currentConvo?.summary && currentConvo) { if (!currentConvo?.summary && currentConvo) {
const updatedConv: Conversation = { const updatedConv: Conversation = {

View File

@ -38,6 +38,7 @@
"tailwind-merge": "^2.0.0", "tailwind-merge": "^2.0.0",
"tailwindcss": "3.3.5", "tailwindcss": "3.3.5",
"typescript": "5.2.2", "typescript": "5.2.2",
"ulid": "^2.3.0",
"uuid": "^9.0.1", "uuid": "^9.0.1",
"zod": "^3.22.4" "zod": "^3.22.4"
}, },

View File

@ -1,6 +1,7 @@
import React, { forwardRef } from 'react' import React, { forwardRef } from 'react'
import SimpleTextMessage from '../SimpleTextMessage' import SimpleTextMessage from '../SimpleTextMessage'
import { ChatMessage } from '@/types/chatMessage'
type Props = { type Props = {
message: ChatMessage message: ChatMessage
@ -8,20 +9,18 @@ type Props = {
type Ref = HTMLDivElement type Ref = HTMLDivElement
const ChatItem = forwardRef<Ref, Props>(({ message }, ref) => { const ChatItem = forwardRef<Ref, Props>(({ message }, ref) => (
return ( <div ref={ref} className="py-4 even:bg-secondary dark:even:bg-secondary/20">
<div ref={ref} className="py-4 even:bg-secondary dark:even:bg-secondary/20"> <SimpleTextMessage
<SimpleTextMessage status={message.status}
status={message.status} key={message.id}
key={message.id} avatarUrl={message.senderAvatarUrl}
avatarUrl={message.senderAvatarUrl} senderName={message.senderName}
senderName={message.senderName} createdAt={message.createdAt}
createdAt={message.createdAt} senderType={message.messageSenderType}
senderType={message.messageSenderType} text={message.text}
text={message.text} />
/> </div>
</div> ))
)
})
export default ChatItem export default ChatItem

View File

@ -6,7 +6,7 @@ enum MessageType {
Error = 'Error', Error = 'Error',
} }
enum MessageSenderType { export enum MessageSenderType {
Ai = 'assistant', Ai = 'assistant',
User = 'user', User = 'user',
} }

View File

@ -25,7 +25,3 @@ export function mergeAndRemoveDuplicates(
return result.reverse() return result.reverse()
} }
export const generateMessageId = () => {
return `m-${Date.now()}`
}