Skip to content

[Dashboard] Nebula - Update context filters + Add OG image #5736

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Dec 13, 2024
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
1 change: 1 addition & 0 deletions apps/dashboard/src/app/nebula-app/(app)/api/chat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import type { ExecuteConfig } from "./types";
export type ContextFilters = {
chainIds?: string[];
contractAddresses?: string[];
walletAddresses?: string[];
};

export async function promptNebula(params: {
Expand Down
2 changes: 2 additions & 0 deletions apps/dashboard/src/app/nebula-app/(app)/api/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export async function createSession(params: {
body.context_filter = {
chain_ids: params.contextFilters.chainIds || [],
contract_addresses: params.contextFilters.contractAddresses || [],
wallet_addresses: params.contextFilters.walletAddresses || [],
};
}

Expand Down Expand Up @@ -62,6 +63,7 @@ export async function updateSession(params: {
body.context_filter = {
chain_ids: params.contextFilters.chainIds || [],
contract_addresses: params.contextFilters.contractAddresses || [],
wallet_addresses: params.contextFilters.walletAddresses || [],
};
}

Expand Down
16 changes: 8 additions & 8 deletions apps/dashboard/src/app/nebula-app/(app)/api/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,12 @@ type ClientConfig = {

export type ExecuteConfig = EngineConfig | SessionKeyConfig | ClientConfig;

type SessionContextFilter = {
chain_ids: string[] | null;
contract_addresses: string[] | null;
wallet_addresses: string[] | null;
};

export type SessionInfo = {
id: string;
account_id: string;
Expand All @@ -37,10 +43,7 @@ export type SessionInfo = {
archived_at: string | null;
title: string | null;
is_public: boolean | null;
context_filter: {
chain_ids: string[];
contract_addresses: string[];
} | null;
context_filter: SessionContextFilter | null;
// memory
// 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
};
Expand All @@ -50,10 +53,7 @@ export type UpdatedSessionInfo = {
modal_name: string;
account_id: string;
execute_config: ExecuteConfig | null;
context_filter: {
chain_ids: string[];
contract_addresses: string[];
} | null;
context_filter: SessionContextFilter | null;
};

export type DeletedSessionInfo = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ import { ScrollShadow } from "@/components/ui/ScrollShadow/ScrollShadow";
import { useThirdwebClient } from "@/constants/thirdweb.client";
import type { Account } from "@3rdweb-sdk/react/hooks/useApi";
import { useMutation } from "@tanstack/react-query";
import { useEffect, useRef, useState } from "react";
import { useEffect, useMemo, useRef, useState } from "react";
import { useActiveAccount } from "thirdweb/react";
import { type ContextFilters, promptNebula } from "../api/chat";
import { createSession, updateSession } from "../api/session";
import type { ExecuteConfig, SessionInfo } from "../api/types";
Expand All @@ -22,6 +23,7 @@ export function ChatPageContent(props: {
type: "landing" | "new-chat";
account: Account;
}) {
const address = useActiveAccount()?.address;
const client = useThirdwebClient();
const [userHasSubmittedMessage, setUserHasSubmittedMessage] = useState(false);
const [messages, setMessages] = useState<Array<ChatMessage>>(() => {
Expand All @@ -36,17 +38,47 @@ export function ChatPageContent(props: {
});

const [_config, setConfig] = useState<ExecuteConfig | null>();
const [contextFilters, setContextFilters] = useState<
const [hasUserUpdatedContextFilters, setHasUserUpdatedContextFilters] =
useState(false);

const [_contextFilters, _setContextFilters] = useState<
ContextFilters | undefined
>(() => {
const contextFilterRes = props.session?.context_filter;
if (contextFilterRes) {
const value: ContextFilters = {
chainIds: contextFilterRes?.chain_ids || undefined,
contractAddresses: contextFilterRes?.contract_addresses || undefined,
walletAddresses: contextFilterRes?.wallet_addresses || undefined,
};

return value;
});

function setContextFilters(filters: ContextFilters | undefined) {
_setContextFilters(filters);
setHasUserUpdatedContextFilters(true);
}

const isNewSession = !props.session;

// if this is a new session, user has not manually updated context filters
// and no wallet address is set in context filters, add the current wallet address
const contextFilters = useMemo(() => {
if (
isNewSession &&
!hasUserUpdatedContextFilters &&
address &&
(!_contextFilters?.walletAddresses ||
_contextFilters.walletAddresses.length === 0)
) {
return {
chainIds: contextFilterRes.chain_ids,
contractAddresses: contextFilterRes.contract_addresses,
..._contextFilters,
walletAddresses: [address],
};
}
});

return _contextFilters;
}, [_contextFilters, address, isNewSession, hasUserUpdatedContextFilters]);

const config = _config || {
mode: "client",
Expand Down Expand Up @@ -293,7 +325,7 @@ export function ChatPageContent(props: {

return (
<div className="flex grow flex-col overflow-hidden">
<header className="flex justify-end border-b bg-background p-4">
<header className="flex justify-start border-b bg-background p-4">
<ContextFiltersButton
contextFilters={contextFilters}
setContextFilters={setContextFilters}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,20 +31,70 @@ export const Mobile: Story = {

function Story() {
return (
<div className="container flex max-w-[1000px] flex-col gap-8 lg:p-10">
<div className="container flex max-w-[1000px] flex-col gap-8 py-10 lg:p-10">
<Variant contextFilters={undefined} label="No Filters Set" />

<Variant
contextFilters={{
chainIds: ["1", "2", "3"],
chainIds: ["137"],
}}
label="Chain ids Set"
label="1 chain"
/>

<Variant
contextFilters={{
chainIds: ["137", "10", "421614"],
}}
label="Few chains"
/>

<Variant
contextFilters={{
contractAddresses: ["0x1E51e33F9838A5a043E099C60409f62aA564272f"],
}}
label="Contract addresses set"
label="1 contract"
/>

<Variant
contextFilters={{
contractAddresses: [
"0x1E51e33F9838A5a043E099C60409f62aA564272f",
"0xF61c8d5492139b40af09bDB353733d5F0a348aCf",
],
}}
label="Few contracts"
/>

<Variant
contextFilters={{
walletAddresses: ["0x1F846F6DAE38E1C88D71EAA191760B15f38B7A37"],
}}
label="1 wallet"
/>

<Variant
contextFilters={{
walletAddresses: [
"0x1F846F6DAE38E1C88D71EAA191760B15f38B7A37",
"0x83Dd93fA5D8343094f850f90B3fb90088C1bB425",
],
}}
label="Few wallets"
/>

<Variant
contextFilters={{
chainIds: ["137", "10", "421614"],
contractAddresses: [
"0x1E51e33F9838A5a043E099C60409f62aA564272f",
"0xF61c8d5492139b40af09bDB353733d5F0a348aCf",
],
walletAddresses: [
"0x1F846F6DAE38E1C88D71EAA191760B15f38B7A37",
"0x83Dd93fA5D8343094f850f90B3fb90088C1bB425",
],
}}
label="chains + wallets + contracts"
/>
<Toaster richColors />
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"use client";

import { Spinner } from "@/components/ui/Spinner/Spinner";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import {
Dialog,
Expand Down Expand Up @@ -42,14 +43,46 @@ export default function ContextFiltersButton(props: {
mutationFn: props.updateContextFilters,
});

const chainIds = props.contextFilters?.chainIds;
const contractAddresses = props.contextFilters?.contractAddresses;
const walletAddresses = props.contextFilters?.walletAddresses;

return (
<Dialog open={isOpen} onOpenChange={setIsOpen}>
<DialogTrigger asChild>
<Button size="sm" variant="outline" className="gap-2">
<SlidersHorizontalIcon className="size-4" />
{props.contextFilters
? "Edit Context Filters"
: "Set context filters"}
<Button size="sm" variant="outline" className="max-w-full gap-2">
<SlidersHorizontalIcon className="size-3.5 shrink-0 text-muted-foreground" />
Context Filters
<div className="flex gap-1 overflow-hidden">
{chainIds && chainIds.length > 0 && (
<Badge className="gap-1 truncate">
Chain
<span className="max-sm:hidden">{chainIds.join(", ")}</span>
</Badge>
)}

{contractAddresses && contractAddresses.length > 0 && (
<Badge className="gap-1 truncate">
Contract
<span className="max-sm:hidden">
{contractAddresses
.map((add) => `${add.trim().slice(0, 6)}..`)
.join(", ")}
</span>
</Badge>
)}

{walletAddresses && walletAddresses.length > 0 && (
<Badge className="gap-1 truncate">
Wallet
<span className="max-sm:hidden">
{walletAddresses
.map((add) => `${add.trim().slice(0, 6)}..`)
.join(", ")}
</span>
</Badge>
)}
</div>
</Button>
</DialogTrigger>
<DialogContent className="p-0">
Expand All @@ -74,6 +107,18 @@ export default function ContextFiltersButton(props: {
);
}

const commaSeparateListOfAddresses = z.string().refine(
(s) => {
if (s.trim() === "") {
return true;
}
return s.split(",").every((s) => isAddress(s.trim()));
},
{
message: "Must be a comma-separated list of valid addresses",
},
);

const formSchema = z.object({
chainIds: z.string().refine(
(s) => {
Expand All @@ -89,18 +134,8 @@ const formSchema = z.object({
message: "Chain IDs must be a comma-separated list of integers",
},
),
contractAddresses: z.string().refine(
(s) => {
if (s.trim() === "") {
return true;
}
return s.trim().split(",").every(isAddress);
},
{
message:
"Contract addresses must be a comma-separated list of valid addresses",
},
),
contractAddresses: commaSeparateListOfAddresses,
walletAddresses: commaSeparateListOfAddresses,
});

function ContextFilterDialogContent(props: {
Expand All @@ -117,25 +152,31 @@ function ContextFilterDialogContent(props: {
contractAddresses: props.contextFilters?.contractAddresses
? props.contextFilters.contractAddresses.join(",")
: "",
walletAddresses: props.contextFilters?.walletAddresses
? props.contextFilters.walletAddresses.join(",")
: "",
},
reValidateMode: "onChange",
});

function onSubmit(values: z.infer<typeof formSchema>) {
const { chainIds, contractAddresses } = values;
const chainIdsArray = chainIds.trim().split(",").filter(Boolean);
const { chainIds, contractAddresses, walletAddresses } = values;

const chainIdsArray = chainIds.split(",").filter((id) => id.trim());

const contractAddressesArray = contractAddresses
.trim()
.split(",")
.filter(Boolean);
if (chainIdsArray.length === 0 && contractAddressesArray.length === 0) {
props.updateFilters(undefined);
} else {
props.updateFilters({
chainIds: chainIdsArray,
contractAddresses: contractAddressesArray,
});
}
.filter((v) => v.trim());

const walletAddressesArray = walletAddresses
.split(",")
.filter((v) => v.trim());

props.updateFilters({
chainIds: chainIdsArray,
contractAddresses: contractAddressesArray,
walletAddresses: walletAddressesArray,
});
}

return (
Expand Down Expand Up @@ -195,6 +236,27 @@ function ContextFilterDialogContent(props: {
</FormItem>
)}
/>

<FormField
control={form.control}
name="walletAddresses"
render={({ field }) => (
<FormItem>
<FormLabel>Wallet Addresses</FormLabel>
<FormControl>
<AutoResizeTextarea
{...field}
placeholder="0x123..., 0x456..."
className="min-h-[32px] resize-none"
/>
</FormControl>
<FormDescription>
Comma separated list of wallet addresses
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
</div>

<div className="mt-10 flex justify-end gap-3 border-t bg-muted/50 p-6">
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading