@@ -10,6 +10,7 @@ import { RAGService } from "@/lib/rag";
10
10
import { Trash2 , Upload } from "lucide-react" ;
11
11
import { embeddingService } from "@/lib/embeddings" ;
12
12
import { cn } from "@/lib/utils" ;
13
+ import { toast } from "@/hooks/use-toast" ;
13
14
14
15
interface EmbeddingProgress {
15
16
status : 'idle' | 'loading' | 'chunking' | 'embedding' | 'complete' | 'error' ;
@@ -19,9 +20,27 @@ interface EmbeddingProgress {
19
20
}
20
21
21
22
const EMBEDDING_MODELS = [
22
- { id : 'Xenova/bge-base-en-v1.5' , name : 'BGE Base' , size : '110MB' } ,
23
- { id : 'Xenova/bge-small-en-v1.5' , name : 'BGE Small' , size : '50MB' } ,
24
- { id : 'Xenova/bge-large-en-v1.5' , name : 'BGE Large' , size : '330MB' }
23
+ // HuggingFace embedding models
24
+ { id : 'Xenova/bge-base-en-v1.5' , name : 'BGE Base English' , size : '436MB' , type : 'local' , device : 'auto' , dtype : 'fp32' } ,
25
+ { id : 'Xenova/bge-small-en-v1.5' , name : 'BGE Small English' , size : '133MB' , type : 'local' , device : 'auto' , dtype : 'fp32' } ,
26
+ { id : 'Xenova/bge-large-en-v1.5' , name : 'BGE Large English' , size : '1.34GB' , type : 'local' , device : 'auto' , dtype : 'fp32' } ,
27
+ { id : 'Xenova/nomic-embed-text-v1' , name : 'Nomic Embed Text v1' , size : '550MB' , type : 'local' , device : 'auto' , dtype : 'fp32' } ,
28
+ { id : 'Xenova/gte-small' , name : 'GTE Small' , size : '133MB' , type : 'local' , device : 'auto' , dtype : 'fp32' } ,
29
+ { id : 'Xenova/gte-large' , name : 'GTE Large' , size : '1.34GB' , type : 'local' , device : 'auto' , dtype : 'fp32' } ,
30
+ { id : 'Xenova/GIST-small-Embedding-v0' , name : 'GIST Small' , size : '133MB' , type : 'local' , device : 'auto' , dtype : 'fp32' } ,
31
+ { id : 'Xenova/ernie-3.0-nano-zh' , name : 'ERNIE 3.0 Nano Chinese' , size : '72MB' , type : 'local' , device : 'auto' , dtype : 'fp32' } ,
32
+ { id : 'Xenova/ernie-3.0-micro-zh' , name : 'ERNIE 3.0 Micro Chinese' , size : '94MB' , type : 'local' , device : 'auto' , dtype : 'fp32' } ,
33
+ { id : 'Xenova/ernie-3.0-mini-zh' , name : 'ERNIE 3.0 Mini Chinese' , size : '107MB' , type : 'local' , device : 'auto' , dtype : 'fp32' } ,
34
+ { id : 'Xenova/all-roberta-large-v1' , name : 'Roberta Large' , size : '1.42GB' , type : 'local' , device : 'auto' , dtype : 'fp32' } ,
35
+ { id : 'Xenova/jina-embeddings-v2-base-en' , name : 'Jina Embeddings v2 Base English' , size : '547MB' , type : 'local' , device : 'auto' , dtype : 'fp32' } ,
36
+ { id : 'Xenova/jina-embeddings-v2-small-en' , name : 'Jina Embeddings v2 Small English' , size : '130MB' , type : 'local' , device : 'auto' , dtype : 'fp32' } ,
37
+ { id : 'Xenova/jina-embeddings-v2-base-zh' , name : 'Jina Embeddings v2 Base Chinese' , size : '641MB' , type : 'local' , device : 'auto' , dtype : 'fp32' } ,
38
+
39
+ // { id: 'jinaai/jina-embeddings-v3', name: 'Jina Embeddings v3', size: '2.29GB', type: 'local', device: 'auto', dtype: 'fp16' },
40
+
41
+ // OpenAI models (requires API key)
42
+ { id : 'text-embedding-3-small' , name : 'OpenAI Embedding 3 Small' , size : '' , type : 'api' } ,
43
+ { id : 'text-embedding-3-large' , name : 'OpenAI Embedding 3 Large' , size : '' , type : 'api' } ,
25
44
] ;
26
45
27
46
export function RAGManager ( ) {
@@ -109,9 +128,20 @@ export function RAGManager() {
109
128
}
110
129
}
111
130
} ;
131
+ const isApiModel = ( modelId : string ) => modelId . startsWith ( 'text-embedding-3' ) ;
112
132
113
133
const handleModelChange = async ( modelId : string ) => {
114
134
try {
135
+ // Check for API key if needed
136
+ if ( isApiModel ( modelId ) && ! settings . openai . apiKey ) {
137
+ toast ( {
138
+ title : "API Key Required" ,
139
+ description : "Please configure your OpenAI API key in settings first." ,
140
+ variant : "destructive"
141
+ } ) ;
142
+ return ;
143
+ }
144
+
115
145
// Update UI immediately
116
146
useStore . setState ( {
117
147
settings : {
@@ -252,6 +282,23 @@ export function RAGManager() {
252
282
}
253
283
} ;
254
284
285
+ const handleUrlChange = ( e : React . ChangeEvent < HTMLInputElement > ) => {
286
+ let url = e . target . value ;
287
+
288
+ // If user is typing and hasn't included a protocol, don't modify input
289
+ if ( ! url . includes ( '://' ) ) {
290
+ setUrlInput ( url ) ;
291
+ return ;
292
+ }
293
+
294
+ // When pasting or finishing input, ensure https:// is present
295
+ if ( ! url . match ( / ^ h t t p s ? : \/ \/ / ) ) {
296
+ url = 'https://' + url ;
297
+ }
298
+
299
+ setUrlInput ( url ) ;
300
+ } ;
301
+
255
302
return (
256
303
< div className = "space-y-6" >
257
304
< p className = "text-sm text-muted-foreground" >
@@ -320,7 +367,7 @@ export function RAGManager() {
320
367
< SelectContent >
321
368
{ EMBEDDING_MODELS . map ( model => (
322
369
< SelectItem key = { model . id } value = { model . id } >
323
- { model . name } ( { model . size } )
370
+ { model . name } { model . type === 'local' && `( $ {model . size } )` } { model . type === 'api' && '(Requires API Key)' }
324
371
</ SelectItem >
325
372
) ) }
326
373
</ SelectContent >
@@ -348,13 +395,18 @@ export function RAGManager() {
348
395
< label className = "text-sm font-medium" > Documents & Websites </ label >
349
396
< div className = "mt-2" >
350
397
< div className = "flex gap-2" >
351
- < Input
352
- type = "url"
353
- placeholder = "Enter website URL"
354
- value = { urlInput }
355
- onChange = { ( e ) => setUrlInput ( e . target . value ) }
356
- disabled = { isUploading }
357
- />
398
+ < Input
399
+ type = "url"
400
+ placeholder = "Enter website URL"
401
+ value = { urlInput }
402
+ onChange = { handleUrlChange }
403
+ onBlur = { ( ) => {
404
+ if ( urlInput && ! urlInput . match ( / ^ h t t p s ? : \/ \/ / ) ) {
405
+ setUrlInput ( 'https://' + urlInput ) ;
406
+ }
407
+ } }
408
+ disabled = { isUploading }
409
+ />
358
410
< Button
359
411
onClick = { handleAddWebsite }
360
412
disabled = { isUploading || ! urlInput }
@@ -363,8 +415,6 @@ export function RAGManager() {
363
415
</ Button >
364
416
</ div >
365
417
366
-
367
-
368
418
< Input
369
419
type = "file"
370
420
ref = { inputRef }
@@ -379,18 +429,14 @@ export function RAGManager() {
379
429
{ uploadingDocs . map ( docId => (
380
430
< div key = { docId } className = "flex items-center justify-between p-2 border rounded" >
381
431
< div className = "flex-1" >
382
- < p className = "text-sm" > Uploading ...</ p >
432
+ < p className = "text-sm" > Embedding ...</ p >
383
433
{ progress [ docId ] && (
384
434
< div className = "mt-2" >
385
435
< Progress
386
436
value = {
387
- progress [ docId ] . status === 'embedding'
388
- ? ( progress [ docId ] . currentChunk / progress [ docId ] . totalChunks ) * 100
389
- : progress [ docId ] . status === 'loading'
390
- ? 20
391
- : progress [ docId ] . status === 'chunking'
392
- ? 40
393
- : 0
437
+
438
+ ( progress [ docId ] . currentChunk / progress [ docId ] . totalChunks ) * 100
439
+
394
440
}
395
441
/>
396
442
< p className = "text-xs text-muted-foreground mt-1" >
0 commit comments