n2r-dev / static /models.js
cacodex's picture
Upload 13 files
5217381 verified
const catalogSummary = document.getElementById("catalog-summary");
const providerFilterBar = document.getElementById("provider-filter-bar");
const providerGrid = document.getElementById("provider-grid");
const catalogUpdated = document.getElementById("catalog-updated");
const catalogEmpty = document.getElementById("catalog-empty");
let catalogState = {
providers: [],
activeProvider: "all",
};
const DISPLAY_TIMEZONE = "Asia/Shanghai";
const dateTimeFormatter = new Intl.DateTimeFormat("zh-CN", {
month: "2-digit",
day: "2-digit",
hour: "2-digit",
minute: "2-digit",
hour12: false,
timeZone: DISPLAY_TIMEZONE,
});
function formatDateTime(value) {
if (!value) return "--";
const date = new Date(value);
if (Number.isNaN(date.getTime())) return "--";
return dateTimeFormatter.format(date);
}
function createSummaryCard(label, value, detail = "") {
const card = document.createElement("article");
card.className = "summary-card";
card.innerHTML = `<span>${label}</span><strong>${value}</strong><p>${detail}</p>`;
return card;
}
function renderSummary(data) {
catalogSummary.innerHTML = "";
catalogSummary.appendChild(createSummaryCard("官方模型总数", data.total_models ?? 0, "来自 NVIDIA 官方 /v1/models"));
catalogSummary.appendChild(createSummaryCard("提供商数量", data.providers?.length ?? 0, "按模型 ID 中的提供商前缀自动归类"));
}
function renderFilterBar() {
providerFilterBar.innerHTML = "";
const options = [{ provider: "all", count: catalogState.providers.reduce((sum, item) => sum + (item.count || 0), 0), label: "全部" }].concat(
catalogState.providers.map((group) => ({ provider: group.provider, count: group.count, label: group.provider }))
);
options.forEach((option) => {
const button = document.createElement("button");
button.className = `provider-filter-btn ${catalogState.activeProvider === option.provider ? "active" : ""}`;
button.type = "button";
button.textContent = `${option.label} (${option.count})`;
button.addEventListener("click", () => {
catalogState.activeProvider = option.provider;
renderFilterBar();
renderProviders();
});
providerFilterBar.appendChild(button);
});
}
function renderProviders() {
providerGrid.innerHTML = "";
const filtered = catalogState.activeProvider === "all"
? catalogState.providers
: catalogState.providers.filter((group) => group.provider === catalogState.activeProvider);
if (filtered.length === 0) {
catalogEmpty.textContent = "当前筛选条件下没有可展示的模型。";
return;
}
catalogEmpty.textContent = "";
filtered.forEach((group) => {
const card = document.createElement("article");
card.className = "provider-card provider-card-fixed";
card.innerHTML = `
<div class="provider-card-head">
<div>
<h3>${group.provider}</h3>
<p>${group.count} 个模型</p>
</div>
</div>
`;
const body = document.createElement("div");
body.className = "provider-card-body";
const list = document.createElement("div");
list.className = "provider-model-list";
(group.models || []).forEach((model) => {
const chip = document.createElement("div");
chip.className = "provider-model-chip";
chip.title = model.id;
chip.textContent = model.id;
list.appendChild(chip);
});
body.appendChild(list);
card.appendChild(body);
providerGrid.appendChild(card);
});
}
async function loadCatalog() {
const response = await fetch("/api/catalog", { headers: { Accept: "application/json" } });
if (!response.ok) {
throw new Error("模型列表加载失败");
}
const payload = await response.json();
catalogUpdated.textContent = formatDateTime(payload.synced_at || payload.generated_at);
catalogState.providers = payload.providers || [];
renderSummary(payload);
renderFilterBar();
renderProviders();
}
window.addEventListener("DOMContentLoaded", async () => {
try {
await loadCatalog();
} catch (error) {
catalogEmpty.textContent = error.message;
providerGrid.innerHTML = "";
providerFilterBar.innerHTML = "";
}
});