/** React client — name form + authenticated chat UI. */ import { useCallback, useState, useEffect, useRef, type FormEvent } from "react"; import { useAgent } from "agents/react"; import { useAgentChat } from "@cloudflare/ai-chat/react"; import { Banner, Button, Input, InputArea, Label, Surface, Text } from "@cloudflare/kumo"; import { ConnectionIndicator, ModeToggle, PoweredByAgents, type ConnectionStatus } from "@cloudflare/agents-ui"; import { PaperPlaneRightIcon, SignOutIcon, ShieldCheckIcon, LockKeyIcon, TrashIcon, InfoIcon } from "@phosphor-icons/react"; import { fetchToken, getToken, getUserName, clearAuth, isTokenExpired } from "./auth-client"; // ── Name form ──────────────────────────────────────────────────────────────── function NameForm({ onSuccess }: { onSuccess: () => void }) { const [name, setName] = useState(""); const [error, setError] = useState(null); const [loading, setLoading] = useState(false); const handleSubmit = useCallback( async (e: FormEvent) => { e.preventDefault(); setError(null); setLoading(true); try { await fetchToken(name.trim()); onSuccess(); } catch { setError("Failed to authenticate"); } finally { setLoading(false); } }, [name, onSuccess] ); return (
{/* Header */}
{/* Content */}
Auth Agent
Enter your name to get a JWT and connect to the agent.
setName(e.target.value)} autoComplete="name" required />
{error && (
{error}
)}
{/* Footer */}
); } // ── Chat view (authenticated) ──────────────────────────────────────────────── function getMessageText(message: { parts: Array<{ type: string; text?: string }>; }): string { return message.parts .filter((p) => p.type === "text") .map((p) => p.text ?? "") .join(""); } function ChatView({ onSignOut }: { onSignOut: () => void }) { const [wsStatus, setWsStatus] = useState("connecting"); const [input, setInput] = useState(""); const messagesEndRef = useRef(null); const userName = getUserName() ?? "user"; const handleOpen = useCallback(() => setWsStatus("connected"), []); const handleClose = useCallback(() => { if (isTokenExpired()) { clearAuth(); onSignOut(); return; } setWsStatus("disconnected"); }, [onSignOut]); const agent = useAgent({ agent: "ChatAgent", name: userName, onOpen: handleOpen, onClose: handleClose, query: async () => ({ token: getToken() || "" }) }); const { messages, sendMessage, clearHistory, status } = useAgentChat({ agent }); const isStreaming = status === "streaming"; const isConnected = wsStatus === "connected"; useEffect(() => { messagesEndRef.current?.scrollIntoView({ behavior: "smooth" }); }, [messages]); const send = useCallback(async () => { const text = input.trim(); if (!text || isStreaming) return; setInput(""); try { await sendMessage({ role: "user", parts: [{ type: "text", text }] }); } catch (err) { console.error("Failed to send message:", err); } }, [input, isStreaming, sendMessage]); const handleSignOut = useCallback(() => { clearAuth(); onSignOut(); }, [onSignOut]); return (
{/* Header */}
Auth Agent
{/* Messages */}
Authenticated Agent Connected as {userName}. Your JWT is verified on every WebSocket connection. The agent knows your name from the token claims.
{messages.map((message, index) => { const isUser = message.role === "user"; const text = getMessageText(message); const isLastAssistant = !isUser && index === messages.length - 1; if (isUser) { return (
{text}
); } return (
{text} {isLastAssistant && isStreaming && ( )}
); })}
{/* Input */}
{ e.preventDefault(); send(); }} className="max-w-3xl mx-auto px-6 py-4" > { if (e.key === "Enter" && !e.shiftKey) { e.preventDefault(); send(); } }} placeholder="Type a message..." disabled={!isConnected || isStreaming} rows={2} className="flex-1 ring-0! focus:ring-0! shadow-none! bg-transparent! outline-none!" />
); } // ── App root ───────────────────────────────────────────────────────────────── function App() { const [isAuthenticated, setIsAuthenticated] = useState(() => { if (isTokenExpired()) { clearAuth(); return false; } return true; }); if (isAuthenticated) { return setIsAuthenticated(false)} />; } return setIsAuthenticated(true)} />; } export default function AppWrapper() { return ; }