How to Build a Real‑Time AI Agent UI with SSE and Session Replay
This article walks through the design and implementation of a visually striking, real‑time front‑end for an AI agent, explaining why Server‑Sent Events were chosen, showing Go and JavaScript code for streaming, and detailing a session‑replay feature that lets users review every agent decision step.
The front‑end of agent-web is crafted to give users a modern, tech‑savvy visual experience, starting with a dark theme highlighted by neon blue/purple accents and a typewriter‑style streaming output powered by Server‑Sent Events (SSE).
Why SSE?
Unidirectional data flow : The agent’s workflow follows a "server thinks → pushes logs/results → client displays" pattern that matches SSE’s one‑way streaming model.
HTTP compatibility : SSE works over a long‑lived HTTP request, avoiding the protocol upgrade required by WebSocket and thus passing firewalls and proxies more easily.
Automatic reconnection : Browsers’ EventSource API automatically retries connections when they drop, eliminating custom reconnection logic.
Backend implementation ( cmd/agent-web/main.go )
// 1. Set SSE headers
w.Header().Set("Content-Type", "text/event-stream")
w.Header().Set("Cache-Control", "no-cache")
w.Header().Set("Connection", "keep-alive")
w.Header().Set("X-Accel-Buffering", "no") // disable Nginx buffering
flusher, ok := w.(http.Flusher)
if !ok {
http.Error(w, "Streaming unsupported", http.StatusInternalServerError)
return
}
// 2. Listen to event channel and push
handler := session.Handler
for {
select {
case event := <-handler.eventChan:
data, err := json.Marshal(event)
if err != nil { continue }
fmt.Fprintf(w, "data: %s
", data)
flusher.Flush()
case <-r.Context().Done():
return
}
}Front‑end handling ( cmd/agent-web/ui/app.js )
function connectSSE() {
// ... close old connection ...
eventSource = new EventSource(`/events?session_id=${sessionId}`);
eventSource.onmessage = (event) => {
const data = JSON.parse(event.data);
handleEvent(data);
};
}
function handleEvent(data) {
switch (data.type) {
case 'log':
handleLog(data.content);
break;
case 'plan_review':
showPlanReview(data.plan);
break;
case 'response':
const tabId = createReportTab(data.content);
activateTab(tabId);
if (data.ppt) renderPPTButton(data.ppt);
if (data.podcast) renderPodcastButton(data.podcast);
break;
case 'done':
addLog('success', '任务已完成!');
setLoading(false);
break;
}
}This event‑driven architecture keeps the front‑end lightweight, delegating complex state management to the back‑end.
Session Replay – Recording, Storing, and Replaying Agent Thoughts
The replay feature captures every Event generated by WebInteractionHandler, serializes the sequence to a JSON file under sessions/, and later replays it on demand.
Recording : All events are kept in memory during a session.
Storing : On session termination, the event list is written to a JSON file.
Playback : When a user selects a past session, the front‑end loads the JSON.
Simulation : The UI replays events using setTimeout to mimic original timing, invoking the same handleEvent logic so logs appear line‑by‑line as if the agent were running again.
// Pseudo‑code for front‑end replay
events.forEach((event, index) => {
setTimeout(() => {
handleEvent(event); // reuse real‑time handler
}, index * 100); // simple delay; could use event.timestamp
});This "time‑capsule" replay helps developers debug decision paths and users understand the agent’s reasoning.
Dynamic Interaction Components
4.1 Plan Review – Human‑in‑the‑Loop
Visual display : The JSON plan is rendered as a clear task list.
User intervention : Users can approve the plan or edit it (e.g., "remove step 3, add competitor analysis").
Dynamic adjustment : If edited, the back‑end PlanningAgent receives the feedback, regenerates the plan, and repeats until the user is satisfied.
4.2 Multimodal Display
Tab system : Each generated report opens in a new tab, allowing independent navigation between "Terminal", "Report 1", "Report 2", etc.
PPT preview : When a PPT is produced, a purple "View PPT" button appears, opening a full‑screen HTML slide deck.
Podcast script : If a podcast script is generated, a blue "View Podcast" button opens a tab with a dialogue script and provides a .txt download.
These components keep complex agent output organized, giving users a seamless, all‑in‑one interface for reading and interacting with AI results.
BirdNest Tech Talk
Author of the rpcx microservice framework, original book author, and chair of Baidu's Go CMC committee.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
