(baseUrl: string, model: string)
| 63 | |
| 64 | /** Ask Ollama about a model: block count (from /api/show) + on-disk size (from /api/tags). */ |
| 65 | export async function fetchOllamaModelFacts(baseUrl: string, model: string): Promise<ModelFacts | null> { |
| 66 | try { |
| 67 | const showRes = await fetch(`${baseUrl.replace(/\/$/, '')}/api/show`, { |
| 68 | method: 'POST', headers: { 'content-type': 'application/json' }, |
| 69 | body: JSON.stringify({ name: model }), signal: AbortSignal.timeout(5000), |
| 70 | }); |
| 71 | if (!showRes.ok) return null; |
| 72 | const show: any = await showRes.json(); |
| 73 | const totalLayers = extractBlockCount(show?.model_info); |
| 74 | if (!totalLayers) return null; |
| 75 | |
| 76 | let modelSizeGB = 0; |
| 77 | try { |
| 78 | const tagsRes = await fetch(`${baseUrl.replace(/\/$/, '')}/api/tags`, { signal: AbortSignal.timeout(5000) }); |
| 79 | if (tagsRes.ok) { |
| 80 | const tags: any = await tagsRes.json(); |
| 81 | const hit = (tags?.models ?? []).find((m: any) => m.name === model || m.model === model); |
| 82 | if (hit?.size) modelSizeGB = Number(hit.size) / 1e9; |
| 83 | } |
| 84 | } catch { /* size optional */ } |
| 85 | if (!modelSizeGB) return null; |
| 86 | return { totalLayers, modelSizeGB }; |
| 87 | } catch { |
| 88 | return null; |
| 89 | } |
| 90 | } |
| 91 | |
| 92 | export interface OffloadSuggestion { plan: OffloadPlan; facts: ModelFacts; vramGB: number; } |
| 93 |
no test coverage detected