Skip to content

Commit b266f5e

Browse files
authored
fix: model download state update (#6882)
1 parent 047483f commit b266f5e

File tree

6 files changed

+159
-125
lines changed

6 files changed

+159
-125
lines changed

web-app/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@
8282
"remark-math": "6.0.0",
8383
"sonner": "2.0.5",
8484
"tailwindcss": "4.1.4",
85-
"token.js": "npm:[email protected].29",
85+
"token.js": "npm:[email protected].30",
8686
"tw-animate-css": "1.2.8",
8787
"ulidx": "2.4.1",
8888
"unified": "11.0.5",

web-app/src/containers/DownloadButton.tsx

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ import { useTranslation } from '@/i18n'
88
import { extractModelName } from '@/lib/models'
99
import { cn, sanitizeModelId } from '@/lib/utils'
1010
import { CatalogModel } from '@/services/models/types'
11-
import { useCallback, useMemo } from 'react'
11+
import { DownloadEvent, DownloadState, events } from '@janhq/core'
12+
import { useCallback, useEffect, useMemo, useState } from 'react'
1213
import { useShallow } from 'zustand/shallow'
1314

1415
type ModelProps = {
@@ -35,6 +36,7 @@ export function DownloadButtonPlaceholder({
3536

3637
const serviceHub = useServiceHub()
3738
const huggingfaceToken = useGeneralSetting((state) => state.huggingfaceToken)
39+
const [isDownloaded, setDownloaded] = useState<boolean>(false)
3840

3941
const quant =
4042
model.quants.find((e) =>
@@ -57,6 +59,26 @@ export function DownloadButtonPlaceholder({
5759
[downloads]
5860
)
5961

62+
useEffect(() => {
63+
const isDownloaded = llamaProvider?.models.some(
64+
(m: { id: string }) =>
65+
m.id === modelId ||
66+
m.id === `${model.developer}/${sanitizeModelId(modelId)}`
67+
)
68+
if (isDownloaded) {
69+
setDownloaded(true)
70+
}
71+
}, [llamaProvider])
72+
73+
useEffect(() => {
74+
events.on(
75+
DownloadEvent.onFileDownloadAndVerificationSuccess,
76+
(state: DownloadState) => {
77+
if (state.modelId === modelId) setDownloaded(true)
78+
}
79+
)
80+
}, [])
81+
6082
const isRecommendedModel = useCallback((modelId: string) => {
6183
return (extractModelName(modelId)?.toLowerCase() ===
6284
'jan-nano-gguf') as boolean
@@ -84,11 +106,7 @@ export function DownloadButtonPlaceholder({
84106

85107
const downloadProgress =
86108
downloadProcesses.find((e) => e.id === modelId)?.progress || 0
87-
const isDownloaded = llamaProvider?.models.some(
88-
(m: { id: string }) =>
89-
m.id === modelId ||
90-
m.id === `${model.developer}/${sanitizeModelId(modelId)}`
91-
)
109+
92110
const isRecommended = isRecommendedModel(model.model_name)
93111

94112
const handleDownload = () => {
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
import { Button } from '@/components/ui/button'
2+
import { Progress } from '@/components/ui/progress'
3+
import { route } from '@/constants/routes'
4+
import { useDownloadStore } from '@/hooks/useDownloadStore'
5+
import { useGeneralSetting } from '@/hooks/useGeneralSetting'
6+
import { useModelProvider } from '@/hooks/useModelProvider'
7+
import { useServiceHub } from '@/hooks/useServiceHub'
8+
import { useTranslation } from '@/i18n'
9+
import { CatalogModel } from '@/services/models/types'
10+
import { IconDownload } from '@tabler/icons-react'
11+
import { useNavigate } from '@tanstack/react-router'
12+
import { useCallback, useMemo } from 'react'
13+
14+
export const ModelDownloadAction = ({
15+
variant,
16+
model,
17+
}: {
18+
variant: { model_id: string; path: string }
19+
model: CatalogModel
20+
}) => {
21+
const serviceHub = useServiceHub()
22+
23+
const { t } = useTranslation()
24+
const huggingfaceToken = useGeneralSetting((state) => state.huggingfaceToken)
25+
const { downloads, localDownloadingModels, addLocalDownloadingModel } =
26+
useDownloadStore()
27+
const downloadProcesses = useMemo(
28+
() =>
29+
Object.values(downloads).map((download) => ({
30+
id: download.name,
31+
name: download.name,
32+
progress: download.progress,
33+
current: download.current,
34+
total: download.total,
35+
})),
36+
[downloads]
37+
)
38+
39+
const navigate = useNavigate()
40+
41+
const handleUseModel = useCallback(
42+
(modelId: string) => {
43+
navigate({
44+
to: route.home,
45+
params: {},
46+
search: {
47+
model: {
48+
id: modelId,
49+
provider: 'llamacpp',
50+
},
51+
},
52+
})
53+
},
54+
[navigate]
55+
)
56+
57+
const isDownloading =
58+
localDownloadingModels.has(variant.model_id) ||
59+
downloadProcesses.some((e) => e.id === variant.model_id)
60+
const downloadProgress =
61+
downloadProcesses.find((e) => e.id === variant.model_id)?.progress || 0
62+
const isDownloaded = useModelProvider
63+
.getState()
64+
.getProviderByName('llamacpp')
65+
?.models.some((m: { id: string }) => m.id === variant.model_id)
66+
67+
if (isDownloading) {
68+
return (
69+
<>
70+
<div className="flex items-center gap-2 w-20">
71+
<Progress value={downloadProgress * 100} />
72+
<span className="text-xs text-center text-main-view-fg/70">
73+
{Math.round(downloadProgress * 100)}%
74+
</span>
75+
</div>
76+
</>
77+
)
78+
}
79+
80+
if (isDownloaded) {
81+
return (
82+
<div
83+
className="flex items-center justify-center rounded bg-main-view-fg/10"
84+
title={t('hub:useModel')}
85+
>
86+
<Button
87+
variant="link"
88+
size="sm"
89+
onClick={() => handleUseModel(variant.model_id)}
90+
>
91+
{t('hub:use')}
92+
</Button>
93+
</div>
94+
)
95+
}
96+
97+
return (
98+
<div
99+
className="size-6 cursor-pointer flex items-center justify-center rounded hover:bg-main-view-fg/10 transition-all duration-200 ease-in-out"
100+
title={t('hub:downloadModel')}
101+
onClick={() => {
102+
addLocalDownloadingModel(variant.model_id)
103+
serviceHub
104+
.models()
105+
.pullModelWithMetadata(
106+
variant.model_id,
107+
variant.path,
108+
(
109+
model.mmproj_models?.find(
110+
(e) => e.model_id.toLowerCase() === 'mmproj-f16'
111+
) || model.mmproj_models?.[0]
112+
)?.path,
113+
huggingfaceToken
114+
)
115+
}}
116+
>
117+
<IconDownload size={16} className="text-main-view-fg/80" />
118+
</div>
119+
)
120+
}

web-app/src/hooks/useModelProvider.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -444,9 +444,14 @@ export const useModelProvider = create<ModelProviderState>()(
444444
}
445445
})
446446
}
447+
if (version <= 6 && state?.providers) {
448+
// Most of legacy cohere models are deprecated now, remove the provider entirely
449+
// TODO: Default providers should be updated automatically somehow later
450+
state.providers = state.providers.filter((provider) => provider.provider !== 'cohere')
451+
}
447452
return state
448453
},
449-
version: 6,
454+
version: 7,
450455
}
451456
)
452457
)

web-app/src/routes/hub/index.tsx

Lines changed: 6 additions & 116 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ import {
1414
useCallback,
1515
useRef,
1616
} from 'react'
17-
import { Button } from '@/components/ui/button'
1817
import { useModelProvider } from '@/hooks/useModelProvider'
1918
import { Card, CardItem } from '@/containers/Card'
2019
import { RenderMarkdown } from '@/containers/RenderMarkdown'
@@ -42,15 +41,14 @@ import {
4241
} from '@/components/ui/dropdown-menu'
4342
import { useServiceHub } from '@/hooks/useServiceHub'
4443
import type { CatalogModel } from '@/services/models/types'
45-
import { useDownloadStore } from '@/hooks/useDownloadStore'
46-
import { Progress } from '@/components/ui/progress'
4744
import HeaderPage from '@/containers/HeaderPage'
4845
import { Loader } from 'lucide-react'
4946
import { useTranslation } from '@/i18n/react-i18next-compat'
5047
import Fuse from 'fuse.js'
5148
import { useGeneralSetting } from '@/hooks/useGeneralSetting'
5249
import { DownloadButtonPlaceholder } from '@/containers/DownloadButton'
5350
import { useShallow } from 'zustand/shallow'
51+
import { ModelDownloadAction } from '@/containers/ModelDownloadAction'
5452

5553
type SearchParams = {
5654
repo: string
@@ -247,21 +245,6 @@ function HubContent() {
247245
}
248246
}
249247

250-
const { downloads, localDownloadingModels, addLocalDownloadingModel } =
251-
useDownloadStore()
252-
253-
const downloadProcesses = useMemo(
254-
() =>
255-
Object.values(downloads).map((download) => ({
256-
id: download.name,
257-
name: download.name,
258-
progress: download.progress,
259-
current: download.current,
260-
total: download.total,
261-
})),
262-
[downloads]
263-
)
264-
265248
const navigate = useNavigate()
266249

267250
const isRecommendedModel = useCallback((modelId: string) => {
@@ -702,105 +685,12 @@ function HubContent() {
702685
checkModelSupport
703686
}
704687
/>
705-
{(() => {
706-
const isDownloading =
707-
localDownloadingModels.has(
708-
variant.model_id
709-
) ||
710-
downloadProcesses.some(
711-
(e) => e.id === variant.model_id
712-
)
713-
const downloadProgress =
714-
downloadProcesses.find(
715-
(e) => e.id === variant.model_id
716-
)?.progress || 0
717-
const isDownloaded =
718-
useModelProvider
719-
.getState()
720-
.getProviderByName('llamacpp')
721-
?.models.some(
722-
(m: { id: string }) =>
723-
m.id === variant.model_id
724-
)
725-
726-
if (isDownloading) {
727-
return (
728-
<>
729-
<div className="flex items-center gap-2 w-20">
730-
<Progress
731-
value={
732-
downloadProgress * 100
733-
}
734-
/>
735-
<span className="text-xs text-center text-main-view-fg/70">
736-
{Math.round(
737-
downloadProgress * 100
738-
)}
739-
%
740-
</span>
741-
</div>
742-
</>
743-
)
744-
}
745-
746-
if (isDownloaded) {
747-
return (
748-
<div
749-
className="flex items-center justify-center rounded bg-main-view-fg/10"
750-
title={t('hub:useModel')}
751-
>
752-
<Button
753-
variant="link"
754-
size="sm"
755-
onClick={() =>
756-
handleUseModel(
757-
variant.model_id
758-
)
759-
}
760-
>
761-
{t('hub:use')}
762-
</Button>
763-
</div>
764-
)
688+
<ModelDownloadAction
689+
variant={variant}
690+
model={
691+
filteredModels[virtualItem.index]
765692
}
766-
767-
return (
768-
<div
769-
className="size-6 cursor-pointer flex items-center justify-center rounded hover:bg-main-view-fg/10 transition-all duration-200 ease-in-out"
770-
title={t('hub:downloadModel')}
771-
onClick={() => {
772-
addLocalDownloadingModel(
773-
variant.model_id
774-
)
775-
serviceHub
776-
.models()
777-
.pullModelWithMetadata(
778-
variant.model_id,
779-
variant.path,
780-
781-
(
782-
filteredModels[
783-
virtualItem.index
784-
].mmproj_models?.find(
785-
(e) =>
786-
e.model_id.toLowerCase() ===
787-
'mmproj-f16'
788-
) ||
789-
filteredModels[
790-
virtualItem.index
791-
].mmproj_models?.[0]
792-
)?.path,
793-
huggingfaceToken
794-
)
795-
}}
796-
>
797-
<IconDownload
798-
size={16}
799-
className="text-main-view-fg/80"
800-
/>
801-
</div>
802-
)
803-
})()}
693+
/>
804694
</div>
805695
}
806696
/>

web-app/src/services/providers/tauri.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ export class TauriProvidersService extends DefaultProvidersService {
2222
try {
2323
const builtinProviders = predefinedProviders.map((provider) => {
2424
let models = provider.models as Model[]
25+
if (!models.length) return undefined
2526
if (Object.keys(providerModels).includes(provider.provider)) {
2627
const builtInModels = providerModels[
2728
provider.provider as unknown as keyof typeof providerModels
@@ -43,7 +44,7 @@ export class TauriProvidersService extends DefaultProvidersService {
4344
...provider,
4445
models,
4546
}
46-
})
47+
}).filter(Boolean)
4748

4849
const runtimeProviders: ModelProvider[] = []
4950
for (const [providerName, value] of EngineManager.instance().engines) {

0 commit comments

Comments
 (0)