AkurAI Build
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'],
  },
};