AkurAI Build
Menu

Public repository / 439f0dcabf5cdf2b120c7724be895cf1df30507f

AkurAI-Framework / site/frontend/ai/components/input/parameter_slider.js

Latest change 63ae119f1bdd5ddffd3f9a2b3b3216aa9d21449b - feat(ai): Input component batch — option_list, parameter_slider, preferences_panel, question_flow by Ólafur Búi Ólafsson

/**
 * Parameter Slider — tool-UI input component.
 * Payload: { label, min, max, step?, value, unit? }
 * Receipt: { component: 'parameter_slider', value: number }
 */

function emit(el, detail) {
  el.dispatchEvent(new CustomEvent('toolui:input', { bubbles: true, detail }));
}

export const def = {
  name: 'parameter_slider',
  category: 'Input',
  title: 'Parameter Slider',

  match(p) {
    return (
      p != null &&
      typeof p.min === 'number' &&
      typeof p.max === 'number' &&
      typeof p.value === 'number'
    );
  },

  render(p, ctx) {
    const { label = '', min, max, step = 1, value, unit = '' } = p;
    let current = value;

    function fmt(v) {
      return unit ? `${v} ${unit}` : String(v);
    }

    const root = document.createElement('div');
    root.className = 'card';
    root.style.cssText = 'display:grid;gap:14px;max-width:420px;';

    // Header row: label + live value badge
    const head = document.createElement('div');
    head.style.cssText =
      'display:flex;align-items:center;justify-content:space-between;gap:12px;';

    const labelEl = document.createElement('span');
    labelEl.style.fontWeight = '600';
    labelEl.textContent = label;
    head.appendChild(labelEl);

    const display = document.createElement('span');
    display.className = 'badge badge-ok';
    display.style.fontFamily = 'var(--mono)';
    display.textContent = fmt(value);
    head.appendChild(display);
    root.appendChild(head);

    // Slider
    const slider = document.createElement('input');
    slider.type = 'range';
    slider.min = String(min);
    slider.max = String(max);
    slider.step = String(step);
    slider.value = String(value);
    slider.style.width = '100%';

    slider.addEventListener('input', () => {
      current = Number(slider.value);
      display.textContent = fmt(current);
    });
    root.appendChild(slider);

    // Min / max range labels
    const rangeRow = document.createElement('div');
    rangeRow.style.cssText =
      'display:flex;justify-content:space-between;font-size:12px;' +
      'color:var(--muted);margin-top:-8px;';
    const minLabel = document.createElement('span');
    minLabel.textContent = fmt(min);
    const maxLabel = document.createElement('span');
    maxLabel.textContent = fmt(max);
    rangeRow.appendChild(minLabel);
    rangeRow.appendChild(maxLabel);
    root.appendChild(rangeRow);

    // Apply button
    const btn = document.createElement('button');
    btn.className = 'btn btn-primary btn-sm';
    btn.textContent = 'Apply';

    btn.addEventListener('click', () => {
      const receipt = { component: 'parameter_slider', value: current };
      if (ctx && typeof ctx.sendReceipt === 'function') ctx.sendReceipt(receipt);
      emit(root, receipt);
      btn.disabled = true;
      btn.textContent = 'Applied ✓';
    });

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

  example: {
    label: 'Temperature',
    min: 0,
    max: 2,
    step: 0.1,
    value: 0.7,
    unit: ''
  }
};