Files
brk/website_next/ask/timing.js
T
2026-07-22 16:50:32 +02:00

69 lines
1.7 KiB
JavaScript

/**
* @typedef {Object} ResponseStep
* @property {string} label
* @property {number} [elapsedMs]
*/
/** @param {number} elapsedMs */
export function formatDuration(elapsedMs) {
if (elapsedMs < 100) return "<0.1s";
if (elapsedMs < 60_000) return `${(elapsedMs / 1_000).toFixed(1)}s`;
const minutes = Math.floor(elapsedMs / 60_000);
const seconds = Math.floor((elapsedMs % 60_000) / 1_000);
return `${minutes}m ${seconds}s`;
}
/** @param {string} label */
function normalizeLabel(label) {
return label
.replace(/\s*·.*$/, "")
.replace(/(?:\.\.\.|…)$/, "")
.trim();
}
/** @param {(steps: ResponseStep[]) => void} onUpdate */
export function createResponseTimer(onUpdate) {
const startedAt = performance.now();
/** @type {{ label: string, startedAt: number } | undefined} */
let active;
/** @type {{ label: string, elapsedMs: number }[]} */
const completed = [];
function publish() {
onUpdate(active
? [...completed, { label: active.label }]
: [...completed]);
}
function completeActive() {
if (!active) return;
completed.push({
label: active.label,
elapsedMs: Math.round(performance.now() - active.startedAt),
});
active = undefined;
}
return {
/** @param {string} nextLabel */
set(nextLabel) {
const label = normalizeLabel(nextLabel);
if (label === active?.label) return;
completeActive();
if (label) active = { label, startedAt: performance.now() };
publish();
},
finish() {
completeActive();
publish();
return {
elapsedMs: Math.round(performance.now() - startedAt),
steps: [...completed],
};
},
};
}