diff --git a/web/containers/Providers/DataLoader.tsx b/web/containers/Providers/DataLoader.tsx index afa47b42eb..1f2b8c835d 100644 --- a/web/containers/Providers/DataLoader.tsx +++ b/web/containers/Providers/DataLoader.tsx @@ -1,11 +1,21 @@ '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 useAssistantQuery from '@/hooks/useAssistantQuery' +import useCortex from '@/hooks/useCortex' import useEngineQuery from '@/hooks/useEngineQuery' import { useLoadTheme } from '@/hooks/useLoadTheme' import useModelHub from '@/hooks/useModelHub' @@ -13,17 +23,24 @@ import useModelQuery from '@/hooks/useModelQuery' import useThreadCreateMutation from '@/hooks/useThreadCreateMutation' 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' const DataLoader: React.FC = () => { const selectedModel = useAtomValue(getSelectedModelAtom) + const setSelectedModel = useSetAtom(updateSelectedModelAtom) const allThreads = useAtomValue(threadsAtom) const { data: assistants } = useAssistantQuery() const { data: models } = useModelQuery() const { data: threads, isLoading: isFetchingThread } = useThreadQuery() + const { data: engineData } = useEngineQuery() + const { data: modelHubData } = useModelHub() const createThreadMutation = useThreadCreateMutation() const assistantCreateMutation = useAssistantCreate() + const { createModel } = useCortex() useEffect(() => { if (!assistants) return @@ -34,19 +51,105 @@ const DataLoader: React.FC = () => { } }, [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 useEffect(() => { if (isFetchingThread) return if (allThreads.length > 0) return if (!assistants || assistants.length === 0) return - if (!models || models.length === 0) return - if (allThreads.length === 0 && !createThreadMutation.isPending) { - const model = selectedModel ?? models[0] - const assistant = assistants[0] + const shouldCreateNewThread = isAnyRemoteModelConfigured || isAnyModelReady + + if (shouldCreateNewThread && !createThreadMutation.isPending) { + // if we already have selected model then can safely proceed + if (selectedModel) { + 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({ + 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: model.id, + modelId: modelToBeUsed.model, assistant: assistant, }) } @@ -58,11 +161,15 @@ const DataLoader: React.FC = () => { createThreadMutation, allThreads, selectedModel, + isAnyModelReady, + isAnyRemoteModelConfigured, + engineData, + modelHubData, + setSelectedModel, + createModel, ]) - useModelHub() useLoadTheme() - useEngineQuery() return null } diff --git a/web/containers/Providers/DownloadEventListener.tsx b/web/containers/Providers/DownloadEventListener.tsx index 9ec6382ae7..bb60fa41dc 100644 --- a/web/containers/Providers/DownloadEventListener.tsx +++ b/web/containers/Providers/DownloadEventListener.tsx @@ -2,11 +2,12 @@ import { useCallback, useEffect, useRef } from 'react' import { DownloadState2 } from '@janhq/core' import { fetchEventSource } from '@microsoft/fetch-event-source' +import { useQueryClient } from '@tanstack/react-query' import { useAtomValue, useSetAtom } from 'jotai' import { downloadStateListAtom } from '@/hooks/useDownloadState' -import useModels from '@/hooks/useModels' +import { modelQueryKey } from '@/hooks/useModelQuery' import { waitingForCortexAtom } from '@/helpers/atoms/App.atom' import { hostAtom } from '@/helpers/atoms/AppConfig.atom' @@ -21,12 +22,12 @@ const DownloadEventListener: React.FC = () => { const abortController = useRef(new AbortController()) const setDownloadStateList = useSetAtom(downloadStateListAtom) const setWaitingForCortex = useSetAtom(waitingForCortexAtom) - const { getModels } = useModels() const updateImportingModelProgress = useSetAtom( updateImportingModelProgressAtom ) const setImportingModelSuccess = useSetAtom(setImportingModelSuccessAtom) + const queryClient = useQueryClient() const handleLocalImportModels = useCallback( (events: DownloadState2[]) => { @@ -38,9 +39,10 @@ const DownloadEventListener: React.FC = () => { updateImportingModelProgress(event.id, event.progress) } } - getModels() + + queryClient.invalidateQueries({ queryKey: modelQueryKey }) }, - [setImportingModelSuccess, updateImportingModelProgress, getModels] + [setImportingModelSuccess, updateImportingModelProgress, queryClient] ) const subscribeDownloadEvent = useCallback(async () => { @@ -54,7 +56,6 @@ const DownloadEventListener: React.FC = () => { const localImportEvents: DownloadState2[] = [] // filter out the import local events for (const event of downloadEvents) { - console.debug('Receiving event', event) if ( isAbsolutePath(event.id) && event.type === 'model' && diff --git a/web/containers/Providers/ModalMigrations.tsx b/web/containers/Providers/ModalMigrations.tsx index 6f3cc6abc1..8f1746674d 100644 --- a/web/containers/Providers/ModalMigrations.tsx +++ b/web/containers/Providers/ModalMigrations.tsx @@ -2,6 +2,7 @@ import React, { Fragment, useCallback, useMemo, useState } from 'react' import { Button, Modal, Badge } from '@janhq/joi' +import { useQueryClient } from '@tanstack/react-query' import { atom, useAtom, useSetAtom } from 'jotai' import { AlertTriangleIcon } from 'lucide-react' @@ -11,7 +12,7 @@ import Spinner from '@/containers/Loader/Spinner' import useMigratingData from '@/hooks/useMigratingData' -import useModels from '@/hooks/useModels' +import { modelQueryKey } from '@/hooks/useModelQuery' import { didShowMigrationWarningAtom } from '@/helpers/atoms/AppConfig.atom' @@ -31,7 +32,7 @@ const ModalMigrations = () => { useState('idle') const [modelMigrationState, setModelMigrationState] = useState('idle') - const { getModels } = useModels() + const queryClient = useQueryClient() const getStepTitle = () => { switch (step) { @@ -74,8 +75,8 @@ const ModalMigrations = () => { setStep(2) await migratingModels() await migrationThreadsAndMessages() - getModels() - }, [migratingModels, migrationThreadsAndMessages, getModels]) + queryClient.invalidateQueries({ queryKey: modelQueryKey }) + }, [migratingModels, migrationThreadsAndMessages, queryClient]) const onDismiss = useCallback(() => { setStep(1) diff --git a/web/containers/Providers/ModelEventListener.tsx b/web/containers/Providers/ModelEventListener.tsx index 110f80c692..b872898d16 100644 --- a/web/containers/Providers/ModelEventListener.tsx +++ b/web/containers/Providers/ModelEventListener.tsx @@ -7,10 +7,11 @@ import { StatusAndEvent, } from '@janhq/core' import { fetchEventSource } from '@microsoft/fetch-event-source' +import { useQueryClient } from '@tanstack/react-query' import { useAtomValue, useSetAtom } from 'jotai' import { removeDownloadSuccessItemAtom } from '@/hooks/useDownloadState' -import useModels from '@/hooks/useModels' +import { modelQueryKey } from '@/hooks/useModelQuery' import { toaster } from '../Toast' @@ -25,7 +26,7 @@ function ModelEventListener() { const removeDownloadSuccessItem = useSetAtom(removeDownloadSuccessItemAtom) const setIsLoadingModel = useSetAtom(isLoadingModelAtom) - const { getModels } = useModels() + const queryClient = useQueryClient() const handleModelEvent = useCallback( (modelEvent: ModelEvent) => { @@ -64,11 +65,11 @@ function ModelEventListener() { case 'model-downloaded': removeDownloadSuccessItem(modelEvent.model) - getModels() + queryClient.invalidateQueries({ queryKey: modelQueryKey }) break case 'model-deleted': - getModels() + queryClient.invalidateQueries({ queryKey: modelQueryKey }) break case 'stopping-failed': @@ -84,7 +85,7 @@ function ModelEventListener() { break } }, - [getModels, removeDownloadSuccessItem, setIsLoadingModel] + [removeDownloadSuccessItem, setIsLoadingModel, queryClient] ) const subscribeModelEvent = useCallback(async () => { diff --git a/web/helpers/atoms/SetupRemoteModel.atom.ts b/web/helpers/atoms/SetupRemoteModel.atom.ts index 798e6d02fd..a3a07eda8c 100644 --- a/web/helpers/atoms/SetupRemoteModel.atom.ts +++ b/web/helpers/atoms/SetupRemoteModel.atom.ts @@ -1,14 +1,8 @@ import { RemoteEngine } from '@janhq/core' import { atom } from 'jotai' -import { atomWithStorage } from 'jotai/utils' 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('NONE') const engineBeingSetUpAtom = atom(undefined) const remoteEngineBeingSetUpMetadataAtom = atom< diff --git a/web/hooks/useModelHub.ts b/web/hooks/useModelHub.ts index 7b29f70163..a87de67c8d 100644 --- a/web/hooks/useModelHub.ts +++ b/web/hooks/useModelHub.ts @@ -168,11 +168,14 @@ const useModelHub = () => { results[1].data.forEach((modelEntry) => { const engine = modelEntry.engine if (modelEntry.remoteModel === true && engine) { - // @ts-expect-error ignore - data.modelCategories[engine] = data.modelCategories[engine] - ? // @ts-expect-error ignore - [...data.modelCategories[engine], modelEntry] - : [modelEntry] + if (data.modelCategories.has(engine)) { + data.modelCategories.set( + engine, + data.modelCategories.get(engine)!.concat(modelEntry) + ) + } else { + data.modelCategories.set(engine, [modelEntry]) + } } }) } diff --git a/web/hooks/useModels.ts b/web/hooks/useModels.ts index 40a42da413..4d097a6da1 100644 --- a/web/hooks/useModels.ts +++ b/web/hooks/useModels.ts @@ -6,27 +6,12 @@ import { toaster } from '@/containers/Toast' import useCortex from './useCortex' -import { - downloadedModelsAtom, - removeDownloadedModelAtom, -} from '@/helpers/atoms/Model.atom' +import { removeDownloadedModelAtom } from '@/helpers/atoms/Model.atom' const useModels = () => { - const setDownloadedModels = useSetAtom(downloadedModelsAtom) const removeDownloadedModel = useSetAtom(removeDownloadedModelAtom) - const { - fetchModels, - deleteModel: cortexDeleteModel, - updateModel: cortexUpdateModel, - } = useCortex() - - const getModels = useCallback(() => { - const getDownloadedModels = async () => { - const models = await fetchModels() - setDownloadedModels(models) - } - getDownloadedModels() - }, [setDownloadedModels, fetchModels]) + const { deleteModel: cortexDeleteModel, updateModel: cortexUpdateModel } = + useCortex() const deleteModel = useCallback( async (modelId: string) => { @@ -48,7 +33,7 @@ const useModels = () => { [cortexUpdateModel] ) - return { getModels, deleteModel, updateModel } + return { deleteModel, updateModel } } export default useModels diff --git a/web/screens/HubScreen2/components/HubModelCard.tsx b/web/screens/HubScreen2/components/HubModelCard.tsx index aa6d934ad9..667bc56c1c 100644 --- a/web/screens/HubScreen2/components/HubModelCard.tsx +++ b/web/screens/HubScreen2/components/HubModelCard.tsx @@ -3,6 +3,7 @@ import React, { useCallback, useMemo } from 'react' import { EngineStatus, LocalEngines, RemoteEngine } from '@janhq/core' import { Button } from '@janhq/joi' +import { useQueryClient } from '@tanstack/react-query' import { useAtomValue, useSetAtom } from 'jotai' import { CloudDownload } from 'lucide-react' @@ -14,7 +15,7 @@ import useAssistantQuery from '@/hooks/useAssistantQuery' import useCortex from '@/hooks/useCortex' import useEngineQuery from '@/hooks/useEngineQuery' -import useModels from '@/hooks/useModels' +import { modelQueryKey } from '@/hooks/useModelQuery' import useThreads from '@/hooks/useThreads' import { HfModelEntry } from '@/utils/huggingface' @@ -32,6 +33,7 @@ const HubModelCard: React.FC = ({ name, downloads, model }) => { const downloadedModels = useAtomValue(downloadedModelsAtom) const { data: assistants } = useAssistantQuery() const { data: engineData } = useEngineQuery() + const queryClient = useQueryClient() const setUpRemoteModelStage = useSetAtom(setUpRemoteModelStageAtom) const setLocalModelModalStage = useSetAtom(localModelModalStageAtom) @@ -39,7 +41,6 @@ const HubModelCard: React.FC = ({ name, downloads, model }) => { const { createThread } = useThreads() const setMainViewState = useSetAtom(mainViewStateAtom) const { createModel } = useCortex() - const { getModels } = useModels() const isLocalModel = useMemo( () => @@ -131,13 +132,12 @@ const HubModelCard: React.FC = ({ name, downloads, model }) => { if (isApiKeyAdded) { createModel(model).then(() => { - getModels() + queryClient.invalidateQueries({ queryKey: modelQueryKey }) }) return } } }, [ - getModels, createModel, createThread, setMainViewState, @@ -149,6 +149,7 @@ const HubModelCard: React.FC = ({ name, downloads, model }) => { isLocalModel, downloadedModels, assistants, + queryClient, ]) const owner = model?.metadata?.owned_by ?? '' diff --git a/web/screens/HubScreen2/components/RemoteModelCard.tsx b/web/screens/HubScreen2/components/RemoteModelCard.tsx index b454f03d98..0d2978307c 100644 --- a/web/screens/HubScreen2/components/RemoteModelCard.tsx +++ b/web/screens/HubScreen2/components/RemoteModelCard.tsx @@ -1,6 +1,8 @@ import React, { useCallback } from 'react' import { EngineStatus, RemoteEngine } from '@janhq/core' +import { useQueryClient } from '@tanstack/react-query' + import { useAtomValue, useSetAtom } from 'jotai' import { toaster } from '@/containers/Toast' @@ -10,7 +12,7 @@ import useAssistantQuery from '@/hooks/useAssistantQuery' import useCortex from '@/hooks/useCortex' import useEngineQuery from '@/hooks/useEngineQuery' -import useModels from '@/hooks/useModels' +import { modelQueryKey } from '@/hooks/useModelQuery' import useThreads from '@/hooks/useThreads' import { HfModelEntry } from '@/utils/huggingface' @@ -23,12 +25,11 @@ const RemoteModelCard: React.FC = ({ name, model }) => { const { createThread } = useThreads() const setMainViewState = useSetAtom(mainViewStateAtom) const setUpRemoteModelStage = useSetAtom(setUpRemoteModelStageAtom) + const queryClient = useQueryClient() const { createModel } = useCortex() - const { getModels } = useModels() const downloadedModels = useAtomValue(downloadedModelsAtom) const { data: assistants } = useAssistantQuery() - const { data: engineData } = useEngineQuery() const modelDisplayName = model?.name ?? name @@ -85,7 +86,7 @@ const RemoteModelCard: React.FC = ({ name, model }) => { if (isApiKeyAdded) { // TODO: useMutation reactQuery? await createModel(model) - getModels() + queryClient.invalidateQueries({ queryKey: modelQueryKey }) if (!assistants || assistants.length === 0) { toaster({ title: 'No assistant available.', @@ -109,11 +110,11 @@ const RemoteModelCard: React.FC = ({ name, model }) => { createModel, createThread, downloadedModels, - getModels, model, setMainViewState, setUpRemoteModelStage, modelDisplayName, + queryClient, ]) return ( diff --git a/web/screens/HubScreen2/components/SetUpApiKeyModal.tsx b/web/screens/HubScreen2/components/SetUpApiKeyModal.tsx index 67b83555e7..05fb8a9fd9 100644 --- a/web/screens/HubScreen2/components/SetUpApiKeyModal.tsx +++ b/web/screens/HubScreen2/components/SetUpApiKeyModal.tsx @@ -4,7 +4,7 @@ import Image from 'next/image' import { EngineStatus } from '@janhq/core' import { Button, Input, Modal } from '@janhq/joi' -import { useAtom, useSetAtom } from 'jotai' +import { useAtom } from 'jotai' import { ArrowUpRight } from 'lucide-react' import useEngineMutation from '@/hooks/useEngineMutation' @@ -12,13 +12,10 @@ import useEngineQuery from '@/hooks/useEngineQuery' import { getTitleByCategory } from '@/utils/model-engine' -import { isAnyRemoteModelConfiguredAtom } from '@/helpers/atoms/SetupRemoteModel.atom' - import { setUpRemoteModelStageAtom } from '@/helpers/atoms/SetupRemoteModel.atom' const SetUpApiKeyModal: React.FC = () => { const updateEngineConfig = useEngineMutation() - const isAnyRemoteModelConfigured = useSetAtom(isAnyRemoteModelConfiguredAtom) const { data: engineData } = useEngineQuery() const [{ stage, remoteEngine, metadata }, setUpRemoteModelStage] = useAtom( @@ -50,8 +47,7 @@ const SetUpApiKeyModal: React.FC = () => { value: apiKey, }, }) - isAnyRemoteModelConfigured(true) - }, [remoteEngine, updateEngineConfig, apiKey, isAnyRemoteModelConfigured]) + }, [remoteEngine, updateEngineConfig, apiKey]) const onDismiss = useCallback(() => { setUpRemoteModelStage('NONE', undefined) diff --git a/web/screens/HubScreen2/index.tsx b/web/screens/HubScreen2/index.tsx index d800df2e9c..b5e5f64efe 100644 --- a/web/screens/HubScreen2/index.tsx +++ b/web/screens/HubScreen2/index.tsx @@ -44,11 +44,11 @@ const HubScreen2: React.FC = () => { if (!data) return
Failed to fetch models
const engineModelMap = new Map() - Object.entries(data.modelCategories).forEach(([key, value]) => { + for (const [key, value] of data.modelCategories) { if (key !== 'HuggingFace' && key !== 'BuiltInModels') { engineModelMap.set(key as unknown as typeof RemoteEngines, value) } - }) + } if (detailCategory) { return ( diff --git a/web/screens/Settings/MyModels/ModelItem/index.tsx b/web/screens/Settings/MyModels/ModelItem/index.tsx index 4c2519e86c..5314ba96ed 100644 --- a/web/screens/Settings/MyModels/ModelItem/index.tsx +++ b/web/screens/Settings/MyModels/ModelItem/index.tsx @@ -21,6 +21,7 @@ import { twMerge } from 'tailwind-merge' import { toaster } from '@/containers/Toast' import useAssistantQuery from '@/hooks/useAssistantQuery' +import useEngineInit from '@/hooks/useEngineInit' import useEngineQuery from '@/hooks/useEngineQuery' import useModelStart from '@/hooks/useModelStart' import useModelStop from '@/hooks/useModelStop' @@ -54,6 +55,7 @@ const ModelItem: React.FC = ({ model }) => { const isEngineReady = engineData?.find((e) => e.name === model.engine)?.status === EngineStatus.Ready + const initializeEngine = useEngineInit() const [menu, setMenu] = useState(null) const [toggle, setToggle] = useState(null) @@ -74,6 +76,46 @@ const ModelItem: React.FC = ({ model }) => { stopModel.mutate(modelId) 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 max concurrent models reached, stop the first model @@ -88,6 +130,9 @@ const ModelItem: React.FC = ({ model }) => { stopModel, activeModels.length, setShowWarningMultipleModelModal, + engineData, + initializeEngine, + model, ] ) diff --git a/web/screens/Thread/ThreadCenterPanel/ChatBody/EmptyModel/OnDeviceListStarter.tsx b/web/screens/Thread/ThreadCenterPanel/ChatBody/EmptyModel/OnDeviceListStarter.tsx index 997e57caec..fc202a0c01 100644 --- a/web/screens/Thread/ThreadCenterPanel/ChatBody/EmptyModel/OnDeviceListStarter.tsx +++ b/web/screens/Thread/ThreadCenterPanel/ChatBody/EmptyModel/OnDeviceListStarter.tsx @@ -53,11 +53,11 @@ const OnDeviceStarterScreen = () => { data.modelCategories.get('HuggingFace') || [] const engineModelMap = new Map() - Object.entries(data.modelCategories).forEach(([key, value]) => { + for (const [key, value] of data.modelCategories) { if (key !== 'HuggingFace' && key !== 'BuiltInModels') { engineModelMap.set(key as unknown as typeof RemoteEngines, value) } - }) + } const models: HfModelEntry[] = builtInModels.concat(huggingFaceModels) diff --git a/web/screens/Thread/index.tsx b/web/screens/Thread/index.tsx index a918b75e66..d3f41ac1d6 100644 --- a/web/screens/Thread/index.tsx +++ b/web/screens/Thread/index.tsx @@ -1,10 +1,14 @@ -import { Fragment, useEffect } from 'react' +import { Fragment, useMemo } from 'react' -import { Model } from '@janhq/core' -import { useAtom, useAtomValue } from 'jotai' +import { + EngineStatus, + LlmEngine, + RemoteEngine, + RemoteEngines, +} from '@janhq/core' +import { useAtomValue } from 'jotai' -import useCortex from '@/hooks/useCortex' -import useModels from '@/hooks/useModels' +import useEngineQuery from '@/hooks/useEngineQuery' import ThreadLeftPanel from '@/screens/Thread/ThreadLeftPanel' @@ -14,43 +18,44 @@ import ThreadRightPanel from './ThreadRightPanel' import { waitingForCortexAtom } from '@/helpers/atoms/App.atom' import { downloadedModelsAtom } from '@/helpers/atoms/Model.atom' -import { - isAnyRemoteModelConfiguredAtom, - setUpRemoteModelStageAtom, -} from '@/helpers/atoms/SetupRemoteModel.atom' const ThreadScreen = () => { const downloadedModels = useAtomValue(downloadedModelsAtom) const waitingForCortex = useAtomValue(waitingForCortexAtom) - const isAnyRemoteModelConfigured = useAtomValue( - isAnyRemoteModelConfiguredAtom - ) - const { createModel } = useCortex() - const { getModels } = useModels() - - const [{ metadata }] = useAtom(setUpRemoteModelStageAtom) - - useEffect(() => { - if (isAnyRemoteModelConfigured) { - createModel(metadata?.model as Model) - getModels() + const { data: engineData } = useEngineQuery() + + 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 + } + } } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [isAnyRemoteModelConfigured]) + return result + }, [engineData]) + + const shouldShowEmptyModel = useMemo( + () => !downloadedModels.length && !isAnyRemoteModelConfigured, + [downloadedModels, isAnyRemoteModelConfigured] + ) if (waitingForCortex) return null return (
- {!downloadedModels.length && !isAnyRemoteModelConfigured ? ( + {shouldShowEmptyModel ? ( ) : ( + )} -
) } diff --git a/web/utils/huggingface.ts b/web/utils/huggingface.ts index ef69195347..cae943b73d 100644 --- a/web/utils/huggingface.ts +++ b/web/utils/huggingface.ts @@ -106,7 +106,6 @@ export const fetchCortexHubModels = async (): Promise => { } } } - return modelEntries } @@ -333,9 +332,8 @@ export const fetchHuggingFaceRepoData = async ( return data } -// TODO: move this to somewhere else export interface HfModelEntry extends ModelEntry { - model?: Model // TODO: deprecated this + model?: Model remoteModel?: boolean engine?: LlmEngine }