AkurAI Build
Menu

Public repository / 439f0dcabf5cdf2b120c7724be895cf1df30507f

AkurAI-Framework / site/frontend/ai/components/progress/plan.js

Latest change d49e9bb8beb5dc669fe029e034a13342d358a4f3 - feat(ai): tool-ui Progress + Confirmation components (AB4-B) by Ólafur Búi Ólafsson

// Plan component — tool-ui Progress category.
// Payload: { title?, steps: [{ label, status: 'pending'|'active'|'done'|'failed' }] }

const ICONS = { done: '✓', active: '▶', pending: '○', failed: '✗' };
const STATUS_CLASS = { done: 'is-done', active: 'is-current', pending: '', failed: 'tc-progress-failed' };

export const def = {
  name: 'plan',
  category: 'Progress',
  title: 'Plan',

  match(payload) {
    return Array.isArray(payload?.steps);
  },

  render(payload) {
    const root = document.createElement('div');
    root.className = 'card tc-progress-plan';

    if (payload.title) {
      const h = document.createElement('h3');
      h.textContent = payload.title;
      root.appendChild(h);
    }

    const list = document.createElement('ul');
    list.className = 'tc-progress-checklist';
    list.style.cssText = 'list-style:none;margin:12px 0 0;padding:0;display:flex;flex-direction:column;gap:8px;';

    for (const step of payload.steps) {
      const status = step.status ?? 'pending';
      const li = document.createElement('li');
      li.style.cssText = 'display:flex;align-items:center;gap:10px;font-size:14px;';

      const icon = document.createElement('span');
      icon.setAttribute('aria-hidden', 'true');
      icon.style.cssText = 'display:inline-flex;align-items:center;justify-content:center;width:22px;height:22px;border-radius:50%;font-size:12px;font-weight:700;flex-shrink:0;';

      if (status === 'done') {
        icon.style.background = 'color-mix(in srgb, var(--ok) 20%, var(--panel))';
        icon.style.color = 'var(--ok)';
        icon.style.border = '1.5px solid var(--ok)';
      } else if (status === 'active') {
        icon.style.background = 'color-mix(in srgb, var(--accent) 22%, var(--panel))';
        icon.style.color = 'var(--fg)';
        icon.style.border = '1.5px solid var(--accent)';
        icon.style.boxShadow = '0 0 0 3px color-mix(in srgb, var(--accent) 20%, transparent)';
      } else if (status === 'failed') {
        icon.style.background = 'color-mix(in srgb, var(--danger) 18%, var(--panel))';
        icon.style.color = 'var(--danger)';
        icon.style.border = '1.5px solid var(--danger)';
      } else {
        icon.style.background = 'var(--panel-2)';
        icon.style.color = 'var(--muted)';
        icon.style.border = '1.5px solid var(--border-2)';
      }

      icon.textContent = ICONS[status] ?? '○';

      const label = document.createElement('span');
      label.textContent = step.label ?? '';
      if (status === 'done') label.style.color = 'var(--muted)';
      else if (status === 'active') label.style.fontWeight = '600';
      else if (status === 'failed') label.style.color = 'var(--danger)';
      else label.style.color = 'var(--muted)';

      li.appendChild(icon);
      li.appendChild(label);

      const cls = STATUS_CLASS[status];
      if (cls) li.className = cls;

      list.appendChild(li);
    }

    root.appendChild(list);
    return root;
  },

  example: {
    title: 'Deploy pipeline',
    steps: [
      { label: 'Build binary', status: 'done' },
      { label: 'Run tests', status: 'done' },
      { label: 'Upload artifact', status: 'active' },
      { label: 'Restart service', status: 'pending' },
      { label: 'Smoke check', status: 'pending' },
    ],
  },
};