22
33import { Attachment , ChatRequestOptions , CreateMessage , Message } from "ai" ;
44import { motion } from "framer-motion" ;
5+ import { useSession } from "next-auth/react" ;
56import React , {
67 useRef ,
78 useEffect ,
@@ -19,6 +20,7 @@ import useWindowSize from "./use-window-size";
1920import { Button } from "../ui/button" ;
2021import { Textarea } from "../ui/textarea" ;
2122
23+
2224const 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}
0 commit comments