mirror of
https://github.com/Geek66666/nim4cc.git
synced 2026-10-06 13:36:08 +08:00
128 lines
4.1 KiB
JavaScript
128 lines
4.1 KiB
JavaScript
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 = "";
|
|
}
|
|
});
|