diff --git a/ai/actions.ts b/ai/actions.ts index 7aa969b7..0e3686ba 100644 --- a/ai/actions.ts +++ b/ai/actions.ts @@ -3,87 +3,90 @@ import { z } from "zod"; import { geminiFlashModel } from "."; -export async function generateSampleFlightStatus({ - flightNumber, +export async function generateSampleTrainStatus({ + trainNumber, date, }: { - flightNumber: string; + trainNumber: string; date: string; }) { - const { object: flightStatus } = await generateObject({ + const { object: trainStatus } = await generateObject({ model: geminiFlashModel, - prompt: `Flight status for flight number ${flightNumber} on ${date}`, + prompt: `Train status for train number ${trainNumber} on ${date}`, schema: z.object({ - flightNumber: z.string().describe("Flight number, e.g., BA123, AA31"), + trainNumber: z.string().describe("Train number, e.g., TR123, ICE31"), departure: z.object({ cityName: z.string().describe("Name of the departure city"), - airportCode: z.string().describe("IATA code of the departure airport"), - airportName: z.string().describe("Full name of the departure airport"), + stationCode: z.string().describe("Code of the departure station"), + stationName: z.string().describe("Full name of the departure station"), timestamp: z.string().describe("ISO 8601 departure date and time"), - terminal: z.string().describe("Departure terminal"), + platform: z.string().describe("Departure platform"), gate: z.string().describe("Departure gate"), }), arrival: z.object({ cityName: z.string().describe("Name of the arrival city"), - airportCode: z.string().describe("IATA code of the arrival airport"), - airportName: z.string().describe("Full name of the arrival airport"), + stationCode: z.string().describe("Code of the arrival station"), + stationName: z.string().describe("Full name of the arrival station"), timestamp: z.string().describe("ISO 8601 arrival date and time"), - terminal: z.string().describe("Arrival terminal"), + platform: z.string().describe("Arrival platform"), gate: z.string().describe("Arrival gate"), }), totalDistanceInMiles: z .number() - .describe("Total flight distance in miles"), + .describe("Total train distance in miles"), }), }); - return flightStatus; + return trainStatus; } -export async function generateSampleFlightSearchResults({ + +export async function generateSampleTrainSearchResults({ origin, destination, + date, }: { origin: string; destination: string; + date: string; }) { - const { object: flightSearchResults } = await generateObject({ + const { object: trainSearchResults } = await generateObject({ model: geminiFlashModel, - prompt: `Generate search results for flights from ${origin} to ${destination}, limit to 4 results`, + prompt: `Generate search results for trains from ${origin} to ${destination} on ${date}, limit to 4 results`, output: "array", schema: z.object({ id: z .string() - .describe("Unique identifier for the flight, like BA123, AA31, etc."), + .describe("Unique identifier for the train, like TR123, ICE31, etc."), departure: z.object({ cityName: z.string().describe("Name of the departure city"), - airportCode: z.string().describe("IATA code of the departure airport"), + stationCode: z.string().describe("Code of the departure station"), timestamp: z.string().describe("ISO 8601 departure date and time"), }), arrival: z.object({ cityName: z.string().describe("Name of the arrival city"), - airportCode: z.string().describe("IATA code of the arrival airport"), + stationCode: z.string().describe("Code of the arrival station"), timestamp: z.string().describe("ISO 8601 arrival date and time"), }), - airlines: z.array( - z.string().describe("Airline names, e.g., American Airlines, Emirates"), + operators: z.array( + z.string().describe("Train operator names, e.g., Amtrak, Eurostar"), ), - priceInUSD: z.number().describe("Flight price in US dollars"), - numberOfStops: z.number().describe("Number of stops during the flight"), + priceInUSD: z.number().describe("Train price in US dollars"), + numberOfStops: z.number().describe("Number of stops during the train journey"), }), }); - return { flights: flightSearchResults }; + return { trains: trainSearchResults }; } export async function generateSampleSeatSelection({ - flightNumber, + trainNumber, }: { - flightNumber: string; + trainNumber: string; }) { const { object: rows } = await generateObject({ model: geminiFlashModel, - prompt: `Simulate available seats for flight number ${flightNumber}, 6 seats on each row and 5 rows in total, adjust pricing based on location of seat`, + prompt: `Simulate available seats for train number ${trainNumber}, 6 seats on each row and 5 rows in total, adjust pricing based on location of seat`, output: "array", schema: z.array( z.object({ @@ -103,26 +106,26 @@ export async function generateSampleSeatSelection({ export async function generateReservationPrice(props: { seats: string[]; - flightNumber: string; + trainNumber: string; departure: { cityName: string; - airportCode: string; + stationCode: string; timestamp: string; gate: string; - terminal: string; + platform: string; }; arrival: { cityName: string; - airportCode: string; + stationCode: string; timestamp: string; gate: string; - terminal: string; + platform: string; }; passengerName: string; }) { const { object: reservation } = await generateObject({ model: geminiFlashModel, - prompt: `Generate price for the following reservation \n\n ${JSON.stringify(props, null, 2)}`, + prompt: `Generate price for the following train reservation \n\n ${JSON.stringify(props, null, 2)}`, schema: z.object({ totalPriceInUSD: z .number() diff --git a/app/(auth)/api/auth/[...nextauth]/route.ts b/app/api/auth/[...nextauth]/route.ts similarity index 100% rename from app/(auth)/api/auth/[...nextauth]/route.ts rename to app/api/auth/[...nextauth]/route.ts diff --git a/app/(chat)/api/chat/route.ts b/app/api/chat/route.ts similarity index 62% rename from app/(chat)/api/chat/route.ts rename to app/api/chat/route.ts index 32f0eb01..03028cdc 100644 --- a/app/(chat)/api/chat/route.ts +++ b/app/api/chat/route.ts @@ -1,11 +1,11 @@ import { convertToCoreMessages, Message, streamText } from "ai"; import { z } from "zod"; -import { geminiProModel } from "@/ai"; +import { geminiProModel, geminiFlashModel } from "@/ai"; import { generateReservationPrice, - generateSampleFlightSearchResults, - generateSampleFlightStatus, + generateSampleTrainSearchResults, + generateSampleTrainStatus, generateSampleSeatSelection, } from "@/ai/actions"; import { auth } from "@/app/(auth)/auth"; @@ -33,22 +33,23 @@ export async function POST(request: Request) { ); const result = await streamText({ - model: geminiProModel, - system: `\n - - you help users book flights! - - keep your responses limited to a sentence. - - DO NOT output lists. - - after every tool call, pretend you're showing the result to the user and keep your response limited to a phrase. + model: geminiFlashModel, + system: ` + - you help users book trains! + - keep your responses limited to a sentence, except when showing train search results. + - When showing train search results, you MAY output a list of train options for the user to choose from. + - When the user selects a train (by clicking or sending a train number), immediately proceed to seat selection for that train. Do NOT ask for further preference or clarification if a train is already selected. + - after every tool call, pretend you're showing the result to the user and keep your response limited to a phrase unless showing train options. - today's date is ${new Date().toLocaleDateString()}. - ask follow up questions to nudge user into the optimal flow - ask for any details you don't know, like name of passenger, etc.' - C and D are aisle seats, A and F are window seats, B and E are middle seats - - assume the most popular airports for the origin and destination + - assume the most popular stations for the origin and destination - here's the optimal flow - - search for flights - - choose flight + - search for trains + - choose train - select seats - - create reservation (ask user whether to proceed with payment or change reservation) + - create reservation (ask user whether to proceed with payment or change reservation; if details are complete, prompt user to confirm and proceed to payment) - authorize payment (requires user consent, wait for user to finish payment and let you know when done) - display boarding pass (DO NOT display boarding pass without verifying payment) ' @@ -70,68 +71,75 @@ export async function POST(request: Request) { return weatherData; }, }, - displayFlightStatus: { - description: "Display the status of a flight", + displayTrainStatus: { + description: "Display the status of a train", parameters: z.object({ - flightNumber: z.string().describe("Flight number"), - date: z.string().describe("Date of the flight"), + trainNumber: z.string().describe("Train number"), + date: z.string().describe("Date of the train"), }), - execute: async ({ flightNumber, date }) => { - const flightStatus = await generateSampleFlightStatus({ - flightNumber, + execute: async ({ trainNumber, date }) => { + console.log('[displayTrainStatus] start', { trainNumber, date }); + const trainStatus = await generateSampleTrainStatus({ + trainNumber, date, }); - - return flightStatus; + console.log('[displayTrainStatus] end', trainStatus); + return trainStatus; }, }, - searchFlights: { - description: "Search for flights based on the given parameters", + searchTrains: { + description: "Search for trains based on the given parameters", parameters: z.object({ - origin: z.string().describe("Origin airport or city"), - destination: z.string().describe("Destination airport or city"), + origin: z.string().describe("Origin station or city"), + destination: z.string().describe("Destination station or city"), + date: z.string().describe("Date of travel (ISO or natural language)") }), - execute: async ({ origin, destination }) => { - const results = await generateSampleFlightSearchResults({ + execute: async ({ origin, destination, date }) => { + console.log('[searchTrains] start', { origin, destination, date }); + const results = await generateSampleTrainSearchResults({ origin, destination, + date, }); - + console.log('[searchTrains] end', results); return results; }, }, selectSeats: { - description: "Select seats for a flight", + description: "Select seats for a train", parameters: z.object({ - flightNumber: z.string().describe("Flight number"), + trainNumber: z.string().describe("Train number"), }), - execute: async ({ flightNumber }) => { - const seats = await generateSampleSeatSelection({ flightNumber }); + execute: async ({ trainNumber }) => { + console.log('[selectSeats] start', { trainNumber }); + const seats = await generateSampleSeatSelection({ trainNumber }); + console.log('[selectSeats] end', seats); return seats; }, }, createReservation: { - description: "Display pending reservation details", + description: "Display pending train reservation details", parameters: z.object({ seats: z.string().array().describe("Array of selected seat numbers"), - flightNumber: z.string().describe("Flight number"), + trainNumber: z.string().describe("Train number"), departure: z.object({ cityName: z.string().describe("Name of the departure city"), - airportCode: z.string().describe("Code of the departure airport"), + stationCode: z.string().describe("Code of the departure station"), timestamp: z.string().describe("ISO 8601 date of departure"), gate: z.string().describe("Departure gate"), - terminal: z.string().describe("Departure terminal"), + platform: z.string().describe("Departure platform"), }), arrival: z.object({ cityName: z.string().describe("Name of the arrival city"), - airportCode: z.string().describe("Code of the arrival airport"), + stationCode: z.string().describe("Code of the arrival station"), timestamp: z.string().describe("ISO 8601 date of arrival"), gate: z.string().describe("Arrival gate"), - terminal: z.string().describe("Arrival terminal"), + platform: z.string().describe("Arrival platform"), }), passengerName: z.string().describe("Name of the passenger"), }), execute: async (props) => { + console.log('[createReservation] start', props); const { totalPriceInUSD } = await generateReservationPrice(props); const session = await auth(); @@ -143,9 +151,19 @@ export async function POST(request: Request) { userId: session.user.id, details: { ...props, totalPriceInUSD }, }); - - return { id, ...props, totalPriceInUSD }; + console.log('[createReservation] end', { id, ...props, totalPriceInUSD }); + // Immediately prompt for payment after reservation creation + return { + id, + ...props, + totalPriceInUSD, + nextTool: { + name: "authorizePayment", + parameters: { reservationId: id }, + }, + }; } else { + console.log('[createReservation] error: not signed in'); return { error: "User is not signed in to perform this action!", }; @@ -161,6 +179,7 @@ export async function POST(request: Request) { .describe("Unique identifier for the reservation"), }), execute: async ({ reservationId }) => { + console.log('[authorizePayment] start', { reservationId }); return { reservationId }; }, }, @@ -172,8 +191,9 @@ export async function POST(request: Request) { .describe("Unique identifier for the reservation"), }), execute: async ({ reservationId }) => { + console.log('[verifyPayment] start', { reservationId }); const reservation = await getReservationById({ id: reservationId }); - + console.log('[verifyPayment] reservation', reservation); if (reservation.hasCompletedPayment) { return { hasCompletedPayment: true }; } else { @@ -182,7 +202,7 @@ export async function POST(request: Request) { }, }, displayBoardingPass: { - description: "Display a boarding pass", + description: "Display a train boarding pass", parameters: z.object({ reservationId: z .string() @@ -190,26 +210,27 @@ export async function POST(request: Request) { passengerName: z .string() .describe("Name of the passenger, in title case"), - flightNumber: z.string().describe("Flight number"), + trainNumber: z.string().describe("Train number"), seat: z.string().describe("Seat number"), departure: z.object({ cityName: z.string().describe("Name of the departure city"), - airportCode: z.string().describe("Code of the departure airport"), - airportName: z.string().describe("Name of the departure airport"), + stationCode: z.string().describe("Code of the departure station"), + stationName: z.string().describe("Name of the departure station"), timestamp: z.string().describe("ISO 8601 date of departure"), - terminal: z.string().describe("Departure terminal"), + platform: z.string().describe("Departure platform"), gate: z.string().describe("Departure gate"), }), arrival: z.object({ cityName: z.string().describe("Name of the arrival city"), - airportCode: z.string().describe("Code of the arrival airport"), - airportName: z.string().describe("Name of the arrival airport"), + stationCode: z.string().describe("Code of the arrival station"), + stationName: z.string().describe("Name of the arrival station"), timestamp: z.string().describe("ISO 8601 date of arrival"), - terminal: z.string().describe("Arrival terminal"), + platform: z.string().describe("Arrival platform"), gate: z.string().describe("Arrival gate"), }), }), execute: async (boardingPass) => { + console.log('[displayBoardingPass] start', boardingPass); return boardingPass; }, }, diff --git a/app/(chat)/api/files/upload/route.ts b/app/api/files/upload/route.ts similarity index 100% rename from app/(chat)/api/files/upload/route.ts rename to app/api/files/upload/route.ts diff --git a/app/(chat)/api/history/route.ts b/app/api/history/route.ts similarity index 100% rename from app/(chat)/api/history/route.ts rename to app/api/history/route.ts diff --git a/app/(chat)/api/reservation/route.ts b/app/api/reservation/route.ts similarity index 77% rename from app/(chat)/api/reservation/route.ts rename to app/api/reservation/route.ts index ca6789a1..e64a5252 100644 --- a/app/(chat)/api/reservation/route.ts +++ b/app/api/reservation/route.ts @@ -59,10 +59,20 @@ export async function PATCH(request: Request) { return new Response("Reservation is already paid!", { status: 409 }); } - const { magicWord } = await request.json(); - - if (magicWord.toLowerCase() !== "vercel") { - return new Response("Invalid magic word!", { status: 400 }); + const body = await request.json(); + + // Legacy flow: magicWord + if (body.magicWord !== undefined) { + if (typeof body.magicWord !== "string" || body.magicWord.toLowerCase() !== "vercel") { + return new Response("Invalid magic word!", { status: 400 }); + } + } else { + // New flow: card details + const { cardNumber, expiry, cvv, name } = body; + if (!cardNumber || !expiry || !cvv || !name) { + return new Response("Missing card details!", { status: 400 }); + } + // Optionally: validate card details format here } const updatedReservation = await updateReservation({ diff --git a/app/layout.tsx b/app/layout.tsx index 1813b10f..99f88e2e 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -1,15 +1,16 @@ import { Metadata } from "next"; +import { SessionProvider } from "next-auth/react"; import { Toaster } from "sonner"; import { Navbar } from "@/components/custom/navbar"; -import { ThemeProvider } from "@/components/custom/theme-provider"; +import ThemeClientLayout from "@/components/custom/theme-client-layout"; import "./globals.css"; export const metadata: Metadata = { - metadataBase: new URL("https://gemini.vercel.ai"), - title: "Next.js Gemini Chatbot", - description: "Next.js chatbot template using the AI SDK and Gemini.", + metadataBase: new URL("https://italiachat.vercel.ai"), + title: "Italia Chat", + description: "Italia Rail chatbot template for train booking.", }; export default async function RootLayout({ @@ -20,16 +21,13 @@ export default async function RootLayout({ return ( - - - - {children} - + + + + + {children} + + ); diff --git a/components/custom/chat.tsx b/components/custom/chat.tsx index 022074a9..528cd160 100644 --- a/components/custom/chat.tsx +++ b/components/custom/chat.tsx @@ -2,7 +2,8 @@ import { Attachment, Message } from "ai"; import { useChat } from "ai/react"; -import { useState } from "react"; +import Image from "next/image"; +import { useEffect, useState } from "react"; import { Message as PreviewMessage } from "@/components/custom/message"; import { useScrollToBottom } from "@/components/custom/use-scroll-to-bottom"; @@ -28,51 +29,121 @@ export function Chat({ }, }); - const [messagesContainerRef, messagesEndRef] = - useScrollToBottom(); - + const [messagesContainerRef, messagesEndRef] = useScrollToBottom(); const [attachments, setAttachments] = useState>([]); - return ( -
-
-
- {messages.length === 0 && } + // Show initial state if no messages (except system/assistant) + const hasUserMessages = messages.some(m => m.role === "user" || m.role === "assistant"); + const isInitialChatLoad = messages.length === 0 && initialMessages.length === 0; - {messages.map((message) => ( - - ))} + // Scroll to bottom on new message or loading + useEffect(() => { + if (messagesEndRef && messagesEndRef.current) { + messagesEndRef.current.scrollIntoView({ behavior: "smooth" }); + } + }, [messages.length, isLoading, messagesEndRef]); + return ( +
+ + {!hasUserMessages ? ( + // Initial state: centered input and logo +
+ {/* Updated Rail logo */} + {isInitialChatLoad && ( +
+ + +
+ )} +
+ + +
+ ) : ( + // Chat state: messages and input at bottom +
+ ref={messagesContainerRef} + className="flex flex-col gap-4 size-full max-w-2xl mx-auto overflow-y-scroll custom-scrollbar pb-32" + > + {messages.map((message) => ( + + ))} +
+
+
+ +
- -
- - -
+ )}
); } diff --git a/components/custom/history.tsx b/components/custom/history.tsx index 8c8b2725..cb05f2c2 100644 --- a/components/custom/history.tsx +++ b/components/custom/history.tsx @@ -44,7 +44,7 @@ import { SheetTitle, } from "../ui/sheet"; -export const History = ({ user }: { user: User | undefined }) => { +export const History = ({ user, triggerOnly = false }: { user: User | undefined; triggerOnly?: boolean }) => { const { id } = useParams(); const pathname = usePathname(); @@ -85,8 +85,148 @@ export const History = ({ user }: { user: User | undefined }) => { setShowDeleteDialog(false); }; + if (triggerOnly) { + return ( + <> + + { + setIsHistoryVisible(state); + }} + > + + + + History + + {history === undefined ? "loading" : history.length} chats + + + + {/* ...existing code for history list... */} +
+ {user && ( + + )} +
+ {/* ...existing code for chat list, loading, empty, etc... */} + {!user ? ( +
+ +
Login to save and revisit previous chats!
+
+ ) : null} + {!isLoading && history?.length === 0 && user ? ( +
+ +
No chats found
+
+ ) : null} + {isLoading && user ? ( +
+ {[44, 32, 28, 52].map((item) => ( +
+
+
+ ))} +
+ ) : null} + {history && + history.map((chat) => ( +
+ + + + + + + + + + + +
+ ))} +
+
+ + + + + + Are you absolutely sure? + + This action cannot be undone. This will permanently delete your + chat and remove it from our servers. + + + + Cancel + + Continue + + + + + + ); + } + // ...existing code for full history panel (if not triggerOnly)... return ( - <> +
- { setIsHistoryVisible(state); }} > - + History @@ -112,17 +251,7 @@ export const History = ({ user }: { user: User | undefined }) => { - -
-
-
History
- -
- {history === undefined ? "loading" : history.length} chats -
-
-
- + {/* ...existing code for history list... */}
{user && ( )} -
+ {/* ...existing code for chat list, loading, empty, etc... */} {!user ? (
Login to save and revisit previous chats!
) : null} - {!isLoading && history?.length === 0 && user ? (
No chats found
) : null} - {isLoading && user ? (
{[44, 32, 28, 52].map((item) => ( @@ -162,7 +289,6 @@ export const History = ({ user }: { user: User | undefined }) => { ))}
) : null} - {history && history.map((chat) => (
{ {getTitleFromChat(chat)} -
- @@ -236,6 +360,6 @@ export const History = ({ user }: { user: User | undefined }) => { - +
); }; diff --git a/components/custom/message.tsx b/components/custom/message.tsx index 031c8a94..fb7c59d7 100644 --- a/components/custom/message.tsx +++ b/components/custom/message.tsx @@ -8,13 +8,14 @@ import { BotIcon, UserIcon } from "./icons"; import { Markdown } from "./markdown"; import { PreviewAttachment } from "./preview-attachment"; import { Weather } from "./weather"; -import { AuthorizePayment } from "../flights/authorize-payment"; -import { DisplayBoardingPass } from "../flights/boarding-pass"; -import { CreateReservation } from "../flights/create-reservation"; -import { FlightStatus } from "../flights/flight-status"; -import { ListFlights } from "../flights/list-flights"; -import { SelectSeats } from "../flights/select-seats"; -import { VerifyPayment } from "../flights/verify-payment"; +import { AuthorizePayment } from "../trains/authorize-payment"; +import { DisplayBoardingPass } from "../trains/boarding-pass"; +import { CreateReservation } from "../trains/create-reservation"; +import { ListTrains } from "../trains/list-trains"; +import { SelectSeats } from "../trains/select-seats"; +import { TrainStatus } from "../trains/train-status"; +import { VerifyPayment } from "../trains/verify-payment"; + export const Message = ({ chatId, @@ -31,7 +32,7 @@ export const Message = ({ }) => { return ( @@ -42,7 +43,11 @@ export const Message = ({
{content && typeof content === "string" && (
- {content} + {/* If train options are present, show a simple summary instead of full details */} + {content.match(/train options from Rome to Florence:/i) + ? I found several train options for your route. Please select or book your preferred train from the list above. + : {content} + }
)} @@ -58,12 +63,12 @@ export const Message = ({
{toolName === "getWeather" ? ( - ) : toolName === "displayFlightStatus" ? ( - - ) : toolName === "searchFlights" ? ( - + ) : toolName === "displayTrainStatus" ? ( + + ) : toolName === "searchTrains" ? ( + ) : toolName === "selectSeats" ? ( - + ) : toolName === "createReservation" ? ( Object.keys(result).includes("error") ? null : ( @@ -81,15 +86,16 @@ export const Message = ({ ); } else { return ( -
+
+
Processing...
{toolName === "getWeather" ? ( - ) : toolName === "displayFlightStatus" ? ( - - ) : toolName === "searchFlights" ? ( - + ) : toolName === "displayTrainStatus" ? ( + + ) : toolName === "searchTrains" ? ( + ) : toolName === "selectSeats" ? ( - + ) : toolName === "createReservation" ? ( ) : toolName === "authorizePayment" ? ( diff --git a/components/custom/multimodal-input.tsx b/components/custom/multimodal-input.tsx index 4e228d59..63fab905 100644 --- a/components/custom/multimodal-input.tsx +++ b/components/custom/multimodal-input.tsx @@ -2,6 +2,7 @@ import { Attachment, ChatRequestOptions, CreateMessage, Message } from "ai"; import { motion } from "framer-motion"; +import { useSession } from "next-auth/react"; import React, { useRef, useEffect, @@ -19,16 +20,17 @@ import useWindowSize from "./use-window-size"; import { Button } from "../ui/button"; import { Textarea } from "../ui/textarea"; + const suggestedActions = [ { - title: "Help me book a flight", - label: "from San Francisco to London", - action: "Help me book a flight from San Francisco to London", + title: "Book a train ", + label: "from Roma Termini to Firenze SMN", + action: "Help me book a train from Rome to Florence", }, { title: "What is the status", - label: "of flight BA142 flying tmrw?", - action: "What is the status of flight BA142 flying tmrw?", + label: "of train ITALO9512 departing tomorrow?", + action: "What is the status of train ITALO9512 departing tomorrow?", }, ]; @@ -151,39 +153,57 @@ export function MultimodalInput({ [setAttachments], ); + const { data: session } = useSession(); + const email = session?.user?.email || ""; + const userName = email.split("@")[0] + ? email.split("@")[0].charAt(0).toUpperCase() + email.split("@")[0].slice(1) + : ""; + + const greetings = [ + `Ciao ${userName}! Where are we heading?`, + `All Aboard, Which station's on your mind?`, + `Buongiorno! What's your next stop?`, + `Next Journey, ${userName}? Tell me where!`, + `Ciao! Which city should we explore?` + ]; + const [greetingIndex, setGreetingIndex] = useState(0); + useEffect(() => { + const interval = setInterval(() => { + setGreetingIndex((i) => (i + 1) % greetings.length); + }, 3000); + return () => clearInterval(interval); + }, [greetings.length]); + return (
- {messages.length === 0 && - attachments.length === 0 && - uploadQueue.length === 0 && ( -
- {suggestedActions.map((suggestedAction, index) => ( - 1 ? "hidden sm:block" : "block"} - > - - - ))} -
- )} + {/* Show greeting and suggested actions only for new chat */} + {messages.length === 0 && ( +
+ + {greetings[greetingIndex]} + +
+ )} )} -