A Phoenix LiveView renderer for A2UI surfaces, built on
a2ui_core. It renders the basic catalog (protocols v0.9, v0.9.1 and v1.0)
as function components, keeps two-way bindings in the surface's data model,
and turns user actions into renderer→agent messages.
Installation
def deps do
[
{:a2ui_phoenix, "~> 0.1.0"}
]
endUsage
defmodule MyAppWeb.AgentLive do
use MyAppWeb, :live_view
alias A2UIPhoenix.{Live, Session}
def mount(_params, _session, socket) do
# Check every component against its catalog; relaxed validation still
# accepts a surface whose components arrive over several messages.
validation = A2UICore.Validation.Config.relaxed()
session = Session.new([A2UICore.Catalog.Basic.catalog("v1.0")], validation: validation)
{:ok, Live.init(socket, session, on_send: &send_to_agent/2)}
end
# Messages from the agent, however they arrive (SSE, A2A, a process).
def handle_info({:agent, payload}, socket) do
{_result, socket} = Live.handle_message(socket, payload)
{:noreply, socket}
end
def render(assigns) do
~H"""
<.live_component
:for={surface <- Session.surfaces(@a2ui)}
module={A2UIPhoenix.Surface}
id={"surface-" <> surface.id}
session={@a2ui}
surface_id={surface.id}
/>
"""
end
# Messages for the agent (`action`, `error`, `callAgentFunction`,
# `rendererFunctionResponse`), here broadcast to the process that talks to it.
defp send_to_agent(message, socket) do
Phoenix.PubSub.broadcast(MyApp.PubSub, "a2ui:renderer", {:renderer, self(), message})
socket
end
endAdd priv/static/a2ui_phoenix.js to the page's scripts, so that openUrl actions
open their URL, and priv/static/a2ui_phoenix.css, the components' design
system: light and dark, themed by --a2ui-* custom properties (or replaced
by your own styles for the a2ui-* classes). The script also holds the hook of the MCP
catalog's McpApp component (hooks: {...window.A2UIPhoenix.hooks}), whose
sandbox proxy page, priv/static/a2ui_mcp_sandbox.html, the endpoint serves.
Rendering surfaces shows how to load them,
and covers theming, agent functions and the MCP catalog.
A catalog of your own brings its components as a map:
<.live_component module={A2UIPhoenix.Surface} components={%{"Chart" => &chart/1}} ... />Each component receives @ctx, @node, @props (bindings resolved),
@a11y and @attrs; see A2UIPhoenix.Components.
Streaming to other renderers
A2UIPhoenix.Plug.SSE streams A2UI messages to a renderer outside LiveView,
such as a web renderer in the browser, as server-sent events, one message per
event:
# In a controller:
def events(conn, params), do: A2UIPhoenix.Plug.SSE.stream(conn, MyAgent.messages(params))
# Or mounted in a router; for each request the plug calls MyAgent.messages(conn):
forward "/a2ui/events", A2UIPhoenix.Plug.SSE, source: {MyAgent, :messages, []}License
Apache-2.0