10Delete Sessions

Delete Sessions

Now that we are on the final stage of the project, this is where we will be adding a feature to delete the sessions right from the UI and from MongoDB as well.


Add Delete Session function

Now, we will add a function to delete the sessions from the UI and from MongoDB as well.

lib/chatMessages.ts

export async function deleteChatSession(sessionId: string) {
    const client = await clientPromise;
    const db = client.db("secondbrain");

    // Delete all messages for this session
    await db.collection("messages").deleteMany({
        sessionId: new ObjectId(sessionId),
    });

    // Delete the session itself
    await db.collection("sessions").deleteOne({
        _id: new ObjectId(sessionId),
    });
}

Update the messeges per session to have the DELETE api call to delete messages from the given session.

app/api/messages/[sessionId]/route.ts

export async function DELETE(_: NextRequest, props: { params: Promise<{ sessionId: string }> }) {
  const params = await props.params;
  try {
    await deleteChatSession(params.sessionId);
    return Response.json({ success: true });
  } catch (error) {
    console.error("Failed to delete chat session:", error);
    return Response.json({ error: "Failed to delete chat session" }, { status: 500 });
  }
}

Update the Sidebar

Now we shall add the delete button on the sidebar and update the contents on the sidebar when any session is deleted and also make it collapsable.

app/components/Sidebar.tsx

"use client";
import { useState, useEffect } from "react";
import Link from "next/link";
import { usePathname, useRouter } from "next/navigation";
import { Button } from "./ui/Button";
import { Plus, MessageSquare, Brain, Trash2, ChevronLeft, ChevronRight } from "lucide-react";
import { cn } from "@/lib/utils";
import { motion, AnimatePresence } from "framer-motion";

export default function ChatSidebar() {
    const [sessions, setSessions] = useState<any[]>([]);
    const [isCollapsed, setIsCollapsed] = useState(false);
    const pathname = usePathname();
    const router = useRouter();

    const fetchSessions = async () => {
        try {
            const res = await fetch("/api/sessions");
            const data = await res.json();
            if (Array.isArray(data)) {
                setSessions(data);
            }
        } catch (error) {
            console.error("Failed to fetch sessions:", error);
        }
    };

    useEffect(() => {
        fetchSessions();

        // Listen for internal updates (e.g. from the chat page)
        const handleUpdate = () => fetchSessions();
        window.addEventListener("chat-updated", handleUpdate);
        return () => window.removeEventListener("chat-updated", handleUpdate);
    }, []);

    const handleDeleteSession = async (e: React.MouseEvent, id: string) => {
        e.preventDefault();
        e.stopPropagation();

        if (!confirm("Are you sure you want to delete this chat session?")) return;

        try {
            const res = await fetch(`/api/messages/${id}`, { method: "DELETE" });
            if (res.ok) {
                setSessions((prev) => prev.filter((s) => s._id !== id));
                if (pathname.includes(id)) {
                    router.push("/chat");
                }
                // Notify other components (if any)
                window.dispatchEvent(new CustomEvent("chat-updated"));
            }
        } catch (error) {
            console.error("Failed to delete session:", error);
        }
    };

    return (
        <motion.aside
            initial={false}
            animate={{ width: isCollapsed ? 64 : 288 }}
            className="h-screen border-r border-white/5 bg-black flex flex-col relative shrink-0 overflow-hidden"
        >
            {/* Collapse Toggle */}
            <button
                onClick={() => setIsCollapsed(!isCollapsed)}
                className="absolute -right-3 top-20 w-6 h-6 rounded-full bg-zinc-900 border border-white/10 flex items-center justify-center text-zinc-500 hover:text-white z-50 transition-colors"
                title={isCollapsed ? "Expand Sidebar" : "Collapse Sidebar"}
            >
                {isCollapsed ? <ChevronRight className="w-3 h-3" /> : <ChevronLeft className="w-3 h-3" />}
            </button>

            {/* Header */}
            <div className={cn("p-6", isCollapsed && "px-4")}>
                <Link href="/" className="flex items-center gap-2 mb-8 group">
                    <div className="w-8 h-8 rounded-lg bg-blue-600 flex items-center justify-center group-hover:rotate-12 transition-transform shrink-0">
                        <Brain className="w-5 h-5 text-white" />
                    </div>
                    {!isCollapsed && <span className="font-bold text-lg tracking-tight truncate">Second Brain</span>}
                </Link>

                <Link href="/chat">
                    <Button variant="primary" className={cn("w-full justify-start gap-2 h-11 rounded-xl shadow-blue-500/10", isCollapsed && "px-0 justify-center")}>
                        <Plus className="w-4 h-4 shrink-0" />
                        {!isCollapsed && <span>New Chat</span>}
                    </Button>
                </Link>
            </div>

            {/* Sessions List */}
            <div className="flex-1 overflow-y-auto px-3 space-y-1">
                <div className={cn("px-3 mb-2", isCollapsed && "px-0 flex justify-center")}>
                    {!isCollapsed ? (
                        <span className="text-[10px] font-bold uppercase tracking-wider text-zinc-500">Recent Chats</span>
                    ) : (
                        <div className="h-px bg-white/5 w-8" />
                    )}
                </div>

                {sessions.length === 0 ? (
                    !isCollapsed && (
                        <div className="px-3 py-4 text-xs text-zinc-500 italic">
                            No recent sessions
                        </div>
                    )
                ) : (
                    sessions.map((s) => {
                        const isActive = pathname.includes(s._id);
                        return (
                            <Link
                                key={s._id}
                                href={`/chat/${s._id}`}
                                className={cn(
                                    "group flex items-center gap-3 px-3 py-2.5 rounded-xl transition-all relative overflow-hidden",
                                    isActive ? "bg-white/10 text-white" : "text-zinc-400 hover:text-white hover:bg-white/5",
                                    isCollapsed && "justify-center px-0"
                                )}
                            >
                                <MessageSquare className={cn("w-4 h-4 shrink-0 opacity-50 group-hover:opacity-100", isActive && "opacity-100 text-blue-400")} />
                                {!isCollapsed && (
                                    <>
                                        <div className="flex-1 min-w-0">
                                            <div className="text-sm font-medium truncate">{s.title || "Untitled Chat"}</div>
                                            <div className="text-[10px] opacity-40 mt-0.5">
                                                {new Date(s.updatedAt).toLocaleDateString([], { month: 'short', day: 'numeric' })}
                                            </div>
                                        </div>
                                        <button
                                            onClick={(e) => handleDeleteSession(e, s._id)}
                                            className="opacity-0 group-hover:opacity-100 p-1.5 rounded-lg hover:bg-red-500/20 text-zinc-500 hover:text-red-400 transition-all"
                                            title="Delete Chat"
                                        >
                                            <Trash2 className="w-3.5 h-3.5" />
                                        </button>
                                    </>
                                )}
                            </Link>
                        );
                    })
                )}
            </div>
        </motion.aside>
    );
}

Update chat pages to dynamically update the sidebar

This API will help us store the current session to MongoDB

app/chat/page.tsx
"use client";

import { Brain, Mic, Send, Pause } from "lucide-react";
import { useEffect, useRef, useState } from "react";
import "regenerator-runtime/runtime";
import SpeechRecognition, { useSpeechRecognition } from "react-speech-recognition";
import { useRouter } from "next/navigation";
import { Button } from "@/app/components/ui/Button";

export default function ChatPage() {
  const [input, setInput] = useState("");
  const [isMounted, setIsMounted] = useState(false);
  const [loading, setLoading] = useState(false);
  const router = useRouter();

  useEffect(() => {
    setIsMounted(true);
  }, []);
  const {
    transcript,
    listening,
    resetTranscript,
    browserSupportsSpeechRecognition
  } = useSpeechRecognition();

  // Effect to append transcript when listening stops
  const isAppending = useRef(false);
  useEffect(() => {
    if (!listening && transcript) {
      if (isAppending.current) return;
      isAppending.current = true;
      setInput((prev) => (prev ? prev + " " + transcript : transcript));
      resetTranscript();
      setTimeout(() => { isAppending.current = false; }, 100);
    }
  }, [listening, transcript, resetTranscript]);

  async function handleSubmit(e: React.FormEvent) {
    e.preventDefault();
    if (!input.trim() || loading) return;

    setLoading(true);

    const res = await fetch("/api/sessions", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ firstMessage: input }),
    });

    const { sessionId } = await res.json();

    // Notify sidebar to refresh
    window.dispatchEvent(new CustomEvent("chat-updated"));

    // Redirect with first message
    router.push(
      `/chat/${sessionId}?q=${encodeURIComponent(input)})`
    );
  };

  function toggleListening() {
    if (listening) {
      SpeechRecognition.stopListening();
    } else {
      SpeechRecognition.startListening({ continuous: true });
    }
  }

  if (!isMounted) {
    return (
      <div className="flex flex-col h-full items-center justify-center p-8 bg-[#0a0a0a] animate-pulse">
        <div className="w-20 h-20 rounded-3xl bg-zinc-900 border border-white/5" />
      </div>
    );
  }

  if (!browserSupportsSpeechRecognition) {
    return (
      <div className="flex h-full items-center justify-center p-8 bg-[#0a0a0a] text-zinc-500">
        Browser doesn't support speech recognition.
      </div>
    );
  }

  if (typeof window !== "undefined" && !("webkitSpeechRecognition" in window)) {
    console.warn("Voice input not supported in this browser");
  }

  return (
    <div className="flex flex-col h-full items-center justify-center p-8 text-center bg-[#0a0a0a]">
      <div className="w-20 h-20 rounded-3xl bg-zinc-900 border border-white/5 flex items-center justify-center mb-6 shadow-2xl">
        <Brain className="w-10 h-10 text-blue-500/50" />
      </div>

      <h2 className="text-2xl font-bold text-white mb-2">
        Start a Conversation
      </h2>

      <p className="text-zinc-500 max-w-xs text-sm leading-relaxed mb-6">
        Ask a question to begin exploring your knowledge base.
      </p>

      {/* Input */}
      <form
        onSubmit={handleSubmit}
        className="w-full max-w-md relative"
      >
        <input
          value={input}
          onChange={(e) => setInput(e.target.value)}
          placeholder="Ask your second brain..."
          disabled={loading}
          className="
            w-full pr-14 py-4 px-5
            rounded-2xl bg-zinc-900/50
            border border-white/10
            focus:border-blue-500/50
            focus:ring-blue-500/20
            shadow-xl
            text-sm
          "
        />
        <div className="absolute right-14 top-2">
          <Button onClick={toggleListening} type="button" size="icon" className={`h-10 w-10 rounded-xl ${listening ? "bg-red-600 animate-pulse" : ""}`}>
            {listening ? <Pause className="w-4 h-4 text-white" /> : <Mic className="w-4 h-4 text-white" />}
          </Button>
        </div>

        <Button
          type="submit"
          disabled={loading || !input.trim()}
          className="absolute right-2 top-2 h-10 w-10 rounded-xl cursor-pointer"
        >
          <Send className="w-4 h-4 bg-white" />
        </Button>
      </form>
    </div>
  );
}                       
app/chat/[id]/page.tsx
"use client";

import { useChat } from "@ai-sdk/react";
import { DefaultChatTransport } from "ai";
import { useState, useRef, useEffect } from "react";
import "regenerator-runtime/runtime";
import SpeechRecognition, { useSpeechRecognition } from "react-speech-recognition";
import Markdown from "react-markdown";
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
import { vscDarkPlus } from 'react-syntax-highlighter/dist/esm/styles/prism';
import TypingIndicator from "@/app/components/TypingIndicator";
import { Button } from "@/app/components/ui/Button";
import { Input } from "@/app/components/ui/Input";
import { Send, User, Bot, Brain, Mic2, Mic, Pause, Trash2 } from "lucide-react";
import { useRouter } from "next/navigation";
import { motion, AnimatePresence } from "framer-motion";
import { use } from "react";
import { useSearchParams } from "next/navigation";
import rehypeRaw from "rehype-raw";
import rehypeSanitize from "rehype-sanitize";
import { markdownSchema } from "@/lib/markdownSanitizer";
import Image from "next/image";



import { Suspense } from "react";

function transformYoutubeLinks(text: string): string {
    const youtubeRegex = /(?:https?://)?(?:www.)?(?:youtube.com/(?:[^/
s]+/S+/|(?:v|e(?:mbed)?)/|S*?[?&]v=)|youtu.be/)([a-zA-Z0-9_-]{11})/g;

    return text.replace(youtubeRegex, (match, videoId) => {
        return `<iframe src="https://www.youtube.com/embed/${videoId}" allowfullscreen></iframe>`;
    });
}

function ChatContent({ id }: { id: string }) {
    const router = useRouter();
    const [input, setInput] = useState("");
    const [isMounted, setIsMounted] = useState(false);

    useEffect(() => {
        setIsMounted(true);
    }, []);
    const { messages, setMessages, sendMessage, status } = useChat({
        transport: new DefaultChatTransport({
            api: `/api/chat?sessionId=${id}`
        }),
        body: {
            sessionId: id
        }
    } as any);

    const [historyLoaded, setHistoryLoaded] = useState(false);
    const {
        transcript,
        listening,
        resetTranscript,
        browserSupportsSpeechRecognition
    } = useSpeechRecognition();

    // Effect to append transcript when listening stops
    const isAppending = useRef(false);
    useEffect(() => {
        if (!listening && transcript) {
            if (isAppending.current) return;
            isAppending.current = true;
            setInput((prev) => (prev ? prev + " " + transcript : transcript));
            resetTranscript();
            setTimeout(() => { isAppending.current = false; }, 100);
        }
    }, [listening, transcript, resetTranscript]);

    useEffect(() => {
        async function fetchHistory() {
            try {
                const res = await fetch(`/api/messages/${id}`);
                const data = await res.json();
                if (Array.isArray(data) && data.length > 0) {
                    const mappedMessages = data.map((m: any) => ({
                        id: m._id,
                        role: m.role as any,
                        parts: [{ type: "text" as const, text: m.content }],
                    }));
                    setMessages(mappedMessages);
                }
                setHistoryLoaded(true);
            } catch (error) {
                console.error("Failed to fetch history:", error);
                setHistoryLoaded(true);
            }
        }
        fetchHistory();
    }, [id, setMessages]);

    const bottomRef = useRef<HTMLDivElement>(null);

    useEffect(() => {
        bottomRef.current?.scrollIntoView({ behavior: "smooth" });
    }, [messages]);

    const onSubmit = async (e: React.FormEvent) => {
        e.preventDefault();
        if (!input.trim() || status === "submitted" || status === "streaming") return;
        await sendMessage({ text: input, metadata: { sessionId: id } });
        setInput("");
    };

    const isLoading = status === "submitted" || status === "streaming";

    const searchParams = useSearchParams();
    const firstQuery = searchParams.get("q");
    const sentRef = useRef(false);

    useEffect(() => {
        if (historyLoaded && firstQuery && !sentRef.current && messages.length === 0) {
            sentRef.current = true;
            sendMessage({
                text: firstQuery,
                metadata: { sessionId: id }
            })
        }
    }, [historyLoaded, firstQuery, sendMessage, messages.length, id]);

    const handleDeleteChat = async () => {
        if (!confirm("Are you sure you want to delete this chat session? This action cannot be undone.")) {
            return;
        }

        try {
            const res = await fetch(`/api/messages/${id}`, {
                method: "DELETE",
            });

            if (res.ok) {
                window.dispatchEvent(new CustomEvent("chat-updated"));
                router.push("/");
            } else {
                alert("Failed to delete chat.");
            }
        } catch (error) {
            console.error("Error deleting chat:", error);
            alert("An error occurred while deleting the chat.");
        }
    };


    function toggleListening() {
        if (listening) {
            SpeechRecognition.stopListening();
        } else {
            SpeechRecognition.startListening({ continuous: true });
        }
    }

    if (!isMounted) {
        return (
            <div className="flex flex-col h-screen bg-[#0a0a0a] animate-pulse">
                <header className="h-16 border-b border-white/5 flex items-center px-8 bg-black/50 backdrop-blur-xl">
                    <div className="flex items-center gap-3">
                        <div className="w-8 h-8 rounded-lg bg-zinc-800" />
                        <div className="h-4 w-32 bg-zinc-800 rounded" />
                    </div>
                </header>
                <main className="flex-1" />
            </div>
        );
    }

    if (!browserSupportsSpeechRecognition) {
        return (
            <div className="flex h-screen items-center justify-center bg-[#0a0a0a] text-zinc-500">
                Voice input not supported in this browser.
            </div>
        );
    }

    if (typeof window !== "undefined" && !("webkitSpeechRecognition" in window)) {
        console.warn("Voice input not supported in this browser");
    }

    return (
        <div className="flex flex-col h-screen bg-[#0a0a0a]">
            {/* Header */}
            <header className="h-16 border-b border-white/5 flex items-center justify-between px-8 bg-black/50 backdrop-blur-xl shrink-0">
                <div className="flex items-center gap-3">
                    <div className="w-8 h-8 rounded-lg bg-zinc-800 flex items-center justify-center border border-white/10">
                        <Brain className="w-4 h-4 text-blue-400" />
                    </div>
                    <div>
                        <h1 className="text-sm font-semibold text-white">Knowledge Session</h1>
                        <p className="text-[10px] text-zinc-500 uppercase tracking-widest">ID: {id.slice(-6)}</p>
                    </div>
                </div>

                <Button
                    onClick={handleDeleteChat}
                    variant="ghost"
                    size="icon"
                    className="text-zinc-500 hover:text-red-400 hover:bg-red-400/10 transition-colors"
                    title="Delete Chat"
                >
                    <Trash2 className="w-4 h-4" />
                </Button>
            </header>

            {/* Messages */}
            <main className="flex-1 overflow-y-auto px-4 md:px-0 py-8">
                <div className="max-w-3xl mx-auto space-y-8 pb-12">
                    {messages.length === 0 && (
                        <motion.div
                            initial={{ opacity: 0, y: 20 }}
                            animate={{ opacity: 1, y: 0 }}
                            className="flex flex-col items-center justify-center py-20 text-center"
                        >
                            <div className="w-16 h-16 rounded-3xl bg-blue-600/10 border border-blue-500/20 flex items-center justify-center mb-6">
                                <Brain className="w-8 h-8 text-blue-500" />
                            </div>
                            <h2 className="text-2xl font-bold text-white mb-2">Deep Knowledge Retrieval</h2>
                            <p className="text-zinc-500 max-w-sm">
                                This session is ready. Ask anything about your indexed documents and notes.
                            </p>
                        </motion.div>
                    )}

                    <AnimatePresence initial={false}>
                        {messages.map((m) => (
                            <motion.div
                                key={m.id}
                                initial={{ opacity: 0, y: 10 }}
                                animate={{ opacity: 1, y: 0 }}
                                className={`flex gap-4 ${m.role === "user" ? "flex-row-reverse" : "flex-row"}`}
                            >
                                <div className={`w-8 h-8 rounded-full flex items-center justify-center shrink-0 border ${m.role === "user"
                                    ? "bg-blue-600/10 border-blue-500/20 text-blue-400"
                                    : "bg-zinc-800 border-white/10 text-zinc-400"
                                    }`}>
                                    {m.role === "user" ? <User className="w-4 h-4" /> : <Bot className="w-4 h-4" />}
                                </div>

                                <div className={`flex flex-col max-w-[85%] ${m.role === "user" ? "items-end" : "items-start"}`}>
                                    <div className={`px-5 py-3 rounded-2xl ${m.role === "user"
                                        ? "bg-blue-600 text-white"
                                        : "bg-zinc-900 border border-white/5 text-zinc-200"
                                        }`}>
                                        {m.parts.map((p, i) => (
                                            p.type === "text" ? (
                                                <div key={i} className="prose prose-sm prose-invert max-w-none">
                                                    {p.text.split("

").map((block, idx) => {

                                                        return (
                                                            <Markdown
                                                                rehypePlugins={[
                                                                    rehypeRaw,
                                                                    [rehypeSanitize, markdownSchema]
                                                                ]}
                                                                key={idx}
                                                                components={{

                                                                    code({ inline, className, children, ...props }: any) {
                                                                        const match = /language-(w+)/.exec(className || "");
                                                                        return !inline && match ? (
                                                                            <SyntaxHighlighter
                                                                                style={vscDarkPlus}
                                                                                language={match[1]}
                                                                                PreTag="div"
                                                                                className="rounded-xl my-4 text-xs"
                                                                                {...props}
                                                                            >
                                                                                {String(children).replace(/
$/, "")}
                                                                            </SyntaxHighlighter>
                                                                        ) : (
                                                                            <code
                                                                                className="bg-white/10 px-1.5 py-0.5 rounded text-sm"
                                                                                {...props}
                                                                            >
                                                                                {children}
                                                                            </code>
                                                                        );
                                                                    },
                                                                    iframe({ src }) {
                                                                        if (!src) return null;

                                                                        return (
                                                                            <div className="my-4 w-full overflow-hidden rounded-xl border border-white/10 bg-black">
                                                                                <div className="relative w-full" style={{ paddingTop: "56.25%" }}>
                                                                                    <iframe
                                                                                        src={src}
                                                                                        title="Embedded video"
                                                                                        className="absolute top-0 left-0 w-full h-full"
                                                                                        allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
                                                                                        allowFullScreen
                                                                                        referrerPolicy="strict-origin-when-cross-origin"
                                                                                    />
                                                                                </div>
                                                                            </div>
                                                                        );
                                                                    },
                                                                    img({ src, alt }) {
                                                                        if (!src) return null;

                                                                        return (
                                                                            <div className="group relative my-8 overflow-hidden rounded-2xl border border-white/10 bg-zinc-900 shadow-2xl transition-all duration-300 hover:border-white/20">
                                                                                <Image
                                                                                    src={(src as string).replace("/public", "")}
                                                                                    alt={alt ?? "Knowledge Image"}
                                                                                    className="h-auto w-full transition-transform duration-700 ease-out group-hover:scale-[1.02]"
                                                                                    loading="lazy"
                                                                                    width={1600}
                                                                                    height={900}
                                                                                    quality={100}
                                                                                />
                                                                            </div>
                                                                        );
                                                                    },
                                                                    p({ children }) {
                                                                        return <div className="my-3">{children}</div>;
                                                                    },

                                                                }}
                                                            >
                                                                {transformYoutubeLinks(block)}
                                                            </Markdown>
                                                        );
                                                    })}
                                                </div>
                                            ) : null
                                        ))}
                                    </div>
                                    <div className="mt-1 px-2 text-[10px] text-zinc-600">
                                        {m.role === "user" ? "You" : "Second Brain"}
                                    </div>
                                </div >
                            </motion.div >
                        ))
                        }
                    </AnimatePresence >

                    {isLoading && (
                        <div className="flex gap-4">
                            <div className="w-8 h-8 rounded-full bg-zinc-800 border border-white/10 flex items-center justify-center text-zinc-400">
                                <Bot className="w-4 h-4" />
                            </div>
                            <div className="bg-zinc-900/50 border border-white/5 rounded-2xl px-4 py-2">
                                <TypingIndicator />
                            </div>
                        </div>
                    )}
                    <div ref={bottomRef} className="h-4" />
                </div >
            </main >

            {/* Input Area */}
            < div className="p-6 bg-linear-to-t from-black via-black to-transparent" >
                <form onSubmit={onSubmit} className="max-w-3xl mx-auto relative group">
                    <Input
                        value={input}
                        onChange={(e) => setInput(e.target.value)}
                        placeholder="Search your brain..."
                        disabled={isLoading}
                        className="pr-14 h-14 rounded-2xl bg-zinc-900/50 border-white/10 focus:border-blue-500/50 focus:ring-blue-500/20 shadow-2xl transition-all"
                    />

                    <div className="absolute right-14 top-2">
                        <Button onClick={toggleListening} type="button" size="icon" className={`h-10 w-10 rounded-xl cursor-pointer ${listening ? "bg-red-600 animate-pulse" : ""}`}>
                            {listening ? <Pause className="w-4 h-4 text-white" /> : <Mic className="w-4 h-4 text-white" />}
                        </Button>
                    </div>

                    <div className="absolute right-2 top-2">
                        <Button
                            type="submit"
                            size="icon"
                            disabled={isLoading || !input.trim()}
                            className="h-10 w-10 rounded-xl cursor-pointer"
                        >
                            <Send className="w-4 h-4" />
                        </Button>
                    </div>
                </form>
                <p className="text-center mt-3 text-[10px] text-zinc-600 tracking-wider">
                    AI generated responses may be inaccurate. Check citations for verification.
                </p>
            </div >
        </div >
    );
}

export default function ChatSessionPage({ params }: { params: Promise<{ id: string }> }) {
    const { id } = use(params);
    return (
        <Suspense fallback={<div className="flex h-screen items-center justify-center bg-[#0a0a0a] text-zinc-500">Loading session...</div>}>
            <ChatContent id={id} />
        </Suspense>
    );
}

You Did It! 🥳

Congratulations! You've successfully built your very own custom ChatGPT-like application. From persistent session management to a real-time streaming interface, you've mastered the core components of modern AI chat platforms.

Watch the final project wrap-up and demo here: