streaming-ui-script · external data ← Back to docs
Live demo · public API

Bring a real API into the UI

Tools aren't limited to local data. This demo registers a single github_repos tool that hits GitHub's public REST API. Whenever the user types a different handle, the $user binding changes, the Query re-fires, and the table + chart redraw automatically. No extra plumbing — same flow you'd use to wire up a real backend.

GitHub repository explorer

Type any GitHub username or organisation. We fetch the top-starred public repos via the search/repositories endpoint, then render them as a table, a stars-by-repo chart, and a quick KPI summary.

$user = "thesysdev"
data = Query("github_repos", {user: $user}, {rows: [], total: 0, status: "idle"})

userField = FormControl("GitHub user", Input("user", "vercel, anthropic, thesysdev…", "text", null, $user))

errBanner = Callout("error", "Couldn't load repos", "Check the username and try again. The GitHub API may also be rate-limiting unauthenticated requests.")
loading = TextContent("Loading repositories…", "small", "muted")
emptyMsg = TextContent("No public repositories found.", "small", "muted")

starsChart = BarChart(data.rows.name, [Series("Stars", data.rows.stars)])
forksChart = BarChart(data.rows.name, [Series("Forks", data.rows.forks)])

chartTabs = Tabs([
  TabItem("stars", "Stars", [starsChart]),
  TabItem("forks", "Forks", [forksChart])
])

repoTable = Table([
  Col("Repository", data.rows.name),
  Col("Description", data.rows.description),
  Col("Language", @Each(data.rows, "row", Tag(row.language, null, "sm", "info"))),
  Col("Stars", data.rows.stars, "number"),
  Col("Forks", data.rows.forks, "number"),
  Col("Open", @Each(data.rows, "row", Button("View", Action([@OpenUrl(row.url)]), "secondary", "normal", "small")))
])

stats = Stack([
  StatCard("Repos shown", "" + @Count(data.rows)),
  StatCard("Total stars", "" + @Sum(data.rows.stars), "up"),
  StatCard("Total forks", "" + @Sum(data.rows.forks), "up")
], "row", "m", "stretch", "start", true)

view = data.status == "error" ? errBanner : data.status == "loading" ? loading : @Count(data.rows) > 0 ? Stack([stats, Card([CardHeader("Top repositories"), repoTable]), Card([CardHeader("Comparison chart"), chartTabs])]) : emptyMsg

root = Stack([Card([CardHeader("GitHub explorer", "Live data via api.github.com"), userField]), view])

How it's wired

A single tool function fetches the API, normalises the response, and hands it back as the data Query result. The library handles caching, re-runs on state change, and the optimistic defaults while the request is in flight.

el.setTools({
  github_repos: async ({ user }) => {
    if (!user) return { rows: [], total: 0, status: "idle" };
    try {
      const url = `https://api.github.com/search/repositories?q=user:${encodeURIComponent(user)}&sort=stars&order=desc&per_page=8`;
      const res = await fetch(url);
      if (!res.ok) return { rows: [], total: 0, status: "error" };
      const data = await res.json();
      const rows = (data.items ?? []).map((r) => ({
        name:        r.name,
        description: r.description ?? "—",
        language:    r.language ?? "Other",
        stars:       r.stargazers_count,
        forks:       r.forks_count,
        url:         r.html_url,
      }));
      return { rows, total: data.total_count ?? rows.length, status: "ok" };
    } catch {
      return { rows: [], total: 0, status: "error" };
    }
  },
});