From 048377682596f58cfa9ff6daed6be4b5e1667f51 Mon Sep 17 00:00:00 2001 From: MananTank Date: Wed, 30 Jul 2025 19:18:10 +0000 Subject: [PATCH] [BLD-26] Portal: AI Chat UI improvements (#7764) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ## PR-Codex overview This PR primarily focuses on enhancing the UI components and improving the styling, particularly around the `ScrollShadow` component. It also introduces new animations and adjusts various layouts for better responsiveness. ### Detailed summary - Removed `ScrollShadow.tsx` and its CSS module. - Updated color in `NextTopLoader` from `--link-foreground` to `--foreground`. - Added `sonner` package for toasts. - Integrated `ScrollShadow` in multiple components. - Adjusted layout styles in `page.tsx` and `MarkdownRenderer.tsx`. - Enhanced `Chat` component with `ScrollShadow` for better scrolling behavior. - Introduced `TextShimmer` and `UnderlineLink` components. - Improved styling and responsiveness in various components. > ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}` ## Summary by CodeRabbit * **New Features** * Introduced a shimmering text effect for loading states. * Added a customizable toast notification system with theme support. * Added an underlined link component for consistent link styling. * **Enhancements** * Refined chat interface with improved empty state, message rendering, feedback handling, and visual distinction between user and AI messages. * Updated markdown rendering for improved link, heading, list, and paragraph styles. * Improved icon rendering with unique ID management and monochrome support. * Adjusted chat page and loader bar styling for better layout and color consistency. * **Bug Fixes** * Corrected React key usage in code snippet rendering to use unique platform IDs. * **Refactor** * Modularized chat message rendering and feedback logic. * Updated import paths for scroll shadow components across multiple files. * **Chores** * Updated dependencies to include the "sonner" package. * Cleaned up whitespace, formatting, and removed unused lint comments. * Adjusted Tailwind configuration for border radius and added shimmer animation. * **Removals** * Deleted legacy scroll shadow component and associated CSS. --- apps/portal/package.json | 1 + apps/portal/src/app/chat/page.tsx | 2 +- apps/portal/src/app/layout.tsx | 2 +- apps/portal/src/components/AI/chat.tsx | 323 +++++++++++------- .../components/Document/AuthMethodsTabs.tsx | 77 ++--- .../src/components/Document/Breadcrumb.tsx | 1 - apps/portal/src/components/Document/Code.tsx | 3 +- apps/portal/src/components/Document/Table.tsx | 2 +- .../components/code/CodeBlockContainer.tsx | 2 +- .../portal/src/components/code/RenderCode.tsx | 2 +- .../components/markdown/MarkdownRenderer.tsx | 55 +-- .../ScrollShadow/ScrollShadow.module.css | 36 -- .../others/ScrollShadow/ScrollShadow.tsx | 153 --------- apps/portal/src/components/ui/sonner.tsx | 32 ++ apps/portal/src/components/ui/table.tsx | 3 +- .../portal/src/components/ui/text-shimmer.tsx | 20 ++ .../src/components/ui/underline-link.tsx | 16 + apps/portal/src/icons/thirdweb.tsx | 41 ++- apps/portal/tailwind.config.ts | 17 +- pnpm-lock.yaml | 21 +- 20 files changed, 405 insertions(+), 404 deletions(-) delete mode 100644 apps/portal/src/components/others/ScrollShadow/ScrollShadow.module.css delete mode 100644 apps/portal/src/components/others/ScrollShadow/ScrollShadow.tsx create mode 100644 apps/portal/src/components/ui/sonner.tsx create mode 100644 apps/portal/src/components/ui/text-shimmer.tsx create mode 100644 apps/portal/src/components/ui/underline-link.tsx diff --git a/apps/portal/package.json b/apps/portal/package.json index f954824c382..3f014468ce6 100644 --- a/apps/portal/package.json +++ b/apps/portal/package.json @@ -34,6 +34,7 @@ "remark-gfm": "4.0.1", "server-only": "^0.0.1", "shiki": "1.27.0", + "sonner": "2.0.6", "tailwind-merge": "^2.6.0", "tailwindcss-animate": "^1.0.7", "thirdweb": "workspace:*", diff --git a/apps/portal/src/app/chat/page.tsx b/apps/portal/src/app/chat/page.tsx index 8c9c068c065..d404db046c0 100644 --- a/apps/portal/src/app/chat/page.tsx +++ b/apps/portal/src/app/chat/page.tsx @@ -8,7 +8,7 @@ const queryClient = new QueryClient(); export default function ChatPage() { return ( -
+
diff --git a/apps/portal/src/app/layout.tsx b/apps/portal/src/app/layout.tsx index 068e41c2e59..705f9e4af29 100644 --- a/apps/portal/src/app/layout.tsx +++ b/apps/portal/src/app/layout.tsx @@ -43,7 +43,7 @@ export default function RootLayout({ enableSystem={false} > void; }) { return ( -
- +
+ {/* tw logo */} +
+
+
+ +
+
+
-

- How can I help you
- build onchain today? -

+ {/* title */} +

+ How can I help you
+ today? +

+ {/* prompts */}
{predefinedPrompts.map((prompt) => ( - - - )} -
- )} - - )} -
-
- ))} + +
+ {messages.map((message) => ( + + ))} +
+
+ + )} +
+ +
+ + +
+
+ ); +} + +const aiIcon = ( +
+ +
+); + +const userIcon = ( +
+ +
+); + +function RenderAIResponse(props: { + conversationId: string | undefined; + message: Message; +}) { + const thumbsUpFeedbackMutation = useMutation({ + mutationFn: () => { + if (!props.conversationId) { + throw new Error("No conversation ID"); + } + return sendFeedback(props.conversationId, 1); + }, + }); + + const thumbsDownFeedbackMutation = useMutation({ + mutationFn: () => { + if (!props.conversationId) { + throw new Error("No conversation ID"); + } + return sendFeedback(props.conversationId, -1); + }, + }); + + return ( +
+ {aiIcon} +
+ + + {props.conversationId && ( +
+ +
)}
-
-
- + ); +} + +function RenderMessage(props: { + message: Message; + conversationId: string | undefined; +}) { + if (props.message.role === "user") { + return ( +
+ {userIcon} +
+ -
-
- ); + ); + } + + if (props.message.role === "assistant" && props.message.isLoading) { + return ( +
+ {aiIcon} + +
+ ); + } + + if (props.message.role === "assistant" && !props.message.isLoading) { + return ( + + ); + } } function StyledMarkdownRenderer(props: { @@ -277,15 +371,16 @@ function StyledMarkdownRenderer(props: { }) { return ( diff --git a/apps/portal/src/components/Document/AuthMethodsTabs.tsx b/apps/portal/src/components/Document/AuthMethodsTabs.tsx index c8c4446e5a1..2a414908aab 100644 --- a/apps/portal/src/components/Document/AuthMethodsTabs.tsx +++ b/apps/portal/src/components/Document/AuthMethodsTabs.tsx @@ -365,7 +365,7 @@ function MyComponent() { // 3. Or use prebuilt UI components (ConnectButton/ConnectEmbed) function PrebuiltUIExample() { - const walletWithAuth = inAppWallet({ + const walletWithAuth = inAppWallet({ auth: { options: ["${authMethod}"] }, metadata: { name: "My App", @@ -380,9 +380,9 @@ function PrebuiltUIExample() { }); return ( - ); }`; @@ -445,7 +445,7 @@ function MyComponent() { } // 3. Or use prebuilt UI components (ConnectButton/ConnectEmbed) -const walletWithAuth = inAppWallet({ +const walletWithAuth = inAppWallet({ auth: { options: ["email"] }, metadata: { name: "My App", @@ -465,9 +465,9 @@ function PrebuiltUIExample() { console.log("Connected as:", activeAccount?.address); return ( - ); }`; @@ -527,7 +527,7 @@ function MyComponent() { // 3. Or use prebuilt UI components (ConnectButton/ConnectEmbed) function PrebuiltUIExample() { - const walletWithAuth = inAppWallet({ + const walletWithAuth = inAppWallet({ auth: { options: ["phone"] }, metadata: { name: "My App", @@ -542,9 +542,9 @@ function PrebuiltUIExample() { }); return ( - ); }`; @@ -718,8 +718,8 @@ const getUnitySnippet = (authMethod: AuthMethod): string => { `// Email authentication var inAppWalletOptions = new InAppWalletOptions(email: "user@example.com"); var options = new WalletOptions( - provider: WalletProvider.InAppWallet, - chainId: 1, + provider: WalletProvider.InAppWallet, + chainId: 1, inAppWalletOptions: inAppWalletOptions ); var wallet = await ThirdwebManager.Instance.ConnectWallet(options);` @@ -731,8 +731,8 @@ var wallet = await ThirdwebManager.Instance.ConnectWallet(options);` `// Phone authentication var inAppWalletOptions = new InAppWalletOptions(phoneNumber: "+1234567890"); var options = new WalletOptions( - provider: WalletProvider.InAppWallet, - chainId: 1, + provider: WalletProvider.InAppWallet, + chainId: 1, inAppWalletOptions: inAppWalletOptions ); var wallet = await ThirdwebManager.Instance.ConnectWallet(options);` @@ -761,8 +761,8 @@ var wallet = await ThirdwebManager.Instance.ConnectWallet(options);` `// ${providerMap[authMethod]} OAuth var inAppWalletOptions = new InAppWalletOptions(authprovider: AuthProvider.${providerMap[authMethod]}); var options = new WalletOptions( - provider: WalletProvider.InAppWallet, - chainId: 1, + provider: WalletProvider.InAppWallet, + chainId: 1, inAppWalletOptions: inAppWalletOptions ); var wallet = await ThirdwebManager.Instance.ConnectWallet(options);` @@ -775,8 +775,8 @@ var wallet = await ThirdwebManager.Instance.ConnectWallet(options);` `// Guest authentication var inAppWalletOptions = new InAppWalletOptions(authprovider: AuthProvider.Guest); var options = new WalletOptions( - provider: WalletProvider.InAppWallet, - chainId: 1, + provider: WalletProvider.InAppWallet, + chainId: 1, inAppWalletOptions: inAppWalletOptions ); var wallet = await ThirdwebManager.Instance.ConnectWallet(options);` @@ -789,8 +789,8 @@ var wallet = await ThirdwebManager.Instance.ConnectWallet(options);` var externalWallet = await PrivateKeyWallet.Create(client, "your-private-key"); var inAppWalletOptions = new InAppWalletOptions(authprovider: AuthProvider.Siwe, siweSigner: externalWallet); var options = new WalletOptions( - provider: WalletProvider.InAppWallet, - chainId: 1, + provider: WalletProvider.InAppWallet, + chainId: 1, inAppWalletOptions: inAppWalletOptions ); var wallet = await ThirdwebManager.Instance.ConnectWallet(options);` @@ -802,8 +802,8 @@ var wallet = await ThirdwebManager.Instance.ConnectWallet(options);` `// ${authMethod} authentication var inAppWalletOptions = new InAppWalletOptions(); var options = new WalletOptions( - provider: WalletProvider.InAppWallet, - chainId: 1, + provider: WalletProvider.InAppWallet, + chainId: 1, inAppWalletOptions: inAppWalletOptions ); var wallet = await ThirdwebManager.Instance.ConnectWallet(options);` @@ -914,25 +914,22 @@ function AuthMethodsTabsContent() {
- {getCodeSnippet(selectedAuth, platform.id).map( - (code, index) => ( - - key={index} - code={code} - lang={ - platform.id === "http" - ? "http" - : platform.id === "dotnet" || - platform.id === "unity" - ? "csharp" - : "typescript" - } - loader={} - className="text-sm" - /> - ), - )} + {getCodeSnippet(selectedAuth, platform.id).map((code) => ( + } + className="text-sm" + /> + ))}
diff --git a/apps/portal/src/components/Document/Breadcrumb.tsx b/apps/portal/src/components/Document/Breadcrumb.tsx index 6cb2820836c..fc6412464c5 100644 --- a/apps/portal/src/components/Document/Breadcrumb.tsx +++ b/apps/portal/src/components/Document/Breadcrumb.tsx @@ -8,7 +8,6 @@ type Crumb = { export function Breadcrumb(props: { crumbs: Crumb[] }) { return ( - // biome-ignore lint/nursery/useUniqueElementIds: this acts as a target for hrefs