Skip to content

Commit 9b58a08

Browse files
committed
[Dashboard] Nebula - Update context filters + Add OG image (#5736)
## Problem solved DASH-615 <!-- start pr-codex --> --- ## PR-Codex overview This PR primarily focuses on enhancing the context filter functionality within the application by introducing support for `walletAddresses`, updating related types, and modifying components to accommodate these changes. ### Detailed summary - Added `walletAddresses` to various types and API parameters. - Updated `SessionContextFilter` to include `wallet_addresses`. - Modified `ChatPageContent` to handle and display `walletAddresses`. - Enhanced `ContextFiltersButton` to show wallet address badges. - Updated form handling to include validation for `walletAddresses`. > ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}` <!-- end pr-codex -->
1 parent 22bc557 commit 9b58a08

File tree

7 files changed

+195
-48
lines changed

7 files changed

+195
-48
lines changed

apps/dashboard/src/app/nebula-app/(app)/api/chat.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import type { ExecuteConfig } from "./types";
77
export type ContextFilters = {
88
chainIds?: string[];
99
contractAddresses?: string[];
10+
walletAddresses?: string[];
1011
};
1112

1213
export async function promptNebula(params: {

apps/dashboard/src/app/nebula-app/(app)/api/session.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ export async function createSession(params: {
2727
body.context_filter = {
2828
chain_ids: params.contextFilters.chainIds || [],
2929
contract_addresses: params.contextFilters.contractAddresses || [],
30+
wallet_addresses: params.contextFilters.walletAddresses || [],
3031
};
3132
}
3233

@@ -62,6 +63,7 @@ export async function updateSession(params: {
6263
body.context_filter = {
6364
chain_ids: params.contextFilters.chainIds || [],
6465
contract_addresses: params.contextFilters.contractAddresses || [],
66+
wallet_addresses: params.contextFilters.walletAddresses || [],
6567
};
6668
}
6769

apps/dashboard/src/app/nebula-app/(app)/api/types.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,12 @@ type ClientConfig = {
1919

2020
export type ExecuteConfig = EngineConfig | SessionKeyConfig | ClientConfig;
2121

22+
type SessionContextFilter = {
23+
chain_ids: string[] | null;
24+
contract_addresses: string[] | null;
25+
wallet_addresses: string[] | null;
26+
};
27+
2228
export type SessionInfo = {
2329
id: string;
2430
account_id: string;
@@ -37,10 +43,7 @@ export type SessionInfo = {
3743
archived_at: string | null;
3844
title: string | null;
3945
is_public: boolean | null;
40-
context_filter: {
41-
chain_ids: string[];
42-
contract_addresses: string[];
43-
} | null;
46+
context_filter: SessionContextFilter | null;
4447
// memory
4548
// action: array<object> | null; <-- type of this is not available on https://nebula-api.thirdweb-dev.com/docs#/default/get_session_session__session_id__get
4649
};
@@ -50,10 +53,7 @@ export type UpdatedSessionInfo = {
5053
modal_name: string;
5154
account_id: string;
5255
execute_config: ExecuteConfig | null;
53-
context_filter: {
54-
chain_ids: string[];
55-
contract_addresses: string[];
56-
} | null;
56+
context_filter: SessionContextFilter | null;
5757
};
5858

5959
export type DeletedSessionInfo = {

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

Lines changed: 39 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ import { ScrollShadow } from "@/components/ui/ScrollShadow/ScrollShadow";
55
import { useThirdwebClient } from "@/constants/thirdweb.client";
66
import type { Account } from "@3rdweb-sdk/react/hooks/useApi";
77
import { useMutation } from "@tanstack/react-query";
8-
import { useEffect, useRef, useState } from "react";
8+
import { useEffect, useMemo, useRef, useState } from "react";
9+
import { useActiveAccount } from "thirdweb/react";
910
import { type ContextFilters, promptNebula } from "../api/chat";
1011
import { createSession, updateSession } from "../api/session";
1112
import type { ExecuteConfig, SessionInfo } from "../api/types";
@@ -22,6 +23,7 @@ export function ChatPageContent(props: {
2223
type: "landing" | "new-chat";
2324
account: Account;
2425
}) {
26+
const address = useActiveAccount()?.address;
2527
const client = useThirdwebClient();
2628
const [userHasSubmittedMessage, setUserHasSubmittedMessage] = useState(false);
2729
const [messages, setMessages] = useState<Array<ChatMessage>>(() => {
@@ -36,17 +38,47 @@ export function ChatPageContent(props: {
3638
});
3739

3840
const [_config, setConfig] = useState<ExecuteConfig | null>();
39-
const [contextFilters, setContextFilters] = useState<
41+
const [hasUserUpdatedContextFilters, setHasUserUpdatedContextFilters] =
42+
useState(false);
43+
44+
const [_contextFilters, _setContextFilters] = useState<
4045
ContextFilters | undefined
4146
>(() => {
4247
const contextFilterRes = props.session?.context_filter;
43-
if (contextFilterRes) {
48+
const value: ContextFilters = {
49+
chainIds: contextFilterRes?.chain_ids || undefined,
50+
contractAddresses: contextFilterRes?.contract_addresses || undefined,
51+
walletAddresses: contextFilterRes?.wallet_addresses || undefined,
52+
};
53+
54+
return value;
55+
});
56+
57+
function setContextFilters(filters: ContextFilters | undefined) {
58+
_setContextFilters(filters);
59+
setHasUserUpdatedContextFilters(true);
60+
}
61+
62+
const isNewSession = !props.session;
63+
64+
// if this is a new session, user has not manually updated context filters
65+
// and no wallet address is set in context filters, add the current wallet address
66+
const contextFilters = useMemo(() => {
67+
if (
68+
isNewSession &&
69+
!hasUserUpdatedContextFilters &&
70+
address &&
71+
(!_contextFilters?.walletAddresses ||
72+
_contextFilters.walletAddresses.length === 0)
73+
) {
4474
return {
45-
chainIds: contextFilterRes.chain_ids,
46-
contractAddresses: contextFilterRes.contract_addresses,
75+
..._contextFilters,
76+
walletAddresses: [address],
4777
};
4878
}
49-
});
79+
80+
return _contextFilters;
81+
}, [_contextFilters, address, isNewSession, hasUserUpdatedContextFilters]);
5082

5183
const config = _config || {
5284
mode: "client",
@@ -293,7 +325,7 @@ export function ChatPageContent(props: {
293325

294326
return (
295327
<div className="flex grow flex-col overflow-hidden">
296-
<header className="flex justify-end border-b bg-background p-4">
328+
<header className="flex justify-start border-b bg-background p-4">
297329
<ContextFiltersButton
298330
contextFilters={contextFilters}
299331
setContextFilters={setContextFilters}

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

Lines changed: 54 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,20 +31,70 @@ export const Mobile: Story = {
3131

3232
function Story() {
3333
return (
34-
<div className="container flex max-w-[1000px] flex-col gap-8 lg:p-10">
34+
<div className="container flex max-w-[1000px] flex-col gap-8 py-10 lg:p-10">
3535
<Variant contextFilters={undefined} label="No Filters Set" />
36+
3637
<Variant
3738
contextFilters={{
38-
chainIds: ["1", "2", "3"],
39+
chainIds: ["137"],
3940
}}
40-
label="Chain ids Set"
41+
label="1 chain"
42+
/>
43+
44+
<Variant
45+
contextFilters={{
46+
chainIds: ["137", "10", "421614"],
47+
}}
48+
label="Few chains"
4149
/>
4250

4351
<Variant
4452
contextFilters={{
4553
contractAddresses: ["0x1E51e33F9838A5a043E099C60409f62aA564272f"],
4654
}}
47-
label="Contract addresses set"
55+
label="1 contract"
56+
/>
57+
58+
<Variant
59+
contextFilters={{
60+
contractAddresses: [
61+
"0x1E51e33F9838A5a043E099C60409f62aA564272f",
62+
"0xF61c8d5492139b40af09bDB353733d5F0a348aCf",
63+
],
64+
}}
65+
label="Few contracts"
66+
/>
67+
68+
<Variant
69+
contextFilters={{
70+
walletAddresses: ["0x1F846F6DAE38E1C88D71EAA191760B15f38B7A37"],
71+
}}
72+
label="1 wallet"
73+
/>
74+
75+
<Variant
76+
contextFilters={{
77+
walletAddresses: [
78+
"0x1F846F6DAE38E1C88D71EAA191760B15f38B7A37",
79+
"0x83Dd93fA5D8343094f850f90B3fb90088C1bB425",
80+
],
81+
}}
82+
label="Few wallets"
83+
/>
84+
85+
<Variant
86+
contextFilters={{
87+
chainIds: ["137", "10", "421614"],
88+
contractAddresses: [
89+
"0x1E51e33F9838A5a043E099C60409f62aA564272f",
90+
"0xF61c8d5492139b40af09bDB353733d5F0a348aCf",
91+
],
92+
walletAddresses: [
93+
"0x1F846F6DAE38E1C88D71EAA191760B15f38B7A37",
94+
"0x83Dd93fA5D8343094f850f90B3fb90088C1bB425",
95+
],
96+
}}
97+
label="chains + wallets + contracts"
4898
/>
4999
<Toaster richColors />
50100
</div>

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

Lines changed: 91 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"use client";
22

33
import { Spinner } from "@/components/ui/Spinner/Spinner";
4+
import { Badge } from "@/components/ui/badge";
45
import { Button } from "@/components/ui/button";
56
import {
67
Dialog,
@@ -42,14 +43,46 @@ export default function ContextFiltersButton(props: {
4243
mutationFn: props.updateContextFilters,
4344
});
4445

46+
const chainIds = props.contextFilters?.chainIds;
47+
const contractAddresses = props.contextFilters?.contractAddresses;
48+
const walletAddresses = props.contextFilters?.walletAddresses;
49+
4550
return (
4651
<Dialog open={isOpen} onOpenChange={setIsOpen}>
4752
<DialogTrigger asChild>
48-
<Button size="sm" variant="outline" className="gap-2">
49-
<SlidersHorizontalIcon className="size-4" />
50-
{props.contextFilters
51-
? "Edit Context Filters"
52-
: "Set context filters"}
53+
<Button size="sm" variant="outline" className="max-w-full gap-2">
54+
<SlidersHorizontalIcon className="size-3.5 shrink-0 text-muted-foreground" />
55+
Context Filters
56+
<div className="flex gap-1 overflow-hidden">
57+
{chainIds && chainIds.length > 0 && (
58+
<Badge className="gap-1 truncate">
59+
Chain
60+
<span className="max-sm:hidden">{chainIds.join(", ")}</span>
61+
</Badge>
62+
)}
63+
64+
{contractAddresses && contractAddresses.length > 0 && (
65+
<Badge className="gap-1 truncate">
66+
Contract
67+
<span className="max-sm:hidden">
68+
{contractAddresses
69+
.map((add) => `${add.trim().slice(0, 6)}..`)
70+
.join(", ")}
71+
</span>
72+
</Badge>
73+
)}
74+
75+
{walletAddresses && walletAddresses.length > 0 && (
76+
<Badge className="gap-1 truncate">
77+
Wallet
78+
<span className="max-sm:hidden">
79+
{walletAddresses
80+
.map((add) => `${add.trim().slice(0, 6)}..`)
81+
.join(", ")}
82+
</span>
83+
</Badge>
84+
)}
85+
</div>
5386
</Button>
5487
</DialogTrigger>
5588
<DialogContent className="p-0">
@@ -74,6 +107,18 @@ export default function ContextFiltersButton(props: {
74107
);
75108
}
76109

110+
const commaSeparateListOfAddresses = z.string().refine(
111+
(s) => {
112+
if (s.trim() === "") {
113+
return true;
114+
}
115+
return s.split(",").every((s) => isAddress(s.trim()));
116+
},
117+
{
118+
message: "Must be a comma-separated list of valid addresses",
119+
},
120+
);
121+
77122
const formSchema = z.object({
78123
chainIds: z.string().refine(
79124
(s) => {
@@ -89,18 +134,8 @@ const formSchema = z.object({
89134
message: "Chain IDs must be a comma-separated list of integers",
90135
},
91136
),
92-
contractAddresses: z.string().refine(
93-
(s) => {
94-
if (s.trim() === "") {
95-
return true;
96-
}
97-
return s.trim().split(",").every(isAddress);
98-
},
99-
{
100-
message:
101-
"Contract addresses must be a comma-separated list of valid addresses",
102-
},
103-
),
137+
contractAddresses: commaSeparateListOfAddresses,
138+
walletAddresses: commaSeparateListOfAddresses,
104139
});
105140

106141
function ContextFilterDialogContent(props: {
@@ -117,25 +152,31 @@ function ContextFilterDialogContent(props: {
117152
contractAddresses: props.contextFilters?.contractAddresses
118153
? props.contextFilters.contractAddresses.join(",")
119154
: "",
155+
walletAddresses: props.contextFilters?.walletAddresses
156+
? props.contextFilters.walletAddresses.join(",")
157+
: "",
120158
},
121159
reValidateMode: "onChange",
122160
});
123161

124162
function onSubmit(values: z.infer<typeof formSchema>) {
125-
const { chainIds, contractAddresses } = values;
126-
const chainIdsArray = chainIds.trim().split(",").filter(Boolean);
163+
const { chainIds, contractAddresses, walletAddresses } = values;
164+
165+
const chainIdsArray = chainIds.split(",").filter((id) => id.trim());
166+
127167
const contractAddressesArray = contractAddresses
128-
.trim()
129168
.split(",")
130-
.filter(Boolean);
131-
if (chainIdsArray.length === 0 && contractAddressesArray.length === 0) {
132-
props.updateFilters(undefined);
133-
} else {
134-
props.updateFilters({
135-
chainIds: chainIdsArray,
136-
contractAddresses: contractAddressesArray,
137-
});
138-
}
169+
.filter((v) => v.trim());
170+
171+
const walletAddressesArray = walletAddresses
172+
.split(",")
173+
.filter((v) => v.trim());
174+
175+
props.updateFilters({
176+
chainIds: chainIdsArray,
177+
contractAddresses: contractAddressesArray,
178+
walletAddresses: walletAddressesArray,
179+
});
139180
}
140181

141182
return (
@@ -195,6 +236,27 @@ function ContextFilterDialogContent(props: {
195236
</FormItem>
196237
)}
197238
/>
239+
240+
<FormField
241+
control={form.control}
242+
name="walletAddresses"
243+
render={({ field }) => (
244+
<FormItem>
245+
<FormLabel>Wallet Addresses</FormLabel>
246+
<FormControl>
247+
<AutoResizeTextarea
248+
{...field}
249+
placeholder="0x123..., 0x456..."
250+
className="min-h-[32px] resize-none"
251+
/>
252+
</FormControl>
253+
<FormDescription>
254+
Comma separated list of wallet addresses
255+
</FormDescription>
256+
<FormMessage />
257+
</FormItem>
258+
)}
259+
/>
198260
</div>
199261

200262
<div className="mt-10 flex justify-end gap-3 border-t bg-muted/50 p-6">
Loading

0 commit comments

Comments
 (0)