From 232401a042de697d4833cb5b25246e8a876700f9 Mon Sep 17 00:00:00 2001 From: "Timothy J. Baek" Date: Sun, 24 Dec 2023 00:27:04 -0800 Subject: [PATCH] feat: gguf file upload status --- backend/apps/web/routers/utils.py | 47 +-- backend/requirements.txt | 1 + src/lib/components/chat/SettingsModal.svelte | 319 +++++++++++++------ 3 files changed, 256 insertions(+), 111 deletions(-) diff --git a/backend/apps/web/routers/utils.py b/backend/apps/web/routers/utils.py index 4a16b5bb5..d8b876c9e 100644 --- a/backend/apps/web/routers/utils.py +++ b/backend/apps/web/routers/utils.py @@ -26,17 +26,20 @@ from urllib.parse import urlparse def parse_huggingface_url(hf_url): - # Parse the URL - parsed_url = urlparse(hf_url) + try: + # Parse the URL + parsed_url = urlparse(hf_url) - # Get the path and split it into components - path_components = parsed_url.path.split("/") + # Get the path and split it into components + path_components = parsed_url.path.split("/") - # Extract the desired output - user_repo = "/".join(path_components[1:3]) - model_file = path_components[-1] + # Extract the desired output + user_repo = "/".join(path_components[1:3]) + model_file = path_components[-1] - return [user_repo, model_file] + return model_file + except ValueError: + return None async def download_file_stream(url, file_path, chunk_size=1024 * 1024): @@ -49,7 +52,7 @@ async def download_file_stream(url, file_path, chunk_size=1024 * 1024): headers = {"Range": f"bytes={current_size}-"} if current_size > 0 else {} - timeout = aiohttp.ClientTimeout(total=60) # Set the timeout + timeout = aiohttp.ClientTimeout(total=600) # Set the timeout async with aiohttp.ClientSession(timeout=timeout) as session: async with session.get(url, headers=headers) as response: @@ -62,7 +65,7 @@ async def download_file_stream(url, file_path, chunk_size=1024 * 1024): done = current_size == total_size progress = round((current_size / total_size) * 100, 2) - yield f'data: {{"progress": {progress}, "current": {current_size}, "total": {total_size}}}\n\n' + yield f'data: {{"progress": {progress}, "completed": {current_size}, "total": {total_size}}}\n\n' if done: file.seek(0) @@ -76,6 +79,7 @@ async def download_file_stream(url, file_path, chunk_size=1024 * 1024): res = { "done": done, "blob": f"sha256:{hashed}", + "name": file.name, } os.remove(file_path) @@ -86,16 +90,20 @@ async def download_file_stream(url, file_path, chunk_size=1024 * 1024): @router.get("/download") async def download( - url: str = "https://huggingface.co/TheBloke/stablelm-zephyr-3b-GGUF/resolve/main/stablelm-zephyr-3b.Q2_K.gguf", + url: str, ): - user_repo, model_file = parse_huggingface_url(url) + # url = "https://huggingface.co/TheBloke/stablelm-zephyr-3b-GGUF/resolve/main/stablelm-zephyr-3b.Q2_K.gguf" + model_file = parse_huggingface_url(url) - os.makedirs("./uploads", exist_ok=True) - file_path = os.path.join("./uploads", f"{model_file}") + if model_file: + os.makedirs("./uploads", exist_ok=True) + file_path = os.path.join("./uploads", f"{model_file}") - return StreamingResponse( - download_file_stream(url, file_path), media_type="text/event-stream" - ) + return StreamingResponse( + download_file_stream(url, file_path), media_type="text/event-stream" + ) + else: + return None @router.post("/upload") @@ -118,10 +126,12 @@ async def upload(file: UploadFile = File(...)): f.write(chunk) total += len(chunk) done = total_size == total + progress = round((total / total_size) * 100, 2) res = { + "progress": progress, "total": total_size, - "uploaded": total, + "completed": total, } yield f"data: {json.dumps(res)}\n\n" @@ -138,6 +148,7 @@ async def upload(file: UploadFile = File(...)): res = { "done": done, "blob": f"sha256:{hashed}", + "name": file.filename, } os.remove(file_path) diff --git a/backend/requirements.txt b/backend/requirements.txt index 1c69fffc9..1568f7bbb 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -12,6 +12,7 @@ passlib[bcrypt] uuid requests +aiohttp pymongo bcrypt diff --git a/src/lib/components/chat/SettingsModal.svelte b/src/lib/components/chat/SettingsModal.svelte index a17c955d7..ea8f2cde1 100644 --- a/src/lib/components/chat/SettingsModal.svelte +++ b/src/lib/components/chat/SettingsModal.svelte @@ -50,15 +50,21 @@ }; // Models - let modelTag = ''; - let modelInputFile = ''; - let modelInputFileBlob = ''; - let modelFileContent = `TEMPLATE """{{ .System }}\nUSER: {{ .Prompt }}\nASSSISTANT: """\nPARAMETER num_ctx 4096\nPARAMETER stop ""\nPARAMETER stop "USER:"\nPARAMETER stop "ASSSISTANT:"`; + let modelTransferring = false; - let deleteModelTag = ''; + let modelTag = ''; let digest = ''; let pullProgress = null; + let modelUploadMode = 'file'; + let modelInputFile = ''; + let modelFileUrl = ''; + let modelFileContent = `TEMPLATE """{{ .System }}\nUSER: {{ .Prompt }}\nASSSISTANT: """\nPARAMETER num_ctx 4096\nPARAMETER stop ""\nPARAMETER stop "USER:"\nPARAMETER stop "ASSSISTANT:"`; + let modelFileDigest = ''; + let uploadProgress = null; + + let deleteModelTag = ''; + // Addons let titleAutoGenerate = true; let speechAutoSend = false; @@ -162,6 +168,7 @@ }; const pullModelHandler = async () => { + modelTransferring = true; const res = await fetch(`${API_BASE_URL}/pull`, { method: 'POST', headers: { @@ -227,6 +234,8 @@ } modelTag = ''; + modelTransferring = false; + models.set(await getModels()); }; @@ -266,26 +275,42 @@ }; const uploadModelHandler = async () => { - const file = modelInputFile[0]; - const formData = new FormData(); - formData.append('file', file); - + modelTransferring = true; let uploaded = false; + let fileResponse = null; + let name = ''; - const res = await fetch(`${WEBUI_API_BASE_URL}/utils/upload`, { - method: 'POST', - headers: { - ...($settings.authHeader && { Authorization: $settings.authHeader }), - ...($user && { Authorization: `Bearer ${localStorage.token}` }) - }, - body: formData - }).catch((error) => { - console.log(error); - return null; - }); + if (modelUploadMode === 'file') { + const file = modelInputFile[0]; + const formData = new FormData(); + formData.append('file', file); - if (res && res.ok) { - const reader = res.body + fileResponse = await fetch(`${WEBUI_API_BASE_URL}/utils/upload`, { + method: 'POST', + headers: { + ...($settings.authHeader && { Authorization: $settings.authHeader }), + ...($user && { Authorization: `Bearer ${localStorage.token}` }) + }, + body: formData + }).catch((error) => { + console.log(error); + return null; + }); + } else { + fileResponse = await fetch(`${WEBUI_API_BASE_URL}/utils/download?url=${modelFileUrl}`, { + method: 'GET', + headers: { + ...($settings.authHeader && { Authorization: $settings.authHeader }), + ...($user && { Authorization: `Bearer ${localStorage.token}` }) + } + }).catch((error) => { + console.log(error); + return null; + }); + } + + if (fileResponse && fileResponse.ok) { + const reader = fileResponse.body .pipeThrough(new TextDecoderStream()) .pipeThrough(splitStream('\n')) .getReader(); @@ -300,14 +325,18 @@ for (const line of lines) { if (line !== '') { let data = JSON.parse(line.replace(/^data: /, '')); - console.log(data); + + if (data.progress) { + uploadProgress = data.progress; + } if (data.error) { throw data.error; } if (data.done) { - modelInputFileBlob = data.blob; + modelFileDigest = data.blob; + name = data.name; uploaded = true; } } @@ -327,8 +356,8 @@ ...($user && { Authorization: `Bearer ${localStorage.token}` }) }, body: JSON.stringify({ - name: `${file.name}:latest`, - modelfile: `FROM @${modelInputFileBlob}\n${modelFileContent}` + name: `${name}:latest`, + modelfile: `FROM @${modelFileDigest}\n${modelFileContent}` }) }).catch((err) => { console.log(err); @@ -390,7 +419,9 @@ } } - modelTag = ''; + modelFileUrl = ''; + modelInputFile = ''; + modelTransferring = false; models.set(await getModels()); }; @@ -977,24 +1008,53 @@ /> @@ -1025,67 +1085,140 @@
-
-
Upload a GGUF model
-
-
- { - console.log(modelInputFile); - }} - hidden - /> +
{ + uploadModelHandler(); + }} + > +
+
Upload a GGUF model
- + +
+ +
+
+ {#if modelUploadMode === 'file'} +
+ { + console.log(modelInputFile); + }} + accept=".gguf" + required + hidden + /> + + +
+ {:else} +
+ +
+ {/if}
- {#if modelInputFile && modelInputFile.length > 0} + {#if (modelUploadMode === 'file' && modelInputFile && modelInputFile.length > 0) || (modelUploadMode === 'url' && modelFileUrl !== '')} {/if}
- {#if modelInputFile && modelInputFile.length > 0} + {#if (modelUploadMode === 'file' && modelInputFile && modelInputFile.length > 0) || (modelUploadMode === 'url' && modelFileUrl !== '')}
Modelfile Content