@@ -29,6 +29,7 @@ import {
2929} from "@/components/ui/dialog" ;
3030import { MarkdownRenderer } from "@/components/ui/markdown-renderer" ;
3131import { Img } from "../../../components/ui/Img" ;
32+ import { Spinner } from "../../../components/ui/Spinner/Spinner" ;
3233import { THIRDWEB_CLIENT } from "../../../lib/client" ;
3334import { type NebulaContext , promptNebula } from "../api/chat" ;
3435import type {
@@ -349,16 +350,17 @@ export function ChatPageContent(props: {
349350 } ;
350351
351352 return (
352- < div className = "flex grow flex-col overflow-hidden" >
353+ < div className = "flex h-screen flex-col overflow-hidden" >
353354 < WalletDisconnectedDialog
354355 onOpenChange = { setShowConnectModal }
355356 open = { showConnectModal }
356357 />
357358
358- < div className = "flex grow overflow-hidden" >
359- < div className = "relative flex grow flex-col overflow-hidden rounded-lg pb-4" >
360- { showEmptyState ? (
361- < div className = "fade-in-0 container flex max-w-[800px] grow animate-in flex-col justify-center" >
359+ { showEmptyState ? (
360+ < div className = "flex h-full flex-col" >
361+ { /* Empty state content - scrollable area */ }
362+ < div className = "flex-1 overflow-y-auto" >
363+ < div className = "fade-in-0 container flex max-w-[800px] min-h-full animate-in flex-col justify-center" >
362364 < EmptyStateChatPageContent
363365 connectedWallets = { connectedWalletsMeta }
364366 context = { contextFilters }
@@ -368,28 +370,60 @@ export function ChatPageContent(props: {
368370 setContext = { setContextFilters }
369371 />
370372 </ div >
371- ) : (
372- < div className = "fade-in-0 relative z-[0] flex max-h-full flex-1 animate-in flex-col overflow-hidden" >
373- { sessionWithNoMessages && (
374- < div className = "container flex max-h-full max-w-[800px] flex-1 flex-col justify-center py-8" >
375- < div className = "flex flex-col items-center justify-center p-4" >
376- < div className = "mb-5 rounded-full border bg-card p-3" >
377- < MessageSquareXIcon className = "size-6 text-muted-foreground" />
378- </ div >
379- < p className = "mb-1 text-center text-foreground" >
380- No messages found
381- </ p >
382- < p className = "text-balance text-center text-muted-foreground text-sm" >
383- This session was aborted before receiving any messages
384- </ p >
373+ </ div >
374+
375+ { /* Chat input - anchored at bottom (same as chat state) */ }
376+ < div className = "flex-shrink-0 border-t bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60" >
377+ < div className = "container max-w-[800px] py-4" >
378+ < SimpleChatBar
379+ abortChatStream = { ( ) => {
380+ chatAbortController ?. abort ( ) ;
381+ setChatAbortController ( undefined ) ;
382+ setIsChatStreaming ( false ) ;
383+ } }
384+ client = { props . client }
385+ connectedWallets = { connectedWalletsMeta }
386+ context = { contextFilters }
387+ isChatStreaming = { isChatStreaming }
388+ isConnectingWallet = { connectionStatus === "connecting" }
389+ placeholder = "Ask thirdweb AI"
390+ sendMessage = { handleSendMessage }
391+ setActiveWallet = { handleSetActiveWallet }
392+ setContext = { ( v ) => {
393+ setContextFilters ( v ) ;
394+ } }
395+ />
396+
397+ { /* Footer disclaimer */ }
398+ < p className = "mt-3 text-center text-muted-foreground text-xs opacity-75 lg:text-sm" >
399+ thirdweb AI can make mistakes. Please use with discretion
400+ </ p >
401+ </ div >
402+ </ div >
403+ </ div >
404+ ) : (
405+ < div className = "flex h-full flex-col" >
406+ { /* Chat messages area - scrollable */ }
407+ < div className = "flex-1 overflow-y-auto" >
408+ { sessionWithNoMessages ? (
409+ < div className = "container flex h-full max-w-[800px] flex-col justify-center py-8" >
410+ < div className = "flex flex-col items-center justify-center p-4" >
411+ < div className = "mb-5 rounded-full border bg-card p-3" >
412+ < MessageSquareXIcon className = "size-6 text-muted-foreground" />
385413 </ div >
414+ < p className = "mb-1 text-center text-foreground" >
415+ No messages found
416+ </ p >
417+ < p className = "text-balance text-center text-muted-foreground text-sm" >
418+ This session was aborted before receiving any messages
419+ </ p >
386420 </ div >
387- ) }
388-
389- { messages . length > 0 && (
421+ </ div >
422+ ) : (
423+ messages . length > 0 && (
390424 < SimpleChats
391425 authToken = { props . authToken }
392- className = "min-w-0 pt-6 pb-32 "
426+ className = "min-w-0"
393427 client = { props . client }
394428 enableAutoScroll = { enableAutoScroll }
395429 isChatStreaming = { isChatStreaming }
@@ -398,36 +432,40 @@ export function ChatPageContent(props: {
398432 sessionId = { sessionId }
399433 setEnableAutoScroll = { setEnableAutoScroll }
400434 />
401- ) }
435+ )
436+ ) }
437+ </ div >
402438
403- < div className = "container max-w-[800px]" >
404- < SimpleChatBar
405- abortChatStream = { ( ) => {
406- chatAbortController ?. abort ( ) ;
407- setChatAbortController ( undefined ) ;
408- setIsChatStreaming ( false ) ;
409- } }
410- client = { props . client }
411- connectedWallets = { connectedWalletsMeta }
412- context = { contextFilters }
413- isChatStreaming = { isChatStreaming }
414- isConnectingWallet = { connectionStatus === "connecting" }
415- placeholder = "Ask Nebula"
416- sendMessage = { handleSendMessage }
417- setActiveWallet = { handleSetActiveWallet }
418- setContext = { ( v ) => {
419- setContextFilters ( v ) ;
420- } }
421- />
422- </ div >
423- </ div >
424- ) }
439+ { /* Chat input - anchored at bottom */ }
440+ < div className = "flex-shrink-0 border-t bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60" >
441+ < div className = "container max-w-[800px] py-4" >
442+ < SimpleChatBar
443+ abortChatStream = { ( ) => {
444+ chatAbortController ?. abort ( ) ;
445+ setChatAbortController ( undefined ) ;
446+ setIsChatStreaming ( false ) ;
447+ } }
448+ client = { props . client }
449+ connectedWallets = { connectedWalletsMeta }
450+ context = { contextFilters }
451+ isChatStreaming = { isChatStreaming }
452+ isConnectingWallet = { connectionStatus === "connecting" }
453+ placeholder = "Ask thirdweb AI"
454+ sendMessage = { handleSendMessage }
455+ setActiveWallet = { handleSetActiveWallet }
456+ setContext = { ( v ) => {
457+ setContextFilters ( v ) ;
458+ } }
459+ />
425460
426- < p className = "mt-4 text-center text-muted-foreground text-xs opacity-75 lg:text-sm" >
427- Nebula may make mistakes. Please use with discretion
428- </ p >
461+ { /* Footer disclaimer */ }
462+ < p className = "mt-3 text-center text-muted-foreground text-xs opacity-75 lg:text-sm" >
463+ thirdweb AI can make mistakes. Please use with discretion
464+ </ p >
465+ </ div >
466+ </ div >
429467 </ div >
430- </ div >
468+ ) }
431469 </ div >
432470 ) ;
433471}
@@ -478,28 +516,15 @@ function EmptyStateChatPageContent(props: {
478516 < div className = "relative py-10" >
479517 < div className = "flex justify-center" >
480518 < div className = "rounded-full border p-4 bg-card" >
481- < MessageSquareXIcon className = "size-8 text-muted-foreground" />
519+ < MessageCircleIcon className = "size-8 text-muted-foreground" />
482520 </ div >
483521 </ div >
484522 < div className = "h-5" />
485523 < h1 className = "px-4 text-center font-semibold text-3xl tracking-tight md:text-4xl" >
486- How can I help you < br /> onchain today?
524+ thirdweb AI demo
487525 </ h1 >
488- < div className = "h-5 " />
526+ < div className = "h-8 " />
489527 < div className = "mx-auto max-w-[600px]" >
490- < SimpleChatBar
491- abortChatStream = { ( ) => { } }
492- client = { { } as ThirdwebClient }
493- connectedWallets = { props . connectedWallets }
494- context = { props . context }
495- isChatStreaming = { false }
496- isConnectingWallet = { props . isConnectingWallet }
497- placeholder = "Ask Nebula"
498- sendMessage = { props . sendMessage }
499- setActiveWallet = { props . setActiveWallet }
500- setContext = { props . setContext }
501- />
502- < div className = "h-5" />
503528 < div className = "flex flex-wrap justify-center gap-2.5" >
504529 { examplePrompts . map ( ( prompt ) => {
505530 return (
@@ -552,7 +577,7 @@ function SimpleChatBar(props: {
552577 < div className = "overflow-hidden rounded-2xl border border-border bg-card transition-colors p-2" >
553578 < div className = "max-h-[200px] overflow-y-auto" >
554579 < textarea
555- className = "min-h-[60px] w-full resize-none border-none bg-transparent pt -2 leading-relaxed focus-visible:ring-0 focus-visible:ring-offset-0 outline-none"
580+ className = "min-h-[60px] w-full resize-none border-none bg-transparent p -2 leading-relaxed focus-visible:ring-0 focus-visible:ring-offset-0 outline-none"
556581 disabled = { props . isChatStreaming }
557582 onChange = { ( e ) => setMessage ( e . target . value ) }
558583 onKeyDown = { ( e ) => {
@@ -627,30 +652,26 @@ function SimpleChats(props: {
627652 } , [ messages , enableAutoScroll ] ) ;
628653
629654 return (
630- < div className = "relative flex max-h-full flex-1 flex-col overflow-hidden" >
631- < div className = "flex-1 overflow-y-auto" >
632- < div className = "container max-w-[800px]" >
633- < div className = { `flex flex-col gap-5 py-4 ${ props . className } ` } >
634- { props . messages . map ( ( message , index ) => {
635- const isMessagePending =
636- props . isChatStreaming && index === props . messages . length - 1 ;
637-
638- return (
639- < div
640- className = "fade-in-0 min-w-0 animate-in pt-1 text-sm duration-300 lg:text-base"
641- key = { `${ index } -${ message } ` }
642- >
643- < RenderMessage
644- isMessagePending = { isMessagePending }
645- message = { message }
646- sendMessage = { props . sendMessage }
647- />
648- </ div >
649- ) ;
650- } ) }
651- < div ref = { scrollAnchorRef } />
652- </ div >
653- </ div >
655+ < div className = "container max-w-[800px]" >
656+ < div className = { `flex flex-col gap-5 py-6 pb-8 ${ props . className } ` } >
657+ { props . messages . map ( ( message , index ) => {
658+ const isMessagePending =
659+ props . isChatStreaming && index === props . messages . length - 1 ;
660+
661+ return (
662+ < div
663+ className = "fade-in-0 min-w-0 animate-in pt-1 text-sm duration-300 lg:text-base"
664+ key = { `${ index } -${ message } ` }
665+ >
666+ < RenderMessage
667+ isMessagePending = { isMessagePending }
668+ message = { message }
669+ sendMessage = { props . sendMessage }
670+ />
671+ </ div >
672+ ) ;
673+ } ) }
674+ < div ref = { scrollAnchorRef } />
654675 </ div >
655676 </ div >
656677 ) ;
@@ -691,7 +712,11 @@ function RenderMessage(props: {
691712 < div className = "flex gap-3" >
692713 < div className = "-translate-y-[2px] relative shrink-0" >
693714 < div className = "flex size-9 items-center justify-center rounded-full border bg-card" >
694- < MessageCircleIcon className = "size-5 text-muted-foreground" />
715+ { props . isMessagePending ? (
716+ < Spinner />
717+ ) : (
718+ < MessageCircleIcon className = "size-5 text-muted-foreground" />
719+ ) }
695720 </ div >
696721 </ div >
697722
@@ -808,7 +833,9 @@ function RenderMessage(props: {
808833 } ) ;
809834 } }
810835 >
811- Execute Swap
836+ { message . data . action === "approval"
837+ ? "Approve"
838+ : "Execute Swap" }
812839 </ TransactionButton >
813840 </ div >
814841 ) : (
0 commit comments