Add AI chat feature

This commit is contained in:
James Blair 2026-05-08 20:24:34 +12:00
parent 80056b3728
commit 6a5041be43
Signed by: james
SSH key fingerprint: SHA256:hsDR3ENI2ZMgh+CNb+0PrFsj29DI007R+BMKUVGi6zQ
15 changed files with 1259 additions and 2 deletions

View file

@ -0,0 +1,34 @@
import { useState, type SubmitEvent } from "react";
import { Button } from "../ui/button";
import { Textarea } from "../ui/textarea";
import { ArrowUpIcon, SendIcon } from "lucide-react";
interface ChatFormProps {
onMessage: (message: { text: string }) => void;
disabled?: boolean;
}
export function ChatForm({ onMessage, disabled }: ChatFormProps) {
const [text, setText] = useState("");
function handleSubmit(e: SubmitEvent<HTMLFormElement>) {
e.preventDefault();
if (!text.trim()) return;
setText("");
onMessage({ text });
}
return (
<form onSubmit={handleSubmit} className="flex gap-2">
<Textarea
name="text"
value={text}
onChange={(e) => setText(e.target.value)}
className="min-h-18"
/>
<Button type="submit" disabled={disabled} size="icon-lg">
<ArrowUpIcon />
</Button>
</form>
);
}

View file

@ -0,0 +1,32 @@
import { cn } from "@/lib/utils";
import { UIMessage } from "ai";
interface ChatMessagesProps {
messages: UIMessage[];
}
export function ChatMessages({ messages }: ChatMessagesProps) {
return (
<div className="flex flex-col gap-2">
{messages.map((message, index) => (
<div
key={index}
className={cn(
"bg-primary text-primary-foreground px-3 py-2 rounded-lg whitespace-pre-wrap wrap-break-word break-all",
message.role === "user"
? "self-end bg-primary text-primary-foreground"
: "self-start bg-accent text-accent-foreground",
)}
>
{message.parts.map((part, index) => {
if (part.type === "text") {
return <span key={index}>{part.text}</span>;
}
return null;
})}
</div>
))}
</div>
);
}

24
components/chat/index.tsx Normal file
View file

@ -0,0 +1,24 @@
"use client";
import { ChatMessages } from "./chat-messages";
import { ChatForm } from "./chat-form";
import { DefaultChatTransport } from "ai";
import { useChat } from "@ai-sdk/react";
export function Chat() {
const { messages, sendMessage, status, error } = useChat({
transport: new DefaultChatTransport({
api: "http://localhost:3000/api/chat",
}),
});
return (
<div className="max-w-2xl flex flex-col gap-2">
<ChatMessages messages={messages} />
<ChatForm
onMessage={sendMessage}
disabled={status === "submitted" || status === "streaming"}
/>
</div>
);
}