@@ -4,26 +4,43 @@ import {
44 clearMetadataCache ,
55 downloadModel ,
66 fetchModelMetadata ,
7+ HUGGINGFACE_MIRROR_SETTING_ID ,
78 isModelDownloadable ,
89 isTrustedHuggingFaceUrl ,
910 openGatedRepoPage ,
11+ resolveHuggingFaceUrl ,
1012 toBrowsableUrl
1113} from './missingModelDownload'
1214
13- const { fetchMock, mockIsDesktop, mockSidebarTabStore, mockStartDownload } =
14- vi . hoisted ( ( ) => ( {
15- fetchMock : vi . fn ( ) ,
16- mockIsDesktop : { value : false } ,
17- mockSidebarTabStore : { activeSidebarTabId : null as string | null } ,
18- mockStartDownload : vi . fn ( )
19- } ) )
15+ const {
16+ fetchMock,
17+ mockHuggingFaceMirror,
18+ mockIsDesktop,
19+ mockSidebarTabStore,
20+ mockStartDownload
21+ } = vi . hoisted ( ( ) => ( {
22+ fetchMock : vi . fn ( ) ,
23+ mockHuggingFaceMirror : { value : '' as string | undefined } ,
24+ mockIsDesktop : { value : false } ,
25+ mockSidebarTabStore : { activeSidebarTabId : null as string | null } ,
26+ mockStartDownload : vi . fn ( )
27+ } ) )
2028
2129vi . mock ( '@/platform/distribution/types' , ( ) => ( {
2230 get isDesktop ( ) {
2331 return mockIsDesktop . value
2432 }
2533} ) )
2634
35+ vi . mock ( '@/platform/settings/settingStore' , ( ) => ( {
36+ useSettingStore : ( ) => ( {
37+ get : ( key : string ) =>
38+ key === HUGGINGFACE_MIRROR_SETTING_ID
39+ ? mockHuggingFaceMirror . value
40+ : undefined
41+ } )
42+ } ) )
43+
2744vi . mock ( '@/stores/electronDownloadStore' , ( ) => ( {
2845 useElectronDownloadStore : ( ) => ( {
2946 start : mockStartDownload
@@ -38,6 +55,7 @@ beforeEach(() => {
3855 vi . stubGlobal ( 'fetch' , fetchMock )
3956 clearMetadataCache ( )
4057 delete window . __comfyDesktop2
58+ mockHuggingFaceMirror . value = ''
4159} )
4260
4361describe ( 'fetchModelMetadata' , ( ) => {
@@ -715,3 +733,122 @@ describe('downloadModel', () => {
715733 } )
716734 } )
717735} )
736+
737+ describe ( 'resolveHuggingFaceUrl' , ( ) => {
738+ it ( 'returns the URL unchanged when no mirror is configured' , ( ) => {
739+ mockHuggingFaceMirror . value = ''
740+ expect (
741+ resolveHuggingFaceUrl ( 'https://huggingface.co/org/model/resolve/main/x' )
742+ ) . toBe ( 'https://huggingface.co/org/model/resolve/main/x' )
743+ } )
744+
745+ it ( 'returns the URL unchanged when the mirror is whitespace' , ( ) => {
746+ mockHuggingFaceMirror . value = ' '
747+ expect (
748+ resolveHuggingFaceUrl ( 'https://huggingface.co/org/model/resolve/main/x' )
749+ ) . toBe ( 'https://huggingface.co/org/model/resolve/main/x' )
750+ } )
751+
752+ it ( 'rewrites huggingface.co to the configured mirror' , ( ) => {
753+ mockHuggingFaceMirror . value = 'https://hf-mirror.com'
754+ expect (
755+ resolveHuggingFaceUrl ( 'https://huggingface.co/org/model/resolve/main/x' )
756+ ) . toBe ( 'https://hf-mirror.com/org/model/resolve/main/x' )
757+ } )
758+
759+ it ( 'strips a trailing slash from the mirror' , ( ) => {
760+ mockHuggingFaceMirror . value = 'https://hf-mirror.com/'
761+ expect (
762+ resolveHuggingFaceUrl ( 'https://huggingface.co/org/model/resolve/main/x' )
763+ ) . toBe ( 'https://hf-mirror.com/org/model/resolve/main/x' )
764+ } )
765+
766+ it ( 'trims surrounding whitespace from the mirror' , ( ) => {
767+ mockHuggingFaceMirror . value = ' https://hf-mirror.com '
768+ expect (
769+ resolveHuggingFaceUrl ( 'https://huggingface.co/org/model/resolve/main/x' )
770+ ) . toBe ( 'https://hf-mirror.com/org/model/resolve/main/x' )
771+ } )
772+
773+ it ( 'leaves non-HuggingFace URLs untouched' , ( ) => {
774+ mockHuggingFaceMirror . value = 'https://hf-mirror.com'
775+ expect (
776+ resolveHuggingFaceUrl ( 'https://civitai.com/api/download/models/12345' )
777+ ) . toBe ( 'https://civitai.com/api/download/models/12345' )
778+ } )
779+
780+ it ( 'does not rewrite URLs whose path merely contains huggingface.co' , ( ) => {
781+ mockHuggingFaceMirror . value = 'https://hf-mirror.com'
782+ expect (
783+ resolveHuggingFaceUrl (
784+ 'https://example.com/huggingface.co/org/model/resolve/main/x'
785+ )
786+ ) . toBe ( 'https://example.com/huggingface.co/org/model/resolve/main/x' )
787+ } )
788+
789+ it ( 'routes the Desktop2 download through the mirror' , ( ) => {
790+ mockHuggingFaceMirror . value = 'https://hf-mirror.com'
791+ const desktopDownloadModel = vi
792+ . fn <
793+ ( url : string , filename : string , directory : string ) => Promise < boolean >
794+ > ( )
795+ . mockResolvedValue ( true )
796+ window . __comfyDesktop2 = {
797+ isRemote : ( ) => false ,
798+ downloadModel : desktopDownloadModel
799+ }
800+
801+ downloadModel (
802+ {
803+ name : 'model.safetensors' ,
804+ url : 'https://huggingface.co/org/model/resolve/main/model.safetensors' ,
805+ directory : 'checkpoints'
806+ } ,
807+ { }
808+ )
809+
810+ expect ( desktopDownloadModel ) . toHaveBeenCalledWith (
811+ 'https://hf-mirror.com/org/model/resolve/main/model.safetensors' ,
812+ 'model.safetensors' ,
813+ 'checkpoints'
814+ )
815+ } )
816+
817+ it ( 'routes the Electron download store through the mirror' , ( ) => {
818+ mockHuggingFaceMirror . value = 'https://hf-mirror.com'
819+ mockIsDesktop . value = true
820+
821+ downloadModel (
822+ {
823+ name : 'model.safetensors' ,
824+ url : 'https://huggingface.co/org/model/resolve/main/model.safetensors' ,
825+ directory : 'checkpoints'
826+ } ,
827+ { checkpoints : [ '/models/checkpoints' ] }
828+ )
829+
830+ expect ( mockStartDownload ) . toHaveBeenCalledWith ( {
831+ url : 'https://hf-mirror.com/org/model/resolve/main/model.safetensors' ,
832+ savePath : '/models/checkpoints' ,
833+ filename : 'model.safetensors'
834+ } )
835+ } )
836+
837+ it ( 'routes the file-size HEAD probe through the mirror' , async ( ) => {
838+ mockHuggingFaceMirror . value = 'https://hf-mirror.com'
839+ fetchMock . mockResolvedValueOnce ( {
840+ ok : true ,
841+ headers : new Headers ( { 'content-length' : '42' } )
842+ } )
843+
844+ const metadata = await fetchModelMetadata (
845+ 'https://huggingface.co/org/model/resolve/main/probe.safetensors'
846+ )
847+
848+ expect ( metadata . fileSize ) . toBe ( 42 )
849+ expect ( fetchMock ) . toHaveBeenCalledWith (
850+ 'https://hf-mirror.com/org/model/resolve/main/probe.safetensors' ,
851+ { method : 'HEAD' }
852+ )
853+ } )
854+ } )
0 commit comments