Skip to content

Commit 60a5b7c

Browse files
committed
[NEB-123] Nebula: Allow setting context with search params (#6736)
<!-- ## title your PR with this format: "[SDK/Dashboard/Portal] Feature/Fix: Concise title for the changes" If you did not copy the branch name from Linear, paste the issue tag here (format is TEAM-0000): ## Notes for the reviewer Anything important to call out? Be sure to also clarify these in your comments. ## How to test Unit tests, playground, etc. --> <!-- start pr-codex --> --- ## PR-Codex overview This PR focuses on enhancing the chat functionality by introducing `prefillMessage` support across various components and updating the way parameters are handled in the chat interface. ### Detailed summary - Replaced `initialPrompt` with `initialParams` in chat components. - Added `prefillMessage` prop to `ChatBar`, `EmptyStateChatPageContent`, and related story files. - Updated `NebulaLogin` to handle search parameters. - Modified `ChatPageContent` to utilize `initialParams` for message handling. - Introduced a new function `getChainIds` for fetching chain IDs based on user input. > ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}` <!-- end pr-codex -->
1 parent baf95b9 commit 60a5b7c

File tree

12 files changed

+180
-46
lines changed

12 files changed

+180
-46
lines changed

.npmrc

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
public-hoist-pattern[]=*import-in-the-middle*
2-
public-hoist-pattern[]=*require-in-the-middle*
2+
public-hoist-pattern[]=*require-in-the-middle*
3+
public-hoist-pattern[]=*pino-pretty*

apps/dashboard/src/app/nebula-app/(app)/chat/[session_id]/page.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ export default async function Page(props: {
3434
session={session}
3535
type="new-chat"
3636
account={account}
37-
initialPrompt={undefined}
37+
initialParams={undefined}
3838
/>
3939
);
4040
}

apps/dashboard/src/app/nebula-app/(app)/chat/page.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ export default async function Page() {
1717
session={undefined}
1818
type="new-chat"
1919
account={account}
20-
initialPrompt={undefined}
20+
initialParams={undefined}
2121
/>
2222
);
2323
}

apps/dashboard/src/app/nebula-app/(app)/components/ChatBar.tsx

+2-1
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,9 @@ export function ChatBar(props: {
1010
sendMessage: (message: string) => void;
1111
isChatStreaming: boolean;
1212
abortChatStream: () => void;
13+
prefillMessage: string | undefined;
1314
}) {
14-
const [message, setMessage] = useState("");
15+
const [message, setMessage] = useState(props.prefillMessage || "");
1516

1617
return (
1718
<div className="rounded-2xl border border-border bg-card p-2">

apps/dashboard/src/app/nebula-app/(app)/components/ChatPageContent.tsx

+29-22
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,13 @@ export function ChatPageContent(props: {
2929
authToken: string;
3030
type: "landing" | "new-chat";
3131
account: Account;
32-
initialPrompt: string | undefined;
32+
initialParams:
33+
| {
34+
q: string | undefined;
35+
chainIds: number[];
36+
wallet: string | undefined;
37+
}
38+
| undefined;
3339
}) {
3440
const address = useActiveAccount()?.address;
3541
const client = useThirdwebClient(props.authToken);
@@ -85,8 +91,12 @@ export function ChatPageContent(props: {
8591
>(() => {
8692
const contextRes = props.session?.context;
8793
const value: NebulaContext = {
88-
chainIds: contextRes?.chain_ids || null,
89-
walletAddress: contextRes?.wallet_address || null,
94+
chainIds:
95+
contextRes?.chain_ids ||
96+
props.initialParams?.chainIds.map((x) => x.toString()) ||
97+
[],
98+
walletAddress:
99+
contextRes?.wallet_address || props.initialParams?.wallet || null,
90100
};
91101

92102
return value;
@@ -118,8 +128,9 @@ export function ChatPageContent(props: {
118128
walletAddress: null,
119129
};
120130

121-
// Only set wallet address from connected wallet
122-
updatedContextFilters.walletAddress = address || null;
131+
if (!updatedContextFilters.walletAddress && address) {
132+
updatedContextFilters.walletAddress = address;
133+
}
123134

124135
// if we have last used chains in storage, continue using them
125136
try {
@@ -176,10 +187,6 @@ export function ChatPageContent(props: {
176187

177188
const handleSendMessage = useCallback(
178189
async (message: string) => {
179-
if (!address) {
180-
setShowConnectModal(true);
181-
return;
182-
}
183190
setUserHasSubmittedMessage(true);
184191
setMessages((prev) => [
185192
...prev,
@@ -355,31 +362,27 @@ export function ChatPageContent(props: {
355362
setEnableAutoScroll(false);
356363
}
357364
},
358-
[
359-
sessionId,
360-
contextFilters,
361-
props.authToken,
362-
messages.length,
363-
initSession,
364-
address,
365-
],
365+
[sessionId, contextFilters, props.authToken, messages.length, initSession],
366366
);
367367

368368
const hasDoneAutoPrompt = useRef(false);
369369

370370
// eslint-disable-next-line no-restricted-syntax
371371
useEffect(() => {
372372
if (
373-
props.initialPrompt &&
373+
props.initialParams?.q &&
374374
messages.length === 0 &&
375375
!hasDoneAutoPrompt.current
376376
) {
377377
hasDoneAutoPrompt.current = true;
378-
handleSendMessage(props.initialPrompt);
378+
handleSendMessage(props.initialParams.q);
379379
}
380-
}, [props.initialPrompt, messages.length, handleSendMessage]);
380+
}, [props.initialParams?.q, messages.length, handleSendMessage]);
381381

382-
const showEmptyState = !userHasSubmittedMessage && messages.length === 0;
382+
const showEmptyState =
383+
!userHasSubmittedMessage &&
384+
messages.length === 0 &&
385+
!props.initialParams?.q;
383386

384387
const handleUpdateContextFilters = async (
385388
values: NebulaContext | undefined,
@@ -412,7 +415,10 @@ export function ChatPageContent(props: {
412415
<div className="relative flex grow flex-col overflow-hidden rounded-lg pb-6">
413416
{showEmptyState ? (
414417
<div className="fade-in-0 container flex max-w-[800px] grow animate-in flex-col justify-center">
415-
<EmptyStateChatPageContent sendMessage={handleSendMessage} />
418+
<EmptyStateChatPageContent
419+
sendMessage={handleSendMessage}
420+
prefillMessage={props.initialParams?.q}
421+
/>
416422
</div>
417423
) : (
418424
<div className="fade-in-0 relative z-[0] flex max-h-full flex-1 animate-in flex-col overflow-hidden">
@@ -430,6 +436,7 @@ export function ChatPageContent(props: {
430436

431437
<div className="container max-w-[800px]">
432438
<ChatBar
439+
prefillMessage={undefined}
433440
sendMessage={handleSendMessage}
434441
isChatStreaming={isChatStreaming}
435442
abortChatStream={() => {

apps/dashboard/src/app/nebula-app/(app)/components/Chatbar.stories.tsx

+11
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ function Story() {
2727
abortChatStream={() => {}}
2828
isChatStreaming={false}
2929
sendMessage={() => {}}
30+
prefillMessage={undefined}
3031
/>
3132
</BadgeContainer>
3233

@@ -35,6 +36,16 @@ function Story() {
3536
abortChatStream={() => {}}
3637
isChatStreaming={true}
3738
sendMessage={() => {}}
39+
prefillMessage={undefined}
40+
/>
41+
</BadgeContainer>
42+
43+
<BadgeContainer label="Prefilled Message">
44+
<ChatBar
45+
abortChatStream={() => {}}
46+
isChatStreaming={false}
47+
sendMessage={() => {}}
48+
prefillMessage="This is a prefilled message"
3849
/>
3950
</BadgeContainer>
4051
</div>

apps/dashboard/src/app/nebula-app/(app)/components/EmptyStateChatPageContent.stories.tsx

+16-3
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,26 @@ export default meta;
1515
type Story = StoryObj<typeof meta>;
1616

1717
export const Default: Story = {
18-
args: {},
18+
args: {
19+
prefillMessage: undefined,
20+
},
21+
};
22+
23+
export const PrefilledMessage: Story = {
24+
args: {
25+
prefillMessage: "This is a prefilled message",
26+
},
1927
};
2028

21-
function Story() {
29+
function Story(props: {
30+
prefillMessage: string | undefined;
31+
}) {
2232
return (
2333
<div className="container flex max-w-[800px] grow flex-col justify-center overflow-hidden">
24-
<EmptyStateChatPageContent sendMessage={() => {}} />
34+
<EmptyStateChatPageContent
35+
sendMessage={() => {}}
36+
prefillMessage={props.prefillMessage}
37+
/>
2538
</div>
2639
);
2740
}

apps/dashboard/src/app/nebula-app/(app)/components/EmptyStateChatPageContent.tsx

+2
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { ChatBar } from "./ChatBar";
88

99
export function EmptyStateChatPageContent(props: {
1010
sendMessage: (message: string) => void;
11+
prefillMessage: string | undefined;
1112
}) {
1213
return (
1314
<div className="py-10 lg:py-16">
@@ -33,6 +34,7 @@ export function EmptyStateChatPageContent(props: {
3334
abortChatStream={() => {
3435
// the page will switch so, no need to handle abort here
3536
}}
37+
prefillMessage={props.prefillMessage}
3638
/>
3739
<div className="h-5" />
3840
<div className="flex flex-wrap justify-center gap-2.5">
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,74 @@
1+
import { unstable_cache } from "next/cache";
2+
import { isAddress } from "thirdweb";
3+
import { fetchChain } from "../../../utils/fetchChain";
14
import { getValidAccount } from "../../account/settings/getAccount";
25
import { getAuthToken } from "../../api/lib/getAuthToken";
36
import { loginRedirect } from "../../login/loginRedirect";
47
import { ChatPageContent } from "./components/ChatPageContent";
58

69
export default async function Page(props: {
710
searchParams: Promise<{
8-
prompt?: string;
11+
q?: string | string[];
12+
chain?: string | string[];
13+
wallet?: string | string[];
914
}>;
1015
}) {
11-
const [searchParams, authToken] = await Promise.all([
12-
props.searchParams,
16+
const searchParams = await props.searchParams;
17+
18+
const [chainIds, authToken, account] = await Promise.all([
19+
getChainIds(searchParams.chain),
1320
getAuthToken(),
21+
getValidAccount(),
1422
]);
1523

1624
if (!authToken) {
1725
loginRedirect();
1826
}
1927

20-
const account = await getValidAccount();
21-
2228
return (
2329
<ChatPageContent
2430
authToken={authToken}
2531
session={undefined}
2632
type="landing"
2733
account={account}
28-
initialPrompt={searchParams.prompt}
34+
initialParams={{
35+
q: typeof searchParams.q === "string" ? searchParams.q : undefined,
36+
chainIds: chainIds,
37+
wallet:
38+
typeof searchParams.wallet === "string" &&
39+
isAddress(searchParams.wallet)
40+
? searchParams.wallet
41+
: undefined,
42+
}}
2943
/>
3044
);
3145
}
46+
47+
const getChainIds = unstable_cache(
48+
async (_chainNames: string[] | string | undefined) => {
49+
if (!_chainNames) {
50+
return [];
51+
}
52+
53+
const chainIds: number[] = [];
54+
55+
const chainNames =
56+
typeof _chainNames === "string" ? [_chainNames] : _chainNames;
57+
58+
const chainResults = await Promise.allSettled(
59+
chainNames.map((x) => fetchChain(x)),
60+
);
61+
62+
for (const chainResult of chainResults) {
63+
if (chainResult.status === "fulfilled" && chainResult.value) {
64+
chainIds.push(chainResult.value.chainId);
65+
}
66+
}
67+
68+
return chainIds;
69+
},
70+
["nebula_getChainIds"],
71+
{
72+
revalidate: 60 * 60 * 24, // 24 hours
73+
},
74+
);

apps/dashboard/src/app/nebula-app/login/NebulaLoginPage.tsx

+30-4
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,38 @@ import { LoginAndOnboardingPageContent } from "../../login/LoginPage";
1111

1212
export function NebulaLoginPage(props: {
1313
account: Account | undefined;
14+
params: {
15+
chain: string | string[] | undefined;
16+
q: string | undefined;
17+
wallet: string | undefined;
18+
};
1419
}) {
15-
const [message, setMessage] = useState<string | undefined>(undefined);
20+
const [message, setMessage] = useState<string | undefined>(props.params.q);
1621
const [showPage, setShowPage] = useState<"connect" | "welcome">(
1722
props.account ? "connect" : "welcome",
1823
);
24+
25+
const redirectPathObj = {
26+
chain: props.params.chain,
27+
q: message, // don't use props.params.q, because message may be updated by user
28+
wallet: props.params.wallet,
29+
};
30+
31+
const redirectPathParams = Object.entries(redirectPathObj)
32+
.map(([key, value]) => {
33+
if (!value) {
34+
return "";
35+
}
36+
37+
if (Array.isArray(value)) {
38+
return value.map((v) => `${key}=${encodeURIComponent(v)}`).join("&");
39+
}
40+
41+
return `${key}=${encodeURIComponent(value)}`;
42+
})
43+
.filter((v) => v !== "")
44+
.join("&");
45+
1946
return (
2047
<div className="relative flex min-h-dvh flex-col overflow-hidden bg-background">
2148
{/* nav */}
@@ -55,15 +82,14 @@ export function NebulaLoginPage(props: {
5582
<LoginAndOnboardingPageContent
5683
loginWithInAppWallet={false}
5784
account={props.account}
58-
redirectPath={
59-
message ? `/?prompt=${encodeURIComponent(message)}` : "/"
60-
}
85+
redirectPath={`/?${redirectPathParams}`}
6186
/>
6287
)}
6388

6489
{showPage === "welcome" && (
6590
<div className="container relative flex max-w-[800px] grow flex-col justify-center overflow-hidden rounded-lg pb-6">
6691
<EmptyStateChatPageContent
92+
prefillMessage={message}
6793
sendMessage={(msg) => {
6894
setMessage(msg);
6995
setShowPage("connect");
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,27 @@
11
import { getRawAccount } from "../../account/settings/getAccount";
22
import { NebulaLoginPage } from "./NebulaLoginPage";
33

4-
export default async function NebulaLogin() {
4+
export default async function NebulaLogin(props: {
5+
searchParams: Promise<{
6+
chain?: string | string[];
7+
q?: string | string[];
8+
wallet?: string | string[];
9+
}>;
10+
}) {
11+
const searchParams = await props.searchParams;
512
const account = await getRawAccount();
613

7-
return <NebulaLoginPage account={account} />;
14+
return (
15+
<NebulaLoginPage
16+
account={account}
17+
params={{
18+
chain: searchParams.chain,
19+
q: typeof searchParams.q === "string" ? searchParams.q : undefined,
20+
wallet:
21+
typeof searchParams.wallet === "string"
22+
? searchParams.wallet
23+
: undefined,
24+
}}
25+
/>
26+
);
827
}

0 commit comments

Comments
 (0)