Merge pull request #458 from janhq/allow-switching-model

fix: allow switching models when switch between conversations
This commit is contained in:
Faisal Amir 2023-10-26 14:29:55 +07:00 committed by GitHub
commit ef00edb7e8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 357 additions and 99 deletions

View File

@ -1,6 +1,5 @@
import React from 'react' import React from 'react'
import { useAtomValue, useSetAtom } from 'jotai' import { useAtomValue, useSetAtom } from 'jotai'
import { ModelManagementService } from '@janhq/core'
import { import {
getActiveConvoIdAtom, getActiveConvoIdAtom,
setActiveConvoIdAtom, setActiveConvoIdAtom,
@ -12,11 +11,13 @@ import {
} from '@helpers/atoms/MainView.atom' } from '@helpers/atoms/MainView.atom'
import { displayDate } from '@utils/datetime' import { displayDate } from '@utils/datetime'
import { twMerge } from 'tailwind-merge' import { twMerge } from 'tailwind-merge'
import { executeSerial } from '@services/pluginService' import { activeAssistantModelAtom } from '@helpers/atoms/Model.atom'
import { switchingModelConfirmationModalPropsAtom } from '@helpers/atoms/Modal.atom'
import useStartStopModel from '@hooks/useStartStopModel'
import useGetModelById from '@hooks/useGetModelById'
type Props = { type Props = {
conversation: Conversation conversation: Conversation
avatarUrl?: string
name: string name: string
summary?: string summary?: string
updatedAt?: string updatedAt?: string
@ -24,22 +25,41 @@ type Props = {
const HistoryItem: React.FC<Props> = ({ const HistoryItem: React.FC<Props> = ({
conversation, conversation,
avatarUrl,
name, name,
summary, summary,
updatedAt, updatedAt,
}) => { }) => {
const setMainViewState = useSetAtom(setMainViewStateAtom)
const activeConvoId = useAtomValue(getActiveConvoIdAtom) const activeConvoId = useAtomValue(getActiveConvoIdAtom)
const isSelected = activeConvoId === conversation._id
const activeModel = useAtomValue(activeAssistantModelAtom)
const { startModel } = useStartStopModel()
const { getModelById } = useGetModelById()
const setMainViewState = useSetAtom(setMainViewStateAtom)
const setActiveConvoId = useSetAtom(setActiveConvoIdAtom) const setActiveConvoId = useSetAtom(setActiveConvoIdAtom)
const updateConvWaiting = useSetAtom(updateConversationWaitingForResponseAtom) const updateConvWaiting = useSetAtom(updateConversationWaitingForResponseAtom)
const isSelected = activeConvoId === conversation._id const setConfirmationModalProps = useSetAtom(
switchingModelConfirmationModalPropsAtom
)
const onClick = async () => { const onClick = async () => {
const model = await executeSerial( if (conversation.modelId == null) {
ModelManagementService.GetModelById, console.debug('modelId is undefined')
conversation.modelId return
) }
const model = await getModelById(conversation.modelId)
if (model != null) {
if (activeModel == null) {
// if there's no active model, we simply load conversation's model
startModel(model._id)
} else if (activeModel._id !== model._id) {
// display confirmation modal
setConfirmationModalProps({
replacingModel: model,
})
}
}
if (conversation._id) updateConvWaiting(conversation._id, true) if (conversation._id) updateConvWaiting(conversation._id, true)

View File

@ -1,5 +1,5 @@
import HistoryItem from '../HistoryItem' import HistoryItem from '../HistoryItem'
import { useEffect, useState } from 'react' import { useEffect } from 'react'
import ExpandableHeader from '../ExpandableHeader' import ExpandableHeader from '../ExpandableHeader'
import { useAtomValue } from 'jotai' import { useAtomValue } from 'jotai'
import { searchAtom } from '@helpers/JotaiWrapper' import { searchAtom } from '@helpers/JotaiWrapper'
@ -33,7 +33,6 @@ const HistoryList: React.FC = () => {
key={convo._id} key={convo._id}
conversation={convo} conversation={convo}
summary={convo.summary} summary={convo.summary}
avatarUrl={convo.image}
name={convo.name || 'Jan'} name={convo.name || 'Jan'}
updatedAt={convo.updatedAt ?? ''} updatedAt={convo.updatedAt ?? ''}
/> />

View File

@ -3,66 +3,27 @@
import BasicPromptInput from '../BasicPromptInput' import BasicPromptInput from '../BasicPromptInput'
import BasicPromptAccessories from '../BasicPromptAccessories' import BasicPromptAccessories from '../BasicPromptAccessories'
import { useAtomValue, useSetAtom } from 'jotai' import { useAtomValue } from 'jotai'
import SecondaryButton from '../SecondaryButton' import SecondaryButton from '../SecondaryButton'
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'
import { import {
currentConversationAtom,
currentConvoStateAtom, currentConvoStateAtom,
getActiveConvoIdAtom,
} from '@helpers/atoms/Conversation.atom' } from '@helpers/atoms/Conversation.atom'
import useGetBots from '@hooks/useGetBots' import useGetInputState from '@hooks/useGetInputState'
import { activeBotAtom } from '@helpers/atoms/Bot.atom' import { Button } from '../../../uikit/button'
import { useGetDownloadedModels } from '@hooks/useGetDownloadedModels' import useStartStopModel from '@hooks/useStartStopModel'
const InputToolbar: React.FC = () => { const InputToolbar: React.FC = () => {
const activeModel = useAtomValue(activeAssistantModelAtom) const activeModel = useAtomValue(activeAssistantModelAtom)
const { requestCreateConvo } = useCreateConversation()
const currentConvoState = useAtomValue(currentConvoStateAtom) const currentConvoState = useAtomValue(currentConvoStateAtom)
const currentConvo = useAtomValue(currentConversationAtom) const { inputState, currentConvo } = useGetInputState()
const { requestCreateConvo } = useCreateConversation()
const { startModel } = useStartStopModel()
const setActiveBot = useSetAtom(activeBotAtom) const activeConvoId = useAtomValue(getActiveConvoIdAtom)
const { getBotById } = useGetBots()
const [inputState, setInputState] = useState<
'available' | 'disabled' | 'loading'
>()
const [error, setError] = useState<string | undefined>()
const { downloadedModels } = useGetDownloadedModels()
useEffect(() => {
const getReplyState = async () => {
setInputState('loading')
if (currentConvo && currentConvo.botId && currentConvo.botId.length > 0) {
// if botId is set, check if bot is available
const bot = await getBotById(currentConvo.botId)
console.debug('Found bot', JSON.stringify(bot, null, 2))
if (bot) {
setActiveBot(bot)
}
setInputState(bot ? 'available' : 'disabled')
setError(
bot
? undefined
: `Bot ${currentConvo.botId} has been deleted by its creator. Your chat history is saved but you won't be able to send new messages.`
)
} else {
const model = downloadedModels.find(
(model) => model._id === activeModel?._id
)
setInputState(model ? 'available' : 'disabled')
setError(
model
? undefined
: `Model ${activeModel?._id} cannot be found. Your chat history is saved but you won't be able to send new messages.`
)
}
}
getReplyState()
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [currentConvo])
const onNewConversationClick = () => { const onNewConversationClick = () => {
if (activeModel) { if (activeModel) {
@ -70,16 +31,42 @@ const InputToolbar: React.FC = () => {
} }
} }
if (inputState === 'loading') return <div>Loading..</div> const onStartModelClick = () => {
const modelId = currentConvo?.modelId
if (!modelId) return
startModel(modelId)
}
if (inputState === 'disabled') if (!activeConvoId) {
return null
}
if (inputState === 'model-mismatch' || inputState === 'loading') {
const message = inputState === 'loading' ? 'Loading..' : 'Model mismatch!'
return (
<div className="sticky bottom-0 flex items-center justify-center bg-background/90">
<div className="mb-2">
<p className="mx-auto my-5 line-clamp-2 text-ellipsis text-center italic text-gray-600">
{message}
</p>
<Button onClick={onStartModelClick}>
Load {currentConvo?.modelId}
</Button>
</div>
</div>
)
}
if (inputState === 'model-not-found') {
return ( return (
<div className="sticky bottom-0 flex items-center justify-center bg-background/90"> <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} Model {currentConvo?.modelId} not found! Please re-download the model
first.
</p> </p>
</div> </div>
) )
}
return ( return (
<div className="sticky bottom-0 w-full bg-background/90 px-5 py-0"> <div className="sticky bottom-0 w-full bg-background/90 px-5 py-0">

View File

@ -0,0 +1,129 @@
import React, { Fragment } from 'react'
import { Dialog, Transition } from '@headlessui/react'
import { ExclamationTriangleIcon, XMarkIcon } from '@heroicons/react/24/outline'
import { switchingModelConfirmationModalPropsAtom } from '@helpers/atoms/Modal.atom'
import { useAtom, useAtomValue } from 'jotai'
import { activeAssistantModelAtom } from '@helpers/atoms/Model.atom'
import useStartStopModel from '@hooks/useStartStopModel'
export type SwitchingModelConfirmationModalProps = {
replacingModel: AssistantModel
}
const SwitchingModelConfirmationModal: React.FC = () => {
const [props, setProps] = useAtom(switchingModelConfirmationModalPropsAtom)
const activeModel = useAtomValue(activeAssistantModelAtom)
const { startModel } = useStartStopModel()
const onConfirmSwitchModelClick = () => {
const modelId = props?.replacingModel._id
if (modelId) {
startModel(modelId)
}
setProps(undefined)
}
return (
<Transition.Root show={props != null} as={Fragment}>
<Dialog
as="div"
className="relative z-10"
onClose={() => setProps(undefined)}
>
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-200"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity" />
</Transition.Child>
<div className="fixed inset-0 z-10 w-screen overflow-y-auto">
<div className="flex min-h-full items-end justify-center p-4 text-center sm:items-center sm:p-0">
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
enterTo="opacity-100 translate-y-0 sm:scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
>
<Dialog.Panel className="relative transform overflow-hidden rounded-lg bg-white px-4 pb-4 pt-5 text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-lg sm:p-6">
<div className="absolute right-0 top-0 hidden pr-4 pt-4 sm:block">
<button
type="button"
className="rounded-md bg-white text-gray-400 hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
onClick={() => setProps(undefined)}
>
<span className="sr-only">Close</span>
<XMarkIcon className="h-6 w-6" aria-hidden="true" />
</button>
</div>
<div className="sm:flex sm:items-start">
<div className="mx-auto flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-full bg-red-100 sm:mx-0 sm:h-10 sm:w-10">
<ExclamationTriangleIcon
className="h-6 w-6 text-red-600"
aria-hidden="true"
/>
</div>
<div className="mt-3 text-center sm:ml-4 sm:mt-0 sm:text-left">
<Dialog.Title
as="h3"
className="text-base font-semibold leading-6 text-gray-900"
>
Switching model
</Dialog.Title>
<div className="mt-2 flex flex-col">
<p className="text-sm text-gray-500">
Selected conversation is using model{' '}
<span className="font-semibold text-black">
{props?.replacingModel._id}
</span>
, but the active model is using{' '}
<span className="font-semibold text-black">
{activeModel?._id}
</span>
.
</p>
<br />
<p className="text-sm text-gray-500">
Switch to
<span className="font-semibold text-black">
{' '}
{props?.replacingModel._id}?
</span>
</p>
</div>
</div>
</div>
<div className="mt-5 sm:mt-4 sm:flex sm:flex-row-reverse">
<button
type="button"
className="inline-flex w-full justify-center rounded-md bg-red-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-red-500 sm:ml-3 sm:w-auto"
onClick={onConfirmSwitchModelClick}
>
Switch
</button>
<button
type="button"
className="mt-3 inline-flex w-full justify-center rounded-md bg-white px-3 py-2 text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50 sm:mt-0 sm:w-auto"
onClick={() => setProps(undefined)}
>
Cancel
</button>
</div>
</Dialog.Panel>
</Transition.Child>
</div>
</div>
</Dialog>
</Transition.Root>
)
}
export default SwitchingModelConfirmationModal

View File

@ -69,17 +69,17 @@ const Providers = (props: PropsWithChildren) => {
return ( return (
<JotaiWrapper> <JotaiWrapper>
{setupCore && ( {setupCore && (
<EventListenerWrapper> <ThemeWrapper>
<ThemeWrapper> {activated ? (
{activated ? ( <EventListenerWrapper>
<ModalWrapper>{children}</ModalWrapper> <ModalWrapper>{children}</ModalWrapper>
) : ( </EventListenerWrapper>
<div className="bg-background flex h-screen w-screen items-center justify-center"> ) : (
<CompactLogo width={56} height={56} /> <div className="flex h-screen w-screen items-center justify-center bg-background">
</div> <CompactLogo width={56} height={56} />
)} </div>
</ThemeWrapper> )}
</EventListenerWrapper> </ThemeWrapper>
)} )}
</JotaiWrapper> </JotaiWrapper>
) )

View File

@ -3,14 +3,29 @@ import { toChatMessage } from '@models/ChatMessage'
import { events, EventName, NewMessageResponse } from '@janhq/core' import { events, EventName, NewMessageResponse } from '@janhq/core'
import { useSetAtom } from 'jotai' import { useSetAtom } from 'jotai'
import { ReactNode, useEffect } from 'react' import { ReactNode, useEffect } from 'react'
import useGetBots from '@hooks/useGetBots'
import useGetUserConversations from '@hooks/useGetUserConversations'
export default function EventHandler({ children }: { children: ReactNode }) { export default function EventHandler({ children }: { children: ReactNode }) {
const addNewMessage = useSetAtom(addNewMessageAtom) const addNewMessage = useSetAtom(addNewMessageAtom)
const updateMessage = useSetAtom(updateMessageAtom) const updateMessage = useSetAtom(updateMessageAtom)
const { getBotById } = useGetBots()
const { getConversationById } = useGetUserConversations()
function handleNewMessageResponse(message: NewMessageResponse) { async function handleNewMessageResponse(message: NewMessageResponse) {
const newResponse = toChatMessage(message) if (message.conversationId) {
addNewMessage(newResponse) const convo = await getConversationById(message.conversationId)
const botId = convo?.botId
console.debug('botId', botId)
if (botId) {
const bot = await getBotById(botId)
const newResponse = toChatMessage(message, bot)
addNewMessage(newResponse)
} else {
const newResponse = toChatMessage(message)
addNewMessage(newResponse)
}
}
} }
async function handleMessageResponseUpdate( async function handleMessageResponseUpdate(
messageResponse: NewMessageResponse messageResponse: NewMessageResponse
@ -40,5 +55,5 @@ export default function EventHandler({ children }: { children: ReactNode }) {
events.off(EventName.OnMessageResponseUpdate, handleMessageResponseUpdate) events.off(EventName.OnMessageResponseUpdate, handleMessageResponseUpdate)
} }
}, []) }, [])
return <> {children}</> return <>{children}</>
} }

View File

@ -5,6 +5,7 @@ import ConfirmDeleteConversationModal from '@/_components/ConfirmDeleteConversat
import ConfirmDeleteModelModal from '@/_components/ConfirmDeleteModelModal' import ConfirmDeleteModelModal from '@/_components/ConfirmDeleteModelModal'
import ConfirmSignOutModal from '@/_components/ConfirmSignOutModal' import ConfirmSignOutModal from '@/_components/ConfirmSignOutModal'
import MobileMenuPane from '@/_components/MobileMenuPane' import MobileMenuPane from '@/_components/MobileMenuPane'
import SwitchingModelConfirmationModal from '@/_components/SwitchingModelConfirmationModal'
import { ReactNode } from 'react' import { ReactNode } from 'react'
type Props = { type Props = {
@ -18,6 +19,7 @@ export const ModalWrapper: React.FC<Props> = ({ children }) => (
<ConfirmSignOutModal /> <ConfirmSignOutModal />
<ConfirmDeleteModelModal /> <ConfirmDeleteModelModal />
<BotListModal /> <BotListModal />
<SwitchingModelConfirmationModal />
{children} {children}
</> </>
) )

View File

@ -1,3 +1,4 @@
import { SwitchingModelConfirmationModalProps } from '@/_components/SwitchingModelConfirmationModal'
import { atom } from 'jotai' import { atom } from 'jotai'
export const showConfirmDeleteConversationModalAtom = atom(false) export const showConfirmDeleteConversationModalAtom = atom(false)
@ -7,3 +8,7 @@ export const showingAdvancedPromptAtom = atom<boolean>(false)
export const showingProductDetailAtom = atom<boolean>(false) export const showingProductDetailAtom = atom<boolean>(false)
export const showingMobilePaneAtom = atom<boolean>(false) export const showingMobilePaneAtom = atom<boolean>(false)
export const showingBotListModalAtom = atom<boolean>(false) export const showingBotListModalAtom = atom<boolean>(false)
export const switchingModelConfirmationModalPropsAtom = atom<
SwitchingModelConfirmationModalProps | undefined
>(undefined)

View File

@ -3,11 +3,15 @@ import { executeSerial } from '@services/pluginService'
import { useAtomValue, useSetAtom } from 'jotai' import { useAtomValue, useSetAtom } from 'jotai'
import { useEffect } from 'react' import { useEffect } from 'react'
import { DataService } from '@janhq/core' import { DataService } from '@janhq/core'
import { getActiveConvoIdAtom } from '@helpers/atoms/Conversation.atom' import {
getActiveConvoIdAtom,
userConversationsAtom,
} from '@helpers/atoms/Conversation.atom'
import { import {
getCurrentChatMessagesAtom, getCurrentChatMessagesAtom,
setCurrentChatMessagesAtom, setCurrentChatMessagesAtom,
} from '@helpers/atoms/ChatMessage.atom' } from '@helpers/atoms/ChatMessage.atom'
import useGetBots from './useGetBots'
/** /**
* Custom hooks to get chat messages for current(active) conversation * Custom hooks to get chat messages for current(active) conversation
@ -16,6 +20,8 @@ const useChatMessages = () => {
const setMessages = useSetAtom(setCurrentChatMessagesAtom) const setMessages = useSetAtom(setCurrentChatMessagesAtom)
const messages = useAtomValue(getCurrentChatMessagesAtom) const messages = useAtomValue(getCurrentChatMessagesAtom)
const activeConvoId = useAtomValue(getActiveConvoIdAtom) const activeConvoId = useAtomValue(getActiveConvoIdAtom)
const userConversations = useAtomValue(userConversationsAtom)
const { getBotById } = useGetBots()
const getMessages = async (convoId: string) => { const getMessages = async (convoId: string) => {
const data: any = await executeSerial( const data: any = await executeSerial(
@ -26,6 +32,12 @@ const useChatMessages = () => {
return [] return []
} }
const convo = userConversations.find((c) => c._id === convoId)
if (convo && convo.botId) {
const bot = await getBotById(convo.botId)
return parseMessages(data, bot)
}
return parseMessages(data) return parseMessages(data)
} }
@ -47,10 +59,10 @@ const useChatMessages = () => {
return { messages } return { messages }
} }
function parseMessages(messages: RawMessage[]): ChatMessage[] { function parseMessages(messages: RawMessage[], bot?: Bot): ChatMessage[] {
const newMessages: ChatMessage[] = [] const newMessages: ChatMessage[] = []
for (const m of messages) { for (const m of messages) {
const chatMessage = toChatMessage(m) const chatMessage = toChatMessage(m, bot)
newMessages.push(chatMessage) newMessages.push(chatMessage)
} }
return newMessages return newMessages

View File

@ -6,19 +6,18 @@ import {
setActiveConvoIdAtom, setActiveConvoIdAtom,
addNewConversationStateAtom, addNewConversationStateAtom,
} from '@helpers/atoms/Conversation.atom' } from '@helpers/atoms/Conversation.atom'
import useGetModelById from './useGetModelById'
const useCreateConversation = () => { const useCreateConversation = () => {
const [userConversations, setUserConversations] = useAtom( const [userConversations, setUserConversations] = useAtom(
userConversationsAtom userConversationsAtom
) )
const { getModelById } = useGetModelById()
const setActiveConvoId = useSetAtom(setActiveConvoIdAtom) const setActiveConvoId = useSetAtom(setActiveConvoIdAtom)
const addNewConvoState = useSetAtom(addNewConversationStateAtom) const addNewConvoState = useSetAtom(addNewConversationStateAtom)
const createConvoByBot = async (bot: Bot) => { const createConvoByBot = async (bot: Bot) => {
const model = await executeSerial( const model = await getModelById(bot.modelId)
ModelManagementService.GetModelById,
bot.modelId
)
if (!model) { if (!model) {
alert( alert(

View File

@ -0,0 +1,52 @@
import { currentConversationAtom } from '@helpers/atoms/Conversation.atom'
import { activeAssistantModelAtom } from '@helpers/atoms/Model.atom'
import { useAtomValue } from 'jotai'
import { useEffect, useState } from 'react'
import { useGetDownloadedModels } from './useGetDownloadedModels'
export default function useGetInputState() {
const [inputState, setInputState] = useState<InputType>('loading')
const currentConvo = useAtomValue(currentConversationAtom)
const activeModel = useAtomValue(activeAssistantModelAtom)
const { downloadedModels } = useGetDownloadedModels()
const handleInputState = (
convo: Conversation | undefined,
currentModel: AssistantModel | undefined,
models: AssistantModel[]
) => {
if (convo == null) return
if (currentModel == null) {
setInputState('loading')
return
}
// check if convo model id is in downloaded models
const isModelAvailable = downloadedModels.some(
(model) => model._id === convo.modelId
)
if (!isModelAvailable) {
// can't find model in downloaded models
setInputState('model-not-found')
return
}
if (convo.modelId !== currentModel._id) {
// in case convo model and active model is different,
// ask user to init the required model
setInputState('model-mismatch')
return
}
setInputState('available')
}
useEffect(() => {
handleInputState(currentConvo, activeModel, downloadedModels)
}, [currentConvo, activeModel, downloadedModels])
return { inputState, currentConvo }
}
type InputType = 'available' | 'loading' | 'model-mismatch' | 'model-not-found'

View File

@ -0,0 +1,23 @@
import { ModelManagementService } from '@janhq/core'
import { executeSerial } from '../../electron/core/plugin-manager/execution/extension-manager'
export default function useGetModelById() {
const getModelById = async (
modelId: string
): Promise<AssistantModel | undefined> => {
return queryModelById(modelId)
}
return { getModelById }
}
const queryModelById = async (
modelId: string
): Promise<AssistantModel | undefined> => {
const model = await executeSerial(
ModelManagementService.GetModelById,
modelId
)
return model
}

View File

@ -29,7 +29,14 @@ const useGetUserConversations = () => {
} }
} }
const getConversationById = async (
id: string
): Promise<Conversation | undefined> => {
return await executeSerial(DataService.GetConversationById, id)
}
return { return {
getConversationById,
getUserConversations, getUserConversations,
} }
} }

View File

@ -1,11 +1,12 @@
import { executeSerial } from '@services/pluginService' import { executeSerial } from '@services/pluginService'
import { ModelManagementService, InferenceService } from '@janhq/core' import { InferenceService } from '@janhq/core'
import { useAtom, useSetAtom } from 'jotai' import { useAtom, useSetAtom } from 'jotai'
import { activeAssistantModelAtom, stateModel } from '@helpers/atoms/Model.atom' import { activeAssistantModelAtom, stateModel } from '@helpers/atoms/Model.atom'
import { useState } from 'react' import useGetModelById from './useGetModelById'
export default function useStartStopModel() { export default function useStartStopModel() {
const [activeModel, setActiveModel] = useAtom(activeAssistantModelAtom) const [activeModel, setActiveModel] = useAtom(activeAssistantModelAtom)
const { getModelById } = useGetModelById()
const setStateModel = useSetAtom(stateModel) const setStateModel = useSetAtom(stateModel)
const startModel = async (modelId: string) => { const startModel = async (modelId: string) => {
@ -16,25 +17,27 @@ export default function useStartStopModel() {
setStateModel({ state: 'start', loading: true, model: modelId }) setStateModel({ state: 'start', loading: true, model: modelId })
const model = await executeSerial( const model = await getModelById(modelId)
ModelManagementService.GetModelById,
modelId
)
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 }))
return
} }
const currentTime = Date.now() const currentTime = Date.now()
console.debug('Init model: ', model._id) console.debug('Init model: ', model._id)
const res = await executeSerial(InferenceService.InitModel, model._id) const res = await initModel(model._id)
if (res?.error) { if (res?.error) {
const errorMessage = `Failed to init model: ${res.error}` const errorMessage = `Failed to init model: ${res.error}`
console.error(errorMessage) console.error(errorMessage)
alert(errorMessage) alert(errorMessage)
} else { } else {
console.debug( console.debug(
`Init model successfully!, take ${Date.now() - currentTime}ms` `Init model ${modelId} successfully!, take ${
Date.now() - currentTime
}ms`
) )
setActiveModel(model) setActiveModel(model)
} }
@ -52,3 +55,7 @@ export default function useStartStopModel() {
return { startModel, stopModel } return { startModel, stopModel }
} }
const initModel = async (modelId: string): Promise<any> => {
return executeSerial(InferenceService.InitModel, modelId)
}

View File

@ -41,7 +41,8 @@ export interface RawMessage {
} }
export const toChatMessage = ( export const toChatMessage = (
m: RawMessage | NewMessageResponse m: RawMessage | NewMessageResponse,
bot?: Bot
): ChatMessage => { ): ChatMessage => {
const createdAt = new Date(m.createdAt ?? '').getTime() const createdAt = new Date(m.createdAt ?? '').getTime()
const imageUrls: string[] = [] const imageUrls: string[] = []
@ -56,18 +57,18 @@ export const toChatMessage = (
const content = m.message ?? '' const content = m.message ?? ''
let senderName = m.user === 'user' ? 'You' : 'Assistant'
if (senderName === 'Assistant' && bot) {
senderName = bot.name
}
return { return {
id: (m._id ?? 0).toString(), id: (m._id ?? 0).toString(),
conversationId: (m.conversationId ?? 0).toString(), conversationId: (m.conversationId ?? 0).toString(),
messageType: messageType, messageType: messageType,
messageSenderType: messageSenderType, messageSenderType: messageSenderType,
senderUid: m.user?.toString() || '0', senderUid: m.user?.toString() || '0',
senderName: senderName: senderName,
m.user === 'user'
? 'You'
: m.user && m.user !== 'ai' && m.user !== 'assistant'
? m.user
: 'Assistant',
senderAvatarUrl: m.avatar senderAvatarUrl: m.avatar
? m.avatar ? m.avatar
: m.user === 'user' : m.user === 'user'