From 1250239e9b1d131e17d21db58bd2c741eed1098e Mon Sep 17 00:00:00 2001 From: Daniel Whiffing Date: Fri, 6 Sep 2024 14:50:28 -0400 Subject: [PATCH 1/2] Allows toggle contracts on/off for easier testing --- app/api/chat/route.ts | 4 ++- app/api/execute/route.ts | 4 ++- app/page.tsx | 6 ++-- components/ChatMessageBubble.tsx | 4 +-- components/ChatWindow.tsx | 4 ++- components/ContractItem.tsx | 56 ++++++++++++++++++++++++++++++ components/UploadContractModal.tsx | 34 +----------------- utils/useContracts.tsx | 7 ++++ utils/useLocalStorage.tsx | 46 ++++++++++++++++++++++++ 9 files changed, 123 insertions(+), 42 deletions(-) create mode 100644 components/ContractItem.tsx create mode 100644 utils/useLocalStorage.tsx diff --git a/app/api/chat/route.ts b/app/api/chat/route.ts index 2d848cc..e9f3f75 100644 --- a/app/api/chat/route.ts +++ b/app/api/chat/route.ts @@ -13,7 +13,9 @@ export async function POST(req: NextRequest) { try { const body = await req.json(); const messages = body.messages ?? []; - const contracts = await contractCollection.get(); + const contracts = (await contractCollection.get()).filter( + (c) => !(body.disabledContractKeys ?? []).includes(c.key), + ); const formattedPreviousMessages = messages.slice(0, -1); const currentMessageContent = messages[messages.length - 1].content; const contractAddresses = contracts.map(({ address }) => address); diff --git a/app/api/execute/route.ts b/app/api/execute/route.ts index d20e4e5..a470216 100644 --- a/app/api/execute/route.ts +++ b/app/api/execute/route.ts @@ -25,7 +25,9 @@ export async function POST(req: NextRequest) { // parse contractAddress from toolCall.name; Should be in format `${contractKey}_${functionName}_${overload function index}`` const contractKey = parseInt(toolCall.name.split("_").at(0) as string, 10); - const contracts = await contractCollection.get(); + const contracts = (await contractCollection.get()).filter( + (c) => !(body.disabledContractKeys ?? []).includes(c.key), + ); const contract = contracts.find(({ key }) => contractKey === key); if (!contract) { diff --git a/app/page.tsx b/app/page.tsx index f8904a9..76c237d 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -5,10 +5,8 @@ import { ChatWindow } from "@/components/ChatWindow"; import { useMagic } from "@/components/MagicProvider"; import { LoadingIcon } from "@/components/LoadingIcon"; import { Button } from "@/components/ui/button"; -import { - ContractItem, - UploadContractModal, -} from "@/components/UploadContractModal"; +import { UploadContractModal } from "@/components/UploadContractModal"; +import { ContractItem } from "@/components/ContractItem"; import { useContracts } from "@/utils/useContracts"; import { ScrollArea } from "@/components/ui/scroll-area"; import { EditContractModal } from "@/components/EditContractModal"; diff --git a/components/ChatMessageBubble.tsx b/components/ChatMessageBubble.tsx index 2c2cbef..c4a7fda 100644 --- a/components/ChatMessageBubble.tsx +++ b/components/ChatMessageBubble.tsx @@ -113,8 +113,7 @@ export function ToolCallMessageBubble(props: { message: Message }) { useState(null); const [loading, setLoading] = useState(false); const { didToken } = useMagic(); - const { contracts } = useContracts(); - + const { contracts, disabledKeys } = useContracts(); const { colorClassName, alignmentClassName, icon } = getStyleForRole( props.message.role, ); @@ -139,6 +138,7 @@ export function ToolCallMessageBubble(props: { message: Message }) { body: JSON.stringify({ toolCall, didToken, + disabledContractKeys: disabledKeys, }), }); diff --git a/components/ChatWindow.tsx b/components/ChatWindow.tsx index 75980ba..9a82495 100644 --- a/components/ChatWindow.tsx +++ b/components/ChatWindow.tsx @@ -18,11 +18,12 @@ import { LoadingIcon } from "@/components/LoadingIcon"; import { Label } from "./ui/label"; import { CornerDownLeft, Trash2 } from "lucide-react"; import { ConfirmAlert } from "./ConfirmAlert"; +import { useContracts } from "@/utils/useContracts"; export function ChatWindow(props: { titleText?: string }) { const { titleText } = props; const chatContainerRef = useRef(null); - + const { disabledKeys } = useContracts(); const { messages, input, @@ -32,6 +33,7 @@ export function ChatWindow(props: { titleText?: string }) { isLoading, } = useChat({ api: "api/chat", + body: { disabledContractKeys: disabledKeys }, streamProtocol: "text", onError: (e) => { toast(e.message); diff --git a/components/ContractItem.tsx b/components/ContractItem.tsx new file mode 100644 index 0000000..a0a48ca --- /dev/null +++ b/components/ContractItem.tsx @@ -0,0 +1,56 @@ +import { Pencil, Circle, CircleCheck } from "lucide-react"; +import { CHAINS } from "@/constants"; +import { IContract } from "@/types"; +import { shortenAddress } from "../utils/shortenAddress"; +import { useContracts } from "@/utils/useContracts"; + +export const ContractItem = (props: { + contract: IContract; + onEdit?: (key: number) => void; +}) => { + const { disabledKeys, setDisabledKeys } = useContracts(); + const isDisabled = disabledKeys.includes(props.contract.key); + const DisabledIcon = isDisabled ? Circle : CircleCheck; + return ( +
+
+
+ + {props.contract.name}{" "} + + ({CHAINS[props.contract.chainId]?.name}) + + + + {shortenAddress(props.contract.address)} + + {props.contract.description && ( + + {props.contract.description} + + )} +
+ + {props.onEdit && ( + props.onEdit?.(props.contract.key)} + className="h-4 w-4 cursor-pointer" + /> + )} + + + isDisabled + ? setDisabledKeys( + disabledKeys.filter((k) => k !== props.contract.key), + ) + : setDisabledKeys([...disabledKeys, props.contract.key]) + } + /> +
+
+ ); +}; diff --git a/components/UploadContractModal.tsx b/components/UploadContractModal.tsx index 217018e..affa825 100644 --- a/components/UploadContractModal.tsx +++ b/components/UploadContractModal.tsx @@ -10,7 +10,6 @@ import { DialogTitle, } from "./ui/dialog"; import { Label } from "./ui/label"; -import { Pencil } from "lucide-react"; import { Select, SelectContent, @@ -20,9 +19,8 @@ import { SelectValue, } from "./ui/select"; import { CHAINS } from "@/constants"; -import { IContract, ChainIdEnum } from "@/types"; +import { ChainIdEnum } from "@/types"; import { useContracts } from "../utils/useContracts"; -import { shortenAddress } from "../utils/shortenAddress"; import { Textarea } from "./ui/textarea"; export function UploadContractModal({ @@ -139,36 +137,6 @@ export function UploadContractModal({ ); } -export const ContractItem = (props: { - contract: IContract; - onEdit?: (key: number) => void; -}) => ( -
-
- - {props.contract.name}{" "} - - ({CHAINS[props.contract.chainId]?.name}) - - - - {shortenAddress(props.contract.address)} - - {props.contract.description && ( - - {props.contract.description} - - )} -
- {props.contract.key > -1 && props.onEdit && ( - props.onEdit?.(props.contract.key!)} - className="h-4 w-4 cursor-pointer" - /> - )} -
-); - const ChainSelect = (props: { chainId: ChainIdEnum | -1; setChainId: (chainId: ChainIdEnum) => void; diff --git a/utils/useContracts.tsx b/utils/useContracts.tsx index 66c7e92..1c5d744 100644 --- a/utils/useContracts.tsx +++ b/utils/useContracts.tsx @@ -1,9 +1,14 @@ import useSWR from "swr"; import { IABIFunctionDescription, IContract } from "@/types"; import { useState } from "react"; +import useLocalStorage from "./useLocalStorage"; export const useContracts = () => { const [erroMessage, setErrorMessage] = useState(""); + const [disabledKeys, setDisabledKeys] = useLocalStorage( + "disabled-contracts", + [], + ); const { data: contracts = [], error, @@ -91,6 +96,8 @@ export const useContracts = () => { }; return { + disabledKeys, + setDisabledKeys, contracts, isLoading: !error && !contracts, errorMessage: erroMessage || error?.message || "", diff --git a/utils/useLocalStorage.tsx b/utils/useLocalStorage.tsx new file mode 100644 index 0000000..dad9e39 --- /dev/null +++ b/utils/useLocalStorage.tsx @@ -0,0 +1,46 @@ +import { useEffect, useState } from "react"; + +export default function useLocalStorage( + key: string, + defaultValue: T, +): [T, (value: T) => void] { + const [value, setValue] = useState(defaultValue); + + useEffect(() => { + const item = localStorage.getItem(key); + + if (!item) { + localStorage.setItem(key, JSON.stringify(defaultValue)); + } + + setValue(item ? JSON.parse(item) : defaultValue); + + function handler(e: StorageEvent) { + if (e.key !== key) return; + + const lsi = localStorage.getItem(key); + setValue(JSON.parse(lsi ?? "")); + } + + window.addEventListener("storage", handler); + + return () => { + window.removeEventListener("storage", handler); + }; + }, []); + + const setValueWrap = (value: T) => { + try { + setValue(value); + + localStorage.setItem(key, JSON.stringify(value)); + if (typeof window !== "undefined") { + window.dispatchEvent(new StorageEvent("storage", { key })); + } + } catch (e) { + console.error(e); + } + }; + + return [value, setValueWrap]; +} From 560903066cdabc7e19fd4b1079b93deb748cc9ee Mon Sep 17 00:00:00 2001 From: Daniel Whiffing Date: Fri, 6 Sep 2024 17:11:57 -0400 Subject: [PATCH 2/2] Fixes tool call errors --- utils/generateToolFromABI.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/generateToolFromABI.ts b/utils/generateToolFromABI.ts index 44ba3b4..5a856fe 100644 --- a/utils/generateToolFromABI.ts +++ b/utils/generateToolFromABI.ts @@ -164,7 +164,7 @@ const getToolFunction = // Just to get around TS if (error instanceof TransactionError) { console.error(`${error.constructor.name}:`, error.message); - const transactionHash = error.context.hash; + const transactionHash = error.context?.hash; return JSON.stringify({ message: error.message, status: "failure",