Merge pull request #16863 from silentoplayz/ollama-cancel-and-toast-cleanup

feat: improve Ollama model management modal
This commit is contained in:
Tim Jaeryang Baek 2025-10-02 17:41:21 -05:00 committed by GitHub
commit 227139aa33
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -36,6 +36,8 @@
let updateModelId = null; let updateModelId = null;
let updateProgress = null; let updateProgress = null;
let updateModelsControllers = {};
let updateCancelled = false;
let showExperimentalOllama = false; let showExperimentalOllama = false;
const MAX_PARALLEL_DOWNLOADS = 3; const MAX_PARALLEL_DOWNLOADS = 3;
@ -65,17 +67,31 @@
let deleteModelTag = ''; let deleteModelTag = '';
const updateModelsHandler = async () => { const updateModelsHandler = async () => {
updateCancelled = false;
toast.info('Checking for model updates...');
for (const model of ollamaModels) { for (const model of ollamaModels) {
if (updateCancelled) {
break;
}
console.debug(model); console.debug(model);
updateModelId = model.id; updateModelId = model.id;
const [res, controller] = await pullModel(localStorage.token, model.id, urlIdx).catch( const [res, controller] = await pullModel(localStorage.token, model.id, urlIdx).catch(
(error) => { (error) => {
if (error.name !== 'AbortError') {
toast.error(`${error}`); toast.error(`${error}`);
return null; }
return [null, null];
} }
); );
updateModelsControllers = {
...updateModelsControllers,
[model.id]: controller
};
if (res) { if (res) {
const reader = res.body const reader = res.body
.pipeThrough(new TextDecoderStream()) .pipeThrough(new TextDecoderStream())
@ -108,19 +124,28 @@
} else { } else {
updateProgress = 100; updateProgress = 100;
} }
} else {
toast.success(data.status);
} }
} }
} }
} }
} catch (err) { } catch (err) {
if (err.name !== 'AbortError') {
console.error(err); console.error(err);
} }
break;
} }
} }
} }
delete updateModelsControllers[model.id];
updateModelsControllers = { ...updateModelsControllers };
}
if (updateCancelled) {
toast.info('Model update cancelled');
} else {
toast.success('All models are up to date');
}
updateModelId = null; updateModelId = null;
updateProgress = null; updateProgress = null;
}; };
@ -143,10 +168,13 @@
return; return;
} }
modelTransferring = true;
const [res, controller] = await pullModel(localStorage.token, sanitizedModelTag, urlIdx).catch( const [res, controller] = await pullModel(localStorage.token, sanitizedModelTag, urlIdx).catch(
(error) => { (error) => {
if (error.name !== 'AbortError') {
toast.error(`${error}`); toast.error(`${error}`);
return null; }
return [null, null];
} }
); );
@ -202,8 +230,6 @@
} }
}); });
} else { } else {
toast.success(data.status);
MODEL_DOWNLOAD_POOL.set({ MODEL_DOWNLOAD_POOL.set({
...$MODEL_DOWNLOAD_POOL, ...$MODEL_DOWNLOAD_POOL,
[sanitizedModelTag]: { [sanitizedModelTag]: {
@ -216,6 +242,7 @@
} }
} }
} catch (err) { } catch (err) {
if (err.name !== 'AbortError') {
console.error(err); console.error(err);
if (typeof err !== 'string') { if (typeof err !== 'string') {
err = err.message; err = err.message;
@ -223,12 +250,15 @@
toast.error(`${err}`); toast.error(`${err}`);
// opts.callback({ success: false, error, modelName: opts.modelName }); // opts.callback({ success: false, error, modelName: opts.modelName });
} else {
break;
}
} }
} }
console.log($MODEL_DOWNLOAD_POOL[sanitizedModelTag]); console.log($MODEL_DOWNLOAD_POOL[sanitizedModelTag]);
if ($MODEL_DOWNLOAD_POOL[sanitizedModelTag].done) { if ($MODEL_DOWNLOAD_POOL[sanitizedModelTag]?.done) {
toast.success( toast.success(
$i18n.t(`Model '{{modelName}}' has been successfully downloaded.`, { $i18n.t(`Model '{{modelName}}' has been successfully downloaded.`, {
modelName: sanitizedModelTag modelName: sanitizedModelTag
@ -425,6 +455,14 @@
); );
}; };
const cancelUpdateModelHandler = async (model: string) => {
const controller = updateModelsControllers[model];
if (controller) {
controller.abort();
updateCancelled = true;
}
};
const cancelModelPullHandler = async (model: string) => { const cancelModelPullHandler = async (model: string) => {
const { reader, abortController } = $MODEL_DOWNLOAD_POOL[model]; const { reader, abortController } = $MODEL_DOWNLOAD_POOL[model];
if (abortController) { if (abortController) {
@ -605,12 +643,13 @@
bind:value={modelTag} bind:value={modelTag}
/> />
</div> </div>
<Tooltip content={$i18n.t('Pull Model')} placement="top">
<button <button
class="px-2.5 bg-gray-50 hover:bg-gray-200 text-gray-800 dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-gray-100 rounded-lg transition" class="px-2.5 bg-gray-50 hover:bg-gray-200 text-gray-800 dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-gray-100 rounded-lg transition"
on:click={() => { on:click={() => {
pullModelHandler(); pullModelHandler();
}} }}
disabled={modelTransferring} disabled={modelTransferring || modelTag.trim() === ''}
> >
{#if modelTransferring} {#if modelTransferring}
<div class="self-center"> <div class="self-center">
@ -658,6 +697,7 @@
</svg> </svg>
{/if} {/if}
</button> </button>
</Tooltip>
</div> </div>
<div class="mt-2 mb-1 text-xs text-gray-400 dark:text-gray-500"> <div class="mt-2 mb-1 text-xs text-gray-400 dark:text-gray-500">
@ -670,8 +710,35 @@
</div> </div>
{#if updateModelId} {#if updateModelId}
<div class="text-xs"> <div class="text-xs flex justify-between items-center">
Updating "{updateModelId}" {updateProgress ? `(${updateProgress}%)` : ''} <div>Updating "{updateModelId}" {updateProgress ? `(${updateProgress}%)` : ''}</div>
<Tooltip content={$i18n.t('Cancel')}>
<button
class="text-gray-800 dark:text-gray-100"
on:click={() => {
cancelUpdateModelHandler(updateModelId);
}}
>
<svg
class="w-4 h-4 text-gray-800 dark:text-white"
aria-hidden="true"
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
fill="currentColor"
viewBox="0 0 24 24"
>
<path
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M6 18 17.94 6M18 18 6.06 6"
/>
</svg>
</button>
</Tooltip>
</div> </div>
{/if} {/if}
@ -754,11 +821,13 @@
{/each} {/each}
</select> </select>
</div> </div>
<Tooltip content={$i18n.t('Delete Model')} placement="top">
<button <button
class="px-2.5 bg-gray-50 hover:bg-gray-200 text-gray-800 dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-gray-100 rounded-lg transition" class="px-2.5 bg-gray-50 hover:bg-gray-200 text-gray-800 dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-gray-100 rounded-lg transition"
on:click={() => { on:click={() => {
showModelDeleteConfirm = true; showModelDeleteConfirm = true;
}} }}
disabled={deleteModelTag === ''}
> >
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
@ -773,6 +842,7 @@
/> />
</svg> </svg>
</button> </button>
</Tooltip>
</div> </div>
</div> </div>
@ -799,12 +869,15 @@
</div> </div>
<div class="flex self-start"> <div class="flex self-start">
<Tooltip content={$i18n.t('Create Model')} placement="top">
<button <button
class="px-2.5 py-2.5 bg-gray-50 hover:bg-gray-200 text-gray-800 dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-gray-100 rounded-lg transition disabled:cursor-not-allowed" class="px-2.5 py-2.5 bg-gray-50 hover:bg-gray-200 text-gray-800 dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-gray-100 rounded-lg transition disabled:cursor-not-allowed"
on:click={() => { on:click={() => {
createModelHandler(); createModelHandler();
}} }}
disabled={createModelLoading} disabled={
createModelLoading || createModelName.trim() === '' || createModelObject.trim() === ''
}
> >
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
@ -820,6 +893,7 @@
/> />
</svg> </svg>
</button> </button>
</Tooltip>
</div> </div>
</div> </div>
@ -936,6 +1010,7 @@
</div> </div>
{#if (modelUploadMode === 'file' && modelInputFile && modelInputFile.length > 0) || (modelUploadMode === 'url' && modelFileUrl !== '')} {#if (modelUploadMode === 'file' && modelInputFile && modelInputFile.length > 0) || (modelUploadMode === 'url' && modelFileUrl !== '')}
<Tooltip content={$i18n.t('Upload Model')} placement="top">
<button <button
class="px-2.5 bg-gray-50 hover:bg-gray-200 text-gray-800 dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-gray-100 rounded-lg disabled:cursor-not-allowed transition" class="px-2.5 bg-gray-50 hover:bg-gray-200 text-gray-800 dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-gray-100 rounded-lg disabled:cursor-not-allowed transition"
type="submit" type="submit"
@ -987,6 +1062,7 @@
</svg> </svg>
{/if} {/if}
</button> </button>
</Tooltip>
{/if} {/if}
</div> </div>