React Examples
The UClaw SDK exports React hooks from @uclaw/sdk/react. Use useApp for app-level agent directory state, and useAgent for one active agent's chat.
Backend: UClaw API Handler
Create an API Route to automatically serve client tokens. The SDK provides AppClient.handler to handle standard Web Request/Response objects.
In Next.js App Router, create a catch-all file at app/api/uclaw/[...all]/route.ts:
typescript
import { AppClient } from "@uclaw/sdk";
const app = new AppClient({
apiKey: process.env.UCLAW_API_KEY,
});
// Automatically handles POST requests to /api/uclaw/client-tokens
export const POST = (request: Request) => app.handler(request);Frontend: App Shell
tsx
import { useApp } from "@uclaw/sdk/react";
import { useState } from "react";
export function ChatApp() {
const [activeAgentId, setActiveAgentId] = useState<string | null>(null);
const { agents, createAgent, deleteAgent, status } = useApp({
appId: "default",
});
async function handleNewChat() {
const agent = await createAgent({
title: "Travel planner",
config: {
modelTier: "fast",
instructions: "You are a friendly travel planner.",
},
});
setActiveAgentId(agent.id);
}
return (
<div className="flex gap-4">
<aside className="w-64 border-r p-4">
<p>App status: {status}</p>
<button onClick={handleNewChat}>New Chat</button>
{agents.map((agent) => (
<div key={agent.id}>
<button onClick={() => setActiveAgentId(agent.id)}>{agent.title}</button>
<button onClick={() => deleteAgent(agent.id)}>Delete</button>
</div>
))}
</aside>
<main className="flex-1">{activeAgentId && <ChatPane agentId={activeAgentId} />}</main>
</div>
);
}Frontend: Active Agent Chat
tsx
import { useAgent } from "@uclaw/sdk/react";
import { useState } from "react";
export function ChatPane({ agentId }: { agentId: string }) {
const [input, setInput] = useState("");
const { chat, status } = useAgent({
agentId,
});
function handleSend(event: React.FormEvent) {
event.preventDefault();
if (!input.trim() || chat.isStreaming) return;
chat.sendMessage({
role: "user",
parts: [{ type: "text", text: input.trim() }],
});
setInput("");
}
return (
<div className="flex h-full flex-col p-4">
<p>Agent status: {status}</p>
<div className="flex-1 space-y-4 overflow-y-auto">
{chat.messages.map((message) => {
const text = message.parts
.filter((part) => part.type === "text")
.map((part) => part.text)
.join("");
return (
<div key={message.id} className={message.role === "user" ? "text-right" : ""}>
{text}
</div>
);
})}
</div>
<form onSubmit={handleSend} className="mt-4 flex gap-2">
<input
value={input}
onChange={(event) => setInput(event.target.value)}
disabled={status !== "connected"}
/>
{chat.isStreaming ? (
<button type="button" onClick={chat.stop}>
Stop
</button>
) : (
<button type="submit">Send</button>
)}
</form>
</div>
);
}