From af48f346f10e29f9002cbd54f237c0f41fd57c41 Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Mon, 20 Jan 2025 22:39:00 -0800 Subject: [PATCH] enh: reintroduce model management to models settings --- .../Connections/ManageOllamaModal.svelte | 1012 +--------------- .../components/admin/Settings/Models.svelte | 16 +- .../Models/ConfigureModelsModal.svelte | 2 +- .../Models/Manage/ManageMultipleOllama.svelte | 26 + .../Models/Manage/ManageOllama.svelte | 1018 +++++++++++++++++ .../Settings/Models/ManageModelsModal.svelte | 110 ++ 6 files changed, 1167 insertions(+), 1017 deletions(-) create mode 100644 src/lib/components/admin/Settings/Models/Manage/ManageMultipleOllama.svelte create mode 100644 src/lib/components/admin/Settings/Models/Manage/ManageOllama.svelte create mode 100644 src/lib/components/admin/Settings/Models/ManageModelsModal.svelte diff --git a/src/lib/components/admin/Settings/Connections/ManageOllamaModal.svelte b/src/lib/components/admin/Settings/Connections/ManageOllamaModal.svelte index 220214ed1..543db060e 100644 --- a/src/lib/components/admin/Settings/Connections/ManageOllamaModal.svelte +++ b/src/lib/components/admin/Settings/Connections/ManageOllamaModal.svelte @@ -3,527 +3,13 @@ import { getContext, onMount } from 'svelte'; const i18n = getContext('i18n'); - import { WEBUI_NAME, models, MODEL_DOWNLOAD_POOL, user, config } from '$lib/stores'; - import { splitStream } from '$lib/utils'; - - import { - createModel, - deleteModel, - downloadModel, - getOllamaUrls, - getOllamaVersion, - pullModel, - uploadModel, - getOllamaConfig, - getOllamaModels - } from '$lib/apis/ollama'; - import { getModels } from '$lib/apis'; - import Modal from '$lib/components/common/Modal.svelte'; - import Tooltip from '$lib/components/common/Tooltip.svelte'; - import ModelDeleteConfirmDialog from '$lib/components/common/ConfirmDialog.svelte'; - import Spinner from '$lib/components/common/Spinner.svelte'; + import ManageOllama from '../Models/Manage/ManageOllama.svelte'; export let show = false; - - let modelUploadInputElement: HTMLInputElement; - let showModelDeleteConfirm = false; - - let loading = true; - - // Models export let urlIdx: number | null = null; - - let ollamaModels = []; - - let updateModelId = null; - let updateProgress = null; - let showExperimentalOllama = false; - - const MAX_PARALLEL_DOWNLOADS = 3; - - let modelTransferring = false; - let modelTag = ''; - - let createModelLoading = false; - let createModelTag = ''; - let createModelContent = ''; - let createModelDigest = ''; - let createModelPullProgress = null; - - let digest = ''; - let pullProgress = null; - - let modelUploadMode = 'file'; - let modelInputFile: File[] | null = null; - let modelFileUrl = ''; - let modelFileContent = `TEMPLATE """{{ .System }}\nUSER: {{ .Prompt }}\nASSISTANT: """\nPARAMETER num_ctx 4096\nPARAMETER stop ""\nPARAMETER stop "USER:"\nPARAMETER stop "ASSISTANT:"`; - let modelFileDigest = ''; - - let uploadProgress = null; - let uploadMessage = ''; - - let deleteModelTag = ''; - - const updateModelsHandler = async () => { - for (const model of ollamaModels) { - console.log(model); - - updateModelId = model.id; - const [res, controller] = await pullModel(localStorage.token, model.id, urlIdx).catch( - (error) => { - toast.error(error); - return null; - } - ); - - if (res) { - const reader = res.body - .pipeThrough(new TextDecoderStream()) - .pipeThrough(splitStream('\n')) - .getReader(); - - while (true) { - try { - const { value, done } = await reader.read(); - if (done) break; - - let lines = value.split('\n'); - - for (const line of lines) { - if (line !== '') { - let data = JSON.parse(line); - - console.log(data); - if (data.error) { - throw data.error; - } - if (data.detail) { - throw data.detail; - } - if (data.status) { - if (data.digest) { - updateProgress = 0; - if (data.completed) { - updateProgress = Math.round((data.completed / data.total) * 1000) / 10; - } else { - updateProgress = 100; - } - } else { - toast.success(data.status); - } - } - } - } - } catch (error) { - console.log(error); - } - } - } - } - - updateModelId = null; - updateProgress = null; - }; - - const pullModelHandler = async () => { - const sanitizedModelTag = modelTag.trim().replace(/^ollama\s+(run|pull)\s+/, ''); - console.log($MODEL_DOWNLOAD_POOL); - if ($MODEL_DOWNLOAD_POOL[sanitizedModelTag]) { - toast.error( - $i18n.t(`Model '{{modelTag}}' is already in queue for downloading.`, { - modelTag: sanitizedModelTag - }) - ); - return; - } - if (Object.keys($MODEL_DOWNLOAD_POOL).length === MAX_PARALLEL_DOWNLOADS) { - toast.error( - $i18n.t('Maximum of 3 models can be downloaded simultaneously. Please try again later.') - ); - return; - } - - const [res, controller] = await pullModel(localStorage.token, sanitizedModelTag, urlIdx).catch( - (error) => { - toast.error(error); - return null; - } - ); - - if (res) { - const reader = res.body - .pipeThrough(new TextDecoderStream()) - .pipeThrough(splitStream('\n')) - .getReader(); - - MODEL_DOWNLOAD_POOL.set({ - ...$MODEL_DOWNLOAD_POOL, - [sanitizedModelTag]: { - ...$MODEL_DOWNLOAD_POOL[sanitizedModelTag], - abortController: controller, - reader, - done: false - } - }); - - while (true) { - try { - const { value, done } = await reader.read(); - if (done) break; - - let lines = value.split('\n'); - - for (const line of lines) { - if (line !== '') { - let data = JSON.parse(line); - console.log(data); - if (data.error) { - throw data.error; - } - if (data.detail) { - throw data.detail; - } - - if (data.status) { - if (data.digest) { - let downloadProgress = 0; - if (data.completed) { - downloadProgress = Math.round((data.completed / data.total) * 1000) / 10; - } else { - downloadProgress = 100; - } - - MODEL_DOWNLOAD_POOL.set({ - ...$MODEL_DOWNLOAD_POOL, - [sanitizedModelTag]: { - ...$MODEL_DOWNLOAD_POOL[sanitizedModelTag], - pullProgress: downloadProgress, - digest: data.digest - } - }); - } else { - toast.success(data.status); - - MODEL_DOWNLOAD_POOL.set({ - ...$MODEL_DOWNLOAD_POOL, - [sanitizedModelTag]: { - ...$MODEL_DOWNLOAD_POOL[sanitizedModelTag], - done: data.status === 'success' - } - }); - } - } - } - } - } catch (error) { - console.log(error); - if (typeof error !== 'string') { - error = error.message; - } - - toast.error(error); - // opts.callback({ success: false, error, modelName: opts.modelName }); - } - } - - console.log($MODEL_DOWNLOAD_POOL[sanitizedModelTag]); - - if ($MODEL_DOWNLOAD_POOL[sanitizedModelTag].done) { - toast.success( - $i18n.t(`Model '{{modelName}}' has been successfully downloaded.`, { - modelName: sanitizedModelTag - }) - ); - - models.set(await getModels(localStorage.token)); - } else { - toast.error($i18n.t('Download canceled')); - } - - delete $MODEL_DOWNLOAD_POOL[sanitizedModelTag]; - - MODEL_DOWNLOAD_POOL.set({ - ...$MODEL_DOWNLOAD_POOL - }); - } - - modelTag = ''; - modelTransferring = false; - }; - - const uploadModelHandler = async () => { - modelTransferring = true; - - let uploaded = false; - let fileResponse = null; - let name = ''; - - if (modelUploadMode === 'file') { - const file = modelInputFile ? modelInputFile[0] : null; - - if (file) { - uploadMessage = 'Uploading...'; - - fileResponse = await uploadModel(localStorage.token, file, urlIdx).catch((error) => { - toast.error(error); - return null; - }); - } - } else { - uploadProgress = 0; - fileResponse = await downloadModel(localStorage.token, modelFileUrl, urlIdx).catch( - (error) => { - toast.error(error); - return null; - } - ); - } - - if (fileResponse && fileResponse.ok) { - const reader = fileResponse.body - .pipeThrough(new TextDecoderStream()) - .pipeThrough(splitStream('\n')) - .getReader(); - - while (true) { - const { value, done } = await reader.read(); - if (done) break; - - try { - let lines = value.split('\n'); - - for (const line of lines) { - if (line !== '') { - let data = JSON.parse(line.replace(/^data: /, '')); - - if (data.progress) { - if (uploadMessage) { - uploadMessage = ''; - } - uploadProgress = data.progress; - } - - if (data.error) { - throw data.error; - } - - if (data.done) { - modelFileDigest = data.blob; - name = data.name; - uploaded = true; - } - } - } - } catch (error) { - console.log(error); - } - } - } else { - const error = await fileResponse?.json(); - toast.error(error?.detail ?? error); - } - - if (uploaded) { - const res = await createModel( - localStorage.token, - `${name}:latest`, - `FROM @${modelFileDigest}\n${modelFileContent}` - ); - - if (res && res.ok) { - const reader = res.body - .pipeThrough(new TextDecoderStream()) - .pipeThrough(splitStream('\n')) - .getReader(); - - while (true) { - const { value, done } = await reader.read(); - if (done) break; - - try { - let lines = value.split('\n'); - - for (const line of lines) { - if (line !== '') { - console.log(line); - let data = JSON.parse(line); - console.log(data); - - if (data.error) { - throw data.error; - } - if (data.detail) { - throw data.detail; - } - - if (data.status) { - if ( - !data.digest && - !data.status.includes('writing') && - !data.status.includes('sha256') - ) { - toast.success(data.status); - } else { - if (data.digest) { - digest = data.digest; - - if (data.completed) { - pullProgress = Math.round((data.completed / data.total) * 1000) / 10; - } else { - pullProgress = 100; - } - } - } - } - } - } - } catch (error) { - console.log(error); - toast.error(error); - } - } - } - } - - modelFileUrl = ''; - - if (modelUploadInputElement) { - modelUploadInputElement.value = ''; - } - modelInputFile = null; - modelTransferring = false; - uploadProgress = null; - - models.set(await getModels(localStorage.token)); - }; - - const deleteModelHandler = async () => { - const res = await deleteModel(localStorage.token, deleteModelTag, urlIdx).catch((error) => { - toast.error(error); - }); - - if (res) { - toast.success($i18n.t(`Deleted {{deleteModelTag}}`, { deleteModelTag })); - } - - deleteModelTag = ''; - models.set(await getModels(localStorage.token)); - }; - - const cancelModelPullHandler = async (model: string) => { - const { reader, abortController } = $MODEL_DOWNLOAD_POOL[model]; - if (abortController) { - abortController.abort(); - } - if (reader) { - await reader.cancel(); - delete $MODEL_DOWNLOAD_POOL[model]; - MODEL_DOWNLOAD_POOL.set({ - ...$MODEL_DOWNLOAD_POOL - }); - await deleteModel(localStorage.token, model); - toast.success(`${model} download has been canceled`); - } - }; - - const createModelHandler = async () => { - createModelLoading = true; - const res = await createModel( - localStorage.token, - createModelTag, - createModelContent, - urlIdx - ).catch((error) => { - toast.error(error); - return null; - }); - - if (res && res.ok) { - const reader = res.body - .pipeThrough(new TextDecoderStream()) - .pipeThrough(splitStream('\n')) - .getReader(); - - while (true) { - const { value, done } = await reader.read(); - if (done) break; - - try { - let lines = value.split('\n'); - - for (const line of lines) { - if (line !== '') { - console.log(line); - let data = JSON.parse(line); - console.log(data); - - if (data.error) { - throw data.error; - } - if (data.detail) { - throw data.detail; - } - - if (data.status) { - if ( - !data.digest && - !data.status.includes('writing') && - !data.status.includes('sha256') - ) { - toast.success(data.status); - } else { - if (data.digest) { - createModelDigest = data.digest; - - if (data.completed) { - createModelPullProgress = - Math.round((data.completed / data.total) * 1000) / 10; - } else { - createModelPullProgress = 100; - } - } - } - } - } - } - } catch (error) { - console.log(error); - toast.error(error); - } - } - } - - models.set(await getModels(localStorage.token)); - - createModelLoading = false; - - createModelTag = ''; - createModelContent = ''; - createModelDigest = ''; - createModelPullProgress = null; - }; - - const init = async () => { - loading = true; - ollamaModels = await getOllamaModels(localStorage.token, urlIdx); - - console.log(ollamaModels); - loading = false; - }; - - $: if (show) { - init(); - } - { - deleteModelHandler(); - }} -/> -
@@ -533,31 +19,6 @@
{$i18n.t('Manage Ollama')}
- -
- - - -
-
- -
- {$i18n.t('To access the available model names for downloading,')} - {$i18n.t('click here.')} -
- - {#if Object.keys($MODEL_DOWNLOAD_POOL).length > 0} - {#each Object.keys($MODEL_DOWNLOAD_POOL) as model} - {#if 'pullProgress' in $MODEL_DOWNLOAD_POOL[model]} -
-
{model}
-
-
-
-
- {$MODEL_DOWNLOAD_POOL[model].pullProgress ?? 0}% -
-
- - - - -
- {#if 'digest' in $MODEL_DOWNLOAD_POOL[model]} -
- {$MODEL_DOWNLOAD_POOL[model].digest} -
- {/if} -
-
- {/if} - {/each} - {/if} - - -
-
{$i18n.t('Delete a model')}
-
-
- -
- -
-
- -
-
{$i18n.t('Create a model')}
-
-
- - -