"use client"; import { useChat } from "@ai-sdk/react"; import { DefaultChatTransport } from "ai"; import { useRouter, useSearchParams } from "next/navigation"; import { useEffect, useRef, useState } from "react"; import useSWR, { useSWRConfig } from "swr"; import { unstable_serialize } from "swr/infinite"; import { ChatHeader } from "@/components/chat-header"; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, } from "@/components/ui/alert-dialog"; import { useArtifactSelector } from "@/hooks/use-artifact"; import { useAutoResume } from "@/hooks/use-auto-resume"; import { useChatVisibility } from "@/hooks/use-chat-visibility"; import type { Vote } from "@/lib/db/schema"; import { OpenChatError } from "@/lib/errors"; import type { Attachment, ChatMessage } from "@/lib/types"; import { fetcher, fetchWithErrorHandlers, generateUUID } from "@/lib/utils"; import { Artifact } from "./artifact"; import { useDataStream } from "./data-stream-provider"; import { Messages } from "./messages"; import { MultimodalInput } from "./multimodal-input"; import { getChatHistoryPaginationKey } from "./sidebar-history"; import { toast } from "./toast"; import type { VisibilityType } from "./visibility-selector"; export function Chat({ id, initialMessages, initialChatModel, initialVisibilityType, isReadonly, autoResume, }: { id: string; initialMessages: ChatMessage[]; initialChatModel: string; initialVisibilityType: VisibilityType; isReadonly: boolean; autoResume: boolean; }) { const router = useRouter(); const { visibilityType } = useChatVisibility({ chatId: id, initialVisibilityType, }); const { mutate } = useSWRConfig(); // Handle browser back/forward navigation useEffect(() => { const handlePopState = () => { // When user navigates back/forward, refresh to sync with URL router.refresh(); }; window.addEventListener("popstate", handlePopState); return () => window.removeEventListener("popstate", handlePopState); }, [router]); const { setDataStream } = useDataStream(); const [input, setInput] = useState(""); const [showCreditCardAlert, setShowCreditCardAlert] = useState(false); const [currentModelId, setCurrentModelId] = useState(initialChatModel); const currentModelIdRef = useRef(currentModelId); useEffect(() => { currentModelIdRef.current = currentModelId; }, [currentModelId]); const { messages, setMessages, sendMessage, status, stop, regenerate, resumeStream, addToolApprovalResponse, } = useChat({ id, messages: initialMessages, generateId: generateUUID, sendAutomaticallyWhen: ({ messages: currentMessages }) => { const lastMessage = currentMessages.at(-1); const shouldContinue = lastMessage?.parts?.some( (part) => "state" in part && part.state === "approval-responded" && "approval" in part && (part.approval as { approved?: boolean })?.approved === true ) ?? false; return shouldContinue; }, transport: new DefaultChatTransport({ api: `${process.env.NEXT_PUBLIC_BASE_PATH ?? ""}/api/chat`, fetch: fetchWithErrorHandlers, prepareSendMessagesRequest(request) { const lastMessage = request.messages.at(-1); const isToolApprovalContinuation = lastMessage?.role !== "user" || request.messages.some((msg) => msg.parts?.some((part) => { const state = (part as { state?: string }).state; return ( state === "approval-responded" || state === "output-denied" ); }) ); return { body: { id: request.id, ...(isToolApprovalContinuation ? { messages: request.messages } : { message: lastMessage }), selectedChatModel: currentModelIdRef.current, selectedVisibilityType: visibilityType, ...request.body, }, }; }, }), onData: (dataPart) => { setDataStream((ds) => (ds ? [...ds, dataPart] : [])); }, onFinish: () => { mutate(unstable_serialize(getChatHistoryPaginationKey)); }, onError: (error) => { if (error instanceof OpenChatError) { if ( error.message?.includes("AI Gateway requires a valid credit card") ) { setShowCreditCardAlert(true); } else { toast({ type: "error", description: error.message, }); } } }, }); const searchParams = useSearchParams(); const query = searchParams.get("query"); const [hasAppendedQuery, setHasAppendedQuery] = useState(false); useEffect(() => { if (query && !hasAppendedQuery) { sendMessage({ role: "user" as const, parts: [{ type: "text", text: query }], }); setHasAppendedQuery(true); window.history.replaceState({}, "", `${process.env.NEXT_PUBLIC_BASE_PATH ?? ""}/chat/${id}`); } }, [query, sendMessage, hasAppendedQuery, id]); const { data: votes } = useSWR( messages.length >= 2 ? `${process.env.NEXT_PUBLIC_BASE_PATH ?? ""}/api/vote?chatId=${id}` : null, fetcher ); const [attachments, setAttachments] = useState([]); const isArtifactVisible = useArtifactSelector((state) => state.isVisible); useAutoResume({ autoResume, initialMessages, resumeStream, setMessages, }); return ( <>
{!isReadonly && ( )}
Activate AI Gateway This application requires{" "} {process.env.NODE_ENV === "production" ? "the owner" : "you"} to activate Vercel AI Gateway. Cancel { window.open( "https://vercel.com/d?to=%2F%5Bteam%5D%2F%7E%2Fai%3Fmodal%3Dadd-credit-card", "_blank" ); window.location.href = `${process.env.NEXT_PUBLIC_BASE_PATH ?? ""}/`; }} > Activate ); }