Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .npmrc
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
public-hoist-pattern[]=*import-in-the-middle*
public-hoist-pattern[]=*require-in-the-middle*
public-hoist-pattern[]=*require-in-the-middle*
public-hoist-pattern[]=*pino-pretty*
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export default async function Page(props: {
session={session}
type="new-chat"
account={account}
initialPrompt={undefined}
initialParams={undefined}
/>
);
}
2 changes: 1 addition & 1 deletion apps/dashboard/src/app/nebula-app/(app)/chat/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export default async function Page() {
session={undefined}
type="new-chat"
account={account}
initialPrompt={undefined}
initialParams={undefined}
/>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
<div className="rounded-2xl border border-border bg-card p-2">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -176,10 +187,6 @@ export function ChatPageContent(props: {

const handleSendMessage = useCallback(
async (message: string) => {
if (!address) {
setShowConnectModal(true);
return;
}
setUserHasSubmittedMessage(true);
setMessages((prev) => [
...prev,
Expand Down Expand Up @@ -355,31 +362,27 @@ 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);

// 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,
Expand Down Expand Up @@ -412,7 +415,10 @@ export function ChatPageContent(props: {
<div className="relative flex grow flex-col overflow-hidden rounded-lg pb-6">
{showEmptyState ? (
<div className="fade-in-0 container flex max-w-[800px] grow animate-in flex-col justify-center">
<EmptyStateChatPageContent sendMessage={handleSendMessage} />
<EmptyStateChatPageContent
sendMessage={handleSendMessage}
prefillMessage={props.initialParams?.q}
/>
</div>
) : (
<div className="fade-in-0 relative z-[0] flex max-h-full flex-1 animate-in flex-col overflow-hidden">
Expand All @@ -430,6 +436,7 @@ export function ChatPageContent(props: {

<div className="container max-w-[800px]">
<ChatBar
prefillMessage={undefined}
sendMessage={handleSendMessage}
isChatStreaming={isChatStreaming}
abortChatStream={() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ function Story() {
abortChatStream={() => {}}
isChatStreaming={false}
sendMessage={() => {}}
prefillMessage={undefined}
/>
</BadgeContainer>

Expand All @@ -35,6 +36,16 @@ function Story() {
abortChatStream={() => {}}
isChatStreaming={true}
sendMessage={() => {}}
prefillMessage={undefined}
/>
</BadgeContainer>

<BadgeContainer label="Prefilled Message">
<ChatBar
abortChatStream={() => {}}
isChatStreaming={false}
sendMessage={() => {}}
prefillMessage="This is a prefilled message"
/>
</BadgeContainer>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,26 @@ export default meta;
type Story = StoryObj<typeof meta>;

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 (
<div className="container flex max-w-[800px] grow flex-col justify-center overflow-hidden">
<EmptyStateChatPageContent sendMessage={() => {}} />
<EmptyStateChatPageContent
sendMessage={() => {}}
prefillMessage={props.prefillMessage}
/>
</div>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { ChatBar } from "./ChatBar";

export function EmptyStateChatPageContent(props: {
sendMessage: (message: string) => void;
prefillMessage: string | undefined;
}) {
return (
<div className="py-10 lg:py-16">
Expand All @@ -33,6 +34,7 @@ export function EmptyStateChatPageContent(props: {
abortChatStream={() => {
// the page will switch so, no need to handle abort here
}}
prefillMessage={props.prefillMessage}
/>
<div className="h-5" />
<div className="flex flex-wrap justify-center gap-2.5">
Expand Down
55 changes: 49 additions & 6 deletions apps/dashboard/src/app/nebula-app/(app)/page.tsx
Original file line number Diff line number Diff line change
@@ -1,31 +1,74 @@
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";
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 (
<ChatPageContent
authToken={authToken}
session={undefined}
type="landing"
account={account}
initialPrompt={searchParams.prompt}
initialParams={{
q: typeof searchParams.q === "string" ? searchParams.q : undefined,
chainIds: chainIds,
wallet:
typeof searchParams.wallet === "string" &&
isAddress(searchParams.wallet)
? searchParams.wallet
: undefined,
}}
/>
);
}

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
},
);
34 changes: 30 additions & 4 deletions apps/dashboard/src/app/nebula-app/login/NebulaLoginPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<string | undefined>(undefined);
const [message, setMessage] = useState<string | undefined>(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 (
<div className="relative flex min-h-dvh flex-col overflow-hidden bg-background">
{/* nav */}
Expand Down Expand Up @@ -55,15 +82,14 @@ export function NebulaLoginPage(props: {
<LoginAndOnboardingPageContent
loginWithInAppWallet={false}
account={props.account}
redirectPath={
message ? `/?prompt=${encodeURIComponent(message)}` : "/"
}
redirectPath={`/?${redirectPathParams}`}
/>
)}

{showPage === "welcome" && (
<div className="container relative flex max-w-[800px] grow flex-col justify-center overflow-hidden rounded-lg pb-6">
<EmptyStateChatPageContent
prefillMessage={message}
sendMessage={(msg) => {
setMessage(msg);
setShowPage("connect");
Expand Down
23 changes: 21 additions & 2 deletions apps/dashboard/src/app/nebula-app/login/page.tsx
Original file line number Diff line number Diff line change
@@ -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 <NebulaLoginPage account={account} />;
return (
<NebulaLoginPage
account={account}
params={{
chain: searchParams.chain,
q: typeof searchParams.q === "string" ? searchParams.q : undefined,
wallet:
typeof searchParams.wallet === "string"
? searchParams.wallet
: undefined,
}}
/>
);
}
Loading
Loading