Using the React SDK

@atanzoai/embed-react wraps the core SDK in React hooks. Session lifecycle, plan state, agent status, glanceable text, credits, knowledge citations, Q&A, live metrics, the end-of-session report, and frame-capture flashes each have a dedicated hook that handles subscription and cleanup automatically.

Installation

npm install @atanzoai/embed-react @atanzoai/embed-voice

@atanzoai/embed-voice is listed separately because it bundles the LiveKit audio transport, which has its own native dependencies. Install both for any session that requires live voice.

Available Hooks

HookPackageReturnsWhen to use
useSession@atanzoai/embed-react{ session, status, error }Always — manages the session lifecycle.
usePlan@atanzoai/embed-react{ currentStep, currentStepIndex, totalSteps }When your UI shows a step panel or progress indicator.
useAgentStatus@atanzoai/embed-reactstringWhen you want to show what the agent is currently doing.
useGlanceable@atanzoai/embed-reactstring | nullWhen you need a persistent HUD banner or AR overlay text.
useCredits@atanzoai/embed-react{ creditsUsed, sessionMinutes, ratePerMin } | nullWhen you want a usage meter visible to the worker.
useKnowledge@atanzoai/embed-reactKnowledgeSource[]When you want to show which documents the agent cited.
useQuestions@atanzoai/embed-reactQuestionEntry[]When you want a running Q&A transcript panel.
useMetrics@atanzoai/embed-reactMetricsSnapshotWhen you want a live metrics HUD (pace, fillers, pose, etc.).
useSessionReport@atanzoai/embed-reactSessionReport | nullWhen you want to render the end-of-session report screen.
useFrameCaptureFlash@atanzoai/embed-reactnumberWhen you want a capture-flash affordance each time a video frame is sent.

useSession(credential)

Manages the full session lifecycle. Pass it the credential object fetched from your backend token endpoint.

const { session, status, error } = useSession(credential);

status values:

ValueMeaning
"idle"No credential yet, or the hook has not started connecting.
"connecting"createSession() has been called; waiting for the LiveKit room to open.
"connected"Session is live and ready to accept transport attachments and event subscriptions.
"error"A fatal error occurred. Inspect error.message and surface it to the user.

Pass session to all other hooks. They are all null-safe — they return empty/null state when session is null, so you do not need to guard each hook call yourself.

usePlan(session)

Returns the current plan state. Updates automatically as plan and step_update events arrive.

const plan = usePlan(session);
// plan.currentStep — active step object (null before first plan)
// plan.currentStepIndex — zero-based index
// plan.totalSteps — total step count

When session is null, usePlan returns { currentStep: null, currentStepIndex: 0, totalSteps: 0 }.

useAgentStatus(session)

Returns a string describing what the agent is currently doing — useful for a status label or animated indicator.

const agentStatus = useAgentStatus(session);
// e.g. "listening", "thinking", "speaking"

Common values: "listening", "thinking", "speaking". The exact set depends on the agent configuration.

useGlanceable(session)

Returns the latest glanceable text string emitted by the agent, or null if none has arrived yet. Updates in near-real time as the agent produces observations.

const glanceable = useGlanceable(session);
// e.g. "Watch your elbow angle" or null

Render it conditionally — null means there is nothing current to display:

{glanceable && <div className="glance-banner">{glanceable}</div>}

useCredits(session)

Returns the current credit usage snapshot, or null before the first update arrives.

const credits = useCredits(session);
// credits.creditsUsed — total credits consumed
// credits.sessionMinutes — elapsed time in minutes
// credits.ratePerMin — burn rate per minute

useKnowledge(session)

Returns the list of knowledge sources the agent has cited so far, updated as knowledge events arrive. Use it to show which documents backed the agent's answers.

const sources = useKnowledge(session);
// sources[0].document_name — display name of the cited document
// sources[0].text_preview — short excerpt shown to the user
// sources[0].page_number — optional page reference
// sources[0].score_label — "high" | "medium" | "low" relevance, if provided
<ul className="citations">
  {sources.map((s, i) => (
    <li key={s.doc_id ?? i}>
      {s.document_name}{s.page_number ? ` (p. ${s.page_number})` : ""} — {s.text_preview}
    </li>
  ))}
</ul>

useQuestions(session)

Returns the accumulated Q&A history for the session, appending a new entry every time a question_exchange event arrives. Use it to build a transcript panel.

const questions = useQuestions(session);
// questions[0].question — what the user asked
// questions[0].answerSummary — short spoken-style answer
// questions[0].answerFull — full answer text, if the agent provided one
// questions[0].stepIndex — which plan step the question was asked during (null if none)
<ul className="qa-transcript">
  {questions.map((q) => (
    <li key={q.questionId}>
      <strong>Q:</strong> {q.question}
      <br />
      <strong>A:</strong> {q.answerSummary}
    </li>
  ))}
</ul>

useMetrics(session)

Returns the most recent metrics_update snapshot, keyed by the metric id defined in the agent's config (e.g. vision.*, audio.*, behavior.* extractors). Each entry has a raw value, a pre-formatted formatted string, and an optional trend array of historical values.

const metrics = useMetrics(session);
// metrics["audio.pace"]?.value — raw numeric value
// metrics["audio.pace"]?.formatted — display-ready string, e.g. "142 wpm"
// metrics["audio.pace"]?.trend — historical values, oldest first (if trend.enabled)

Returns {} until the first snapshot arrives, and forever on agents/protocol versions that don't emit metrics — guard on key count before rendering:

{Object.keys(metrics).length > 0 && (
  <div className="metrics-hud">
    {Object.entries(metrics).map(([id, m]) => (
      <div key={id}>{id}: {m.formatted}</div>
    ))}
  </div>
)}

useSessionReport(session)

Returns the structured end-of-session report once the agent emits an artifact event with kind: "report" (just before session_ended), or null until then. Use it to drive the post-session report screen.

const report = useSessionReport(session);
// report.summary — overall LLM-generated summary
// report.scores — array of { id, label, value, scaleType, rationale }
// report.coachingTips — ordered list of tips, most impactful first
// report.finalSnapshot — the MetricsSnapshot at end of session

useArtifacts(session)

Returns every durable output the run has produced so far — documents, the end-of-session report, generated images, etc. — deduped by artifactId with the highest version winning.

const artifacts = useArtifacts(session);
// artifacts.filter((a) => a.kind === "document")
{report && (
  <div className="session-report">
    <p>{report.summary}</p>
    <ul>
      {report.scores.map((s) => (
        <li key={s.id}>{s.label}: {s.value} ({s.scaleType})</li>
      ))}
    </ul>
    <ol>
      {report.coachingTips.map((tip, i) => <li key={i}>{tip}</li>)}
    </ol>
  </div>
)}

For historical retrieval after the WebRTC session has closed, call your backend's session-report endpoint (e.g. the console's getsessionreport action) rather than relying on this hook — it only ever holds the report from the live session.

useFrameCaptureFlash(session)

Increments a numeric counter every time the agent samples a video frame (frame_captured event). Pass it to a key prop on a CSS-animated overlay element to retrigger a flash animation on each capture.

const flashKey = useFrameCaptureFlash(session);
<div className="camera-wrapper">
  <video ref={videoRef} autoPlay playsInline muted />
  <div key={flashKey} className={flashKey > 0 ? "animate-frame-capture" : ""} />
</div>

Do not key the <video> element itself on the flash value. Changing a key forces React to unmount and remount the element, which drops and re-acquires the MediaStream — the camera feed goes blank for a frame (or longer) on every capture. Key only a decorative overlay element (a border, a flash div) that sits alongside the <video>, never the <video> itself.

Full Widget Example

The example below combines the five core hooks — useSession, usePlan, useAgentStatus, useGlanceable, and useCredits — into a single GuidedTaskWidget component. It also shows how to attach the voice transport inside useEffect for proper cleanup. For useKnowledge, useQuestions, useMetrics, useSessionReport, and useFrameCaptureFlash, see the per-hook examples above.

import { useSession, usePlan, useAgentStatus, useGlanceable, useCredits } from "@atanzoai/embed-react";
import { attachVoice } from "@atanzoai/embed-voice";
import { useEffect, useRef, useState } from "react";

function GuidedTaskWidget({ credential }) {
  const { session, status, error } = useSession(credential);
  const plan = usePlan(session);
  const agentStatus = useAgentStatus(session);
  const glanceable = useGlanceable(session);
  const credits = useCredits(session);
  const voiceRef = useRef(null);
  const [agentSpeaking, setAgentSpeaking] = useState(false);

  useEffect(() => {
    if (!session) return;
    voiceRef.current = attachVoice(session, {
      onAgentSpeaking: (s) => setAgentSpeaking(s),
    });
    return () => voiceRef.current?.disconnect();
  }, [session]);

  if (status === "connecting") return <p>Connecting...</p>;
  if (error) return <p>Error: {error.message}</p>;

  return (
    <div>
      <p>Agent: {agentStatus}</p>

      {glanceable && <div className="glance-banner">{glanceable}</div>}

      {plan.currentStep && (
        <div>
          <h3>Step {plan.currentStepIndex + 1} of {plan.totalSteps}</h3>
          <p>{plan.currentStep.description}</p>
          <button onClick={() => session?.send({ type: "command", cmd: "next_step" })}>
            Next
          </button>
        </div>
      )}

      {credits && (
        <div className="credits-hud">
          {credits.creditsUsed} credits used ({credits.sessionMinutes.toFixed(1)} min)
        </div>
      )}
    </div>
  );
}

Key points in the example

  • attachVoice is called inside useEffect with session as the dependency. This ensures the voice transport is attached as soon as the session is live and disconnected when the component unmounts.
  • session?.send(...) uses optional chaining — safe to call even while session is null during initial render.
  • credits.sessionMinutes.toFixed(1) formats elapsed time to one decimal place.
  • The agentSpeaking state wired from onAgentSpeaking is available if you want to animate a speaking indicator (e.g. pulsing avatar).

Next Steps