Run the title catch-up in its own YouTube lane

This commit is contained in:
Fisto
2026-09-17 14:25:08 +02:00
parent 8befc05591
commit 9481286169
16 changed files with 495 additions and 82 deletions
+11
View File
@@ -791,3 +791,14 @@
.vk-chip-strike {
text-decoration: line-through;
}
/* The YouTube title catch-up lane, inside the Bot tab backfill section. */
.vk-bot-lane {
border: 1px dashed var(--color-neutral-700);
border-radius: 5px;
padding: 6px 8px;
margin-bottom: 8px;
display: flex;
flex-direction: column;
gap: 4px;
}
+63 -3
View File
@@ -40,6 +40,7 @@
loadError: false,
pageSize: 60,
backfill: null,
titles: null,
preferences: null,
/** Scrapes that finished while this page was open, until the tab is reloaded. */
completed: new Set()
@@ -75,6 +76,7 @@
</span>
</div>
<div id="backfill-summary" class="vk-bot-summary"></div>
<div id="title-lane" class="vk-bot-lane"></div>
<div id="backfill-jobs" class="vk-bot-jobs"></div>
</div>
@@ -101,6 +103,7 @@
const botLimited = overlay.querySelector("#bot-limited");
const backfillSummary = overlay.querySelector("#backfill-summary");
const backfillJobs = overlay.querySelector("#backfill-jobs");
const titleLane = overlay.querySelector("#title-lane");
const tunablesHost = overlay.querySelector("#bot-tunables");
const botErrors = overlay.querySelector("#bot-errors");
@@ -790,7 +793,6 @@
"vk-bot-job-member",
`${member.scrapeId} \u00b7 ${member.state} \u00b7 ${member.mode} \u00b7 ` +
`${member.backend} \u00b7 ${member.pending} pending` +
(member.pendingTitles ? ` + ${member.pendingTitles} titles` : "") +
(kinds ? ` (${kinds})` : "")
)
);
@@ -826,6 +828,57 @@
}
}
function renderTitleLane(status) {
if (!status) return;
titleLane.replaceChildren();
const head = el("div", "vk-bot-job-head");
head.append(el("span", "vk-bot-job-title", "Titles (YouTube)"));
head.append(el("span", `vk-bot-state vk-bot-state-${status.state}`, status.state));
const parts = [`${status.pending} pending`, `${status.resolved} titles`];
if (status.failed) parts.push(`${status.failed} failed`);
if (status.perMinute) parts.push(`${status.perMinute}/min`);
if (status.pending > 0) parts.push(`ETA ${formatEta(status.etaMs)}`);
if (status.currentScrapeId) parts.push(`on ${channelLabel(status.currentScrapeId)}`);
head.append(el("span", "vk-bot-job-meta", parts.join(" \u00b7 ")));
titleLane.append(head);
const actions = el("div", "vk-bot-job-actions");
if (status.state === "running") {
actions.append(button("Pause", () => void titleAction("pause")));
} else if (status.pending > 0) {
actions.append(button("Start", () => void titleAction("start")));
}
titleLane.append(actions);
const note = el(
"div",
"vk-bot-job-member",
"One-time catch-up for rows indexed before titles were stored; talks to YouTube only, " +
"100 per batch, 10 s between batches."
);
titleLane.append(note);
}
async function titleAction(action) {
try {
state.titles = await window.VK.api.post(`/api/media/titles/${action}`, {});
renderTitleLane(state.titles);
} catch (error) {
setStatus(`titles ${action} failed: ${error.message}`);
}
}
async function refreshTitles() {
try {
state.titles = await window.VK.api.get("/api/media/titles");
} catch {
state.titles = null;
}
renderTitleLane(state.titles);
}
async function refreshBackfill() {
try {
state.backfill = await window.VK.api.get("/api/media/backfill");
@@ -1765,6 +1818,7 @@
: "slopbot is disabled or not logged in (VESKSLOP_SLOPBOT_ENABLED=1 and a token)";
if (!state.preferences) void refreshPreferences();
void refreshBackfill();
void refreshTitles();
void loadScrapes().then(renderScrapes);
void refreshErrors();
} else {
@@ -1782,7 +1836,10 @@
state.completed.clear();
void loadScrapes();
if (state.view === "media") void loadMedia(true);
if (state.view === "bot") void refreshBackfill();
if (state.view === "bot") {
void refreshBackfill();
void refreshTitles();
}
});
overlay.querySelector("#backfill-start").addEventListener("click", () => void backfillAction("start"));
@@ -1812,7 +1869,10 @@
// Backfill progress while the Bot tab is open.
setInterval(() => {
if (state.view === "bot") void refreshBackfill();
if (state.view === "bot") {
void refreshBackfill();
void refreshTitles();
}
}, 3000);
// Infinite scroll: the buffer entering the viewport starts the next page.