feat: allow users to refresh cloud model list (#4698)

* feat: allow users to refresh cloud model list

* chore: reusable model list refresh

* chore: clean up
This commit is contained in:
Louis 2025-02-19 23:24:29 +07:00 committed by GitHub
parent 046e8d5094
commit eba6884abb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 105 additions and 13 deletions

View File

@ -1,6 +1,6 @@
{
"name": "jan",
"version": "0.1.1737985524",
"version": "0.1.1",
"main": "./build/main.js",
"author": "Jan <service@jan.ai>",
"license": "MIT",

View File

@ -1 +1 @@
1.0.10
1.0.11-rc1

View File

@ -1,6 +1,6 @@
import { motion } from 'framer-motion'
const Spinner = ({ size = 40, strokeWidth = 4 }) => {
const Spinner = ({ size = 40, strokeWidth = 4, className = '' }) => {
const radius = size / 2 - strokeWidth
const circumference = 2 * Math.PI * radius
@ -11,6 +11,7 @@ const Spinner = ({ size = 40, strokeWidth = 4 }) => {
viewBox={`0 0 ${size} ${size}`}
style={{ overflow: 'visible' }}
animate={{ rotate: 360 }}
className={className}
transition={{
repeat: Infinity,
duration: 2, // Adjust for desired speed

View File

@ -1,4 +1,4 @@
import { useMemo } from 'react'
import { useCallback, useMemo, useState } from 'react'
import {
ExtensionTypeEnum,
@ -32,6 +32,13 @@ export const releasedEnginesLatestCacheAtom = atomWithStorage<{
timestamp: number
} | null>('releasedEnginesLatestCache', null, undefined, { getOnInit: true })
export interface RemoteModelList {
data?: {
id?: string
name?: string
}[]
}
// fetcher function
async function fetchExtensionData<T>(
extension: EngineManagementExtension | null,
@ -88,8 +95,12 @@ export function useGetRemoteModels(name: string) {
error,
mutate,
} = useSWR(
extension ? 'remoteModels' : null,
() => fetchExtensionData(extension, (ext) => ext.getRemoteModels(name)),
extension ? `remoteModels_${name}` : null,
() =>
fetchExtensionData(
extension,
(ext) => ext.getRemoteModels(name) as Promise<RemoteModelList>
),
{
revalidateOnFocus: false,
revalidateOnReconnect: true,
@ -456,3 +467,30 @@ export const useGetEngineModelSources = () => {
),
}
}
/**
* Refresh model list
* @param engine
* @returns
*/
export const useRefreshModelList = (engine: string) => {
const [refreshingModels, setRefreshingModels] = useState(false)
const { mutate: fetchRemoteModels } = useGetRemoteModels(engine)
const refreshModels = useCallback(() => {
setRefreshingModels(true)
fetchRemoteModels()
.then((remoteModelList) =>
Promise.all(
remoteModelList?.data?.map((model: { id?: string }) =>
model?.id
? addRemoteEngineModel(model.id, engine).catch(() => {})
: {}
) ?? []
)
)
.finally(() => setRefreshingModels(false))
}, [fetchRemoteModels])
return { refreshingModels, refreshModels }
}

View File

@ -1,6 +1,6 @@
{
"name": "@janhq/web",
"version": "0.5.13",
"version": "0.5.15",
"private": true,
"homepage": "./",
"scripts": {

View File

@ -7,13 +7,17 @@ import {
ArrowLeftIcon,
DownloadIcon,
FileJson,
RefreshCwIcon,
SettingsIcon,
} from 'lucide-react'
import Spinner from '@/containers/Loader/Spinner'
import ModelDownloadButton from '@/containers/ModelDownloadButton'
import { MainViewState } from '@/constants/screens'
import { useRefreshModelList } from '@/hooks/useEngineManagement'
import { MarkdownTextMessage } from '@/screens/Thread/ThreadCenterPanel/TextMessage/MarkdownTextMessage'
import { toGigabytes } from '@/utils/converter'
@ -30,6 +34,8 @@ type Props = {
const ModelPage = ({ model, onGoBack }: Props) => {
const setSelectedSetting = useSetAtom(selectedSettingAtom)
const setMainViewState = useSetAtom(mainViewStateAtom)
const { refreshingModels, refreshModels } = useRefreshModelList(model.id)
return (
<ScrollArea data-testid="hub-container-test-id" className="h-full w-full">
<div className="flex h-full w-full justify-center">
@ -127,7 +133,7 @@ const ModelPage = ({ model, onGoBack }: Props) => {
<table className="w-full p-4">
<thead className="bg-[hsla(var(--tertiary-bg))]">
<tr>
<th className="flex-1 px-6 py-3 text-left text-sm font-semibold">
<th className="flex flex-1 flex-row items-center justify-between px-6 py-3 text-left text-sm font-semibold">
{model.type !== 'cloud' ? 'Version' : 'Models'}
</th>
{model.type !== 'cloud' && (
@ -140,7 +146,27 @@ const ModelPage = ({ model, onGoBack }: Props) => {
</th>
</>
)}
<th className="w-[120px]"></th>
<th className="w-[120px]">
{model.type === 'cloud' && (
<Button
theme={'ghost'}
variant={'outline'}
className="h-7 px-2"
onClick={() => refreshModels()}
>
{refreshingModels ? (
<Spinner
size={16}
strokeWidth={2}
className="mr-2"
/>
) : (
<RefreshCwIcon size={16} className="mr-2" />
)}
Refresh
</Button>
)}
</th>
</tr>
</thead>
<tbody>

View File

@ -15,15 +15,27 @@ interface EngineConfig extends OriginalEngineConfig {
[key: string]: any
}
import { ScrollArea, Input, TextArea } from '@janhq/joi'
import { ScrollArea, Input, TextArea, Button } from '@janhq/joi'
import { useAtomValue, useSetAtom } from 'jotai'
import { set } from 'lodash'
import { ChevronDown, ChevronRight, Eye, EyeOff } from 'lucide-react'
import {
ChevronDown,
ChevronRight,
Eye,
EyeOff,
RefreshCwIcon,
} from 'lucide-react'
import { twMerge } from 'tailwind-merge'
import { updateEngine, useGetEngines } from '@/hooks/useEngineManagement'
import Spinner from '@/containers/Loader/Spinner'
import {
updateEngine,
useGetEngines,
useRefreshModelList,
} from '@/hooks/useEngineManagement'
import { getTitleByEngine } from '@/utils/modelEngine'
@ -51,6 +63,7 @@ const RemoteEngineSettings = ({
const setSelectedModel = useSetAtom(selectedModelAtom)
const customEngineLogo = getLogoEngine(name)
const threads = useAtomValue(threadsAtom)
const { refreshingModels, refreshModels } = useRefreshModelList(name)
const engine =
engines &&
@ -214,8 +227,22 @@ const RemoteEngineSettings = ({
<div>
<h6 className="mb-2 line-clamp-1 font-semibold">Model</h6>
</div>
<div className="flex gap-2">
<Button
theme={'ghost'}
variant={'outline'}
onClick={() => refreshModels()}
>
{refreshingModels ? (
<Spinner size={16} strokeWidth={2} className="mr-2" />
) : (
<RefreshCwIcon size={16} className="mr-2" />
)}
Refresh
</Button>
<ModalAddModel engine={name} />
</div>
</div>
<div>
{remoteModels &&