name: ago-react
description: Build a custom AGO chat interface in React using the SDK hooks (useChat, useMessages, useConversation, useAgoFunction). Keywords: ago, react, chat, useChat, hook, custom UI, integration, chatbot.
allowed-tools: Read, Write, Edit, Glob, Grep, Bash
/ago-react - Build a Custom AGO Chat UI in React
Creates a custom chat interface using AGO SDK React hooks.
Usage
/ago-react [page-path]
Example: /ago-react src/pages/Support.tsx
Steps
1. Verify prerequisites
- Check that
@useago/sdk is installed (if not, suggest /ago-setup)
- Check that
AgoProvider wraps the app (if not, add it)
2. Ask what the user needs
Options range from simple to advanced:
- Drop-in widget: Pre-built
<ChatWidget /> component
- Custom chat page: Full page with message list, input, conversation sidebar
- Embedded chat: Chat panel inside an existing page
3. Generate the appropriate integration
Option A: Drop-in ChatWidget (simplest)
import { ChatWidget } from "@useago/sdk/react";
export function SupportPage() {
return (
<ChatWidget
agentId="your-agent-id" // Optional: specific agent
placeholder="Ask a question..."
welcomeMessage="Hi! How can I help?"
/>
);
}
Option B: Custom Chat with useChat (recommended)
import { useChat } from "@useago/sdk/react";
import type { AgoMessage } from "@useago/sdk";
export function ChatPage() {
const {
messages,
isLoading,
sendMessage,
conversationId,
conversations,
selectConversation,
startNewConversation,
} = useChat();
const [input, setInput] = useState("");
const handleSend = async () => {
if (!input.trim()) return;
const text = input;
setInput("");
await sendMessage(text);
};
return (
<div className="flex h-screen">
{/* Sidebar: conversation list */}
<aside className="w-64 border-r overflow-y-auto">
<button onClick={startNewConversation}>New conversation</button>
{conversations.map((conv) => (
<button
key={conv.id}
onClick={() => selectConversation(conv.id)}
className={conv.id === conversationId ? "bg-gray-100" : ""}
>
{conv.title || "Untitled"}
</button>
))}
</aside>
{/* Main chat area */}
<main className="flex-1 flex flex-col">
<div className="flex-1 overflow-y-auto p-4 space-y-4">
{messages.map((msg) => (
<MessageBubble key={msg.id} message={msg} />
))}
{isLoading && <div>Thinking...</div>}
</div>
<form onSubmit={(e) => { e.preventDefault(); handleSend(); }} className="p-4 border-t flex gap-2">
<input
value={input}
onChange={(e) => setInput(e.target.value)}
placeholder="Type a message..."
className="flex-1 border rounded px-3 py-2"
/>
<button type="submit" disabled={isLoading}>Send</button>
</form>
</main>
</div>
);
}
function MessageBubble({ message }: { message: AgoMessage }) {
const isUser = message.role === "user";
return (
<div className={`flex ${isUser ? "justify-end" : "justify-start"}`}>
<div className={`max-w-[70%] rounded-lg px-4 py-2 ${isUser ? "bg-blue-500 text-white" : "bg-gray-100"}`}>
<p>{message.content}</p>
{/* Sources / citations */}
{message.sources?.length > 0 && (
<div className="mt-2 text-xs opacity-70">
Sources: {message.sources.map((s) => (
<a key={s.id} href={s.url} target="_blank" rel="noopener" className="underline mr-2">{s.title}</a>
))}
</div>
)}
</div>
</div>
);
}
Option C: Streaming with event listeners (advanced)
import { useAgoClient } from "@useago/sdk/react";
import { onMessageChunk, onMessage } from "@useago/sdk";
function useStreamingChat() {
const client = useAgoClient();
const [streamingContent, setStreamingContent] = useState("");
useEffect(() => {
const unsubChunk = onMessageChunk(client, (data) => {
setStreamingContent((prev) => prev + data.content);
});
const unsubComplete = onMessage(client, () => {
setStreamingContent("");
});
return () => { unsubChunk(); unsubComplete(); };
}, [client]);
return { streamingContent };
}
4. Handle tool calls (forms & confirmations)
If the agent uses tools that require user input:
function ToolCallHandler({ toolCall }: { toolCall: ToolCallData }) {
const client = useAgoClient();
if (toolCall.type === "form") {
return (
<form onSubmit={async (e) => {
e.preventDefault();
const formData = Object.fromEntries(new FormData(e.currentTarget));
await client.submitToolCallForm(toolCall.id, formData);
}}>
{toolCall.formSchema?.fields.map((field) => (
<input key={field.name} name={field.name} placeholder={field.label} required={field.required} />
))}
<button type="submit">Submit</button>
</form>
);
}
if (toolCall.type === "confirmation_input") {
return (
<div>
<p>{toolCall.toolDisplayName}</p>
<button onClick={() => client.confirmToolCall(toolCall.id)}>Confirm</button>
<button onClick={() => client.rejectToolCall(toolCall.id)}>Cancel</button>
</div>
);
}
return null;
}
5. Handle feedback
function FeedbackButtons({ messageId }: { messageId: string }) {
const client = useAgoClient();
return (
<div>
<button onClick={() => client.submitFeedback(messageId, "positive")}>👍</button>
<button onClick={() => client.submitFeedback(messageId, "negative")}>👎</button>
</div>
);
}
Available React Hooks
| Hook | Purpose |
|---|
useChat(options?) | All-in-one: messages, conversations, sending, streaming |
useMessages(options?) | Message list and sending only |
useConversation(options?) | Single conversation data |
useAgoClient() | Access the raw AgoClient instance |
useAgoFunction(name, options) | Register a client-side function |
useAgoNavigation(navigate, routes) | Register navigation routes |
Checklist
AgoProvider wraps the component tree
- Environment variables are set for API key and widget ID
- Messages render both user and assistant roles
- Loading state is shown during streaming
- Run typecheck and lint