diff --git a/apps/dashboard/src/app/nebula-app/(app)/chat/history/ChatHistoryPage.tsx b/apps/dashboard/src/app/nebula-app/(app)/chat/history/ChatHistoryPage.tsx index efc24f3caeb..3b303e9c9fe 100644 --- a/apps/dashboard/src/app/nebula-app/(app)/chat/history/ChatHistoryPage.tsx +++ b/apps/dashboard/src/app/nebula-app/(app)/chat/history/ChatHistoryPage.tsx @@ -81,12 +81,12 @@ export function ChatHistoryPageUI(props: { {filteredSessions.length > 0 && ( {filteredSessions.length > 0 && ( -
+
{filteredSessions.map((session) => ( -

{props.session.title}

+
+

+ {props.session.title || "Untitled"} +

Updated{" "} diff --git a/apps/dashboard/src/app/nebula-app/(app)/components/ChatPageContent.tsx b/apps/dashboard/src/app/nebula-app/(app)/components/ChatPageContent.tsx index fa08800b51b..6b61c2e27e2 100644 --- a/apps/dashboard/src/app/nebula-app/(app)/components/ChatPageContent.tsx +++ b/apps/dashboard/src/app/nebula-app/(app)/components/ChatPageContent.tsx @@ -1,7 +1,4 @@ "use client"; - -/* eslint-disable no-restricted-syntax */ -import { ScrollShadow } from "@/components/ui/ScrollShadow/ScrollShadow"; import { useThirdwebClient } from "@/constants/thirdweb.client"; import type { Account } from "@3rdweb-sdk/react/hooks/useApi"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; @@ -111,18 +108,8 @@ export function ChatPageContent(props: { [props.type], ); - const messagesEndRef = useRef(null); const [isChatStreaming, setIsChatStreaming] = useState(false); - const [isUserSubmittedMessage, setIsUserSubmittedMessage] = useState(false); - - // biome-ignore lint/correctness/useExhaustiveDependencies: - useEffect(() => { - if (!isUserSubmittedMessage) { - return; - } - - messagesEndRef.current?.scrollIntoView({ behavior: "smooth" }); - }, [messages, isUserSubmittedMessage]); + const [enableAutoScroll, setEnableAutoScroll] = useState(false); const initSession = useCallback(async () => { const session = await createSession({ @@ -148,7 +135,7 @@ export function ChatPageContent(props: { ]); setIsChatStreaming(true); - setIsUserSubmittedMessage(true); + setEnableAutoScroll(true); const abortController = new AbortController(); try { @@ -292,6 +279,7 @@ export function ChatPageContent(props: { }); } finally { setIsChatStreaming(false); + setEnableAutoScroll(false); } }, [ @@ -305,6 +293,8 @@ export function ChatPageContent(props: { ); const hasDoneAutoPrompt = useRef(false); + + // eslint-disable-next-line no-restricted-syntax useEffect(() => { if ( props.initialPrompt && @@ -337,45 +327,40 @@ export function ChatPageContent(props: { }} /> -

+
{showEmptyState ? ( -
+
) : (
- - - {/* Scroll anchor */} -
- - - { - chatAbortController?.abort(); - setChatAbortController(undefined); - setIsChatStreaming(false); - // if last message is presence, remove it - if (messages[messages.length - 1]?.type === "presence") { - setMessages((prev) => prev.slice(0, -1)); - } - }} + authToken={props.authToken} + sessionId={sessionId} + className="min-w-0 pt-10 pb-32" + twAccount={props.account} + client={client} + enableAutoScroll={enableAutoScroll} + setEnableAutoScroll={setEnableAutoScroll} /> + +
+ { + chatAbortController?.abort(); + setChatAbortController(undefined); + setIsChatStreaming(false); + // if last message is presence, remove it + if (messages[messages.length - 1]?.type === "presence") { + setMessages((prev) => prev.slice(0, -1)); + } + }} + /> +
)} diff --git a/apps/dashboard/src/app/nebula-app/(app)/components/Chats.tsx b/apps/dashboard/src/app/nebula-app/(app)/components/Chats.tsx index 082b1660468..2d42c17f3a5 100644 --- a/apps/dashboard/src/app/nebula-app/(app)/components/Chats.tsx +++ b/apps/dashboard/src/app/nebula-app/(app)/components/Chats.tsx @@ -1,7 +1,9 @@ import { GradientAvatar } from "@/components/blocks/Avatars/GradientAvatar"; +import { ScrollShadow } from "@/components/ui/ScrollShadow/ScrollShadow"; import { Spinner } from "@/components/ui/Spinner/Spinner"; import { Alert, AlertTitle } from "@/components/ui/alert"; import { Button } from "@/components/ui/button"; +import { getThirdwebClient } from "@/constants/thirdweb.server"; import { cn } from "@/lib/utils"; import type { Account as TWAccount } from "@3rdweb-sdk/react/hooks/useApi"; import { useMutation } from "@tanstack/react-query"; @@ -12,13 +14,12 @@ import { ThumbsDownIcon, ThumbsUpIcon, } from "lucide-react"; -import { useState } from "react"; +import { useEffect, useRef, useState } from "react"; import { toast } from "sonner"; import type { ThirdwebClient } from "thirdweb"; import { sendTransaction } from "thirdweb"; import { useActiveAccount } from "thirdweb/react"; import type { Account } from "thirdweb/wallets"; -import { getThirdwebClient } from "../../../../@/constants/thirdweb.server"; import { TransactionButton } from "../../../../components/buttons/TransactionButton"; import { MarkdownRenderer } from "../../../../components/contract-components/published-contract/markdown-renderer"; import { useV5DashboardChain } from "../../../../lib/v5-adapter"; @@ -51,97 +52,153 @@ export function Chats(props: { className?: string; twAccount: TWAccount; client: ThirdwebClient; + setEnableAutoScroll: (enable: boolean) => void; + enableAutoScroll: boolean; }) { + const { messages, setEnableAutoScroll, enableAutoScroll } = props; + const scrollAnchorRef = useRef(null); + const chatContainerRef = useRef(null); + + // auto scroll to bottom when messages change + // eslint-disable-next-line no-restricted-syntax + useEffect(() => { + if (!enableAutoScroll || messages.length === 0) { + return; + } + + scrollAnchorRef.current?.scrollIntoView({ behavior: "smooth" }); + }, [messages, enableAutoScroll]); + + // stop auto scrolling when user interacts with chat + // eslint-disable-next-line no-restricted-syntax + useEffect(() => { + if (!enableAutoScroll) { + return; + } + + const chatScrollContainer = + chatContainerRef.current?.querySelector("[data-scrollable]"); + + if (!chatScrollContainer) { + return; + } + + const disableScroll = () => { + setEnableAutoScroll(false); + chatScrollContainer.removeEventListener("mousedown", disableScroll); + chatScrollContainer.removeEventListener("wheel", disableScroll); + }; + + chatScrollContainer.addEventListener("mousedown", disableScroll); + chatScrollContainer.addEventListener("wheel", disableScroll); + }, [setEnableAutoScroll, enableAutoScroll]); + return ( -
- {props.messages.map((message, index) => { - const isMessagePending = - props.isChatStreaming && index === props.messages.length - 1; - return ( -
-
-
- {message.type === "user" ? ( - - ) : ( +
+ +
+
+ {props.messages.map((message, index) => { + const isMessagePending = + props.isChatStreaming && index === props.messages.length - 1; + return ( +
- {message.type === "presence" && ( - - )} +
+ {message.type === "user" ? ( + + ) : ( +
+ {message.type === "presence" && ( + + )} - {message.type === "assistant" && ( - - )} + {message.type === "assistant" && ( + + )} - {message.type === "error" && ( - - )} -
- )} -
-
- {message.type === "assistant" ? ( - - ) : message.type === "error" ? ( - - {message.text} - - ) : message.type === "send_transaction" ? ( - - ) : ( - {message.text} - )} + {message.type === "error" && ( + + )} +
+ )} +
+
+ {message.type === "assistant" ? ( + + ) : message.type === "error" ? ( + + {message.text} + + ) : message.type === "send_transaction" ? ( + + ) : ( + {message.text} + )} - {message.type === "assistant" && - !props.isChatStreaming && - props.sessionId && - message.request_id && ( - - )} -
-
+ {message.type === "assistant" && + !props.isChatStreaming && + props.sessionId && + message.request_id && ( + + )} +
+
+
+ ); + })} +
- ); - })} +
+
); } diff --git a/apps/dashboard/src/app/nebula-app/[...not-found]/page.tsx b/apps/dashboard/src/app/nebula-app/[...not-found]/page.tsx new file mode 100644 index 00000000000..ff297366998 --- /dev/null +++ b/apps/dashboard/src/app/nebula-app/[...not-found]/page.tsx @@ -0,0 +1,67 @@ +import { TrackedLinkTW } from "@/components/ui/tracked-link"; + +export default function NebulaNotFound() { + return ( +
+
+
+
+ 404 +
+ +
+ +

+ Uh oh. + Looks like Nebula + can't be found here. +

+ +
+ +
+

+ Go to{" "} + + homepage + +

+
+ + +
+
+
+ ); +} + +type AuroraProps = { + size: { width: string; height: string }; + pos: { top: string; left: string }; + color: string; +}; + +const Aurora: React.FC = ({ color, pos, size }) => { + return ( +
+ ); +}; diff --git a/apps/dashboard/src/components/notices/AnnouncementBanner.tsx b/apps/dashboard/src/components/notices/AnnouncementBanner.tsx index 74e44a0975c..c5cd7948f7a 100644 --- a/apps/dashboard/src/components/notices/AnnouncementBanner.tsx +++ b/apps/dashboard/src/components/notices/AnnouncementBanner.tsx @@ -15,6 +15,7 @@ function AnnouncementBanner(props: { useLocalStorage(`dismissed-${props.trackingLabel}`, false, true); if ( + layoutSegment === "/_not-found" || hasDismissedAnnouncement || layoutSegment === "login" || layoutSegment === "nebula-app"