ui : move discover download types, enums and constants to lib

Extract the download-options shared symbols out of the component-local
download-options.utils.ts into their lib-scoped folders: QuantOption,
DownloadEntryState, BitDepthRow and SelectableFile into $lib/types; a new
SelectableFileKind enum (replacing the 'main'/'draft'/'aux' string union)
into $lib/enums; SPEC_TYPE, the bit-depth buckets and the draft label into
a new models-discover-download.constants.ts. The util keeps only its
dedicated functions, with named constants for its magic values.

Assisted-by: llama-ui:Qwen3.8-Flash-Next
This commit is contained in:
Aleksander Grygier
2026-09-04 13:09:32 +02:00
parent 587c43ef8f
commit a02c90cdc5
12 changed files with 134 additions and 79 deletions
@@ -3,9 +3,10 @@
import ModelsDiscoverDetailsHeader from './ModelsDiscoverDetailsHeader.svelte';
import ModelsDiscoverDetailsReadme from './ModelsDiscoverDetailsReadme.svelte';
import ModelsDiscoverDetailsSkeleton from './ModelsDiscoverDetailsSkeleton.svelte';
import { OTHER_BIT_DEPTH } from '$lib/constants';
import { ModelAuxSidecar } from '$lib/enums';
import { HuggingFaceService } from '$lib/services';
import type { HfModelDetailInfo, HfModelSibling } from '$lib/types/huggingface';
import type { BitDepthRow, HfModelDetailInfo, HfModelSibling } from '$lib/types';
import { detectThinkingSupport, detectToolUseSupport } from '$lib/utils';
import { SvelteMap } from 'svelte/reactivity';
@@ -48,7 +49,6 @@
// Draft sidecars (mtp, dflash, dspark, eagle3) present in the repo, e.g.
// speculative-decoding drafts. mmproj is excluded: it is vision.
type BitDepthRow = { bitDepth: number; files: HfModelSibling[] };
let bitDepthRows = $derived.by<BitDepthRow[]>(() => {
const rows = new SvelteMap<number, HfModelSibling[]>();
@@ -60,7 +60,7 @@
if (meta?.sidecar === ModelAuxSidecar.MMPROJ) continue;
const depth = meta?.quant ? HuggingFaceService.getBitDepth(meta.quant) : null;
const bucket = depth ?? 99;
const bucket = depth ?? OTHER_BIT_DEPTH;
const list = rows.get(bucket) ?? [];
list.push(file);
@@ -1,16 +1,11 @@
<script lang="ts">
import {
type BitDepthRow,
classify,
type DownloadEntryState,
labelFor,
type QuantOption,
type SelectableFile
} from './download-options.utils';
import { classify, labelFor } from './download-options.utils';
import ModelsDiscoverDetailsDownloadOptionsDownloadCommand from './ModelsDiscoverDetailsDownloadOptionsDownloadCommand.svelte';
import ModelsDiscoverDetailsDownloadOptionsRow from './ModelsDiscoverDetailsDownloadOptionsRow.svelte';
import { SelectableFileKind } from '$lib/enums';
import { HuggingFaceService, ModelsService } from '$lib/services';
import { modelsStore } from '$lib/stores';
import type { BitDepthRow, DownloadEntryState, QuantOption, SelectableFile } from '$lib/types';
interface Props {
/** Full HuggingFace repo id, e.g. `ggml-org/gemma-3-4b-it-GGUF`. */
@@ -98,7 +93,9 @@
}
/** Non-draft quants for the command's base select, in row order. */
let mainOptions = $derived(selectableFiles.filter((f) => f.kind === 'main').map(optionFor));
let mainOptions = $derived(
selectableFiles.filter((f) => f.kind === SelectableFileKind.MAIN).map(optionFor)
);
/**
* Draft options for the command's draft select, with their sidecar type
@@ -106,7 +103,7 @@
*/
let draftOptions = $derived(
selectableFiles
.filter((f) => f.kind === 'draft')
.filter((f) => f.kind === SelectableFileKind.DRAFT)
.map((f) => ({
...optionFor(f),
badge: HuggingFaceService.extractQuantMeta(f.path)?.sidecar ?? null
@@ -1,9 +1,10 @@
<script lang="ts">
import { quantBitDepth, type QuantOption, SPEC_TYPE } from './download-options.utils';
import { quantBitDepth } from './download-options.utils';
import { Check, Copy, Plus, X } from '@lucide/svelte';
import * as Select from '$lib/components/ui/select';
import { type ModelSidecar } from '$lib/constants';
import { DEFAULT_BASE_BIT_DEPTH, type ModelSidecar,SPEC_TYPE } from '$lib/constants';
import { HuggingFaceService } from '$lib/services';
import type { QuantOption } from '$lib/types';
import { copyToClipboard } from '$lib/utils';
interface Props {
@@ -22,9 +23,6 @@
let draftTypePick = $state<ModelSidecar | null>(null);
let withDraft = $state(false);
/** Bit depth to prefer in the default base quant; the closest one wins. */
const DEFAULT_BASE_BIT_DEPTH = 4;
function bitDepthOf(path: string): number {
return quantBitDepth(HuggingFaceService.extractQuantMeta(path)?.quant ?? null);
}
@@ -1,12 +1,12 @@
<script lang="ts">
import DownloadProgressBar from '../../DownloadProgressBar.svelte';
import { type DownloadEntryState, labelFor } from './download-options.utils';
import { labelFor } from './download-options.utils';
import { Check, Download, Loader2, Pause, Play, RotateCw, X } from '@lucide/svelte';
import DialogConfirmation from '$lib/components/app/dialogs/DialogConfirmation.svelte';
import * as Tooltip from '$lib/components/ui/tooltip';
import { HuggingFaceService } from '$lib/services';
import { modelsStore } from '$lib/stores';
import type { HfModelSibling } from '$lib/types';
import type { DownloadEntryState, HfModelSibling } from '$lib/types';
interface Props {
/** GGUF file the chip stands for. */
@@ -1,6 +1,7 @@
<script lang="ts">
import type { DownloadEntryState, SelectableFile } from './download-options.utils';
import ModelsDiscoverDetailsDownloadOptionsQuantDownloadButton from './ModelsDiscoverDetailsDownloadOptionsQuantDownloadButton.svelte';
import { SelectableFileKind } from '$lib/enums';
import type { DownloadEntryState, SelectableFile } from '$lib/types';
import { minMemoryTierGb } from '$lib/utils';
interface Props {
@@ -12,8 +13,8 @@
let { bitDepth, files }: Props = $props();
let mainFile = $derived(files.find((f) => f.kind === 'main') ?? null);
let draftFile = $derived(files.find((f) => f.kind === 'draft') ?? null);
let mainFile = $derived(files.find((f) => f.kind === SelectableFileKind.MAIN) ?? null);
let draftFile = $derived(files.find((f) => f.kind === SelectableFileKind.DRAFT) ?? null);
let mainMemGb = $derived(mainFile ? minMemoryTierGb(mainFile.size ?? 0) : null);
let draftMemGb = $derived(draftFile ? minMemoryTierGb(draftFile.size ?? 0) : null);
@@ -1,44 +1,24 @@
import { isAuxSidecar, isDraftSidecar, type ModelSidecar } from '$lib/constants';
import { ModelAuxSidecar, ModelDraftSidecar } from '$lib/enums';
import { DRAFT_FILE_LABEL, isAuxSidecar, isDraftSidecar, OTHER_BIT_DEPTH } from '$lib/constants';
import { SelectableFileKind } from '$lib/enums';
import { HuggingFaceService } from '$lib/services';
import type { HfModelSibling } from '$lib/types/huggingface';
/**
* Option of a quant `<select>`; the picks only compose the serve command and
* are not bound to the quant chips.
*/
export interface QuantOption {
/** Quant token, or the file name when the file carries no quant (e.g. BF16). */
label: string;
/** Repo-relative file path the option stands for. */
path: string;
}
/** Download state of a single repo entry, injected by the integration layer. */
export interface DownloadEntryState {
/** Server identifier the entry's actions (pause / resume / cancel / retry) target. */
repoWithTag: string;
isDownloading: boolean;
progress: ModelDownloadProgress | null;
isDownloaded: boolean;
isPaused: boolean;
isFailed: boolean;
}
/** Download state of a single repo entry, injected by the integration layer. */
export type BitDepthRow = { bitDepth: number; files: HfModelSibling[] };
/** A selectable GGUF, tagged with its kind: main weights, draft, or aux (mmproj). */
export type SelectableFile = HfModelSibling & { kind: 'main' | 'draft' | 'aux' };
/** Leading `UD-` (Unsloth Dynamic) quant prefix and its length. */
const UD_QUANT_PREFIX = 'UD-';
const UD_QUANT_PREFIX_REGEX = /^UD-(?=.)/i;
/** Captures the bit-depth digits of a quant token, e.g. `Q4_K_M` -> `4`. */
const QUANT_BIT_DEPTH_REGEX = /(?:I?Q|F)(\d+)/i;
/** Trailing weight extension, stripped from a quantless file's label. */
const WEIGHT_EXTENSION_LABEL_REGEX = /\.gguf$/i;
/** Path separator between path segments. */
const PATH_SEPARATOR = '/';
/** Kind of a file path: the main weights, a draft sidecar, or an aux sidecar (mmproj). */
export function classify(path: string): 'main' | 'draft' | 'aux' {
export function classify(path: string): SelectableFileKind {
const sidecar = HuggingFaceService.extractQuantMeta(path)?.sidecar;
if (!sidecar) return 'main';
if (!sidecar) return SelectableFileKind.MAIN;
return isAuxSidecar(sidecar) ? 'aux' : 'draft';
return isAuxSidecar(sidecar) ? SelectableFileKind.AUX : SelectableFileKind.DRAFT;
}
/** Display label of a file: its quant, else the file name without the extension. */
@@ -49,32 +29,22 @@ export function labelFor(path: string): string {
// Quantless sidecar files: the chip badge already carries the sidecar type,
// so the label only marks draft files; aux sidecars badge alone.
if (meta?.sidecar) return isDraftSidecar(meta.sidecar) ? 'draft' : '';
if (meta?.sidecar) return isDraftSidecar(meta.sidecar) ? DRAFT_FILE_LABEL : '';
const basename = path.split('/').pop() ?? path;
const basename = path.split(PATH_SEPARATOR).pop() ?? path;
return basename.replace(/\.gguf$/i, '');
return basename.replace(WEIGHT_EXTENSION_LABEL_REGEX, '');
}
/**
* Bit depth of a quant token; `99` (Other) when it carries none. The `UD-`
* Bit depth of a quant token; `OTHER_BIT_DEPTH` when it carries none. The `UD-`
* unsloth prefix is stripped before matching.
*/
export function quantBitDepth(quant: string | null): number {
if (!quant) return 99;
if (!quant) return OTHER_BIT_DEPTH;
const stripped = quant.match(/^UD-(?=.)/i) ? quant.slice(3) : quant;
const bit = stripped.match(/(?:I?Q|F)(\d+)/i)?.[1];
const stripped = UD_QUANT_PREFIX_REGEX.test(quant) ? quant.slice(UD_QUANT_PREFIX.length) : quant;
const bit = stripped.match(QUANT_BIT_DEPTH_REGEX)?.[1];
return bit ? Number(bit) : 99;
return bit ? Number(bit) : OTHER_BIT_DEPTH;
}
// LLAMA-APP-REUSE: --spec-type value for each draft sidecar; aux sidecars stay empty
export const SPEC_TYPE: Record<ModelSidecar, string> = {
[ModelAuxSidecar.IMATRIX]: '',
[ModelAuxSidecar.MMPROJ]: '',
[ModelDraftSidecar.DFLASH]: 'draft-dflash',
[ModelDraftSidecar.DSPARK]: 'draft-dspark',
[ModelDraftSidecar.EAGLE3]: 'eagle3',
[ModelDraftSidecar.MTP]: 'draft-mtp'
};
+1
View File
@@ -45,6 +45,7 @@ export * from './path-display.constants';
export * from './model-id.constants';
export * from './model-loading.constants';
export * from './models-discover.constants';
export * from './models-discover-download.constants';
export * from './huggingface.constants';
export * from './precision.constants';
export * from './pwa.constants';
@@ -0,0 +1,34 @@
/**
* Models Discover — download options constants.
*
* Shared values backing the download-options area of the details pane: the
* serve `--spec-type` mapping, the bit-depth buckets, and the standalone
* command builder. Kept here (not component-local) so they sit with the rest
* of the discover domain constants.
*/
import type { ModelSidecar } from '$lib/constants';
import { ModelAuxSidecar, ModelDraftSidecar } from '$lib/enums';
/**
* `--spec-type` value for each draft sidecar; aux sidecars (mmproj, imatrix)
* carry none and stay empty.
*/
// LLAMA-APP-REUSE: --spec-type value for each draft sidecar; aux sidecars stay empty
export const SPEC_TYPE: Record<ModelSidecar, string> = {
[ModelAuxSidecar.IMATRIX]: '',
[ModelAuxSidecar.MMPROJ]: '',
[ModelDraftSidecar.DFLASH]: 'draft-dflash',
[ModelDraftSidecar.DSPARK]: 'draft-dspark',
[ModelDraftSidecar.EAGLE3]: 'eagle3',
[ModelDraftSidecar.MTP]: 'draft-mtp'
};
/** Bit-depth bucket for files that carry no quant token; rendered as "Other". */
export const OTHER_BIT_DEPTH = 99;
/** Bit depth preferred for the command's default base quant. */
export const DEFAULT_BASE_BIT_DEPTH = 4;
/** Label shown for a draft-sidecar chip whose quant is unknown. */
export const DRAFT_FILE_LABEL = 'draft';
+7 -1
View File
@@ -69,7 +69,13 @@ export {
JsonSchemaType
} from './mcp.enums';
export { ModelAuxSidecar, ModelCapability, ModelDraftSidecar, ModelModality } from './model.enums';
export {
ModelAuxSidecar,
ModelCapability,
ModelDraftSidecar,
ModelModality,
SelectableFileKind
} from './model.enums';
export { ServerRole, ServerModelStatus, ServerModelsSseEventType } from './server.enums';
+13 -3
View File
@@ -31,8 +31,18 @@ export enum ModelDraftSidecar {
* accompanying the main model weights.
*/
export enum ModelAuxSidecar {
/** Multimodal projector: unlocks vision and/or audio input modalities. */
MMPROJ = 'mmproj',
/** Importance-matrix data used to build imatrix quants; not loaded at serve time. */
IMATRIX = 'imatrix'
IMATRIX = 'imatrix',
/** Multimodal projector: unlocks vision and/or audio input modalities. */
MMPROJ = 'mmproj'
}
/**
* Role of a selectable GGUF in the download options: the main weights, a
* speculative-decoding draft sidecar, or an auxiliary sidecar (mmproj).
*/
export enum SelectableFileKind {
AUX = 'aux',
DRAFT = 'draft',
MAIN = 'main'
}
+8
View File
@@ -115,6 +115,14 @@ export type {
ModalityCapabilities
} from './models';
// Models discover types
export type {
BitDepthRow,
DownloadEntryState,
QuantOption,
SelectableFile
} from './models-discover';
// Settings types
export type {
SettingsConfigValue,
+30
View File
@@ -0,0 +1,30 @@
import type { SelectableFileKind } from '$lib/enums';
import type { HfModelSibling } from '$lib/types/huggingface';
/**
* Option of a quant `<select>` in the download-options command builder; the
* picks only compose the serve command and are not bound to the quant chips.
*/
export interface QuantOption {
/** Quant token, or the file name when the file carries no quant (e.g. BF16). */
label: string;
/** Repo-relative file path the option stands for. */
path: string;
}
/** Download state of a single repo entry, injected by the integration layer. */
export interface DownloadEntryState {
/** Server identifier the entry's actions (pause / resume / cancel / retry) target. */
repoWithTag: string;
isDownloading: boolean;
progress: ModelDownloadProgress | null;
isDownloaded: boolean;
isPaused: boolean;
isFailed: boolean;
}
/** A group of GGUF files sharing one bit-depth bucket. */
export type BitDepthRow = { bitDepth: number; files: HfModelSibling[] };
/** A selectable GGUF, tagged with its role: main weights, draft, or aux (mmproj). */
export type SelectableFile = HfModelSibling & { kind: SelectableFileKind };