diff --git a/.npmrc b/.npmrc
index 7d8c50db0f7..c63239aaee1 100644
--- a/.npmrc
+++ b/.npmrc
@@ -1,2 +1,3 @@
public-hoist-pattern[]=*import-in-the-middle*
-public-hoist-pattern[]=*require-in-the-middle*
\ No newline at end of file
+public-hoist-pattern[]=*require-in-the-middle*
+public-hoist-pattern[]=*pino-pretty*
diff --git a/apps/dashboard/src/app/nebula-app/(app)/chat/[session_id]/page.tsx b/apps/dashboard/src/app/nebula-app/(app)/chat/[session_id]/page.tsx
index 75d0c4f5de3..cf321b43ef3 100644
--- a/apps/dashboard/src/app/nebula-app/(app)/chat/[session_id]/page.tsx
+++ b/apps/dashboard/src/app/nebula-app/(app)/chat/[session_id]/page.tsx
@@ -34,7 +34,7 @@ export default async function Page(props: {
session={session}
type="new-chat"
account={account}
- initialPrompt={undefined}
+ initialParams={undefined}
/>
);
}
diff --git a/apps/dashboard/src/app/nebula-app/(app)/chat/page.tsx b/apps/dashboard/src/app/nebula-app/(app)/chat/page.tsx
index 7132b6d949e..a1e2c71f40b 100644
--- a/apps/dashboard/src/app/nebula-app/(app)/chat/page.tsx
+++ b/apps/dashboard/src/app/nebula-app/(app)/chat/page.tsx
@@ -17,7 +17,7 @@ export default async function Page() {
session={undefined}
type="new-chat"
account={account}
- initialPrompt={undefined}
+ initialParams={undefined}
/>
);
}
diff --git a/apps/dashboard/src/app/nebula-app/(app)/components/ChatBar.tsx b/apps/dashboard/src/app/nebula-app/(app)/components/ChatBar.tsx
index c122d5d24e0..e65f25c66c2 100644
--- a/apps/dashboard/src/app/nebula-app/(app)/components/ChatBar.tsx
+++ b/apps/dashboard/src/app/nebula-app/(app)/components/ChatBar.tsx
@@ -10,8 +10,9 @@ export function ChatBar(props: {
sendMessage: (message: string) => void;
isChatStreaming: boolean;
abortChatStream: () => void;
+ prefillMessage: string | undefined;
}) {
- const [message, setMessage] = useState("");
+ const [message, setMessage] = useState(props.prefillMessage || "");
return (
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 5f9b74316aa..e34db3c2e40 100644
--- a/apps/dashboard/src/app/nebula-app/(app)/components/ChatPageContent.tsx
+++ b/apps/dashboard/src/app/nebula-app/(app)/components/ChatPageContent.tsx
@@ -29,7 +29,13 @@ export function ChatPageContent(props: {
authToken: string;
type: "landing" | "new-chat";
account: Account;
- initialPrompt: string | undefined;
+ initialParams:
+ | {
+ q: string | undefined;
+ chainIds: number[];
+ wallet: string | undefined;
+ }
+ | undefined;
}) {
const address = useActiveAccount()?.address;
const client = useThirdwebClient(props.authToken);
@@ -85,8 +91,12 @@ export function ChatPageContent(props: {
>(() => {
const contextRes = props.session?.context;
const value: NebulaContext = {
- chainIds: contextRes?.chain_ids || null,
- walletAddress: contextRes?.wallet_address || null,
+ chainIds:
+ contextRes?.chain_ids ||
+ props.initialParams?.chainIds.map((x) => x.toString()) ||
+ [],
+ walletAddress:
+ contextRes?.wallet_address || props.initialParams?.wallet || null,
};
return value;
@@ -118,8 +128,9 @@ export function ChatPageContent(props: {
walletAddress: null,
};
- // Only set wallet address from connected wallet
- updatedContextFilters.walletAddress = address || null;
+ if (!updatedContextFilters.walletAddress && address) {
+ updatedContextFilters.walletAddress = address;
+ }
// if we have last used chains in storage, continue using them
try {
@@ -176,10 +187,6 @@ export function ChatPageContent(props: {
const handleSendMessage = useCallback(
async (message: string) => {
- if (!address) {
- setShowConnectModal(true);
- return;
- }
setUserHasSubmittedMessage(true);
setMessages((prev) => [
...prev,
@@ -355,14 +362,7 @@ export function ChatPageContent(props: {
setEnableAutoScroll(false);
}
},
- [
- sessionId,
- contextFilters,
- props.authToken,
- messages.length,
- initSession,
- address,
- ],
+ [sessionId, contextFilters, props.authToken, messages.length, initSession],
);
const hasDoneAutoPrompt = useRef(false);
@@ -370,16 +370,19 @@ export function ChatPageContent(props: {
// eslint-disable-next-line no-restricted-syntax
useEffect(() => {
if (
- props.initialPrompt &&
+ props.initialParams?.q &&
messages.length === 0 &&
!hasDoneAutoPrompt.current
) {
hasDoneAutoPrompt.current = true;
- handleSendMessage(props.initialPrompt);
+ handleSendMessage(props.initialParams.q);
}
- }, [props.initialPrompt, messages.length, handleSendMessage]);
+ }, [props.initialParams?.q, messages.length, handleSendMessage]);
- const showEmptyState = !userHasSubmittedMessage && messages.length === 0;
+ const showEmptyState =
+ !userHasSubmittedMessage &&
+ messages.length === 0 &&
+ !props.initialParams?.q;
const handleUpdateContextFilters = async (
values: NebulaContext | undefined,
@@ -412,7 +415,10 @@ export function ChatPageContent(props: {
{showEmptyState ? (
-
+
) : (
@@ -430,6 +436,7 @@ export function ChatPageContent(props: {
{
diff --git a/apps/dashboard/src/app/nebula-app/(app)/components/Chatbar.stories.tsx b/apps/dashboard/src/app/nebula-app/(app)/components/Chatbar.stories.tsx
index 08fd722963e..de76c613283 100644
--- a/apps/dashboard/src/app/nebula-app/(app)/components/Chatbar.stories.tsx
+++ b/apps/dashboard/src/app/nebula-app/(app)/components/Chatbar.stories.tsx
@@ -27,6 +27,7 @@ function Story() {
abortChatStream={() => {}}
isChatStreaming={false}
sendMessage={() => {}}
+ prefillMessage={undefined}
/>
@@ -35,6 +36,16 @@ function Story() {
abortChatStream={() => {}}
isChatStreaming={true}
sendMessage={() => {}}
+ prefillMessage={undefined}
+ />
+
+
+
+ {}}
+ isChatStreaming={false}
+ sendMessage={() => {}}
+ prefillMessage="This is a prefilled message"
/>
diff --git a/apps/dashboard/src/app/nebula-app/(app)/components/EmptyStateChatPageContent.stories.tsx b/apps/dashboard/src/app/nebula-app/(app)/components/EmptyStateChatPageContent.stories.tsx
index fe85df5b0c5..d6ab43e2429 100644
--- a/apps/dashboard/src/app/nebula-app/(app)/components/EmptyStateChatPageContent.stories.tsx
+++ b/apps/dashboard/src/app/nebula-app/(app)/components/EmptyStateChatPageContent.stories.tsx
@@ -15,13 +15,26 @@ export default meta;
type Story = StoryObj
;
export const Default: Story = {
- args: {},
+ args: {
+ prefillMessage: undefined,
+ },
+};
+
+export const PrefilledMessage: Story = {
+ args: {
+ prefillMessage: "This is a prefilled message",
+ },
};
-function Story() {
+function Story(props: {
+ prefillMessage: string | undefined;
+}) {
return (
- {}} />
+ {}}
+ prefillMessage={props.prefillMessage}
+ />
);
}
diff --git a/apps/dashboard/src/app/nebula-app/(app)/components/EmptyStateChatPageContent.tsx b/apps/dashboard/src/app/nebula-app/(app)/components/EmptyStateChatPageContent.tsx
index 9dd7dd2d192..763a2b0e42f 100644
--- a/apps/dashboard/src/app/nebula-app/(app)/components/EmptyStateChatPageContent.tsx
+++ b/apps/dashboard/src/app/nebula-app/(app)/components/EmptyStateChatPageContent.tsx
@@ -8,6 +8,7 @@ import { ChatBar } from "./ChatBar";
export function EmptyStateChatPageContent(props: {
sendMessage: (message: string) => void;
+ prefillMessage: string | undefined;
}) {
return (
@@ -33,6 +34,7 @@ export function EmptyStateChatPageContent(props: {
abortChatStream={() => {
// the page will switch so, no need to handle abort here
}}
+ prefillMessage={props.prefillMessage}
/>
diff --git a/apps/dashboard/src/app/nebula-app/(app)/page.tsx b/apps/dashboard/src/app/nebula-app/(app)/page.tsx
index e86bfda456e..ab7064e4ac2 100644
--- a/apps/dashboard/src/app/nebula-app/(app)/page.tsx
+++ b/apps/dashboard/src/app/nebula-app/(app)/page.tsx
@@ -1,3 +1,6 @@
+import { unstable_cache } from "next/cache";
+import { isAddress } from "thirdweb";
+import { fetchChain } from "../../../utils/fetchChain";
import { getValidAccount } from "../../account/settings/getAccount";
import { getAuthToken } from "../../api/lib/getAuthToken";
import { loginRedirect } from "../../login/loginRedirect";
@@ -5,27 +8,67 @@ import { ChatPageContent } from "./components/ChatPageContent";
export default async function Page(props: {
searchParams: Promise<{
- prompt?: string;
+ q?: string | string[];
+ chain?: string | string[];
+ wallet?: string | string[];
}>;
}) {
- const [searchParams, authToken] = await Promise.all([
- props.searchParams,
+ const searchParams = await props.searchParams;
+
+ const [chainIds, authToken, account] = await Promise.all([
+ getChainIds(searchParams.chain),
getAuthToken(),
+ getValidAccount(),
]);
if (!authToken) {
loginRedirect();
}
- const account = await getValidAccount();
-
return (
);
}
+
+const getChainIds = unstable_cache(
+ async (_chainNames: string[] | string | undefined) => {
+ if (!_chainNames) {
+ return [];
+ }
+
+ const chainIds: number[] = [];
+
+ const chainNames =
+ typeof _chainNames === "string" ? [_chainNames] : _chainNames;
+
+ const chainResults = await Promise.allSettled(
+ chainNames.map((x) => fetchChain(x)),
+ );
+
+ for (const chainResult of chainResults) {
+ if (chainResult.status === "fulfilled" && chainResult.value) {
+ chainIds.push(chainResult.value.chainId);
+ }
+ }
+
+ return chainIds;
+ },
+ ["nebula_getChainIds"],
+ {
+ revalidate: 60 * 60 * 24, // 24 hours
+ },
+);
diff --git a/apps/dashboard/src/app/nebula-app/login/NebulaLoginPage.tsx b/apps/dashboard/src/app/nebula-app/login/NebulaLoginPage.tsx
index b2d39305553..f27cc5bfa69 100644
--- a/apps/dashboard/src/app/nebula-app/login/NebulaLoginPage.tsx
+++ b/apps/dashboard/src/app/nebula-app/login/NebulaLoginPage.tsx
@@ -11,11 +11,38 @@ import { LoginAndOnboardingPageContent } from "../../login/LoginPage";
export function NebulaLoginPage(props: {
account: Account | undefined;
+ params: {
+ chain: string | string[] | undefined;
+ q: string | undefined;
+ wallet: string | undefined;
+ };
}) {
- const [message, setMessage] = useState
(undefined);
+ const [message, setMessage] = useState(props.params.q);
const [showPage, setShowPage] = useState<"connect" | "welcome">(
props.account ? "connect" : "welcome",
);
+
+ const redirectPathObj = {
+ chain: props.params.chain,
+ q: message, // don't use props.params.q, because message may be updated by user
+ wallet: props.params.wallet,
+ };
+
+ const redirectPathParams = Object.entries(redirectPathObj)
+ .map(([key, value]) => {
+ if (!value) {
+ return "";
+ }
+
+ if (Array.isArray(value)) {
+ return value.map((v) => `${key}=${encodeURIComponent(v)}`).join("&");
+ }
+
+ return `${key}=${encodeURIComponent(value)}`;
+ })
+ .filter((v) => v !== "")
+ .join("&");
+
return (
{/* nav */}
@@ -55,15 +82,14 @@ export function NebulaLoginPage(props: {
)}
{showPage === "welcome" && (
{
setMessage(msg);
setShowPage("connect");
diff --git a/apps/dashboard/src/app/nebula-app/login/page.tsx b/apps/dashboard/src/app/nebula-app/login/page.tsx
index 2e9d930936c..4a31ccfd4d5 100644
--- a/apps/dashboard/src/app/nebula-app/login/page.tsx
+++ b/apps/dashboard/src/app/nebula-app/login/page.tsx
@@ -1,8 +1,27 @@
import { getRawAccount } from "../../account/settings/getAccount";
import { NebulaLoginPage } from "./NebulaLoginPage";
-export default async function NebulaLogin() {
+export default async function NebulaLogin(props: {
+ searchParams: Promise<{
+ chain?: string | string[];
+ q?: string | string[];
+ wallet?: string | string[];
+ }>;
+}) {
+ const searchParams = await props.searchParams;
const account = await getRawAccount();
- return ;
+ return (
+
+ );
}
diff --git a/apps/dashboard/src/middleware.ts b/apps/dashboard/src/middleware.ts
index ce8f8923fc2..a6b6d08579a 100644
--- a/apps/dashboard/src/middleware.ts
+++ b/apps/dashboard/src/middleware.ts
@@ -32,14 +32,29 @@ export async function middleware(request: NextRequest) {
const subdomain = host?.split(".")[0];
const paths = pathname.slice(1).split("/");
+ const activeAccount = request.cookies.get(COOKIE_ACTIVE_ACCOUNT)?.value;
+ const authCookie = activeAccount
+ ? request.cookies.get(COOKIE_PREFIX_TOKEN + getAddress(activeAccount))
+ : null;
+
// nebula.thirdweb.com -> render page at app/nebula-app
// on vercel preview, the format is nebula---thirdweb-www-git-.thirdweb-preview.com
if (
subdomain &&
(subdomain === "nebula" || subdomain.startsWith("nebula---"))
) {
+ // preserve search params when redirecting to /login page
+ if (!authCookie && paths[0] !== "login") {
+ return redirect(request, "/login", {
+ searchParams: request.nextUrl.searchParams.toString(),
+ });
+ }
+
const newPaths = ["nebula-app", ...paths];
- return rewrite(request, `/${newPaths.join("/")}`, undefined);
+
+ return rewrite(request, `/${newPaths.join("/")}`, {
+ searchParams: request.nextUrl.searchParams.toString(),
+ });
}
// requesting page at app/nebula-app on thirdweb.com -> redirect to nebula.thirdweb.com
@@ -53,10 +68,6 @@ export async function middleware(request: NextRequest) {
}
let cookiesToSet: Record | undefined = undefined;
- const activeAccount = request.cookies.get(COOKIE_ACTIVE_ACCOUNT)?.value;
- const authCookie = activeAccount
- ? request.cookies.get(COOKIE_PREFIX_TOKEN + getAddress(activeAccount))
- : null;
// utm collection
// if user is already signed in - don't bother capturing utm params