AkurAI Build
Menu

Public repository / 439f0dcabf5cdf2b120c7724be895cf1df30507f

AkurAI-Framework / site/frontend/ai/components/display/citation.js

Latest change 88d669a6b3c5464f8a947a66be0437a90424470f - feat(ai): add Display components — citation, link_preview, stats_display, terminal, weather_widget by Ólafur Búi Ólafsson

/**
 * Citation — compact cited-source card.
 * Payload: { title, url, snippet, favicon? }
 */

function esc(str) {
  const d = document.createElement("div");
  d.textContent = String(str ?? "");
  return d.innerHTML;
}

function el(tag, cls, children) {
  const node = document.createElement(tag);
  if (cls) node.className = cls;
  if (Array.isArray(children)) children.forEach(c => c && node.appendChild(c));
  return node;
}

function txt(content) {
  return document.createTextNode(content);
}

export const def = {
  name: "citation",
  category: "Display",
  title: "Citation",

  match(payload) {
    return (
      payload !== null &&
      typeof payload === "object" &&
      typeof payload.title === "string" &&
      typeof payload.url === "string" &&
      typeof payload.snippet === "string"
    );
  },

  render(payload) {
    const { title, url, snippet, favicon } = payload;

    // Derive display domain from url (best-effort, no throws)
    let domain = "";
    try { domain = new URL(url).hostname.replace(/^www\./, ""); } catch (_) { domain = url; }

    const card = el("div", "card tc-display-citation");

    // Header row: favicon + domain + external link icon
    const header = el("div", "tc-display-citation__header");

    if (favicon) {
      const img = document.createElement("img");
      img.className = "tc-display-citation__favicon";
      img.width = 16;
      img.height = 16;
      // favicon is a URL — XSS: we only set src, not innerHTML
      img.src = favicon;
      img.alt = "";
      header.appendChild(img);
    } else {
      // Fallback: first letter of domain as small badge
      const fb = el("span", "tc-display-citation__favicon-fallback");
      fb.textContent = domain.charAt(0).toUpperCase();
      header.appendChild(fb);
    }

    const domainSpan = el("span", "tc-display-citation__domain muted");
    domainSpan.textContent = domain;
    header.appendChild(domainSpan);

    card.appendChild(header);

    // Title as a link
    const titleLink = document.createElement("a");
    titleLink.className = "tc-display-citation__title";
    titleLink.href = url;
    titleLink.rel = "noopener noreferrer";
    titleLink.target = "_blank";
    titleLink.textContent = title;
    card.appendChild(titleLink);

    // Snippet
    const snip = el("p", "tc-display-citation__snippet muted");
    snip.textContent = snippet;
    card.appendChild(snip);

    return card;
  },

  example: {
    title: "From-scratch SQL engine — AkurAI-Framework",
    url: "https://github.com/olibuijr/AkurAI-Framework",
    snippet:
      "A pure-Rust zero-runtime-dep full-stack framework with a built-in SQL engine, native ESM frontend, and semantic search.",
    favicon: "https://github.com/favicon.ico",
  },
};