/** * Downloadgroesse des Sprachmodells bei Hugging Face erfragen, damit im Reiter * eine echte Zahl steht statt einer Schaetzung. * * Geladen werden zwei Dateien: der Encoder und der zusammengefuehrte Decoder, * jeweils in der Variante q4. */ const DTYPE_SUFFIX = '_q4'; export interface ModelSize { totalBytes: number; /** Groesste Einzeldatei - sie entscheidet, ob der Browser sie halten kann. */ largestBytes: number; } interface Sibling { rfilename: string; size?: number; } const cache = new Map(); export async function fetchModelSize(model: string): Promise { const cached = cache.get(model); if (cached) return cached; const response = await fetch(`https://huggingface.co/api/models/${model}?blobs=true`); if (!response.ok) throw new Error(`Modellinformationen nicht abrufbar (HTTP ${response.status}).`); const data = (await response.json()) as { siblings?: Sibling[] }; const files = new Map(); for (const sibling of data.siblings ?? []) { if (sibling.rfilename.endsWith('.onnx')) { files.set(sibling.rfilename.replace(/^onnx\//, '').replace(/\.onnx$/, ''), sibling.size ?? 0); } } // transformers.js nimmt den zusammengefuehrten Decoder, wenn er vorliegt. const decoderName = files.has(`decoder_model_merged${DTYPE_SUFFIX}`) ? `decoder_model_merged${DTYPE_SUFFIX}` : `decoder_model${DTYPE_SUFFIX}`; const encoder = files.get(`encoder_model${DTYPE_SUFFIX}`); const decoder = files.get(decoderName); if (encoder === undefined || decoder === undefined) return null; const size: ModelSize = { totalBytes: encoder + decoder, largestBytes: Math.max(encoder, decoder), }; cache.set(model, size); return size; } /** "299 MB" bzw. "1,6 GB" */ export function formatSize(bytes: number): string { if (bytes >= 1_000_000_000) return `${(bytes / 1e9).toFixed(1).replace('.', ',')} GB`; return `${Math.round(bytes / 1e6)} MB`; }