feat: download Hugging Face GGUF models
This commit is contained in:
Vendored
+22
-7
@@ -126,7 +126,7 @@
|
||||
}
|
||||
|
||||
function ModelCard(props) {
|
||||
var model = props.model, installed = props.installed, busy = props.busy, action = props.action;
|
||||
var model = props.model, installed = props.installed, busy = props.busy, action = props.action, onHuggingFace = props.onHuggingFace;
|
||||
var openState = React.useState(false), open = openState[0], setOpen = openState[1];
|
||||
var badges = [];
|
||||
if (model.source === "huggingface") badges.push(h(Badge, { key: "huggingface", tone: "popular" }, "Hugging Face"));
|
||||
@@ -143,6 +143,7 @@
|
||||
installed && h(Button, { disabled: !!busy, onClick: function () { action("redownload", model.name); } }, busy === model.name + ":redownload" ? "Updating…" : "Update / re-download"),
|
||||
installed && h(Button, { disabled: !!busy, className: "ollama-button danger", onClick: function () { action("delete", model.name); } }, busy === model.name + ":delete" ? "Removing…" : "Remove"),
|
||||
!installed && model.source === "huggingface" && model.hf_url && h("a", { className: "ollama-button secondary", href: model.hf_url, target: "_blank", rel: "noreferrer" }, "Open on Hugging Face"),
|
||||
!installed && model.source === "huggingface" && onHuggingFace && h(Button, { disabled: !!busy, onClick: function () { onHuggingFace(model); } }, "Download GGUF"),
|
||||
!installed && model.source !== "huggingface" && h(Button, { disabled: !!busy, onClick: function () { action("pull", model.name); } }, busy === model.name + ":pull" ? "Downloading…" : "Download")
|
||||
)
|
||||
),
|
||||
@@ -510,6 +511,8 @@
|
||||
var busyState = React.useState(""), busy = busyState[0], setBusy = busyState[1];
|
||||
var noticeState = React.useState(null), notice = noticeState[0], setNotice = noticeState[1];
|
||||
var targetDialogState = React.useState(null), targetDialog = targetDialogState[0], setTargetDialog = targetDialogState[1];
|
||||
var hfFileDialogState = React.useState(null), hfFileDialog = hfFileDialogState[0], setHfFileDialog = hfFileDialogState[1];
|
||||
var hfSearchState = React.useState(false), includeHuggingFace = hfSearchState[0], setIncludeHuggingFace = hfSearchState[1];
|
||||
var loadingState = React.useState(true), loading = loadingState[0], setLoading = loadingState[1];
|
||||
var externalSearchState = React.useState([]), externalSearch = externalSearchState[0], setExternalSearch = externalSearchState[1];
|
||||
var externalSearchBusyState = React.useState(false), externalSearchBusy = externalSearchBusyState[0], setExternalSearchBusy = externalSearchBusyState[1];
|
||||
@@ -523,15 +526,15 @@
|
||||
React.useEffect(function () {
|
||||
var term = query.trim();
|
||||
var sequence = ++searchSequence.current;
|
||||
if (term.length < 2) { setExternalSearch([]); setExternalSearchBusy(false); return; }
|
||||
if (term.length < 2 || !includeHuggingFace) { setExternalSearch([]); setExternalSearchBusy(false); return; }
|
||||
setExternalSearchBusy(true);
|
||||
var timer = setTimeout(function () {
|
||||
fetchJSON(API + "/catalog/search?q=" + encodeURIComponent(term) + "&limit=30").then(function (value) {
|
||||
if (sequence === searchSequence.current) setExternalSearch(value.results || []);
|
||||
fetchJSON(API + "/catalog/search?q=" + encodeURIComponent(term) + "&limit=30&include_huggingface=true").then(function (value) {
|
||||
if (sequence === searchSequence.current) setExternalSearch(value.huggingface_results || []);
|
||||
}).catch(function () { if (sequence === searchSequence.current) setExternalSearch([]); }).finally(function () { if (sequence === searchSequence.current) setExternalSearchBusy(false); });
|
||||
}, 250);
|
||||
return function () { clearTimeout(timer); };
|
||||
}, [query]);
|
||||
}, [query, includeHuggingFace]);
|
||||
function action(kind, name, selectedTarget) {
|
||||
if (kind === "delete" && !window.confirm("Remove " + name + " from Ollama?")) return;
|
||||
if ((kind === "pull" || kind === "redownload") && !selectedTarget) {
|
||||
@@ -544,6 +547,16 @@
|
||||
fetchJSON(API + (kind === "delete" ? "/model" : "/" + kind), { method: kind === "delete" ? "DELETE" : "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ name: name, target: selectedTarget || "local" }) }).then(function (result) { setNotice({ ok: result.message || "Action started." }); load(); }).catch(function (err) { setNotice({ error: err.message || String(err) }); }).finally(function () { setBusy(""); });
|
||||
}
|
||||
function refreshCatalog() { setBusy("catalog"); setNotice(null); fetchJSON(API + "/catalog/refresh", { method: "POST" }).then(function (result) { setNotice({ ok: "Catalog refreshed: " + result.count + " models." }); load(); }).catch(function (err) { setNotice({ error: err.message || String(err) }); }).finally(function () { setBusy(""); }); }
|
||||
function openHuggingFaceFiles(model) {
|
||||
if (!model || !model.name) return;
|
||||
setBusy("hf-files"); setNotice(null); setHfFileDialog({ model: model, files: null, import_supported: false });
|
||||
fetchJSON(API + "/huggingface/files?repo_id=" + encodeURIComponent(model.name)).then(function (value) { setHfFileDialog({ model: model, files: value.files || [], import_supported: !!value.import_supported }); }).catch(function (err) { setHfFileDialog(null); setNotice({ error: err.message || String(err) }); }).finally(function () { setBusy(""); });
|
||||
}
|
||||
function downloadHuggingFaceFile(model, file) {
|
||||
if (!model || !file || !file.filename) return;
|
||||
setBusy("hf-download"); setNotice(null);
|
||||
fetchJSON(API + "/huggingface/download", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ repo_id: model.name, filename: file.filename }) }).then(function (result) { setHfFileDialog(null); setNotice({ ok: result.message || "Hugging Face GGUF download started." }); load(); }).catch(function (err) { setNotice({ error: err.message || String(err) }); }).finally(function () { setBusy(""); });
|
||||
}
|
||||
var baseModels = data ? (tab === "installed" ? data.models || [] : tab === "popular" ? data.popular || [] : tab === "catalog" ? (showOversized ? data.catalog_all || data.catalog || [] : data.catalog || []) : []) : [];
|
||||
var models = baseModels;
|
||||
if (tab === "catalog") {
|
||||
@@ -590,18 +603,20 @@
|
||||
h("label", { className: "ollama-catalog-checkbox ollama-catalog-memory-bypass", title: "This only bypasses the catalog display filter; loading remains protected by the 95% RAM safety guard." }, h("input", { type: "checkbox", checked: showOversized, onChange: function (event) { setShowOversized(event.target.checked); } }), h("span", null, "Show models above estimated RAM"))
|
||||
);
|
||||
var browseToolbar = tab !== "chat" && h("div", { className: "ollama-browse-row" },
|
||||
h("input", { className: "ollama-search", value: query, placeholder: "Search Ollama + Hugging Face models…", onChange: function (event) { setQuery(event.target.value); } }),
|
||||
h("input", { className: "ollama-search", value: query, placeholder: includeHuggingFace ? "Search Ollama + Hugging Face models…" : "Search Ollama models…", onChange: function (event) { setQuery(event.target.value); } }),
|
||||
tab === "catalog" && h("label", { className: "ollama-catalog-checkbox ollama-huggingface-checkbox", title: "Search the public Hugging Face Hub in addition to Ollama." }, h("input", { type: "checkbox", checked: includeHuggingFace, onChange: function (event) { setIncludeHuggingFace(event.target.checked); } }), h("span", null, "Search Hugging Face")),
|
||||
externalSearchBusy && h("small", { className: "ollama-search-status" }, "Searching Hugging Face…"),
|
||||
catalogControls
|
||||
);
|
||||
return h("main", { className: "ollama-page" }, h("header", { className: "ollama-hero" }, h("div", null, h("div", { className: "ollama-eyebrow" }, "LOCAL MODEL OPERATIONS"), h("h1", null, "Ollama Models"), h("p", null, "Inspect, chat with, download, update, and remove models from the local Ollama runtime.")), h("div", { className: "ollama-health" }, h(Badge, { tone: data && data.ollama && data.ollama.available ? "live" : "danger" }, data && data.ollama && data.ollama.available ? "Ollama online" : "Ollama unavailable"), data && data.ollama && h("span", null, "v" + (data.ollama.version || "unknown")), h(Button, { disabled: busy === "catalog", onClick: refreshCatalog }, busy === "catalog" ? "Refreshing…" : "Refresh catalog")), h(ConnectionPanel, { data: data, reload: load })),
|
||||
targetDialog && h("div", { className: "ollama-target-modal" }, h("div", { className: "ollama-target-card" }, h("h3", null, "Where should " + targetDialog.name + " be downloaded?"), h("p", null, "Both local and remote Ollama instances are online. Choose the destination for this model."), targetDialog.targets.map(function (item) { return h(Button, { key: item.kind, onClick: function () { var chosen = targetDialog; setTargetDialog(null); action(chosen.kind, chosen.name, item.kind); } }, (item.kind || "local").toUpperCase(), " · ", item.url, " · v", item.version, " · ", item.models, " models"); }), h(Button, { className: "secondary", onClick: function () { setTargetDialog(null); } }, "Cancel"))),
|
||||
hfFileDialog && h("div", { className: "ollama-target-modal" }, h("div", { className: "ollama-target-card ollama-huggingface-file-card" }, h("h3", null, "Download GGUF from ", hfFileDialog.model.name), h("p", null, hfFileDialog.import_supported ? "Choose a GGUF file. It will be downloaded and imported into the local Ollama runtime." : "Choose a GGUF file. It will be downloaded to Hermes storage; automatic Ollama import is unavailable in this deployment."), hfFileDialog.files === null ? h("p", null, "Loading repository files…") : hfFileDialog.files.length ? hfFileDialog.files.map(function (file) { return h("div", { className: "ollama-huggingface-file-row", key: file.filename }, h("span", null, h("strong", null, file.filename), h("small", null, (file.size_label || "Unknown size") + (file.split ? " · complete " + file.file_count + "-shard set" : ""))), h(Button, { disabled: !!busy, onClick: function () { downloadHuggingFaceFile(hfFileDialog.model, file); } }, busy === "hf-download" ? "Starting…" : (file.split ? "Download set" : "Download"))); }) : h("p", null, "This repository has no compatible GGUF files. Open the repository to inspect Transformers, safetensors, or other formats."), h("div", { className: "ollama-huggingface-file-actions" }, h("a", { className: "ollama-button secondary", href: hfFileDialog.model.hf_url, target: "_blank", rel: "noreferrer" }, "Open repository"), h(Button, { className: "secondary", onClick: function () { setHfFileDialog(null); } }, "Cancel")))),
|
||||
notice && h("div", { className: "ollama-notice " + (notice.error ? "error" : notice.warning ? "warning" : "ok") }, notice.error || notice.warning || notice.ok),
|
||||
h("section", { className: "ollama-toolbar" }, h("div", { className: "ollama-nav-row" }, navTabs, h("div", { className: "ollama-toolbar-disk" }, h("span", null, "Disk"), h("strong", null, disk.used_percent == null ? "n/a" : Number(disk.used_percent).toFixed(1) + "%"), h("small", null, disk.available ? fmtBytes(disk.free_bytes) + " free" : "Unavailable"))), browseToolbar),
|
||||
tab !== "chat" && h("div", { className: "ollama-info-strip" }, h("span", null, data && data.models ? data.models.filter(function (m) { return m.loaded; }).length + " currently loaded" : "Loading runtime state…"), h("span", null, "Catalog checked " + (data && data.catalog_updated_at ? fmtDate(data.catalog_updated_at) : "not yet")), h("span", null, "Next daily check " + (data && data.next_catalog_refresh ? fmtDate(data.next_catalog_refresh) : "01:00 Melbourne time") + " (1:00 AM Melbourne time)")),
|
||||
tab === "chat" && h(ChatPanel, { models: data && data.models ? data.models : [], refresh: load }),
|
||||
tab === "popular" && h("p", { className: "ollama-popular-note" }, "Popular is limited to models with known size and RAM estimates at or below the detected system RAM (" + (data && data.popular_filter && data.popular_filter.max_expected_ram_gib ? data.popular_filter.max_expected_ram_gib + " GiB" : "detecting…") + "). Oversized families are represented by a smaller fitting variant when available."), jobs.length > 0 && h("section", { className: "ollama-jobs" }, jobs.map(function (job) { return h("div", { key: job.id }, h("strong", null, job.action + " · " + job.name + " · " + (job.target || "local") + (job.endpoint ? " · " + job.endpoint : "")), h("span", null, job.percent == null ? job.status : job.percent + "%")); })),
|
||||
tab !== "chat" && loading && h(Empty, null, "Loading local Ollama inventory…"), tab !== "chat" && !loading && !models.length && h(Empty, null, tab === "installed" ? "No local models found." : tab === "popular" ? "No popular catalog entries available." : query.trim().length >= 2 ? "No Ollama or Hugging Face models matched this search." : "No catalog entries available. Try Refresh catalog."), tab !== "chat" && h("section", { className: "ollama-grid" }, models.map(function (model) { return h(ModelCard, { key: model.source + ":" + model.name, model: model, installed: tab === "installed" || !!model.installed, busy: busy, action: action }); }))
|
||||
tab !== "chat" && loading && h(Empty, null, "Loading local Ollama inventory…"), tab !== "chat" && !loading && !models.length && h(Empty, null, tab === "installed" ? "No local models found." : tab === "popular" ? "No popular catalog entries available." : query.trim().length >= 2 ? "No Ollama or Hugging Face models matched this search." : "No catalog entries available. Try Refresh catalog."), tab !== "chat" && h("section", { className: "ollama-grid" }, models.map(function (model) { return h(ModelCard, { key: model.source + ":" + model.name, model: model, installed: tab === "installed" || !!model.installed, busy: busy, action: action, onHuggingFace: openHuggingFaceFiles }); }))
|
||||
);
|
||||
}
|
||||
registry.register("ollama-manager", Page);
|
||||
|
||||
Reference in New Issue
Block a user