pinecallDocs
Guides / The side panel: @view

The side panel: @view

What a supervisor sees beside a conversation, drawn by the agent.

Beside every conversation in the console's Calls, a supervisor sees a pane about the person. With @view, your agent draws its own panel there: the customer's file, their orders, what they owe, read from your systems.

What is a view?#

A view is a function you write beside the agent. The console asks for it one conversation at a time, your process runs it, and what comes back is drawn above the console's own facts.

Without a view, the pane still shows what the console knows: how many conversations there have been with this person, how they reached the agent, how long the agent has spent with them, and what a reviewer should look at first.

Write the view#

agents/front-desk/view.tsx
import { type Who } from "@pinecall/agents";
import { Badge, Panel, Row, Rows, Stat, Table } from "@pinecall/agents/panels";
import { crm } from "../../lib/crm";

/** The customer's file, beside the conversation. */
export default async function CustomerCard(who: Who) {
  const client = await crm.find(who.contact);
  if (client === undefined) return <Panel title="No file">Not in the CRM.</Panel>;
  return (
    <Panel title={client.name}>
      <Rows>
        <Row label="Customer since">{client.since}</Row>
        <Row label="Area">{client.area}</Row>
      </Rows>
      <Stat label="Jobs" value={client.jobs.length} />
      <Table columns={["date", "service", "amount"]} rows={client.jobs} />
      <Badge tone={client.debt > 0 ? "warn" : "good"}>{client.debt > 0 ? "owes" : "paid up"}</Badge>
    </Panel>
  );
}

who is the conversation and nothing else:

field what it is
agent the agent whose conversation is open
contact the other party: a phone number, a contact id, or a web visitor's id
call the conversation's most recent call id

There is no live state in it. The panel is also shown beside conversations that ended weeks ago, when no instance of your class is serving anybody, so a view fetches what it shows.

Declare it#

agents/front-desk/agent.tsx
import { Agent, view } from "@pinecall/agents";
import CustomerCard from "./view";

@view(CustomerCard)                 // titled from the function: "Customer Card"
export default class FrontDesk extends Agent { … }

@view(CustomerCard, "Customer") names the panel outright. A class draws one panel: two @view decorators on one class are refused when the class is defined.

The tags#

@pinecall/agents/panels exports the only tags a view can use:

tag draws
<Panel title> a titled section; several stack
<Rows> · <Row label value> a list of labelled lines; the value is value or the children
<Stat label value> one prominent number
<Table columns rows> a table; each row is an array in column order, or an object keyed by column name
<Badge tone> a status; tone is neutral, good, warn or bad
<Text> a line of text

These tags belong to a view, never to a prompt. Used inside render() they throw by name, so panel text never leaks into what the model reads.

How it works#

When the agent registers, the gateway is told the panel's name, nothing more. When someone opens a conversation, the console asks the process that holds the agent to draw it, and that process runs your function and sends back the tree. The console draws it with its own parts, in the theme the reader chose, so nothing your view returns can reach the page's styles, scripts or key.

A view that throws is shown in the pane in its own words, and the console's facts under it are still drawn.

The panel is drawn by the process pinecall start runs: on your laptop, on your server, or deployed with pinecall deploy. An app that mounts the class itself with mount() answers no panel, and the pane shows the console's facts alone.

Next steps#