Add Hermes Ollama Models dashboard plugin
This commit is contained in:
Vendored
+188
@@ -0,0 +1,188 @@
|
||||
(function () {
|
||||
"use strict";
|
||||
var SDK = window.__HERMES_PLUGIN_SDK__;
|
||||
var registry = window.__HERMES_PLUGINS__;
|
||||
if (!SDK || !registry) return;
|
||||
var React = SDK.React;
|
||||
var h = React.createElement;
|
||||
var fetchJSON = SDK.fetchJSON;
|
||||
var API = "/api/plugins/ollama-manager";
|
||||
|
||||
function fmtBytes(bytes) {
|
||||
if (!bytes) return "Unknown";
|
||||
var units = ["B", "GiB", "TiB"], value = Number(bytes), index = 0;
|
||||
while (value >= 1024 && index < units.length - 1) { value /= 1024; index += 1; }
|
||||
return value.toFixed(index ? 2 : 0) + " " + units[index];
|
||||
}
|
||||
function fmtDate(value) {
|
||||
if (!value) return "Unknown";
|
||||
try { return new Date(value).toLocaleString(); } catch (_) { return value; }
|
||||
}
|
||||
function Badge(props) { return h("span", { className: "ollama-badge " + (props.tone || "") }, props.children); }
|
||||
function Button(props) { return h("button", Object.assign({ className: "ollama-button" }, props), props.children); }
|
||||
function CapabilityList(props) {
|
||||
var model = props.model;
|
||||
return h("div", { className: "ollama-capabilities" },
|
||||
(model.capabilities || []).map(function (cap) {
|
||||
return h("div", { className: "ollama-capability", key: cap },
|
||||
h(Badge, null, cap),
|
||||
h("span", null, (model.capability_breakdown || {})[cap] || "Advertised by model metadata")
|
||||
);
|
||||
})
|
||||
);
|
||||
}
|
||||
function VariantTable(props) {
|
||||
var model = props.model, variants = model.variants || [], action = props.action, busy = props.busy;
|
||||
if (!variants.length) return null;
|
||||
return h("div", { className: "ollama-variants" },
|
||||
h("div", { className: "ollama-variants-heading" },
|
||||
h("h4", null, "Available ", model.name.split(":")[0], " sizes"),
|
||||
h("span", null, "MLX variants excluded")
|
||||
),
|
||||
h("div", { className: "ollama-variant-table-wrap" },
|
||||
h("table", { className: "ollama-variant-table" },
|
||||
h("thead", null, h("tr", null,
|
||||
h("th", null, "Name"), h("th", null, "Size / RAM"), h("th", null, "Context"), h("th", null, "Input"), h("th", null, "Action")
|
||||
)),
|
||||
h("tbody", null, variants.map(function (variant) {
|
||||
var current = variant.name === model.name;
|
||||
var installed = variant.installed;
|
||||
return h("tr", { key: variant.name, className: current ? "current" : "" },
|
||||
h("td", null, h("strong", null, variant.name), current && h(Badge, { tone: "current" }, "current"), installed && !current && h(Badge, { tone: "installed" }, "installed")),
|
||||
h("td", null, h("strong", null, variant.size_label || "Unknown"), h("small", null, variant.expected_ram_label || "RAM unknown")),
|
||||
h("td", null, variant.context_length ? Math.round(Number(variant.context_length) / 1024) + "K" : "Unknown"),
|
||||
h("td", null, (variant.input_modalities || ["Text"]).join(", ")),
|
||||
h("td", null, current ? h("span", { className: "ollama-current-label" }, "Current") : installed ? h("span", { className: "ollama-current-label" }, "Installed") : h(Button, { disabled: !!busy, onClick: function () { action("pull", variant.name); } }, busy === variant.name + ":pull" ? "Downloading…" : "Download"))
|
||||
);
|
||||
}))
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function ModelCard(props) {
|
||||
var model = props.model, installed = props.installed, busy = props.busy;
|
||||
var openState = React.useState(false), open = openState[0], setOpen = openState[1];
|
||||
var action = props.action;
|
||||
return h("article", { className: "ollama-model-card" },
|
||||
h("div", { className: "ollama-card-top" },
|
||||
h("div", { className: "ollama-model-title" },
|
||||
h("h3", null, model.name),
|
||||
h("div", { className: "ollama-badge-row" },
|
||||
installed && model.loaded && h(Badge, { tone: "live" }, "loaded"),
|
||||
installed && h(Badge, { tone: "installed" }, "installed"),
|
||||
!installed && h(Badge, { tone: "download" }, "available"),
|
||||
model.popularity_rank && h(Badge, { tone: "popular" }, "#" + model.popularity_rank + " popular"),
|
||||
model.is_moe && h(Badge, { tone: "moe" }, "MoE")
|
||||
)
|
||||
),
|
||||
h("div", { className: "ollama-card-actions" },
|
||||
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 && h(Button, { disabled: !!busy, onClick: function () { action("pull", model.name); } }, busy === model.name + ":pull" ? "Downloading…" : "Download")
|
||||
)
|
||||
),
|
||||
h("div", { className: "ollama-model-summary" },
|
||||
h("div", null, h("small", null, "Size"), h("strong", null, model.size_gb ? model.size_gb + " GiB" : "Unknown")),
|
||||
h("div", null, h("small", null, "Expected RAM"), h("strong", null, model.expected_ram_label || "Unknown")),
|
||||
h("div", null, h("small", null, "Type"), h("strong", null, model.architecture || "Unknown")),
|
||||
h("div", null, h("small", null, "Parameters"), h("strong", null, model.parameter_size || "Unknown"))
|
||||
),
|
||||
h("div", { className: "ollama-card-meta" },
|
||||
h("span", null, (model.quantization || "Unknown") + " · " + (model.format || "Unknown")),
|
||||
model.context_length && h("span", null, "Context " + Number(model.context_length).toLocaleString()),
|
||||
installed && model.modified_at && h("span", null, "Updated " + fmtDate(model.modified_at))
|
||||
),
|
||||
h("div", { className: "ollama-strengths" }, h("strong", null, "Excels at: "), (model.strengths || []).join(" · ")),
|
||||
installed && h(VariantTable, { model: model, action: action, busy: busy }),
|
||||
h(Button, { className: "ollama-details-toggle", onClick: function () { setOpen(!open); } }, open ? "Hide capability breakdown" : "Show capability breakdown"),
|
||||
open && h("div", { className: "ollama-details" },
|
||||
h("h4", null, "Capabilities"), h(CapabilityList, { model: model }),
|
||||
h("h4", null, "Runtime estimate"),
|
||||
h("p", null, model.expected_ram_basis || "No estimate basis available.", " Actual memory varies with context length, KV cache, GPU offload, and concurrent requests."),
|
||||
h("div", { className: "ollama-detail-grid" },
|
||||
h("span", null, "Family: ", h("strong", null, model.family || "unknown")),
|
||||
h("span", null, "Digest: ", h("strong", null, model.digest ? model.digest.slice(0, 16) + "…" : "unknown")),
|
||||
h("span", null, "Embedding: ", h("strong", null, model.embedding_length || "unknown")),
|
||||
h("span", null, "Loaded VRAM: ", h("strong", null, fmtBytes(model.loaded_vram_bytes)))
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
function Empty(props) { return h("div", { className: "ollama-empty" }, props.children); }
|
||||
|
||||
function Page() {
|
||||
var dataState = React.useState(null), data = dataState[0], setData = dataState[1];
|
||||
var tabState = React.useState("installed"), tab = tabState[0], setTab = tabState[1];
|
||||
var queryState = React.useState(""), query = queryState[0], setQuery = queryState[1];
|
||||
var busyState = React.useState(""), busy = busyState[0], setBusy = busyState[1];
|
||||
var noticeState = React.useState(null), notice = noticeState[0], setNotice = noticeState[1];
|
||||
var loadingState = React.useState(true), loading = loadingState[0], setLoading = loadingState[1];
|
||||
|
||||
function load() {
|
||||
return fetchJSON(API + "/status").then(function (value) {
|
||||
setData(value); setLoading(false); return value;
|
||||
}).catch(function (err) { setNotice({ error: err.message || String(err) }); setLoading(false); });
|
||||
}
|
||||
React.useEffect(function () {
|
||||
load();
|
||||
var timer = setInterval(load, 5000);
|
||||
return function () { clearInterval(timer); };
|
||||
}, []);
|
||||
|
||||
function action(kind, name) {
|
||||
if (kind === "delete" && !window.confirm("Remove " + name + " from Ollama?")) return;
|
||||
var key = name + ":" + kind;
|
||||
setBusy(key); setNotice(null);
|
||||
var method = kind === "delete" ? "DELETE" : "POST";
|
||||
var path = kind === "delete" ? "/model" : "/" + kind;
|
||||
fetchJSON(API + path, { method: method, headers: { "Content-Type": "application/json" }, body: JSON.stringify({ name: name }) })
|
||||
.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(""); });
|
||||
}
|
||||
|
||||
var models = data ? (tab === "installed" ? data.models || [] : tab === "popular" ? data.popular || [] : data.catalog || []) : [];
|
||||
var needle = query.toLowerCase().trim();
|
||||
if (needle) models = models.filter(function (model) { return (model.name + " " + model.family + " " + (model.strengths || []).join(" ") + " " + (model.capabilities || []).join(" ")).toLowerCase().indexOf(needle) >= 0; });
|
||||
var jobs = data && data.jobs ? data.jobs.filter(function (job) { return job.state === "running"; }) : [];
|
||||
|
||||
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, 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")
|
||||
)
|
||||
),
|
||||
notice && h("div", { className: "ollama-notice " + (notice.error ? "error" : "ok") }, notice.error || notice.ok),
|
||||
h("section", { className: "ollama-toolbar" },
|
||||
h("div", { className: "ollama-tabs" },
|
||||
h(Button, { className: tab === "installed" ? "selected" : "", onClick: function () { setTab("installed"); } }, "Installed (" + ((data && data.models) || []).length + ")"),
|
||||
h(Button, { className: tab === "popular" ? "selected" : "", onClick: function () { setTab("popular"); } }, "Top 20 popular (" + ((data && data.popular) || []).length + ")"),
|
||||
h(Button, { className: tab === "catalog" ? "selected" : "", onClick: function () { setTab("catalog"); } }, "Available downloads (" + ((data && data.catalog) || []).length + ")")
|
||||
),
|
||||
h("input", { className: "ollama-search", value: query, placeholder: "Search models, capabilities, or strengths…", onChange: function (event) { setQuery(event.target.value); } })
|
||||
),
|
||||
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 === "popular" && h("p", { className: "ollama-popular-note" }, "Popular is limited to models with known size and RAM estimates at or below 30 GiB. 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), h("span", null, job.percent == null ? job.status : job.percent + "%")); })),
|
||||
loading && h(Empty, null, "Loading local Ollama inventory…"),
|
||||
!loading && !models.length && h(Empty, null, tab === "installed" ? "No local models found." : tab === "popular" ? "No popular catalog entries available." : "No catalog entries available. Try Refresh catalog."),
|
||||
h("section", { className: "ollama-grid" }, models.map(function (model) { return h(ModelCard, { key: model.name, model: model, installed: tab === "installed" || !!model.installed, busy: busy, action: action }); }))
|
||||
);
|
||||
}
|
||||
registry.register("ollama-manager", Page);
|
||||
})();
|
||||
Vendored
+3
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user