Skip to content

Commit e819866

Browse files
gguf-viewer: split the SPA logic into focused modules for shared helpers and each page
1 parent 4306e29 commit e819866

File tree

10 files changed

+3963
-3957
lines changed

10 files changed

+3963
-3957
lines changed

tools/gguf-viewer/public/app.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
window.addEventListener("resize", () => {
2+
syncHeatmapToViewport(true);
3+
const statisticsVisible = normalizePageId(activePage || DEFAULT_PAGE) === "statistics";
4+
syncHistogramToViewport(statisticsVisible && !!heatmapState.tensor);
5+
});
6+
7+
syncHeatmapToViewport(false);
8+
resetViewer();
9+
10+
(async function init() {
11+
await loadModelList();
12+
if (currentModelPath) {
13+
await refreshAll();
14+
} else {
15+
resetViewer();
16+
}
17+
urlSyncReady = true;
18+
syncFullUrlState({ force: true });
19+
})();
Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
async function requestModelSelection(path) {
2+
if (!path) {
3+
throw new Error("Model path is required");
4+
}
5+
const response = await fetchJSON(`api/models/select?model=${encodeURIComponent(path)}`, { method: "POST" });
6+
const selected = typeof response?.selected === "string" && response.selected.length > 0
7+
? response.selected
8+
: path;
9+
backendSelectedModel = selected;
10+
return selected;
11+
}
12+
13+
function setCurrentModel(path, options = {}) {
14+
const { updateBrowser = true, updateUrl = true } = options;
15+
const normalized = typeof path === "string" && path.length > 0 ? path : null;
16+
currentModelPath = normalized;
17+
if (!normalized) {
18+
backendSelectedModel = null;
19+
}
20+
const items = Array.isArray(modelBrowserState.items) ? modelBrowserState.items : [];
21+
const updatedItems = items.map((item) => ({ ...item, selected: !!normalized && item.path === normalized }));
22+
modelBrowserState = { ...modelBrowserState, selected: normalized, items: updatedItems };
23+
if (updateBrowser) {
24+
renderModelBrowser();
25+
}
26+
if (updateUrl) {
27+
updateModelUrlState();
28+
}
29+
}
30+
31+
function renderModelBrowser() {
32+
if (!modelBrowserContainer) {
33+
return;
34+
}
35+
36+
modelBrowserContainer.innerHTML = "";
37+
38+
if (modelBrowserRoot) {
39+
modelBrowserRoot.innerHTML = "";
40+
const hasRoot = typeof modelBrowserState.root === "string" && modelBrowserState.root.length > 0;
41+
modelBrowserRoot.hidden = !hasRoot;
42+
if (hasRoot) {
43+
const code = document.createElement("code");
44+
code.textContent = modelBrowserState.root;
45+
modelBrowserRoot.appendChild(code);
46+
}
47+
}
48+
49+
const items = Array.isArray(modelBrowserState.items) ? modelBrowserState.items : [];
50+
if (items.length === 0) {
51+
renderEmptyNote(modelBrowserContainer, BROWSER_EMPTY_MESSAGE);
52+
return;
53+
}
54+
55+
const list = document.createElement("ul");
56+
list.className = "model-list";
57+
58+
items.forEach((item) => {
59+
const li = document.createElement("li");
60+
li.className = "model-item";
61+
62+
const button = document.createElement("button");
63+
button.type = "button";
64+
button.className = "model-button";
65+
const isSelected = !!item.selected;
66+
button.dataset.selected = isSelected ? "true" : "false";
67+
button.disabled = selectingModel;
68+
69+
const pathLabel = document.createElement("span");
70+
pathLabel.className = "model-path-entry";
71+
pathLabel.textContent = item.path;
72+
73+
const size = document.createElement("span");
74+
size.className = "model-meta";
75+
size.textContent = Number.isFinite(item.size) ? formatBytes(item.size) : "N/A";
76+
77+
button.appendChild(pathLabel);
78+
button.appendChild(size);
79+
80+
button.addEventListener("click", () => {
81+
if (!selectingModel) {
82+
selectModel(item.path);
83+
}
84+
});
85+
86+
li.appendChild(button);
87+
list.appendChild(li);
88+
});
89+
90+
modelBrowserContainer.appendChild(list);
91+
}
92+
93+
async function loadModelList() {
94+
if (modelBrowserContainer) {
95+
renderEmptyNote(modelBrowserContainer, LOADING_MESSAGE);
96+
}
97+
98+
try {
99+
const data = await fetchJSON(withModel("api/models"));
100+
const rawItems = Array.isArray(data?.items) ? data.items : [];
101+
const rootPath = typeof data?.root === "string" ? data.root : "";
102+
let selected = typeof data?.selected === "string" && data.selected.length > 0 ? data.selected : null;
103+
if (currentModelPath && currentModelPath.length > 0) {
104+
selected = currentModelPath;
105+
}
106+
const items = rawItems.map((item) => ({
107+
path: item.path,
108+
name: item.name,
109+
size: item.size,
110+
}));
111+
if (selected && !items.some((item) => item.path === selected)) {
112+
selected = null;
113+
}
114+
modelBrowserState = {
115+
root: rootPath,
116+
items: items.map((item) => ({ ...item, selected: !!selected && item.path === selected })),
117+
selected,
118+
};
119+
currentModelPath = selected;
120+
updateModelUrlState();
121+
renderModelBrowser();
122+
} catch (err) {
123+
modelBrowserState = { root: "", items: [], selected: null };
124+
if (modelBrowserRoot) {
125+
modelBrowserRoot.textContent = "";
126+
modelBrowserRoot.hidden = true;
127+
}
128+
if (modelBrowserContainer) {
129+
renderEmptyNote(modelBrowserContainer, err.message);
130+
}
131+
}
132+
}
133+
134+
async function selectModel(path) {
135+
if (!path || selectingModel) {
136+
return;
137+
}
138+
if (currentModelPath && currentModelPath === path) {
139+
return;
140+
}
141+
142+
selectingModel = true;
143+
let errorMessage = null;
144+
try {
145+
const selectedPath = await requestModelSelection(path);
146+
setCurrentModel(selectedPath, { updateBrowser: false });
147+
pendingHeatmapState.applied = true;
148+
pendingHeatmapState.tensor = null;
149+
pendingHeatmapState.slice = null;
150+
pendingHeatmapState.min = null;
151+
pendingHeatmapState.max = null;
152+
pendingHeatmapState.x = null;
153+
pendingHeatmapState.y = null;
154+
await refreshAll({ ensureSelected: false });
155+
} catch (err) {
156+
errorMessage = err.status === 409 ? NO_MODEL_MESSAGE : err.message;
157+
} finally {
158+
selectingModel = false;
159+
renderModelBrowser();
160+
if (errorMessage && modelBrowserContainer) {
161+
const note = document.createElement("div");
162+
note.className = "empty-note";
163+
note.textContent = errorMessage;
164+
modelBrowserContainer.appendChild(note);
165+
}
166+
}
167+
}

0 commit comments

Comments
 (0)