From ed65f4cbe73a14a7c2f411918784186bcbe673fa Mon Sep 17 00:00:00 2001 From: MananTank Date: Wed, 30 Jul 2025 22:22:35 +0000 Subject: [PATCH] Portal: Move Chat UI in floating widget (#7765) 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 chat functionality in the application by introducing a new `ChatButton` component, improving code structure, and refining the chat interface with better loading states and animations. ### Detailed summary - Removed the `Button` component linking to `/chat` in favor of `ChatButton`. - Introduced `ChatButton` component with modal functionality for the chat interface. - Added a `QueryClientProvider` to manage chat state. - Improved conditional rendering and loading states in chat messages. - Enhanced CSS animations for chat components. - Updated key usage in `CodeClient` mapping to ensure unique keys. > ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}` ## Summary by CodeRabbit * **New Features** * Introduced a new "Ask AI" button that opens a modal chat interface, accessible across the portal. * The chat modal includes options to reset the conversation and close the modal, with a loading spinner for improved user experience. * **Improvements** * Enhanced chat interface with animated message appearance and a more precise scroll-to-latest-message effect. * Updated code snippet rendering in authentication tabs to ensure unique keys for better performance and reliability. * **Bug Fixes** * Resolved potential issues with rendering multiple code snippets for the same platform by ensuring unique keys. * **Refactor** * Replaced previous "Ask AI" button implementations with the new unified ChatButton component for consistency. --- apps/portal/src/app/Header.tsx | 21 +-- apps/portal/src/app/chat/page.tsx | 16 --- apps/portal/src/app/page.tsx | 12 +- apps/portal/src/components/AI/chat-button.tsx | 88 +++++++++++++ apps/portal/src/components/AI/chat.tsx | 123 ++++++++++-------- .../components/Document/AuthMethodsTabs.tsx | 34 ++--- 6 files changed, 182 insertions(+), 112 deletions(-) delete mode 100644 apps/portal/src/app/chat/page.tsx create mode 100644 apps/portal/src/components/AI/chat-button.tsx diff --git a/apps/portal/src/app/Header.tsx b/apps/portal/src/app/Header.tsx index 4d91309b59c..a7ad861a7a1 100644 --- a/apps/portal/src/app/Header.tsx +++ b/apps/portal/src/app/Header.tsx @@ -1,12 +1,7 @@ "use client"; import clsx from "clsx"; -import { - ChevronDownIcon, - MenuIcon, - MessageCircleIcon, - TableOfContentsIcon, -} from "lucide-react"; +import { ChevronDownIcon, MenuIcon, TableOfContentsIcon } from "lucide-react"; import Link from "next/link"; import { usePathname } from "next/navigation"; import { useState } from "react"; @@ -18,6 +13,7 @@ import { DropdownMenuItem, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; +import { ChatButton } from "../components/AI/chat-button"; import { GithubIcon } from "../components/Document/GithubButtonLink"; import { CustomAccordion } from "../components/others/CustomAccordion"; import { ThemeSwitcher } from "../components/others/theme/ThemeSwitcher"; @@ -242,22 +238,13 @@ export function Header() {
- +
- + +
diff --git a/apps/portal/src/components/AI/chat-button.tsx b/apps/portal/src/components/AI/chat-button.tsx new file mode 100644 index 00000000000..5b8051ba87b --- /dev/null +++ b/apps/portal/src/components/AI/chat-button.tsx @@ -0,0 +1,88 @@ +"use client"; + +import { MessageCircleIcon, RefreshCcwIcon, XIcon } from "lucide-react"; +import { lazy, Suspense, useCallback, useState } from "react"; +import { Button } from "@/components/ui/button"; +import { cn } from "@/lib/utils"; +import { Spinner } from "../ui/Spinner/Spinner"; + +const Chat = lazy(() => + import("./chat").then((mod) => ({ default: mod.Chat })), +); + +export function ChatButton() { + const [isOpen, setIsOpen] = useState(false); + const [hasBeenOpened, setHasBeenOpened] = useState(false); + const closeModal = useCallback(() => setIsOpen(false), []); + const [id, setId] = useState(0); + + return ( + <> + {/* Inline Button (not floating) */} + + + {/* Popup/Modal */} +
+ {/* Header with close button */} +
+
+ Ask AI +
+ +
+ + + +
+
+ {/* Chat Content */} +
+ {hasBeenOpened && ( + }> + + + )} +
+
+ + ); +} + +function ChatLoading() { + return ( +
+ +
+ ); +} diff --git a/apps/portal/src/components/AI/chat.tsx b/apps/portal/src/components/AI/chat.tsx index 4a1bec015c7..d3590f4fcde 100644 --- a/apps/portal/src/components/AI/chat.tsx +++ b/apps/portal/src/components/AI/chat.tsx @@ -1,6 +1,10 @@ "use client"; -import { useMutation } from "@tanstack/react-query"; +import { + QueryClient, + QueryClientProvider, + useMutation, +} from "@tanstack/react-query"; import { ArrowUpIcon, ThumbsDownIcon, @@ -41,6 +45,8 @@ const predefinedPrompts = [ "How do I send a transaction in Unity?", ]; +const queryClient = new QueryClient(); + // Empty State Component function ChatEmptyState({ onPromptClick, @@ -156,13 +162,16 @@ export function Chat() { [conversationId, posthog], ); + const lastMessageLength = messages[messages.length - 1]?.content.length ?? 0; + + // biome-ignore lint/correctness/useExhaustiveDependencies: need both the number of messages and the last message length to trigger the scroll useEffect(() => { if (scrollAnchorRef.current && messages.length > 0) { scrollAnchorRef.current.scrollIntoView({ behavior: "smooth", }); } - }, [messages.length]); + }, [messages.length, lastMessageLength]); const handleInputChange = (e: ChangeEvent) => { setInput(e.target.value); @@ -178,57 +187,59 @@ export function Chat() { }; return ( -
- -
- {messages.length === 0 ? ( - - ) : ( - -
- {messages.map((message) => ( - - ))} -
-
- - )} -
+ +
+ +
+ {messages.length === 0 ? ( + + ) : ( + +
+ {messages.map((message) => ( + + ))} +
+
+ + )} +
-
- - +
+ + +
-
+ ); } @@ -272,7 +283,7 @@ function RenderAIResponse(props: { return (
{aiIcon} -
+
{userIcon} -
+
{aiIcon} - +
+ +
); } @@ -371,7 +384,7 @@ function StyledMarkdownRenderer(props: { }) { return (
- {getCodeSnippet(selectedAuth, platform.id).map((code) => ( - } - className="text-sm" - /> - ))} + {getCodeSnippet(selectedAuth, platform.id).map( + (code, i) => ( + } + className="text-sm" + /> + ), + )}