Vercel Logo

A Web Dashboard

You've talked to the dispatcher in a terminal and over curl. Neither is something you'd hand a customer. The front door most people expect is a web page: a text box, a conversation, a send button.

Here's the good news you earned back in 1.3: a browser chat is just another client of the same HTTP session API you already drove by hand. Nothing about the agent changes. We're not rebuilding the dispatcher for the web, we're putting a nicer door on the routes it already serves. Two pieces make that door, and one command generates both: withEve wires the agent's routes into a Next.js app, and useEveAgent drives them from React.

So this lesson is less "build a chat UI" and more "understand the one you've got, then make it the shop's." The interesting front-door work, Slack and real auth, is the next two lessons.

Outcome

The dispatcher answers in a browser dashboard, approvals included, and the page wears the Spoke & Mirror name instead of the scaffold's default.

Hands-on exercise

Add the web channel. One command scaffolds the whole front end:

npx eve channels add web

That generates a Next.js app around your agent: a next.config.ts that mounts the routes, an app/ directory with a chat component, and the React wiring. Run git status afterward to see exactly what it added, the file paths can shift between eve versions, so trust your generated tree over any path printed here.

How the door is wired. The generated next.config.ts wraps your config with withEve:

next.config.ts
import type { NextConfig } from "next";
import { withEve } from "eve/next";
 
const nextConfig: NextConfig = {};
 
export default withEve(nextConfig);

That one wrapper is the whole integration. withEve mounts the agent's /eve/v1/* routes onto your Next.js app's own origin. The browser never crosses a CORS boundary or reads an env var to find the agent, the routes are there, same-origin. In dev, npm run dev boots the eve runtime right next to next dev and forwards those routes to it.

npm run dev changed meaning

Until now you ran the agent with npx eve dev, the terminal UI. Now that there's a web app, npm run dev runs next dev and serves the dashboard at localhost:3000. The TUI is still there whenever you want it (npx eve dev); the web app is just the new default dev.

Now, how the browser drives it. Open the generated chat component. It leans on one hook:

const agent = useEveAgent();

That single call opens a durable session, sends turns, streams replies, and hands you render-ready state. The component reads from it:

  • agent.data.messages, the conversation (an EveMessage[] following the AI SDK UIMessage convention), mapped to message bubbles.
  • agent.status, "ready" | "submitted" | "streaming" | "error", used to disable the composer while the agent is working.
  • agent.send({ message }), to send a turn. The same send carries your answer back when the agent stops for an approval.

The approval gate you built in 3.2 shows up here automatically. When book_repair parks on a big booking, the message renders approve/deny controls, and clicking one sends your answer back through the hook. You wrote zero approval-UI code; useEveAgent surfaces the parked request and the generated component renders it.

Make it the shop's. The generated app names the agent after your project. Give it the shop's identity instead. Open the generated chat component, app/_components/agent-chat.tsx in this eve version, and rename the agent (the constant the scaffold uses for the display name):

const AGENT_NAME = "Spoke & Mirror";

Then set the page metadata in the app's layout.tsx:

export const metadata: Metadata = {
  title: "Spoke & Mirror Dispatcher",
  description: "Book bike repairs at Spoke & Mirror Cyclery.",
};
Match your generated files

The exact paths and the display-name constant come from whatever eve channels add web generated for your eve version. If your component names the agent differently, rename whatever it actually uses. A quick git status plus a grep for your project name points you at both spots.

Try It

Boot the dashboard:

npm run dev

Open http://localhost:3000. You'll see the Spoke & Mirror chat: a header with the shop's name and a message box at the bottom. Click into the box, type a real customer question, and press Enter:

my rear brake feels spongy on the way down the hill

The reply streams into the page, the dispatcher diagnosing in the same front-desk voice you wrote in 1.1. That's the "hey, it's working" moment: the agent you built and drove from a terminal is now answering in a browser, same brain, new door, and you didn't touch a single tool to get here.

Now run it through the rest of its paces:

  1. Ask "what's a brake bleed cost?", the lookup_service tool runs and you get the real price, same as the TUI.
  2. Ask it to book a flat repair, it books straight through.
  3. Ask for the Full Overhaul, and the approval gate from 3.2 appears as approve / deny buttons right in the chat. Click approve and the booking completes; the turn resumed exactly where it parked, this time driven by a click instead of typing "approve."

Same dispatcher, same tools, same approval logic. The only new thing is the surface.

The approval crossed surfaces unchanged

You wrote approval once, in a tool. It rendered as a text prompt in the TUI and as buttons in the browser, with no per-surface code. That's the portability payoff: behavior lives in the tool, and each channel renders it however suits the platform.

Blank page or a connection error in the console? Make sure you ran npm run dev (not npx eve dev, that's the TUI and doesn't serve the web app). If the chat loads but every message errors, the agent couldn't reach a model, the same credential check from 1.1 applies (eve link or AI_GATEWAY_API_KEY).

Done-When

  • npm run dev serves the dashboard at localhost:3000.
  • The dispatcher answers in the browser and calls tools (you see real prices/slots).
  • An expensive booking renders approve/deny controls, and approving completes it.
  • The page and chat header show "Spoke & Mirror", not the scaffold default.

Solution

eve channels add web did the wiring; withEve and useEveAgent came with it. Your only edits are the shop's identity, the display name in the chat component:

const AGENT_NAME = "Spoke & Mirror";

and the page metadata in layout.tsx:

export const metadata: Metadata = {
  title: "Spoke & Mirror Dispatcher",
  description: "Book bike repairs at Spoke & Mirror Cyclery.",
};

That's the point worth taking away: standing up a browser front end for an eve agent isn't a project, it's one command, a config wrapper, and a hook. The dispatcher you spent three sections building works in the browser without a single change to its tools, state, skill, or approval logic. Next we give it a second front door, Slack, and it'll be just as little new code.

Was this helpful?

supported.