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:
parent
fdab8af057
commit
9e29fcd69e
2
.github/ISSUE_TEMPLATE/bug_report.md
vendored
2
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@ -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'
|
||||||
|
|||||||
@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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' &&
|
||||||
|
|||||||
@ -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)
|
||||||
|
|||||||
@ -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 () => {
|
||||||
|
|||||||
@ -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<
|
||||||
|
|||||||
@ -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])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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 ?? ''
|
||||||
|
|||||||
@ -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 (
|
||||||
|
|||||||
@ -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)
|
||||||
|
|||||||
@ -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 (
|
||||||
|
|||||||
@ -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,
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@ -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)
|
||||||
|
|
||||||
|
|||||||
@ -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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user