Skip to content

Commit b4791e7

Browse files
Merge pull request #4 from Daslin-Davidson/fix/train-update
Fix/train update
2 parents e6229bd + 4324a68 commit b4791e7

File tree

9 files changed

+211
-116
lines changed

9 files changed

+211
-116
lines changed

ai/actions.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,16 +40,19 @@ export async function generateSampleTrainStatus({
4040
return trainStatus;
4141
}
4242

43+
4344
export async function generateSampleTrainSearchResults({
4445
origin,
4546
destination,
47+
date,
4648
}: {
4749
origin: string;
4850
destination: string;
51+
date: string;
4952
}) {
5053
const { object: trainSearchResults } = await generateObject({
5154
model: geminiFlashModel,
52-
prompt: `Generate search results for trains from ${origin} to ${destination}, limit to 4 results`,
55+
prompt: `Generate search results for trains from ${origin} to ${destination} on ${date}, limit to 4 results`,
5356
output: "array",
5457
schema: z.object({
5558
id: z

app/api/chat/route.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,12 +92,14 @@ export async function POST(request: Request) {
9292
parameters: z.object({
9393
origin: z.string().describe("Origin station or city"),
9494
destination: z.string().describe("Destination station or city"),
95+
date: z.string().describe("Date of travel (ISO or natural language)")
9596
}),
96-
execute: async ({ origin, destination }) => {
97-
console.log('[searchTrains] start', { origin, destination });
97+
execute: async ({ origin, destination, date }) => {
98+
console.log('[searchTrains] start', { origin, destination, date });
9899
const results = await generateSampleTrainSearchResults({
99100
origin,
100101
destination,
102+
date,
101103
});
102104
console.log('[searchTrains] end', results);
103105
return results;

app/layout.tsx

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import { Metadata } from "next";
2+
import { SessionProvider } from "next-auth/react";
23
import { Toaster } from "sonner";
34

45
import { Navbar } from "@/components/custom/navbar";
5-
import { ThemeProvider } from "@/components/custom/theme-provider";
6+
import ThemeClientLayout from "@/components/custom/theme-client-layout";
67

78
import "./globals.css";
89

@@ -20,16 +21,13 @@ export default async function RootLayout({
2021
return (
2122
<html lang="en">
2223
<body className="antialiased">
23-
<ThemeProvider
24-
attribute="class"
25-
defaultTheme="system"
26-
enableSystem
27-
disableTransitionOnChange
28-
>
29-
<Toaster position="top-center" />
30-
<Navbar />
31-
{children}
32-
</ThemeProvider>
24+
<ThemeClientLayout>
25+
<SessionProvider>
26+
<Toaster position="top-center" />
27+
<Navbar />
28+
{children}
29+
</SessionProvider>
30+
</ThemeClientLayout>
3331
</body>
3432
</html>
3533
);

components/custom/chat.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,6 @@ export function Chat({
6868
// Initial state: centered input and logo
6969
<div className="flex flex-col items-center justify-center size-full">
7070
{/* Updated Rail logo */}
71-
<Image src="/images/ir-logo.png" alt="Rail Logo" width={80} height={80} className="size-20 mb-6 rounded-full shadow object-cover" />
7271
<form className="flex flex-row gap-3 items-center size-full max-w-xl px-6">
7372
<MultimodalInput
7473
input={input}

components/custom/multimodal-input.tsx

Lines changed: 137 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import { Attachment, ChatRequestOptions, CreateMessage, Message } from "ai";
44
import { motion } from "framer-motion";
5+
import { useSession } from "next-auth/react";
56
import React, {
67
useRef,
78
useEffect,
@@ -19,6 +20,7 @@ import useWindowSize from "./use-window-size";
1920
import { Button } from "../ui/button";
2021
import { Textarea } from "../ui/textarea";
2122

23+
2224
const suggestedActions = [
2325
{
2426
title: "Book a train ",
@@ -151,39 +153,57 @@ export function MultimodalInput({
151153
[setAttachments],
152154
);
153155

156+
const { data: session } = useSession();
157+
const email = session?.user?.email || "";
158+
const userName = email.split("@")[0]
159+
? email.split("@")[0].charAt(0).toUpperCase() + email.split("@")[0].slice(1)
160+
: "";
161+
162+
const greetings = [
163+
`Ciao ${userName}! Where are we heading?`,
164+
`All Aboard, Which station's on your mind?`,
165+
`Buongiorno! What's your next stop?`,
166+
`Next Journey, ${userName}? Tell me where!`,
167+
`Ciao! Which city should we explore?`
168+
];
169+
const [greetingIndex, setGreetingIndex] = useState(0);
170+
useEffect(() => {
171+
const interval = setInterval(() => {
172+
setGreetingIndex((i) => (i + 1) % greetings.length);
173+
}, 3000);
174+
return () => clearInterval(interval);
175+
}, [greetings.length]);
176+
154177
return (
155178
<div className="relative w-full flex flex-col gap-4">
156-
{messages.length === 0 &&
157-
attachments.length === 0 &&
158-
uploadQueue.length === 0 && (
159-
<div className="grid sm:grid-cols-2 gap-4 w-full md:px-0 mx-auto md:max-w-[500px]">
160-
{suggestedActions.map((suggestedAction, index) => (
161-
<motion.div
162-
initial={{ opacity: 0, y: 20 }}
163-
animate={{ opacity: 1, y: 0 }}
164-
exit={{ opacity: 0, y: 20 }}
165-
transition={{ delay: 0.05 * index }}
166-
key={index}
167-
className={index > 1 ? "hidden sm:block" : "block"}
168-
>
169-
<button
170-
onClick={async () => {
171-
append({
172-
role: "user",
173-
content: suggestedAction.action,
174-
});
175-
}}
176-
className="border-none bg-muted/50 w-full text-left border border-zinc-200 dark:border-zinc-800 text-zinc-800 dark:text-zinc-300 rounded-lg p-3 text-sm hover:bg-zinc-100 dark:hover:bg-zinc-800 transition-colors flex flex-col"
177-
>
178-
<span className="font-medium">{suggestedAction.title}</span>
179-
<span className="text-zinc-500 dark:text-zinc-400">
180-
{suggestedAction.label}
181-
</span>
182-
</button>
183-
</motion.div>
184-
))}
185-
</div>
186-
)}
179+
{/* Show greeting and suggested actions only for new chat */}
180+
{messages.length === 0 && (
181+
<div
182+
className="w-full flex justify-center items-center my-2"
183+
style={{
184+
minHeight: "2.5rem",
185+
height: "2.5rem",
186+
perspective: "800px"
187+
}}
188+
>
189+
<motion.div
190+
key={greetingIndex}
191+
initial={{ rotateX: -90, opacity: 0 }}
192+
animate={{ rotateX: 0, opacity: 1 }}
193+
exit={{ rotateX: 90, opacity: 0 }}
194+
transition={{ duration: 0.5 }}
195+
className="text-2xl font-semibold"
196+
style={{
197+
textAlign: "center",
198+
display: "inline-block",
199+
width: "100%",
200+
backfaceVisibility: "hidden"
201+
}}
202+
>
203+
{greetings[greetingIndex]}
204+
</motion.div>
205+
</div>
206+
)}
187207

188208
<input
189209
type="file"
@@ -214,60 +234,97 @@ export function MultimodalInput({
214234
</div>
215235
)}
216236

217-
<Textarea
218-
ref={textareaRef}
219-
placeholder="Send a message..."
220-
value={input}
221-
onChange={handleInput}
222-
className="min-h-[24px] overflow-hidden resize-none rounded-lg text-base bg-muted border-none"
223-
rows={3}
224-
onKeyDown={(event) => {
225-
if (event.key === "Enter" && !event.shiftKey) {
226-
event.preventDefault();
237+
<div className="relative w-full flex flex-row items-center bg-muted rounded-lg">
238+
<Textarea
239+
ref={textareaRef}
240+
placeholder="Send a message..."
241+
value={input}
242+
onChange={handleInput}
243+
className="min-h-[24px] overflow-hidden resize-none rounded-lg text-base bg-muted border-none pr-20"
244+
rows={3}
245+
onKeyDown={(event) => {
246+
if (event.key === "Enter" && !event.shiftKey) {
247+
event.preventDefault();
227248

228-
if (isLoading) {
229-
toast.error("Please wait for the model to finish its response!");
230-
} else {
231-
submitForm();
249+
if (isLoading) {
250+
toast.error("Please wait for the model to finish its response!");
251+
} else {
252+
submitForm();
253+
}
232254
}
233-
}
234-
}}
235-
/>
236-
237-
{isLoading ? (
238-
<Button
239-
className="rounded-full p-1.5 h-fit absolute bottom-2 right-2 m-0.5 text-white"
240-
onClick={(event) => {
241-
event.preventDefault();
242-
stop();
243255
}}
244-
>
245-
<StopIcon size={14} />
246-
</Button>
247-
) : (
248-
<Button
249-
className="rounded-full p-1.5 h-fit absolute bottom-2 right-2 m-0.5 text-white"
250-
onClick={(event) => {
251-
event.preventDefault();
252-
submitForm();
253-
}}
254-
disabled={input.length === 0 || uploadQueue.length > 0}
255-
>
256-
<ArrowUpIcon size={14} />
257-
</Button>
256+
/>
257+
<div className="absolute right-2 bottom-2 flex flex-row gap-2">
258+
<Button
259+
className="rounded-full p-1.5 h-fit m-0.5 text-zinc-700 dark:text-zinc-300"
260+
onClick={(event) => {
261+
event.preventDefault();
262+
fileInputRef.current?.click();
263+
}}
264+
variant="outline"
265+
disabled={isLoading}
266+
type="button"
267+
>
268+
<PaperclipIcon size={14} />
269+
</Button>
270+
{isLoading ? (
271+
<Button
272+
className="rounded-full p-1.5 h-fit m-0.5 text-white"
273+
onClick={(event) => {
274+
event.preventDefault();
275+
stop();
276+
}}
277+
type="button"
278+
>
279+
<StopIcon size={14} />
280+
</Button>
281+
) : (
282+
<Button
283+
className="rounded-full p-1.5 h-fit m-0.5 text-white"
284+
onClick={(event) => {
285+
event.preventDefault();
286+
submitForm();
287+
}}
288+
disabled={input.length === 0 || uploadQueue.length > 0}
289+
type="button"
290+
>
291+
<ArrowUpIcon size={14} />
292+
</Button>
293+
)}
294+
</div>
295+
</div>
296+
297+
{/* Suggested actions below input, always visible for new chat */}
298+
{messages.length === 0 && (
299+
<div className="grid sm:grid-cols-2 gap-4 w-full md:px-0 mx-auto md:max-w-[500px] mt-4">
300+
{suggestedActions.map((suggestedAction, index) => (
301+
<motion.div
302+
initial={{ opacity: 0, y: 20 }}
303+
animate={{ opacity: 1, y: 0 }}
304+
exit={{ opacity: 0, y: 20 }}
305+
transition={{ delay: 0.05 * index }}
306+
key={index}
307+
className={index > 1 ? "hidden sm:block" : "block"}
308+
>
309+
<button
310+
onClick={async () => {
311+
append({
312+
role: "user",
313+
content: suggestedAction.action,
314+
});
315+
}}
316+
className="border-none bg-muted/50 w-full text-left border border-zinc-200 dark:border-zinc-800 text-zinc-800 dark:text-zinc-300 rounded-lg p-3 text-sm hover:bg-zinc-100 dark:hover:bg-zinc-800 transition-colors flex flex-col"
317+
>
318+
<span className="font-medium">{suggestedAction.title}</span>
319+
<span className="text-zinc-500 dark:text-zinc-400">
320+
{suggestedAction.label}
321+
</span>
322+
</button>
323+
</motion.div>
324+
))}
325+
</div>
258326
)}
259327

260-
<Button
261-
className="rounded-full p-1.5 h-fit absolute bottom-2 right-10 m-0.5 dark:border-zinc-700"
262-
onClick={(event) => {
263-
event.preventDefault();
264-
fileInputRef.current?.click();
265-
}}
266-
variant="outline"
267-
disabled={isLoading}
268-
>
269-
<PaperclipIcon size={14} />
270-
</Button>
271328
</div>
272329
);
273330
}

components/custom/navbar.tsx

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,19 @@ export const Navbar = async () => {
1919

2020
return (
2121
<>
22-
<div className="bg-background absolute top-0 left-0 w-dvw py-2 px-3 justify-between flex flex-row items-center z-30">
22+
<div className="bg-background absolute top-0 left-0 w-dvw py-1 px-2 justify-between flex flex-row items-center z-30">
2323
<div className="flex flex-row gap-3 items-center">
2424
<div className="flex flex-row gap-2 items-center">
25-
<Image
26-
src="/images/italia-rail.png"
27-
height={200}
28-
width={200}
29-
alt="Italia Rail logo"
30-
/>
25+
<Link href="/" passHref>
26+
<Image
27+
src="/images/italia-rail.png"
28+
height={200}
29+
width={200}
30+
alt="Italia Rail logo"
31+
priority
32+
style={{ width: 'auto', height: 'auto', cursor: 'pointer' }}
33+
/>
34+
</Link>
3135
</div>
3236
</div>
3337

@@ -36,12 +40,15 @@ export const Navbar = async () => {
3640
<>
3741
<DropdownMenu>
3842
<DropdownMenuTrigger asChild>
39-
<Button
40-
className="py-1.5 px-2 font-normal rounded-full bg-emerald-600 text-white size-8 flex items-center justify-center text-lg"
41-
variant="secondary"
42-
>
43-
{session.user?.email?.slice(0, 2).toUpperCase()}
44-
</Button>
43+
<div className="size-8 rounded-full overflow-hidden flex items-center justify-center bg-emerald-600 shadow-md border border-zinc-200 dark:border-zinc-700">
44+
<Image
45+
src="/images/person.png"
46+
alt="User avatar"
47+
width={32}
48+
height={32}
49+
style={{ width: "32px", height: "32px" }}
50+
/>
51+
</div>
4552
</DropdownMenuTrigger>
4653
<DropdownMenuContent align="end">
4754
<DropdownMenuItem>
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
"use client";
2+
3+
import { ThemeProvider } from "@/components/custom/theme-provider";
4+
5+
export default function ThemeClientLayout({ children }: { children: React.ReactNode }) {
6+
return (
7+
<ThemeProvider
8+
attribute="class"
9+
defaultTheme="system"
10+
enableSystem
11+
disableTransitionOnChange
12+
>
13+
{children}
14+
</ThemeProvider>
15+
);
16+
}

0 commit comments

Comments
 (0)