forked from open-webui/open-webui
		
	feat: parallel model downloads
This commit is contained in:
		
							parent
							
								
									cb93038abf
								
							
						
					
					
						commit
						ea721feea9
					
				
					 3 changed files with 79 additions and 13 deletions
				
			
		
							
								
								
									
										11
									
								
								package-lock.json
									
										
									
										generated
									
									
									
								
							
							
						
						
									
										11
									
								
								package-lock.json
									
										
									
										generated
									
									
									
								
							|  | @ -9,6 +9,7 @@ | ||||||
| 			"version": "0.0.1", | 			"version": "0.0.1", | ||||||
| 			"dependencies": { | 			"dependencies": { | ||||||
| 				"@sveltejs/adapter-node": "^1.3.1", | 				"@sveltejs/adapter-node": "^1.3.1", | ||||||
|  | 				"async": "^3.2.5", | ||||||
| 				"file-saver": "^2.0.5", | 				"file-saver": "^2.0.5", | ||||||
| 				"highlight.js": "^11.9.0", | 				"highlight.js": "^11.9.0", | ||||||
| 				"idb": "^7.1.1", | 				"idb": "^7.1.1", | ||||||
|  | @ -1208,6 +1209,11 @@ | ||||||
| 				"node": ">=8" | 				"node": ">=8" | ||||||
| 			} | 			} | ||||||
| 		}, | 		}, | ||||||
|  | 		"node_modules/async": { | ||||||
|  | 			"version": "3.2.5", | ||||||
|  | 			"resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz", | ||||||
|  | 			"integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==" | ||||||
|  | 		}, | ||||||
| 		"node_modules/autoprefixer": { | 		"node_modules/autoprefixer": { | ||||||
| 			"version": "10.4.16", | 			"version": "10.4.16", | ||||||
| 			"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.16.tgz", | 			"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.16.tgz", | ||||||
|  | @ -4645,6 +4651,11 @@ | ||||||
| 			"integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", | 			"integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", | ||||||
| 			"dev": true | 			"dev": true | ||||||
| 		}, | 		}, | ||||||
|  | 		"async": { | ||||||
|  | 			"version": "3.2.5", | ||||||
|  | 			"resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz", | ||||||
|  | 			"integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==" | ||||||
|  | 		}, | ||||||
| 		"autoprefixer": { | 		"autoprefixer": { | ||||||
| 			"version": "10.4.16", | 			"version": "10.4.16", | ||||||
| 			"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.16.tgz", | 			"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.16.tgz", | ||||||
|  |  | ||||||
|  | @ -39,6 +39,7 @@ | ||||||
| 	"type": "module", | 	"type": "module", | ||||||
| 	"dependencies": { | 	"dependencies": { | ||||||
| 		"@sveltejs/adapter-node": "^1.3.1", | 		"@sveltejs/adapter-node": "^1.3.1", | ||||||
|  | 		"async": "^3.2.5", | ||||||
| 		"file-saver": "^2.0.5", | 		"file-saver": "^2.0.5", | ||||||
| 		"highlight.js": "^11.9.0", | 		"highlight.js": "^11.9.0", | ||||||
| 		"idb": "^7.1.1", | 		"idb": "^7.1.1", | ||||||
|  |  | ||||||
|  | @ -6,6 +6,7 @@ | ||||||
| 	import { onMount } from 'svelte'; | 	import { onMount } from 'svelte'; | ||||||
| 	import { config, models, settings, user, chats } from '$lib/stores'; | 	import { config, models, settings, user, chats } from '$lib/stores'; | ||||||
| 	import { splitStream, getGravatarURL } from '$lib/utils'; | 	import { splitStream, getGravatarURL } from '$lib/utils'; | ||||||
|  | 	import queue from 'async/queue'; | ||||||
| 
 | 
 | ||||||
| 	import { getOllamaVersion } from '$lib/apis/ollama'; | 	import { getOllamaVersion } from '$lib/apis/ollama'; | ||||||
| 	import { createNewChat, deleteAllChats, getAllChats, getChatList } from '$lib/apis/chats'; | 	import { createNewChat, deleteAllChats, getAllChats, getChatList } from '$lib/apis/chats'; | ||||||
|  | @ -38,6 +39,8 @@ | ||||||
| 	let theme = 'dark'; | 	let theme = 'dark'; | ||||||
| 	let notificationEnabled = false; | 	let notificationEnabled = false; | ||||||
| 	let system = ''; | 	let system = ''; | ||||||
|  | 	const modelDownloadQueue = queue((task:{modelName: string}, cb) => pullModelHandlerProcessor({modelName: task.modelName, callback: cb}), 3); | ||||||
|  | 	let modelDownloadStatus: Record<string, any> = {}; | ||||||
| 
 | 
 | ||||||
| 	// Advanced | 	// Advanced | ||||||
| 	let requestFormat = ''; | 	let requestFormat = ''; | ||||||
|  | @ -224,8 +227,9 @@ | ||||||
| 		authEnabled = !authEnabled; | 		authEnabled = !authEnabled; | ||||||
| 	}; | 	}; | ||||||
| 
 | 
 | ||||||
| 	const pullModelHandler = async () => { | 	const pullModelHandlerProcessor = async (opts:{modelName:string, callback: Function}) => { | ||||||
| 		modelTransferring = true; | 		console.log('Pull model name', opts.modelName); | ||||||
|  | 		 | ||||||
| 		const res = await fetch(`${API_BASE_URL}/pull`, { | 		const res = await fetch(`${API_BASE_URL}/pull`, { | ||||||
| 			method: 'POST', | 			method: 'POST', | ||||||
| 			headers: { | 			headers: { | ||||||
|  | @ -234,7 +238,7 @@ | ||||||
| 				...($user && { Authorization: `Bearer ${localStorage.token}` }) | 				...($user && { Authorization: `Bearer ${localStorage.token}` }) | ||||||
| 			}, | 			}, | ||||||
| 			body: JSON.stringify({ | 			body: JSON.stringify({ | ||||||
| 				name: modelTag | 				name: opts.modelName | ||||||
| 			}) | 			}) | ||||||
| 		}); | 		}); | ||||||
| 
 | 
 | ||||||
|  | @ -265,11 +269,9 @@ | ||||||
| 						} | 						} | ||||||
| 						if (data.status) { | 						if (data.status) { | ||||||
| 							if (!data.digest) { | 							if (!data.digest) { | ||||||
| 								toast.success(data.status); |  | ||||||
| 
 |  | ||||||
| 								if (data.status === 'success') { | 								if (data.status === 'success') { | ||||||
| 									const notification = new Notification(`Ollama`, { | 									const notification = new Notification(`Ollama`, { | ||||||
| 										body: `Model '${modelTag}' has been successfully downloaded.`, | 										body: `Model '${opts.modelName}' has been successfully downloaded.`, | ||||||
| 										icon: '/favicon.png' | 										icon: '/favicon.png' | ||||||
| 									}); | 									}); | ||||||
| 								} | 								} | ||||||
|  | @ -280,21 +282,48 @@ | ||||||
| 								} else { | 								} else { | ||||||
| 									pullProgress = 100; | 									pullProgress = 100; | ||||||
| 								} | 								} | ||||||
|  | 								modelDownloadStatus[opts.modelName] = {pullProgress}; | ||||||
| 							} | 							} | ||||||
| 						} | 						} | ||||||
| 					} | 					} | ||||||
| 				} | 				} | ||||||
| 			} catch (error) { | 			} catch (error) { | ||||||
| 				console.log(error); | 				console.error(error); | ||||||
| 				toast.error(error); | 				opts.callback({success:false, error, modelName: opts.modelName}); | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  | 		opts.callback({success: true, modelName: opts.modelName}); | ||||||
|  | 	}; | ||||||
|  | 
 | ||||||
|  | 	const pullModelHandler = async() => { | ||||||
|  | 		if(modelDownloadStatus[modelTag]){ | ||||||
|  | 			toast.error("Model already in queue for downloading."); | ||||||
|  | 			return; | ||||||
|  | 		} | ||||||
|  | 		if(Object.keys(modelDownloadStatus).length === 3){ | ||||||
|  | 			toast.error('Maximum of 3 models can be downloading simultaneously. Please try again later'); | ||||||
|  | 			return; | ||||||
|  | 		} | ||||||
|  | 		modelTransferring = true; | ||||||
|  | 
 | ||||||
|  | 		modelDownloadQueue.push({modelName: modelTag},async (data:{modelName: string; success: boolean; error?: Error}) => { | ||||||
|  | 			const {modelName} = data; | ||||||
|  | 			// Remove the downloaded model | ||||||
|  | 			delete modelDownloadStatus[modelName]; | ||||||
|  | 
 | ||||||
|  | 			if(!data.success){ | ||||||
|  | 				toast.error(`There was some issue in downloading the model ${modelName}`); | ||||||
|  | 				return; | ||||||
|  | 			} | ||||||
|  | 		 | ||||||
|  | 			toast.success(`Model ${modelName} was successfully downloaded`); | ||||||
|  | 			models.set(await getModels()); | ||||||
|  | 		}); | ||||||
| 
 | 
 | ||||||
| 		modelTag = ''; | 		modelTag = ''; | ||||||
| 		modelTransferring = false; | 		modelTransferring = false;	 | ||||||
|  | 	} | ||||||
| 
 | 
 | ||||||
| 		models.set(await getModels()); |  | ||||||
| 	}; |  | ||||||
| 
 | 
 | ||||||
| 	const calculateSHA256 = async (file) => { | 	const calculateSHA256 = async (file) => { | ||||||
| 		console.log(file); | 		console.log(file); | ||||||
|  | @ -1248,7 +1277,7 @@ | ||||||
| 									> | 									> | ||||||
| 								</div> | 								</div> | ||||||
| 
 | 
 | ||||||
| 								{#if pullProgress !== null} | 								<!-- {#if pullProgress !== null} | ||||||
| 									<div class="mt-2"> | 									<div class="mt-2"> | ||||||
| 										<div class=" mb-2 text-xs">Pull Progress</div> | 										<div class=" mb-2 text-xs">Pull Progress</div> | ||||||
| 										<div class="w-full rounded-full dark:bg-gray-800"> | 										<div class="w-full rounded-full dark:bg-gray-800"> | ||||||
|  | @ -1263,8 +1292,33 @@ | ||||||
| 											{digest} | 											{digest} | ||||||
| 										</div> | 										</div> | ||||||
| 									</div> | 									</div> | ||||||
| 								{/if} | 								{/if} --> | ||||||
| 							</div> | 							</div> | ||||||
|  | 							{#if Object.keys(modelDownloadStatus).length > 0} | ||||||
|  | 							<table class="w-full text-sm text-left text-gray-500 dark:text-gray-400"> | ||||||
|  | 								<thead | ||||||
|  | 									class="text-xs text-gray-700 uppercase bg-gray-50 dark:bg-gray-700 dark:text-gray-400" | ||||||
|  | 								> | ||||||
|  | 									<tr> | ||||||
|  | 										<th scope="col" class="px-6 py-3"> Model Name </th> | ||||||
|  | 										<th scope="col" class="px-6 py-3"> Download progress </th> | ||||||
|  | 									</tr> | ||||||
|  | 								</thead> | ||||||
|  | 								<tbody> | ||||||
|  | 									{#each Object.entries(modelDownloadStatus) as [modelName, payload]} | ||||||
|  | 									<tr class="bg-white border-b dark:bg-gray-800 dark:border-gray-700"> | ||||||
|  | 										<td class="px-6 py-4">{modelName}</td> | ||||||
|  | 										<td class="px-6 py-4"><div | ||||||
|  | 											class="dark:bg-gray-600 text-xs font-medium text-blue-100 text-center p-0.5 leading-none rounded-full" | ||||||
|  | 											style="width: {Math.max(15, payload.pullProgress ?? 0)}%" | ||||||
|  | 										> | ||||||
|  | 											{ payload.pullProgress ?? 0}% | ||||||
|  | 										</div></td> | ||||||
|  | 									</tr> | ||||||
|  | 									{/each} | ||||||
|  | 									</tbody> | ||||||
|  | 									</table> | ||||||
|  | 									{/if} | ||||||
| 							<hr class=" dark:border-gray-700" /> | 							<hr class=" dark:border-gray-700" /> | ||||||
| 
 | 
 | ||||||
| 							<div> | 							<div> | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Anuraag Jain
						Anuraag Jain