fix: empty model page not shown when delete all threads and models (#3343)

* fix: empty model page not shown when delete all threads and models

* fix: blank state when delete jan data folder content (#3345)

* test template name

---------

Co-authored-by: Van Pham <64197333+Van-QA@users.noreply.github.com>
This commit is contained in:
NamH 2024-08-12 19:51:58 +07:00 committed by GitHub
parent fdab8af057
commit 9e29fcd69e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 239 additions and 101 deletions

View File

@ -1,5 +1,5 @@
--- ---
name: Bug report name: _Bug report
about: Create a report to help us improve Jan about: Create a report to help us improve Jan
title: 'bug: [DESCRIPTION]' title: 'bug: [DESCRIPTION]'
labels: 'type: bug' labels: 'type: bug'

View File

@ -1,11 +1,21 @@
'use client' 'use client'
import { useEffect } from 'react' import { useEffect, useMemo } from 'react'
import { useAtomValue } from 'jotai' import { Engine } from '@cortexso/cortex.js/resources'
import {
EngineStatus,
LocalEngine,
LocalEngines,
Model,
RemoteEngine,
RemoteEngines,
} from '@janhq/core'
import { useAtomValue, useSetAtom } from 'jotai'
import useAssistantCreate, { janAssistant } from '@/hooks/useAssistantCreate' import useAssistantCreate, { janAssistant } from '@/hooks/useAssistantCreate'
import useAssistantQuery from '@/hooks/useAssistantQuery' import useAssistantQuery from '@/hooks/useAssistantQuery'
import useCortex from '@/hooks/useCortex'
import useEngineQuery from '@/hooks/useEngineQuery' import useEngineQuery from '@/hooks/useEngineQuery'
import { useLoadTheme } from '@/hooks/useLoadTheme' import { useLoadTheme } from '@/hooks/useLoadTheme'
import useModelHub from '@/hooks/useModelHub' import useModelHub from '@/hooks/useModelHub'
@ -13,17 +23,24 @@ import useModelQuery from '@/hooks/useModelQuery'
import useThreadCreateMutation from '@/hooks/useThreadCreateMutation' import useThreadCreateMutation from '@/hooks/useThreadCreateMutation'
import useThreadQuery from '@/hooks/useThreadQuery' import useThreadQuery from '@/hooks/useThreadQuery'
import { getSelectedModelAtom } from '@/helpers/atoms/Model.atom' import {
getSelectedModelAtom,
updateSelectedModelAtom,
} from '@/helpers/atoms/Model.atom'
import { threadsAtom } from '@/helpers/atoms/Thread.atom' import { threadsAtom } from '@/helpers/atoms/Thread.atom'
const DataLoader: React.FC = () => { const DataLoader: React.FC = () => {
const selectedModel = useAtomValue(getSelectedModelAtom) const selectedModel = useAtomValue(getSelectedModelAtom)
const setSelectedModel = useSetAtom(updateSelectedModelAtom)
const allThreads = useAtomValue(threadsAtom) const allThreads = useAtomValue(threadsAtom)
const { data: assistants } = useAssistantQuery() const { data: assistants } = useAssistantQuery()
const { data: models } = useModelQuery() const { data: models } = useModelQuery()
const { data: threads, isLoading: isFetchingThread } = useThreadQuery() const { data: threads, isLoading: isFetchingThread } = useThreadQuery()
const { data: engineData } = useEngineQuery()
const { data: modelHubData } = useModelHub()
const createThreadMutation = useThreadCreateMutation() const createThreadMutation = useThreadCreateMutation()
const assistantCreateMutation = useAssistantCreate() const assistantCreateMutation = useAssistantCreate()
const { createModel } = useCortex()
useEffect(() => { useEffect(() => {
if (!assistants) return if (!assistants) return
@ -34,19 +51,105 @@ const DataLoader: React.FC = () => {
} }
}, [assistants, assistantCreateMutation]) }, [assistants, assistantCreateMutation])
const isAnyRemoteModelConfigured = useMemo(() => {
if (!engineData) return false
let result = false
for (const engine of engineData) {
if (RemoteEngines.includes(engine.name as RemoteEngine)) {
if (engine.status === EngineStatus.Ready) {
result = true
}
}
}
return result
}, [engineData])
const isAnyModelReady = useMemo(() => {
if (!models) return false
return models.length > 0
}, [models])
// automatically create new thread if thread list is empty // automatically create new thread if thread list is empty
useEffect(() => { useEffect(() => {
if (isFetchingThread) return if (isFetchingThread) return
if (allThreads.length > 0) return if (allThreads.length > 0) return
if (!assistants || assistants.length === 0) return if (!assistants || assistants.length === 0) return
if (!models || models.length === 0) return const shouldCreateNewThread = isAnyRemoteModelConfigured || isAnyModelReady
if (allThreads.length === 0 && !createThreadMutation.isPending) {
const model = selectedModel ?? models[0] if (shouldCreateNewThread && !createThreadMutation.isPending) {
// if we already have selected model then can safely proceed
if (selectedModel) {
const assistant = assistants[0] const assistant = assistants[0]
console.log('Create new thread because user have no thread') console.debug(
'Create new thread because user have no thread, with selected model',
selectedModel.model
)
createThreadMutation.mutate({ createThreadMutation.mutate({
modelId: model.id, modelId: selectedModel.model,
assistant: assistant,
})
return
}
let modelToBeUsed: Model | undefined = undefined
// if we have a model registered already, try to use it and prioritize local model
if (models && models.length > 0) {
for (const model of models) {
if (!model.engine) continue
if (LocalEngines.includes(model.engine as LocalEngine)) {
modelToBeUsed = model
}
}
// if we don't have it, then just take the first one
if (!modelToBeUsed) {
modelToBeUsed = models[0]
}
} else {
if (!engineData) return
// we don't have nay registered model, so will need to check the remote engine
const remoteEngineReadyList: Engine[] = []
for (const engine of engineData) {
if (RemoteEngines.includes(engine.name as RemoteEngine)) {
if (engine.status === EngineStatus.Ready) {
remoteEngineReadyList.push(engine)
}
}
}
if (remoteEngineReadyList.length === 0) {
console.debug("No remote engine ready, can't create thread")
return
}
// find the model from hub that using the engine
if (!modelHubData) return
const remoteEngineReadyNames = remoteEngineReadyList.map((e) => e.name)
console.log('remoteEngineReady:', remoteEngineReadyNames)
// loop through the modelHubData.modelCategories to find the model that using the engine
for (const [key, value] of modelHubData.modelCategories) {
if (remoteEngineReadyNames.includes(key) && value.length > 0) {
modelToBeUsed = value[0].model
if (modelToBeUsed) break
}
}
}
if (!modelToBeUsed) {
console.debug('No model to be used')
return
}
console.log(
'Create new thread because user have no thread, model to be used:',
modelToBeUsed.model
)
createModel(modelToBeUsed)
setSelectedModel(modelToBeUsed)
const assistant = assistants[0]
createThreadMutation.mutate({
modelId: modelToBeUsed.model,
assistant: assistant, assistant: assistant,
}) })
} }
@ -58,11 +161,15 @@ const DataLoader: React.FC = () => {
createThreadMutation, createThreadMutation,
allThreads, allThreads,
selectedModel, selectedModel,
isAnyModelReady,
isAnyRemoteModelConfigured,
engineData,
modelHubData,
setSelectedModel,
createModel,
]) ])
useModelHub()
useLoadTheme() useLoadTheme()
useEngineQuery()
return null return null
} }

View File

@ -2,11 +2,12 @@ import { useCallback, useEffect, useRef } from 'react'
import { DownloadState2 } from '@janhq/core' import { DownloadState2 } from '@janhq/core'
import { fetchEventSource } from '@microsoft/fetch-event-source' import { fetchEventSource } from '@microsoft/fetch-event-source'
import { useQueryClient } from '@tanstack/react-query'
import { useAtomValue, useSetAtom } from 'jotai' import { useAtomValue, useSetAtom } from 'jotai'
import { downloadStateListAtom } from '@/hooks/useDownloadState' import { downloadStateListAtom } from '@/hooks/useDownloadState'
import useModels from '@/hooks/useModels' import { modelQueryKey } from '@/hooks/useModelQuery'
import { waitingForCortexAtom } from '@/helpers/atoms/App.atom' import { waitingForCortexAtom } from '@/helpers/atoms/App.atom'
import { hostAtom } from '@/helpers/atoms/AppConfig.atom' import { hostAtom } from '@/helpers/atoms/AppConfig.atom'
@ -21,12 +22,12 @@ const DownloadEventListener: React.FC = () => {
const abortController = useRef(new AbortController()) const abortController = useRef(new AbortController())
const setDownloadStateList = useSetAtom(downloadStateListAtom) const setDownloadStateList = useSetAtom(downloadStateListAtom)
const setWaitingForCortex = useSetAtom(waitingForCortexAtom) const setWaitingForCortex = useSetAtom(waitingForCortexAtom)
const { getModels } = useModels()
const updateImportingModelProgress = useSetAtom( const updateImportingModelProgress = useSetAtom(
updateImportingModelProgressAtom updateImportingModelProgressAtom
) )
const setImportingModelSuccess = useSetAtom(setImportingModelSuccessAtom) const setImportingModelSuccess = useSetAtom(setImportingModelSuccessAtom)
const queryClient = useQueryClient()
const handleLocalImportModels = useCallback( const handleLocalImportModels = useCallback(
(events: DownloadState2[]) => { (events: DownloadState2[]) => {
@ -38,9 +39,10 @@ const DownloadEventListener: React.FC = () => {
updateImportingModelProgress(event.id, event.progress) updateImportingModelProgress(event.id, event.progress)
} }
} }
getModels()
queryClient.invalidateQueries({ queryKey: modelQueryKey })
}, },
[setImportingModelSuccess, updateImportingModelProgress, getModels] [setImportingModelSuccess, updateImportingModelProgress, queryClient]
) )
const subscribeDownloadEvent = useCallback(async () => { const subscribeDownloadEvent = useCallback(async () => {
@ -54,7 +56,6 @@ const DownloadEventListener: React.FC = () => {
const localImportEvents: DownloadState2[] = [] const localImportEvents: DownloadState2[] = []
// filter out the import local events // filter out the import local events
for (const event of downloadEvents) { for (const event of downloadEvents) {
console.debug('Receiving event', event)
if ( if (
isAbsolutePath(event.id) && isAbsolutePath(event.id) &&
event.type === 'model' && event.type === 'model' &&

View File

@ -2,6 +2,7 @@ import React, { Fragment, useCallback, useMemo, useState } from 'react'
import { Button, Modal, Badge } from '@janhq/joi' import { Button, Modal, Badge } from '@janhq/joi'
import { useQueryClient } from '@tanstack/react-query'
import { atom, useAtom, useSetAtom } from 'jotai' import { atom, useAtom, useSetAtom } from 'jotai'
import { AlertTriangleIcon } from 'lucide-react' import { AlertTriangleIcon } from 'lucide-react'
@ -11,7 +12,7 @@ import Spinner from '@/containers/Loader/Spinner'
import useMigratingData from '@/hooks/useMigratingData' import useMigratingData from '@/hooks/useMigratingData'
import useModels from '@/hooks/useModels' import { modelQueryKey } from '@/hooks/useModelQuery'
import { didShowMigrationWarningAtom } from '@/helpers/atoms/AppConfig.atom' import { didShowMigrationWarningAtom } from '@/helpers/atoms/AppConfig.atom'
@ -31,7 +32,7 @@ const ModalMigrations = () => {
useState<MigrationState>('idle') useState<MigrationState>('idle')
const [modelMigrationState, setModelMigrationState] = const [modelMigrationState, setModelMigrationState] =
useState<MigrationState>('idle') useState<MigrationState>('idle')
const { getModels } = useModels() const queryClient = useQueryClient()
const getStepTitle = () => { const getStepTitle = () => {
switch (step) { switch (step) {
@ -74,8 +75,8 @@ const ModalMigrations = () => {
setStep(2) setStep(2)
await migratingModels() await migratingModels()
await migrationThreadsAndMessages() await migrationThreadsAndMessages()
getModels() queryClient.invalidateQueries({ queryKey: modelQueryKey })
}, [migratingModels, migrationThreadsAndMessages, getModels]) }, [migratingModels, migrationThreadsAndMessages, queryClient])
const onDismiss = useCallback(() => { const onDismiss = useCallback(() => {
setStep(1) setStep(1)

View File

@ -7,10 +7,11 @@ import {
StatusAndEvent, StatusAndEvent,
} from '@janhq/core' } from '@janhq/core'
import { fetchEventSource } from '@microsoft/fetch-event-source' import { fetchEventSource } from '@microsoft/fetch-event-source'
import { useQueryClient } from '@tanstack/react-query'
import { useAtomValue, useSetAtom } from 'jotai' import { useAtomValue, useSetAtom } from 'jotai'
import { removeDownloadSuccessItemAtom } from '@/hooks/useDownloadState' import { removeDownloadSuccessItemAtom } from '@/hooks/useDownloadState'
import useModels from '@/hooks/useModels' import { modelQueryKey } from '@/hooks/useModelQuery'
import { toaster } from '../Toast' import { toaster } from '../Toast'
@ -25,7 +26,7 @@ function ModelEventListener() {
const removeDownloadSuccessItem = useSetAtom(removeDownloadSuccessItemAtom) const removeDownloadSuccessItem = useSetAtom(removeDownloadSuccessItemAtom)
const setIsLoadingModel = useSetAtom(isLoadingModelAtom) const setIsLoadingModel = useSetAtom(isLoadingModelAtom)
const { getModels } = useModels() const queryClient = useQueryClient()
const handleModelEvent = useCallback( const handleModelEvent = useCallback(
(modelEvent: ModelEvent) => { (modelEvent: ModelEvent) => {
@ -64,11 +65,11 @@ function ModelEventListener() {
case 'model-downloaded': case 'model-downloaded':
removeDownloadSuccessItem(modelEvent.model) removeDownloadSuccessItem(modelEvent.model)
getModels() queryClient.invalidateQueries({ queryKey: modelQueryKey })
break break
case 'model-deleted': case 'model-deleted':
getModels() queryClient.invalidateQueries({ queryKey: modelQueryKey })
break break
case 'stopping-failed': case 'stopping-failed':
@ -84,7 +85,7 @@ function ModelEventListener() {
break break
} }
}, },
[getModels, removeDownloadSuccessItem, setIsLoadingModel] [removeDownloadSuccessItem, setIsLoadingModel, queryClient]
) )
const subscribeModelEvent = useCallback(async () => { const subscribeModelEvent = useCallback(async () => {

View File

@ -1,14 +1,8 @@
import { RemoteEngine } from '@janhq/core' import { RemoteEngine } from '@janhq/core'
import { atom } from 'jotai' import { atom } from 'jotai'
import { atomWithStorage } from 'jotai/utils'
export type SetupRemoteModelStage = 'NONE' | 'SETUP_INTRO' | 'SETUP_API_KEY' export type SetupRemoteModelStage = 'NONE' | 'SETUP_INTRO' | 'SETUP_API_KEY'
const IS_ANY_REMOTE_MODEL_CONFIGURED = 'isAnyRemoteModelConfigured'
export const isAnyRemoteModelConfiguredAtom = atomWithStorage(
IS_ANY_REMOTE_MODEL_CONFIGURED,
false
)
const remoteModelSetUpStageAtom = atom<SetupRemoteModelStage>('NONE') const remoteModelSetUpStageAtom = atom<SetupRemoteModelStage>('NONE')
const engineBeingSetUpAtom = atom<RemoteEngine | undefined>(undefined) const engineBeingSetUpAtom = atom<RemoteEngine | undefined>(undefined)
const remoteEngineBeingSetUpMetadataAtom = atom< const remoteEngineBeingSetUpMetadataAtom = atom<

View File

@ -168,11 +168,14 @@ const useModelHub = () => {
results[1].data.forEach((modelEntry) => { results[1].data.forEach((modelEntry) => {
const engine = modelEntry.engine const engine = modelEntry.engine
if (modelEntry.remoteModel === true && engine) { if (modelEntry.remoteModel === true && engine) {
// @ts-expect-error ignore if (data.modelCategories.has(engine)) {
data.modelCategories[engine] = data.modelCategories[engine] data.modelCategories.set(
? // @ts-expect-error ignore engine,
[...data.modelCategories[engine], modelEntry] data.modelCategories.get(engine)!.concat(modelEntry)
: [modelEntry] )
} else {
data.modelCategories.set(engine, [modelEntry])
}
} }
}) })
} }

View File

@ -6,27 +6,12 @@ import { toaster } from '@/containers/Toast'
import useCortex from './useCortex' import useCortex from './useCortex'
import { import { removeDownloadedModelAtom } from '@/helpers/atoms/Model.atom'
downloadedModelsAtom,
removeDownloadedModelAtom,
} from '@/helpers/atoms/Model.atom'
const useModels = () => { const useModels = () => {
const setDownloadedModels = useSetAtom(downloadedModelsAtom)
const removeDownloadedModel = useSetAtom(removeDownloadedModelAtom) const removeDownloadedModel = useSetAtom(removeDownloadedModelAtom)
const { const { deleteModel: cortexDeleteModel, updateModel: cortexUpdateModel } =
fetchModels, useCortex()
deleteModel: cortexDeleteModel,
updateModel: cortexUpdateModel,
} = useCortex()
const getModels = useCallback(() => {
const getDownloadedModels = async () => {
const models = await fetchModels()
setDownloadedModels(models)
}
getDownloadedModels()
}, [setDownloadedModels, fetchModels])
const deleteModel = useCallback( const deleteModel = useCallback(
async (modelId: string) => { async (modelId: string) => {
@ -48,7 +33,7 @@ const useModels = () => {
[cortexUpdateModel] [cortexUpdateModel]
) )
return { getModels, deleteModel, updateModel } return { deleteModel, updateModel }
} }
export default useModels export default useModels

View File

@ -3,6 +3,7 @@ import React, { useCallback, useMemo } from 'react'
import { EngineStatus, LocalEngines, RemoteEngine } from '@janhq/core' import { EngineStatus, LocalEngines, RemoteEngine } from '@janhq/core'
import { Button } from '@janhq/joi' import { Button } from '@janhq/joi'
import { useQueryClient } from '@tanstack/react-query'
import { useAtomValue, useSetAtom } from 'jotai' import { useAtomValue, useSetAtom } from 'jotai'
import { CloudDownload } from 'lucide-react' import { CloudDownload } from 'lucide-react'
@ -14,7 +15,7 @@ import useAssistantQuery from '@/hooks/useAssistantQuery'
import useCortex from '@/hooks/useCortex' import useCortex from '@/hooks/useCortex'
import useEngineQuery from '@/hooks/useEngineQuery' import useEngineQuery from '@/hooks/useEngineQuery'
import useModels from '@/hooks/useModels' import { modelQueryKey } from '@/hooks/useModelQuery'
import useThreads from '@/hooks/useThreads' import useThreads from '@/hooks/useThreads'
import { HfModelEntry } from '@/utils/huggingface' import { HfModelEntry } from '@/utils/huggingface'
@ -32,6 +33,7 @@ const HubModelCard: React.FC<HfModelEntry> = ({ name, downloads, model }) => {
const downloadedModels = useAtomValue(downloadedModelsAtom) const downloadedModels = useAtomValue(downloadedModelsAtom)
const { data: assistants } = useAssistantQuery() const { data: assistants } = useAssistantQuery()
const { data: engineData } = useEngineQuery() const { data: engineData } = useEngineQuery()
const queryClient = useQueryClient()
const setUpRemoteModelStage = useSetAtom(setUpRemoteModelStageAtom) const setUpRemoteModelStage = useSetAtom(setUpRemoteModelStageAtom)
const setLocalModelModalStage = useSetAtom(localModelModalStageAtom) const setLocalModelModalStage = useSetAtom(localModelModalStageAtom)
@ -39,7 +41,6 @@ const HubModelCard: React.FC<HfModelEntry> = ({ name, downloads, model }) => {
const { createThread } = useThreads() const { createThread } = useThreads()
const setMainViewState = useSetAtom(mainViewStateAtom) const setMainViewState = useSetAtom(mainViewStateAtom)
const { createModel } = useCortex() const { createModel } = useCortex()
const { getModels } = useModels()
const isLocalModel = useMemo( const isLocalModel = useMemo(
() => () =>
@ -131,13 +132,12 @@ const HubModelCard: React.FC<HfModelEntry> = ({ name, downloads, model }) => {
if (isApiKeyAdded) { if (isApiKeyAdded) {
createModel(model).then(() => { createModel(model).then(() => {
getModels() queryClient.invalidateQueries({ queryKey: modelQueryKey })
}) })
return return
} }
} }
}, [ }, [
getModels,
createModel, createModel,
createThread, createThread,
setMainViewState, setMainViewState,
@ -149,6 +149,7 @@ const HubModelCard: React.FC<HfModelEntry> = ({ name, downloads, model }) => {
isLocalModel, isLocalModel,
downloadedModels, downloadedModels,
assistants, assistants,
queryClient,
]) ])
const owner = model?.metadata?.owned_by ?? '' const owner = model?.metadata?.owned_by ?? ''

View File

@ -1,6 +1,8 @@
import React, { useCallback } from 'react' import React, { useCallback } from 'react'
import { EngineStatus, RemoteEngine } from '@janhq/core' import { EngineStatus, RemoteEngine } from '@janhq/core'
import { useQueryClient } from '@tanstack/react-query'
import { useAtomValue, useSetAtom } from 'jotai' import { useAtomValue, useSetAtom } from 'jotai'
import { toaster } from '@/containers/Toast' import { toaster } from '@/containers/Toast'
@ -10,7 +12,7 @@ import useAssistantQuery from '@/hooks/useAssistantQuery'
import useCortex from '@/hooks/useCortex' import useCortex from '@/hooks/useCortex'
import useEngineQuery from '@/hooks/useEngineQuery' import useEngineQuery from '@/hooks/useEngineQuery'
import useModels from '@/hooks/useModels' import { modelQueryKey } from '@/hooks/useModelQuery'
import useThreads from '@/hooks/useThreads' import useThreads from '@/hooks/useThreads'
import { HfModelEntry } from '@/utils/huggingface' import { HfModelEntry } from '@/utils/huggingface'
@ -23,12 +25,11 @@ const RemoteModelCard: React.FC<HfModelEntry> = ({ name, model }) => {
const { createThread } = useThreads() const { createThread } = useThreads()
const setMainViewState = useSetAtom(mainViewStateAtom) const setMainViewState = useSetAtom(mainViewStateAtom)
const setUpRemoteModelStage = useSetAtom(setUpRemoteModelStageAtom) const setUpRemoteModelStage = useSetAtom(setUpRemoteModelStageAtom)
const queryClient = useQueryClient()
const { createModel } = useCortex() const { createModel } = useCortex()
const { getModels } = useModels()
const downloadedModels = useAtomValue(downloadedModelsAtom) const downloadedModels = useAtomValue(downloadedModelsAtom)
const { data: assistants } = useAssistantQuery() const { data: assistants } = useAssistantQuery()
const { data: engineData } = useEngineQuery() const { data: engineData } = useEngineQuery()
const modelDisplayName = model?.name ?? name const modelDisplayName = model?.name ?? name
@ -85,7 +86,7 @@ const RemoteModelCard: React.FC<HfModelEntry> = ({ name, model }) => {
if (isApiKeyAdded) { if (isApiKeyAdded) {
// TODO: useMutation reactQuery? // TODO: useMutation reactQuery?
await createModel(model) await createModel(model)
getModels() queryClient.invalidateQueries({ queryKey: modelQueryKey })
if (!assistants || assistants.length === 0) { if (!assistants || assistants.length === 0) {
toaster({ toaster({
title: 'No assistant available.', title: 'No assistant available.',
@ -109,11 +110,11 @@ const RemoteModelCard: React.FC<HfModelEntry> = ({ name, model }) => {
createModel, createModel,
createThread, createThread,
downloadedModels, downloadedModels,
getModels,
model, model,
setMainViewState, setMainViewState,
setUpRemoteModelStage, setUpRemoteModelStage,
modelDisplayName, modelDisplayName,
queryClient,
]) ])
return ( return (

View File

@ -4,7 +4,7 @@ import Image from 'next/image'
import { EngineStatus } from '@janhq/core' import { EngineStatus } from '@janhq/core'
import { Button, Input, Modal } from '@janhq/joi' import { Button, Input, Modal } from '@janhq/joi'
import { useAtom, useSetAtom } from 'jotai' import { useAtom } from 'jotai'
import { ArrowUpRight } from 'lucide-react' import { ArrowUpRight } from 'lucide-react'
import useEngineMutation from '@/hooks/useEngineMutation' import useEngineMutation from '@/hooks/useEngineMutation'
@ -12,13 +12,10 @@ import useEngineQuery from '@/hooks/useEngineQuery'
import { getTitleByCategory } from '@/utils/model-engine' import { getTitleByCategory } from '@/utils/model-engine'
import { isAnyRemoteModelConfiguredAtom } from '@/helpers/atoms/SetupRemoteModel.atom'
import { setUpRemoteModelStageAtom } from '@/helpers/atoms/SetupRemoteModel.atom' import { setUpRemoteModelStageAtom } from '@/helpers/atoms/SetupRemoteModel.atom'
const SetUpApiKeyModal: React.FC = () => { const SetUpApiKeyModal: React.FC = () => {
const updateEngineConfig = useEngineMutation() const updateEngineConfig = useEngineMutation()
const isAnyRemoteModelConfigured = useSetAtom(isAnyRemoteModelConfiguredAtom)
const { data: engineData } = useEngineQuery() const { data: engineData } = useEngineQuery()
const [{ stage, remoteEngine, metadata }, setUpRemoteModelStage] = useAtom( const [{ stage, remoteEngine, metadata }, setUpRemoteModelStage] = useAtom(
@ -50,8 +47,7 @@ const SetUpApiKeyModal: React.FC = () => {
value: apiKey, value: apiKey,
}, },
}) })
isAnyRemoteModelConfigured(true) }, [remoteEngine, updateEngineConfig, apiKey])
}, [remoteEngine, updateEngineConfig, apiKey, isAnyRemoteModelConfigured])
const onDismiss = useCallback(() => { const onDismiss = useCallback(() => {
setUpRemoteModelStage('NONE', undefined) setUpRemoteModelStage('NONE', undefined)

View File

@ -44,11 +44,11 @@ const HubScreen2: React.FC = () => {
if (!data) return <div>Failed to fetch models</div> if (!data) return <div>Failed to fetch models</div>
const engineModelMap = new Map<typeof RemoteEngines, HfModelEntry[]>() const engineModelMap = new Map<typeof RemoteEngines, HfModelEntry[]>()
Object.entries(data.modelCategories).forEach(([key, value]) => { for (const [key, value] of data.modelCategories) {
if (key !== 'HuggingFace' && key !== 'BuiltInModels') { if (key !== 'HuggingFace' && key !== 'BuiltInModels') {
engineModelMap.set(key as unknown as typeof RemoteEngines, value) engineModelMap.set(key as unknown as typeof RemoteEngines, value)
} }
}) }
if (detailCategory) { if (detailCategory) {
return ( return (

View File

@ -21,6 +21,7 @@ import { twMerge } from 'tailwind-merge'
import { toaster } from '@/containers/Toast' import { toaster } from '@/containers/Toast'
import useAssistantQuery from '@/hooks/useAssistantQuery' import useAssistantQuery from '@/hooks/useAssistantQuery'
import useEngineInit from '@/hooks/useEngineInit'
import useEngineQuery from '@/hooks/useEngineQuery' import useEngineQuery from '@/hooks/useEngineQuery'
import useModelStart from '@/hooks/useModelStart' import useModelStart from '@/hooks/useModelStart'
import useModelStop from '@/hooks/useModelStop' import useModelStop from '@/hooks/useModelStop'
@ -54,6 +55,7 @@ const ModelItem: React.FC<Props> = ({ model }) => {
const isEngineReady = const isEngineReady =
engineData?.find((e) => e.name === model.engine)?.status === engineData?.find((e) => e.name === model.engine)?.status ===
EngineStatus.Ready EngineStatus.Ready
const initializeEngine = useEngineInit()
const [menu, setMenu] = useState<HTMLDivElement | null>(null) const [menu, setMenu] = useState<HTMLDivElement | null>(null)
const [toggle, setToggle] = useState<HTMLDivElement | null>(null) const [toggle, setToggle] = useState<HTMLDivElement | null>(null)
@ -74,6 +76,46 @@ const ModelItem: React.FC<Props> = ({ model }) => {
stopModel.mutate(modelId) stopModel.mutate(modelId)
return return
} }
const modelEngine = model.engine
if (!modelEngine) {
toaster({
title: 'Failed to start model',
description: `Engine for model ${model.model} is undefined`,
type: 'error',
})
return
}
if (!engineData) {
toaster({
title: 'Failed to start model',
description: `Engine data is not available. Please try again!`,
type: 'error',
})
return
}
const engineStatus = engineData.find((e) => e.name === modelEngine)
if (!engineStatus) {
toaster({
title: 'Failed to start model',
description: `Engine ${modelEngine} is not available`,
type: 'error',
})
console.error(`Engine ${modelEngine} is not available`)
return
}
if (
LocalEngines.find((e) => e === modelEngine) != null &&
engineStatus.status === 'not_initialized'
) {
toaster({
title: 'Please wait for engine to initialize',
description: `Please retry after engine ${engineStatus.name} is installed.`,
type: 'default',
})
initializeEngine.mutate(modelEngine)
return
}
if (activeModels.length >= concurrentModelWarningThreshold) { if (activeModels.length >= concurrentModelWarningThreshold) {
// if max concurrent models reached, stop the first model // if max concurrent models reached, stop the first model
@ -88,6 +130,9 @@ const ModelItem: React.FC<Props> = ({ model }) => {
stopModel, stopModel,
activeModels.length, activeModels.length,
setShowWarningMultipleModelModal, setShowWarningMultipleModelModal,
engineData,
initializeEngine,
model,
] ]
) )

View File

@ -53,11 +53,11 @@ const OnDeviceStarterScreen = () => {
data.modelCategories.get('HuggingFace') || [] data.modelCategories.get('HuggingFace') || []
const engineModelMap = new Map<typeof RemoteEngines, HfModelEntry[]>() const engineModelMap = new Map<typeof RemoteEngines, HfModelEntry[]>()
Object.entries(data.modelCategories).forEach(([key, value]) => { for (const [key, value] of data.modelCategories) {
if (key !== 'HuggingFace' && key !== 'BuiltInModels') { if (key !== 'HuggingFace' && key !== 'BuiltInModels') {
engineModelMap.set(key as unknown as typeof RemoteEngines, value) engineModelMap.set(key as unknown as typeof RemoteEngines, value)
} }
}) }
const models: HfModelEntry[] = builtInModels.concat(huggingFaceModels) const models: HfModelEntry[] = builtInModels.concat(huggingFaceModels)

View File

@ -1,10 +1,14 @@
import { Fragment, useEffect } from 'react' import { Fragment, useMemo } from 'react'
import { Model } from '@janhq/core' import {
import { useAtom, useAtomValue } from 'jotai' EngineStatus,
LlmEngine,
RemoteEngine,
RemoteEngines,
} from '@janhq/core'
import { useAtomValue } from 'jotai'
import useCortex from '@/hooks/useCortex' import useEngineQuery from '@/hooks/useEngineQuery'
import useModels from '@/hooks/useModels'
import ThreadLeftPanel from '@/screens/Thread/ThreadLeftPanel' import ThreadLeftPanel from '@/screens/Thread/ThreadLeftPanel'
@ -14,43 +18,44 @@ import ThreadRightPanel from './ThreadRightPanel'
import { waitingForCortexAtom } from '@/helpers/atoms/App.atom' import { waitingForCortexAtom } from '@/helpers/atoms/App.atom'
import { downloadedModelsAtom } from '@/helpers/atoms/Model.atom' import { downloadedModelsAtom } from '@/helpers/atoms/Model.atom'
import {
isAnyRemoteModelConfiguredAtom,
setUpRemoteModelStageAtom,
} from '@/helpers/atoms/SetupRemoteModel.atom'
const ThreadScreen = () => { const ThreadScreen = () => {
const downloadedModels = useAtomValue(downloadedModelsAtom) const downloadedModels = useAtomValue(downloadedModelsAtom)
const waitingForCortex = useAtomValue(waitingForCortexAtom) const waitingForCortex = useAtomValue(waitingForCortexAtom)
const isAnyRemoteModelConfigured = useAtomValue( const { data: engineData } = useEngineQuery()
isAnyRemoteModelConfiguredAtom
)
const { createModel } = useCortex()
const { getModels } = useModels()
const [{ metadata }] = useAtom(setUpRemoteModelStageAtom) const isAnyRemoteModelConfigured = useMemo(() => {
if (!engineData) return false
useEffect(() => { let result = false
if (isAnyRemoteModelConfigured) { for (const engine of engineData) {
createModel(metadata?.model as Model) if (RemoteEngines.includes(engine.name as RemoteEngine)) {
getModels() if (engine.status === EngineStatus.Ready) {
result = true
} }
// eslint-disable-next-line react-hooks/exhaustive-deps }
}, [isAnyRemoteModelConfigured]) }
return result
}, [engineData])
const shouldShowEmptyModel = useMemo(
() => !downloadedModels.length && !isAnyRemoteModelConfigured,
[downloadedModels, isAnyRemoteModelConfigured]
)
if (waitingForCortex) return null if (waitingForCortex) return null
return ( return (
<div className="relative flex h-full w-full flex-1 overflow-x-hidden"> <div className="relative flex h-full w-full flex-1 overflow-x-hidden">
{!downloadedModels.length && !isAnyRemoteModelConfigured ? ( {shouldShowEmptyModel ? (
<EmptyModel /> <EmptyModel />
) : ( ) : (
<Fragment> <Fragment>
<ThreadLeftPanel /> <ThreadLeftPanel />
<ThreadCenterPanel /> <ThreadCenterPanel />
<ThreadRightPanel />
</Fragment> </Fragment>
)} )}
<ThreadRightPanel />
</div> </div>
) )
} }

View File

@ -106,7 +106,6 @@ export const fetchCortexHubModels = async (): Promise<HfModelEntry[]> => {
} }
} }
} }
return modelEntries return modelEntries
} }
@ -333,9 +332,8 @@ export const fetchHuggingFaceRepoData = async (
return data return data
} }
// TODO: move this to somewhere else
export interface HfModelEntry extends ModelEntry { export interface HfModelEntry extends ModelEntry {
model?: Model // TODO: deprecated this model?: Model
remoteModel?: boolean remoteModel?: boolean
engine?: LlmEngine engine?: LlmEngine
} }