@@ -99,10 +99,9 @@ export class ModelsCliUsecases {
9999 }
100100
101101 async pullModel ( modelId : string ) {
102- if ( modelId . includes ( '/' ) ) {
102+ if ( modelId . includes ( '/' ) || modelId . includes ( ':' ) ) {
103103 await this . pullHuggingFaceModel ( modelId ) ;
104104 }
105-
106105 const bar = new SingleBar ( { } , Presets . shades_classic ) ;
107106 bar . start ( 100 , 0 ) ;
108107 const callback = ( progress : number ) => {
@@ -112,20 +111,35 @@ export class ModelsCliUsecases {
112111 }
113112
114113 private async pullHuggingFaceModel ( modelId : string ) {
115- const data = await this . fetchHuggingFaceRepoData ( modelId ) ;
116- const { quantization } = await this . inquirerService . inquirer . prompt ( {
117- type : 'list' ,
118- name : 'quantization' ,
119- message : 'Select quantization' ,
120- choices : data . siblings
121- . map ( ( e ) => e . quantization )
122- . filter ( ( e ) => e != null ) ,
123- } ) ;
124-
125- const sibling = data . siblings
126- . filter ( ( e ) => ! ! e . quantization )
127- . find ( ( e : any ) => e . quantization === quantization ) ;
114+ let data : HuggingFaceRepoData ;
115+ if ( modelId . includes ( '/' ) )
116+ data = await this . fetchHuggingFaceRepoData ( modelId ) ;
117+ else data = await this . fetchJanRepoData ( modelId ) ;
118+
119+ let sibling ;
120+
121+ const listChoices = data . siblings
122+ . filter ( ( e ) => e . quantization != null )
123+ . map ( ( e ) => {
124+ return {
125+ name : e . quantization ,
126+ value : e . quantization ,
127+ } ;
128+ } ) ;
128129
130+ if ( listChoices . length > 1 ) {
131+ const { quantization } = await this . inquirerService . inquirer . prompt ( {
132+ type : 'list' ,
133+ name : 'quantization' ,
134+ message : 'Select quantization' ,
135+ choices : listChoices ,
136+ } ) ;
137+ sibling = data . siblings
138+ . filter ( ( e ) => ! ! e . quantization )
139+ . find ( ( e : any ) => e . quantization === quantization ) ;
140+ } else {
141+ sibling = data . siblings . find ( ( e ) => e . rfilename . includes ( '.gguf' ) ) ;
142+ }
129143 if ( ! sibling ) throw 'No expected quantization found' ;
130144
131145 let stopWord = '' ;
@@ -141,9 +155,7 @@ export class ModelsCliUsecases {
141155
142156 // @ts -expect-error "tokenizer.ggml.tokens"
143157 stopWord = metadata [ 'tokenizer.ggml.tokens' ] [ index ] ?? '' ;
144- } catch ( err ) {
145- console . log ( 'Failed to get stop word: ' , err ) ;
146- }
158+ } catch ( err ) { }
147159
148160 const stopWords : string [ ] = [ ] ;
149161 if ( stopWord . length > 0 ) {
@@ -163,6 +175,7 @@ export class ModelsCliUsecases {
163175 description : '' ,
164176 settings : {
165177 prompt_template : promptTemplate ,
178+ llama_model_path : sibling . rfilename ,
166179 } ,
167180 parameters : {
168181 stop : stopWords ,
@@ -209,6 +222,59 @@ export class ModelsCliUsecases {
209222 }
210223 }
211224
225+ private async fetchJanRepoData ( modelId : string ) {
226+ const repo = modelId . split ( ':' ) [ 0 ] ;
227+ const tree = modelId . split ( ':' ) [ 1 ] ;
228+ const url = this . toHuggingFaceUrl ( `janhq/${ repo } ` , tree ) ;
229+ const res = await fetch ( url ) ;
230+ const response :
231+ | {
232+ path : string ;
233+ size : number ;
234+ } [ ]
235+ | { error : string } = await res . json ( ) ;
236+
237+ if ( 'error' in response && response . error != null ) {
238+ throw new Error ( response . error ) ;
239+ }
240+
241+ const data : HuggingFaceRepoData = {
242+ siblings : Array . isArray ( response )
243+ ? response . map ( ( e ) => {
244+ return {
245+ rfilename : e . path ,
246+ downloadUrl : `https://huggingface.co/janhq/${ repo } /resolve/${ tree } /${ e . path } ` ,
247+ fileSize : e . size ?? 0 ,
248+ } ;
249+ } )
250+ : [ ] ,
251+ tags : [ 'gguf' ] ,
252+ id : modelId ,
253+ modelId : modelId ,
254+ author : 'janhq' ,
255+ sha : '' ,
256+ downloads : 0 ,
257+ lastModified : '' ,
258+ private : false ,
259+ disabled : false ,
260+ gated : false ,
261+ pipeline_tag : 'text-generation' ,
262+ cardData : { } ,
263+ createdAt : '' ,
264+ } ;
265+
266+ AllQuantizations . forEach ( ( quantization ) => {
267+ data . siblings . forEach ( ( sibling : any ) => {
268+ if ( ! sibling . quantization && sibling . rfilename . includes ( quantization ) ) {
269+ sibling . quantization = quantization ;
270+ }
271+ } ) ;
272+ } ) ;
273+
274+ data . modelUrl = url ;
275+ return data ;
276+ }
277+
212278 private async fetchHuggingFaceRepoData ( repoId : string ) {
213279 const sanitizedUrl = this . toHuggingFaceUrl ( repoId ) ;
214280
@@ -245,24 +311,7 @@ export class ModelsCliUsecases {
245311 return data ;
246312 }
247313
248- private toHuggingFaceUrl ( repoId : string ) : string {
249- try {
250- const url = new URL ( `https://huggingface.co/${ repoId } ` ) ;
251- if ( url . host !== 'huggingface.co' ) {
252- throw `Invalid Hugging Face repo URL: ${ repoId } ` ;
253- }
254-
255- const paths = url . pathname . split ( '/' ) . filter ( ( e ) => e . trim ( ) . length > 0 ) ;
256- if ( paths . length < 2 ) {
257- throw `Invalid Hugging Face repo URL: ${ repoId } ` ;
258- }
259-
260- return `${ url . origin } /api/models/${ paths [ 0 ] } /${ paths [ 1 ] } ` ;
261- } catch ( err ) {
262- if ( repoId . startsWith ( 'https' ) ) {
263- throw new Error ( `Cannot parse url: ${ repoId } ` ) ;
264- }
265- throw err ;
266- }
314+ private toHuggingFaceUrl ( repoId : string , tree ?: string ) : string {
315+ return `https://huggingface.co/api/models/${ repoId } ${ tree ? `/tree/${ tree } ` : '' } ` ;
267316 }
268317}
0 commit comments