Files
devplacepy/devplacepy/static/js/components/AppIsslopRun.js
T

147 lines
5.4 KiB
JavaScript

// retoor <retoor@molodetz.nl>
import { Component } from "./Component.js";
import { Http } from "../Http.js";
import { Poller } from "../Poller.js";
const POLL_INTERVAL_MS = 3000;
const FEED_CAP = 600;
const RELOAD_DELAY_MS = 1500;
const TERMINAL_KINDS = ["done", "error"];
const KIND_ICONS = {
stage: "🧭",
log: "📋",
file: "📄",
signal: "🚩",
ai: "🤖",
image: "🖼️",
progress: "⏳",
score: "🏁",
report: "📝",
error: "❌",
done: "✅",
status: "📡",
};
const KIND_ICON_FALLBACK = "🔹";
export class AppIsslopRun extends Component {
connectedCallback() {
this._uid = this.attr("uid");
this._topic = this.attr("topic");
this._eventsUrl = this.attr("events-url", `/tools/isslop/${this._uid}/events`);
this._lastSeq = 0;
this._finished = false;
this._render();
this._status = this.querySelector("[data-isslop-status]");
this._bar = this.querySelector("[data-isslop-bar]");
this._feed = this.querySelector("[data-isslop-feed]");
this._subscribed = false;
this._subscribe();
this._poller = new Poller(() => this._poll(), POLL_INTERVAL_MS);
}
disconnectedCallback() {
if (this._poller) this._poller.stop();
if (this._subscribed && window.app && window.app.pubsub) {
window.app.pubsub.unsubscribe(this._topic, this._onFrame);
}
}
_render() {
this.innerHTML = `
<div class="isslop-live-head">
<span class="isslop-live-status" data-isslop-status role="status" aria-live="polite">Waiting for the analyzer…</span>
</div>
<div class="isslop-progress"><div class="isslop-progress-bar" data-isslop-bar></div></div>
<ul class="isslop-feed" data-isslop-feed role="log" aria-live="polite"></ul>
`;
this._loader = document.createElement("li");
this._loader.className = "isslop-feed-item isslop-feed-loader";
this._loader.setAttribute("aria-hidden", "true");
this._loader.innerHTML = '<span class="isslop-loader-cursor"></span><span class="isslop-loader-text">working</span>';
this.querySelector("[data-isslop-feed]").appendChild(this._loader);
}
_subscribe() {
const app = window.app;
if (!this._topic || !app || !app.pubsub || typeof app.pubsub.subscribe !== "function") return;
this._onFrame = (frame) => this._apply(frame);
app.pubsub.subscribe(this._topic, this._onFrame);
this._subscribed = true;
}
async _poll() {
if (this._finished) return;
let payload;
try {
payload = await Http.getJson(`${this._eventsUrl}?after=${this._lastSeq}`);
} catch (error) {
return;
}
(payload.events || []).forEach((event) => this._apply(event));
if (!this._finished && payload.status === "failed" && this._lastSeq === 0) {
this._finish(false, "Analysis failed before producing events.");
}
}
_apply(event) {
if (!event || typeof event.seq !== "number") return;
if (event.seq > 0 && event.seq <= this._lastSeq) return;
if (event.seq > 0) this._lastSeq = event.seq;
if (event.kind === "stage") this._status.textContent = event.message;
if (event.kind === "progress" && event.data && event.data.total) {
const percent = Math.min(100, Math.round((event.data.current / event.data.total) * 100));
this._bar.style.width = `${percent}%`;
}
this._append(event);
if (TERMINAL_KINDS.includes(event.kind)) {
this._finish(event.kind === "done", event.message);
}
}
_append(event) {
const item = document.createElement("li");
item.className = `isslop-feed-item isslop-feed-${event.kind}`;
const icon = document.createElement("span");
icon.className = "isslop-feed-icon";
icon.textContent = KIND_ICONS[event.kind] || KIND_ICON_FALLBACK;
const text = document.createElement("span");
text.className = "isslop-feed-text";
text.textContent = event.message;
item.append(icon, text);
const thumb = event.data ? event.data.thumb : null;
if (thumb && /^[a-f0-9]{16}\.webp$/.test(thumb)) {
const preview = document.createElement("img");
preview.className = "isslop-feed-thumb";
preview.src = `/tools/isslop/${this._uid}/media/${thumb}`;
preview.alt = "";
preview.loading = "lazy";
item.appendChild(preview);
}
this._feed.appendChild(item);
this._feed.appendChild(this._loader);
while (this._feed.children.length > FEED_CAP) {
this._feed.removeChild(this._feed.firstChild);
}
this._feed.scrollTop = this._feed.scrollHeight;
}
_finish(success, message) {
if (this._finished) return;
this._finished = true;
if (this._loader) this._loader.remove();
if (this._poller) this._poller.stop();
this._bar.style.width = "100%";
this._bar.classList.toggle("isslop-progress-failed", !success);
this._status.textContent = success
? "Analysis complete, loading the report…"
: message || "Analysis failed.";
if (success) {
window.setTimeout(() => window.location.reload(), RELOAD_DELAY_MS);
}
}
}
customElements.define("dp-isslop-run", AppIsslopRun);