Here is an AI Chat Assistant, a React-based web app that offers smart, interactive conversations. It uses advanced AI to understand and respond to user queries, providing a simple and engaging chat experience. Built with React, the app is fast, responsive, and easy to customize.

File and Folder Organization

Setup
- Install Client Dependencies: Navigate to the client folder and install the dependencies:
cd client
npm install- Install Server Dependencies: Navigate to the server folder and install the server-side dependencies:
cd ../server
npm install- Start the Client (React App):
cd client
npm run dev- Start the Server (Backend):
cd ../server
npm run devCode base
- src
:root {
--bg: #0b0b0c;
--panel: #121214;
--muted: #a1a1aa;
--text: #e5e7eb;
--primary: #6366f1;
--bubble-user: #1f2937;
--bubble-ai: #0f172a;
--border: #26272b;
}
* { box-sizing: border-box; }
html, body, #root { height: 100%; }
body { margin: 0; background: var(--bg); color: var(--text); font: 14px/1.4 system-ui, -apple-system, Segoe UI, Roboto, Inter, Arial; }
.app { display: flex; flex-direction: column; height: 100vh; }
.app__header { position: sticky; top: 0; z-index: 10; display: flex; gap: 12px; align-items: center; padding: 14px 18px; background: rgba(18,18,20,0.8); backdrop-filter: blur(8px); border-bottom: 1px solid var(--border); }
.logo { width: 36px; height: 36px; display: grid; place-items: center; background: var(--primary); color: white; border-radius: 12px; font-weight: 700; }
.titles h1 { margin: 0; font-size: 16px; }
.titles p { margin: 2px 0 0; font-size: 12px; color: var(--muted); }
.chat { flex: 1; display: grid; grid-template-rows: 1fr auto; }
.chat__list { padding: 16px; max-width: 880px; width: 100%; margin: 0 auto; overflow-y: auto; }
.message { display: flex; margin: 10px 0; }
.message--right { justify-content: flex-end; }
.bubble { max-width: 80%; padding: 12px 14px; border-radius: 14px; box-shadow: 0 1px 0 rgba(0,0,0,.2); border: 1px solid var(--border); white-space: pre-wrap; }
.bubble--user { background: var(--bubble-user); }
.bubble--ai { background: var(--bubble-ai); }
.bubble--error { background: #3b0a0a; border-color: #5f1111; color: #ffd7d7; }
.bubble__header { display: flex; align-items: center; gap: 10px; margin-bottom: 6px; opacity: .85; }
.avatar { width: 28px; height: 28px; border-radius: 10px; display: grid; place-items: center; font-size: 12px; font-weight: 700; }
.avatar--ai { background: #4f46e5; color: white; }
.avatar--user { background: #e5e7eb; color: #0b0b0c; }
.avatar--error { background: #ef4444; color: white; }
.meta { font-size: 12px; color: var(--muted); }
.composer { border-top: 1px solid var(--border); background: var(--panel); }
.composer__inner { max-width: 880px; margin: 0 auto; padding: 10px 16px; }
.box { background: #0e0f12; border: 1px solid var(--border); border-radius: 16px; padding: 8px; }
textarea.input { width: 100%; background: transparent; color: var(--text); border: 0; outline: 0; resize: none; padding: 10px 12px; font: inherit; }
.toolbar { display: flex; align-items: center; justify-content: space-between; padding: 6px 8px 2px; }
button.btn { display: inline-flex; align-items: center; gap: 8px; padding: 8px 12px; border-radius: 12px; border: 1px solid var(--border); background: var(--primary); color: white; cursor: pointer; }
button.btn:disabled { opacity: .5; cursor: not-allowed; }
button.ghost { background: transparent; color: var(--text); }
.small { font-size: 12px; color: var(--muted); }
.copy { font-size: 12px; padding: 4px 8px; border-radius: 8px; border: 1px solid var(--border); background: transparent; color: var(--text); cursor: pointer; }
.spinner { width: 16px; height: 16px; border: 2px solid rgba(255,255,255,.3); border-top-color: white; border-radius: 50%; animation: spin 1s linear infinite; }
@keyframes spin { to { transform: rotate(360deg); } }
import React from "react";
import Chat from "./components/Chat.jsx";
export default function App() {
return (
<div className="app">
<header className="app__header">
<div className="logo">AI</div>
<div className="titles">
<h1>AI Chatbot</h1>
<p>Clean UI • Your API key stays on server • Enter to send, Shift+Enter = newline</p>
</div>
</header>
<Chat />
</div>
);
}
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App.jsx";
import "./styles.css";
ReactDOM.createRoot(document.getElementById("root")).render(
<React.StrictMode>
<App />
</React.StrictMode>
);
- src/components
import React from "react";
export default function Avatar({ role }) {
const base = "avatar";
if (role === "error") return <div className={`${base} avatar--error`}>!</div>;
if (role === "user") return <div className={`${base} avatar--user`}>You</div>;
return <div className={`${base} avatar--ai`}>AI</div>;
}
import React, { useEffect, useMemo, useRef, useState } from "react";
import { sendChat } from "../services/api.js";
import MessageBubble from "./MessageBubble.jsx";
import { SendIcon } from "./Icons.jsx";
export default function Chat() {
const [messages, setMessages] = useState([
{ id: crypto.randomUUID(), role: "assistant", content: "Hi! I’m your AI assistant. Ask me anything. 🧠💬" },
]);
const [input, setInput] = useState("");
const [model, setModel] = useState("gpt-4.1-mini");
const [loading, setLoading] = useState(false);
const [error, setError] = useState("");
const listRef = useRef(null);
const canSend = useMemo(() => input.trim().length > 0 && !loading, [input, loading]);
useEffect(() => {
const el = listRef.current; if (!el) return; el.scrollTo({ top: el.scrollHeight, behavior: "smooth" });
}, [messages, loading]);
async function handleSend() {
if (!canSend) return;
setError("");
const userMsg = { id: crypto.randomUUID(), role: "user", content: input.trim() };
setMessages(m => [...m, userMsg]);
setInput("");
setLoading(true);
try {
const payload = messages.filter(m => m.role !== "error").concat(userMsg).map(({ role, content }) => ({ role, content }));
const { text } = await sendChat({ model, messages: payload });
setMessages(m => [...m, { id: crypto.randomUUID(), role: "assistant", content: text || "(No response)" }]);
} catch (e) {
const msg = e instanceof Error ? e.message : String(e);
setMessages(m => [...m, { id: crypto.randomUUID(), role: "error", content: `⚠️ Request failed: ${msg}` }]);
setError(msg);
} finally {
setLoading(false);
}
}
function onKeyDown(e) {
if (e.key === "Enter" && !e.shiftKey) { e.preventDefault(); handleSend(); }
}
function clearChat() {
setMessages([{ id: crypto.randomUUID(), role: "assistant", content: "Chat cleared. How can I help now?" }]);
setError("");
}
return (
<div className="chat">
<div ref={listRef} className="chat__list">
{messages.map(m => (<MessageBubble key={m.id} role={m.role} text={m.content} />))}
{loading && (
<div className="small" style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
<div className="spinner" /> Thinking…
</div>
)}
</div>
<div className="composer">
<div className="composer__inner">
<div className="box">
<textarea
className="input"
rows={1}
placeholder="Ask me anything…"
value={input}
onChange={(e) => setInput(e.target.value)}
onKeyDown={onKeyDown}
/>
<div className="toolbar">
<div className="small">{error ? "Error — try again" : ""}</div>
<div style={{ display: 'flex', gap: 8, alignItems: 'center' }}>
<select value={model} onChange={(e) => setModel(e.target.value)} className="ghost">
<option value="gpt-4.1-mini">gpt-4.1-mini</option>
<option value="gpt-4o-mini">gpt-4o-mini</option>
</select>
<button className="btn" disabled={!canSend} onClick={handleSend}>
<SendIcon /> {loading ? "Sending…" : "Send"}
</button>
<button className="btn ghost" onClick={clearChat}>Clear</button>
</div>
</div>
</div>
<div className="small" style={{ marginTop: 8 }}>Backend: <code>{import.meta.env.VITE_API_BASE || "/api"}</code> • Set <code>OPENAI_API_KEY</code> on the server.</div>
</div>
</div>
</div>
);
}
import React, { useState } from "react";
export default function CopyButton({ text }) {
const [done, setDone] = useState(false);
async function copy() {
await navigator.clipboard.writeText(text);
setDone(true);
setTimeout(() => setDone(false), 1200);
}
return (
<button className="copy" onClick={copy}>{done ? "Copied" : "Copy"}</button>
);
}
import React from "react";
export function SendIcon() {
return (
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="16" height="16" fill="currentColor">
<path d="M2.01 21L23 12 2.01 3 2 10l15 2-15 2z" />
</svg>
);
}
import React from "react";
import Avatar from "./Avatar.jsx";
import CopyButton from "./CopyButton.jsx";
export default function MessageBubble({ role, text }) {
const isUser = role === "user";
const isError = role === "error";
return (
<div className={`message ${isUser ? "message--right" : ""}`}>
<div className={`bubble ${isUser ? "bubble--user" : isError ? "bubble--error" : "bubble--ai"}`}>
<div className="bubble__header">
<Avatar role={role} />
<span className="meta">{role}</span>
</div>
<div>{text}</div>
{!isError && (
<div style={{ marginTop: 8, opacity: .8 }}>
<CopyButton text={text} />
</div>
)}
</div>
</div>
);
}
- src/services
const API_BASE = import.meta.env.VITE_API_BASE || "";
export async function sendChat({ model, messages }) {
const res = await fetch(`${API_BASE}/api/chat`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ model, messages }),
});
if (!res.ok) {
const text = await res.text();
throw new Error(text || `HTTP ${res.status}`);
}
return res.json();
}
- src/server
import express from "express";
import cors from "cors";
import dotenv from "dotenv";
import OpenAI from "openai";
dotenv.config();
const app = express();
app.use(cors());
app.use(express.json());
const client = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
// Health check
app.get("/api/health", (req, res) => res.json({ ok: true }));
// POST /api/chat { model, messages: [{role, content}, ...] }
app.post("/api/chat", async (req, res) => {
try {
const { model = "gpt-4.1-mini", messages = [] } = req.body || {};
const response = await client.responses.create({
model,
input: messages.map(({ role, content }) => ({ role, content })),
});
const text = response.output_text ?? "(no output_text)";
res.json({ text });
} catch (err) {
console.error(err);
res.status(500).json({ error: err?.message || "Unknown error" });
}
});
const PORT = process.env.PORT || 3001;
app.listen(PORT, () => console.log(`✅ API ready on http://localhost:${PORT}`));
# Rename to .env and add your key
OPENAI_API_KEY=hjd.. //Put your own api key here
PORT=3001
Output
Core Files Explained
Entry Point
main.jsx: Renders the root React component (App) into the DOM.
App Component
App.jsx: Sets up the layout and renders the header, titles, and the main Chat component. It provides the structure for the chat interface and contains static content like the app title.
Components
- Chat.jsx: Handles chat functionality, manages state (messages, loading, error), and sends/receives messages from the server.
- Avatar.jsx: Displays avatars for user, AI, or error.
- MessageBubble.jsx: Renders individual chat messages with an option to copy.
- CopyButton.jsx: Allows the user to copy a specific message to the clipboard.
- SendIcon.jsx: Displays the send icon used in the chat input form.
API Interaction
api.js: Handles communication with the back-end server. It sends a POST request to the /api/chat route with the chat messages and receives the AI’s response.
Backend (Express)
server.js: Handles two routes:
- /api/health: Health check to verify server is running.
- /api/chat: Sends messages to OpenAI API and returns responses.
Environment Variables
.env: Stores the OpenAI API key and server port.