AkurAI Build
Menu

Public repository / 91bd2a37654909c5a3aef6ddd614991bd378ccb8

AkurAI-Framework / site/frontend/ai/runtime/suggestions.js

Latest change 22a320a8e40a1822b386e2d5fd9bcf3537282876 - feat(ai): generative-UI / tool-call runtime + primitives by Ólafur Búi Ólafsson

// suggestions.js — tappable suggestion chips that fill / submit the composer.
//
// Renders a row of chips. Tapping one emits a `suggestion:pick` CustomEvent
// (detail = { text, submit }), fills a target composer input if given, and
// optionally submits. Lets the assistant offer canned next prompts.
//
// createSuggestions(items, opts) -> {
//   element,                      // <div class="toolui-suggestions">
//   setItems(items),
// }
//   items = ["Tell me more", { text: "Summarize", submit: true }, ...]
//   opts = {
//     composer,                   // <input>/<textarea> to fill (optional)
//     form,                       // <form> to submit when submit:true (optional)
//     submit = false,             // default submit behavior for plain-string items
//     onPick(text, item),
//   }

function normalizeItem(item) {
  if (typeof item === "string") return { text: item };
  return { text: String(item.text ?? ""), submit: item.submit };
}

function createSuggestions(items, opts) {
  const o = opts || {};

  const element = document.createElement("div");
  element.className = "toolui-suggestions";
  element.style.display = "flex";
  element.style.flexWrap = "wrap";
  element.style.gap = "8px";
  element.setAttribute("role", "listbox");
  element.setAttribute("aria-label", "Suggestions");

  function pick(item) {
    const submit = item.submit ?? o.submit ?? false;
    const detail = { text: item.text, submit };

    // Fill the composer if one was provided.
    if (o.composer) {
      o.composer.value = item.text;
      o.composer.dispatchEvent(new Event("input", { bubbles: true }));
    }
    element.dispatchEvent(new CustomEvent("suggestion:pick", { detail, bubbles: true }));
    document.dispatchEvent(new CustomEvent("suggestion:pick", { detail }));
    if (typeof o.onPick === "function") o.onPick(item.text, item);
    console.log("[runtime] suggestion:pick", detail);

    // Submit the form if asked (and a form is wired up).
    if (submit && o.form && typeof o.form.requestSubmit === "function") {
      o.form.requestSubmit();
    }
  }

  function render(list) {
    element.textContent = "";
    for (const raw of list) {
      const item = normalizeItem(raw);
      if (!item.text) continue;
      const chip = document.createElement("button");
      chip.type = "button";
      chip.className = "btn btn-secondary btn-sm toolui-suggestion-chip";
      chip.setAttribute("role", "option");
      chip.textContent = item.text;
      chip.addEventListener("click", () => pick(item));
      element.appendChild(chip);
    }
  }

  render(Array.isArray(items) ? items : []);

  return {
    element,
    setItems: (next) => render(Array.isArray(next) ? next : []),
  };
}

const ToolUiSuggestions = { createSuggestions };
if (typeof window !== "undefined") window.ToolUiSuggestions = ToolUiSuggestions;

export { createSuggestions };
export default ToolUiSuggestions;