AI chat interface
A chat UI that posts the conversation to a backend endpoint you configure. No provider keys in the browser.
A complete chat front end: message list with user/assistant bubbles, Enter-to-send textarea, pending and error states, cancel with AbortController, and persisted history. It talks to a single configurable endpoint (VITE_CHAT_API_URL or a field in the header) using a documented JSON contract, and renders an explicit 'No backend configured' state until you point it at a server that holds the provider key. Pair it with the Express REST API template for the backend.
- react
- ai
- chat
- assistant
- tailwind
This preview is the template's actual file tree and source code — exactly what is copied into a new project. Codexa does not publish screenshots or mock-ups of templates; run the project to see it rendered.
Files12 files
import { useEffect, useRef, useState, type FormEvent } from "react";
import { sendMessages, type Message } from "./chat";
import { useLocalStorage } from "./useLocalStorage";
const envEndpoint = (import.meta.env.VITE_CHAT_API_URL as string | undefined) ?? "";
export default function App() {
const [endpoint, setEndpoint] = useLocalStorage<string>("chat-endpoint", envEndpoint);
const [messages, setMessages] = useLocalStorage<Message[]>("chat-messages", []);
const [input, setInput] = useState("");
const [pending, setPending] = useState(false);
const [error, setError] = useState<string | null>(null);
const abort = useRef<AbortController | null>(null);
const bottom = useRef<HTMLDivElement>(null);
const configured = endpoint.trim().length > 0;
useEffect(() => {
bottom.current?.scrollIntoView({ behavior: "smooth" });
}, [messages, pending]);
async function submit(e: FormEvent) {
e.preventDefault();
const content = input.trim();
if (!content || pending || !configured) return;
const next: Message[] = [...messages, { role: "user", content }];
setMessages(next);
setInput("");
setError(null);
setPending(true);
abort.current = new AbortController();
try {
const reply = await sendMessages(endpoint.trim(), next, abort.current.signal);
setMessages([...next, { role: "assistant", content: reply }]);
} catch (err) {
setError(err instanceof Error ? err.message : "Request failed");
} finally {
setPending(false);
}
}
return (
<div className="flex h-screen flex-col bg-slate-50 text-slate-900">
<header className="border-b border-slate-200 bg-white px-4 py-3">
<div className="mx-auto flex max-w-3xl flex-wrap items-center justify-between gap-2">
<h1 className="font-semibold">Assistant</h1>
<label className="flex items-center gap-2 text-xs text-slate-600">
Backend URL
<input value={endpoint} onChange={(e) => setEndpoint(e.target.value)} placeholder="https://your-api.example/chat" className="w-56 rounded-md border border-slate-300 px-2 py-1 text-xs" />
</label>
</div>
</header>
<main className="flex-1 overflow-y-auto px-4 py-6">
<div className="mx-auto max-w-3xl space-y-4">
{!configured ? (
<div role="status" className="rounded-lg border border-amber-300 bg-amber-50 p-4 text-sm text-amber-900">
<p className="font-medium">No backend configured</p>
<p className="mt-1">
Set <code>VITE_CHAT_API_URL</code> or paste your endpoint above. This template does not call any AI provider directly and ships no API keys — see README.md for the request/response contract.
</p>
</div>
) : null}
{messages.length === 0 && configured ? <p className="text-center text-sm text-slate-500">Send a message to start the conversation.</p> : null}
{messages.map((m, i) => (
<div key={i} className={"flex " + (m.role === "user" ? "justify-end" : "justify-start")}>
<div className={"max-w-[85%] whitespace-pre-wrap rounded-2xl px-4 py-2.5 text-sm " + (m.role === "user" ? "bg-blue-600 text-white" : "border border-slate-200 bg-white")}>
<span className="sr-only">{m.role === "user" ? "You: " : "Assistant: "}</span>
{m.content}
</div>
</div>
))}
{pending ? (
<p className="text-sm text-slate-500" aria-live="polite">
Waiting for the backend…
</p>
) : null}
{error ? (
<p role="alert" className="rounded-md border border-red-200 bg-red-50 p-3 text-sm text-red-700">
{error}
</p>
) : null}
<div ref={bottom} />
</div>
</main>
<form onSubmit={submit} className="border-t border-slate-200 bg-white p-3">
<div className="mx-auto flex max-w-3xl items-end gap-2">
<label className="sr-only" htmlFor="chat-input">
Message
</label>
<textarea
id="chat-input"
value={input}
onChange={(e) => setInput(e.target.value)}
onKeyDown={(e) => {
if (e.key === "Enter" && !e.shiftKey) {
e.preventDefault();
e.currentTarget.form?.requestSubmit();
}
}}
rows={2}
placeholder={configured ? "Ask something… (Enter to send, Shift+Enter for a new line)" : "Configure a backend URL first"}
disabled={!configured || pending}
className="flex-1 resize-none rounded-lg border border-slate-300 px-3 py-2 text-sm disabled:bg-slate-100"
/>
{pending ? (
<button type="button" onClick={() => abort.current?.abort()} className="rounded-lg border border-slate-300 px-4 py-2 text-sm">
Stop
</button>
) : (
<button type="submit" disabled={!configured || !input.trim()} className="rounded-lg bg-blue-600 px-4 py-2 text-sm font-medium text-white disabled:opacity-50">
Send
</button>
)}
<button type="button" onClick={() => setMessages([])} className="rounded-lg border border-slate-300 px-3 py-2 text-sm" disabled={messages.length === 0}>
Clear
</button>
</div>
</form>
</div>
);
}
Dependencies
Documented from the template's package.json. The runtime has a prebuilt dependency set for Vite + React + TypeScript, so installs are fast.
| Package | Version |
|---|---|
| react | ^19.1.0 |
| react-dom | ^19.1.0 |
| vite | ^6.3.0 |
| @vitejs/plugin-react | ^4.4.0 |
| tailwindcss | ^4.1.0 |
| @tailwindcss/vite | ^4.1.0 |
| typescript | ^5.8.0 |
How it runs
- Framework
- Vite + React + TypeScript
- Dev command
- npm run dev
- Build command
- npm run build
- Output directory
- dist
- Port
- 5173
- Opens at
- /
- Difficulty
- intermediate
Projects run in an isolated sandbox with a live preview. Read the projects documentation for details on settings, snapshots and deployment.
Need the raw data? GET /api/v1/templates/vite-react-ai-chat returns this template with its files.
Build on AI chat interface
Create a free account, start from this template, and ask the AI agent to turn it into your app.