Menu
Public repository / 439f0dcabf5cdf2b120c7724be895cf1df30507f
AkurAI-Framework / site/frontend/ai/components/progress/progress_tracker.js
Latest change d49e9bb8beb5dc669fe029e034a13342d358a4f3 - feat(ai): tool-ui Progress + Confirmation components (AB4-B) by Ólafur Búi Ólafsson
// Progress Tracker component — tool-ui Progress category.
// Payload: { label?, value, total? } or { label?, percent } or { label?, steps?: string[] }
export const def = {
name: 'progress_tracker',
category: 'Progress',
title: 'Progress Tracker',
match(payload) {
return payload?.value !== undefined || payload?.percent !== undefined;
},
render(payload) {
const root = document.createElement('div');
root.className = 'card tc-progress-tracker';
root.style.cssText = 'display:flex;flex-direction:column;gap:10px;';
// Compute percent
let pct;
if (payload.percent !== undefined) {
pct = Math.min(100, Math.max(0, Number(payload.percent)));
} else if (payload.total) {
pct = Math.min(100, Math.max(0, (Number(payload.value) / Number(payload.total)) * 100));
} else {
pct = null; // indeterminate
}
// Header row: label + fraction/percent
const header = document.createElement('div');
header.style.cssText = 'display:flex;align-items:center;justify-content:space-between;font-size:14px;';
if (payload.label) {
const lbl = document.createElement('span');
lbl.style.fontWeight = '600';
lbl.textContent = payload.label;
header.appendChild(lbl);
}
const stat = document.createElement('span');
stat.style.color = 'var(--muted)';
stat.style.fontSize = '13px';
if (pct !== null) {
if (payload.total !== undefined && payload.value !== undefined) {
stat.textContent = `${payload.value} / ${payload.total}`;
} else {
stat.textContent = `${Math.round(pct)}%`;
}
}
header.appendChild(stat);
if (payload.label || pct !== null) root.appendChild(header);
// Progress bar
const track = document.createElement('div');
track.className = pct === null ? 'progress progress-indeterminate' : 'progress';
if (pct !== null) track.style.setProperty('--progress-value', `${pct}%`);
const bar = document.createElement('span');
bar.className = 'progress-bar';
track.appendChild(bar);
root.appendChild(track);
// Optional step labels below
if (Array.isArray(payload.steps) && payload.steps.length) {
const steps = document.createElement('ol');
steps.className = 'steps';
steps.style.marginTop = '8px';
const doneCount = pct !== null && payload.total
? Math.round((pct / 100) * payload.steps.length)
: 0;
payload.steps.forEach((label, i) => {
const li = document.createElement('li');
const isDone = i < doneCount;
const isCurrent = i === doneCount;
li.className = 'step' + (isDone ? ' is-done' : isCurrent ? ' is-current' : '');
const marker = document.createElement('span');
marker.className = 'step-marker';
if (isDone) marker.textContent = '✓';
const lbl = document.createElement('span');
lbl.className = 'step-label';
lbl.textContent = label;
li.appendChild(marker);
li.appendChild(lbl);
steps.appendChild(li);
});
root.appendChild(steps);
}
return root;
},
example: {
label: 'Processing files',
value: 3,
total: 10,
steps: ['Parse', 'Validate', 'Transform', 'Upload'],
},
};