Skip to content

Commit d128752

Browse files
playground
1 parent 135e014 commit d128752

File tree

2 files changed

+126
-94
lines changed

2 files changed

+126
-94
lines changed

apps/playground-web/src/app/ai/components/ChatPageContent.tsx

Lines changed: 120 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import {
2929
} from "@/components/ui/dialog";
3030
import { MarkdownRenderer } from "@/components/ui/markdown-renderer";
3131
import { Img } from "../../../components/ui/Img";
32+
import { Spinner } from "../../../components/ui/Spinner/Spinner";
3233
import { THIRDWEB_CLIENT } from "../../../lib/client";
3334
import { type NebulaContext, promptNebula } from "../api/chat";
3435
import 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
) : (

apps/portal/src/app/ai/sidebar.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { BookIcon, ZapIcon } from "lucide-react";
1+
import { BookIcon, PlayIcon, ZapIcon } from "lucide-react";
22
import type { SideBar } from "@/components/Layouts/DocLayout";
33

44
export const sidebar: SideBar = {
@@ -12,6 +12,11 @@ export const sidebar: SideBar = {
1212
href: "/ai/chat",
1313
icon: <ZapIcon />,
1414
},
15+
{
16+
name: "Playground",
17+
href: "https://playground.thirdweb.com/ai/chat",
18+
icon: <PlayIcon />,
19+
},
1520
{
1621
name: "Response Handling",
1722
href: "/ai/chat/handling-responses",

0 commit comments

Comments
 (0)