fix: duplicated messages when user switch between conversations (#441)

Signed-off-by: James <james@jan.ai>
Co-authored-by: James <james@jan.ai>
This commit is contained in:
NamH 2023-10-24 20:30:06 -07:00 committed by GitHub
parent e05c08b95f
commit 1fd47ba453
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 102 additions and 193 deletions

View File

@ -67,7 +67,6 @@
"electron-store": "^8.1.0", "electron-store": "^8.1.0",
"electron-updater": "^6.1.4", "electron-updater": "^6.1.4",
"pacote": "^17.0.4", "pacote": "^17.0.4",
"react-intersection-observer": "^9.5.2",
"request": "^2.88.2", "request": "^2.88.2",
"request-progress": "^3.0.0", "request-progress": "^3.0.0",
"use-debounce": "^9.0.4" "use-debounce": "^9.0.4"

View File

@ -4,7 +4,6 @@ import { currentPromptAtom } from '@helpers/JotaiWrapper'
import { getActiveConvoIdAtom } from '@helpers/atoms/Conversation.atom' import { getActiveConvoIdAtom } from '@helpers/atoms/Conversation.atom'
import { selectedModelAtom } from '@helpers/atoms/Model.atom' import { selectedModelAtom } from '@helpers/atoms/Model.atom'
import useCreateConversation from '@hooks/useCreateConversation' import useCreateConversation from '@hooks/useCreateConversation'
import useInitModel from '@hooks/useInitModel'
import useSendChatMessage from '@hooks/useSendChatMessage' import useSendChatMessage from '@hooks/useSendChatMessage'
import { useAtom, useAtomValue } from 'jotai' import { useAtom, useAtomValue } from 'jotai'
import { ChangeEvent, useEffect, useRef } from 'react' import { ChangeEvent, useEffect, useRef } from 'react'
@ -16,8 +15,6 @@ const BasicPromptInput: React.FC = () => {
const { sendChatMessage } = useSendChatMessage() const { sendChatMessage } = useSendChatMessage()
const { requestCreateConvo } = useCreateConversation() const { requestCreateConvo } = useCreateConversation()
const { initModel } = useInitModel()
const textareaRef = useRef<HTMLTextAreaElement>(null) const textareaRef = useRef<HTMLTextAreaElement>(null)
const handleKeyDown = async ( const handleKeyDown = async (
@ -35,7 +32,6 @@ const BasicPromptInput: React.FC = () => {
} }
await requestCreateConvo(selectedModel) await requestCreateConvo(selectedModel)
await initModel(selectedModel)
sendChatMessage() sendChatMessage()
} }
} }

View File

@ -1,60 +1,17 @@
'use client' 'use client'
import React, { useCallback, useRef, useState, useEffect } from 'react' import React from 'react'
import ChatItem from '../ChatItem' import ChatItem from '../ChatItem'
import useChatMessages from '@hooks/useChatMessages' import useChatMessages from '@hooks/useChatMessages'
import { useAtomValue } from 'jotai'
import { selectAtom } from 'jotai/utils'
import { getActiveConvoIdAtom } from '@helpers/atoms/Conversation.atom'
import { chatMessages } from '@helpers/atoms/ChatMessage.atom'
const ChatBody: React.FC = () => { const ChatBody: React.FC = () => {
const activeConversationId = useAtomValue(getActiveConvoIdAtom) ?? '' const { messages } = useChatMessages()
const messageList = useAtomValue(
selectAtom(
chatMessages,
useCallback((v) => v[activeConversationId], [activeConversationId])
)
)
const [content, setContent] = useState<React.JSX.Element[]>([])
const [offset, setOffset] = useState(0)
const { loading, hasMore } = useChatMessages(offset)
const intersectObs = useRef<any>(null)
const lastPostRef = useCallback(
(message: ChatMessage) => {
if (loading) return
if (intersectObs.current) intersectObs.current.disconnect()
intersectObs.current = new IntersectionObserver((entries) => {
if (entries[0].isIntersecting && hasMore) {
setOffset((prevOffset) => prevOffset + 5)
}
})
if (message) intersectObs.current.observe(message)
},
[loading, hasMore]
)
useEffect(() => {
const list = messageList?.map((message, index) => {
if (messageList?.length === index + 1) {
return (
// @ts-ignore
<ChatItem ref={lastPostRef} message={message} key={message.id} />
)
}
return <ChatItem message={message} key={message.id} />
})
setContent(list)
}, [messageList, lastPostRef])
return ( return (
<div className="[&>*:nth-child(odd)]:bg-background flex h-full flex-1 flex-col-reverse overflow-y-auto"> <div className="flex h-full flex-1 flex-col-reverse overflow-y-auto [&>*:nth-child(odd)]:bg-background">
{content} {messages.map((message) => (
<ChatItem message={message} key={message.id} />
))}
</div> </div>
) )
} }

View File

@ -1,19 +1,16 @@
import React from 'react' import React from 'react'
import { useAtomValue, useSetAtom } from 'jotai' import { useAtomValue, useSetAtom } from 'jotai'
import Image from 'next/image'
import { ModelManagementService } from '@janhq/core' import { ModelManagementService } from '@janhq/core'
import { executeSerial } from '../../../../electron/core/plugin-manager/execution/extension-manager' import { executeSerial } from '../../../../electron/core/plugin-manager/execution/extension-manager'
import { import {
getActiveConvoIdAtom, getActiveConvoIdAtom,
setActiveConvoIdAtom, setActiveConvoIdAtom,
updateConversationErrorAtom,
updateConversationWaitingForResponseAtom, updateConversationWaitingForResponseAtom,
} from '@helpers/atoms/Conversation.atom' } from '@helpers/atoms/Conversation.atom'
import { import {
setMainViewStateAtom, setMainViewStateAtom,
MainViewState, MainViewState,
} from '@helpers/atoms/MainView.atom' } from '@helpers/atoms/MainView.atom'
import useInitModel from '@hooks/useInitModel'
import { displayDate } from '@utils/datetime' import { displayDate } from '@utils/datetime'
import { twMerge } from 'tailwind-merge' import { twMerge } from 'tailwind-merge'
@ -36,11 +33,8 @@ const HistoryItem: React.FC<Props> = ({
const activeConvoId = useAtomValue(getActiveConvoIdAtom) const activeConvoId = useAtomValue(getActiveConvoIdAtom)
const setActiveConvoId = useSetAtom(setActiveConvoIdAtom) const setActiveConvoId = useSetAtom(setActiveConvoIdAtom)
const updateConvWaiting = useSetAtom(updateConversationWaitingForResponseAtom) const updateConvWaiting = useSetAtom(updateConversationWaitingForResponseAtom)
const updateConvError = useSetAtom(updateConversationErrorAtom)
const isSelected = activeConvoId === conversation._id const isSelected = activeConvoId === conversation._id
const { initModel } = useInitModel()
const onClick = async () => { const onClick = async () => {
const model = await executeSerial( const model = await executeSerial(
ModelManagementService.GetModelById, ModelManagementService.GetModelById,
@ -48,13 +42,6 @@ const HistoryItem: React.FC<Props> = ({
) )
if (conversation._id) updateConvWaiting(conversation._id, true) if (conversation._id) updateConvWaiting(conversation._id, true)
initModel(model).then((res: any) => {
if (conversation._id) updateConvWaiting(conversation._id, false)
if (res?.error && conversation._id) {
updateConvError(conversation._id, res.error)
}
})
if (activeConvoId !== conversation._id) { if (activeConvoId !== conversation._id) {
setMainViewState(MainViewState.Conversation) setMainViewState(MainViewState.Conversation)

View File

@ -4,9 +4,8 @@
import BasicPromptInput from '../BasicPromptInput' import BasicPromptInput from '../BasicPromptInput'
import BasicPromptAccessories from '../BasicPromptAccessories' import BasicPromptAccessories from '../BasicPromptAccessories'
import { useAtomValue, useSetAtom } from 'jotai' import { useAtomValue, useSetAtom } from 'jotai'
import { showingAdvancedPromptAtom } from '@helpers/atoms/Modal.atom'
import SecondaryButton from '../SecondaryButton' import SecondaryButton from '../SecondaryButton'
import { Fragment, useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import { PlusIcon } from '@heroicons/react/24/outline' import { PlusIcon } from '@heroicons/react/24/outline'
import useCreateConversation from '@hooks/useCreateConversation' import useCreateConversation from '@hooks/useCreateConversation'
import { activeAssistantModelAtom } from '@helpers/atoms/Model.atom' import { activeAssistantModelAtom } from '@helpers/atoms/Model.atom'
@ -19,7 +18,6 @@ import { activeBotAtom } from '@helpers/atoms/Bot.atom'
import { useGetDownloadedModels } from '@hooks/useGetDownloadedModels' import { useGetDownloadedModels } from '@hooks/useGetDownloadedModels'
const InputToolbar: React.FC = () => { const InputToolbar: React.FC = () => {
const showingAdvancedPrompt = useAtomValue(showingAdvancedPromptAtom)
const activeModel = useAtomValue(activeAssistantModelAtom) const activeModel = useAtomValue(activeAssistantModelAtom)
const { requestCreateConvo } = useCreateConversation() const { requestCreateConvo } = useCreateConversation()
const currentConvoState = useAtomValue(currentConvoStateAtom) const currentConvoState = useAtomValue(currentConvoStateAtom)
@ -76,7 +74,7 @@ const InputToolbar: React.FC = () => {
if (inputState === 'disabled') if (inputState === 'disabled')
return ( return (
<div className="bg-background/90 sticky bottom-0 flex items-center justify-center"> <div className="sticky bottom-0 flex items-center justify-center bg-background/90">
<p className="mx-auto my-5 line-clamp-2 text-ellipsis text-center italic text-gray-600"> <p className="mx-auto my-5 line-clamp-2 text-ellipsis text-center italic text-gray-600">
{error} {error}
</p> </p>
@ -84,7 +82,7 @@ const InputToolbar: React.FC = () => {
) )
return ( return (
<div className="bg-background/90 sticky bottom-0 w-full px-5 py-0"> <div className="sticky bottom-0 w-full bg-background/90 px-5 py-0">
{currentConvoState?.error && ( {currentConvoState?.error && (
<div className="flex flex-row justify-center"> <div className="flex flex-row justify-center">
<span className="mx-5 my-2 text-sm text-red-500"> <span className="mx-5 my-2 text-sm text-red-500">

View File

@ -24,12 +24,14 @@ const modelActionMapper: Record<ModelActionType, ModelActionStyle> = {
} }
type Props = { type Props = {
disabled?: boolean
type: ModelActionType type: ModelActionType
onActionClick: (type: ModelActionType) => void onActionClick: (type: ModelActionType) => void
onDeleteClick: () => void onDeleteClick: () => void
} }
const ModelActionButton: React.FC<Props> = ({ const ModelActionButton: React.FC<Props> = ({
disabled = false,
type, type,
onActionClick, onActionClick,
onDeleteClick, onDeleteClick,
@ -48,6 +50,7 @@ const ModelActionButton: React.FC<Props> = ({
<div className="flex items-center justify-end gap-x-4"> <div className="flex items-center justify-end gap-x-4">
<ModelActionMenu onDeleteClick={onDeleteClick} /> <ModelActionMenu onDeleteClick={onDeleteClick} />
<Button <Button
disabled={disabled}
size="sm" size="sm"
themes={styles.title === 'Start' ? 'accent' : 'default'} themes={styles.title === 'Start' ? 'accent' : 'default'}
onClick={() => onClick()} onClick={() => onClick()}

View File

@ -12,7 +12,7 @@ type Props = {
} }
const ModelRow: React.FC<Props> = ({ model }) => { const ModelRow: React.FC<Props> = ({ model }) => {
const { startModel, stopModel } = useStartStopModel() const { loading, startModel, stopModel } = useStartStopModel()
const activeModel = useAtomValue(activeAssistantModelAtom) const activeModel = useAtomValue(activeAssistantModelAtom)
const { deleteModel } = useDeleteModel() const { deleteModel } = useDeleteModel()
@ -57,6 +57,7 @@ const ModelRow: React.FC<Props> = ({ model }) => {
<ModelStatusComponent status={status} /> <ModelStatusComponent status={status} />
</td> </td>
<ModelActionButton <ModelActionButton
disabled={loading}
type={actionButtonType} type={actionButtonType}
onActionClick={onModelActionClick} onActionClick={onModelActionClick}
onDeleteClick={onDeleteClick} onDeleteClick={onDeleteClick}

View File

@ -1,5 +1,4 @@
import useCreateConversation from '@hooks/useCreateConversation' import useCreateConversation from '@hooks/useCreateConversation'
import PrimaryButton from '../PrimaryButton'
import { useAtomValue, useSetAtom } from 'jotai' import { useAtomValue, useSetAtom } from 'jotai'
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import { import {
@ -7,11 +6,9 @@ import {
setMainViewStateAtom, setMainViewStateAtom,
} from '@helpers/atoms/MainView.atom' } from '@helpers/atoms/MainView.atom'
import { activeAssistantModelAtom } from '@helpers/atoms/Model.atom' import { activeAssistantModelAtom } from '@helpers/atoms/Model.atom'
import useInitModel from '@hooks/useInitModel'
import { useGetDownloadedModels } from '@hooks/useGetDownloadedModels' import { useGetDownloadedModels } from '@hooks/useGetDownloadedModels'
import { Button } from '@uikit' import { Button } from '@uikit'
import { MessageCircle } from 'lucide-react'
import {MessageCircle} from "lucide-react"
enum ActionButton { enum ActionButton {
DownloadModel = 'Download a Model', DownloadModel = 'Download a Model',
@ -25,8 +22,6 @@ const SidebarEmptyHistory: React.FC = () => {
const { requestCreateConvo } = useCreateConversation() const { requestCreateConvo } = useCreateConversation()
const [action, setAction] = useState(ActionButton.DownloadModel) const [action, setAction] = useState(ActionButton.DownloadModel)
const { initModel } = useInitModel()
useEffect(() => { useEffect(() => {
if (downloadedModels.length > 0) { if (downloadedModels.length > 0) {
setAction(ActionButton.StartChat) setAction(ActionButton.StartChat)
@ -35,29 +30,24 @@ const SidebarEmptyHistory: React.FC = () => {
} }
}, [downloadedModels]) }, [downloadedModels])
const onClick = () => { const onClick = async () => {
if (action === ActionButton.DownloadModel) { if (action === ActionButton.DownloadModel) {
setMainView(MainViewState.ExploreModel) setMainView(MainViewState.ExploreModel)
} else { } else {
if (!activeModel) { if (!activeModel) {
setMainView(MainViewState.ConversationEmptyModel) setMainView(MainViewState.ConversationEmptyModel)
} else { } else {
createConversationAndInitModel(activeModel) await requestCreateConvo(activeModel)
} }
} }
} }
const createConversationAndInitModel = async (model: AssistantModel) => {
await requestCreateConvo(model)
await initModel(model)
}
return ( return (
<div className="flex flex-col items-center gap-3 py-10"> <div className="flex flex-col items-center gap-3 py-10">
<MessageCircle size={32} /> <MessageCircle size={32} />
<div className="flex flex-col items-center gap-y-2"> <div className="flex flex-col items-center gap-y-2">
<h6 className="text-center text-base">No Chat History</h6> <h6 className="text-center text-base">No Chat History</h6>
<p className="text-center text-muted-foreground mb-6"> <p className="mb-6 text-center text-muted-foreground">
Get started by creating a new chat. Get started by creating a new chat.
</p> </p>
<Button onClick={onClick} themes="accent"> <Button onClick={onClick} themes="accent">

View File

@ -4,14 +4,34 @@ import { getActiveConvoIdAtom } from './Conversation.atom'
/** /**
* Stores all chat messages for all conversations * Stores all chat messages for all conversations
*/ */
export const chatMessages = atom<Record<string, ChatMessage[]>>({}) const chatMessages = atom<Record<string, ChatMessage[]>>({})
export const currentChatMessagesAtom = atom<ChatMessage[]>((get) => { /**
* Return the chat messages for the current active conversation
*/
export const getCurrentChatMessagesAtom = atom<ChatMessage[]>((get) => {
const activeConversationId = get(getActiveConvoIdAtom) const activeConversationId = get(getActiveConvoIdAtom)
if (!activeConversationId) return [] if (!activeConversationId) return []
return get(chatMessages)[activeConversationId] ?? [] return get(chatMessages)[activeConversationId] ?? []
}) })
export const setCurrentChatMessagesAtom = atom(
null,
(get, set, messages: ChatMessage[]) => {
const currentConvoId = get(getActiveConvoIdAtom)
if (!currentConvoId) return
const newData: Record<string, ChatMessage[]> = {
...get(chatMessages),
}
newData[currentConvoId] = messages
set(chatMessages, newData)
}
)
/**
* Used for pagination. Add old messages to the current conversation
*/
export const addOldMessagesAtom = atom( export const addOldMessagesAtom = atom(
null, null,
(get, set, newMessages: ChatMessage[]) => { (get, set, newMessages: ChatMessage[]) => {

View File

@ -1,62 +1,50 @@
import { toChatMessage } from '@models/ChatMessage' import { toChatMessage } from '@models/ChatMessage'
import { executeSerial } from '@services/pluginService' import { executeSerial } from '@services/pluginService'
import { useAtomValue, useSetAtom } from 'jotai' import { useAtomValue, useSetAtom } from 'jotai'
import { useEffect, useState } from 'react' import { useEffect } from 'react'
import { DataService } from '@janhq/core' import { DataService } from '@janhq/core'
import { addOldMessagesAtom } from '@helpers/atoms/ChatMessage.atom' import { getActiveConvoIdAtom } from '@helpers/atoms/Conversation.atom'
import { import {
currentConversationAtom, getCurrentChatMessagesAtom,
conversationStatesAtom, setCurrentChatMessagesAtom,
updateConversationHasMoreAtom, } from '@helpers/atoms/ChatMessage.atom'
} from '@helpers/atoms/Conversation.atom'
/** /**
* Custom hooks to get chat messages for current(active) conversation * Custom hooks to get chat messages for current(active) conversation
*
* @param offset for pagination purpose
* @returns
*/ */
const useChatMessages = (offset = 0) => { const useChatMessages = () => {
const [loading, setLoading] = useState(true) const setMessages = useSetAtom(setCurrentChatMessagesAtom)
const addOldChatMessages = useSetAtom(addOldMessagesAtom) const messages = useAtomValue(getCurrentChatMessagesAtom)
const currentConvo = useAtomValue(currentConversationAtom) const activeConvoId = useAtomValue(getActiveConvoIdAtom)
const convoStates = useAtomValue(conversationStatesAtom)
const updateConvoHasMore = useSetAtom(updateConversationHasMoreAtom) const getMessages = async (convoId: string) => {
const data: any = await executeSerial(
DataService.GetConversationMessages,
convoId
)
if (!data) {
return []
}
return parseMessages(data)
}
useEffect(() => { useEffect(() => {
if (!currentConvo) { if (!activeConvoId) {
console.error('active convo is undefined')
return return
} }
const hasMore = convoStates[currentConvo._id ?? '']?.hasMore ?? true
if (!hasMore) return
const getMessages = async () => { getMessages(activeConvoId)
executeSerial(DataService.GetConversationMessages, currentConvo._id).then( .then((messages) => {
(data: any) => { setMessages(messages)
if (!data) { })
return .catch((err) => {
} console.error(err)
const newMessages = parseMessages(data ?? []) })
addOldChatMessages(newMessages) }, [activeConvoId])
updateConvoHasMore(currentConvo._id ?? '', false)
setLoading(false)
}
)
}
getMessages()
}, [
offset,
convoStates,
addOldChatMessages,
updateConvoHasMore,
currentConvo,
])
return { return { messages }
loading: loading,
error: undefined,
hasMore: convoStates[currentConvo?._id ?? '']?.hasMore ?? true,
}
} }
function parseMessages(messages: RawMessage[]): ChatMessage[] { function parseMessages(messages: RawMessage[]): ChatMessage[] {

View File

@ -1,25 +1,18 @@
import { useAtom, useSetAtom } from 'jotai' import { useAtom, useSetAtom } from 'jotai'
import { executeSerial } from '@services/pluginService' import { executeSerial } from '@services/pluginService'
import { DataService, ModelManagementService } from '@janhq/core' import { DataService, ModelManagementService } from '@janhq/core'
import { import {
userConversationsAtom, userConversationsAtom,
setActiveConvoIdAtom, setActiveConvoIdAtom,
addNewConversationStateAtom, addNewConversationStateAtom,
updateConversationWaitingForResponseAtom,
updateConversationErrorAtom,
} from '@helpers/atoms/Conversation.atom' } from '@helpers/atoms/Conversation.atom'
import useInitModel from './useInitModel'
const useCreateConversation = () => { const useCreateConversation = () => {
const { initModel } = useInitModel()
const [userConversations, setUserConversations] = useAtom( const [userConversations, setUserConversations] = useAtom(
userConversationsAtom userConversationsAtom
) )
const setActiveConvoId = useSetAtom(setActiveConvoIdAtom) const setActiveConvoId = useSetAtom(setActiveConvoIdAtom)
const addNewConvoState = useSetAtom(addNewConversationStateAtom) const addNewConvoState = useSetAtom(addNewConversationStateAtom)
const updateConvWaiting = useSetAtom(updateConversationWaitingForResponseAtom)
const updateConvError = useSetAtom(updateConversationErrorAtom)
const createConvoByBot = async (bot: Bot) => { const createConvoByBot = async (bot: Bot) => {
const model = await executeSerial( const model = await executeSerial(
@ -48,14 +41,6 @@ const useCreateConversation = () => {
} }
const id = await executeSerial(DataService.CreateConversation, conv) const id = await executeSerial(DataService.CreateConversation, conv)
if (id) updateConvWaiting(id, true)
initModel(model).then((res: any) => {
if (id) updateConvWaiting(id, false)
if (res?.error) {
updateConvError(id, res.error)
}
})
const mappedConvo: Conversation = { const mappedConvo: Conversation = {
_id: id, _id: id,
modelId: model._id, modelId: model._id,

View File

@ -1,5 +1,5 @@
import { currentPromptAtom } from '@helpers/JotaiWrapper' import { currentPromptAtom } from '@helpers/JotaiWrapper'
import { execute } from '@services/pluginService' import { executeSerial } from '@services/pluginService'
import { useAtom, useAtomValue, useSetAtom } from 'jotai' import { useAtom, useAtomValue, useSetAtom } from 'jotai'
import { DataService } from '@janhq/core' import { DataService } from '@janhq/core'
import { deleteConversationMessage } from '@helpers/atoms/ChatMessage.atom' import { deleteConversationMessage } from '@helpers/atoms/ChatMessage.atom'
@ -33,7 +33,7 @@ export default function useDeleteConversation() {
const deleteConvo = async () => { const deleteConvo = async () => {
if (activeConvoId) { if (activeConvoId) {
try { try {
await execute(DataService.DeleteConversation, activeConvoId) await executeSerial(DataService.DeleteConversation, activeConvoId)
const currentConversations = userConversations.filter( const currentConversations = userConversations.filter(
(c) => c._id !== activeConvoId (c) => c._id !== activeConvoId
) )

View File

@ -1,32 +0,0 @@
import { executeSerial } from '@services/pluginService'
import { InferenceService } from '@janhq/core'
import { useAtom } from 'jotai'
import { activeAssistantModelAtom } from '@helpers/atoms/Model.atom'
export default function useInitModel() {
const [activeModel, setActiveModel] = useAtom(activeAssistantModelAtom)
const initModel = async (model: AssistantModel) => {
if (activeModel && activeModel._id === model._id) {
console.debug(`Model ${model._id} is already init. Ignore..`)
return
}
const currentTime = Date.now()
console.debug('Init model: ', model._id)
const res = await executeSerial(InferenceService.InitModel, model._id)
if (res?.error) {
console.error('Failed to init model: ', res.error)
return res
} else {
console.debug(
`Init model successfully!, take ${Date.now() - currentTime}ms`
)
setActiveModel(model)
return {}
}
}
return { initModel }
}

View File

@ -1,16 +1,22 @@
import { executeSerial } from '@services/pluginService' import { executeSerial } from '@services/pluginService'
import { ModelManagementService, InferenceService } from '@janhq/core' import { ModelManagementService, InferenceService } from '@janhq/core'
import useInitModel from './useInitModel' import { useAtom, useSetAtom } from 'jotai'
import { useSetAtom } from 'jotai'
import { activeAssistantModelAtom, stateModel } from '@helpers/atoms/Model.atom' import { activeAssistantModelAtom, stateModel } from '@helpers/atoms/Model.atom'
import { useState } from 'react'
export default function useStartStopModel() { export default function useStartStopModel() {
const { initModel } = useInitModel() const [activeModel, setActiveModel] = useAtom(activeAssistantModelAtom)
const setActiveModel = useSetAtom(activeAssistantModelAtom) const [loading, setLoading] = useState<boolean>(false)
const setStateModel = useSetAtom(stateModel) const setStateModel = useSetAtom(stateModel)
const startModel = async (modelId: string) => { const startModel = async (modelId: string) => {
if (activeModel && activeModel._id === modelId) {
console.debug(`Model ${modelId} is already init. Ignore..`)
return
}
setStateModel({ state: 'start', loading: true, model: modelId }) setStateModel({ state: 'start', loading: true, model: modelId })
const model = await executeSerial( const model = await executeSerial(
ModelManagementService.GetModelById, ModelManagementService.GetModelById,
modelId modelId
@ -18,10 +24,23 @@ export default function useStartStopModel() {
if (!model) { if (!model) {
alert(`Model ${modelId} not found! Please re-download the model first.`) alert(`Model ${modelId} not found! Please re-download the model first.`)
setStateModel((prev) => ({ ...prev, loading: false })) setStateModel((prev) => ({ ...prev, loading: false }))
} else {
await initModel(model)
setStateModel((prev) => ({ ...prev, loading: false }))
} }
const currentTime = Date.now()
console.debug('Init model: ', model._id)
const res = await executeSerial(InferenceService.InitModel, model._id)
if (res?.error) {
const errorMessage = `Failed to init model: ${res.error}`
console.error(errorMessage)
alert(errorMessage)
} else {
console.debug(
`Init model successfully!, take ${Date.now() - currentTime}ms`
)
setActiveModel(model)
}
setLoading(false)
} }
const stopModel = async (modelId: string) => { const stopModel = async (modelId: string) => {
@ -33,5 +52,5 @@ export default function useStartStopModel() {
}, 500) }, 500)
} }
return { startModel, stopModel } return { loading, startModel, stopModel }
} }

View File

@ -24,12 +24,11 @@
"autoprefixer": "10.4.14", "autoprefixer": "10.4.14",
"class-variance-authority": "^0.7.0", "class-variance-authority": "^0.7.0",
"classnames": "^2.3.2", "classnames": "^2.3.2",
"embla-carousel": "^8.0.0-rc11",
"embla-carousel-react": "^8.0.0-rc11",
"eslint": "8.45.0", "eslint": "8.45.0",
"eslint-config-next": "13.4.10", "eslint-config-next": "13.4.10",
"framer-motion": "^10.16.4", "framer-motion": "^10.16.4",
"highlight.js": "^11.9.0", "highlight.js": "^11.9.0",
"react-intersection-observer": "^9.5.2",
"jotai": "^2.4.0", "jotai": "^2.4.0",
"jotai-optics": "^0.3.1", "jotai-optics": "^0.3.1",
"jwt-decode": "^3.1.2", "jwt-decode": "^3.1.2",
@ -39,7 +38,6 @@
"next": "13.4.10", "next": "13.4.10",
"next-auth": "^4.23.1", "next-auth": "^4.23.1",
"next-themes": "^0.2.1", "next-themes": "^0.2.1",
"optics-ts": "^2.4.1",
"postcss": "8.4.26", "postcss": "8.4.26",
"react": "18.2.0", "react": "18.2.0",
"react-dom": "18.2.0", "react-dom": "18.2.0",