Skip to content

Commit

Permalink
fix(thread): janhq#1043 default model to prefer active model (janhq#1070
Browse files Browse the repository at this point in the history
)

Signed-off-by: James <[email protected]>
Co-authored-by: James <[email protected]>
  • Loading branch information
namchuai and James authored Dec 19, 2023
1 parent d528dc8 commit 55ab4ae
Show file tree
Hide file tree
Showing 4 changed files with 136 additions and 47 deletions.
57 changes: 20 additions & 37 deletions web/containers/DropdownListSidebar/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useEffect, useState } from 'react'
import { useCallback, useEffect, useState } from 'react'

import { InferenceEngine, Model } from '@janhq/core'
import {
Expand All @@ -20,26 +20,25 @@ import { twMerge } from 'tailwind-merge'

import { MainViewState } from '@/constants/screens'

import { useActiveModel } from '@/hooks/useActiveModel'
import { useEngineSettings } from '@/hooks/useEngineSettings'
import { getDownloadedModels } from '@/hooks/useGetDownloadedModels'

import { useMainViewState } from '@/hooks/useMainViewState'

import useRecommendedModel from '@/hooks/useRecommendedModel'

import { toGigabytes } from '@/utils/converter'

import { activeThreadAtom, threadStatesAtom } from '@/helpers/atoms/Thread.atom'

export const selectedModelAtom = atom<Model | undefined>(undefined)

export default function DropdownListSidebar() {
const [downloadedModels, setDownloadedModels] = useState<Model[]>([])
const setSelectedModel = useSetAtom(selectedModelAtom)
const threadStates = useAtomValue(threadStatesAtom)
const activeThread = useAtomValue(activeThreadAtom)
const [selected, setSelected] = useState<Model | undefined>()
const { setMainViewState } = useMainViewState()
const { activeModel, stateModel } = useActiveModel()
const [opeenAISettings, setOpenAISettings] = useState<
const [openAISettings, setOpenAISettings] = useState<
{ api_key: string } | undefined
>(undefined)
const { readOpenAISettings, saveOpenAISettings } = useEngineSettings()
Expand All @@ -50,43 +49,27 @@ export default function DropdownListSidebar() {
})
}, [])

const { recommendedModel, downloadedModels } = useRecommendedModel()

useEffect(() => {
getDownloadedModels().then((downloadedModels) => {
setDownloadedModels(
downloadedModels.sort((a, b) =>
a.engine !== InferenceEngine.nitro &&
b.engine === InferenceEngine.nitro
? 1
: -1
)
)
if (downloadedModels.length > 0) {
setSelected(
downloadedModels.filter(
(x) => x.id === activeThread?.assistants[0].model.id
)[0] || downloadedModels[0]
)
setSelectedModel(
downloadedModels.filter(
(x) => x.id === activeThread?.assistants[0].model.id
)[0] || downloadedModels[0]
)
}
})
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [activeThread, activeModel, stateModel.loading])
setSelected(recommendedModel)
setSelectedModel(recommendedModel)
}, [recommendedModel, setSelectedModel])

const onValueSelected = useCallback(
(modelId: string) => {
const model = downloadedModels.find((m) => m.id === modelId)
setSelected(model)
setSelectedModel(model)
},
[downloadedModels, setSelectedModel]
)

const threadStates = useAtomValue(threadStatesAtom)
if (!activeThread) {
return null
}
const finishInit = threadStates[activeThread.id].isFinishInit ?? true

const onValueSelected = (value: string) => {
setSelected(downloadedModels.filter((x) => x.id === value)[0])
setSelectedModel(downloadedModels.filter((x) => x.id === value)[0])
}

return (
<>
<Select
Expand Down Expand Up @@ -151,7 +134,7 @@ export default function DropdownListSidebar() {
<Input
id="assistant-instructions"
placeholder="Enter your API_KEY"
defaultValue={opeenAISettings?.api_key}
defaultValue={openAISettings?.api_key}
onChange={(e) => {
saveOpenAISettings({ apiKey: e.target.value })
}}
Expand Down
7 changes: 3 additions & 4 deletions web/hooks/useActiveModel.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { EventName, events } from '@janhq/core'
import { Model } from '@janhq/core'
import { EventName, events, Model } from '@janhq/core'
import { atom, useAtom } from 'jotai'

import { toaster } from '@/containers/Toast'

import { useGetDownloadedModels } from './useGetDownloadedModels'

import { extensionManager } from '@/extension'
import { LAST_USED_MODEL_ID } from './useRecommendedModel'

export const activeModelAtom = atom<Model | undefined>(undefined)

Expand Down Expand Up @@ -51,6 +49,7 @@ export function useActiveModel() {
return
}

localStorage.setItem(LAST_USED_MODEL_ID, model.id)
events.emit(EventName.OnModelInit, model)
}

Expand Down
9 changes: 3 additions & 6 deletions web/hooks/useGetDownloadedModels.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,7 @@ export function useGetDownloadedModels() {
return { downloadedModels, setDownloadedModels }
}

export async function getDownloadedModels(): Promise<Model[]> {
const models = await extensionManager
export const getDownloadedModels = async (): Promise<Model[]> =>
extensionManager
.get<ModelExtension>(ExtensionType.Model)
?.getDownloadedModels()

return models ?? []
}
?.getDownloadedModels() ?? []
110 changes: 110 additions & 0 deletions web/hooks/useRecommendedModel.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import { useCallback, useEffect, useState } from 'react'

import { Model, InferenceEngine } from '@janhq/core'

import { atom, useAtomValue } from 'jotai'

import { activeModelAtom } from './useActiveModel'
import { getDownloadedModels } from './useGetDownloadedModels'

import { activeThreadAtom, threadStatesAtom } from '@/helpers/atoms/Thread.atom'

export const lastUsedModel = atom<Model | undefined>(undefined)

export const LAST_USED_MODEL_ID = 'last-used-model-id'

/**
* A hook that return the recommended model when user
* wants to create a new thread.
*
* The precedence is as follows:
* 1. Active model
* 2. If no active model(s), then the last used model
* 3. If no active or last used model, then the 1st model on the list
*/
export default function useRecommendedModel() {
const activeModel = useAtomValue(activeModelAtom)
const [downloadedModels, setDownloadedModels] = useState<Model[]>([])
const [recommendedModel, setRecommendedModel] = useState<Model | undefined>()
const threadStates = useAtomValue(threadStatesAtom)
const activeThread = useAtomValue(activeThreadAtom)

const getAndSortDownloadedModels = useCallback(async (): Promise<Model[]> => {
const models = (await getDownloadedModels()).sort((a, b) =>
a.engine !== InferenceEngine.nitro && b.engine === InferenceEngine.nitro
? 1
: -1
)
setDownloadedModels(models)
return models
}, [])

const getRecommendedModel = useCallback(async (): Promise<
Model | undefined
> => {
if (!activeThread) {
return
}

const finishInit = threadStates[activeThread.id].isFinishInit ?? true
if (finishInit) {
const modelId = activeThread.assistants[0]?.model.id
const models = await getAndSortDownloadedModels()
const model = models.find((model) => model.id === modelId)

if (model) {
setRecommendedModel(model)
}

return
}

if (activeModel) {
// if we have active model alr, then we can just use that
console.debug(`Using active model ${activeModel.id}`)
setRecommendedModel(activeModel)
return
}

// sort the model, for display purpose
const models = await getAndSortDownloadedModels()
if (models.length === 0) {
// if we have no downloaded models, then can't recommend anything
console.debug("No downloaded models, can't recommend anything")
return
}

// otherwise, get the last used model id
const lastUsedModelId = localStorage.getItem(LAST_USED_MODEL_ID)

// if we don't have [lastUsedModelId], then we can just use the first model
// in the downloaded list
if (!lastUsedModelId) {
console.debug(
`No last used model, using first model in list ${models[0].id}}`
)
setRecommendedModel(models[0])
return
}

const lastUsedModel = models.find((model) => model.id === lastUsedModelId)
if (!lastUsedModel) {
// if we can't find the last used model, then we can just use the first model
// in the downloaded list
console.debug(
`Last used model ${lastUsedModelId} not found, using first model in list ${models[0].id}}`
)
setRecommendedModel(models[0])
return
}

console.debug(`Using last used model ${lastUsedModel.id}`)
setRecommendedModel(lastUsedModel)
}, [getAndSortDownloadedModels, activeThread])

useEffect(() => {
getRecommendedModel()
}, [getRecommendedModel])

return { recommendedModel, downloadedModels }
}

0 comments on commit 55ab4ae

Please sign in to comment.