mirror of
https://github.com/ggml-org/llama.cpp.git
synced 2026-09-17 20:31:47 +02:00
ui : share one download confirmation dialog across chips and rows
Extract DialogConfirmDownload for the destructive download actions (cancel an in-flight download, delete a downloaded model), keyed by a DownloadConfirmAction enum, and mount a single instance per surface instead of one per quant chip or download row. The chips and download rows now only signal intent; the discover options panel and the selector list own the dialog. Assisted-by: llama-ui:Qwen3.8-Flash-Next
This commit is contained in:
@@ -0,0 +1,60 @@
|
||||
<script lang="ts">
|
||||
import DialogConfirmation from '$lib/components/app/dialogs/DialogConfirmation.svelte';
|
||||
import { DownloadConfirmAction } from '$lib/enums';
|
||||
import { modelsStore } from '$lib/stores';
|
||||
|
||||
interface Props {
|
||||
/** Action being confirmed; drives the wording. */
|
||||
action: DownloadConfirmAction;
|
||||
/** `<repo>:<tag>` the action targets. */
|
||||
repoWithTag: string;
|
||||
onClose: () => void;
|
||||
/** Overrides the default store removal; defaults to removing the entry. */
|
||||
onConfirm?: (repoWithTag: string) => void;
|
||||
open?: boolean;
|
||||
}
|
||||
|
||||
let { action, onClose, onConfirm, open = true, repoWithTag }: Props = $props();
|
||||
|
||||
// Both actions resolve through the same store removal (cancelDownload drops a
|
||||
// running download's partial files or a cached model's files); only the copy
|
||||
// differs. One component so the discover chips and the selector rows word the
|
||||
// destructive confirmations identically.
|
||||
const COPY = {
|
||||
[DownloadConfirmAction.CANCEL]: {
|
||||
cancelText: 'Keep downloading',
|
||||
confirmText: 'Cancel download',
|
||||
description: (name: string) =>
|
||||
`This stops the download of ${name} and removes the partial files. Pause it instead to keep the progress.`,
|
||||
title: 'Cancel download'
|
||||
},
|
||||
[DownloadConfirmAction.DELETE]: {
|
||||
cancelText: 'Keep model',
|
||||
confirmText: 'Delete',
|
||||
description: (name: string) =>
|
||||
`This permanently removes ${name} from disk. You can download it again later.`,
|
||||
title: 'Delete model'
|
||||
}
|
||||
} as const;
|
||||
|
||||
let copy = $derived(COPY[action]);
|
||||
let displayName = $derived(modelsStore.toDisplayName(repoWithTag));
|
||||
|
||||
function confirm() {
|
||||
if (onConfirm) onConfirm(repoWithTag);
|
||||
else void modelsStore.status.cancelDownload(repoWithTag);
|
||||
|
||||
onClose();
|
||||
}
|
||||
</script>
|
||||
|
||||
<DialogConfirmation
|
||||
cancelText={copy.cancelText}
|
||||
confirmText={copy.confirmText}
|
||||
description={copy.description(displayName)}
|
||||
onCancel={onClose}
|
||||
onConfirm={confirm}
|
||||
{open}
|
||||
title={copy.title}
|
||||
variant="destructive"
|
||||
/>
|
||||
@@ -108,6 +108,17 @@ export { default as DialogExportSettings } from './DialogExportSettings.svelte';
|
||||
*/
|
||||
export { default as DialogConfirmation } from './DialogConfirmation.svelte';
|
||||
|
||||
/**
|
||||
* **DialogConfirmDownload** - Confirm a destructive download action
|
||||
*
|
||||
* Shared confirmation for stopping/cancelling an in-flight download or deleting
|
||||
* a downloaded model, used by the discover quant chips and the model selector's
|
||||
* download rows so both word the action identically. Owns the copy and the
|
||||
* default store removal; render one instance per surface keyed by the acted-on
|
||||
* repo:tag.
|
||||
*/
|
||||
export { default as DialogConfirmDownload } from './DialogConfirmDownload.svelte';
|
||||
|
||||
/**
|
||||
* **DialogConversationRename** - Rename a conversation
|
||||
*
|
||||
|
||||
+7
-21
@@ -3,7 +3,6 @@
|
||||
import ModelsDiscoverAvatar from '../discover/ModelsDiscoverAvatar.svelte';
|
||||
import { Loader2, Pause, Play, X } from '@lucide/svelte';
|
||||
import { ModelId } from '$lib/components/app';
|
||||
import DialogConfirmation from '$lib/components/app/dialogs/DialogConfirmation.svelte';
|
||||
import { HuggingFaceService, ModelsService } from '$lib/services';
|
||||
import { modelsStore } from '$lib/stores';
|
||||
import type { ModelDownloadProgress } from '$lib/types';
|
||||
@@ -11,12 +10,14 @@
|
||||
interface Props {
|
||||
/** One entry from the status feed: an in-flight or paused download. */
|
||||
entry: { isPaused: boolean; progress: ModelDownloadProgress | null; repoWithTag: string };
|
||||
/**
|
||||
* Ask the list to confirm cancelling this download. The row owns no dialog;
|
||||
* the list renders a single shared confirmation.
|
||||
*/
|
||||
onRequestCancel?: (repoWithTag: string) => void;
|
||||
}
|
||||
|
||||
let { entry }: Props = $props();
|
||||
|
||||
// cancel confirmation state
|
||||
let confirmCancelOpen = $state(false);
|
||||
let { entry, onRequestCancel }: Props = $props();
|
||||
|
||||
let percent = $derived(
|
||||
entry.progress && entry.progress.totalBytes > 0
|
||||
@@ -105,7 +106,7 @@
|
||||
<button
|
||||
aria-label="Cancel downloading"
|
||||
class="inline-flex h-4 w-4 shrink-0 scale-75 cursor-pointer items-center justify-center rounded-sm text-muted-foreground/70 opacity-0 transition-[opacity,transform,color] duration-150 ease-[cubic-bezier(0.23,1,0.32,1)] hover:text-destructive group-hover:scale-100 group-hover:opacity-100 [@media(pointer:coarse)]:scale-100 [@media(pointer:coarse)]:opacity-100"
|
||||
onclick={() => (confirmCancelOpen = true)}
|
||||
onclick={() => onRequestCancel?.(entry.repoWithTag)}
|
||||
type="button"
|
||||
>
|
||||
<X class="h-4 w-4" />
|
||||
@@ -119,18 +120,3 @@
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<DialogConfirmation
|
||||
cancelText="Keep downloading"
|
||||
confirmText="Cancel download"
|
||||
description={`This stops the download of ${modelsStore.toDisplayName(entry.repoWithTag)} and removes the partial files. Pause it instead to keep the progress.`}
|
||||
onCancel={() => (confirmCancelOpen = false)}
|
||||
onConfirm={() => {
|
||||
confirmCancelOpen = false;
|
||||
|
||||
void modelsStore.status.cancelDownload(entry.repoWithTag);
|
||||
}}
|
||||
open={confirmCancelOpen}
|
||||
title="Cancel download"
|
||||
variant="destructive"
|
||||
/>
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
<script lang="ts">
|
||||
import ModelsSelectorDownloadItem from './ModelsSelectorDownloadItem.svelte';
|
||||
import { ModelsSelectorOption } from '$lib/components/app';
|
||||
import { DialogConfirmDownload } from '$lib/components/app/dialogs';
|
||||
import type { GroupedModelOptions, ModelItem } from '$lib/components/app/navigation/utils';
|
||||
import { DownloadConfirmAction } from '$lib/enums';
|
||||
import { modelsStore } from '$lib/stores';
|
||||
|
||||
interface Props {
|
||||
@@ -27,6 +29,17 @@
|
||||
|
||||
/** In-flight / paused downloads, tracked by the status feed. */
|
||||
let downloadEntries = $derived(modelsStore.status.downloadEntries());
|
||||
|
||||
// Cancel is confirmed once for the whole list rather than per download row, so
|
||||
// a single dialog instance is mounted however many downloads are in flight.
|
||||
// The target is kept while the dialog closes so its copy stays rendered.
|
||||
let pendingCancel = $state('');
|
||||
let cancelOpen = $state(false);
|
||||
|
||||
function requestCancel(repoWithTag: string) {
|
||||
pendingCancel = repoWithTag;
|
||||
cancelOpen = true;
|
||||
}
|
||||
</script>
|
||||
|
||||
{#snippet defaultOption(item: ModelItem, _hideOrgName: boolean)}
|
||||
@@ -60,7 +73,7 @@
|
||||
<p class={sectionHeaderClass}>Download in progress</p>
|
||||
|
||||
{#each downloadEntries as entry (entry.repoWithTag)}
|
||||
<ModelsSelectorDownloadItem {entry} />
|
||||
<ModelsSelectorDownloadItem {entry} onRequestCancel={requestCancel} />
|
||||
{/each}
|
||||
{/if}
|
||||
|
||||
@@ -81,3 +94,10 @@
|
||||
{/each}
|
||||
{/each}
|
||||
{/if}
|
||||
|
||||
<DialogConfirmDownload
|
||||
action={DownloadConfirmAction.CANCEL}
|
||||
onClose={() => (cancelOpen = false)}
|
||||
open={cancelOpen}
|
||||
repoWithTag={pendingCancel}
|
||||
/>
|
||||
|
||||
+22
-30
@@ -2,8 +2,8 @@
|
||||
import { classify, labelFor } from './download-options.utils';
|
||||
import ModelsDiscoverDetailsDownloadOptionsDownloadCommand from './ModelsDiscoverDetailsDownloadOptionsDownloadCommand.svelte';
|
||||
import ModelsDiscoverDetailsDownloadOptionsRow from './ModelsDiscoverDetailsDownloadOptionsRow.svelte';
|
||||
import DialogConfirmation from '$lib/components/app/dialogs/DialogConfirmation.svelte';
|
||||
import { SelectableFileKind } from '$lib/enums';
|
||||
import { DialogConfirmDownload } from '$lib/components/app/dialogs';
|
||||
import { DownloadConfirmAction, SelectableFileKind } from '$lib/enums';
|
||||
import { HuggingFaceService, ModelsService } from '$lib/services';
|
||||
import { modelsStore } from '$lib/stores';
|
||||
import type { BitDepthRow, DownloadEntryState, QuantOption, SelectableFile } from '$lib/types';
|
||||
@@ -24,26 +24,25 @@
|
||||
let { bitDepthRows, getDownloadState, modelId }: Props = $props();
|
||||
|
||||
// Destructive chip actions (delete a downloaded model, cancel a download) are
|
||||
// confirmed here rather than inside each chip: one pair of dialogs owned by
|
||||
// the options panel, keyed by the repo+tag the user acted on.
|
||||
let pending: { action: 'cancel' | 'delete'; repoWithTag: string } | null = $state(null);
|
||||
// confirmed here rather than inside each chip: a single shared dialog owned by
|
||||
// the options panel, keyed by the repo+tag the user acted on, so one dialog is
|
||||
// mounted for the whole panel instead of one per chip.
|
||||
// The acted-on target is kept after closing so the copy stays rendered through
|
||||
// the dialog's close transition.
|
||||
let pending: { action: DownloadConfirmAction; repoWithTag: string } = $state({
|
||||
action: DownloadConfirmAction.CANCEL,
|
||||
repoWithTag: ''
|
||||
});
|
||||
let confirmOpen = $state(false);
|
||||
|
||||
function requestCancel(repoWithTag: string) {
|
||||
pending = { action: 'cancel', repoWithTag };
|
||||
pending = { action: DownloadConfirmAction.CANCEL, repoWithTag };
|
||||
confirmOpen = true;
|
||||
}
|
||||
|
||||
function requestDelete(repoWithTag: string) {
|
||||
pending = { action: 'delete', repoWithTag };
|
||||
}
|
||||
|
||||
function closePending() {
|
||||
pending = null;
|
||||
}
|
||||
|
||||
function confirmPending() {
|
||||
if (pending) void modelsStore.status.cancelDownload(pending.repoWithTag);
|
||||
|
||||
pending = null;
|
||||
pending = { action: DownloadConfirmAction.DELETE, repoWithTag };
|
||||
confirmOpen = true;
|
||||
}
|
||||
|
||||
function stateFor(repoWithTag: string, filePath: string, isSidecar: boolean): DownloadEntryState {
|
||||
@@ -157,16 +156,9 @@
|
||||
</section>
|
||||
{/if}
|
||||
|
||||
{#if pending}
|
||||
<DialogConfirmation
|
||||
confirmText={pending.action === 'delete' ? 'Delete' : 'Cancel download'}
|
||||
description={pending.action === 'delete'
|
||||
? `This permanently removes ${modelsStore.toDisplayName(pending.repoWithTag)} from disk. You can download it again later.`
|
||||
: `This stops the download of ${modelsStore.toDisplayName(pending.repoWithTag)} and removes the partial files. Pause it instead to keep the progress.`}
|
||||
onCancel={closePending}
|
||||
onConfirm={confirmPending}
|
||||
open
|
||||
title={pending.action === 'delete' ? 'Delete model' : 'Cancel download'}
|
||||
variant="destructive"
|
||||
/>
|
||||
{/if}
|
||||
<DialogConfirmDownload
|
||||
action={pending.action}
|
||||
onClose={() => (confirmOpen = false)}
|
||||
open={confirmOpen}
|
||||
repoWithTag={pending.repoWithTag}
|
||||
/>
|
||||
|
||||
@@ -77,7 +77,7 @@ export {
|
||||
SelectableFileKind
|
||||
} from './model.enums';
|
||||
|
||||
export { DownloadStopRequest } from './model.enums';
|
||||
export { DownloadConfirmAction, DownloadStopRequest } from './model.enums';
|
||||
|
||||
export { ServerRole, ServerModelStatus, ServerModelsSseEventType } from './server.enums';
|
||||
|
||||
|
||||
@@ -56,3 +56,13 @@ export enum DownloadStopRequest {
|
||||
CANCEL = 'cancel',
|
||||
PAUSE = 'pause'
|
||||
}
|
||||
|
||||
/**
|
||||
* Destructive download action the user is asked to confirm: stop and discard an
|
||||
* in-flight download, or delete an already-downloaded model from disk. Both
|
||||
* resolve through the same store removal call, differing only in the copy.
|
||||
*/
|
||||
export enum DownloadConfirmAction {
|
||||
CANCEL = 'cancel',
|
||||
DELETE = 'delete'
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user